1*c8dee2aaSAndroid Build Coastguard Worker#!/usr/bin/env python 2*c8dee2aaSAndroid Build Coastguard Worker# 3*c8dee2aaSAndroid Build Coastguard Worker# Copyright 2016 Google Inc. 4*c8dee2aaSAndroid Build Coastguard Worker# 5*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 6*c8dee2aaSAndroid Build Coastguard Worker# found in the LICENSE file. 7*c8dee2aaSAndroid Build Coastguard Worker 8*c8dee2aaSAndroid Build Coastguard Worker 9*c8dee2aaSAndroid Build Coastguard Worker"""Create the SKP asset.""" 10*c8dee2aaSAndroid Build Coastguard Worker 11*c8dee2aaSAndroid Build Coastguard Worker 12*c8dee2aaSAndroid Build Coastguard Workerfrom __future__ import print_function 13*c8dee2aaSAndroid Build Coastguard Workerimport argparse 14*c8dee2aaSAndroid Build Coastguard Workerfrom distutils.dir_util import copy_tree 15*c8dee2aaSAndroid Build Coastguard Workerimport os 16*c8dee2aaSAndroid Build Coastguard Workerimport shutil 17*c8dee2aaSAndroid Build Coastguard Workerimport subprocess 18*c8dee2aaSAndroid Build Coastguard Workerimport sys 19*c8dee2aaSAndroid Build Coastguard Workerimport tempfile 20*c8dee2aaSAndroid Build Coastguard Worker 21*c8dee2aaSAndroid Build Coastguard WorkerFILE_DIR = os.path.dirname(os.path.abspath(__file__)) 22*c8dee2aaSAndroid Build Coastguard WorkerINFRA_BOTS_DIR = os.path.realpath(os.path.join(FILE_DIR, os.pardir, os.pardir)) 23*c8dee2aaSAndroid Build Coastguard Workersys.path.insert(0, INFRA_BOTS_DIR) 24*c8dee2aaSAndroid Build Coastguard Workerimport utils 25*c8dee2aaSAndroid Build Coastguard Worker 26*c8dee2aaSAndroid Build Coastguard Worker 27*c8dee2aaSAndroid Build Coastguard WorkerBROWSER_EXECUTABLE_ENV_VAR = 'SKP_BROWSER_EXECUTABLE' 28*c8dee2aaSAndroid Build Coastguard WorkerCHROME_SRC_PATH_ENV_VAR = 'SKP_CHROME_SRC_PATH' 29*c8dee2aaSAndroid Build Coastguard WorkerUPLOAD_TO_PARTNER_BUCKET_ENV_VAR = 'SKP_UPLOAD_TO_PARTNER_BUCKET' 30*c8dee2aaSAndroid Build Coastguard WorkerDM_PATH_ENV_VAR = 'DM_PATH' 31*c8dee2aaSAndroid Build Coastguard Worker 32*c8dee2aaSAndroid Build Coastguard WorkerSKIA_TOOLS = os.path.join(INFRA_BOTS_DIR, os.pardir, os.pardir, 'tools') 33*c8dee2aaSAndroid Build Coastguard WorkerPRIVATE_SKPS_GS = 'gs://skia-skps/private/skps' 34*c8dee2aaSAndroid Build Coastguard Worker 35*c8dee2aaSAndroid Build Coastguard Worker 36*c8dee2aaSAndroid Build Coastguard Workerdef getenv(key): 37*c8dee2aaSAndroid Build Coastguard Worker val = os.environ.get(key) 38*c8dee2aaSAndroid Build Coastguard Worker if not val: 39*c8dee2aaSAndroid Build Coastguard Worker print(('Environment variable %s not set; you should run this via ' 40*c8dee2aaSAndroid Build Coastguard Worker 'create_and_upload.py.' % key), file=sys.stderr) 41*c8dee2aaSAndroid Build Coastguard Worker sys.exit(1) 42*c8dee2aaSAndroid Build Coastguard Worker return val 43*c8dee2aaSAndroid Build Coastguard Worker 44*c8dee2aaSAndroid Build Coastguard Worker 45*c8dee2aaSAndroid Build Coastguard Workerdef get_flutter_skps(target_dir): 46*c8dee2aaSAndroid Build Coastguard Worker """Creates SKPs using Flutter's skp_generator tool. 47*c8dee2aaSAndroid Build Coastguard Worker 48*c8dee2aaSAndroid Build Coastguard Worker Documentation is at https://github.com/flutter/tests/tree/master/skp_generator 49*c8dee2aaSAndroid Build Coastguard Worker """ 50*c8dee2aaSAndroid Build Coastguard Worker with utils.tmp_dir(): 51*c8dee2aaSAndroid Build Coastguard Worker print('Retrieving Flutter SKPs...') 52*c8dee2aaSAndroid Build Coastguard Worker utils.git_clone('https://github.com/flutter/tests.git', '.') 53*c8dee2aaSAndroid Build Coastguard Worker os.chdir('skp_generator') 54*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['bash', 'build.sh']) 55*c8dee2aaSAndroid Build Coastguard Worker # Fix invalid SKP file names. 56*c8dee2aaSAndroid Build Coastguard Worker for f in os.listdir('skps'): 57*c8dee2aaSAndroid Build Coastguard Worker original_file_name = os.path.splitext(f)[0] 58*c8dee2aaSAndroid Build Coastguard Worker new_file_name = ''.join([x if x.isalnum() else "_" 59*c8dee2aaSAndroid Build Coastguard Worker for x in original_file_name]) 60*c8dee2aaSAndroid Build Coastguard Worker if new_file_name != original_file_name: 61*c8dee2aaSAndroid Build Coastguard Worker os.rename(os.path.join('skps', f), 62*c8dee2aaSAndroid Build Coastguard Worker os.path.join('skps', new_file_name + '.skp')) 63*c8dee2aaSAndroid Build Coastguard Worker copy_tree('skps', target_dir) 64*c8dee2aaSAndroid Build Coastguard Worker print('Done retrieving Flutter SKPs.') 65*c8dee2aaSAndroid Build Coastguard Worker 66*c8dee2aaSAndroid Build Coastguard Worker 67*c8dee2aaSAndroid Build Coastguard Workerdef create_asset(chrome_src_path, browser_executable, target_dir, 68*c8dee2aaSAndroid Build Coastguard Worker upload_to_partner_bucket, dm_path): 69*c8dee2aaSAndroid Build Coastguard Worker """Create the SKP asset. 70*c8dee2aaSAndroid Build Coastguard Worker 71*c8dee2aaSAndroid Build Coastguard Worker Creates the asset from 3 sources: 72*c8dee2aaSAndroid Build Coastguard Worker 1. From Flutter's skp_generator tool. 73*c8dee2aaSAndroid Build Coastguard Worker 2. The web pages defined in the tools/skp/page_sets/ directory. 74*c8dee2aaSAndroid Build Coastguard Worker 3. Any private SKPs stored in $PRIVATE_SKPS_GS after running dm on 75*c8dee2aaSAndroid Build Coastguard Worker them (see below). 76*c8dee2aaSAndroid Build Coastguard Worker 77*c8dee2aaSAndroid Build Coastguard Worker The script runs the following cmd on the non-generated SKPs stored in 78*c8dee2aaSAndroid Build Coastguard Worker $PRIVATE_SKPS_GS - 79*c8dee2aaSAndroid Build Coastguard Worker `dm --config skp -w newskps/ --skps oldskps/ --src skp` 80*c8dee2aaSAndroid Build Coastguard Worker The cmd updates the version stored in the SKPs so that the versions in 81*c8dee2aaSAndroid Build Coastguard Worker them do not eventually become unsupported. 82*c8dee2aaSAndroid Build Coastguard Worker """ 83*c8dee2aaSAndroid Build Coastguard Worker browser_executable = os.path.realpath(browser_executable) 84*c8dee2aaSAndroid Build Coastguard Worker chrome_src_path = os.path.realpath(chrome_src_path) 85*c8dee2aaSAndroid Build Coastguard Worker dm_path = os.path.realpath(dm_path) 86*c8dee2aaSAndroid Build Coastguard Worker target_dir = os.path.realpath(target_dir) 87*c8dee2aaSAndroid Build Coastguard Worker 88*c8dee2aaSAndroid Build Coastguard Worker if not os.path.exists(target_dir): 89*c8dee2aaSAndroid Build Coastguard Worker os.makedirs(target_dir) 90*c8dee2aaSAndroid Build Coastguard Worker 91*c8dee2aaSAndroid Build Coastguard Worker # 1. Flutter SKPs 92*c8dee2aaSAndroid Build Coastguard Worker get_flutter_skps(target_dir) 93*c8dee2aaSAndroid Build Coastguard Worker 94*c8dee2aaSAndroid Build Coastguard Worker # 2. Skia's SKPs from tools/skp/page_sets/ 95*c8dee2aaSAndroid Build Coastguard Worker with utils.tmp_dir(): 96*c8dee2aaSAndroid Build Coastguard Worker if os.environ.get('CHROME_HEADLESS'): 97*c8dee2aaSAndroid Build Coastguard Worker print('Starting xvfb') 98*c8dee2aaSAndroid Build Coastguard Worker # Start Xvfb if running on a bot. 99*c8dee2aaSAndroid Build Coastguard Worker try: 100*c8dee2aaSAndroid Build Coastguard Worker xvfb_proc = subprocess.Popen([ 101*c8dee2aaSAndroid Build Coastguard Worker 'sudo', 'Xvfb', ':0', '-screen', '0', '1280x1024x24']) 102*c8dee2aaSAndroid Build Coastguard Worker except Exception: 103*c8dee2aaSAndroid Build Coastguard Worker # It is ok if the above command fails, it just means that DISPLAY=:0 104*c8dee2aaSAndroid Build Coastguard Worker # is already up. 105*c8dee2aaSAndroid Build Coastguard Worker xvfb_proc = None 106*c8dee2aaSAndroid Build Coastguard Worker 107*c8dee2aaSAndroid Build Coastguard Worker print('Running webpages_playback to generate SKPs...') 108*c8dee2aaSAndroid Build Coastguard Worker webpages_playback_cmd = [ 109*c8dee2aaSAndroid Build Coastguard Worker 'python', '-u', os.path.join(SKIA_TOOLS, 'skp', 'webpages_playback.py'), 110*c8dee2aaSAndroid Build Coastguard Worker '--page_sets', 'all', 111*c8dee2aaSAndroid Build Coastguard Worker '--browser_executable', browser_executable, 112*c8dee2aaSAndroid Build Coastguard Worker '--non-interactive', 113*c8dee2aaSAndroid Build Coastguard Worker '--output_dir', os.getcwd(), 114*c8dee2aaSAndroid Build Coastguard Worker '--chrome_src_path', chrome_src_path, 115*c8dee2aaSAndroid Build Coastguard Worker ] 116*c8dee2aaSAndroid Build Coastguard Worker if upload_to_partner_bucket: 117*c8dee2aaSAndroid Build Coastguard Worker webpages_playback_cmd.append('--upload_to_partner_bucket') 118*c8dee2aaSAndroid Build Coastguard Worker print('Running webpages_playback command:\n$ %s' % 119*c8dee2aaSAndroid Build Coastguard Worker ' '.join(webpages_playback_cmd)) 120*c8dee2aaSAndroid Build Coastguard Worker try: 121*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(webpages_playback_cmd) 122*c8dee2aaSAndroid Build Coastguard Worker finally: 123*c8dee2aaSAndroid Build Coastguard Worker if xvfb_proc: 124*c8dee2aaSAndroid Build Coastguard Worker try: 125*c8dee2aaSAndroid Build Coastguard Worker xvfb_proc.kill() 126*c8dee2aaSAndroid Build Coastguard Worker except OSError as e: 127*c8dee2aaSAndroid Build Coastguard Worker print('Failed to kill xvfb process via Popen.kill();' 128*c8dee2aaSAndroid Build Coastguard Worker ' attempting `sudo kill`...') 129*c8dee2aaSAndroid Build Coastguard Worker try: 130*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['sudo', 'kill', '-9', str(xvfb_proc.pid)]) 131*c8dee2aaSAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 132*c8dee2aaSAndroid Build Coastguard Worker print('Failed to kill xvfb process via `sudo kill`;' 133*c8dee2aaSAndroid Build Coastguard Worker 'this may cause a hang.') 134*c8dee2aaSAndroid Build Coastguard Worker # Clean up any leftover browser instances. This can happen if there are 135*c8dee2aaSAndroid Build Coastguard Worker # telemetry crashes, processes are not always cleaned up appropriately by 136*c8dee2aaSAndroid Build Coastguard Worker # the webpagereplay and telemetry frameworks. 137*c8dee2aaSAndroid Build Coastguard Worker procs = subprocess.check_output(['ps', 'ax']).decode() 138*c8dee2aaSAndroid Build Coastguard Worker for line in procs.splitlines(): 139*c8dee2aaSAndroid Build Coastguard Worker if browser_executable in line: 140*c8dee2aaSAndroid Build Coastguard Worker print(line) 141*c8dee2aaSAndroid Build Coastguard Worker pid = line.strip().split(' ')[0] 142*c8dee2aaSAndroid Build Coastguard Worker if pid != str(os.getpid()) and not 'python' in line: 143*c8dee2aaSAndroid Build Coastguard Worker print('Kill browser process %s' % str(pid)) 144*c8dee2aaSAndroid Build Coastguard Worker try: 145*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['kill', '-9', str(pid)]) 146*c8dee2aaSAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 147*c8dee2aaSAndroid Build Coastguard Worker print(e) 148*c8dee2aaSAndroid Build Coastguard Worker else: 149*c8dee2aaSAndroid Build Coastguard Worker pass 150*c8dee2aaSAndroid Build Coastguard Worker if 'Xvfb' in line: 151*c8dee2aaSAndroid Build Coastguard Worker print(line) 152*c8dee2aaSAndroid Build Coastguard Worker pid = line.strip().split(' ')[0] 153*c8dee2aaSAndroid Build Coastguard Worker print('Kill Xvfb process %s' % str(pid)) 154*c8dee2aaSAndroid Build Coastguard Worker try: 155*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call(['sudo', 'kill', '-9', str(pid)]) 156*c8dee2aaSAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 157*c8dee2aaSAndroid Build Coastguard Worker print(e) 158*c8dee2aaSAndroid Build Coastguard Worker 159*c8dee2aaSAndroid Build Coastguard Worker src = os.path.join(os.getcwd(), 'playback', 'skps') 160*c8dee2aaSAndroid Build Coastguard Worker for f in os.listdir(src): 161*c8dee2aaSAndroid Build Coastguard Worker if f.endswith('.skp'): 162*c8dee2aaSAndroid Build Coastguard Worker shutil.copyfile(os.path.join(src, f), os.path.join(target_dir, f)) 163*c8dee2aaSAndroid Build Coastguard Worker print('Done running webpages_playback.') 164*c8dee2aaSAndroid Build Coastguard Worker 165*c8dee2aaSAndroid Build Coastguard Worker # 3. Copy over private SKPs from Google storage into the target_dir. 166*c8dee2aaSAndroid Build Coastguard Worker old_skps_dir = tempfile.mkdtemp() 167*c8dee2aaSAndroid Build Coastguard Worker new_skps_dir = tempfile.mkdtemp() 168*c8dee2aaSAndroid Build Coastguard Worker print('Copying non-generated SKPs from private GCS bucket...') 169*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call([ 170*c8dee2aaSAndroid Build Coastguard Worker 'gsutil', 'cp', os.path.join(PRIVATE_SKPS_GS, '*'), old_skps_dir]) 171*c8dee2aaSAndroid Build Coastguard Worker print('Updating non-generated SKP versions') 172*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call([ 173*c8dee2aaSAndroid Build Coastguard Worker dm_path, 174*c8dee2aaSAndroid Build Coastguard Worker '--config', 'skp', 175*c8dee2aaSAndroid Build Coastguard Worker '-w', new_skps_dir, 176*c8dee2aaSAndroid Build Coastguard Worker '--skps', old_skps_dir, 177*c8dee2aaSAndroid Build Coastguard Worker '--src', 'skp']) 178*c8dee2aaSAndroid Build Coastguard Worker 179*c8dee2aaSAndroid Build Coastguard Worker # DM creates artifacts in 2 directory level- one for the config and one for 180*c8dee2aaSAndroid Build Coastguard Worker # the output type. In this case it ends up creating ${temp_dir}/skp/skp/ 181*c8dee2aaSAndroid Build Coastguard Worker for f in os.listdir(os.path.join(new_skps_dir, 'skp', 'skp')): 182*c8dee2aaSAndroid Build Coastguard Worker if f.endswith('.skp'): 183*c8dee2aaSAndroid Build Coastguard Worker # Files generated by DM add a suffix based on the output type. 184*c8dee2aaSAndroid Build Coastguard Worker # For "xyz.skp" DM will create "xyz.skp.skp". We strip out 185*c8dee2aaSAndroid Build Coastguard Worker # the final ".skp" here. 186*c8dee2aaSAndroid Build Coastguard Worker shutil.copyfile( 187*c8dee2aaSAndroid Build Coastguard Worker os.path.join(new_skps_dir, 'skp', 'skp', f), 188*c8dee2aaSAndroid Build Coastguard Worker os.path.join(target_dir, f.replace('.skp.skp', '.skp'))) 189*c8dee2aaSAndroid Build Coastguard Worker shutil.rmtree(old_skps_dir) 190*c8dee2aaSAndroid Build Coastguard Worker shutil.rmtree(new_skps_dir) 191*c8dee2aaSAndroid Build Coastguard Worker 192*c8dee2aaSAndroid Build Coastguard Worker 193*c8dee2aaSAndroid Build Coastguard Workerdef main(): 194*c8dee2aaSAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 195*c8dee2aaSAndroid Build Coastguard Worker parser.add_argument('--target_dir', '-t', required=True) 196*c8dee2aaSAndroid Build Coastguard Worker args = parser.parse_args() 197*c8dee2aaSAndroid Build Coastguard Worker 198*c8dee2aaSAndroid Build Coastguard Worker # Obtain flags from create_and_upload via environment variables, since 199*c8dee2aaSAndroid Build Coastguard Worker # this script is called via `sk` and not directly. 200*c8dee2aaSAndroid Build Coastguard Worker chrome_src_path = getenv(CHROME_SRC_PATH_ENV_VAR) 201*c8dee2aaSAndroid Build Coastguard Worker browser_executable = getenv(BROWSER_EXECUTABLE_ENV_VAR) 202*c8dee2aaSAndroid Build Coastguard Worker upload_to_partner_bucket = getenv(UPLOAD_TO_PARTNER_BUCKET_ENV_VAR) == '1' 203*c8dee2aaSAndroid Build Coastguard Worker dm_path = getenv(DM_PATH_ENV_VAR) 204*c8dee2aaSAndroid Build Coastguard Worker 205*c8dee2aaSAndroid Build Coastguard Worker create_asset(chrome_src_path, browser_executable, args.target_dir, 206*c8dee2aaSAndroid Build Coastguard Worker upload_to_partner_bucket, dm_path) 207*c8dee2aaSAndroid Build Coastguard Worker 208*c8dee2aaSAndroid Build Coastguard Worker 209*c8dee2aaSAndroid Build Coastguard Workerif __name__ == '__main__': 210*c8dee2aaSAndroid Build Coastguard Worker main() 211