| OLD | NEW |
| (Empty) |
| 1 # Copyright 2014 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 | |
| 6 # pylint: disable=W0201 | |
| 7 | |
| 8 | |
| 9 import copy | |
| 10 import default_flavor | |
| 11 | |
| 12 | |
| 13 """Android flavor utils, used for building for and running tests on Android.""" | |
| 14 | |
| 15 | |
| 16 def get_device(api): | |
| 17 builder_cfg = api.builder_name_schema.DictForBuilderName( | |
| 18 api.vars.builder_name) | |
| 19 if 'Android' in builder_cfg.get('extra_config', ''): | |
| 20 # Compile bots. | |
| 21 if 'NoNeon' in builder_cfg['extra_config']: | |
| 22 return 'arm_v7' | |
| 23 return { | |
| 24 'Arm64': 'arm64', | |
| 25 'x86': 'x86', | |
| 26 'x86_64': 'x86_64', | |
| 27 'Mips': 'mips', | |
| 28 'Mips64': 'mips64', | |
| 29 'MipsDSP2': 'mips_dsp2', | |
| 30 }.get(builder_cfg['target_arch'], 'arm_v7_neon') | |
| 31 else: | |
| 32 # Test/Perf bots. | |
| 33 return { | |
| 34 'AndroidOne': 'arm_v7_neon', | |
| 35 'GalaxyS3': 'arm_v7_neon', | |
| 36 'GalaxyS4': 'arm_v7_neon', | |
| 37 'GalaxyS7': 'arm64', | |
| 38 'NVIDIA_Shield': 'arm64', | |
| 39 'Nexus10': 'arm_v7_neon', | |
| 40 'Nexus5': 'arm_v7_neon', | |
| 41 'Nexus6': 'arm_v7_neon', | |
| 42 'Nexus6p': 'arm64', | |
| 43 'Nexus7': 'arm_v7_neon', | |
| 44 'Nexus7v2': 'arm_v7_neon', | |
| 45 'Nexus9': 'arm64', | |
| 46 'NexusPlayer': 'x86', | |
| 47 }[builder_cfg['model']] | |
| 48 | |
| 49 | |
| 50 class _ADBWrapper(object): | |
| 51 """Wrapper for the ADB recipe module. | |
| 52 | |
| 53 The ADB recipe module looks for the ADB binary at a path we don't have checked | |
| 54 out on our bots. This wrapper ensures that we set a custom ADB path before | |
| 55 attempting to use the module. | |
| 56 """ | |
| 57 def __init__(self, m, path_to_adb, serial_args, android_flavor): | |
| 58 self.m = m | |
| 59 self.m.adb.set_adb_path(path_to_adb) | |
| 60 self._has_root = False # This is set in install(). | |
| 61 self._serial_args = serial_args | |
| 62 self._wait_count = 0 | |
| 63 self._android_flavor = android_flavor | |
| 64 | |
| 65 def wait_for_device(self): | |
| 66 """Run 'adb wait-for-device'.""" | |
| 67 self._wait_count += 1 | |
| 68 cmd = [ | |
| 69 self._android_flavor.android_bin.join('adb_wait_for_device') | |
| 70 ] + self._serial_args | |
| 71 self.m.run( | |
| 72 self.m.step, | |
| 73 name='wait for device (%d)' % self._wait_count, | |
| 74 cmd=cmd, | |
| 75 env=self._android_flavor._default_env, | |
| 76 infra_step=True) | |
| 77 | |
| 78 cmd = [ | |
| 79 self._android_flavor.android_bin.join('adb_wait_for_charge'), | |
| 80 ] + self._serial_args | |
| 81 self.m.run( | |
| 82 self.m.step, | |
| 83 name='wait for charge (%d)' % self._wait_count, | |
| 84 cmd=cmd, | |
| 85 env=self._android_flavor._default_env, | |
| 86 infra_step=True) | |
| 87 | |
| 88 def maybe_wait_for_device(self): | |
| 89 """Run 'adb wait-for-device' if it hasn't already been run.""" | |
| 90 if self._wait_count == 0: | |
| 91 self.wait_for_device() | |
| 92 | |
| 93 def __call__(self, *args, **kwargs): | |
| 94 self.maybe_wait_for_device() | |
| 95 return self.m.run(self.m.adb, *args, **kwargs) | |
| 96 | |
| 97 | |
| 98 class AndroidFlavorUtils(default_flavor.DefaultFlavorUtils): | |
| 99 def __init__(self, m): | |
| 100 super(AndroidFlavorUtils, self).__init__(m) | |
| 101 self.device = get_device(m) | |
| 102 self.android_bin = self.m.vars.skia_dir.join( | |
| 103 'platform_tools', 'android', 'bin') | |
| 104 self._android_sdk_root = self.m.vars.slave_dir.join( | |
| 105 'android_sdk', 'android-sdk') | |
| 106 self.serial = None | |
| 107 self.serial_args = [] | |
| 108 try: | |
| 109 path_to_adb = self.m.step( | |
| 110 'which adb', | |
| 111 cmd=['which', 'adb'], | |
| 112 stdout=self.m.raw_io.output(), | |
| 113 infra_step=True).stdout.rstrip() | |
| 114 except self.m.step.StepFailure: | |
| 115 path_to_adb = self.m.path.join(self._android_sdk_root, | |
| 116 'platform-tools', 'adb') | |
| 117 self._adb = _ADBWrapper( | |
| 118 self.m, path_to_adb, self.serial_args, self) | |
| 119 self._default_env = {'ANDROID_SDK_ROOT': self._android_sdk_root, | |
| 120 'ANDROID_HOME': self._android_sdk_root, | |
| 121 'SKIA_ANDROID_VERBOSE_SETUP': 1} | |
| 122 | |
| 123 def step(self, name, cmd, env=None, **kwargs): | |
| 124 self._adb.maybe_wait_for_device() | |
| 125 args = [ | |
| 126 self.android_bin.join('android_run_skia'), | |
| 127 '--verbose', | |
| 128 '--logcat', | |
| 129 '-d', self.device, | |
| 130 ] + self.serial_args + [ | |
| 131 '-t', self.m.vars.configuration, | |
| 132 ] | |
| 133 env = dict(env or {}) | |
| 134 env.update(self._default_env) | |
| 135 | |
| 136 return self.m.run(self.m.step, name=name, cmd=args + cmd, | |
| 137 env=env, **kwargs) | |
| 138 | |
| 139 def compile(self, target, **kwargs): | |
| 140 """Build the given target.""" | |
| 141 env = kwargs.pop('env', {}) | |
| 142 env.update(dict(self._default_env)) | |
| 143 ccache = self.m.run.ccache() | |
| 144 if ccache: | |
| 145 env['ANDROID_MAKE_CCACHE'] = ccache | |
| 146 | |
| 147 cmd = [self.android_bin.join('android_ninja'), target, '-d', self.device] | |
| 148 if 'Clang' in self.m.vars.builder_name: | |
| 149 cmd.append('--clang') | |
| 150 if 'GCC' in self.m.vars.builder_name: | |
| 151 cmd.append('--gcc') | |
| 152 if 'Vulkan' in self.m.vars.builder_name: | |
| 153 cmd.append('--vulkan') | |
| 154 self.m.run(self.m.step, 'build %s' % target, cmd=cmd, | |
| 155 env=env, cwd=self.m.path['checkout'], **kwargs) | |
| 156 | |
| 157 def device_path_join(self, *args): | |
| 158 """Like os.path.join(), but for paths on a connected Android device.""" | |
| 159 return '/'.join(args) | |
| 160 | |
| 161 def device_path_exists(self, path): | |
| 162 """Like os.path.exists(), but for paths on a connected device.""" | |
| 163 exists_str = 'FILE_EXISTS' | |
| 164 return exists_str in self._adb( | |
| 165 name='exists %s' % self.m.path.basename(path), | |
| 166 serial=self.serial, | |
| 167 cmd=['shell', 'if', '[', '-e', path, '];', | |
| 168 'then', 'echo', exists_str + ';', 'fi'], | |
| 169 stdout=self.m.raw_io.output(), | |
| 170 infra_step=True | |
| 171 ).stdout | |
| 172 | |
| 173 def _remove_device_dir(self, path): | |
| 174 """Remove the directory on the device.""" | |
| 175 self._adb(name='rmdir %s' % self.m.path.basename(path), | |
| 176 serial=self.serial, | |
| 177 cmd=['shell', 'rm', '-r', path], | |
| 178 infra_step=True) | |
| 179 # Sometimes the removal fails silently. Verify that it worked. | |
| 180 if self.device_path_exists(path): | |
| 181 raise Exception('Failed to remove %s!' % path) # pragma: no cover | |
| 182 | |
| 183 def _create_device_dir(self, path): | |
| 184 """Create the directory on the device.""" | |
| 185 self._adb(name='mkdir %s' % self.m.path.basename(path), | |
| 186 serial=self.serial, | |
| 187 cmd=['shell', 'mkdir', '-p', path], | |
| 188 infra_step=True) | |
| 189 | |
| 190 def copy_directory_contents_to_device(self, host_dir, device_dir): | |
| 191 """Like shutil.copytree(), but for copying to a connected device.""" | |
| 192 self.m.run( | |
| 193 self.m.step, | |
| 194 name='push %s' % self.m.path.basename(host_dir), | |
| 195 cmd=[ | |
| 196 self.android_bin.join('adb_push_if_needed'), '--verbose', | |
| 197 ] + self.serial_args + [ | |
| 198 host_dir, device_dir, | |
| 199 ], | |
| 200 env=self._default_env, | |
| 201 infra_step=True) | |
| 202 | |
| 203 def copy_directory_contents_to_host(self, device_dir, host_dir): | |
| 204 """Like shutil.copytree(), but for copying from a connected device.""" | |
| 205 self.m.run( | |
| 206 self.m.step, | |
| 207 name='pull %s' % self.m.path.basename(device_dir), | |
| 208 cmd=[ | |
| 209 self.android_bin.join('adb_pull_if_needed'), '--verbose', | |
| 210 ] + self.serial_args + [ | |
| 211 device_dir, host_dir, | |
| 212 ], | |
| 213 env=self._default_env, | |
| 214 infra_step=True) | |
| 215 | |
| 216 def copy_file_to_device(self, host_path, device_path): | |
| 217 """Like shutil.copyfile, but for copying to a connected device.""" | |
| 218 self._adb(name='push %s' % self.m.path.basename(host_path), | |
| 219 serial=self.serial, | |
| 220 cmd=['push', host_path, device_path], | |
| 221 infra_step=True) | |
| 222 | |
| 223 def create_clean_device_dir(self, path): | |
| 224 """Like shutil.rmtree() + os.makedirs(), but on a connected device.""" | |
| 225 self._remove_device_dir(path) | |
| 226 self._create_device_dir(path) | |
| 227 | |
| 228 def has_root(self): | |
| 229 """Determine if we have root access on this device.""" | |
| 230 # Special case: GalaxyS3 hangs on `adb root`. Don't bother. | |
| 231 if 'GalaxyS3' in self.m.vars.builder_name: | |
| 232 return False | |
| 233 | |
| 234 # Determine if we have root access. | |
| 235 has_root = False | |
| 236 try: | |
| 237 output = self._adb(name='adb root', | |
| 238 serial=self.serial, | |
| 239 cmd=['root'], | |
| 240 stdout=self.m.raw_io.output(), | |
| 241 infra_step=True).stdout.rstrip() | |
| 242 if ('restarting adbd as root' in output or | |
| 243 'adbd is already running as root' in output): | |
| 244 has_root = True | |
| 245 except self.m.step.StepFailure: # pragma: nocover | |
| 246 pass | |
| 247 # Wait for the device to reconnect. | |
| 248 self.m.run( | |
| 249 self.m.step, | |
| 250 name='wait', | |
| 251 cmd=['sleep', '10'], | |
| 252 infra_step=True) | |
| 253 self._adb.wait_for_device() | |
| 254 return has_root | |
| 255 | |
| 256 def install(self): | |
| 257 """Run device-specific installation steps.""" | |
| 258 device_scratch_dir = self._adb( | |
| 259 name='get EXTERNAL_STORAGE dir', | |
| 260 serial=self.serial, | |
| 261 cmd=['shell', 'echo', '$EXTERNAL_STORAGE'], | |
| 262 stdout=self.m.raw_io.output(), | |
| 263 infra_step=True, | |
| 264 ).stdout.rstrip() | |
| 265 prefix = self.device_path_join(device_scratch_dir, 'skiabot', 'skia_') | |
| 266 self.device_dirs = default_flavor.DeviceDirs( | |
| 267 dm_dir=prefix + 'dm', | |
| 268 perf_data_dir=prefix + 'perf', | |
| 269 resource_dir=prefix + 'resources', | |
| 270 images_dir=prefix + 'images', | |
| 271 skp_dir=prefix + 'skp/skps', | |
| 272 svg_dir=prefix + 'svg/svgs', | |
| 273 tmp_dir=prefix + 'tmp_dir') | |
| 274 | |
| 275 self._has_root = self.has_root() | |
| 276 self.m.run(self.m.step, | |
| 277 name='kill skia', | |
| 278 cmd=[ | |
| 279 self.android_bin.join('android_kill_skia'), | |
| 280 '--verbose', | |
| 281 ] + self.serial_args, | |
| 282 env=self._default_env, | |
| 283 infra_step=True) | |
| 284 if self._has_root: | |
| 285 self._adb(name='stop shell', | |
| 286 serial=self.serial, | |
| 287 cmd=['shell', 'stop'], | |
| 288 infra_step=True) | |
| 289 | |
| 290 # Print out battery stats. | |
| 291 self._adb(name='starting battery stats', | |
| 292 serial=self.serial, | |
| 293 cmd=['shell', 'dumpsys', 'batteryproperties'], | |
| 294 infra_step=True) | |
| 295 | |
| 296 # Print out CPU scale info. | |
| 297 if self._has_root: | |
| 298 self._adb(name='cat scaling_governor', | |
| 299 serial=self.serial, | |
| 300 cmd=['shell', 'cat', | |
| 301 '/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'], | |
| 302 infra_step=True) | |
| 303 self._adb(name='cat cpu_freq', | |
| 304 serial=self.serial, | |
| 305 cmd=['shell', 'cat', | |
| 306 '/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq'], | |
| 307 infra_step=True) | |
| 308 | |
| 309 def cleanup_steps(self): | |
| 310 """Run any device-specific cleanup steps.""" | |
| 311 if self.m.vars.role in (self.m.builder_name_schema.BUILDER_ROLE_TEST, | |
| 312 self.m.builder_name_schema.BUILDER_ROLE_PERF): | |
| 313 self._adb(name='final battery stats', | |
| 314 serial=self.serial, | |
| 315 cmd=['shell', 'dumpsys', 'batteryproperties'], | |
| 316 infra_step=True) | |
| 317 self._adb(name='reboot', | |
| 318 serial=self.serial, | |
| 319 cmd=['reboot'], | |
| 320 infra_step=True) | |
| 321 self.m.run( | |
| 322 self.m.step, | |
| 323 name='wait for reboot', | |
| 324 cmd=['sleep', '10'], | |
| 325 infra_step=True) | |
| 326 self._adb.wait_for_device() | |
| 327 # The ADB binary conflicts with py-adb used by swarming. Kill it | |
| 328 # when finished to play nice. | |
| 329 self._adb(name='kill-server', | |
| 330 serial=self.serial, | |
| 331 cmd=['kill-server'], | |
| 332 infra_step=True) | |
| 333 | |
| 334 def read_file_on_device(self, path, *args, **kwargs): | |
| 335 """Read the given file.""" | |
| 336 return self._adb(name='read %s' % self.m.path.basename(path), | |
| 337 serial=self.serial, | |
| 338 cmd=['shell', 'cat', path], | |
| 339 stdout=self.m.raw_io.output(), | |
| 340 infra_step=True).stdout.rstrip() | |
| 341 | |
| 342 def remove_file_on_device(self, path, *args, **kwargs): | |
| 343 """Delete the given file.""" | |
| 344 return self._adb(name='rm %s' % self.m.path.basename(path), | |
| 345 serial=self.serial, | |
| 346 cmd=['shell', 'rm', '-f', path], | |
| 347 infra_step=True, | |
| 348 *args, | |
| 349 **kwargs) | |
| OLD | NEW |