xref: /aosp_15_r20/external/toolchain-utils/rust_tools/copy_rust_bootstrap.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
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