| #!/usr/bin/env python3 | 
 |  | 
 | # 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()) |