1*760c253cSXin Li#!/usr/bin/env python3 2*760c253cSXin Li# Copyright 2022 The ChromiumOS Authors 3*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be 4*760c253cSXin Li# found in the LICENSE file. 5*760c253cSXin Li 6*760c253cSXin Li"""Copies rust-bootstrap artifacts from an SDK build to localmirror. 7*760c253cSXin Li 8*760c253cSXin LiWe use localmirror to host these artifacts, but they've changed a bit over 9*760c253cSXin Litime, so simply `gsutil cp $FROM $TO` doesn't work. This script allows the 10*760c253cSXin Liconvenience of the old `cp` command. 11*760c253cSXin Li""" 12*760c253cSXin Li 13*760c253cSXin Liimport argparse 14*760c253cSXin Liimport logging 15*760c253cSXin Liimport os 16*760c253cSXin Lifrom pathlib import Path 17*760c253cSXin Liimport shutil 18*760c253cSXin Liimport subprocess 19*760c253cSXin Liimport sys 20*760c253cSXin Liimport tempfile 21*760c253cSXin Lifrom typing import List 22*760c253cSXin Li 23*760c253cSXin Li 24*760c253cSXin Li_LOCALMIRROR_ROOT = "gs://chromeos-localmirror/distfiles/" 25*760c253cSXin Li 26*760c253cSXin Li 27*760c253cSXin Lidef _is_in_chroot() -> bool: 28*760c253cSXin Li return Path("/etc/cros_chroot_version").exists() 29*760c253cSXin Li 30*760c253cSXin Li 31*760c253cSXin Lidef _ensure_lbzip2_is_installed(): 32*760c253cSXin Li if shutil.which("lbzip2"): 33*760c253cSXin Li return 34*760c253cSXin Li 35*760c253cSXin Li logging.info("Auto-installing lbzip2...") 36*760c253cSXin Li subprocess.run(["sudo", "emerge", "-g", "lbzip2"], check=True) 37*760c253cSXin Li 38*760c253cSXin Li 39*760c253cSXin Lidef determine_target_path(sdk_path: str) -> str: 40*760c253cSXin Li """Determine where `sdk_path` should sit in localmirror.""" 41*760c253cSXin Li gs_prefix = "gs://" 42*760c253cSXin Li if not sdk_path.startswith(gs_prefix): 43*760c253cSXin Li raise ValueError(f"Invalid GS path: {sdk_path!r}") 44*760c253cSXin Li 45*760c253cSXin Li file_name = Path(sdk_path[len(gs_prefix) :]).name 46*760c253cSXin Li return _LOCALMIRROR_ROOT + file_name 47*760c253cSXin Li 48*760c253cSXin Li 49*760c253cSXin Lidef _download(remote_path: str, local_file: Path): 50*760c253cSXin Li """Downloads the given gs:// path to the given local file.""" 51*760c253cSXin Li logging.info("Downloading %s -> %s", remote_path, local_file) 52*760c253cSXin Li subprocess.run( 53*760c253cSXin Li ["gsutil", "cp", remote_path, str(local_file)], 54*760c253cSXin Li check=True, 55*760c253cSXin Li stdin=subprocess.DEVNULL, 56*760c253cSXin Li ) 57*760c253cSXin Li 58*760c253cSXin Li 59*760c253cSXin Lidef _debinpkgify(binpkg_file: Path) -> Path: 60*760c253cSXin Li """Converts a binpkg into the files it installs. 61*760c253cSXin Li 62*760c253cSXin Li Note that this function makes temporary files in the same directory as 63*760c253cSXin Li `binpkg_file`. It makes no attempt to clean them up. 64*760c253cSXin Li """ 65*760c253cSXin Li logging.info("Converting %s from a binpkg...", binpkg_file) 66*760c253cSXin Li 67*760c253cSXin Li # The SDK builder produces binary packages: 68*760c253cSXin Li # https://wiki.gentoo.org/wiki/Binary_package_guide 69*760c253cSXin Li # 70*760c253cSXin Li # Which means that `binpkg_file` is in the XPAK format. We want to split 71*760c253cSXin Li # that out, and recompress it from zstd (which is the compression format 72*760c253cSXin Li # that CrOS uses) to bzip2 (which is what we've historically used, and 73*760c253cSXin Li # which is what our ebuild expects). 74*760c253cSXin Li tmpdir = binpkg_file.parent 75*760c253cSXin Li 76*760c253cSXin Li def _mkstemp(suffix=None) -> Path: 77*760c253cSXin Li fd, file_path = tempfile.mkstemp(dir=tmpdir, suffix=suffix) 78*760c253cSXin Li os.close(fd) 79*760c253cSXin Li return Path(file_path) 80*760c253cSXin Li 81*760c253cSXin Li # First, split the actual artifacts that land in the chroot out to 82*760c253cSXin Li # `temp_file`. 83*760c253cSXin Li artifacts_file = _mkstemp() 84*760c253cSXin Li logging.info( 85*760c253cSXin Li "Extracting artifacts from %s into %s...", binpkg_file, artifacts_file 86*760c253cSXin Li ) 87*760c253cSXin Li with artifacts_file.open("wb") as f: 88*760c253cSXin Li subprocess.run( 89*760c253cSXin Li [ 90*760c253cSXin Li "qtbz2", 91*760c253cSXin Li "-s", 92*760c253cSXin Li "-t", 93*760c253cSXin Li "-O", 94*760c253cSXin Li str(binpkg_file), 95*760c253cSXin Li ], 96*760c253cSXin Li check=True, 97*760c253cSXin Li stdout=f, 98*760c253cSXin Li ) 99*760c253cSXin Li 100*760c253cSXin Li decompressed_artifacts_file = _mkstemp() 101*760c253cSXin Li decompressed_artifacts_file.unlink() 102*760c253cSXin Li logging.info( 103*760c253cSXin Li "Decompressing artifacts from %s to %s...", 104*760c253cSXin Li artifacts_file, 105*760c253cSXin Li decompressed_artifacts_file, 106*760c253cSXin Li ) 107*760c253cSXin Li subprocess.run( 108*760c253cSXin Li [ 109*760c253cSXin Li "zstd", 110*760c253cSXin Li "-d", 111*760c253cSXin Li str(artifacts_file), 112*760c253cSXin Li "-o", 113*760c253cSXin Li str(decompressed_artifacts_file), 114*760c253cSXin Li ], 115*760c253cSXin Li check=True, 116*760c253cSXin Li ) 117*760c253cSXin Li 118*760c253cSXin Li # Finally, recompress it as a tbz2. 119*760c253cSXin Li tbz2_file = _mkstemp(".tbz2") 120*760c253cSXin Li logging.info( 121*760c253cSXin Li "Recompressing artifacts from %s to %s (this may take a while)...", 122*760c253cSXin Li decompressed_artifacts_file, 123*760c253cSXin Li tbz2_file, 124*760c253cSXin Li ) 125*760c253cSXin Li with tbz2_file.open("wb") as f: 126*760c253cSXin Li subprocess.run( 127*760c253cSXin Li [ 128*760c253cSXin Li "lbzip2", 129*760c253cSXin Li "-9", 130*760c253cSXin Li "-c", 131*760c253cSXin Li str(decompressed_artifacts_file), 132*760c253cSXin Li ], 133*760c253cSXin Li check=True, 134*760c253cSXin Li stdout=f, 135*760c253cSXin Li ) 136*760c253cSXin Li return tbz2_file 137*760c253cSXin Li 138*760c253cSXin Li 139*760c253cSXin Lidef _upload(local_file: Path, remote_path: str, force: bool): 140*760c253cSXin Li """Uploads the local file to the given gs:// path.""" 141*760c253cSXin Li logging.info("Uploading %s -> %s", local_file, remote_path) 142*760c253cSXin Li cmd_base = ["gsutil", "cp", "-a", "public-read"] 143*760c253cSXin Li if not force: 144*760c253cSXin Li cmd_base.append("-n") 145*760c253cSXin Li subprocess.run( 146*760c253cSXin Li cmd_base + [str(local_file), remote_path], 147*760c253cSXin Li check=True, 148*760c253cSXin Li stdin=subprocess.DEVNULL, 149*760c253cSXin Li ) 150*760c253cSXin Li 151*760c253cSXin Li 152*760c253cSXin Lidef main(argv: List[str]): 153*760c253cSXin Li logging.basicConfig( 154*760c253cSXin Li format=">> %(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: " 155*760c253cSXin Li "%(message)s", 156*760c253cSXin Li level=logging.INFO, 157*760c253cSXin Li ) 158*760c253cSXin Li 159*760c253cSXin Li parser = argparse.ArgumentParser( 160*760c253cSXin Li description=__doc__, 161*760c253cSXin Li formatter_class=argparse.RawDescriptionHelpFormatter, 162*760c253cSXin Li ) 163*760c253cSXin Li 164*760c253cSXin Li parser.add_argument( 165*760c253cSXin Li "sdk_artifact", 166*760c253cSXin Li help="Path to the SDK rust-bootstrap artifact to copy. e.g., " 167*760c253cSXin Li "gs://chromeos-prebuilt/host/amd64/amd64-host/" 168*760c253cSXin Li "chroot-2022.07.12.134334/packages/dev-lang/" 169*760c253cSXin Li "rust-bootstrap-1.59.0.tbz2.", 170*760c253cSXin Li ) 171*760c253cSXin Li parser.add_argument( 172*760c253cSXin Li "-n", 173*760c253cSXin Li "--dry-run", 174*760c253cSXin Li action="store_true", 175*760c253cSXin Li help="Do everything except actually uploading the artifact.", 176*760c253cSXin Li ) 177*760c253cSXin Li parser.add_argument( 178*760c253cSXin Li "--force", 179*760c253cSXin Li action="store_true", 180*760c253cSXin Li help="Upload the artifact even if one exists in localmirror already.", 181*760c253cSXin Li ) 182*760c253cSXin Li opts = parser.parse_args(argv) 183*760c253cSXin Li 184*760c253cSXin Li if not _is_in_chroot(): 185*760c253cSXin Li parser.error("Run me from within the chroot.") 186*760c253cSXin Li _ensure_lbzip2_is_installed() 187*760c253cSXin Li 188*760c253cSXin Li target_path = determine_target_path(opts.sdk_artifact) 189*760c253cSXin Li with tempfile.TemporaryDirectory() as tempdir: 190*760c253cSXin Li download_path = Path(tempdir) / "sdk_artifact" 191*760c253cSXin Li _download(opts.sdk_artifact, download_path) 192*760c253cSXin Li file_to_upload = _debinpkgify(download_path) 193*760c253cSXin Li if opts.dry_run: 194*760c253cSXin Li logging.info( 195*760c253cSXin Li "--dry-run specified; skipping upload of %s to %s", 196*760c253cSXin Li file_to_upload, 197*760c253cSXin Li target_path, 198*760c253cSXin Li ) 199*760c253cSXin Li else: 200*760c253cSXin Li _upload(file_to_upload, target_path, opts.force) 201*760c253cSXin Li 202*760c253cSXin Li 203*760c253cSXin Liif __name__ == "__main__": 204*760c253cSXin Li main(sys.argv[1:]) 205