Home | History | Annotate | Download | only in flavor
      1 # Copyright 2016 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 from recipe_engine import recipe_api
      6 
      7 import default_flavor
      8 import re
      9 import subprocess
     10 
     11 
     12 """GN Android flavor utils, used for building Skia for Android with GN."""
     13 class GNAndroidFlavorUtils(default_flavor.DefaultFlavorUtils):
     14   def __init__(self, m):
     15     super(GNAndroidFlavorUtils, self).__init__(m)
     16     self._ever_ran_adb = False
     17     self.ADB_BINARY = '/usr/bin/adb.1.0.35'
     18     self._golo_devices = ['Nexus5x']
     19     if self.m.vars.builder_cfg.get('model') in self._golo_devices:
     20       self.ADB_BINARY = '/opt/infra-android/tools/adb'
     21 
     22     self.device_dirs = default_flavor.DeviceDirs(
     23         dm_dir        = self.m.vars.android_data_dir + 'dm_out',
     24         perf_data_dir = self.m.vars.android_data_dir + 'perf',
     25         resource_dir  = self.m.vars.android_data_dir + 'resources',
     26         images_dir    = self.m.vars.android_data_dir + 'images',
     27         skp_dir       = self.m.vars.android_data_dir + 'skps',
     28         svg_dir       = self.m.vars.android_data_dir + 'svgs',
     29         tmp_dir       = self.m.vars.android_data_dir)
     30 
     31     # A list of devices we can't root.  If rooting fails and a device is not
     32     # on the list, we fail the task to avoid perf inconsistencies.
     33     self.rootable_blacklist = ['GalaxyS6', 'GalaxyS7_G930A', 'GalaxyS7_G930FD',
     34                                'MotoG4', 'NVIDIA_Shield']
     35 
     36     # Maps device type -> CPU ids that should be scaled for nanobench.
     37     # Many devices have two (or more) different CPUs (e.g. big.LITTLE
     38     # on Nexus5x). The CPUs listed are the biggest cpus on the device.
     39     # The CPUs are grouped together, so we only need to scale one of them
     40     # (the one listed) in order to scale them all.
     41     # E.g. Nexus5x has cpu0-3 as one chip and cpu4-5 as the other. Thus,
     42     # if one wants to run a single-threaded application (e.g. nanobench), one
     43     # can disable cpu0-3 and scale cpu 4 to have only cpu4 and 5 at the same
     44     # frequency.  See also disable_for_nanobench.
     45     self.cpus_to_scale = {
     46       'Nexus5x': [4],
     47       'NexusPlayer': [0, 2], # has 2 identical chips, so scale them both.
     48       'Pixel': [2],
     49       'Pixel2XL': [4]
     50     }
     51 
     52     # Maps device type -> CPU ids that should be turned off when running
     53     # single-threaded applications like nanobench. The devices listed have
     54     # multiple, differnt CPUs. We notice a lot of noise that seems to be
     55     # caused by nanobench running on the slow CPU, then the big CPU. By
     56     # disabling this, we see less of that noise by forcing the same CPU
     57     # to be used for the performance testing every time.
     58     self.disable_for_nanobench = {
     59       'Nexus5x': range(0, 4),
     60       'Pixel': range(0, 2),
     61       'Pixel2XL': range(0, 4),
     62       'PixelC': range(0, 2)
     63     }
     64 
     65     self.gpu_scaling = {
     66       "Nexus5":  450000000,
     67       "Nexus5x": 600000000,
     68     }
     69 
     70   def _run(self, title, *cmd, **kwargs):
     71     with self.m.context(cwd=self.m.vars.skia_dir):
     72       return self.m.run(self.m.step, title, cmd=list(cmd), **kwargs)
     73 
     74   def _py(self, title, script, infra_step=True):
     75     with self.m.context(cwd=self.m.vars.skia_dir):
     76       return self.m.run(self.m.python, title, script=script,
     77                         infra_step=infra_step)
     78 
     79   def _adb(self, title, *cmd, **kwargs):
     80     # The only non-infra adb steps (dm / nanobench) happen to not use _adb().
     81     if 'infra_step' not in kwargs:
     82       kwargs['infra_step'] = True
     83 
     84     self._ever_ran_adb = True
     85     attempts = 1
     86     flaky_devices = ['NexusPlayer', 'PixelC']
     87     if self.m.vars.builder_cfg.get('model') in flaky_devices:
     88       attempts = 3
     89 
     90     def wait_for_device(attempt):
     91       self.m.run(self.m.step,
     92                  'kill adb server after failure of \'%s\' (attempt %d)' % (
     93                      title, attempt),
     94                  cmd=[self.ADB_BINARY, 'kill-server'],
     95                  infra_step=True, timeout=30, abort_on_failure=False,
     96                  fail_build_on_failure=False)
     97       self.m.run(self.m.step,
     98                  'wait for device after failure of \'%s\' (attempt %d)' % (
     99                      title, attempt),
    100                  cmd=[self.ADB_BINARY, 'wait-for-device'], infra_step=True,
    101                  timeout=180, abort_on_failure=False,
    102                  fail_build_on_failure=False)
    103 
    104     with self.m.context(cwd=self.m.vars.skia_dir):
    105       return self.m.run.with_retry(self.m.step, title, attempts,
    106                                    cmd=[self.ADB_BINARY]+list(cmd),
    107                                    between_attempts_fn=wait_for_device,
    108                                    **kwargs)
    109 
    110   def _scale_for_dm(self):
    111     device = self.m.vars.builder_cfg.get('model')
    112     if (device in self.rootable_blacklist or
    113         self.m.vars.internal_hardware_label):
    114       return
    115 
    116     # This is paranoia... any CPUs we disabled while running nanobench
    117     # ought to be back online now that we've restarted the device.
    118     for i in self.disable_for_nanobench.get(device, []):
    119       self._set_cpu_online(i, 1) # enable
    120 
    121     scale_up = self.cpus_to_scale.get(device, [0])
    122     # For big.LITTLE devices, make sure we scale the LITTLE cores up;
    123     # there is a chance they are still in powersave mode from when
    124     # swarming slows things down for cooling down and charging.
    125     if 0 not in scale_up:
    126       scale_up.append(0)
    127     for i in scale_up:
    128       # AndroidOne doesn't support ondemand governor. hotplug is similar.
    129       if device == 'AndroidOne':
    130         self._set_governor(i, 'hotplug')
    131       else:
    132         self._set_governor(i, 'ondemand')
    133 
    134   def _scale_for_nanobench(self):
    135     device = self.m.vars.builder_cfg.get('model')
    136     if (device in self.rootable_blacklist or
    137       self.m.vars.internal_hardware_label):
    138       return
    139 
    140     for i in self.cpus_to_scale.get(device, [0]):
    141       self._set_governor(i, 'userspace')
    142       self._scale_cpu(i, 0.6)
    143 
    144     for i in self.disable_for_nanobench.get(device, []):
    145       self._set_cpu_online(i, 0) # disable
    146 
    147     if device in self.gpu_scaling:
    148       #https://developer.qualcomm.com/qfile/28823/lm80-p0436-11_adb_commands.pdf
    149       # Section 3.2.1 Commands to put the GPU in performance mode
    150       # Nexus 5 is  320000000 by default
    151       # Nexus 5x is 180000000 by default
    152       gpu_freq = self.gpu_scaling[device]
    153       self.m.run.with_retry(self.m.python.inline,
    154         "Lock GPU to %d (and other perf tweaks)" % gpu_freq,
    155         3, # attempts
    156         program="""
    157 import os
    158 import subprocess
    159 import sys
    160 import time
    161 ADB = sys.argv[1]
    162 freq = sys.argv[2]
    163 idle_timer = "10000"
    164 
    165 log = subprocess.check_output([ADB, 'root'])
    166 # check for message like 'adbd cannot run as root in production builds'
    167 print log
    168 if 'cannot' in log:
    169   raise Exception('adb root failed')
    170 
    171 subprocess.check_output([ADB, 'shell', 'stop', 'thermald'])
    172 
    173 subprocess.check_output([ADB, 'shell', 'echo "%s" > '
    174     '/sys/class/kgsl/kgsl-3d0/gpuclk' % freq])
    175 
    176 actual_freq = subprocess.check_output([ADB, 'shell', 'cat '
    177     '/sys/class/kgsl/kgsl-3d0/gpuclk']).strip()
    178 if actual_freq != freq:
    179   raise Exception('Frequency (actual, expected) (%s, %s)'
    180                   % (actual_freq, freq))
    181 
    182 subprocess.check_output([ADB, 'shell', 'echo "%s" > '
    183     '/sys/class/kgsl/kgsl-3d0/idle_timer' % idle_timer])
    184 
    185 actual_timer = subprocess.check_output([ADB, 'shell', 'cat '
    186     '/sys/class/kgsl/kgsl-3d0/idle_timer']).strip()
    187 if actual_timer != idle_timer:
    188   raise Exception('idle_timer (actual, expected) (%s, %s)'
    189                   % (actual_timer, idle_timer))
    190 
    191 for s in ['force_bus_on', 'force_rail_on', 'force_clk_on']:
    192   subprocess.check_output([ADB, 'shell', 'echo "1" > '
    193       '/sys/class/kgsl/kgsl-3d0/%s' % s])
    194   actual_set = subprocess.check_output([ADB, 'shell', 'cat '
    195       '/sys/class/kgsl/kgsl-3d0/%s' % s]).strip()
    196   if actual_set != "1":
    197     raise Exception('%s (actual, expected) (%s, 1)'
    198                     % (s, actual_set))
    199 """,
    200         args = [self.ADB_BINARY, gpu_freq],
    201         infra_step=True,
    202         timeout=30)
    203 
    204   def _set_governor(self, cpu, gov):
    205     self._ever_ran_adb = True
    206     self.m.run.with_retry(self.m.python.inline,
    207         "Set CPU %d's governor to %s" % (cpu, gov),
    208         3, # attempts
    209         program="""
    210 import os
    211 import subprocess
    212 import sys
    213 import time
    214 ADB = sys.argv[1]
    215 cpu = int(sys.argv[2])
    216 gov = sys.argv[3]
    217 
    218 log = subprocess.check_output([ADB, 'root'])
    219 # check for message like 'adbd cannot run as root in production builds'
    220 print log
    221 if 'cannot' in log:
    222   raise Exception('adb root failed')
    223 
    224 subprocess.check_output([ADB, 'shell', 'echo "%s" > '
    225     '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % (gov, cpu)])
    226 actual_gov = subprocess.check_output([ADB, 'shell', 'cat '
    227     '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor' % cpu]).strip()
    228 if actual_gov != gov:
    229   raise Exception('(actual, expected) (%s, %s)'
    230                   % (actual_gov, gov))
    231 """,
    232         args = [self.ADB_BINARY, cpu, gov],
    233         infra_step=True,
    234         timeout=30)
    235 
    236 
    237   def _set_cpu_online(self, cpu, value):
    238     """Set /sys/devices/system/cpu/cpu{N}/online to value (0 or 1)."""
    239     self._ever_ran_adb = True
    240     msg = 'Disabling'
    241     if value:
    242       msg = 'Enabling'
    243     self.m.run.with_retry(self.m.python.inline,
    244         '%s CPU %d' % (msg, cpu),
    245         3, # attempts
    246         program="""
    247 import os
    248 import subprocess
    249 import sys
    250 import time
    251 ADB = sys.argv[1]
    252 cpu = int(sys.argv[2])
    253 value = int(sys.argv[3])
    254 
    255 log = subprocess.check_output([ADB, 'root'])
    256 # check for message like 'adbd cannot run as root in production builds'
    257 print log
    258 if 'cannot' in log:
    259   raise Exception('adb root failed')
    260 
    261 # If we try to echo 1 to an already online cpu, adb returns exit code 1.
    262 # So, check the value before trying to write it.
    263 prior_status = subprocess.check_output([ADB, 'shell', 'cat '
    264     '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()
    265 if prior_status == str(value):
    266   print 'CPU %d online already %d' % (cpu, value)
    267   sys.exit()
    268 
    269 subprocess.check_output([ADB, 'shell', 'echo %s > '
    270     '/sys/devices/system/cpu/cpu%d/online' % (value, cpu)])
    271 actual_status = subprocess.check_output([ADB, 'shell', 'cat '
    272     '/sys/devices/system/cpu/cpu%d/online' % cpu]).strip()
    273 if actual_status != str(value):
    274   raise Exception('(actual, expected) (%s, %d)'
    275                   % (actual_status, value))
    276 """,
    277         args = [self.ADB_BINARY, cpu, value],
    278         infra_step=True,
    279         timeout=30)
    280 
    281 
    282   def _scale_cpu(self, cpu, target_percent):
    283     self._ever_ran_adb = True
    284     self.m.run.with_retry(self.m.python.inline,
    285         'Scale CPU %d to %f' % (cpu, target_percent),
    286         3, # attempts
    287         program="""
    288 import os
    289 import subprocess
    290 import sys
    291 import time
    292 ADB = sys.argv[1]
    293 target_percent = float(sys.argv[2])
    294 cpu = int(sys.argv[3])
    295 log = subprocess.check_output([ADB, 'root'])
    296 # check for message like 'adbd cannot run as root in production builds'
    297 print log
    298 if 'cannot' in log:
    299   raise Exception('adb root failed')
    300 
    301 root = '/sys/devices/system/cpu/cpu%d/cpufreq' %cpu
    302 
    303 # All devices we test on give a list of their available frequencies.
    304 available_freqs = subprocess.check_output([ADB, 'shell',
    305     'cat %s/scaling_available_frequencies' % root])
    306 
    307 # Check for message like '/system/bin/sh: file not found'
    308 if available_freqs and '/system/bin/sh' not in available_freqs:
    309   available_freqs = sorted(
    310       int(i) for i in available_freqs.strip().split())
    311 else:
    312   raise Exception('Could not get list of available frequencies: %s' %
    313                   available_freqs)
    314 
    315 maxfreq = available_freqs[-1]
    316 target = int(round(maxfreq * target_percent))
    317 freq = maxfreq
    318 for f in reversed(available_freqs):
    319   if f <= target:
    320     freq = f
    321     break
    322 
    323 print 'Setting frequency to %d' % freq
    324 
    325 # If scaling_max_freq is lower than our attempted setting, it won't take.
    326 # We must set min first, because if we try to set max to be less than min
    327 # (which sometimes happens after certain devices reboot) it returns a
    328 # perplexing permissions error.
    329 subprocess.check_output([ADB, 'shell', 'echo 0 > '
    330     '%s/scaling_min_freq' % root])
    331 subprocess.check_output([ADB, 'shell', 'echo %d > '
    332     '%s/scaling_max_freq' % (freq, root)])
    333 subprocess.check_output([ADB, 'shell', 'echo %d > '
    334     '%s/scaling_setspeed' % (freq, root)])
    335 time.sleep(5)
    336 actual_freq = subprocess.check_output([ADB, 'shell', 'cat '
    337     '%s/scaling_cur_freq' % root]).strip()
    338 if actual_freq != str(freq):
    339   raise Exception('(actual, expected) (%s, %d)'
    340                   % (actual_freq, freq))
    341 """,
    342         args = [self.ADB_BINARY, str(target_percent), cpu],
    343         infra_step=True,
    344         timeout=30)
    345 
    346   def compile(self, unused_target):
    347     compiler      = self.m.vars.builder_cfg.get('compiler')
    348     configuration = self.m.vars.builder_cfg.get('configuration')
    349     extra_tokens  = self.m.vars.extra_tokens
    350     os            = self.m.vars.builder_cfg.get('os')
    351     target_arch   = self.m.vars.builder_cfg.get('target_arch')
    352 
    353     assert compiler == 'Clang'  # At this rate we might not ever support GCC.
    354 
    355     extra_cflags = []
    356     if configuration == 'Debug':
    357       extra_cflags.append('-O1')
    358 
    359     ndk_asset = 'android_ndk_linux'
    360     if 'Mac' in os:
    361       ndk_asset = 'android_ndk_darwin'
    362     elif 'Win' in os:
    363       ndk_asset = 'n'
    364 
    365     quote = lambda x: '"%s"' % x
    366     args = {
    367         'ndk': quote(self.m.vars.slave_dir.join(ndk_asset)),
    368         'target_cpu': quote(target_arch),
    369     }
    370 
    371     if configuration != 'Debug':
    372       args['is_debug'] = 'false'
    373     if 'Vulkan' in extra_tokens:
    374       args['ndk_api'] = 24
    375       args['skia_enable_vulkan_debug_layers'] = 'false'
    376     if 'ASAN' in extra_tokens:
    377       args['sanitize'] = '"ASAN"'
    378       if target_arch == 'arm' and 'ndk_api' not in args:
    379         args['ndk_api'] = 21
    380 
    381     # If an Android API level is specified, use that.
    382     for t in extra_tokens:
    383       m = re.search(r'API(\d+)', t)
    384       if m and len(m.groups()) == 1:
    385         args['ndk_api'] = m.groups()[0]
    386         break
    387 
    388     if extra_cflags:
    389       args['extra_cflags'] = repr(extra_cflags).replace("'", '"')
    390 
    391     gn_args = ' '.join('%s=%s' % (k,v) for (k,v) in sorted(args.iteritems()))
    392     gn      = 'gn.exe'    if 'Win' in os else 'gn'
    393     ninja   = 'ninja.exe' if 'Win' in os else 'ninja'
    394     gn      = self.m.vars.skia_dir.join('bin', gn)
    395 
    396     self._py('fetch-gn', self.m.vars.skia_dir.join('bin', 'fetch-gn'))
    397 
    398     # If this is the SkQP built, set up the environment and run the script
    399     # build the universal APK. This should only run the skqp branches.
    400     if 'SKQP' in extra_tokens:
    401       ndk_asset = 'android_ndk_linux'
    402       sdk_asset = 'android_sdk_linux'
    403       android_ndk = self.m.vars.slave_dir.join(ndk_asset)
    404       android_home = self.m.vars.slave_dir.join(sdk_asset, 'android-sdk')
    405       env = {
    406         'ANDROID_NDK': android_ndk,
    407         'ANDROID_HOME': android_home,
    408       }
    409 
    410       mk_universal = self.m.vars.skia_dir.join('tools', 'skqp',
    411                                                'make_universal_apk')
    412       with self.m.context(env=env):
    413         self._run('make_universal', mk_universal)
    414     else:
    415       self._run('gn gen', gn, 'gen', self.out_dir, '--args=' + gn_args)
    416       self._run('ninja', ninja, '-k', '0', '-C', self.out_dir)
    417 
    418   def install(self):
    419     self._adb('mkdir ' + self.device_dirs.resource_dir,
    420               'shell', 'mkdir', '-p', self.device_dirs.resource_dir)
    421     if 'ASAN' in self.m.vars.extra_tokens:
    422       asan_setup = self.m.vars.slave_dir.join(
    423             'android_ndk_linux', 'toolchains', 'llvm', 'prebuilt',
    424             'linux-x86_64', 'bin', 'asan_device_setup')
    425       self.m.run(self.m.python.inline, 'Setting up device to run ASAN',
    426         program="""
    427 import os
    428 import subprocess
    429 import sys
    430 import time
    431 ADB = sys.argv[1]
    432 ASAN_SETUP = sys.argv[2]
    433 
    434 def wait_for_device():
    435   while True:
    436     time.sleep(5)
    437     print 'Waiting for device'
    438     subprocess.check_output([ADB, 'wait-for-device'])
    439     bit1 = subprocess.check_output([ADB, 'shell', 'getprop',
    440                                    'dev.bootcomplete'])
    441     bit2 = subprocess.check_output([ADB, 'shell', 'getprop',
    442                                    'sys.boot_completed'])
    443     if '1' in bit1 and '1' in bit2:
    444       print 'Device detected'
    445       break
    446 
    447 log = subprocess.check_output([ADB, 'root'])
    448 # check for message like 'adbd cannot run as root in production builds'
    449 print log
    450 if 'cannot' in log:
    451   raise Exception('adb root failed')
    452 
    453 output = subprocess.check_output([ADB, 'disable-verity'])
    454 print output
    455 
    456 if 'already disabled' not in output:
    457   print 'Rebooting device'
    458   subprocess.check_output([ADB, 'reboot'])
    459   wait_for_device()
    460 
    461 # ASAN setup script is idempotent, either it installs it or says it's installed
    462 output = subprocess.check_output([ADB, 'wait-for-device'])
    463 process = subprocess.Popen([ASAN_SETUP], env={'ADB': ADB},
    464                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    465 
    466 # this also blocks until command finishes
    467 (stdout, stderr) = process.communicate()
    468 print stdout
    469 print 'Stderr: %s' % stderr
    470 if process.returncode:
    471   raise Exception('setup ASAN returned with non-zero exit code: %d' %
    472                   process.returncode)
    473 
    474 if 'Please wait until the device restarts' in stdout:
    475   # Sleep because device does not reboot instantly
    476   time.sleep(30)
    477 wait_for_device()
    478 """,
    479         args = [self.ADB_BINARY, asan_setup],
    480           infra_step=True,
    481           timeout=300,
    482           abort_on_failure=True)
    483 
    484   def cleanup_steps(self):
    485     if self._ever_ran_adb:
    486       self.m.run(self.m.python.inline, 'dump log', program="""
    487           import os
    488           import subprocess
    489           import sys
    490           out = sys.argv[1]
    491           log = subprocess.check_output(['%s', 'logcat', '-d'])
    492           for line in log.split('\\n'):
    493             tokens = line.split()
    494             if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':
    495               addr, path = tokens[-2:]
    496               local = os.path.join(out, os.path.basename(path))
    497               if os.path.exists(local):
    498                 sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])
    499                 line = line.replace(addr, addr + ' ' + sym.strip())
    500             print line
    501           """ % self.ADB_BINARY,
    502           args=[self.m.vars.skia_out.join(self.m.vars.configuration)],
    503           infra_step=True,
    504           timeout=300,
    505           abort_on_failure=False)
    506 
    507     # Only quarantine the bot if the first failed step
    508     # is an infra step. If, instead, we did this for any infra failures, we
    509     # would do this too much. For example, if a Nexus 10 died during dm
    510     # and the following pull step would also fail "device not found" - causing
    511     # us to run the shutdown command when the device was probably not in a
    512     # broken state; it was just rebooting.
    513     if (self.m.run.failed_steps and
    514         isinstance(self.m.run.failed_steps[0], recipe_api.InfraFailure)):
    515       self.m.file.write_text('Quarantining Bot',
    516                              '/home/chrome-bot/force_quarantine', ' ')
    517 
    518     if self._ever_ran_adb:
    519       self._adb('kill adb server', 'kill-server')
    520 
    521   def step(self, name, cmd, **kwargs):
    522     if (cmd[0] == 'nanobench'):
    523       self._scale_for_nanobench()
    524     else:
    525       self._scale_for_dm()
    526     app = self.m.vars.skia_out.join(self.m.vars.configuration, cmd[0])
    527     self._adb('push %s' % cmd[0],
    528               'push', app, self.m.vars.android_bin_dir)
    529 
    530     sh = '%s.sh' % cmd[0]
    531     self.m.run.writefile(self.m.vars.tmp_dir.join(sh),
    532         'set -x; %s%s; echo $? >%src' %
    533         (self.m.vars.android_bin_dir, subprocess.list2cmdline(map(str, cmd)),
    534             self.m.vars.android_bin_dir))
    535     self._adb('push %s' % sh,
    536               'push', self.m.vars.tmp_dir.join(sh), self.m.vars.android_bin_dir)
    537 
    538     self._adb('clear log', 'logcat', '-c')
    539     self.m.python.inline('%s' % cmd[0], """
    540     import subprocess
    541     import sys
    542     bin_dir = sys.argv[1]
    543     sh      = sys.argv[2]
    544     subprocess.check_call(['%s', 'shell', 'sh', bin_dir + sh])
    545     try:
    546       sys.exit(int(subprocess.check_output(['%s', 'shell', 'cat',
    547                                             bin_dir + 'rc'])))
    548     except ValueError:
    549       print "Couldn't read the return code.  Probably killed for OOM."
    550       sys.exit(1)
    551     """ % (self.ADB_BINARY, self.ADB_BINARY),
    552       args=[self.m.vars.android_bin_dir, sh])
    553 
    554   def copy_file_to_device(self, host, device):
    555     self._adb('push %s %s' % (host, device), 'push', host, device)
    556 
    557   def copy_directory_contents_to_device(self, host, device):
    558     # Copy the tree, avoiding hidden directories and resolving symlinks.
    559     self.m.run(self.m.python.inline, 'push %s/* %s' % (host, device),
    560                program="""
    561     import os
    562     import subprocess
    563     import sys
    564     host   = sys.argv[1]
    565     device = sys.argv[2]
    566     for d, _, fs in os.walk(host):
    567       p = os.path.relpath(d, host)
    568       if p != '.' and p.startswith('.'):
    569         continue
    570       for f in fs:
    571         print os.path.join(p,f)
    572         subprocess.check_call(['%s', 'push',
    573                                os.path.realpath(os.path.join(host, p, f)),
    574                                os.path.join(device, p, f)])
    575     """ % self.ADB_BINARY, args=[host, device], infra_step=True)
    576 
    577   def copy_directory_contents_to_host(self, device, host):
    578     self._adb('pull %s %s' % (device, host), 'pull', device, host)
    579 
    580   def read_file_on_device(self, path, **kwargs):
    581     rv = self._adb('read %s' % path,
    582                    'shell', 'cat', path, stdout=self.m.raw_io.output(),
    583                    **kwargs)
    584     return rv.stdout.rstrip() if rv and rv.stdout else None
    585 
    586   def remove_file_on_device(self, path):
    587     self._adb('rm %s' % path, 'shell', 'rm', '-f', path)
    588 
    589   def create_clean_device_dir(self, path):
    590     self._adb('rm %s' % path, 'shell', 'rm', '-rf', path)
    591     self._adb('mkdir %s' % path, 'shell', 'mkdir', '-p', path)
    592