1#!/usr/bin/env python
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
4# found in the LICENSE file.
5
6"""Installs deps for using SDK emulator for testing.
7
8The script will download the SDK and system images, if they are not present, and
9install and enable KVM, if virtualization has been enabled in the BIOS.
10"""
11
12
13import logging
14import optparse
15import os
16import re
17import sys
18
19import devil_chromium
20from devil.utils import cmd_helper
21from devil.utils import run_tests_helper
22from pylib import constants
23from pylib import pexpect
24
25# Android API level
26DEFAULT_ANDROID_API_LEVEL = constants.ANDROID_SDK_VERSION
27# Android ABI/Arch
28DEFAULT_ABI = 'x86'
29
30# Default Time out for downloading SDK component
31DOWNLOAD_SYSTEM_IMAGE_TIMEOUT = 30
32DOWNLOAD_SDK_PLATFORM_TIMEOUT = 60
33
34def CheckSDK():
35  """Check if SDK is already installed.
36
37  Returns:
38    True if the emulator SDK directory (src/android_emulator_sdk/) exists.
39  """
40  return os.path.exists(constants.ANDROID_SDK_ROOT)
41
42
43def CheckSDKPlatform(api_level=DEFAULT_ANDROID_API_LEVEL, google=False):
44  """Check if the "SDK Platform" for the specified API level is installed.
45     This is necessary in order for the emulator to run when the target
46     is specified.
47
48  Args:
49    abi: target abi, x86 or arm
50    api_level: the Android API level to check; defaults to the latest API.
51    google: use Google build system image instead of AOSP build
52
53  Returns:
54    True if the platform is already installed.
55  """
56  android_binary = os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'android')
57  if google:
58    pattern = re.compile('id: [0-9]+ or "Google Inc.:Google APIs:%s"' %
59                         api_level)
60  else:
61    pattern = re.compile('id: [0-9]+ or "android-%d"' % api_level)
62
63  try:
64    exit_code, stdout = cmd_helper.GetCmdStatusAndOutput(
65        [android_binary, 'list'])
66    if exit_code != 0:
67      raise Exception('\'android list\' command failed')
68    for line in stdout.split('\n'):
69      if pattern.match(line):
70        return True
71    return False
72  except OSError:
73    logging.exception('Unable to execute \'android list\'')
74    return False
75
76
77def CheckSystemImage(abi, api_level=DEFAULT_ANDROID_API_LEVEL, google=False):
78  """Check if Android system images have been installed.
79
80  Args:
81    abi: target abi, x86 or arm
82    api_level: the Android API level to check for; defaults to the latest API.
83    google: use Google build system image instead of AOSP build
84
85  Returns:
86    True if x86 image has been previously downloaded.
87  """
88  api_target = 'android-%d' % api_level
89  system_image_root = os.path.join(constants.ANDROID_SDK_ROOT,
90                                   'system-images', api_target)
91  if abi == 'x86':
92    if google:
93      return os.path.exists(os.path.join(system_image_root, 'google_apis',
94                                         'x86'))
95    else:
96      return os.path.exists(os.path.join(system_image_root, 'default', 'x86'))
97  elif abi == 'arm':
98    if google:
99      return os.path.exists(os.path.join(system_image_root, 'google_apis',
100                                         'armeabi-v7a'))
101    else:
102      return os.path.exists(os.path.join(system_image_root, 'default',
103                                         'armeabi-v7a'))
104  else:
105    raise Exception("abi option invalid")
106
107def CheckKVM():
108  """Quickly check whether KVM is enabled.
109
110  Returns:
111    True iff /dev/kvm exists (Linux only).
112  """
113  return os.path.exists('/dev/kvm')
114
115def RunKvmOk():
116  """Run kvm-ok as root to check that KVM is properly enabled after installation
117     of the required packages.
118
119  Returns:
120    True iff KVM is enabled (/dev/kvm exists). On failure, returns False
121    but also print detailed information explaining why KVM isn't enabled
122    (e.g. CPU doesn't support it, or BIOS disabled it).
123  """
124  try:
125    # Note: kvm-ok is in /usr/sbin, so always use 'sudo' to run it.
126    return not cmd_helper.RunCmd(['sudo', 'kvm-ok'])
127  except OSError:
128    logging.info('kvm-ok not installed')
129    return False
130
131
132def InstallKVM():
133  """Installs KVM packages."""
134  rc = cmd_helper.RunCmd(['sudo', 'apt-get', 'install', 'kvm'])
135  if rc:
136    logging.critical('ERROR: Did not install KVM. Make sure hardware '
137                     'virtualization is enabled in BIOS (i.e. Intel VT-x or '
138                     'AMD SVM).')
139  # TODO(navabi): Use modprobe kvm-amd on AMD processors.
140  rc = cmd_helper.RunCmd(['sudo', 'modprobe', 'kvm-intel'])
141  if rc:
142    logging.critical('ERROR: Did not add KVM module to Linux Kernel. Make sure '
143                     'hardware virtualization is enabled in BIOS.')
144  # Now check to ensure KVM acceleration can be used.
145  if not RunKvmOk():
146    logging.critical('ERROR: Can not use KVM acceleration. Make sure hardware '
147                     'virtualization is enabled in BIOS (i.e. Intel VT-x or '
148                     'AMD SVM).')
149
150
151def UpdateSDK(api_level, package_name, package_pattern, timeout):
152  """This function update SDK with a filter index.
153
154  Args:
155    api_level: the Android API level to download for.
156    package_name: logging name of package that is being updated.
157    package_pattern: the pattern to match the filter index from.
158    timeout: the amount of time wait for update command.
159  """
160  android_binary = os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'android')
161
162  list_sdk_repo_command = [android_binary, 'list', 'sdk', '--all']
163
164  exit_code, stdout = cmd_helper.GetCmdStatusAndOutput(list_sdk_repo_command)
165
166  if exit_code != 0:
167    raise Exception('\'android list sdk --all\' command return %d' % exit_code)
168
169  for line in stdout.split('\n'):
170    match = package_pattern.match(line)
171    if match:
172      index = match.group(1)
173      logging.info('package %s corresponds to %s with api level %d',
174                   index, package_name, api_level)
175      update_command = [android_binary, 'update', 'sdk', '--no-ui', '--all',
176                         '--filter', index]
177      update_command_str = ' '.join(update_command)
178      logging.info('running update command: %s', update_command_str)
179      update_process = pexpect.spawn(update_command_str)
180
181      if update_process.expect('Do you accept the license') != 0:
182        raise Exception('License agreement check failed')
183      update_process.sendline('y')
184      if update_process.expect(
185        'Done. 1 package installed.', timeout=timeout) == 0:
186        logging.info('Successfully installed %s for API level %d',
187                      package_name, api_level)
188        return
189      else:
190        raise Exception('Failed to install platform update')
191  raise Exception('Could not find android-%d update for the SDK!' % api_level)
192
193def GetSystemImage(abi, api_level=DEFAULT_ANDROID_API_LEVEL, google=False):
194  """Download system image files
195
196  Args:
197    abi: target abi, x86 or arm
198    api_level: the Android API level to download for.
199    google: use Google build system image instead of AOSP build
200  """
201  logging.info('Download x86 system image directory into sdk directory.')
202
203  if abi == 'x86':
204    if google:
205      package_name = 'Google Intel x86 Atom System Image'
206      pattern = re.compile(
207         r'\s*([0-9]+)- Google APIs Intel x86 Atom System Image, Google Inc.'
208        ' API %d.*' % api_level)
209    else:
210      package_name = 'Intel x86 system image'
211      pattern = re.compile(
212        r'\s*([0-9]+)- Intel x86 Atom System Image, Android API %d.*'
213        % api_level)
214  elif abi == 'arm':
215    if google:
216      package_name = 'Google arm system image'
217      pattern = re.compile(
218        r'\s*([0-9]+)- Google APIs ARM EABI v7a System Image, Google Inc. API '
219        '%d.*' % api_level)
220    else:
221      package_name = 'Android arm system image'
222      pattern = re.compile(
223        r'\s*([0-9]+)- ARM EABI v7a System Image, Android API %d.*' % api_level)
224  else:
225    raise Exception('abi option is invalid')
226
227  UpdateSDK(api_level, package_name, pattern, DOWNLOAD_SYSTEM_IMAGE_TIMEOUT)
228
229def GetSDKPlatform(api_level=DEFAULT_ANDROID_API_LEVEL, google=False):
230  """Update the SDK to include the platform specified.
231
232  Args:
233    api_level: the Android API level to download
234    google: use Google build system image instead of AOSP build
235  """
236  logging.info('Download SDK Platform directory into sdk directory.')
237
238  platform_package_pattern = re.compile(
239      r'\s*([0-9]+)- SDK Platform Android [\.,0-9]+, API %d.*' % api_level)
240
241  UpdateSDK(api_level, 'SDK Platform', platform_package_pattern,
242            DOWNLOAD_SDK_PLATFORM_TIMEOUT)
243
244  if google:
245    google_api_package_pattern = re.compile(
246      r'\s*([0-9]+)- Google APIs, Android API %d.*' % api_level)
247    UpdateSDK(api_level, 'Google APIs', google_api_package_pattern,
248              DOWNLOAD_SDK_PLATFORM_TIMEOUT)
249
250
251def main(argv):
252  opt_parser = optparse.OptionParser(
253      description='Install dependencies for running the Android emulator')
254  opt_parser.add_option('--abi',
255                        dest='abi',
256                        help='The targeted abi for emulator system image',
257                        type='string',
258                        default=DEFAULT_ABI)
259  opt_parser.add_option('--api-level',
260                        dest='api_level',
261                        help=('The API level (e.g., 19 for Android 4.4) to '
262                              'ensure is available'),
263                        type='int',
264                        default=DEFAULT_ANDROID_API_LEVEL)
265  opt_parser.add_option('-v',
266                        dest='verbosity',
267                        default=1,
268                        action='count',
269                        help='Verbose level (multiple times for more)')
270  opt_parser.add_option('--google',
271                        dest='google',
272                        action='store_true',
273                        default=False,
274                        help='Install Google System Image instead of AOSP')
275
276  options, _ = opt_parser.parse_args(argv[1:])
277
278  run_tests_helper.SetLogLevel(verbose_count=options.verbosity)
279
280  devil_chromium.Initialize()
281
282  # Calls below will download emulator SDK and/or system images only if needed.
283  if CheckSDK():
284    logging.info('android_emulator_sdk/ exists')
285  else:
286    logging.critical('ERROR: Emulator SDK not installed in %s'
287                     , constants.ANDROID_SDK_ROOT)
288    return 1
289
290  # Check target. The target has to be installed in order to run the emulator.
291  if CheckSDKPlatform(options.api_level, options.google):
292    logging.info('SDK platform %s %s android-%d already present, skipping.',
293                 'Google' if options.google else 'AOSP', options.abi,
294                 options.api_level)
295  else:
296    logging.info('SDK platform %s %s android-%d not present, installing.',
297                 'Google' if options.google else 'AOSP', options.abi,
298                 options.api_level)
299    GetSDKPlatform(options.api_level, options.google)
300
301  # Download the system image needed
302  if CheckSystemImage(options.abi, options.api_level, options.google):
303    logging.info('system image for %s %s android-%d already present, skipping.',
304                 'Google' if options.google else 'AOSP', options.abi,
305                 options.api_level)
306  else:
307    GetSystemImage(options.abi, options.api_level, options.google)
308
309  # Make sure KVM packages are installed and enabled.
310  if options.abi == 'x86':
311    if CheckKVM():
312      logging.info('KVM already installed and enabled.')
313    else:
314      logging.warning('KVM is not installed or enabled.')
315
316
317if __name__ == '__main__':
318  sys.exit(main(sys.argv))
319