OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Installs deps for using SDK emulator for testing. | 6 """Installs deps for using SDK emulator for testing. |
7 | 7 |
8 The script will download the SDK and system images, if they are not present, and | 8 The script will download the SDK and system images, if they are not present, and |
9 install and enable KVM, if virtualization has been enabled in the BIOS. | 9 install and enable KVM, if virtualization has been enabled in the BIOS. |
10 """ | 10 """ |
11 | 11 |
12 | 12 |
13 import logging | 13 import logging |
14 import optparse | 14 import optparse |
15 import os | 15 import os |
16 import re | 16 import re |
17 import shutil | |
18 import sys | 17 import sys |
19 | 18 |
20 from devil.utils import cmd_helper | 19 from devil.utils import cmd_helper |
21 from devil.utils import run_tests_helper | 20 from devil.utils import run_tests_helper |
22 from pylib import constants | 21 from pylib import constants |
23 from pylib import pexpect | 22 from pylib import pexpect |
24 | 23 |
25 # Android API level | 24 # Android API level |
26 DEFAULT_ANDROID_API_LEVEL = constants.ANDROID_SDK_VERSION | 25 DEFAULT_ANDROID_API_LEVEL = constants.ANDROID_SDK_VERSION |
27 | 26 |
28 # From the Android Developer's website. | 27 # Default Time out for downloading SDK component |
29 # Keep this up to date; the user can install older API levels as necessary. | 28 DOWNLOAD_SYSTEM_IMAGE_TIMEOUT = 30 |
30 SDK_BASE_URL = 'http://dl.google.com/android/adt' | 29 DOWNLOAD_SDK_PLATFORM_TIMEOUT = 60 |
31 SDK_ZIP = 'adt-bundle-linux-x86_64-20131030.zip' | |
32 | |
33 # pylint: disable=line-too-long | |
34 # Android x86 system image from the Intel website: | |
35 # http://software.intel.com/en-us/articles/intel-eula-x86-android-4-2-jelly-bean -bin | |
36 # These don't exist prior to Android-15. | |
37 # As of 08 Nov 2013, Android-19 is not yet available either. | |
38 X86_IMG_URLS = { | |
39 15: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-15_r01.zi p', | |
40 16: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-16_r01.zi p', | |
41 17: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-17_r01.zi p', | |
42 18: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-18_r01.zi p', | |
43 19: 'https://software.intel.com/sites/landingpage/android/sysimg_x86-19_r01.zi p'} | |
44 #pylint: enable=line-too-long | |
45 | 30 |
46 def CheckSDK(): | 31 def CheckSDK(): |
47 """Check if SDK is already installed. | 32 """Check if SDK is already installed. |
48 | 33 |
49 Returns: | 34 Returns: |
50 True if the emulator SDK directory (src/android_emulator_sdk/) exists. | 35 True if the emulator SDK directory (src/android_emulator_sdk/) exists. |
51 """ | 36 """ |
52 return os.path.exists(constants.EMULATOR_SDK_ROOT) | 37 return os.path.exists(constants.ANDROID_SDK_ROOT) |
53 | 38 |
54 | 39 |
55 def CheckSDKPlatform(api_level=DEFAULT_ANDROID_API_LEVEL): | 40 def CheckSDKPlatform(api_level=DEFAULT_ANDROID_API_LEVEL): |
56 """Check if the "SDK Platform" for the specified API level is installed. | 41 """Check if the "SDK Platform" for the specified API level is installed. |
57 This is necessary in order for the emulator to run when the target | 42 This is necessary in order for the emulator to run when the target |
58 is specified. | 43 is specified. |
59 | 44 |
60 Args: | 45 Args: |
61 api_level: the Android API level to check; defaults to the latest API. | 46 api_level: the Android API level to check; defaults to the latest API. |
62 | 47 |
63 Returns: | 48 Returns: |
64 True if the platform is already installed. | 49 True if the platform is already installed. |
65 """ | 50 """ |
66 android_binary = os.path.join(constants.EMULATOR_SDK_ROOT, | 51 android_binary = os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'android') |
67 'sdk', 'tools', 'android') | |
68 pattern = re.compile('id: [0-9]+ or "android-%d"' % api_level) | 52 pattern = re.compile('id: [0-9]+ or "android-%d"' % api_level) |
69 try: | 53 try: |
70 exit_code, stdout = cmd_helper.GetCmdStatusAndOutput( | 54 exit_code, stdout = cmd_helper.GetCmdStatusAndOutput( |
71 [android_binary, 'list']) | 55 [android_binary, 'list']) |
72 if exit_code != 0: | 56 if exit_code != 0: |
73 raise Exception('\'android list\' command failed') | 57 raise Exception('\'android list\' command failed') |
74 for line in stdout.split('\n'): | 58 for line in stdout.split('\n'): |
75 if pattern.match(line): | 59 if pattern.match(line): |
76 return True | 60 return True |
77 return False | 61 return False |
78 except OSError: | 62 except OSError: |
79 logging.exception('Unable to execute \'android list\'') | 63 logging.exception('Unable to execute \'android list\'') |
80 return False | 64 return False |
81 | 65 |
82 | 66 |
83 def CheckX86Image(api_level=DEFAULT_ANDROID_API_LEVEL): | 67 def CheckX86Image(api_level=DEFAULT_ANDROID_API_LEVEL): |
84 """Check if Android system images have been installed. | 68 """Check if Android system images have been installed. |
85 | 69 |
86 Args: | 70 Args: |
87 api_level: the Android API level to check for; defaults to the latest API. | 71 api_level: the Android API level to check for; defaults to the latest API. |
88 | 72 |
89 Returns: | 73 Returns: |
90 True if sdk/system-images/android-<api_level>/x86 exists inside | 74 True if sdk/system-images/android-<api_level>/default/x86 exists inside |
mikecase (-- gone --)
2015/10/07 17:45:38
Not a very useful comment since it says exactly wh
Yoland Yan(Google)
2015/10/08 01:48:12
Done
| |
91 EMULATOR_SDK_ROOT. | 75 ANDROID_SDK_ROOT. |
92 """ | 76 """ |
93 api_target = 'android-%d' % api_level | 77 api_target = 'android-%d' % api_level |
94 return os.path.exists(os.path.join(constants.EMULATOR_SDK_ROOT, | 78 return os.path.exists(os.path.join(constants.ANDROID_SDK_ROOT, |
95 'sdk', 'system-images', | 79 'system-images', api_target, 'default', |
96 api_target, 'x86')) | 80 'x86')) |
97 | 81 |
98 | 82 |
99 def CheckKVM(): | 83 def CheckKVM(): |
100 """Quickly check whether KVM is enabled. | 84 """Quickly check whether KVM is enabled. |
101 | 85 |
102 Returns: | 86 Returns: |
103 True iff /dev/kvm exists (Linux only). | 87 True iff /dev/kvm exists (Linux only). |
104 """ | 88 """ |
105 return os.path.exists('/dev/kvm') | 89 return os.path.exists('/dev/kvm') |
106 | 90 |
107 | 91 |
108 def RunKvmOk(): | 92 def RunKvmOk(): |
109 """Run kvm-ok as root to check that KVM is properly enabled after installation | 93 """Run kvm-ok as root to check that KVM is properly enabled after installation |
110 of the required packages. | 94 of the required packages. |
111 | 95 |
112 Returns: | 96 Returns: |
113 True iff KVM is enabled (/dev/kvm exists). On failure, returns False | 97 True iff KVM is enabled (/dev/kvm exists). On failure, returns False |
114 but also print detailed information explaining why KVM isn't enabled | 98 but also print detailed information explaining why KVM isn't enabled |
115 (e.g. CPU doesn't support it, or BIOS disabled it). | 99 (e.g. CPU doesn't support it, or BIOS disabled it). |
116 """ | 100 """ |
117 try: | 101 try: |
118 # Note: kvm-ok is in /usr/sbin, so always use 'sudo' to run it. | 102 # Note: kvm-ok is in /usr/sbin, so always use 'sudo' to run it. |
119 return not cmd_helper.RunCmd(['sudo', 'kvm-ok']) | 103 return not cmd_helper.RunCmd(['sudo', 'kvm-ok']) |
120 except OSError: | 104 except OSError: |
121 logging.info('kvm-ok not installed') | 105 logging.info('kvm-ok not installed') |
122 return False | 106 return False |
123 | 107 |
124 | 108 |
125 def GetSDK(): | |
126 """Download the SDK and unzip it into EMULATOR_SDK_ROOT.""" | |
127 logging.info('Download Android SDK.') | |
128 sdk_url = '%s/%s' % (SDK_BASE_URL, SDK_ZIP) | |
129 try: | |
130 cmd_helper.RunCmd(['curl', '-o', '/tmp/sdk.zip', sdk_url]) | |
131 print 'curled unzipping...' | |
132 rc = cmd_helper.RunCmd(['unzip', '-o', '/tmp/sdk.zip', '-d', '/tmp/']) | |
133 if rc: | |
134 raise Exception('ERROR: could not download/unzip Android SDK.') | |
135 # Get the name of the sub-directory that everything will be extracted to. | |
136 dirname, _ = os.path.splitext(SDK_ZIP) | |
137 zip_dir = '/tmp/%s' % dirname | |
138 # Move the extracted directory to EMULATOR_SDK_ROOT | |
139 shutil.move(zip_dir, constants.EMULATOR_SDK_ROOT) | |
140 finally: | |
141 os.unlink('/tmp/sdk.zip') | |
142 | |
143 | |
144 def InstallKVM(): | 109 def InstallKVM(): |
145 """Installs KVM packages.""" | 110 """Installs KVM packages.""" |
146 rc = cmd_helper.RunCmd(['sudo', 'apt-get', 'install', 'kvm']) | 111 rc = cmd_helper.RunCmd(['sudo', 'apt-get', 'install', 'kvm']) |
147 if rc: | 112 if rc: |
148 logging.critical('ERROR: Did not install KVM. Make sure hardware ' | 113 logging.critical('ERROR: Did not install KVM. Make sure hardware ' |
149 'virtualization is enabled in BIOS (i.e. Intel VT-x or ' | 114 'virtualization is enabled in BIOS (i.e. Intel VT-x or ' |
150 'AMD SVM).') | 115 'AMD SVM).') |
151 # TODO(navabi): Use modprobe kvm-amd on AMD processors. | 116 # TODO(navabi): Use modprobe kvm-amd on AMD processors. |
152 rc = cmd_helper.RunCmd(['sudo', 'modprobe', 'kvm-intel']) | 117 rc = cmd_helper.RunCmd(['sudo', 'modprobe', 'kvm-intel']) |
153 if rc: | 118 if rc: |
154 logging.critical('ERROR: Did not add KVM module to Linux Kernel. Make sure ' | 119 logging.critical('ERROR: Did not add KVM module to Linux Kernel. Make sure ' |
155 'hardware virtualization is enabled in BIOS.') | 120 'hardware virtualization is enabled in BIOS.') |
156 # Now check to ensure KVM acceleration can be used. | 121 # Now check to ensure KVM acceleration can be used. |
157 if not RunKvmOk(): | 122 if not RunKvmOk(): |
158 logging.critical('ERROR: Can not use KVM acceleration. Make sure hardware ' | 123 logging.critical('ERROR: Can not use KVM acceleration. Make sure hardware ' |
159 'virtualization is enabled in BIOS (i.e. Intel VT-x or ' | 124 'virtualization is enabled in BIOS (i.e. Intel VT-x or ' |
160 'AMD SVM).') | 125 'AMD SVM).') |
161 | 126 |
162 | 127 |
128 def UpdateSDK(api_level, package_name, package_pattern, timeout): | |
mikecase (-- gone --)
2015/10/07 17:45:38
Do you remember where the sdk gets downloaded? My
Yoland Yan(Google)
2015/10/08 01:48:12
It's downloaded to third_party/android_tools/sdk/.
| |
129 '''This function update SDK with a filter index. | |
mikecase (-- gone --)
2015/10/07 17:45:38
nit: Use double quotes for multiline comments. The
Yoland Yan(Google)
2015/10/08 01:48:12
Done
Yoland Yan(Google)
2015/10/08 01:48:12
Done
| |
130 | |
131 Args: | |
132 api_level: the Android API level to download for. | |
133 package_name: logging name of package that is being updated. | |
134 package_pattern: the pattern to match the filter index from. | |
135 timeout: the amount of time wait for update command. | |
136 ''' | |
137 android_binary = os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'android') | |
138 | |
139 list_sdk_repo_command = [android_binary, 'list', 'sdk', '--all'] | |
140 | |
141 exit_code, stdout = cmd_helper.GetCmdStatusAndOutput(list_sdk_repo_command) | |
142 | |
143 if exit_code != 0: | |
144 raise Exception('\'android list sdk --all\' command return %d' % exit_code) | |
145 | |
146 for line in stdout.split('\n'): | |
147 match = package_pattern.match(line) | |
148 if match: | |
149 index = match.group(1) | |
150 logging.info('package %s corresponds to %s with api level %d' | |
151 % (index, package_name, api_level)) | |
152 update_command = [android_binary, 'update', 'sdk', '--no-ui', '--all', | |
153 '--filter', index] | |
154 update_command_str = ' '.join(update_command) | |
155 logging.info('running update command: %s', update_command_str) | |
156 update_process = pexpect.spawn(update_command_str) | |
157 | |
158 if update_process.expect('Do you accept the license') != 0: | |
159 raise Exception('License agreement check failed') | |
160 update_process.sendline('y') | |
161 if update_process.expect( | |
162 'Done. 1 package installed.', timeout=timeout) == 0: | |
163 logging.info('Successfully installed %s for API level %d' | |
164 % (package_name, api_level)) | |
165 return | |
166 else: | |
167 raise Exception('Failed to install platform update') | |
168 raise Exception('Could not find android-%d update for the SDK!' % api_level) | |
169 | |
163 def GetX86Image(api_level=DEFAULT_ANDROID_API_LEVEL): | 170 def GetX86Image(api_level=DEFAULT_ANDROID_API_LEVEL): |
164 """Download x86 system image from Intel's website. | 171 """Download x86 system image from Intel's website. |
165 | 172 |
166 Args: | 173 Args: |
167 api_level: the Android API level to download for. | 174 api_level: the Android API level to download for. |
168 """ | 175 """ |
169 logging.info('Download x86 system image directory into sdk directory.') | 176 logging.info('Download x86 system image directory into sdk directory.') |
170 # TODO(andrewhayden): Use python tempfile lib instead | |
171 temp_file = '/tmp/x86_img_android-%d.zip' % api_level | |
172 if api_level not in X86_IMG_URLS: | |
173 raise Exception('ERROR: no URL known for x86 image for android-%s' % | |
174 api_level) | |
175 try: | |
176 cmd_helper.RunCmd(['curl', '-o', temp_file, X86_IMG_URLS[api_level]]) | |
177 rc = cmd_helper.RunCmd(['unzip', '-o', temp_file, '-d', '/tmp/']) | |
178 if rc: | |
179 raise Exception('ERROR: Could not download/unzip image zip.') | |
180 api_target = 'android-%d' % api_level | |
181 sys_imgs = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk', | |
182 'system-images', api_target, 'x86') | |
183 logging.info('Deploying system image to %s', sys_imgs) | |
184 shutil.move('/tmp/x86', sys_imgs) | |
185 finally: | |
186 os.unlink(temp_file) | |
187 | 177 |
178 x86_package_pattern = re.compile( | |
179 r'\s*([0-9]+)- Intel x86 Atom System Image, Android API %d.*' % api_level) | |
180 | |
181 UpdateSDK(api_level, 'x86 system image', x86_package_pattern, | |
182 DOWNLOAD_SYSTEM_IMAGE_TIMEOUT) | |
188 | 183 |
189 def GetSDKPlatform(api_level=DEFAULT_ANDROID_API_LEVEL): | 184 def GetSDKPlatform(api_level=DEFAULT_ANDROID_API_LEVEL): |
190 """Update the SDK to include the platform specified. | 185 """Update the SDK to include the platform specified. |
191 | 186 |
192 Args: | 187 Args: |
193 api_level: the Android API level to download | 188 api_level: the Android API level to download |
194 """ | 189 """ |
195 android_binary = os.path.join(constants.EMULATOR_SDK_ROOT, | 190 logging.info('Download SDK Platform directory into sdk directory.') |
196 'sdk', 'tools', 'android') | 191 |
197 pattern = re.compile( | 192 platform_package_pattern = re.compile( |
198 r'\s*([0-9]+)- SDK Platform Android [\.,0-9]+, API %d.*' % api_level) | 193 r'\s*([0-9]+)- SDK Platform Android [\.,0-9]+, API %d.*' % api_level) |
199 # Example: | 194 |
200 # 2- SDK Platform Android 4.3, API 18, revision 2 | 195 UpdateSDK(api_level, 'SDK Platform', platform_package_pattern, |
201 exit_code, stdout = cmd_helper.GetCmdStatusAndOutput( | 196 DOWNLOAD_SDK_PLATFORM_TIMEOUT) |
202 [android_binary, 'list', 'sdk']) | |
203 if exit_code != 0: | |
204 raise Exception('\'android list sdk\' command return %d' % exit_code) | |
205 for line in stdout.split('\n'): | |
206 match = pattern.match(line) | |
207 if match: | |
208 index = match.group(1) | |
209 print 'package %s corresponds to platform level %d' % (index, api_level) | |
210 # update sdk --no-ui --filter $INDEX | |
211 update_command = [android_binary, | |
212 'update', 'sdk', '--no-ui', '--filter', index] | |
213 update_command_str = ' '.join(update_command) | |
214 logging.info('running update command: %s', update_command_str) | |
215 update_process = pexpect.spawn(update_command_str) | |
216 # TODO(andrewhayden): Do we need to bug the user about this? | |
217 if update_process.expect('Do you accept the license') != 0: | |
218 raise Exception('License agreement check failed') | |
219 update_process.sendline('y') | |
220 if update_process.expect('Done. 1 package installed.') == 0: | |
221 print 'Successfully installed platform for API level %d' % api_level | |
222 return | |
223 else: | |
224 raise Exception('Failed to install platform update') | |
225 raise Exception('Could not find android-%d update for the SDK!' % api_level) | |
226 | 197 |
227 | 198 |
228 def main(argv): | 199 def main(argv): |
229 opt_parser = optparse.OptionParser( | 200 opt_parser = optparse.OptionParser( |
230 description='Install dependencies for running the Android emulator') | 201 description='Install dependencies for running the Android emulator') |
231 opt_parser.add_option('--api-level', dest='api_level', | 202 opt_parser.add_option('--api-level', |
232 help='The API level (e.g., 19 for Android 4.4) to ensure is available', | 203 dest='api_level', |
233 type='int', default=DEFAULT_ANDROID_API_LEVEL) | 204 help=('The API level (e.g., 19 for Android 4.4) to ' |
234 opt_parser.add_option('-v', dest='verbose', action='store_true', | 205 'ensure is available'), |
235 help='enable verbose logging') | 206 type='int', |
207 default=DEFAULT_ANDROID_API_LEVEL) | |
208 opt_parser.add_option('-v', | |
209 dest='verbosity', | |
210 default=1, | |
211 action='count', | |
212 help='enable INFO logging') | |
236 options, _ = opt_parser.parse_args(argv[1:]) | 213 options, _ = opt_parser.parse_args(argv[1:]) |
237 | 214 |
238 # run_tests_helper will set logging to INFO or DEBUG | 215 run_tests_helper.SetLogLevel(verbose_count=options.verbosity) |
239 # We achieve verbose output by configuring it with 2 (==DEBUG) | |
240 verbosity = 1 | |
241 if options.verbose: | |
242 verbosity = 2 | |
243 logging.basicConfig(level=logging.INFO, | |
244 format='# %(asctime)-15s: %(message)s') | |
245 run_tests_helper.SetLogLevel(verbose_count=verbosity) | |
246 | 216 |
247 # Calls below will download emulator SDK and/or system images only if needed. | 217 # Calls below will download emulator SDK and/or system images only if needed. |
248 if CheckSDK(): | 218 if CheckSDK(): |
249 logging.info('android_emulator_sdk/ already exists, skipping download.') | 219 logging.info('android_emulator_sdk/ already exists, skipping download.') |
250 else: | 220 else: |
251 GetSDK() | 221 logging.critical('ERROR: Emulator SDK not installed in %s' |
222 % constants.ANDROID_SDK_ROOT) | |
223 return 1 | |
252 | 224 |
253 # Check target. The target has to be installed in order to run the emulator. | 225 # Check target. The target has to be installed in order to run the emulator. |
254 if CheckSDKPlatform(options.api_level): | 226 if CheckSDKPlatform(options.api_level): |
255 logging.info('SDK platform android-%d already present, skipping.', | 227 logging.info('SDK platform android-%d already present, skipping.', |
256 options.api_level) | 228 options.api_level) |
257 else: | 229 else: |
258 logging.info('SDK platform android-%d not present, installing.', | 230 logging.info('SDK platform android-%d not present, installing.', |
259 options.api_level) | 231 options.api_level) |
260 GetSDKPlatform(options.api_level) | 232 GetSDKPlatform(options.api_level) |
261 | 233 |
262 # Download the x86 system image only if needed. | 234 # Download the x86 system image only if needed. |
263 if CheckX86Image(options.api_level): | 235 if CheckX86Image(options.api_level): |
264 logging.info('x86 image for android-%d already present, skipping.', | 236 logging.info('x86 image for android-%d already present, skipping.', |
265 options.api_level) | 237 options.api_level) |
266 else: | 238 else: |
267 GetX86Image(options.api_level) | 239 GetX86Image(options.api_level) |
268 | 240 |
269 # Make sure KVM packages are installed and enabled. | 241 # Make sure KVM packages are installed and enabled. |
270 if CheckKVM(): | 242 if CheckKVM(): |
271 logging.info('KVM already installed and enabled.') | 243 logging.info('KVM already installed and enabled.') |
272 else: | 244 else: |
273 InstallKVM() | 245 InstallKVM() |
274 | 246 |
275 | 247 |
276 if __name__ == '__main__': | 248 if __name__ == '__main__': |
277 sys.exit(main(sys.argv)) | 249 sys.exit(main(sys.argv)) |
OLD | NEW |