Skip to content

Commit 94ce4ac

Browse files
authored
Adds Continuous Integration scripts to the repo. (#17)
* Adds Continuous Integration scripts to the repo. The scripts are in the form of a Python CLI application that can be used to run various tasks like: * starting/stopping the Android emulator * handling build artifacts * running gradle tasks * running test-apps * Add integration tests.
1 parent 9bda0fa commit 94ce4ac

18 files changed

+744
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ captures/
66
local.properties
77
google-services.json
88
/build.gradle
9+
_artifacts

ci/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Continuous Integration
2+
3+
This directory contains tooling used to run Continuous Integration tasks.
4+
5+
## Prerequisites
6+
7+
- Requires python3.5+ and setuptools to be installed.
8+
9+
## Setup
10+
11+
- Optionally create a virtualenv to avoid installing system-wide.
12+
```bash
13+
python3 -m venv ~/.venvs/fireci
14+
source ~/.venvs/fireci/bin/activate
15+
```
16+
- At the root of of the firebase sdk repo, run
17+
```
18+
./ci/fireci/setup.py develop
19+
```
20+
21+
- For usage help, see:
22+
```
23+
fireci --help
24+
```
25+

ci/fireci/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.egg-info
2+
.venv
3+
__pycache__
4+
bdist
5+
dist

ci/fireci/.style.yapf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[style]
2+
based_on_style = chromium

ci/fireci/fireci/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.

ci/fireci/fireci/commands.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import click
16+
import os
17+
18+
from . import gradle
19+
from .internal import ci_command
20+
21+
22+
@click.argument('task', required=True, nargs=-1)
23+
@click.option(
24+
'--gradle-opts',
25+
default='',
26+
help='GRADLE_OPTS passed to the gradle invocation.')
27+
@ci_command('gradle')
28+
def gradle_command(task, gradle_opts):
29+
"""Runs the specified gradle commands."""
30+
gradle.run(*task, gradle_opts=gradle_opts)
31+
32+
33+
@ci_command()
34+
def smoke_tests_experimental():
35+
"""Builds all SDKs in release mode and then tests test-apps against them."""
36+
gradle.run('publishAllToBuildDir')
37+
38+
cwd = os.getcwd()
39+
gradle.run(
40+
'connectedReleaseAndroidTest',
41+
gradle_opts='-Dmaven.repo.local={}'.format(
42+
os.path.join(cwd, 'build', 'm2repository')),
43+
workdir=os.path.join(cwd, 'test-apps'),
44+
)

ci/fireci/fireci/emulator.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
17+
_logger = logging.getLogger('fireci.emulator')
18+
19+
20+
# TODO(vkryachko): start/shutdown the emulator.
21+
class EmulatorHandler:
22+
23+
def __init__(self, artifacts_dir):
24+
self._artifacts_dir = artifacts_dir
25+
26+
def __enter__(self):
27+
_logger.debug('Pretend to start the emulator(TODO)')
28+
29+
def __exit__(self, exception_type, exception_value, traceback):
30+
_logger.debug('Pretend to stop the emulator(TODO)')

ci/fireci/fireci/gradle.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
import os
17+
import subprocess
18+
import sys
19+
20+
_logger = logging.getLogger('fireci.gradle')
21+
22+
ADB_INSTALL_TIMEOUT = '5'
23+
24+
25+
def run(*args, gradle_opts='', workdir=None):
26+
"""Invokes gradle with specified args and gradle_opts."""
27+
new_env = dict(os.environ)
28+
if gradle_opts:
29+
new_env['GRADLE_OPTS'] = gradle_opts
30+
new_env[
31+
'ADB_INSTALL_TIMEOUT'] = ADB_INSTALL_TIMEOUT # 5 minutes, rather than 2 minutes
32+
33+
command = ['./gradlew'] + list(args)
34+
_logger.info('Executing gradle command: "%s" in directory: "%s"',
35+
" ".join(command), workdir if workdir else '.')
36+
return subprocess.check_call(
37+
command,
38+
cwd=workdir,
39+
env=new_env,
40+
)

ci/fireci/fireci/internal.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import click
16+
import contextlib
17+
import functools
18+
import glob
19+
import itertools
20+
import logging
21+
import os
22+
import shutil
23+
24+
from . import emulator
25+
26+
_logger = logging.getLogger('fireci')
27+
28+
29+
def _ensure_dir(directory):
30+
if not os.path.exists(directory):
31+
os.makedirs(directory)
32+
33+
34+
@contextlib.contextmanager
35+
def _artifact_handler(target_directory, artifact_patterns):
36+
_logger.debug(
37+
'Artifacts will be searched for in directories matching {} patterns and placed in {}'
38+
.format(artifact_patterns, target_directory))
39+
try:
40+
yield
41+
finally:
42+
_ensure_dir(target_directory)
43+
paths = itertools.chain(*(glob.iglob(x, recursive=True)
44+
for x in artifact_patterns))
45+
for path in paths:
46+
target_name = os.path.join(target_directory, "_".join(path.split('/')))
47+
if os.path.isdir(path):
48+
shutil.copytree(path, target_name)
49+
else:
50+
shutil.copyfile(path, target_name)
51+
52+
53+
@contextlib.contextmanager
54+
def _emulator_handler(enabled, target_directory):
55+
if not enabled:
56+
yield
57+
return
58+
59+
with emulator.EmulatorHandler(target_directory):
60+
yield
61+
62+
63+
class _CommonOptions:
64+
pass
65+
66+
67+
_pass_options = click.make_pass_decorator(_CommonOptions, ensure=True)
68+
69+
70+
@click.group()
71+
@click.option(
72+
'--artifact-target-dir',
73+
default='_artifacts',
74+
help='Directory where artifacts will be symlinked to.',
75+
type=click.Path(dir_okay=True, resolve_path=True),
76+
)
77+
@click.option(
78+
'--artifact-patterns',
79+
default=('**/build/test-results', '**/build/reports'),
80+
help=
81+
'Shell-style artifact patterns that are symlinked into `artifact-target-dir`.'\
82+
'Can be specified multiple times.',
83+
multiple=True,
84+
type=str,
85+
)
86+
@click.option(
87+
'--with-emulator',
88+
default=False,
89+
help='Specifies whether to start an Android emulator a command executes.',
90+
is_flag=True,
91+
)
92+
@_pass_options
93+
def main(options, **kwargs):
94+
"""Main command group.
95+
96+
Should be the "main" entrypoint of the binary.
97+
"""
98+
for k, v in kwargs.items():
99+
setattr(options, k, v)
100+
101+
102+
def ci_command(name=None):
103+
"""Decorator to use for CI commands.
104+
105+
The differences from the standard @click.command are:
106+
107+
* Allows configuration of artifacts that are uploaded for later viewing in CI.
108+
* Registers the command automatically
109+
110+
:param name: Optional name of the task. Defaults to the function name that is decorated with
111+
this decorator.
112+
"""
113+
114+
def ci_command(f):
115+
116+
@main.command(name=f.__name__ if name is None else name, help=f.__doc__)
117+
@_pass_options
118+
@click.pass_context
119+
def new_func(ctx, options, *args, **kwargs):
120+
with _artifact_handler(options.artifact_target_dir,
121+
options.artifact_patterns), _emulator_handler(
122+
options.with_emulator,
123+
options.artifact_target_dir):
124+
return ctx.invoke(f, *args, **kwargs)
125+
126+
return functools.update_wrapper(new_func, f)
127+
128+
return ci_command

ci/fireci/fireci/main.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
17+
from . import commands
18+
from .internal import main
19+
20+
logging.basicConfig(
21+
format='%(name)s: [%(levelname)s] %(message)s',
22+
level=logging.DEBUG,
23+
)
24+
25+
cli = main

ci/fireci/setup.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright 2018 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import os
18+
from setuptools import find_packages, setup
19+
20+
os.chdir(os.path.abspath(os.path.dirname(__file__)))
21+
22+
requires = []
23+
24+
setup(
25+
name='fireci',
26+
version='0.1',
27+
install_requires=[
28+
'click==6.7',
29+
],
30+
packages=find_packages(exclude=['tests']),
31+
entry_points={
32+
'console_scripts': ['fireci = fireci.main:cli'],
33+
},
34+
)

ci/fireci/tests/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
17+
logging.disable(logging.CRITICAL)

0 commit comments

Comments
 (0)