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