blob: 4dd1093450822f6b20c347a7b655a45508ab0101 [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from recipe_engine import recipe_api
import default_flavor
import subprocess
"""GN Android flavor utils, used for building Skia for Android with GN."""
class GNAndroidFlavorUtils(default_flavor.DefaultFlavorUtils):
def __init__(self, m):
super(GNAndroidFlavorUtils, self).__init__(m)
self._ever_ran_adb = False
self.device_dirs = default_flavor.DeviceDirs(
dm_dir = self.m.vars.android_data_dir + 'dm_out',
perf_data_dir = self.m.vars.android_data_dir + 'perf',
resource_dir = self.m.vars.android_data_dir + 'resources',
images_dir = self.m.vars.android_data_dir + 'images',
skp_dir = self.m.vars.android_data_dir + 'skps',
svg_dir = self.m.vars.android_data_dir + 'svgs',
tmp_dir = self.m.vars.android_data_dir)
def _run(self, title, *cmd, **kwargs):
with self.m.context(cwd=self.m.vars.skia_dir):
return self.m.run(self.m.step, title, cmd=list(cmd), **kwargs)
def _py(self, title, script, infra_step=True):
with self.m.context(cwd=self.m.vars.skia_dir):
return self.m.run(self.m.python, title, script=script,
infra_step=infra_step)
def _adb(self, title, *cmd, **kwargs):
self._ever_ran_adb = True
# The only non-infra adb steps (dm / nanobench) happen to not use _adb().
if 'infra_step' not in kwargs:
kwargs['infra_step'] = True
return self._run(title, 'adb', *cmd, **kwargs)
def compile(self, unused_target):
compiler = self.m.vars.builder_cfg.get('compiler')
configuration = self.m.vars.builder_cfg.get('configuration')
extra_config = self.m.vars.builder_cfg.get('extra_config', '')
os = self.m.vars.builder_cfg.get('os')
target_arch = self.m.vars.builder_cfg.get('target_arch')
assert compiler == 'Clang' # At this rate we might not ever support GCC.
extra_cflags = []
if configuration == 'Debug':
extra_cflags.append('-O1')
ndk_asset = 'android_ndk_linux'
if 'Mac' in os:
ndk_asset = 'android_ndk_darwin'
elif 'Win' in os:
ndk_asset = 'n'
quote = lambda x: '"%s"' % x
args = {
'ndk': quote(self.m.vars.slave_dir.join(ndk_asset)),
'target_cpu': quote(target_arch),
}
if configuration != 'Debug':
args['is_debug'] = 'false'
if 'Vulkan' in extra_config:
args['ndk_api'] = 24
args['skia_enable_vulkan_debug_layers'] = 'false'
if extra_cflags:
args['extra_cflags'] = repr(extra_cflags).replace("'", '"')
gn_args = ' '.join('%s=%s' % (k,v) for (k,v) in sorted(args.iteritems()))
gn = 'gn.exe' if 'Win' in os else 'gn'
ninja = 'ninja.exe' if 'Win' in os else 'ninja'
gn = self.m.vars.skia_dir.join('bin', gn)
self._py('fetch-gn', self.m.vars.skia_dir.join('bin', 'fetch-gn'))
self._run('gn gen', gn, 'gen', self.out_dir, '--args=' + gn_args)
self._run('ninja', ninja, '-C', self.out_dir)
def install(self):
self._adb('mkdir ' + self.device_dirs.resource_dir,
'shell', 'mkdir', '-p', self.device_dirs.resource_dir)
def cleanup_steps(self):
if self._ever_ran_adb:
self.m.run(self.m.python.inline, 'dump log', program="""
import os
import subprocess
import sys
out = sys.argv[1]
log = subprocess.check_output(['adb', 'logcat', '-d'])
for line in log.split('\\n'):
tokens = line.split()
if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':
addr, path = tokens[-2:]
local = os.path.join(out, os.path.basename(path))
if os.path.exists(local):
sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])
line = line.replace(addr, addr + ' ' + sym.strip())
print line
""",
args=[self.m.vars.skia_out.join(self.m.vars.configuration)],
infra_step=True,
abort_on_failure=False)
# Only shutdown the device and quarantine the bot if the first failed step
# is an infra step. If, instead, we did this for any infra failures, we
# would shutdown too much. For example, if a Nexus 10 died during dm
# and the following pull step would also fail "device not found" - causing
# us to run the shutdown command when the device was probably not in a
# broken state; it was just rebooting.
if (self.m.run.failed_steps and
isinstance(self.m.run.failed_steps[0], recipe_api.InfraFailure)):
self._adb('shut down device to quarantine bot', 'shell', 'reboot', '-p')
if self._ever_ran_adb:
self._adb('kill adb server', 'kill-server')
def step(self, name, cmd, **kwargs):
app = self.m.vars.skia_out.join(self.m.vars.configuration, cmd[0])
self._adb('push %s' % cmd[0],
'push', app, self.m.vars.android_bin_dir)
sh = '%s.sh' % cmd[0]
self.m.run.writefile(self.m.vars.tmp_dir.join(sh),
'set -x; %s%s; echo $? >%src' %
(self.m.vars.android_bin_dir, subprocess.list2cmdline(map(str, cmd)),
self.m.vars.android_bin_dir))
self._adb('push %s' % sh,
'push', self.m.vars.tmp_dir.join(sh), self.m.vars.android_bin_dir)
self._adb('clear log', 'logcat', '-c')
self.m.python.inline('%s' % cmd[0], """
import subprocess
import sys
bin_dir = sys.argv[1]
sh = sys.argv[2]
subprocess.check_call(['adb', 'shell', 'sh', bin_dir + sh])
try:
sys.exit(int(subprocess.check_output(['adb', 'shell', 'cat',
bin_dir + 'rc'])))
except ValueError:
print "Couldn't read the return code. Probably killed for OOM."
sys.exit(1)
""", args=[self.m.vars.android_bin_dir, sh])
def copy_file_to_device(self, host, device):
self._adb('push %s %s' % (host, device), 'push', host, device)
def copy_directory_contents_to_device(self, host, device):
# Copy the tree, avoiding hidden directories and resolving symlinks.
self.m.run(self.m.python.inline, 'push %s/* %s' % (host, device),
program="""
import os
import subprocess
import sys
host = sys.argv[1]
device = sys.argv[2]
for d, _, fs in os.walk(host):
p = os.path.relpath(d, host)
if p != '.' and p.startswith('.'):
continue
for f in fs:
print os.path.join(p,f)
subprocess.check_call(['adb', 'push',
os.path.realpath(os.path.join(host, p, f)),
os.path.join(device, p, f)])
""", args=[host, device], infra_step=True)
def copy_directory_contents_to_host(self, device, host):
self._adb('pull %s %s' % (device, host), 'pull', device, host)
def read_file_on_device(self, path, **kwargs):
rv = self._adb('read %s' % path,
'shell', 'cat', path, stdout=self.m.raw_io.output(),
**kwargs)
return rv.stdout.rstrip() if rv and rv.stdout else None
def remove_file_on_device(self, path):
self._adb('rm %s' % path, 'shell', 'rm', '-f', path)
def create_clean_device_dir(self, path):
self._adb('rm %s' % path, 'shell', 'rm', '-rf', path)
self._adb('mkdir %s' % path, 'shell', 'mkdir', '-p', path)