1# Copyright 2017 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 6from . import default 7 8 9"""iOS flavor, used for running code on iOS.""" 10 11 12class iOSFlavor(default.DefaultFlavor): 13 def __init__(self, m, app_name): 14 super(iOSFlavor, self).__init__(m, app_name) 15 self.device_dirs = default.DeviceDirs( 16 bin_dir='[unused]', 17 dm_dir='dm', 18 perf_data_dir='perf', 19 resource_dir='resources', 20 images_dir='images', 21 lotties_dir='lotties', 22 skp_dir='skps', 23 svg_dir='svgs', 24 mskp_dir='mskp', 25 tmp_dir='tmp', 26 texttraces_dir='') 27 28 @property 29 def env(self): 30 return { 31 'IOS_BUNDLE_ID': 'com.google.%s' % self.app_name, 32 'IOS_MOUNT_POINT': self.m.vars.workdir.join('mnt_iosdevice'), 33 } 34 35 def context(self): 36 return self.m.context(env=self.env) 37 38 def _run(self, title, *cmd, **kwargs): 39 def sleep(attempt): 40 self.m.python.inline('sleep before attempt %d' % attempt, """ 41import time 42time.sleep(2) 43""") # pragma: nocover 44 return self.m.run.with_retry(self.m.step, title, 3, cmd=list(cmd), 45 between_attempts_fn=sleep, **kwargs) 46 47 def install(self): 48 with self.context(): 49 self._install() 50 51 def _install(self): 52 # We assume a single device is connected. 53 54 # Pair the device. 55 try: 56 self.m.run(self.m.step, 'check if device is paired', 57 cmd=['idevicepair', 'validate'], 58 infra_step=True, abort_on_failure=True, 59 fail_build_on_failure=False) 60 except self.m.step.StepFailure: # pragma: nocover 61 self._run('pair device', 'idevicepair', 'pair') # pragma: nocover 62 63 # Mount developer image. 64 image_info = self._run('list mounted image', 65 'ideviceimagemounter', '--list', 66 stdout=self.m.raw_io.output()) 67 image_info_out = ( 68 image_info.stdout.decode('utf-8').strip() if image_info.stdout else '') 69 70 if 'ImageSignature' not in image_info_out: 71 image_pkgs = self.m.file.glob_paths('locate ios-dev-image package', 72 self.m.path['start_dir'], 73 'ios-dev-image*', 74 test_data=['ios-dev-image-13.2']) 75 if len(image_pkgs) != 1: 76 raise Exception('glob for ios-dev-image* returned %s' 77 % image_pkgs) # pragma: nocover 78 79 image_pkg = image_pkgs[0] 80 contents = self.m.file.listdir( 81 'locate image and signature', image_pkg, 82 test_data=['DeveloperDiskImage.dmg', 83 'DeveloperDiskImage.dmg.signature']) 84 image = None 85 sig = None 86 for f in contents: 87 if str(f).endswith('.dmg'): 88 image = f 89 if str(f).endswith('.dmg.signature'): 90 sig = f 91 if not image or not sig: 92 raise Exception('%s does not contain *.dmg and *.dmg.signature' % 93 image_pkg) # pragma: nocover 94 95 self._run('mount developer image', 'ideviceimagemounter', image, sig) 96 97 # Install app (necessary before copying any resources to the device). 98 if self.app_name: 99 app_package = self.host_dirs.bin_dir.join('%s.app' % self.app_name) 100 101 def uninstall_app(attempt): 102 # If app ID changes, upgrade will fail, so try uninstalling. 103 self.m.run(self.m.step, 104 'uninstall %s' % self.app_name, 105 cmd=['ideviceinstaller', '-U', 106 'com.google.%s' % self.app_name], 107 infra_step=True, 108 # App may not be installed. 109 abort_on_failure=False, fail_build_on_failure=False) 110 111 num_attempts = 2 112 self.m.run.with_retry(self.m.step, 'install %s' % self.app_name, 113 num_attempts, 114 cmd=['ideviceinstaller', '-i', app_package], 115 between_attempts_fn=uninstall_app, 116 infra_step=True) 117 118 def step(self, name, cmd, **kwargs): 119 app_name = cmd[0] 120 bundle_id = 'com.google.%s' % app_name 121 args = [bundle_id] + [str(ele) for ele in cmd[1:]] 122 success = False 123 with self.context(): 124 try: 125 self.m.run(self.m.step, name, cmd=['idevicedebug', 'run'] + args) 126 success = True 127 finally: 128 if not success: 129 self.m.run(self.m.python, '%s with full debug output' % name, 130 script=self.module.resource('ios_debug_cmd.py'), 131 args=args) 132 133 def _run_ios_script(self, script, first, *rest): 134 with self.context(): 135 full = self.m.path['start_dir'].join( 136 'skia', 'platform_tools', 'ios', 'bin', 'ios_' + script) 137 self.m.run(self.m.step, 138 name = '%s %s' % (script, first), 139 cmd = [full, first] + list(rest), 140 infra_step=True) 141 142 def copy_file_to_device(self, host, device): 143 self._run_ios_script('push_file', host, device) 144 145 def copy_directory_contents_to_device(self, host, device): 146 self._run_ios_script('push_if_needed', host, device) 147 148 def copy_directory_contents_to_host(self, device, host): 149 self._run_ios_script('pull_if_needed', device, host) 150 151 def remove_file_on_device(self, path): 152 self._run_ios_script('rm', path) 153 154 def create_clean_device_dir(self, path): 155 self._run_ios_script('rm', path) 156 self._run_ios_script('mkdir', path) 157 158 def read_file_on_device(self, path, **kwargs): 159 with self.context(): 160 full = self.m.path['start_dir'].join( 161 'skia', 'platform_tools', 'ios', 'bin', 'ios_cat_file') 162 rv = self.m.run(self.m.step, 163 name = 'cat_file %s' % path, 164 cmd = [full, path], 165 stdout=self.m.raw_io.output(), 166 infra_step=True, 167 **kwargs) 168 return rv.stdout.decode('utf-8').rstrip() if rv and rv.stdout else None 169