Chromium Code Reviews| Index: runtime/tools/android_finder.py |
| diff --git a/runtime/tools/android_finder.py b/runtime/tools/android_finder.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..c7e53b95c60050443388ba639a7163fc26583607 |
| --- /dev/null |
| +++ b/runtime/tools/android_finder.py |
| @@ -0,0 +1,368 @@ |
| +#!/usr/bin/env python |
| + |
| +# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| +# for details. All rights reserved. Use of this source code is governed by a |
| +# BSD-style license that can be found in the LICENSE file. |
| + |
| +""" |
| +Find an Android device with a given ABI |
|
ahe
2012/08/30 10:24:10
Missing period.
jackpal
2012/08/30 18:25:20
Done.
|
| + |
| +The name of the Android device is printed to stdout |
|
ahe
2012/08/30 10:24:10
Missing period.
jackpal
2012/08/30 18:25:20
Done.
|
| + |
| +Optionally configure and launch an emulator if there's no existing device for a |
| +given ABI. Will download and install Android SDK components as needed. |
| +""" |
| + |
| +import optparse |
| +import os |
| +import re |
| +import sys |
| +import traceback |
| +import utils |
| + |
| + |
| +DEBUG = False |
| +VERBOSE = False |
| + |
| + |
| +def BuildOptions(): |
| + result = optparse.OptionParser() |
| + result.add_option( |
| + "-a", "--abi", |
| + action="store", type="string", |
| + help="Desired ABI. armeabi-v7a or x86") |
|
ahe
2012/08/30 10:24:10
Missing period.
jackpal
2012/08/30 18:25:20
Done.
|
| + result.add_option( |
| + "-b", "--bootstrap", |
| + help='Bootstrap - create an emulator, installing SDK packages if needed.', |
| + default=False, action="store_true") |
| + result.add_option( |
| + "-d", "--debug", |
| + help='Turn on debugging diagnostics.', |
| + default=False, action="store_true") |
| + result.add_option( |
| + "-v", "--verbose", |
| + help='Verbose output.', |
| + default=False, action="store_true") |
| + return result |
| + |
| + |
| +def ProcessOptions(options): |
| + global DEBUG |
| + DEBUG = options.debug |
| + global VERBOSE |
| + VERBOSE = options.verbose |
| + if options.abi is None: |
| + sys.stderr.write('--abi not specified\n') |
|
ahe
2012/08/30 10:24:10
Missing period.
ahe
2012/08/30 10:24:10
FYI: I just discovered this alternative:
print >>
jackpal
2012/08/30 18:25:20
Interesting! I read on stack overflow that one ben
|
| + return False |
| + return True |
| + |
| + |
| +def ParseAndroidListSdkResult(text): |
| + """ |
| + Parse the output of an 'android list sdk' command. |
| + |
| + Return list of (id-num, id-key, type, description) |
|
ahe
2012/08/30 10:24:10
Missing period.
jackpal
2012/08/30 18:25:20
Done.
|
| + """ |
| + header_regex = re.compile( |
| + r'Packages available for installation or update: \d+\n') |
| + packages = re.split(header_regex, text) |
| + if len(packages) != 2: |
| + raise Exception("Could not get a list of packages to install") |
| + entry_regex = re.compile( |
| + r'^id\: (\d+) or "([^"]*)"\n\s*Type\: ([^\n]*)\n\s*Desc\: (.*)') |
| + entries = [] |
| + for entry in packages[1].split('----------\n'): |
| + match = entry_regex.match(entry) |
| + if match == None: |
| + continue |
| + entries.append((int(match.group(1)), match.group(2), match.group(3), |
| + match.group(4))) |
| + return entries |
| + |
| + |
| +def AndroidListSdk(): |
| + return ParseAndroidListSdkResult(utils.RunCommand( |
| + ["android", "list", "sdk", "-a", "-e"])) |
|
ahe
2012/08/30 10:24:10
I assume you already checked that there is no "mac
jackpal
2012/08/30 18:25:20
Yeah, unfortunately the "android" tool wasn't desi
|
| + |
| + |
| +def AndroidSdkFindPackage(packages, key): |
| + """key is (id-key, type, description-prefix)""" |
|
ahe
2012/08/30 10:24:10
If you want symbolic names instead of indexing, I
jackpal
2012/08/30 18:25:20
Done.
|
| + for package in packages: |
|
ahe
2012/08/30 10:24:10
And here I think you can write this:
for (i, t, d
jackpal
2012/08/30 18:25:20
Well, I want to return the package in line 93, so
|
| + if (package[1] == key[0] and package[2] == key[1] |
| + and package[3].find(key[2]) == 0): |
| + return package |
| + return None |
| + |
| + |
| +def EnsureSdkPackageInstalled(packages, key): |
| + """ |
| + Makes sure the package with a given key is installed. |
| + |
| + key is (id-key, type, description-prefix) |
| + |
| + Returns True if the package was not already installed. |
| + """ |
| + entry = AndroidSdkFindPackage(packages, key) |
| + if entry is None: |
| + raise Exception("Could not find a package for key %s" % key) |
| + packageId = entry[0] |
| + if VERBOSE: |
| + sys.stderr.write('Checking Android SDK package %s...\n' % str(entry)) |
|
ahe
2012/08/30 10:24:10
Why is verbose output written to stderr?
jackpal
2012/08/30 18:25:20
To avoid polluting stdout, which is where we retur
|
| + out = utils.RunCommand( |
| + ["android", "update", "sdk", "-a", "-u", "--filter", str(packageId)]) |
| + return out.find('\nInstalling Archives:\n') >= 0 |
|
ahe
2012/08/30 10:24:10
return '\nInstalling Archives:\n' in out
jackpal
2012/08/30 18:25:20
Sweet!
|
| + |
| + |
| +def SdkPackagesForAbi(abi): |
| + packagesForAbi = { |
| + 'armeabi-v7a': [ |
| + # The platform needed to install the armeabi ABI system image: |
| + ('android-15', 'Platform', 'Android SDK Platform 4.0.3'), |
| + # The armeabi-v7a ABI system image: |
| + ('sysimg-15', 'SystemImage', 'Android SDK Platform 4.0.3') |
| + ], |
| + 'x86': [ |
| + # The platform needed to install the x86 ABI system image: |
| + ('android-15', 'Platform', 'Android SDK Platform 4.0.3'), |
| + # The x86 ABI system image: |
| + ('sysimg-15', 'SystemImage', 'Android SDK Platform 4.0.4') |
| + ] |
| + } |
| + |
| + if abi not in packagesForAbi: |
| + raise Exception('Unsupported abi %s' % abi) |
| + return packagesForAbi[abi] |
| + |
| + |
| +def TargetForAbi(abi): |
| + for package in SdkPackagesForAbi(abi): |
| + if package[1] == 'Platform': |
| + return package[0] |
| + |
|
ahe
2012/08/30 10:24:10
Missing newline.
jackpal
2012/08/30 18:25:20
Done.
|
| +def EnsureAndroidSdkPackagesInstalled(abi): |
| + """Return true if at least one package was not already installed.""" |
| + abiPackageList = SdkPackagesForAbi(abi) |
| + installedSomething = False |
| + packages = AndroidListSdk() |
| + for package in abiPackageList: |
| + installedSomething |= EnsureSdkPackageInstalled(packages, package) |
| + return installedSomething |
| + |
| + |
| +def ParseAndroidListAvdResult(text): |
| + """ |
| + Parse the output of an 'android list avd' command. |
| + Return List of {Name: Path: Target: ABI: Skin: Sdcard:} |
| + """ |
| + text = text.split('Available Android Virtual Devices:\n')[-1] |
| + text = text.split( |
| + 'The following Android Virtual Devices could not be loaded:\n')[0] |
| + result = [] |
| + line_re = re.compile(r'^\s*([^\:]+)\:\s*(.*)$') |
| + for chunk in text.split('\n---------\n'): |
| + entry = {} |
| + for line in chunk.split('\n'): |
| + line = line.strip() |
| + if len(line) == 0: |
| + continue |
| + match = line_re.match(line) |
| + if match is None: |
| + raise Exception('Match failed') |
| + entry[match.group(1)] = match.group(2) |
| + if len(entry) > 0: |
| + result.append(entry) |
| + return result |
| + |
| + |
| +def AndroidListAvd(): |
| + """Returns a list of available Android Virtual Devices.""" |
| + return ParseAndroidListAvdResult(utils.RunCommand(["android", "list", "avd"])) |
| + |
| + |
| +def FindAvd(avds, key): |
| + for avd in avds: |
| + if avd['Name'] == key: |
| + return avd |
| + return None |
| + |
| + |
| +def CreateAvd(avdName, abi): |
| + out = utils.RunCommand(["android", "create", "avd", "--name", avdName, |
| + "--target", TargetForAbi(abi), '--abi', abi], |
| + input="no\n") |
| + if out.find('Created AVD ') < 0: |
| + if VERBOSE: |
| + sys.stderr.write('Could not create AVD:\n%s\n' % out) |
| + raise Exception('Could not create AVD') |
| + |
| + |
| +def AvdExists(avdName): |
| + avdList = AndroidListAvd() |
| + return FindAvd(avdList, avdName) is not None |
| + |
| + |
| +def EnsureAvdExists(avdName, abi): |
| + if AvdExists(avdName): |
| + return |
| + if VERBOSE: |
| + sys.stderr.write('Checking SDK packages...\n') |
| + if EnsureAndroidSdkPackagesInstalled(abi): |
| + # Installing a new package could have made a previously invalid AVD valid |
| + if AvdExists(avdName): |
| + return |
| + CreateAvd(avdName, abi) |
| + |
|
ahe
2012/08/30 10:24:10
Missing newline.
|
| +def dumpenv(map): |
|
ahe
2012/08/30 10:24:10
Inconsistent name.
jackpal
2012/08/30 18:25:20
Oops! - this is unused debugging code, I'll remove
|
| + e = map.keys() |
| + e.sort() |
| + for k in e: |
| + sys.stderr.write("%s: %s\n" % (k, map[k])) |
| + |
| + |
| +def StartEmulator(abi, avdName, pollFn): |
| + """ |
| + Start an emulator for a given abi and svdName. |
| + |
| + Echo the emulator's stderr and stdout output to our stderr. |
| + |
| + Call pollFn repeatedly until it returns False. Leave the emulator running |
| + when we return. |
| + |
| + Implementation note: Normally we would call the 'emulator' binary, which |
| + is a wrapper that launches the appropriate abi-specific emulator. But there |
| + is a bug that causes the emulator to exit immediately with a result code of |
| + -11 if run from a ssh shell or a NX Machine shell. (And only if called from |
| + three levels of nested python scripts.) Calling the ABI-specific versions |
| + of the emulator directly works around this bug. |
| + """ |
| + emulatorName = {'x86' : 'emulator-x86', 'armeabi-v7a': 'emulator-arm'}[abi] |
| + command = [emulatorName, '-avd', avdName, '-no-boot-anim', '-no-window'] |
| + utils.RunCommand(command, pollFn=pollFn, killOnEarlyReturn=False, |
| + outStream=sys.stderr, errStream=sys.stderr) |
| + |
| + |
| +def ParseAndroidDevices(text): |
| + """Return Dictionary [name] -> status""" |
| + text = text.split('List of devices attached')[-1] |
| + lines = [line.strip() for line in text.split('\n')] |
| + lines = [line for line in lines if len(line) > 0] |
| + devices = {} |
| + for line in lines: |
| + lineItems = line.split('\t') |
| + devices[lineItems[0]] = lineItems[1] |
| + return devices |
| + |
| + |
| +def GetAndroidDevices(): |
| + return ParseAndroidDevices(utils.RunCommand(["adb", "devices"])) |
| + |
| + |
| +def FilterOfflineDevices(devices): |
| + online = {} |
| + for device in devices.keys(): |
| + status = devices[device] |
| + if status != 'offline': |
| + online[device] = status |
| + return online |
| + |
| + |
| +def GetOnlineAndroidDevices(): |
| + return FilterOfflineDevices(GetAndroidDevices()) |
| + |
| + |
| +def GetAndroidDeviceProperty(device, property): |
| + return utils.RunCommand( |
| + ["adb", "-s", device, "shell", "getprop", property]).strip() |
| + |
| + |
| +def GetAndroidDeviceAbis(device): |
| + abis = [] |
| + for property in ['ro.product.cpu.abi', 'ro.product.cpu.abi2']: |
| + out = GetAndroidDeviceProperty(device, property) |
| + if len(out) > 0: |
| + abis.append(out) |
| + return abis |
| + |
| + |
| +def FindAndroidRunning(abi): |
| + for device in GetOnlineAndroidDevices().keys(): |
| + if abi in GetAndroidDeviceAbis(device): |
| + return device |
| + return None |
| + |
| + |
| +def AddSdkToolsToPath(): |
| + script_dir = os.path.dirname(sys.argv[0]) |
| + dart_root = os.path.realpath(os.path.join(script_dir, '..', '..')) |
| + third_party_root = os.path.join(dart_root, 'third_party') |
| + android_tools = os.path.join(third_party_root, 'android_tools') |
| + android_sdk_root = os.path.join(android_tools, 'sdk') |
| + android_sdk_tools = os.path.join(android_sdk_root, 'tools') |
| + android_sdk_platform_tools = os.path.join(android_sdk_root, 'platform-tools') |
| + os.environ['PATH'] = ':'.join([ |
| + os.environ['PATH'], android_sdk_tools, android_sdk_platform_tools]) |
| + # Remove any environment variables that would affect our build. |
| + for i in ['ANDROID_NDK_ROOT', 'ANDROID_SDK_ROOT', 'ANDROID_TOOLCHAIN', |
| + 'AR', 'BUILDTYPE', 'CC', 'CXX', 'GYP_DEFINES', |
| + 'LD_LIBRARY_PATH', 'LINK', 'MAKEFLAGS', 'MAKELEVEL', |
| + 'MAKEOVERRIDES', 'MFLAGS', 'NM']: |
| + if i in os.environ: |
| + del os.environ[i] |
| + |
|
ahe
2012/08/30 10:24:10
Missing newline.
jackpal
2012/08/30 18:25:20
Done.
|
| +def FindAndroid(abi, bootstrap): |
| + if VERBOSE: |
| + sys.stderr.write('Looking for an Android device running abi %s...\n' % abi) |
| + AddSdkToolsToPath() |
| + device = FindAndroidRunning(abi) |
| + if device is None: |
|
ahe
2012/08/30 10:24:10
if not device:
jackpal
2012/08/30 18:25:20
Done.
|
| + if bootstrap: |
| + if VERBOSE: |
| + sys.stderr.write("No emulator found, try to create one.\n") |
| + avdName = 'dart-build-%s' % abi |
| + EnsureAvdExists(avdName, abi) |
| + |
| + # It takes a while to start up an emulator. |
| + # Provide feedback while we wait. |
| + pollResult = [None] |
| + def pollFunction(): |
| + if VERBOSE: |
| + sys.stderr.write('.') |
| + pollResult[0] = FindAndroidRunning(abi) |
| + # Stop polling once we have our result. |
| + return pollResult[0] != None |
| + StartEmulator(abi, avdName, pollFunction) |
| + device = pollResult[0] |
| + return device |
| + |
| + |
| +def Main(): |
| + # Parse options. |
| + parser = BuildOptions() |
| + (options, args) = parser.parse_args() |
| + if not ProcessOptions(options): |
| + parser.print_help() |
| + return 1 |
| + |
| + # If there are additional arguments, report error and exit. |
| + if args: |
| + parser.print_help() |
| + return 1 |
| + |
| + try: |
| + device = FindAndroid(options.abi, options.bootstrap) |
| + if device != None: |
| + sys.stdout.write("%s\n" % device) |
| + return 0 |
| + else: |
| + if VERBOSE: |
| + sys.stderr.write('Could not find device\n') |
| + return 2 |
| + except Exception as e: |
|
ahe
2012/08/30 10:24:10
I think this is problematic. It also catches, for
jackpal
2012/08/30 18:25:20
Done.
|
| + sys.stderr.write("error: %s\n" % e) |
| + if DEBUG: |
| + traceback.print_exc(file=sys.stderr) |
| + return -1 |
| + |
| + |
| +if __name__ == '__main__': |
| + sys.exit(Main()) |