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