-
Notifications
You must be signed in to change notification settings - Fork 261
/
emu_docker.py
306 lines (262 loc) · 11.9 KB
/
emu_docker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# Copyright 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Minimal dependency script to create a Dockerfile for a particular combination of emulator and system image."""
import argparse
import itertools
import logging
import os
import sys
import re
import click
import colorlog
import emu
import emu.emu_downloads_menu as emu_downloads_menu
from emu.docker_config import DockerConfig
from emu.cloud_build import cloud_build
from emu.containers.emulator_container import EmulatorContainer
from emu.containers.system_image_container import SystemImageContainer
def list_images(args):
"""Lists all the publicly available system and emlator images."""
emu_downloads_menu.list_all_downloads(args.arm)
def accept_licenses(args):
emu_downloads_menu.accept_licenses(args.accept)
def create_cloud_build_distribuition(args):
cloud_build(args)
def metrics_config(args):
cfg = DockerConfig()
if args.metrics:
cfg.set_collect_metrics(True)
if args.no_metrics:
cfg.set_collect_metrics(False)
if not cfg.decided_on_metrics():
logging.warning(
"Please opt in or out of metrics collection.\n"
"You will receive this warning until an option is selected.\n"
"To opt in or out pass the --metrics or --no-metrics flag\n"
"Note, that metrics will only be collected if you opt in."
)
return cfg
def create_docker_image(args):
"""Create a directory containing all the necessary ingredients to construct a docker image.
Returns the created DockerDevice objects.
"""
cfg = metrics_config(args)
imgzip = [args.imgzip]
if not os.path.exists(imgzip[0]):
imgzip = emu_downloads_menu.find_image(imgzip[0])
emuzip = [args.emuzip]
if emuzip[0] in ["stable", "canary", "all"]:
emuzip = [x.download() for x in emu_downloads_menu.find_emulator(emuzip[0])]
elif re.match(r"\d+", emuzip[0]):
# We must be looking for a build id
logging.info("Treating %s as a build id", emuzip[0])
emuzip = [emu_downloads_menu.download_build(emuzip[0])]
devices = []
logging.info("Using repo %s", args.repo)
for (img, emu) in itertools.product(imgzip, emuzip):
logging.info("Processing %s, %s", img, emu)
sys_docker = SystemImageContainer(img, args.repo)
if not sys_docker.available() and not sys_docker.can_pull():
sys_docker.build(args.dest)
else:
print("No need to build {}, it's already available".format(sys_docker))
if args.push:
sys_docker.push()
if args.sys:
continue
emu_docker = EmulatorContainer(emu, sys_docker, args.repo, cfg.collect_metrics(), args.extra)
emu_docker.build(args.dest)
if args.start:
emu_docker.launch({"5555/tcp": 5555, "8554/tcp": 8554})
if args.push:
emu_docker.push()
devices.append(emu_docker)
return devices
def create_docker_image_interactive(args):
"""Interactively create a docker image by selecting the desired combination from a menu."""
img = emu_downloads_menu.select_image(args.arm) or sys.exit(1)
emulator = emu_downloads_menu.select_emulator() or sys.exit(1)
cfg = DockerConfig()
metrics = False
if not cfg.decided_on_metrics():
cfg.set_collect_metrics(
click.confirm(
"Would you like to help make the emulator better by sending usage statistics to Google upon (graceful) emulator exit?"
)
)
metrics = cfg.collect_metrics()
emu_zip = emulator.download("linux")
logging.info("Processing %s, %s", img, emu)
sys_docker = SystemImageContainer(img, args.repo)
if not sys_docker.available() and not sys_docker.can_pull():
sys_docker.build(args.dest)
emu_docker = EmulatorContainer(emu_zip, sys_docker, args.repo, metrics)
emu_docker.build(args.dest)
if args.start:
emu_docker.launch({"5555/tcp": 5555, "8554/tcp": 8554})
def main():
"""Entry point that parses the argument, and invokes the proper functions."""
parser = argparse.ArgumentParser(
description="List and create emulator docker containers ({}).".format(emu.__version__),
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Set verbose logging")
subparsers = parser.add_subparsers()
list_parser = subparsers.add_parser(
"list", help="list all the available the publicly available emulators and system images."
)
list_parser.add_argument(
"--arm",
action="store_true",
help="Display arm images. Note that arm images are not hardware accelerated and are *extremely* slow.",
)
list_parser.set_defaults(func=list_images)
license_parser = subparsers.add_parser(
"licenses", help="Lists all licenses and gives you a chance to accept or reject them."
)
license_parser.add_argument("--accept", action="store_true", help="Accept all licensens after displaying them.")
license_parser.set_defaults(func=accept_licenses)
create_parser = subparsers.add_parser(
"create",
help="Given an emulator and system image zip file, "
"generates a Docker image comprising complete environment in which the Android Emulator runs. "
"After the Docker image is started up, interaction with the emulator is made possible via port forwarding and ADB, "
"or gRPC and WebRTC.",
)
create_parser.add_argument(
"emuzip",
help="Zipfile containing the a publicly released emulator, or (canary|stable|[0-9]+) to use the latest canary, stable, or build id of the emulator to use. "
"Keep in mind that using a build id can result in downloading an untested pre-release emulator build from the android ci server.",
)
create_parser.add_argument(
"imgzip",
help="Zipfile containing a public system image that should be launched, or a regexp matching the image to retrieve. "
"All the matching images will be selected when using a regex. "
'Use the list command to show all available images. For example "P google_apis_playstore x86_64".',
)
create_parser.add_argument(
"--extra",
default="",
help="Series of additional commands to pass on to the emulator. This *MUST* be the last parameter. "
"For example: --extra -http-proxy http://example.google.com",
nargs=argparse.REMAINDER,
)
create_parser.add_argument(
"--dest", default=os.path.join(os.getcwd(), "src"), help="Destination for the generated docker files"
)
create_parser.add_argument("--tag", default="", help="Docker tag, defaults to the emulator build id")
create_parser.add_argument(
"--repo",
default="us-docker.pkg.dev/android-emulator-268719/images",
help="Repo prefix, for example: us.gcr.io/emu-dev/",
)
create_parser.add_argument(
"--push",
action="store_true",
help="Push the created image to your repository, as marked by the --repo argument.",
)
create_parser.add_argument(
"--gpu", action="store_true", help="Build an image with gpu drivers, providing hardware acceleration"
)
create_parser.add_argument(
"--metrics",
action="store_true",
help="When enabled, the emulator will send usage metrics to Google when the container exists gracefully.",
)
create_parser.add_argument("--no-metrics", action="store_true", help="Disables the collection of usage metrics.")
create_parser.add_argument(
"--start",
action="store_true",
help="Starts the container after creating it. "
"All exposed ports are forwarded, and your private adbkey (if available) is injected but not stored.",
)
create_parser.add_argument("--sys", action="store_true", help="Process system image layer only.")
create_parser.set_defaults(func=create_docker_image)
create_inter = subparsers.add_parser(
"interactive",
help="Interactively select which system image and emulator binary to use when creating a docker container",
)
create_inter.add_argument(
"--extra",
default="",
help="Series of additional commands to pass on to the emulator. "
'For example -turncfg \\"curl -s -X POST https://networktraversal.googleapis.com/v1alpha/iceconfig?key=MySec\\"',
)
create_inter.add_argument(
"--dest", default=os.path.join(os.getcwd(), "src"), help="Destination for the generated docker files"
)
create_inter.add_argument(
"--gpu", action="store_true", help="Build an image with gpu drivers, providing hardware acceleration"
)
create_inter.add_argument(
"--start",
action="store_true",
help="Starts the container after creating it. "
"All exposed ports are forwarded, and your private adbkey (if available) is injected but not stored.",
)
create_inter.add_argument(
"--arm",
action="store_true",
help="Display arm images. Note that arm images are not hardware accelerated and are *extremely* slow.",
)
create_inter.add_argument(
"--repo",
default="us-docker.pkg.dev/android-emulator-268719/images",
help="Repo prefix, for example: us.gcr.io/emu-dev/",
)
create_inter.set_defaults(func=create_docker_image_interactive)
dist_parser = subparsers.add_parser(
"cloud-build",
help="Create a cloud builder distribution. This will create a distribution for publishing container images to a GCE repository."
"This is likely only useful if you are within Google.",
)
dist_parser.add_argument(
"--repo",
default="us-docker.pkg.dev/android-emulator-268719/images",
help="Repo prefix, for example: us.gcr.io/emu-dev/",
)
dist_parser.add_argument(
"--dest", default=os.path.join(os.getcwd(), "src"), help="Destination for the generated docker files"
)
dist_parser.add_argument("--git", action="store_true", help="Create a git commit, and push to destination.")
dist_parser.add_argument(
"--sys", action="store_true", help="Write system image steps, otherwise write emulator steps."
)
dist_parser.add_argument(
"emuzip",
help="Zipfile containing the a publicly released emulator, or (canary|stable|[0-9]+) to use the latest canary, stable, or build id of the emulator to use. "
"Keep in mind that using a build id can result in downloading an untested pre-release emulator build from the android ci server.",
)
dist_parser.add_argument(
"img",
default="P google_apis_playstore x86_64|Q google_apis_playstore x86_64",
help="A regexp matching the image to retrieve. "
"All the matching images will be selected when using a regex. "
'Use the list command to show all available images. For example "P google_apis_playstore x86_64".',
)
dist_parser.set_defaults(func=create_cloud_build_distribuition)
args = parser.parse_args()
# Configure logger.
lvl = logging.DEBUG if args.verbose else logging.WARNING
handler = colorlog.StreamHandler()
handler.setFormatter(colorlog.ColoredFormatter("%(log_color)s%(levelname)s:%(message)s"))
logging.root = colorlog.getLogger("root")
logging.root.addHandler(handler)
logging.root.setLevel(lvl)
if hasattr(args, "func"):
args.func(args)
else:
parser.print_help()
if __name__ == "__main__":
main()