17fbcd145b
This CL fixes a bug in the logic that checks whether there's an image mounted on the attached iOS device. Currently this is determined by checking whether strings "ImagePresent: true" or "ImageSignature:" are present in the output of command "ideviceimagemounter --list". However, these strings do not match the command's actual output: # No image mounted. $ ideviceimagemounter --list Status: Complete # Image mounted (fake signature for illustrative purposes). $ ideviceimagemounter --list ImageSignature[1]: 0: Rm9yIGlsbHVzdHJhdGl2ZSBwdXJwb3NlcyBvbmx5LiBUaGlzIGlzIG5vdCBhIHJlYWwgc2lnbmF0dXJlLg== Status: Complete As is, the recipe fails to detect that an image is already mounted on the attached iOS device. The recipe then tries to mount one, which makes the "ideviceimagemounter" command fail with "Error: ImageAlreadyMounted". The following example shows how to reproduce this error: # Initially no image is mounted. $ ideviceimagemounter --list Status: Complete # Mount an image. $ ideviceimagemounter DeveloperDiskImage.dmg DeveloperDiskImage.dmg.signature Uploading DeveloperDiskImage.dmg done. Mounting... Done. Status: Complete # Verify that the image is mounted. $ ideviceimagemounter --list ImageSignature[1]: 0: Rm9yIGlsbHVzdHJhdGl2ZSBwdXJwb3NlcyBvbmx5LiBUaGlzIGlzIG5vdCBhIHJlYWwgc2lnbmF0dXJlLg== Status: Complete # Try to mount an image when one is already mounted. $ ideviceimagemounter DeveloperDiskImage.dmg DeveloperDiskImage.dmg.signature Uploading DeveloperDiskImage.dmg done. Mounting... Error: ImageMountFailed Checking for presence of string "ImageSignature" in the ideviceimagemounter command's output seems to solve the problem. In the process I also discovered that self._run() requires keyword argument stdout=self.m.raw_io.output() in order to return the command's output. As is, the returned stdout is always blank. Note to reviewer: - Patchsets 1 to 11 iterate on a potential fix. - Patchset 12 reproduces the bug. See the failing tryjob. - Patchset 13 tries the fix. See the successful tryjob. - Patchsets 14+ clean up the CL. Change-Id: I3ca1cdbf4bfa450c3e87d6d87b5f615f28c4aaba Reviewed-on: https://skia-review.googlesource.com/c/skia/+/281058 Reviewed-by: Weston Tracey <westont@google.com> Commit-Queue: Leandro Lovisolo <lovisolo@google.com>
168 lines
5.9 KiB
Python
168 lines
5.9 KiB
Python
# Copyright 2017 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 . import default
|
|
|
|
|
|
"""iOS flavor, used for running code on iOS."""
|
|
|
|
|
|
class iOSFlavor(default.DefaultFlavor):
|
|
def __init__(self, m, app_name):
|
|
super(iOSFlavor, self).__init__(m, app_name)
|
|
self.device_dirs = default.DeviceDirs(
|
|
bin_dir='[unused]',
|
|
dm_dir='dm',
|
|
perf_data_dir='perf',
|
|
resource_dir='resources',
|
|
images_dir='images',
|
|
lotties_dir='lotties',
|
|
skp_dir='skps',
|
|
svg_dir='svgs',
|
|
mskp_dir='mskp',
|
|
tmp_dir='tmp',
|
|
texttraces_dir='')
|
|
|
|
@property
|
|
def env(self):
|
|
return {
|
|
'IOS_BUNDLE_ID': 'com.google.%s' % self.app_name,
|
|
'IOS_MOUNT_POINT': self.m.vars.slave_dir.join('mnt_iosdevice'),
|
|
}
|
|
|
|
def context(self):
|
|
return self.m.context(env=self.env)
|
|
|
|
def _run(self, title, *cmd, **kwargs):
|
|
def sleep(attempt):
|
|
self.m.python.inline('sleep before attempt %d' % attempt, """
|
|
import time
|
|
time.sleep(2)
|
|
""") # pragma: nocover
|
|
return self.m.run.with_retry(self.m.step, title, 3, cmd=list(cmd),
|
|
between_attempts_fn=sleep, **kwargs)
|
|
|
|
def install(self):
|
|
with self.context():
|
|
self._install()
|
|
|
|
def _install(self):
|
|
# We assume a single device is connected.
|
|
|
|
# Pair the device.
|
|
try:
|
|
self.m.run(self.m.step, 'check if device is paired',
|
|
cmd=['idevicepair', 'validate'],
|
|
infra_step=True, abort_on_failure=True,
|
|
fail_build_on_failure=False)
|
|
except self.m.step.StepFailure: # pragma: nocover
|
|
self._run('pair device', 'idevicepair', 'pair') # pragma: nocover
|
|
|
|
# Mount developer image.
|
|
image_info = self._run('list mounted image',
|
|
'ideviceimagemounter', '--list',
|
|
stdout=self.m.raw_io.output())
|
|
image_info_out = image_info.stdout.strip() if image_info.stdout else ''
|
|
|
|
if 'ImageSignature' not in image_info_out:
|
|
image_pkgs = self.m.file.glob_paths('locate ios-dev-image package',
|
|
self.m.path['start_dir'],
|
|
'ios-dev-image*',
|
|
test_data=['ios-dev-image-13.2'])
|
|
if len(image_pkgs) != 1:
|
|
raise Exception('glob for ios-dev-image* returned %s'
|
|
% image_pkgs) # pragma: nocover
|
|
|
|
image_pkg = image_pkgs[0]
|
|
contents = self.m.file.listdir(
|
|
'locate image and signature', image_pkg,
|
|
test_data=['DeveloperDiskImage.dmg',
|
|
'DeveloperDiskImage.dmg.signature'])
|
|
image = None
|
|
sig = None
|
|
for f in contents:
|
|
if str(f).endswith('.dmg'):
|
|
image = f
|
|
if str(f).endswith('.dmg.signature'):
|
|
sig = f
|
|
if not image or not sig:
|
|
raise Exception('%s does not contain *.dmg and *.dmg.signature' %
|
|
image_pkg) # pragma: nocover
|
|
|
|
self._run('mount developer image', 'ideviceimagemounter', image, sig)
|
|
|
|
# Install app (necessary before copying any resources to the device).
|
|
if self.app_name:
|
|
app_package = self.host_dirs.bin_dir.join('%s.app' % self.app_name)
|
|
|
|
def uninstall_app(attempt):
|
|
# If app ID changes, upgrade will fail, so try uninstalling.
|
|
self.m.run(self.m.step,
|
|
'uninstall %s' % self.app_name,
|
|
cmd=['ideviceinstaller', '-U',
|
|
'com.google.%s' % self.app_name],
|
|
infra_step=True,
|
|
# App may not be installed.
|
|
abort_on_failure=False, fail_build_on_failure=False)
|
|
|
|
num_attempts = 2
|
|
self.m.run.with_retry(self.m.step, 'install %s' % self.app_name,
|
|
num_attempts,
|
|
cmd=['ideviceinstaller', '-i', app_package],
|
|
between_attempts_fn=uninstall_app,
|
|
infra_step=True)
|
|
|
|
def step(self, name, cmd, **kwargs):
|
|
app_name = cmd[0]
|
|
bundle_id = 'com.google.%s' % app_name
|
|
args = [bundle_id] + map(str, cmd[1:])
|
|
success = False
|
|
with self.context():
|
|
try:
|
|
self.m.run(self.m.step, name, cmd=['idevicedebug', 'run'] + args)
|
|
success = True
|
|
finally:
|
|
if not success:
|
|
self.m.run(self.m.python, '%s with full debug output' % name,
|
|
script=self.module.resource('ios_debug_cmd.py'),
|
|
args=args)
|
|
|
|
def _run_ios_script(self, script, first, *rest):
|
|
with self.context():
|
|
full = self.m.path['start_dir'].join(
|
|
'skia', 'platform_tools', 'ios', 'bin', 'ios_' + script)
|
|
self.m.run(self.m.step,
|
|
name = '%s %s' % (script, first),
|
|
cmd = [full, first] + list(rest),
|
|
infra_step=True)
|
|
|
|
def copy_file_to_device(self, host, device):
|
|
self._run_ios_script('push_file', host, device)
|
|
|
|
def copy_directory_contents_to_device(self, host, device):
|
|
self._run_ios_script('push_if_needed', host, device)
|
|
|
|
def copy_directory_contents_to_host(self, device, host):
|
|
self._run_ios_script('pull_if_needed', device, host)
|
|
|
|
def remove_file_on_device(self, path):
|
|
self._run_ios_script('rm', path)
|
|
|
|
def create_clean_device_dir(self, path):
|
|
self._run_ios_script('rm', path)
|
|
self._run_ios_script('mkdir', path)
|
|
|
|
def read_file_on_device(self, path, **kwargs):
|
|
with self.context():
|
|
full = self.m.path['start_dir'].join(
|
|
'skia', 'platform_tools', 'ios', 'bin', 'ios_cat_file')
|
|
rv = self.m.run(self.m.step,
|
|
name = 'cat_file %s' % path,
|
|
cmd = [full, path],
|
|
stdout=self.m.raw_io.output(),
|
|
infra_step=True,
|
|
**kwargs)
|
|
return rv.stdout.rstrip() if rv and rv.stdout else None
|