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()) |