Flutter Engine
The Flutter Engine
android_finder.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
4# for details. All rights reserved. Use of this source code is governed by a
5# BSD-style license that can be found in the LICENSE file.
6"""
7Find an Android device with a given ABI.
8
9The name of the Android device is printed to stdout.
10
11Optionally configure and launch an emulator if there's no existing device for a
12given ABI. Will download and install Android SDK components as needed.
13"""
14
15import optparse
16import os
17import re
18import sys
19import traceback
20import utils
21
22DEBUG = False
23VERBOSE = False
24
25
27 result = optparse.OptionParser()
28 result.add_option(
29 "-a",
30 "--abi",
31 action="store",
32 type="string",
33 help="Desired ABI. armeabi-v7a or x86.")
34 result.add_option(
35 "-b",
36 "--bootstrap",
37 help=
38 'Bootstrap - create an emulator, installing SDK packages if needed.',
39 default=False,
40 action="store_true")
41 result.add_option(
42 "-d",
43 "--debug",
44 help='Turn on debugging diagnostics.',
45 default=False,
46 action="store_true")
47 result.add_option(
48 "-v",
49 "--verbose",
50 help='Verbose output.',
51 default=False,
52 action="store_true")
53 return result
54
55
56def ProcessOptions(options):
57 global DEBUG
58 DEBUG = options.debug
59 global VERBOSE
60 VERBOSE = options.verbose
61 if options.abi is None:
62 sys.stderr.write('--abi not specified.\n')
63 return False
64 return True
65
66
68 """
69 Parse the output of an 'android list sdk' command.
70
71 Return list of (id-num, id-key, type, description).
72 """
73 header_regex = re.compile(
74 r'Packages available for installation or update: \d+\n')
75 packages = re.split(header_regex, text)
76 if len(packages) != 2:
77 raise utils.Error("Could not get a list of packages to install")
78 entry_regex = re.compile(
79 r'^id\: (\d+) or "([^"]*)"\n\s*Type\: ([^\n]*)\n\s*Desc\: (.*)')
80 entries = []
81 for entry in packages[1].split('----------\n'):
82 match = entry_regex.match(entry)
83 if match == None:
84 continue
85 entries.append((int(match.group(1)), match.group(2), match.group(3),
86 match.group(4)))
87 return entries
88
89
92 utils.RunCommand(["android", "list", "sdk", "-a", "-e"]))
93
94
95def AndroidSdkFindPackage(packages, key):
96 """
97 Args:
98 packages: list of (id-num, id-key, type, description).
99 key: (id-key, type, description-prefix).
100 """
101 (key_id, key_type, key_description_prefix) = key
102 for package in packages:
103 (package_num, package_id, package_type, package_description) = package
104 if (package_id == key_id and package_type == key_type and
105 package_description.startswith(key_description_prefix)):
106 return package
107 return None
108
109
110def EnsureSdkPackageInstalled(packages, key):
111 """
112 Makes sure the package with a given key is installed.
113
114 key is (id-key, type, description-prefix)
115
116 Returns True if the package was not already installed.
117 """
118 entry = AndroidSdkFindPackage(packages, key)
119 if entry is None:
120 raise utils.Error("Could not find a package for key %s" % key)
121 packageId = entry[0]
122 if VERBOSE:
123 sys.stderr.write('Checking Android SDK package %s...\n' % str(entry))
124 out = utils.RunCommand(
125 ["android", "update", "sdk", "-a", "-u", "--filter",
126 str(packageId)])
127 return '\nInstalling Archives:\n' in out
128
129
131 packagesForAbi = {
132 'armeabi-v7a': [
133 # The platform needed to install the armeabi ABI system image:
134 ('android-15', 'Platform', 'Android SDK Platform 4.0.3'),
135 # The armeabi-v7a ABI system image:
136 ('sysimg-15', 'SystemImage', 'Android SDK Platform 4.0.3')
137 ],
138 'x86': [
139 # The platform needed to install the x86 ABI system image:
140 ('android-15', 'Platform', 'Android SDK Platform 4.0.3'),
141 # The x86 ABI system image:
142 ('sysimg-15', 'SystemImage', 'Android SDK Platform 4.0.4')
143 ]
144 }
145
146 if abi not in packagesForAbi:
147 raise utils.Error('Unsupported abi %s' % abi)
148 return packagesForAbi[abi]
149
150
152 for package in SdkPackagesForAbi(abi):
153 if package[1] == 'Platform':
154 return package[0]
155
156
158 """Return true if at least one package was not already installed."""
159 abiPackageList = SdkPackagesForAbi(abi)
160 installedSomething = False
161 packages = AndroidListSdk()
162 for package in abiPackageList:
163 installedSomething |= EnsureSdkPackageInstalled(packages, package)
164 return installedSomething
165
166
168 """
169 Parse the output of an 'android list avd' command.
170 Return List of {Name: Path: Target: ABI: Skin: Sdcard:}
171 """
172 text = text.split('Available Android Virtual Devices:\n')[-1]
173 text = text.split(
174 'The following Android Virtual Devices could not be loaded:\n')[0]
175 result = []
176 line_re = re.compile(r'^\s*([^\:]+)\:\s*(.*)$')
177 for chunk in text.split('\n---------\n'):
178 entry = {}
179 for line in chunk.split('\n'):
180 line = line.strip()
181 if len(line) == 0:
182 continue
183 match = line_re.match(line)
184 if match is None:
185 sys.stderr.write('Match fail %s\n' % str(line))
186 continue
187 #raise utils.Error('Match failed')
188 entry[match.group(1)] = match.group(2)
189 if len(entry) > 0:
190 result.append(entry)
191 return result
192
193
195 """Returns a list of available Android Virtual Devices."""
197 utils.RunCommand(["android", "list", "avd"]))
198
199
200def FindAvd(avds, key):
201 for avd in avds:
202 if avd['Name'] == key:
203 return avd
204 return None
205
206
207def CreateAvd(avdName, abi):
208 out = utils.RunCommand([
209 "android", "create", "avd", "--name", avdName, "--target",
210 TargetForAbi(abi), '--abi', abi
211 ],
212 input="no\n")
213 if out.find('Created AVD ') < 0:
214 if VERBOSE:
215 sys.stderr.write('Could not create AVD:\n%s\n' % out)
216 raise utils.Error('Could not create AVD')
217
218
219def AvdExists(avdName):
220 avdList = AndroidListAvd()
221 return FindAvd(avdList, avdName) is not None
222
223
224def EnsureAvdExists(avdName, abi):
225 if AvdExists(avdName):
226 return
227 if VERBOSE:
228 sys.stderr.write('Checking SDK packages...\n')
230 # Installing a new package could have made a previously invalid AVD valid
231 if AvdExists(avdName):
232 return
233 CreateAvd(avdName, abi)
234
235
236def StartEmulator(abi, avdName, pollFn):
237 """
238 Start an emulator for a given abi and svdName.
239
240 Echo the emulator's stderr and stdout output to our stderr.
241
242 Call pollFn repeatedly until it returns False. Leave the emulator running
243 when we return.
244
245 Implementation note: Normally we would call the 'emulator' binary, which
246 is a wrapper that launches the appropriate abi-specific emulator. But there
247 is a bug that causes the emulator to exit immediately with a result code of
248 -11 if run from a ssh shell or a No Machine shell. (And only if called from
249 three levels of nested python scripts.) Calling the ABI-specific versions
250 of the emulator directly works around this bug.
251 """
252 emulatorName = {'x86': 'emulator-x86', 'armeabi-v7a': 'emulator-arm'}[abi]
253 command = [emulatorName, '-avd', avdName, '-no-boot-anim', '-no-window']
255 command,
256 pollFn=pollFn,
257 killOnEarlyReturn=False,
258 outStream=sys.stderr,
259 errStream=sys.stderr)
260
261
263 """Return Dictionary [name] -> status"""
264 text = text.split('List of devices attached')[-1]
265 lines = [line.strip() for line in text.split('\n')]
266 lines = [line for line in lines if len(line) > 0]
267 devices = {}
268 for line in lines:
269 lineItems = line.split('\t')
270 devices[lineItems[0]] = lineItems[1]
271 return devices
272
273
275 return ParseAndroidDevices(utils.RunCommand(["adb", "devices"]))
276
277
279 online = {}
280 for device in devices.keys():
281 status = devices[device]
282 if status != 'offline':
283 online[device] = status
284 return online
285
286
289
290
291def GetAndroidDeviceProperty(device, property):
292 return utils.RunCommand(["adb", "-s", device, "shell", "getprop",
293 property]).strip()
294
295
297 abis = []
298 for property in ['ro.product.cpu.abi', 'ro.product.cpu.abi2']:
299 out = GetAndroidDeviceProperty(device, property)
300 if len(out) > 0:
301 abis.append(out)
302 return abis
303
304
306 for device in GetOnlineAndroidDevices().keys():
307 if abi in GetAndroidDeviceAbis(device):
308 return device
309 return None
310
311
313 script_dir = os.path.dirname(sys.argv[0])
314 dart_root = os.path.realpath(os.path.join(script_dir, '..', '..'))
315 third_party_root = os.path.join(dart_root, 'third_party')
316 android_tools = os.path.join(third_party_root, 'android_tools')
317 android_sdk_root = os.path.join(android_tools, 'sdk')
318 android_sdk_tools = os.path.join(android_sdk_root, 'tools')
319 android_sdk_platform_tools = os.path.join(android_sdk_root,
320 'platform-tools')
321 os.environ['PATH'] = ':'.join(
322 [os.environ['PATH'], android_sdk_tools, android_sdk_platform_tools])
323 # Remove any environment variables that would affect our build.
324 for i in [
325 'ANDROID_NDK_ROOT', 'ANDROID_SDK_ROOT', 'ANDROID_TOOLCHAIN', 'AR',
326 'BUILDTYPE', 'CC', 'CXX', 'GYP_DEFINES', 'LD_LIBRARY_PATH', 'LINK',
327 'MAKEFLAGS', 'MAKELEVEL', 'MAKEOVERRIDES', 'MFLAGS', 'NM'
328 ]:
329 if i in os.environ:
330 del os.environ[i]
331
332
333def FindAndroid(abi, bootstrap):
334 if VERBOSE:
335 sys.stderr.write(
336 'Looking for an Android device running abi %s...\n' % abi)
338 device = FindAndroidRunning(abi)
339 if not device:
340 if bootstrap:
341 if VERBOSE:
342 sys.stderr.write("No emulator found, try to create one.\n")
343 avdName = 'dart-build-%s' % abi
344 EnsureAvdExists(avdName, abi)
345
346 # It takes a while to start up an emulator.
347 # Provide feedback while we wait.
348 pollResult = [None]
349
350 def pollFunction():
351 if VERBOSE:
352 sys.stderr.write('.')
353 pollResult[0] = FindAndroidRunning(abi)
354 # Stop polling once we have our result.
355 return pollResult[0] != None
356
357 StartEmulator(abi, avdName, pollFunction)
358 device = pollResult[0]
359 return device
360
361
362def Main():
363 # Parse options.
364 parser = BuildOptions()
365 (options, args) = parser.parse_args()
366 if not ProcessOptions(options):
367 parser.print_help()
368 return 1
369
370 # If there are additional arguments, report error and exit.
371 if args:
372 parser.print_help()
373 return 1
374
375 try:
376 device = FindAndroid(options.abi, options.bootstrap)
377 if device != None:
378 sys.stdout.write("%s\n" % device)
379 return 0
380 else:
381 if VERBOSE:
382 sys.stderr.write('Could not find device\n')
383 return 2
384 except utils.Error as e:
385 sys.stderr.write("error: %s\n" % e)
386 if DEBUG:
387 traceback.print_exc(file=sys.stderr)
388 return -1
389
390
391if __name__ == '__main__':
392 sys.exit(Main())
def EnsureAndroidSdkPackagesInstalled(abi)
def ParseAndroidDevices(text)
def ParseAndroidListAvdResult(text)
def CreateAvd(avdName, abi)
def GetOnlineAndroidDevices()
def FindAndroidRunning(abi)
def FilterOfflineDevices(devices)
def SdkPackagesForAbi(abi)
def ParseAndroidListSdkResult(text)
def TargetForAbi(abi)
def FindAndroid(abi, bootstrap)
def AndroidSdkFindPackage(packages, key)
def GetAndroidDeviceProperty(device, property)
def StartEmulator(abi, avdName, pollFn)
def GetAndroidDeviceAbis(device)
def EnsureSdkPackageInstalled(packages, key)
def EnsureAvdExists(avdName, abi)
def ProcessOptions(options)
def FindAvd(avds, key)
def AvdExists(avdName)
def RunCommand(command, input=None, pollFn=None, outStream=None, errStream=None, killOnEarlyReturn=True, verbose=False, debug=False, printErrorInfo=False)
Definition: utils.py:160
static SkString join(const CommandLineFlags::StringArray &)
Definition: skpbench.cpp:741