• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2023 The ChromiumOS Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import argparse
7import json
8import os
9import subprocess
10import sys
11from itertools import chain, product, starmap
12from pathlib import Path
13from typing import Dict, Iterable, List, NamedTuple
14
15from impl.common import CROSVM_ROOT, Triple, verbose
16
17USAGE = """\
18Build crosvm with release (optimized) profile.
19
20To target local machine:
21
22    $ ./tools/build_release
23
24To cross-compile for aarch64, armhf or windows you can use:
25
26    $ ./tools/build_release --platform=aarch64
27    $ ./tools/build_release --platform=armhf
28    $ ./tools/build_release --platform=mingw64
29"""
30
31# We only need PGO for main binary, but for consistency, only exclude incompatible parts from PGO
32PGO_EXCLUDE = ["crosvm_control"]
33
34
35class Executable(NamedTuple):
36    """Container for info about an executable generated by cargo build/test."""
37
38    binary_path: Path
39    crate_name: str
40    cargo_target: str
41    kind: str
42    is_test: bool
43    is_fresh: bool
44
45    @property
46    def name(self):
47        return f"{self.crate_name}:{self.cargo_target}"
48
49
50def cargo(
51    cargo_command: str,
52    cwd: Path,
53    flags: List[str],
54    env: Dict[str, str],
55) -> Iterable[Executable]:
56    """
57    Executes a cargo command and returns the list of test binaries generated.
58
59    The build log will be hidden by default and only printed if the build
60    fails. In VERBOSE mode the output will be streamed directly.
61
62    Note: Exits the program if the build fails.
63    """
64    message_format = "json-diagnostic-rendered-ansi" if sys.stdout.isatty() else "json"
65    cmd = [
66        "cargo",
67        cargo_command,
68        f"--message-format={message_format}",
69        *flags,
70    ]
71    if verbose():
72        print("$", " ".join(cmd))
73    process = subprocess.Popen(
74        cmd,
75        cwd=cwd,
76        stdout=subprocess.PIPE,
77        stderr=subprocess.STDOUT,
78        text=True,
79        env=env,
80    )
81
82    messages: List[str] = []
83
84    # Read messages as cargo is running.
85    assert process.stdout
86    for line in iter(process.stdout.readline, ""):
87        # any non-json line is a message to print
88        if not line.startswith("{"):
89            if verbose():
90                print(line.rstrip())
91            messages.append(line.rstrip())
92            continue
93        json_line = json.loads(line)
94
95        # 'message' type lines will be printed
96        if json_line.get("message"):
97            message = json_line.get("message").get("rendered")
98            if verbose():
99                print(message)
100            messages.append(message)
101
102        # Collect info about test executables produced
103        elif json_line.get("executable"):
104            yield Executable(
105                Path(json_line.get("executable")),
106                crate_name=json_line.get("package_id", "").split(" ")[0],
107                cargo_target=json_line.get("target").get("name"),
108                kind=json_line.get("target").get("kind")[0],
109                is_test=json_line.get("profile", {}).get("test", False),
110                is_fresh=json_line.get("fresh", False),
111            )
112
113    if process.wait() != 0:
114        if not verbose():
115            for message in messages:
116                print(message)
117        sys.exit(-1)
118
119
120def main():
121    parser = argparse.ArgumentParser(usage=USAGE)
122    parser.add_argument(
123        "--build-target",
124        "--platform",
125        "-p",
126        help=(
127            "Override the cargo triple to build. Shorthands are available: (x86_64, armhf, "
128            + "aarch64, mingw64, msvc64)."
129        ),
130    )
131    parser.add_argument(
132        "--json",
133        action="store_true",
134        help="Output in JSON instead of human readable format.",
135    )
136    parser.add_argument("--strip", action="store_true", help="Strip output binaries")
137    pgo_group = parser.add_mutually_exclusive_group()
138    pgo_group.add_argument(
139        "--profile-generate",
140        help="Target directory to generate profile when running, must use absolute path",
141    )
142    pgo_group.add_argument(
143        "--profile-use", help="Profile file used for PGO, must use absolute path"
144    )
145    parser.add_argument("cargo_arguments", nargs="*", help="Extra arguments pass to cargo")
146
147    args = parser.parse_args()
148
149    if args.profile_generate and (
150        not os.path.isabs(args.profile_generate) or not os.path.isdir(args.profile_generate)
151    ):
152        raise ValueError("--profile-generate argument is not an absolute path to a folder")
153    if args.profile_use and (
154        not os.path.isabs(args.profile_use) or not os.path.isfile(args.profile_use)
155    ):
156        raise ValueError("--profile-use argument is not an absolute path to a file")
157
158    build_target = Triple.from_shorthand(args.build_target) if args.build_target else None
159    build_target = build_target or Triple.host_default()
160
161    features = build_target.feature_flag
162    cargo_args = [
163        "--release",
164        "--features=" + features,
165        f"--target={build_target}",
166        "--workspace",
167        *[f"--exclude={x}" for x in PGO_EXCLUDE if args.profile_generate or args.profile_use],
168        *args.cargo_arguments,
169    ]
170
171    build_env = os.environ.copy()
172    build_env.update(build_target.get_cargo_env())
173    build_env.setdefault("RUSTFLAGS", "")
174    build_env["RUSTFLAGS"] += " -D warnings"
175    if args.strip:
176        build_env["RUSTFLAGS"] += " -C strip=symbols"
177    if args.profile_generate:
178        build_env["RUSTFLAGS"] += " -C profile-generate=" + args.profile_generate
179    if args.profile_use:
180        build_env["RUSTFLAGS"] += " -C profile-use=" + args.profile_use
181
182    executables = list(cargo("build", CROSVM_ROOT, cargo_args, build_env))
183
184    if args.json:
185        result = {}
186        for exe in executables:
187            assert exe.cargo_target not in result
188            result[exe.cargo_target] = str(exe.binary_path)
189        print(json.dumps(result))
190    else:
191        print("Release binaries:")
192        for exe in executables:
193            print(f"Name: {exe.cargo_target}")
194            print(f"Path: {str(exe.binary_path)}")
195            print()
196
197
198if __name__ == "__main__":
199    main()
200