| #!/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. | |
| The name of the Android device is printed to stdout. | |
| 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.") | |
| 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') | |
| 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). | |
| """ | |
| header_regex = re.compile( | |
| r'Packages available for installation or update: \d+\n') | |
| packages = re.split(header_regex, text) | |
| if len(packages) != 2: | |
| raise utils.Error("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"])) | |
| def AndroidSdkFindPackage(packages, key): | |
| """ | |
| Args: | |
| packages: list of (id-num, id-key, type, description). | |
| key: (id-key, type, description-prefix). | |
| """ | |
| (key_id, key_type, key_description_prefix) = key | |
| for package in packages: | |
| (package_num, package_id, package_type, package_description) = package | |
| if (package_id == key_id and package_type == key_type and | |
| package_description.startswith(key_description_prefix)): | |
| 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 utils.Error("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)) | |
| out = utils.RunCommand( | |
| ["android", "update", "sdk", "-a", "-u", "--filter", | |
| str(packageId)]) | |
| return '\nInstalling Archives:\n' in out | |
| 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 utils.Error('Unsupported abi %s' % abi) | |
| return packagesForAbi[abi] | |
| def TargetForAbi(abi): | |
| for package in SdkPackagesForAbi(abi): | |
| if package[1] == 'Platform': | |
| return package[0] | |
| 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: | |
| sys.stderr.write('Match fail %s\n' % str(line)) | |
| continue | |
| #raise utils.Error('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 utils.Error('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) | |
| 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 No 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] | |
| def FindAndroid(abi, bootstrap): | |
| if VERBOSE: | |
| sys.stderr.write( | |
| 'Looking for an Android device running abi %s...\n' % abi) | |
| AddSdkToolsToPath() | |
| device = FindAndroidRunning(abi) | |
| if not device: | |
| 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 utils.Error as e: | |
| sys.stderr.write("error: %s\n" % e) | |
| if DEBUG: | |
| traceback.print_exc(file=sys.stderr) | |
| return -1 | |
| if __name__ == '__main__': | |
| sys.exit(Main()) |