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