xref: /aosp_15_r20/external/crosvm/tools/impl/util.py (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2023 The ChromiumOS Authors
3*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file.
5*bb4ee6a4SAndroid Build Coastguard Worker
6*bb4ee6a4SAndroid Build Coastguard Worker"""
7*bb4ee6a4SAndroid Build Coastguard WorkerProvides general utility functions.
8*bb4ee6a4SAndroid Build Coastguard Worker"""
9*bb4ee6a4SAndroid Build Coastguard Worker
10*bb4ee6a4SAndroid Build Coastguard Workerimport argparse
11*bb4ee6a4SAndroid Build Coastguard Workerimport contextlib
12*bb4ee6a4SAndroid Build Coastguard Workerimport datetime
13*bb4ee6a4SAndroid Build Coastguard Workerimport functools
14*bb4ee6a4SAndroid Build Coastguard Workerimport os
15*bb4ee6a4SAndroid Build Coastguard Workerimport re
16*bb4ee6a4SAndroid Build Coastguard Workerimport subprocess
17*bb4ee6a4SAndroid Build Coastguard Workerimport sys
18*bb4ee6a4SAndroid Build Coastguard Workerimport urllib
19*bb4ee6a4SAndroid Build Coastguard Workerimport urllib.request
20*bb4ee6a4SAndroid Build Coastguard Workerimport urllib.error
21*bb4ee6a4SAndroid Build Coastguard Workerfrom pathlib import Path
22*bb4ee6a4SAndroid Build Coastguard Workerfrom subprocess import DEVNULL, PIPE, STDOUT  # type: ignore
23*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import (
24*bb4ee6a4SAndroid Build Coastguard Worker    Dict,
25*bb4ee6a4SAndroid Build Coastguard Worker    List,
26*bb4ee6a4SAndroid Build Coastguard Worker    NamedTuple,
27*bb4ee6a4SAndroid Build Coastguard Worker    Optional,
28*bb4ee6a4SAndroid Build Coastguard Worker    Tuple,
29*bb4ee6a4SAndroid Build Coastguard Worker    Union,
30*bb4ee6a4SAndroid Build Coastguard Worker)
31*bb4ee6a4SAndroid Build Coastguard Worker
32*bb4ee6a4SAndroid Build Coastguard WorkerPathLike = Union[Path, str]
33*bb4ee6a4SAndroid Build Coastguard Worker
34*bb4ee6a4SAndroid Build Coastguard Worker# Regex that matches ANSI escape sequences
35*bb4ee6a4SAndroid Build Coastguard WorkerANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
36*bb4ee6a4SAndroid Build Coastguard Worker
37*bb4ee6a4SAndroid Build Coastguard Worker
38*bb4ee6a4SAndroid Build Coastguard Workerdef find_crosvm_root():
39*bb4ee6a4SAndroid Build Coastguard Worker    "Walk up from CWD until we find the crosvm root dir."
40*bb4ee6a4SAndroid Build Coastguard Worker    path = Path("").resolve()
41*bb4ee6a4SAndroid Build Coastguard Worker    while True:
42*bb4ee6a4SAndroid Build Coastguard Worker        if (path / "tools/impl/common.py").is_file():
43*bb4ee6a4SAndroid Build Coastguard Worker            return path
44*bb4ee6a4SAndroid Build Coastguard Worker        if path.parent:
45*bb4ee6a4SAndroid Build Coastguard Worker            path = path.parent
46*bb4ee6a4SAndroid Build Coastguard Worker        else:
47*bb4ee6a4SAndroid Build Coastguard Worker            raise Exception("Cannot find crosvm root dir.")
48*bb4ee6a4SAndroid Build Coastguard Worker
49*bb4ee6a4SAndroid Build Coastguard Worker
50*bb4ee6a4SAndroid Build Coastguard Worker"Root directory of crosvm derived from CWD."
51*bb4ee6a4SAndroid Build Coastguard WorkerCROSVM_ROOT = find_crosvm_root()
52*bb4ee6a4SAndroid Build Coastguard Worker
53*bb4ee6a4SAndroid Build Coastguard Worker"Cargo.toml file of crosvm"
54*bb4ee6a4SAndroid Build Coastguard WorkerCROSVM_TOML = CROSVM_ROOT / "Cargo.toml"
55*bb4ee6a4SAndroid Build Coastguard Worker
56*bb4ee6a4SAndroid Build Coastguard Worker"""
57*bb4ee6a4SAndroid Build Coastguard WorkerRoot directory of crosvm devtools.
58*bb4ee6a4SAndroid Build Coastguard Worker
59*bb4ee6a4SAndroid Build Coastguard WorkerMay be different from `CROSVM_ROOT/tools`, which is allows you to run the crosvm dev
60*bb4ee6a4SAndroid Build Coastguard Workertools from this directory on another crosvm repo.
61*bb4ee6a4SAndroid Build Coastguard Worker
62*bb4ee6a4SAndroid Build Coastguard WorkerUse this if you want to call crosvm dev tools, which will use the scripts relative
63*bb4ee6a4SAndroid Build Coastguard Workerto this file.
64*bb4ee6a4SAndroid Build Coastguard Worker"""
65*bb4ee6a4SAndroid Build Coastguard WorkerTOOLS_ROOT = Path(__file__).parent.parent.resolve()
66*bb4ee6a4SAndroid Build Coastguard Worker
67*bb4ee6a4SAndroid Build Coastguard Worker"Cache directory that is preserved between builds in CI."
68*bb4ee6a4SAndroid Build Coastguard WorkerCACHE_DIR = Path(os.environ.get("CROSVM_CACHE_DIR", os.environ.get("TMPDIR", "/tmp")))
69*bb4ee6a4SAndroid Build Coastguard Worker
70*bb4ee6a4SAndroid Build Coastguard Worker# Ensure that we really found the crosvm root directory
71*bb4ee6a4SAndroid Build Coastguard Workerassert 'name = "crosvm"' in CROSVM_TOML.read_text()
72*bb4ee6a4SAndroid Build Coastguard Worker
73*bb4ee6a4SAndroid Build Coastguard Worker# List of times recorded by `record_time` which will be printed if --timing-info is provided.
74*bb4ee6a4SAndroid Build Coastguard Workerglobal_time_records: List[Tuple[str, datetime.timedelta]] = []
75*bb4ee6a4SAndroid Build Coastguard Worker
76*bb4ee6a4SAndroid Build Coastguard Worker
77*bb4ee6a4SAndroid Build Coastguard Workerdef crosvm_target_dir():
78*bb4ee6a4SAndroid Build Coastguard Worker    crosvm_target = os.environ.get("CROSVM_TARGET_DIR")
79*bb4ee6a4SAndroid Build Coastguard Worker    cargo_target = os.environ.get("CARGO_TARGET_DIR")
80*bb4ee6a4SAndroid Build Coastguard Worker    if crosvm_target:
81*bb4ee6a4SAndroid Build Coastguard Worker        return Path(crosvm_target)
82*bb4ee6a4SAndroid Build Coastguard Worker    elif cargo_target:
83*bb4ee6a4SAndroid Build Coastguard Worker        return Path(cargo_target) / "crosvm"
84*bb4ee6a4SAndroid Build Coastguard Worker    else:
85*bb4ee6a4SAndroid Build Coastguard Worker        return CROSVM_ROOT / "target/crosvm"
86*bb4ee6a4SAndroid Build Coastguard Worker
87*bb4ee6a4SAndroid Build Coastguard Worker
88*bb4ee6a4SAndroid Build Coastguard Worker@functools.lru_cache(None)
89*bb4ee6a4SAndroid Build Coastguard Workerdef parse_common_args():
90*bb4ee6a4SAndroid Build Coastguard Worker    """
91*bb4ee6a4SAndroid Build Coastguard Worker    Parse args common to all scripts
92*bb4ee6a4SAndroid Build Coastguard Worker
93*bb4ee6a4SAndroid Build Coastguard Worker    These args are parsed separately of the run_main/run_commands method so we can access
94*bb4ee6a4SAndroid Build Coastguard Worker    verbose/etc before the commands arguments are parsed.
95*bb4ee6a4SAndroid Build Coastguard Worker    """
96*bb4ee6a4SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(add_help=False)
97*bb4ee6a4SAndroid Build Coastguard Worker    add_common_args(parser)
98*bb4ee6a4SAndroid Build Coastguard Worker    return parser.parse_known_args()[0]
99*bb4ee6a4SAndroid Build Coastguard Worker
100*bb4ee6a4SAndroid Build Coastguard Worker
101*bb4ee6a4SAndroid Build Coastguard Workerdef add_common_args(parser: argparse.ArgumentParser):
102*bb4ee6a4SAndroid Build Coastguard Worker    "These args are added to all commands."
103*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument(
104*bb4ee6a4SAndroid Build Coastguard Worker        "--color",
105*bb4ee6a4SAndroid Build Coastguard Worker        default="auto",
106*bb4ee6a4SAndroid Build Coastguard Worker        choices=("always", "never", "auto"),
107*bb4ee6a4SAndroid Build Coastguard Worker        help="Force enable or disable colors. Defaults to automatic detection.",
108*bb4ee6a4SAndroid Build Coastguard Worker    )
109*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument(
110*bb4ee6a4SAndroid Build Coastguard Worker        "--verbose",
111*bb4ee6a4SAndroid Build Coastguard Worker        "-v",
112*bb4ee6a4SAndroid Build Coastguard Worker        action="store_true",
113*bb4ee6a4SAndroid Build Coastguard Worker        default=False,
114*bb4ee6a4SAndroid Build Coastguard Worker        help="Print more details about the commands this script is running.",
115*bb4ee6a4SAndroid Build Coastguard Worker    )
116*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument(
117*bb4ee6a4SAndroid Build Coastguard Worker        "--very-verbose",
118*bb4ee6a4SAndroid Build Coastguard Worker        "-vv",
119*bb4ee6a4SAndroid Build Coastguard Worker        action="store_true",
120*bb4ee6a4SAndroid Build Coastguard Worker        default=False,
121*bb4ee6a4SAndroid Build Coastguard Worker        help="Print more debug output",
122*bb4ee6a4SAndroid Build Coastguard Worker    )
123*bb4ee6a4SAndroid Build Coastguard Worker    parser.add_argument(
124*bb4ee6a4SAndroid Build Coastguard Worker        "--timing-info",
125*bb4ee6a4SAndroid Build Coastguard Worker        action="store_true",
126*bb4ee6a4SAndroid Build Coastguard Worker        default=False,
127*bb4ee6a4SAndroid Build Coastguard Worker        help="Print info on how long which parts of the command take",
128*bb4ee6a4SAndroid Build Coastguard Worker    )
129*bb4ee6a4SAndroid Build Coastguard Worker
130*bb4ee6a4SAndroid Build Coastguard Worker
131*bb4ee6a4SAndroid Build Coastguard Workerdef verbose():
132*bb4ee6a4SAndroid Build Coastguard Worker    return very_verbose() or parse_common_args().verbose
133*bb4ee6a4SAndroid Build Coastguard Worker
134*bb4ee6a4SAndroid Build Coastguard Worker
135*bb4ee6a4SAndroid Build Coastguard Workerdef very_verbose():
136*bb4ee6a4SAndroid Build Coastguard Worker    return parse_common_args().very_verbose
137*bb4ee6a4SAndroid Build Coastguard Worker
138*bb4ee6a4SAndroid Build Coastguard Worker
139*bb4ee6a4SAndroid Build Coastguard Workerdef color_enabled():
140*bb4ee6a4SAndroid Build Coastguard Worker    color_arg = parse_common_args().color
141*bb4ee6a4SAndroid Build Coastguard Worker    if color_arg == "never":
142*bb4ee6a4SAndroid Build Coastguard Worker        return False
143*bb4ee6a4SAndroid Build Coastguard Worker    if color_arg == "always":
144*bb4ee6a4SAndroid Build Coastguard Worker        return True
145*bb4ee6a4SAndroid Build Coastguard Worker    return sys.stdout.isatty()
146*bb4ee6a4SAndroid Build Coastguard Worker
147*bb4ee6a4SAndroid Build Coastguard Worker
148*bb4ee6a4SAndroid Build Coastguard Workerdef find_scripts(path: Path, shebang: str):
149*bb4ee6a4SAndroid Build Coastguard Worker    for file in path.glob("*"):
150*bb4ee6a4SAndroid Build Coastguard Worker        if file.is_file() and file.open(errors="ignore").read(512).startswith(f"#!{shebang}"):
151*bb4ee6a4SAndroid Build Coastguard Worker            yield file
152*bb4ee6a4SAndroid Build Coastguard Worker
153*bb4ee6a4SAndroid Build Coastguard Worker
154*bb4ee6a4SAndroid Build Coastguard Workerdef confirm(message: str, default: bool = False):
155*bb4ee6a4SAndroid Build Coastguard Worker    print(message, "[y/N]" if default == False else "[Y/n]", end=" ", flush=True)
156*bb4ee6a4SAndroid Build Coastguard Worker    response = sys.stdin.readline().strip()
157*bb4ee6a4SAndroid Build Coastguard Worker    if response in ("y", "Y"):
158*bb4ee6a4SAndroid Build Coastguard Worker        return True
159*bb4ee6a4SAndroid Build Coastguard Worker    if response in ("n", "N"):
160*bb4ee6a4SAndroid Build Coastguard Worker        return False
161*bb4ee6a4SAndroid Build Coastguard Worker    return default
162*bb4ee6a4SAndroid Build Coastguard Worker
163*bb4ee6a4SAndroid Build Coastguard Worker
164*bb4ee6a4SAndroid Build Coastguard Workerdef is_cros_repo():
165*bb4ee6a4SAndroid Build Coastguard Worker    "Returns true if the crosvm repo is a symlink or worktree to a CrOS repo checkout."
166*bb4ee6a4SAndroid Build Coastguard Worker    dot_git = CROSVM_ROOT / ".git"
167*bb4ee6a4SAndroid Build Coastguard Worker    if not dot_git.is_symlink() and dot_git.is_dir():
168*bb4ee6a4SAndroid Build Coastguard Worker        return False
169*bb4ee6a4SAndroid Build Coastguard Worker    return (cros_repo_root() / ".repo").exists()
170*bb4ee6a4SAndroid Build Coastguard Worker
171*bb4ee6a4SAndroid Build Coastguard Worker
172*bb4ee6a4SAndroid Build Coastguard Workerdef cros_repo_root():
173*bb4ee6a4SAndroid Build Coastguard Worker    "Root directory of the CrOS repo checkout."
174*bb4ee6a4SAndroid Build Coastguard Worker    return (CROSVM_ROOT / "../../..").resolve()
175*bb4ee6a4SAndroid Build Coastguard Worker
176*bb4ee6a4SAndroid Build Coastguard Worker
177*bb4ee6a4SAndroid Build Coastguard Workerdef is_kiwi_repo():
178*bb4ee6a4SAndroid Build Coastguard Worker    "Returns true if the crosvm repo contains .kiwi_repo file."
179*bb4ee6a4SAndroid Build Coastguard Worker    dot_kiwi_repo = CROSVM_ROOT / ".kiwi_repo"
180*bb4ee6a4SAndroid Build Coastguard Worker    return dot_kiwi_repo.exists()
181*bb4ee6a4SAndroid Build Coastguard Worker
182*bb4ee6a4SAndroid Build Coastguard Worker
183*bb4ee6a4SAndroid Build Coastguard Workerdef kiwi_repo_root():
184*bb4ee6a4SAndroid Build Coastguard Worker    "Root directory of the kiwi repo checkout."
185*bb4ee6a4SAndroid Build Coastguard Worker    return (CROSVM_ROOT / "../..").resolve()
186*bb4ee6a4SAndroid Build Coastguard Worker
187*bb4ee6a4SAndroid Build Coastguard Workerdef is_aosp_repo():
188*bb4ee6a4SAndroid Build Coastguard Worker    "Returns true if the crosvm repo is an AOSP repo checkout."
189*bb4ee6a4SAndroid Build Coastguard Worker    android_bp = CROSVM_ROOT / "Android.bp"
190*bb4ee6a4SAndroid Build Coastguard Worker    return android_bp.exists()
191*bb4ee6a4SAndroid Build Coastguard Worker
192*bb4ee6a4SAndroid Build Coastguard Workerdef aosp_repo_root():
193*bb4ee6a4SAndroid Build Coastguard Worker    "Root directory of AOSP repo checkout."
194*bb4ee6a4SAndroid Build Coastguard Worker    return (CROSVM_ROOT / "../..").resolve()
195*bb4ee6a4SAndroid Build Coastguard Worker
196*bb4ee6a4SAndroid Build Coastguard Workerdef is_aosp_repo():
197*bb4ee6a4SAndroid Build Coastguard Worker    "Returns true if the crosvm repo is an AOSP repo checkout."
198*bb4ee6a4SAndroid Build Coastguard Worker    android_bp = CROSVM_ROOT / "Android.bp"
199*bb4ee6a4SAndroid Build Coastguard Worker    return android_bp.exists()
200*bb4ee6a4SAndroid Build Coastguard Worker
201*bb4ee6a4SAndroid Build Coastguard Worker
202*bb4ee6a4SAndroid Build Coastguard Workerdef aosp_repo_root():
203*bb4ee6a4SAndroid Build Coastguard Worker    "Root directory of AOSP repo checkout."
204*bb4ee6a4SAndroid Build Coastguard Worker    return (CROSVM_ROOT / "../..").resolve()
205*bb4ee6a4SAndroid Build Coastguard Worker
206*bb4ee6a4SAndroid Build Coastguard Worker
207*bb4ee6a4SAndroid Build Coastguard Workerdef sudo_is_passwordless():
208*bb4ee6a4SAndroid Build Coastguard Worker    # Run with --askpass but no askpass set, succeeds only if passwordless sudo
209*bb4ee6a4SAndroid Build Coastguard Worker    # is available.
210*bb4ee6a4SAndroid Build Coastguard Worker    (ret, _) = subprocess.getstatusoutput("SUDO_ASKPASS=false sudo --askpass true")
211*bb4ee6a4SAndroid Build Coastguard Worker    return ret == 0
212*bb4ee6a4SAndroid Build Coastguard Worker
213*bb4ee6a4SAndroid Build Coastguard Worker
214*bb4ee6a4SAndroid Build Coastguard WorkerSHORTHANDS = {
215*bb4ee6a4SAndroid Build Coastguard Worker    "mingw64": "x86_64-pc-windows-gnu",
216*bb4ee6a4SAndroid Build Coastguard Worker    "msvc64": "x86_64-pc-windows-msvc",
217*bb4ee6a4SAndroid Build Coastguard Worker    "armhf": "armv7-unknown-linux-gnueabihf",
218*bb4ee6a4SAndroid Build Coastguard Worker    "aarch64": "aarch64-unknown-linux-gnu",
219*bb4ee6a4SAndroid Build Coastguard Worker    "riscv64": "riscv64gc-unknown-linux-gnu",
220*bb4ee6a4SAndroid Build Coastguard Worker    "x86_64": "x86_64-unknown-linux-gnu",
221*bb4ee6a4SAndroid Build Coastguard Worker    "android": "aarch64-linux-android",
222*bb4ee6a4SAndroid Build Coastguard Worker}
223*bb4ee6a4SAndroid Build Coastguard Worker
224*bb4ee6a4SAndroid Build Coastguard Worker
225*bb4ee6a4SAndroid Build Coastguard Workerclass Triple(NamedTuple):
226*bb4ee6a4SAndroid Build Coastguard Worker    """
227*bb4ee6a4SAndroid Build Coastguard Worker    Build triple in cargo format.
228*bb4ee6a4SAndroid Build Coastguard Worker
229*bb4ee6a4SAndroid Build Coastguard Worker    The format is: <arch><sub>-<vendor>-<sys>-<abi>, However, we will treat <arch><sub> as a single
230*bb4ee6a4SAndroid Build Coastguard Worker    arch to simplify things.
231*bb4ee6a4SAndroid Build Coastguard Worker    """
232*bb4ee6a4SAndroid Build Coastguard Worker
233*bb4ee6a4SAndroid Build Coastguard Worker    arch: str
234*bb4ee6a4SAndroid Build Coastguard Worker    vendor: str
235*bb4ee6a4SAndroid Build Coastguard Worker    sys: Optional[str]
236*bb4ee6a4SAndroid Build Coastguard Worker    abi: Optional[str]
237*bb4ee6a4SAndroid Build Coastguard Worker
238*bb4ee6a4SAndroid Build Coastguard Worker    @classmethod
239*bb4ee6a4SAndroid Build Coastguard Worker    def from_shorthand(cls, shorthand: str):
240*bb4ee6a4SAndroid Build Coastguard Worker        "These shorthands make it easier to specify triples on the command line."
241*bb4ee6a4SAndroid Build Coastguard Worker        if "-" in shorthand:
242*bb4ee6a4SAndroid Build Coastguard Worker            triple = shorthand
243*bb4ee6a4SAndroid Build Coastguard Worker        elif shorthand in SHORTHANDS:
244*bb4ee6a4SAndroid Build Coastguard Worker            triple = SHORTHANDS[shorthand]
245*bb4ee6a4SAndroid Build Coastguard Worker        else:
246*bb4ee6a4SAndroid Build Coastguard Worker            raise Exception(f"Not a valid build triple shorthand: {shorthand}")
247*bb4ee6a4SAndroid Build Coastguard Worker        return cls.from_str(triple)
248*bb4ee6a4SAndroid Build Coastguard Worker
249*bb4ee6a4SAndroid Build Coastguard Worker    @classmethod
250*bb4ee6a4SAndroid Build Coastguard Worker    def from_str(cls, triple: str):
251*bb4ee6a4SAndroid Build Coastguard Worker        parts = triple.split("-")
252*bb4ee6a4SAndroid Build Coastguard Worker        if len(parts) < 2:
253*bb4ee6a4SAndroid Build Coastguard Worker            raise Exception(f"Unsupported triple {triple}")
254*bb4ee6a4SAndroid Build Coastguard Worker        return cls(
255*bb4ee6a4SAndroid Build Coastguard Worker            parts[0],
256*bb4ee6a4SAndroid Build Coastguard Worker            parts[1],
257*bb4ee6a4SAndroid Build Coastguard Worker            parts[2] if len(parts) > 2 else None,
258*bb4ee6a4SAndroid Build Coastguard Worker            parts[3] if len(parts) > 3 else None,
259*bb4ee6a4SAndroid Build Coastguard Worker        )
260*bb4ee6a4SAndroid Build Coastguard Worker
261*bb4ee6a4SAndroid Build Coastguard Worker    @classmethod
262*bb4ee6a4SAndroid Build Coastguard Worker    def from_linux_arch(cls, arch: str):
263*bb4ee6a4SAndroid Build Coastguard Worker        "Rough logic to convert the output of `arch` into a corresponding linux build triple."
264*bb4ee6a4SAndroid Build Coastguard Worker        if arch == "armhf":
265*bb4ee6a4SAndroid Build Coastguard Worker            return cls.from_str("armv7-unknown-linux-gnueabihf")
266*bb4ee6a4SAndroid Build Coastguard Worker        else:
267*bb4ee6a4SAndroid Build Coastguard Worker            return cls.from_str(f"{arch}-unknown-linux-gnu")
268*bb4ee6a4SAndroid Build Coastguard Worker
269*bb4ee6a4SAndroid Build Coastguard Worker    @classmethod
270*bb4ee6a4SAndroid Build Coastguard Worker    def host_default(cls):
271*bb4ee6a4SAndroid Build Coastguard Worker        "Returns the default build triple of the host."
272*bb4ee6a4SAndroid Build Coastguard Worker        rustc_info = subprocess.check_output(["rustc", "-vV"], text=True)
273*bb4ee6a4SAndroid Build Coastguard Worker        match = re.search(r"host: (\S+)", rustc_info)
274*bb4ee6a4SAndroid Build Coastguard Worker        if not match:
275*bb4ee6a4SAndroid Build Coastguard Worker            raise Exception(f"Cannot parse rustc info: {rustc_info}")
276*bb4ee6a4SAndroid Build Coastguard Worker        return cls.from_str(match.group(1))
277*bb4ee6a4SAndroid Build Coastguard Worker
278*bb4ee6a4SAndroid Build Coastguard Worker    @property
279*bb4ee6a4SAndroid Build Coastguard Worker    def feature_flag(self):
280*bb4ee6a4SAndroid Build Coastguard Worker        triple_to_shorthand = {v: k for k, v in SHORTHANDS.items()}
281*bb4ee6a4SAndroid Build Coastguard Worker        shorthand = triple_to_shorthand.get(str(self))
282*bb4ee6a4SAndroid Build Coastguard Worker        if not shorthand:
283*bb4ee6a4SAndroid Build Coastguard Worker            raise Exception(f"No feature set for triple {self}")
284*bb4ee6a4SAndroid Build Coastguard Worker        return f"all-{shorthand}"
285*bb4ee6a4SAndroid Build Coastguard Worker
286*bb4ee6a4SAndroid Build Coastguard Worker    @property
287*bb4ee6a4SAndroid Build Coastguard Worker    def target_dir(self):
288*bb4ee6a4SAndroid Build Coastguard Worker        return crosvm_target_dir() / str(self)
289*bb4ee6a4SAndroid Build Coastguard Worker
290*bb4ee6a4SAndroid Build Coastguard Worker    def get_cargo_env(self):
291*bb4ee6a4SAndroid Build Coastguard Worker        """Environment variables to make cargo use the test target."""
292*bb4ee6a4SAndroid Build Coastguard Worker        env: Dict[str, str] = {}
293*bb4ee6a4SAndroid Build Coastguard Worker        cargo_target = str(self)
294*bb4ee6a4SAndroid Build Coastguard Worker        env["CARGO_BUILD_TARGET"] = cargo_target
295*bb4ee6a4SAndroid Build Coastguard Worker        env["CARGO_TARGET_DIR"] = str(self.target_dir)
296*bb4ee6a4SAndroid Build Coastguard Worker        env["CROSVM_TARGET_DIR"] = str(crosvm_target_dir())
297*bb4ee6a4SAndroid Build Coastguard Worker        # Android builds are not fully supported and can only be used to run clippy.
298*bb4ee6a4SAndroid Build Coastguard Worker        # Underlying libraries (e.g. minijail) will be built for linux instead
299*bb4ee6a4SAndroid Build Coastguard Worker        # TODO(denniskempin): This could be better done with [env] in Cargo.toml if it supported
300*bb4ee6a4SAndroid Build Coastguard Worker        # per-target configuration. See https://github.com/rust-lang/cargo/issues/10273
301*bb4ee6a4SAndroid Build Coastguard Worker        if str(self).endswith("-linux-android"):
302*bb4ee6a4SAndroid Build Coastguard Worker            env["MINIJAIL_DO_NOT_BUILD"] = "true"
303*bb4ee6a4SAndroid Build Coastguard Worker            env["MINIJAIL_BINDGEN_TARGET"] = f"{self.arch}-unknown-linux-gnu"
304*bb4ee6a4SAndroid Build Coastguard Worker        return env
305*bb4ee6a4SAndroid Build Coastguard Worker
306*bb4ee6a4SAndroid Build Coastguard Worker    def __str__(self):
307*bb4ee6a4SAndroid Build Coastguard Worker        parts = [self.arch, self.vendor]
308*bb4ee6a4SAndroid Build Coastguard Worker        if self.sys:
309*bb4ee6a4SAndroid Build Coastguard Worker            parts = [*parts, self.sys]
310*bb4ee6a4SAndroid Build Coastguard Worker        if self.abi:
311*bb4ee6a4SAndroid Build Coastguard Worker            parts = [*parts, self.abi]
312*bb4ee6a4SAndroid Build Coastguard Worker        return "-".join(parts)
313*bb4ee6a4SAndroid Build Coastguard Worker
314*bb4ee6a4SAndroid Build Coastguard Worker
315*bb4ee6a4SAndroid Build Coastguard Workerdef download_file(url: str, filename: Path, attempts: int = 3):
316*bb4ee6a4SAndroid Build Coastguard Worker    assert attempts > 0
317*bb4ee6a4SAndroid Build Coastguard Worker    while True:
318*bb4ee6a4SAndroid Build Coastguard Worker        attempts -= 1
319*bb4ee6a4SAndroid Build Coastguard Worker        try:
320*bb4ee6a4SAndroid Build Coastguard Worker            urllib.request.urlretrieve(url, filename)
321*bb4ee6a4SAndroid Build Coastguard Worker            return
322*bb4ee6a4SAndroid Build Coastguard Worker        except Exception as e:
323*bb4ee6a4SAndroid Build Coastguard Worker            if attempts == 0:
324*bb4ee6a4SAndroid Build Coastguard Worker                raise e
325*bb4ee6a4SAndroid Build Coastguard Worker            else:
326*bb4ee6a4SAndroid Build Coastguard Worker                print("Download failed:", e)
327*bb4ee6a4SAndroid Build Coastguard Worker
328*bb4ee6a4SAndroid Build Coastguard Worker
329*bb4ee6a4SAndroid Build Coastguard Workerdef strip_ansi_escape_sequences(line: str) -> str:
330*bb4ee6a4SAndroid Build Coastguard Worker    return ANSI_ESCAPE.sub("", line)
331*bb4ee6a4SAndroid Build Coastguard Worker
332*bb4ee6a4SAndroid Build Coastguard Worker
333*bb4ee6a4SAndroid Build Coastguard Workerdef ensure_packages_exist(*packages: str):
334*bb4ee6a4SAndroid Build Coastguard Worker    """
335*bb4ee6a4SAndroid Build Coastguard Worker    Exits if one of the listed packages does not exist.
336*bb4ee6a4SAndroid Build Coastguard Worker    """
337*bb4ee6a4SAndroid Build Coastguard Worker    missing_packages: List[str] = []
338*bb4ee6a4SAndroid Build Coastguard Worker
339*bb4ee6a4SAndroid Build Coastguard Worker    for package in packages:
340*bb4ee6a4SAndroid Build Coastguard Worker        try:
341*bb4ee6a4SAndroid Build Coastguard Worker            __import__(package)
342*bb4ee6a4SAndroid Build Coastguard Worker        except ImportError:
343*bb4ee6a4SAndroid Build Coastguard Worker            missing_packages.append(package)
344*bb4ee6a4SAndroid Build Coastguard Worker
345*bb4ee6a4SAndroid Build Coastguard Worker    if missing_packages:
346*bb4ee6a4SAndroid Build Coastguard Worker        debian_packages = [f"python3-{p}" for p in missing_packages]
347*bb4ee6a4SAndroid Build Coastguard Worker        package_list = " ".join(debian_packages)
348*bb4ee6a4SAndroid Build Coastguard Worker        print("Missing python dependencies. Please re-run ./tools/install-deps")
349*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Or `sudo apt install {package_list}`")
350*bb4ee6a4SAndroid Build Coastguard Worker        sys.exit(1)
351*bb4ee6a4SAndroid Build Coastguard Worker
352*bb4ee6a4SAndroid Build Coastguard Worker
353*bb4ee6a4SAndroid Build Coastguard Worker@contextlib.contextmanager
354*bb4ee6a4SAndroid Build Coastguard Workerdef record_time(title: str):
355*bb4ee6a4SAndroid Build Coastguard Worker    """
356*bb4ee6a4SAndroid Build Coastguard Worker    Records wall-time of how long this context lasts.
357*bb4ee6a4SAndroid Build Coastguard Worker
358*bb4ee6a4SAndroid Build Coastguard Worker    The results will be printed at the end of script executation if --timing-info is specified.
359*bb4ee6a4SAndroid Build Coastguard Worker    """
360*bb4ee6a4SAndroid Build Coastguard Worker    start_time = datetime.datetime.now()
361*bb4ee6a4SAndroid Build Coastguard Worker    try:
362*bb4ee6a4SAndroid Build Coastguard Worker        yield
363*bb4ee6a4SAndroid Build Coastguard Worker    finally:
364*bb4ee6a4SAndroid Build Coastguard Worker        global_time_records.append((title, datetime.datetime.now() - start_time))
365*bb4ee6a4SAndroid Build Coastguard Worker
366*bb4ee6a4SAndroid Build Coastguard Worker
367*bb4ee6a4SAndroid Build Coastguard Workerdef print_timing_info():
368*bb4ee6a4SAndroid Build Coastguard Worker    print()
369*bb4ee6a4SAndroid Build Coastguard Worker    print("Timing info:")
370*bb4ee6a4SAndroid Build Coastguard Worker    print()
371*bb4ee6a4SAndroid Build Coastguard Worker    for title, delta in global_time_records:
372*bb4ee6a4SAndroid Build Coastguard Worker        print(f"  {title:20} {delta.total_seconds():.2f}s")
373