1*8975f5c5SAndroid Build Coastguard Worker# Copyright 2024 The Chromium Authors 2*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 3*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 4*8975f5c5SAndroid Build Coastguard Worker 5*8975f5c5SAndroid Build Coastguard Workerimport argparse 6*8975f5c5SAndroid Build Coastguard Workerimport logging 7*8975f5c5SAndroid Build Coastguard Workerimport os 8*8975f5c5SAndroid Build Coastguard Workerimport pathlib 9*8975f5c5SAndroid Build Coastguard Workerimport shlex 10*8975f5c5SAndroid Build Coastguard Workerimport shutil 11*8975f5c5SAndroid Build Coastguard Workerimport subprocess 12*8975f5c5SAndroid Build Coastguard Workerimport sys 13*8975f5c5SAndroid Build Coastguard Workerimport tarfile 14*8975f5c5SAndroid Build Coastguard Workerimport tempfile 15*8975f5c5SAndroid Build Coastguard Workerimport time 16*8975f5c5SAndroid Build Coastguard Workerimport urllib.request 17*8975f5c5SAndroid Build Coastguard Worker 18*8975f5c5SAndroid Build Coastguard Workerimport scripthash 19*8975f5c5SAndroid Build Coastguard Worker 20*8975f5c5SAndroid Build Coastguard Worker_THIS_DIR = pathlib.Path(__file__).resolve().parent 21*8975f5c5SAndroid Build Coastguard Worker_SRC_ROOT = _THIS_DIR.parents[1] 22*8975f5c5SAndroid Build Coastguard Worker_CHECKOUT_SRC_ROOT_SUBDIR = '.3pp/chromium' 23*8975f5c5SAndroid Build Coastguard Worker 24*8975f5c5SAndroid Build Coastguard Worker 25*8975f5c5SAndroid Build Coastguard Workerdef parse_args(): 26*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 27*8975f5c5SAndroid Build Coastguard Worker # TODO(agrieve): Add required=True once 3pp builds with > python3.6. 28*8975f5c5SAndroid Build Coastguard Worker subparsers = parser.add_subparsers() 29*8975f5c5SAndroid Build Coastguard Worker 30*8975f5c5SAndroid Build Coastguard Worker subparser = subparsers.add_parser( 31*8975f5c5SAndroid Build Coastguard Worker 'latest', help='Prints the version as $LATEST.$RUNTIME_DEPS_HASH') 32*8975f5c5SAndroid Build Coastguard Worker subparser.set_defaults(action='latest') 33*8975f5c5SAndroid Build Coastguard Worker 34*8975f5c5SAndroid Build Coastguard Worker subparser = subparsers.add_parser( 35*8975f5c5SAndroid Build Coastguard Worker 'checkout', help='Copies files into the workdir used by docker') 36*8975f5c5SAndroid Build Coastguard Worker subparser.add_argument('checkout_dir') 37*8975f5c5SAndroid Build Coastguard Worker subparser.add_argument('--version', help='Output from "latest"') 38*8975f5c5SAndroid Build Coastguard Worker subparser.set_defaults(action='checkout') 39*8975f5c5SAndroid Build Coastguard Worker 40*8975f5c5SAndroid Build Coastguard Worker subparser = subparsers.add_parser( 41*8975f5c5SAndroid Build Coastguard Worker 'install', 42*8975f5c5SAndroid Build Coastguard Worker help=('Run from workdir, inside docker container. ' 43*8975f5c5SAndroid Build Coastguard Worker 'Builds & copies outputs into |output_prefix| directory')) 44*8975f5c5SAndroid Build Coastguard Worker subparser.add_argument('output_prefix', 45*8975f5c5SAndroid Build Coastguard Worker help='The path to install the compiled package to.') 46*8975f5c5SAndroid Build Coastguard Worker subparser.add_argument('deps_prefix', 47*8975f5c5SAndroid Build Coastguard Worker help='The path to a directory containing all deps.') 48*8975f5c5SAndroid Build Coastguard Worker subparser.add_argument('--version', help='Output from "latest"') 49*8975f5c5SAndroid Build Coastguard Worker subparser.add_argument('--checkout-dir', help='Directory to use as CWD') 50*8975f5c5SAndroid Build Coastguard Worker subparser.set_defaults(action='install') 51*8975f5c5SAndroid Build Coastguard Worker 52*8975f5c5SAndroid Build Coastguard Worker subparser = subparsers.add_parser( 53*8975f5c5SAndroid Build Coastguard Worker 'local-test', help='Run latest / checkout / install locally') 54*8975f5c5SAndroid Build Coastguard Worker subparser.add_argument('--checkout-dir', 55*8975f5c5SAndroid Build Coastguard Worker default='3pp_workdir', 56*8975f5c5SAndroid Build Coastguard Worker help='Workdir to use') 57*8975f5c5SAndroid Build Coastguard Worker subparser.add_argument('--output-prefix', 58*8975f5c5SAndroid Build Coastguard Worker default='3pp_out', 59*8975f5c5SAndroid Build Coastguard Worker help='Directory for final artifacts') 60*8975f5c5SAndroid Build Coastguard Worker subparser.set_defaults(action='local-test') 61*8975f5c5SAndroid Build Coastguard Worker 62*8975f5c5SAndroid Build Coastguard Worker args = parser.parse_args() 63*8975f5c5SAndroid Build Coastguard Worker if not hasattr(args, 'action'): 64*8975f5c5SAndroid Build Coastguard Worker parser.print_help() 65*8975f5c5SAndroid Build Coastguard Worker sys.exit(1) 66*8975f5c5SAndroid Build Coastguard Worker 67*8975f5c5SAndroid Build Coastguard Worker if hasattr(args, 'version'): 68*8975f5c5SAndroid Build Coastguard Worker args.version = args.version or os.environ.get('_3PP_VERSION') 69*8975f5c5SAndroid Build Coastguard Worker if not args.version: 70*8975f5c5SAndroid Build Coastguard Worker parser.error('Must set --version or _3PP_VERSION') 71*8975f5c5SAndroid Build Coastguard Worker if hasattr(args, 'output_prefix') and args.output_prefix: 72*8975f5c5SAndroid Build Coastguard Worker args.output_prefix = os.path.abspath(args.output_prefix) 73*8975f5c5SAndroid Build Coastguard Worker if hasattr(args, 'checkout_dir') and args.checkout_dir: 74*8975f5c5SAndroid Build Coastguard Worker args.checkout_dir = os.path.abspath(args.checkout_dir) 75*8975f5c5SAndroid Build Coastguard Worker 76*8975f5c5SAndroid Build Coastguard Worker if args.action == 'checkout': 77*8975f5c5SAndroid Build Coastguard Worker # 3pp bot recipe does this, so needed only when running locally. 78*8975f5c5SAndroid Build Coastguard Worker os.makedirs(args.checkout_dir, exist_ok=True) 79*8975f5c5SAndroid Build Coastguard Worker 80*8975f5c5SAndroid Build Coastguard Worker if args.action == 'install': 81*8975f5c5SAndroid Build Coastguard Worker if args.checkout_dir: 82*8975f5c5SAndroid Build Coastguard Worker logging.info('Setting CWD=%s', args.checkout_dir) 83*8975f5c5SAndroid Build Coastguard Worker os.chdir(args.checkout_dir) 84*8975f5c5SAndroid Build Coastguard Worker 85*8975f5c5SAndroid Build Coastguard Worker if not os.path.exists(_CHECKOUT_SRC_ROOT_SUBDIR): 86*8975f5c5SAndroid Build Coastguard Worker parser.error(f'Does not exist: {_CHECKOUT_SRC_ROOT_SUBDIR}.' 87*8975f5c5SAndroid Build Coastguard Worker f' Use --checkout-dir?') 88*8975f5c5SAndroid Build Coastguard Worker 89*8975f5c5SAndroid Build Coastguard Worker # 3pp bot recipe does this, so needed only when running locally. 90*8975f5c5SAndroid Build Coastguard Worker os.makedirs(args.output_prefix, exist_ok=True) 91*8975f5c5SAndroid Build Coastguard Worker 92*8975f5c5SAndroid Build Coastguard Worker return args 93*8975f5c5SAndroid Build Coastguard Worker 94*8975f5c5SAndroid Build Coastguard Worker 95*8975f5c5SAndroid Build Coastguard Workerdef path_within_checkout(subpath): 96*8975f5c5SAndroid Build Coastguard Worker return os.path.abspath(os.path.join(_CHECKOUT_SRC_ROOT_SUBDIR, subpath)) 97*8975f5c5SAndroid Build Coastguard Worker 98*8975f5c5SAndroid Build Coastguard Worker 99*8975f5c5SAndroid Build Coastguard Workerdef _all_files(path): 100*8975f5c5SAndroid Build Coastguard Worker if os.path.isfile(path): 101*8975f5c5SAndroid Build Coastguard Worker return [path] 102*8975f5c5SAndroid Build Coastguard Worker assert os.path.isdir(path), 'Not a file or dir: ' + path 103*8975f5c5SAndroid Build Coastguard Worker all_paths = pathlib.Path(path).glob('**/*') 104*8975f5c5SAndroid Build Coastguard Worker return [str(f) for f in all_paths if f.is_file()] 105*8975f5c5SAndroid Build Coastguard Worker 106*8975f5c5SAndroid Build Coastguard Worker 107*8975f5c5SAndroid Build Coastguard Workerdef _resolve_runtime_deps(runtime_deps): 108*8975f5c5SAndroid Build Coastguard Worker ret = [] 109*8975f5c5SAndroid Build Coastguard Worker for p in runtime_deps: 110*8975f5c5SAndroid Build Coastguard Worker if p.startswith('//'): 111*8975f5c5SAndroid Build Coastguard Worker ret.append(os.path.relpath(str(_SRC_ROOT / p[2:]))) 112*8975f5c5SAndroid Build Coastguard Worker elif os.path.isabs(p): 113*8975f5c5SAndroid Build Coastguard Worker ret.append(os.path.relpath(p)) 114*8975f5c5SAndroid Build Coastguard Worker else: 115*8975f5c5SAndroid Build Coastguard Worker ret.append(p) 116*8975f5c5SAndroid Build Coastguard Worker return ret 117*8975f5c5SAndroid Build Coastguard Worker 118*8975f5c5SAndroid Build Coastguard Worker 119*8975f5c5SAndroid Build Coastguard Workerdef copy_runtime_deps(checkout_dir, runtime_deps): 120*8975f5c5SAndroid Build Coastguard Worker # Make 3pp_common scripts available in the docker container install.py 121*8975f5c5SAndroid Build Coastguard Worker # will run in. 122*8975f5c5SAndroid Build Coastguard Worker dest_dir = os.path.join(checkout_dir, _CHECKOUT_SRC_ROOT_SUBDIR) 123*8975f5c5SAndroid Build Coastguard Worker 124*8975f5c5SAndroid Build Coastguard Worker for src_path in _resolve_runtime_deps(runtime_deps): 125*8975f5c5SAndroid Build Coastguard Worker relpath = os.path.relpath(src_path, _SRC_ROOT) 126*8975f5c5SAndroid Build Coastguard Worker dest_path = os.path.join(dest_dir, relpath) 127*8975f5c5SAndroid Build Coastguard Worker os.makedirs(os.path.dirname(dest_path), exist_ok=True) 128*8975f5c5SAndroid Build Coastguard Worker if os.path.isfile(src_path): 129*8975f5c5SAndroid Build Coastguard Worker shutil.copy(src_path, dest_path) 130*8975f5c5SAndroid Build Coastguard Worker else: 131*8975f5c5SAndroid Build Coastguard Worker shutil.copytree(src_path, 132*8975f5c5SAndroid Build Coastguard Worker dest_path, 133*8975f5c5SAndroid Build Coastguard Worker ignore=shutil.ignore_patterns('.*', '__pycache__')) 134*8975f5c5SAndroid Build Coastguard Worker logging.info('Runtime deps:') 135*8975f5c5SAndroid Build Coastguard Worker sys.stderr.write('\n'.join(_all_files(checkout_dir)) + '\n') 136*8975f5c5SAndroid Build Coastguard Worker 137*8975f5c5SAndroid Build Coastguard Worker 138*8975f5c5SAndroid Build Coastguard Workerdef download_file(url, dest): 139*8975f5c5SAndroid Build Coastguard Worker logging.info('Downloading %s', url) 140*8975f5c5SAndroid Build Coastguard Worker with urllib.request.urlopen(url) as r: 141*8975f5c5SAndroid Build Coastguard Worker with open(dest, 'wb') as f: 142*8975f5c5SAndroid Build Coastguard Worker shutil.copyfileobj(r, f) 143*8975f5c5SAndroid Build Coastguard Worker 144*8975f5c5SAndroid Build Coastguard Worker 145*8975f5c5SAndroid Build Coastguard Workerdef extract_tar(path, dest): 146*8975f5c5SAndroid Build Coastguard Worker logging.info('Extracting %s to %s', path, dest) 147*8975f5c5SAndroid Build Coastguard Worker with tarfile.open(path) as f: 148*8975f5c5SAndroid Build Coastguard Worker f.extractall(dest) 149*8975f5c5SAndroid Build Coastguard Worker 150*8975f5c5SAndroid Build Coastguard Worker 151*8975f5c5SAndroid Build Coastguard Workerdef run_cmd(cmd, check=True, *args, **kwargs): 152*8975f5c5SAndroid Build Coastguard Worker logging.info('Running: %s', shlex.join(cmd)) 153*8975f5c5SAndroid Build Coastguard Worker return subprocess.run(cmd, check=check, *args, **kwargs) 154*8975f5c5SAndroid Build Coastguard Worker 155*8975f5c5SAndroid Build Coastguard Worker 156*8975f5c5SAndroid Build Coastguard Workerdef apply_patches(patches_dir, checkout_dir): 157*8975f5c5SAndroid Build Coastguard Worker for path in sorted(pathlib.Path(patches_dir).glob('*.patch')): 158*8975f5c5SAndroid Build Coastguard Worker cmd = ['git', 'apply', '-v', str(path)] 159*8975f5c5SAndroid Build Coastguard Worker run_cmd(cmd, cwd=checkout_dir) 160*8975f5c5SAndroid Build Coastguard Worker 161*8975f5c5SAndroid Build Coastguard Worker 162*8975f5c5SAndroid Build Coastguard Workerdef main(*, do_latest, do_install, runtime_deps): 163*8975f5c5SAndroid Build Coastguard Worker logging.basicConfig( 164*8975f5c5SAndroid Build Coastguard Worker level=logging.DEBUG, 165*8975f5c5SAndroid Build Coastguard Worker format='%(levelname).1s %(relativeCreated)6d %(message)s') 166*8975f5c5SAndroid Build Coastguard Worker args = parse_args() 167*8975f5c5SAndroid Build Coastguard Worker runtime_deps = [str(_THIS_DIR)] + runtime_deps 168*8975f5c5SAndroid Build Coastguard Worker 169*8975f5c5SAndroid Build Coastguard Worker if args.action == 'local-test': 170*8975f5c5SAndroid Build Coastguard Worker logging.warning('Will use work dir: %s', args.checkout_dir) 171*8975f5c5SAndroid Build Coastguard Worker logging.warning('Will use output dir: %s', args.output_prefix) 172*8975f5c5SAndroid Build Coastguard Worker if os.path.exists(args.checkout_dir) and os.listdir(args.checkout_dir): 173*8975f5c5SAndroid Build Coastguard Worker logging.warning( 174*8975f5c5SAndroid Build Coastguard Worker '*** Work dir not empty. This often causes failures. ***') 175*8975f5c5SAndroid Build Coastguard Worker time.sleep(4) 176*8975f5c5SAndroid Build Coastguard Worker # Approximates what 3pp recipe does for minimal configs. 177*8975f5c5SAndroid Build Coastguard Worker # https://source.chromium.org/search?q=symbol:Chromium3ppApi.execute&ss=chromium 178*8975f5c5SAndroid Build Coastguard Worker prog = os.path.abspath(sys.argv[0]) 179*8975f5c5SAndroid Build Coastguard Worker cmd = [prog, 'latest'] 180*8975f5c5SAndroid Build Coastguard Worker version = run_cmd(cmd, stdout=subprocess.PIPE, text=True).stdout 181*8975f5c5SAndroid Build Coastguard Worker os.environ['_3PP_VERSION'] = version 182*8975f5c5SAndroid Build Coastguard Worker checkout_dir = args.checkout_dir 183*8975f5c5SAndroid Build Coastguard Worker run_cmd([prog, 'checkout', checkout_dir]) 184*8975f5c5SAndroid Build Coastguard Worker run_cmd([prog, 'install', args.output_prefix, 'UNUSED-DEPS-DIR'], 185*8975f5c5SAndroid Build Coastguard Worker cwd=checkout_dir) 186*8975f5c5SAndroid Build Coastguard Worker logging.warning('Local test complete.') 187*8975f5c5SAndroid Build Coastguard Worker return 188*8975f5c5SAndroid Build Coastguard Worker 189*8975f5c5SAndroid Build Coastguard Worker if args.action == 'latest': 190*8975f5c5SAndroid Build Coastguard Worker version = do_latest() 191*8975f5c5SAndroid Build Coastguard Worker assert version, 'do_latest() returned ' + repr(version) 192*8975f5c5SAndroid Build Coastguard Worker extra_paths = [] 193*8975f5c5SAndroid Build Coastguard Worker for p in _resolve_runtime_deps(runtime_deps): 194*8975f5c5SAndroid Build Coastguard Worker extra_paths += _all_files(p) 195*8975f5c5SAndroid Build Coastguard Worker deps_hash = scripthash.compute(extra_paths=extra_paths) 196*8975f5c5SAndroid Build Coastguard Worker print(f'{version}.{deps_hash}') 197*8975f5c5SAndroid Build Coastguard Worker return 198*8975f5c5SAndroid Build Coastguard Worker 199*8975f5c5SAndroid Build Coastguard Worker # Remove the hash at the end: 30.4.0-alpha05.HASH => 30.4.0-alpha05 200*8975f5c5SAndroid Build Coastguard Worker args.version = args.version.rsplit('.', 1)[0] 201*8975f5c5SAndroid Build Coastguard Worker if args.action == 'checkout': 202*8975f5c5SAndroid Build Coastguard Worker copy_runtime_deps(args.checkout_dir, runtime_deps) 203*8975f5c5SAndroid Build Coastguard Worker return 204*8975f5c5SAndroid Build Coastguard Worker 205*8975f5c5SAndroid Build Coastguard Worker assert args.action == 'install' 206*8975f5c5SAndroid Build Coastguard Worker do_install(args) 207*8975f5c5SAndroid Build Coastguard Worker prefix_len = len(args.output_prefix) + 1 208*8975f5c5SAndroid Build Coastguard Worker logging.info( 209*8975f5c5SAndroid Build Coastguard Worker 'Contents of %s: \n%s\n', args.output_prefix, 210*8975f5c5SAndroid Build Coastguard Worker '\n'.join(p[prefix_len:] for p in _all_files(args.output_prefix))) 211