Home | History | Annotate | Download | only in android
      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 
      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.
     10 """
     11 
     12 
     13 import logging
     14 import optparse
     15 import os
     16 import re
     17 import sys
     18 
     19 import devil_chromium
     20 from devil.utils import cmd_helper
     21 from devil.utils import run_tests_helper
     22 from pylib import constants
     23 from pylib import pexpect
     24 
     25 # Android API level
     26 DEFAULT_ANDROID_API_LEVEL = constants.ANDROID_SDK_VERSION
     27 # Android ABI/Arch
     28 DEFAULT_ABI = 'x86'
     29 
     30 # Default Time out for downloading SDK component
     31 DOWNLOAD_SYSTEM_IMAGE_TIMEOUT = 30
     32 DOWNLOAD_SDK_PLATFORM_TIMEOUT = 60
     33 
     34 def 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 
     43 def 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 
     77 def 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 
    107 def 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 
    115 def 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 
    132 def 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 
    151 def 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 
    193 def 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 
    229 def 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 
    251 def 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 
    317 if __name__ == '__main__':
    318   sys.exit(main(sys.argv))
    319