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 7import default_flavor 8import subprocess 9 10 11"""GN Android flavor utils, used for building Skia for Android with GN.""" 12class GNAndroidFlavorUtils(default_flavor.DefaultFlavorUtils): 13 def __init__(self, m): 14 super(GNAndroidFlavorUtils, self).__init__(m) 15 self._ever_ran_adb = False 16 17 self.device_dirs = default_flavor.DeviceDirs( 18 dm_dir = self.m.vars.android_data_dir + 'dm_out', 19 perf_data_dir = self.m.vars.android_data_dir + 'perf', 20 resource_dir = self.m.vars.android_data_dir + 'resources', 21 images_dir = self.m.vars.android_data_dir + 'images', 22 skp_dir = self.m.vars.android_data_dir + 'skps', 23 svg_dir = self.m.vars.android_data_dir + 'svgs', 24 tmp_dir = self.m.vars.android_data_dir) 25 26 def _run(self, title, *cmd, **kwargs): 27 with self.m.step.context({'cwd': self.m.vars.skia_dir}): 28 return self.m.run(self.m.step, title, cmd=list(cmd), **kwargs) 29 30 def _py(self, title, script, infra_step=True): 31 with self.m.step.context({'cwd': self.m.vars.skia_dir}): 32 return self.m.run(self.m.python, title, script=script, 33 infra_step=infra_step) 34 35 def _adb(self, title, *cmd, **kwargs): 36 self._ever_ran_adb = True 37 # The only non-infra adb steps (dm / nanobench) happen to not use _adb(). 38 if 'infra_step' not in kwargs: 39 kwargs['infra_step'] = True 40 return self._run(title, 'adb', *cmd, **kwargs) 41 42 # Waits for an android device to be available 43 def _wait_for_device(self): 44 self.m.run(self.m.python.inline, 'wait for device', program=""" 45 import subprocess 46 import sys 47 import time 48 49 kicks = 0 50 while True: 51 52 times = 0 53 while times < 30: 54 print 'Waiting for the device to be connected and ready.' 55 try: 56 times += 1 57 output = subprocess.check_output(['adb', 'shell', 58 'getprop', 'sys.boot_completed']) 59 if '1' in output: 60 print 'Connected' 61 sys.exit(0) 62 except subprocess.CalledProcessError: 63 # no device connected/authorized yet 64 pass 65 time.sleep(5) 66 if kicks >= 3: 67 break 68 print 'Giving the device a "kick" by trying to reboot it.' 69 kicks += 1 70 print subprocess.check_output(['adb', 'reboot']) 71 72 print 'Timed out waiting for device' 73 sys.exit(1) 74 """, 75 infra_step=True) 76 77 78 def compile(self, unused_target): 79 compiler = self.m.vars.builder_cfg.get('compiler') 80 configuration = self.m.vars.builder_cfg.get('configuration') 81 extra_config = self.m.vars.builder_cfg.get('extra_config', '') 82 os = self.m.vars.builder_cfg.get('os') 83 target_arch = self.m.vars.builder_cfg.get('target_arch') 84 85 assert compiler == 'Clang' # At this rate we might not ever support GCC. 86 87 extra_cflags = [] 88 if configuration == 'Debug': 89 extra_cflags.append('-O1') 90 91 ndk_asset = 'android_ndk_linux' 92 if 'Mac' in os: 93 ndk_asset = 'android_ndk_darwin' 94 elif 'Win' in os: 95 ndk_asset = 'n' 96 97 quote = lambda x: '"%s"' % x 98 args = { 99 'ndk': quote(self.m.vars.slave_dir.join(ndk_asset)), 100 'target_cpu': quote(target_arch), 101 } 102 103 if configuration != 'Debug': 104 args['is_debug'] = 'false' 105 if 'Vulkan' in extra_config: 106 args['ndk_api'] = 24 107 args['skia_enable_vulkan_debug_layers'] = 'false' 108 if 'FrameworkDefs' in extra_config: 109 args['skia_enable_android_framework_defines'] = 'true' 110 if extra_cflags: 111 args['extra_cflags'] = repr(extra_cflags).replace("'", '"') 112 113 gn_args = ' '.join('%s=%s' % (k,v) for (k,v) in sorted(args.iteritems())) 114 115 gn = 'gn.exe' if 'Win' in os else 'gn' 116 ninja = 'ninja.exe' if 'Win' in os else 'ninja' 117 gn = self.m.vars.skia_dir.join('bin', gn) 118 119 self._py('fetch-gn', self.m.vars.skia_dir.join('bin', 'fetch-gn')) 120 self._run('gn gen', gn, 'gen', self.out_dir, '--args=' + gn_args) 121 self._run('ninja', ninja, '-C', self.out_dir) 122 123 def install(self): 124 reboot_always = ['NexusPlayer', 'PixelC'] 125 if self.m.vars.builder_cfg.get('model') in reboot_always: 126 self._adb('rebooting device', 'reboot') 127 self._wait_for_device() 128 self._adb('mkdir ' + self.device_dirs.resource_dir, 129 'shell', 'mkdir', '-p', self.device_dirs.resource_dir) 130 131 132 def cleanup_steps(self): 133 if self._ever_ran_adb: 134 self.m.run(self.m.python.inline, 'dump log', program=""" 135 import os 136 import subprocess 137 import sys 138 out = sys.argv[1] 139 log = subprocess.check_output(['adb', 'logcat', '-d']) 140 for line in log.split('\\n'): 141 tokens = line.split() 142 if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc': 143 addr, path = tokens[-2:] 144 local = os.path.join(out, os.path.basename(path)) 145 if os.path.exists(local): 146 sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr]) 147 line = line.replace(addr, addr + ' ' + sym.strip()) 148 print line 149 """, 150 args=[self.m.vars.skia_out.join(self.m.vars.configuration)], 151 infra_step=True) 152 153 # Only shutdown the device and quarantine the bot if the first failed step 154 # is an infra step. If, instead, we did this for any infra failures, we 155 # would shutdown too much. For example, if a Nexus 10 died during dm 156 # and the following pull step would also fail "device not found" - causing 157 # us to run the shutdown command when the device was probably not in a 158 # broken state; it was just rebooting. 159 if (self.m.run.failed_steps and 160 isinstance(self.m.run.failed_steps[0], recipe_api.InfraFailure)): 161 self._adb('shut down device to quarantine bot', 'shell', 'reboot', '-p') 162 163 if self._ever_ran_adb: 164 self._adb('kill adb server', 'kill-server') 165 166 def step(self, name, cmd, **kwargs): 167 app = self.m.vars.skia_out.join(self.m.vars.configuration, cmd[0]) 168 self._adb('push %s' % cmd[0], 169 'push', app, self.m.vars.android_bin_dir) 170 171 sh = '%s.sh' % cmd[0] 172 self.m.run.writefile(self.m.vars.tmp_dir.join(sh), 173 'set -x; %s%s; echo $? >%src' % 174 (self.m.vars.android_bin_dir, subprocess.list2cmdline(map(str, cmd)), 175 self.m.vars.android_bin_dir)) 176 self._adb('push %s' % sh, 177 'push', self.m.vars.tmp_dir.join(sh), self.m.vars.android_bin_dir) 178 179 self._adb('clear log', 'logcat', '-c') 180 self.m.python.inline('%s' % cmd[0], """ 181 import subprocess 182 import sys 183 bin_dir = sys.argv[1] 184 sh = sys.argv[2] 185 subprocess.check_call(['adb', 'shell', 'sh', bin_dir + sh]) 186 try: 187 sys.exit(int(subprocess.check_output(['adb', 'shell', 'cat', 188 bin_dir + 'rc']))) 189 except ValueError: 190 print "Couldn't read the return code. Probably killed for OOM." 191 sys.exit(1) 192 """, args=[self.m.vars.android_bin_dir, sh]) 193 194 def copy_file_to_device(self, host, device): 195 self._adb('push %s %s' % (host, device), 'push', host, device) 196 197 def copy_directory_contents_to_device(self, host, device): 198 # Copy the tree, avoiding hidden directories and resolving symlinks. 199 self.m.run(self.m.python.inline, 'push %s/* %s' % (host, device), 200 program=""" 201 import os 202 import subprocess 203 import sys 204 host = sys.argv[1] 205 device = sys.argv[2] 206 for d, _, fs in os.walk(host): 207 p = os.path.relpath(d, host) 208 if p != '.' and p.startswith('.'): 209 continue 210 for f in fs: 211 print os.path.join(p,f) 212 subprocess.check_call(['adb', 'push', 213 os.path.realpath(os.path.join(host, p, f)), 214 os.path.join(device, p, f)]) 215 """, args=[host, device], infra_step=True) 216 217 def copy_directory_contents_to_host(self, device, host): 218 self._adb('pull %s %s' % (device, host), 'pull', device, host) 219 220 def read_file_on_device(self, path): 221 return self._adb('read %s' % path, 222 'shell', 'cat', path, stdout=self.m.raw_io.output()).stdout 223 224 def remove_file_on_device(self, path): 225 self._adb('rm %s' % path, 'shell', 'rm', '-f', path) 226 227 def create_clean_device_dir(self, path): 228 self._adb('rm %s' % path, 'shell', 'rm', '-rf', path) 229 self._adb('mkdir %s' % path, 'shell', 'mkdir', '-p', path) 230