• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2016 Google Inc.
4#
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8
9import android_devices
10import default_flavor
11import os
12
13
14"""Android flavor utils, used for building for and running tests on Android."""
15
16
17class _ADBWrapper(object):
18  """Wrapper for ADB."""
19  def __init__(self, path_to_adb, serial, android_flavor):
20    self._adb = path_to_adb
21    self._serial = serial
22    self._wait_count = 0
23    self._android_flavor = android_flavor
24
25  def wait_for_device(self):
26    """Run 'adb wait-for-device'."""
27    self._wait_count += 1
28    cmd = [
29        os.path.join(self._android_flavor.android_bin, 'adb_wait_for_device'),
30        '-s', self._serial,
31    ]
32    self._android_flavor._bot_info.run(
33        cmd, env=self._android_flavor._default_env)
34
35  def maybe_wait_for_device(self):
36    """Run 'adb wait-for-device' if it hasn't already been run."""
37    if self._wait_count == 0:
38      self.wait_for_device()
39
40  def __call__(self, *args, **kwargs):
41    self.maybe_wait_for_device()
42    return self._android_flavor._bot_info.run(self._adb + args, **kwargs)
43
44
45class AndroidFlavorUtils(default_flavor.DefaultFlavorUtils):
46  def __init__(self, skia_api):
47    super(AndroidFlavorUtils, self).__init__(skia_api)
48    self.device = self._bot_info.spec['device_cfg']
49    slave_info = android_devices.SLAVE_INFO.get(
50        self._bot_info.slave_name,
51        android_devices.SLAVE_INFO['default'])
52    self.serial = slave_info.serial
53    self.android_bin = os.path.join(
54        self._bot_info.skia_dir, 'platform_tools', 'android', 'bin')
55    self._android_sdk_root = slave_info.android_sdk_root
56    self._adb = _ADBWrapper(
57        os.path.join(self._android_sdk_root, 'platform-tools', 'adb'),
58        self.serial,
59        self)
60    self._has_root = slave_info.has_root
61    self._default_env = {'ANDROID_SDK_ROOT': self._android_sdk_root,
62                         'ANDROID_HOME': self._android_sdk_root,
63                         'SKIA_ANDROID_VERBOSE_SETUP': '1'}
64
65  def step(self, name, cmd, env=None, **kwargs):
66    self._adb.maybe_wait_for_device()
67    args = [self.android_bin.join('android_run_skia'),
68            '--verbose',
69            '--logcat',
70            '-d', self.device,
71            '-s', self.serial,
72            '-t', self._bot_info.configuration,
73    ]
74    env = dict(env or {})
75    env.update(self._default_env)
76
77    return self._bot_info.run(self._bot_info.m.step, name=name, cmd=args + cmd,
78                              env=env, **kwargs)
79
80  def compile(self, target):
81    """Build the given target."""
82    env = dict(self._default_env)
83    ccache = self._bot_info.ccache
84    if ccache:
85      env['ANDROID_MAKE_CCACHE'] = ccache
86
87    cmd = [os.path.join(self.android_bin, 'android_ninja'), target,
88           '-d', self.device]
89    if 'Clang' in self._bot_info.name:
90      cmd.append('--clang')
91    self._bot_info.run(cmd, env=env)
92
93  def device_path_join(self, *args):
94    """Like os.path.join(), but for paths on a connected Android device."""
95    return '/'.join(args)
96
97  def device_path_exists(self, path):
98    """Like os.path.exists(), but for paths on a connected device."""
99    exists_str = 'FILE_EXISTS'
100    return exists_str in self._adb(
101        name='exists %s' % self._bot_info.m.path.basename(path),
102        serial=self.serial,
103        cmd=['shell', 'if', '[', '-e', path, '];',
104             'then', 'echo', exists_str + ';', 'fi'],
105        stdout=self._bot_info.m.raw_io.output(),
106        infra_step=True
107    ).stdout
108
109  def _remove_device_dir(self, path):
110    """Remove the directory on the device."""
111    self._adb(name='rmdir %s' % self._bot_info.m.path.basename(path),
112              serial=self.serial,
113              cmd=['shell', 'rm', '-r', path],
114              infra_step=True)
115    # Sometimes the removal fails silently. Verify that it worked.
116    if self.device_path_exists(path):
117      raise Exception('Failed to remove %s!' % path)  # pragma: no cover
118
119  def _create_device_dir(self, path):
120    """Create the directory on the device."""
121    self._adb(name='mkdir %s' % self._bot_info.m.path.basename(path),
122              serial=self.serial,
123              cmd=['shell', 'mkdir', '-p', path],
124              infra_step=True)
125
126  def copy_directory_contents_to_device(self, host_dir, device_dir):
127    """Like shutil.copytree(), but for copying to a connected device."""
128    self._bot_info.run(
129        self._bot_info.m.step,
130        name='push %s' % self._bot_info.m.path.basename(host_dir),
131        cmd=[self.android_bin.join('adb_push_if_needed'), '--verbose',
132             '-s', self.serial, host_dir, device_dir],
133        env=self._default_env,
134        infra_step=True)
135
136  def copy_directory_contents_to_host(self, device_dir, host_dir):
137    """Like shutil.copytree(), but for copying from a connected device."""
138    self._bot_info.run(
139        self._bot_info.m.step,
140        name='pull %s' % self._bot_info.m.path.basename(device_dir),
141        cmd=[self.android_bin.join('adb_pull_if_needed'), '--verbose',
142             '-s', self.serial, device_dir, host_dir],
143        env=self._default_env,
144        infra_step=True)
145
146  def copy_file_to_device(self, host_path, device_path):
147    """Like shutil.copyfile, but for copying to a connected device."""
148    self._adb(name='push %s' % self._bot_info.m.path.basename(host_path),
149              serial=self.serial,
150              cmd=['push', host_path, device_path],
151              infra_step=True)
152
153  def create_clean_device_dir(self, path):
154    """Like shutil.rmtree() + os.makedirs(), but on a connected device."""
155    self._remove_device_dir(path)
156    self._create_device_dir(path)
157
158  def install(self):
159    """Run device-specific installation steps."""
160    if self._has_root:
161      self._adb(name='adb root',
162                serial=self.serial,
163                cmd=['root'],
164                infra_step=True)
165      # Wait for the device to reconnect.
166      self._bot_info.run(
167          self._bot_info.m.step,
168          name='wait',
169          cmd=['sleep', '10'],
170          infra_step=True)
171      self._adb.wait_for_device()
172
173    # TODO(borenet): Set CPU scaling mode to 'performance'.
174    self._bot_info.run(self._bot_info.m.step,
175                       name='kill skia',
176                       cmd=[self.android_bin.join('android_kill_skia'),
177                            '--verbose', '-s', self.serial],
178                       env=self._default_env,
179                       infra_step=True)
180    if self._has_root:
181      self._adb(name='stop shell',
182                serial=self.serial,
183                cmd=['shell', 'stop'],
184                infra_step=True)
185
186    # Print out battery stats.
187    self._adb(name='starting battery stats',
188              serial=self.serial,
189              cmd=['shell', 'dumpsys', 'batteryproperties'],
190              infra_step=True)
191
192  def cleanup_steps(self):
193    """Run any device-specific cleanup steps."""
194    self._adb(name='final battery stats',
195              serial=self.serial,
196              cmd=['shell', 'dumpsys', 'batteryproperties'],
197              infra_step=True)
198    self._adb(name='reboot',
199              serial=self.serial,
200              cmd=['reboot'],
201              infra_step=True)
202    self._bot_info.run(
203        self._bot_info.m.step,
204        name='wait for reboot',
205        cmd=['sleep', '10'],
206        infra_step=True)
207    self._adb.wait_for_device()
208
209  def read_file_on_device(self, path, *args, **kwargs):
210    """Read the given file."""
211    return self._adb(name='read %s' % self._bot_info.m.path.basename(path),
212                     serial=self.serial,
213                     cmd=['shell', 'cat', path],
214                     stdout=self._bot_info.m.raw_io.output(),
215                     infra_step=True).stdout.rstrip()
216
217  def remove_file_on_device(self, path, *args, **kwargs):
218    """Delete the given file."""
219    return self._adb(name='rm %s' % self._bot_info.m.path.basename(path),
220                     serial=self.serial,
221                     cmd=['shell', 'rm', '-f', path],
222                     infra_step=True,
223                     *args,
224                     **kwargs)
225
226  def get_device_dirs(self):
227    """ Set the directories which will be used by the build steps."""
228    device_scratch_dir = self._adb(
229        name='get EXTERNAL_STORAGE dir',
230        serial=self.serial,
231        cmd=['shell', 'echo', '$EXTERNAL_STORAGE'],
232    )
233    prefix = self.device_path_join(device_scratch_dir, 'skiabot', 'skia_')
234    return default_flavor.DeviceDirs(
235        dm_dir=prefix + 'dm',
236        perf_data_dir=prefix + 'perf',
237        resource_dir=prefix + 'resources',
238        images_dir=prefix + 'images',
239        skp_dir=prefix + 'skp/skps',
240        tmp_dir=prefix + 'tmp_dir')
241
242