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 from . import android
      8 from . import default
      9 
     10 
     11 """Chromecast flavor, used for running code on Chromecast"""
     12 
     13 
     14 class ChromecastFlavor(android.AndroidFlavor):
     15   def __init__(self, m):
     16     super(ChromecastFlavor, self).__init__(m)
     17     self._ever_ran_adb = False
     18     self._user_ip = ''
     19 
     20     # Disk space is extremely tight on the Chromecasts (~100M) There is not
     21     # enough space on the android_data_dir (/cache/skia) to fit the images,
     22     # resources, executable and output the dm images.  So, we have dm_out be
     23     # on the tempfs (i.e. RAM) /dev/shm. (which is about 140M)
     24     data_dir = '/cache/skia/'
     25     self.device_dirs = default.DeviceDirs(
     26         bin_dir       = '/cache/skia/bin',
     27         dm_dir        = '/dev/shm/skia/dm_out',
     28         perf_data_dir = data_dir + 'perf',
     29         resource_dir  = data_dir + 'resources',
     30         images_dir    = data_dir + 'images',
     31         lotties_dir   = data_dir + 'lotties',
     32         skp_dir       = data_dir + 'skps',
     33         svg_dir       = data_dir + 'svgs',
     34         tmp_dir       = data_dir)
     35 
     36   @property
     37   def user_ip_host(self):
     38     if not self._user_ip:
     39       self._user_ip = self.m.run(self.m.python.inline, 'read chromecast ip',
     40                                  program="""
     41       import os
     42       CHROMECAST_IP_FILE = os.path.expanduser('~/chromecast.txt')
     43       with open(CHROMECAST_IP_FILE, 'r') as f:
     44         print f.read()
     45       """,
     46       stdout=self.m.raw_io.output(),
     47       infra_step=True).stdout
     48 
     49     return self._user_ip
     50 
     51   @property
     52   def user_ip(self):
     53     return self.user_ip_host.split(':')[0]
     54 
     55   def install(self):
     56     super(ChromecastFlavor, self).install()
     57     self._adb('mkdir ' + self.device_dirs.bin_dir,
     58               'shell', 'mkdir', '-p', self.device_dirs.bin_dir)
     59 
     60   def _adb(self, title, *cmd, **kwargs):
     61     if not self._ever_ran_adb:
     62       self._connect_to_remote()
     63 
     64     self._ever_ran_adb = True
     65     # The only non-infra adb steps (dm / nanobench) happen to not use _adb().
     66     if 'infra_step' not in kwargs:
     67       kwargs['infra_step'] = True
     68     return self._run(title, 'adb', *cmd, **kwargs)
     69 
     70   def _connect_to_remote(self):
     71     self.m.run(self.m.step, 'adb connect %s' % self.user_ip_host, cmd=['adb',
     72       'connect', self.user_ip_host], infra_step=True)
     73 
     74   def create_clean_device_dir(self, path):
     75     # Note: Chromecast does not support -rf
     76     self._adb('rm %s' % path, 'shell', 'rm', '-r', path)
     77     self._adb('mkdir %s' % path, 'shell', 'mkdir', '-p', path)
     78 
     79   def copy_directory_contents_to_device(self, host, device):
     80     # Copy the tree, avoiding hidden directories and resolving symlinks.
     81     # Additionally, due to space restraints, we don't push files > 3 MB
     82     # which cuts down the size of the SKP asset to be around 50 MB as of
     83     # version 41.
     84     self.m.run(self.m.python.inline, 'push %s/* %s' % (host, device),
     85                program="""
     86     import os
     87     import subprocess
     88     import sys
     89     host   = sys.argv[1]
     90     device = sys.argv[2]
     91     for d, _, fs in os.walk(host):
     92       p = os.path.relpath(d, host)
     93       if p != '.' and p.startswith('.'):
     94         continue
     95       for f in fs:
     96         print os.path.join(p,f)
     97         hp = os.path.realpath(os.path.join(host, p, f))
     98         if os.stat(hp).st_size > (1.5 * 1024 * 1024):
     99           print "Skipping because it is too big"
    100         else:
    101           subprocess.check_call(['adb', 'push',
    102                                 hp, os.path.join(device, p, f)])
    103     """, args=[host, device], infra_step=True)
    104 
    105   def cleanup_steps(self):
    106     if self._ever_ran_adb:
    107       # To clean up disk space for next time
    108       self._ssh('Delete executables', 'rm', '-r', self.device_dirs.bin_dir,
    109                 abort_on_failure=False, infra_step=True)
    110       # Reconnect if was disconnected
    111       self._adb('disconnect', 'disconnect')
    112       self._connect_to_remote()
    113       self.m.run(self.m.python.inline, 'dump log', program="""
    114           import os
    115           import subprocess
    116           import sys
    117           out = sys.argv[1]
    118           log = subprocess.check_output(['adb', 'logcat', '-d'])
    119           for line in log.split('\\n'):
    120             tokens = line.split()
    121             if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':
    122               addr, path = tokens[-2:]
    123               local = os.path.join(out, os.path.basename(path))
    124               if os.path.exists(local):
    125                 sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])
    126                 line = line.replace(addr, addr + ' ' + sym.strip())
    127             print line
    128           """,
    129           args=[self.host_dirs.bin_dir],
    130           infra_step=True,
    131           abort_on_failure=False)
    132 
    133       self._adb('disconnect', 'disconnect')
    134       self._adb('kill adb server', 'kill-server')
    135 
    136   def _ssh(self, title, *cmd, **kwargs):
    137     # Don't use -t -t (Force psuedo-tty allocation) like in the ChromeOS
    138     # version because the pseudo-tty allocation seems to fail
    139     # instantly when talking to a Chromecast.
    140     # This was excacerbated when we migrated to kitchen and was marked by
    141     # the symptoms of all the ssh commands instantly failing (even after
    142     # connecting and authenticating) with exit code -1 (255)
    143     ssh_cmd = ['ssh', '-oConnectTimeout=15', '-oBatchMode=yes',
    144                '-T', 'root@%s' % self.user_ip] + list(cmd)
    145 
    146     return self.m.run(self.m.step, title, cmd=ssh_cmd, **kwargs)
    147 
    148   def step(self, name, cmd, **kwargs):
    149     app = self.host_dirs.bin_dir.join(cmd[0])
    150 
    151     self._adb('push %s' % cmd[0],
    152               'push', app, self.device_dirs.bin_dir)
    153 
    154     cmd[0] = '%s/%s' % (self.device_dirs.bin_dir, cmd[0])
    155     self._ssh(str(name), *cmd, infra_step=False)
    156