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