Home | History | Annotate | Download | only in flavor
      1 #!/usr/bin/env python
      2 #
      3 # Copyright 2016 Google Inc.
      4 #
      5 # Use of this source code is governed by a BSD-style license that can be
      6 # found in the LICENSE file.
      7 
      8 
      9 import android_devices
     10 import default_flavor
     11 import os
     12 
     13 
     14 """Android flavor utils, used for building for and running tests on Android."""
     15 
     16 
     17 class _ADBWrapper(object):
     18   """Wrapper for ADB."""
     19   def __init__(self, path_to_adb, serial, android_flavor):
     20     self._adb = path_to_adb
     21     self._serial = serial
     22     self._wait_count = 0
     23     self._android_flavor = android_flavor
     24 
     25   def wait_for_device(self):
     26     """Run 'adb wait-for-device'."""
     27     self._wait_count += 1
     28     cmd = [
     29         os.path.join(self._android_flavor.android_bin, 'adb_wait_for_device'),
     30         '-s', self._serial,
     31     ]
     32     self._android_flavor._bot_info.run(
     33         cmd, env=self._android_flavor._default_env)
     34 
     35   def maybe_wait_for_device(self):
     36     """Run 'adb wait-for-device' if it hasn't already been run."""
     37     if self._wait_count == 0:
     38       self.wait_for_device()
     39 
     40   def __call__(self, *args, **kwargs):
     41     self.maybe_wait_for_device()
     42     return self._android_flavor._bot_info.run(self._adb + args, **kwargs)
     43 
     44 
     45 class AndroidFlavorUtils(default_flavor.DefaultFlavorUtils):
     46   def __init__(self, skia_api):
     47     super(AndroidFlavorUtils, self).__init__(skia_api)
     48     self.device = self._bot_info.spec['device_cfg']
     49     slave_info = android_devices.SLAVE_INFO.get(
     50         self._bot_info.slave_name,
     51         android_devices.SLAVE_INFO['default'])
     52     self.serial = slave_info.serial
     53     self.android_bin = os.path.join(
     54         self._bot_info.skia_dir, 'platform_tools', 'android', 'bin')
     55     self._android_sdk_root = slave_info.android_sdk_root
     56     self._adb = _ADBWrapper(
     57         os.path.join(self._android_sdk_root, 'platform-tools', 'adb'),
     58         self.serial,
     59         self)
     60     self._has_root = slave_info.has_root
     61     self._default_env = {'ANDROID_SDK_ROOT': self._android_sdk_root,
     62                          'ANDROID_HOME': self._android_sdk_root,
     63                          'SKIA_ANDROID_VERBOSE_SETUP': '1'}
     64 
     65   def step(self, name, cmd, env=None, **kwargs):
     66     self._adb.maybe_wait_for_device()
     67     args = [self.android_bin.join('android_run_skia'),
     68             '--verbose',
     69             '--logcat',
     70             '-d', self.device,
     71             '-s', self.serial,
     72             '-t', self._bot_info.configuration,
     73     ]
     74     env = dict(env or {})
     75     env.update(self._default_env)
     76 
     77     return self._bot_info.run(self._bot_info.m.step, name=name, cmd=args + cmd,
     78                               env=env, **kwargs)
     79 
     80   def compile(self, target):
     81     """Build the given target."""
     82     env = dict(self._default_env)
     83     ccache = self._bot_info.ccache
     84     if ccache:
     85       env['ANDROID_MAKE_CCACHE'] = ccache
     86 
     87     cmd = [os.path.join(self.android_bin, 'android_ninja'), target,
     88            '-d', self.device]
     89     if 'Clang' in self._bot_info.name:
     90       cmd.append('--clang')
     91     self._bot_info.run(cmd, env=env)
     92 
     93   def device_path_join(self, *args):
     94     """Like os.path.join(), but for paths on a connected Android device."""
     95     return '/'.join(args)
     96 
     97   def device_path_exists(self, path):
     98     """Like os.path.exists(), but for paths on a connected device."""
     99     exists_str = 'FILE_EXISTS'
    100     return exists_str in self._adb(
    101         name='exists %s' % self._bot_info.m.path.basename(path),
    102         serial=self.serial,
    103         cmd=['shell', 'if', '[', '-e', path, '];',
    104              'then', 'echo', exists_str + ';', 'fi'],
    105         stdout=self._bot_info.m.raw_io.output(),
    106         infra_step=True
    107     ).stdout
    108 
    109   def _remove_device_dir(self, path):
    110     """Remove the directory on the device."""
    111     self._adb(name='rmdir %s' % self._bot_info.m.path.basename(path),
    112               serial=self.serial,
    113               cmd=['shell', 'rm', '-r', path],
    114               infra_step=True)
    115     # Sometimes the removal fails silently. Verify that it worked.
    116     if self.device_path_exists(path):
    117       raise Exception('Failed to remove %s!' % path)  # pragma: no cover
    118 
    119   def _create_device_dir(self, path):
    120     """Create the directory on the device."""
    121     self._adb(name='mkdir %s' % self._bot_info.m.path.basename(path),
    122               serial=self.serial,
    123               cmd=['shell', 'mkdir', '-p', path],
    124               infra_step=True)
    125 
    126   def copy_directory_contents_to_device(self, host_dir, device_dir):
    127     """Like shutil.copytree(), but for copying to a connected device."""
    128     self._bot_info.run(
    129         self._bot_info.m.step,
    130         name='push %s' % self._bot_info.m.path.basename(host_dir),
    131         cmd=[self.android_bin.join('adb_push_if_needed'), '--verbose',
    132              '-s', self.serial, host_dir, device_dir],
    133         env=self._default_env,
    134         infra_step=True)
    135 
    136   def copy_directory_contents_to_host(self, device_dir, host_dir):
    137     """Like shutil.copytree(), but for copying from a connected device."""
    138     self._bot_info.run(
    139         self._bot_info.m.step,
    140         name='pull %s' % self._bot_info.m.path.basename(device_dir),
    141         cmd=[self.android_bin.join('adb_pull_if_needed'), '--verbose',
    142              '-s', self.serial, device_dir, host_dir],
    143         env=self._default_env,
    144         infra_step=True)
    145 
    146   def copy_file_to_device(self, host_path, device_path):
    147     """Like shutil.copyfile, but for copying to a connected device."""
    148     self._adb(name='push %s' % self._bot_info.m.path.basename(host_path),
    149               serial=self.serial,
    150               cmd=['push', host_path, device_path],
    151               infra_step=True)
    152 
    153   def create_clean_device_dir(self, path):
    154     """Like shutil.rmtree() + os.makedirs(), but on a connected device."""
    155     self._remove_device_dir(path)
    156     self._create_device_dir(path)
    157 
    158   def install(self):
    159     """Run device-specific installation steps."""
    160     if self._has_root:
    161       self._adb(name='adb root',
    162                 serial=self.serial,
    163                 cmd=['root'],
    164                 infra_step=True)
    165       # Wait for the device to reconnect.
    166       self._bot_info.run(
    167           self._bot_info.m.step,
    168           name='wait',
    169           cmd=['sleep', '10'],
    170           infra_step=True)
    171       self._adb.wait_for_device()
    172 
    173     # TODO(borenet): Set CPU scaling mode to 'performance'.
    174     self._bot_info.run(self._bot_info.m.step,
    175                        name='kill skia',
    176                        cmd=[self.android_bin.join('android_kill_skia'),
    177                             '--verbose', '-s', self.serial],
    178                        env=self._default_env,
    179                        infra_step=True)
    180     if self._has_root:
    181       self._adb(name='stop shell',
    182                 serial=self.serial,
    183                 cmd=['shell', 'stop'],
    184                 infra_step=True)
    185 
    186     # Print out battery stats.
    187     self._adb(name='starting battery stats',
    188               serial=self.serial,
    189               cmd=['shell', 'dumpsys', 'batteryproperties'],
    190               infra_step=True)
    191 
    192   def cleanup_steps(self):
    193     """Run any device-specific cleanup steps."""
    194     self._adb(name='final battery stats',
    195               serial=self.serial,
    196               cmd=['shell', 'dumpsys', 'batteryproperties'],
    197               infra_step=True)
    198     self._adb(name='reboot',
    199               serial=self.serial,
    200               cmd=['reboot'],
    201               infra_step=True)
    202     self._bot_info.run(
    203         self._bot_info.m.step,
    204         name='wait for reboot',
    205         cmd=['sleep', '10'],
    206         infra_step=True)
    207     self._adb.wait_for_device()
    208 
    209   def read_file_on_device(self, path, *args, **kwargs):
    210     """Read the given file."""
    211     return self._adb(name='read %s' % self._bot_info.m.path.basename(path),
    212                      serial=self.serial,
    213                      cmd=['shell', 'cat', path],
    214                      stdout=self._bot_info.m.raw_io.output(),
    215                      infra_step=True).stdout.rstrip()
    216 
    217   def remove_file_on_device(self, path, *args, **kwargs):
    218     """Delete the given file."""
    219     return self._adb(name='rm %s' % self._bot_info.m.path.basename(path),
    220                      serial=self.serial,
    221                      cmd=['shell', 'rm', '-f', path],
    222                      infra_step=True,
    223                      *args,
    224                      **kwargs)
    225 
    226   def get_device_dirs(self):
    227     """ Set the directories which will be used by the build steps."""
    228     device_scratch_dir = self._adb(
    229         name='get EXTERNAL_STORAGE dir',
    230         serial=self.serial,
    231         cmd=['shell', 'echo', '$EXTERNAL_STORAGE'],
    232     )
    233     prefix = self.device_path_join(device_scratch_dir, 'skiabot', 'skia_')
    234     return default_flavor.DeviceDirs(
    235         dm_dir=prefix + 'dm',
    236         perf_data_dir=prefix + 'perf',
    237         resource_dir=prefix + 'resources',
    238         images_dir=prefix + 'images',
    239         skp_dir=prefix + 'skp/skps',
    240         tmp_dir=prefix + 'tmp_dir')
    241 
    242