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 |