xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/build.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1#!/usr/bin/env python3
2# Copyright 2019 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
6"""Build script that builds a binary from a bundle."""
7
8import argparse
9import os.path
10import re
11import subprocess
12import sys
13
14
15def parse_args():
16    parser = argparse.ArgumentParser()
17    parser.add_argument(
18        "--config",
19        required=True,
20        choices=["cros.hardened", "cros.nonhardened", "cros.host", "android"],
21    )
22    parser.add_argument(
23        "--use_ccache", required=True, choices=["true", "false"]
24    )
25    parser.add_argument(
26        "--use_llvm_next", required=True, choices=["true", "false"]
27    )
28    parser.add_argument("--output_file", required=True, type=str)
29    parser.add_argument(
30        "--static",
31        choices=["true", "false"],
32        help="If true, produce a static wrapper. Autodetects a good value if "
33        "unspecified.",
34    )
35
36    version_args = parser.add_mutually_exclusive_group()
37    version_args.add_argument(
38        "--version",
39        help="""
40        A string to pass to `go` that instructs the compiler wrapper about what
41        version to print. Automatically selects the current git commit SHA if
42        this is left unspecified.
43        """,
44    )
45    parser.add_argument(
46        "--version_suffix",
47        help="""
48        A string appended to the **computed** version of the wrapper. This is
49        appended directly without any delimiter. Incompatible with
50        `--version`.
51        """,
52    )
53    args = parser.parse_args()
54
55    if args.static is None:
56        args.static = "cros" not in args.config
57    else:
58        args.static = args.static == "true"
59
60    return args
61
62
63def calc_go_args(args, version, build_dir, output_file):
64    # These seem unnecessary, and might lead to breakages with Go's ldflag
65    # parsing. Don't allow them.
66    if "'" in version:
67        raise ValueError("`version` should not contain single quotes")
68
69    ldFlags = [
70        "-X",
71        "main.ConfigName=" + args.config,
72        "-X",
73        "main.UseCCache=" + args.use_ccache,
74        "-X",
75        "main.UseLlvmNext=" + args.use_llvm_next,
76        "-X",
77        # Quote this, as `version` may have spaces in it.
78        "'main.Version=" + version + "'",
79    ]
80
81    # If the wrapper is intended for ChromeOS, we need to use libc's exec.
82    extra_args = []
83    if not args.static:
84        extra_args += ["-tags", "libc_exec"]
85
86    if args.config == "android":
87        # If android_llvm_next_flags.go DNE, we'll get an obscure "no
88        # llvmNextFlags" build error; complaining here is clearer.
89        if not os.path.exists(
90            os.path.join(build_dir, "android_llvm_next_flags.go")
91        ):
92            sys.exit(
93                "In order to build the Android wrapper, you must have a local "
94                "android_llvm_next_flags.go file; please see "
95                "cros_llvm_next_flags.go."
96            )
97        extra_args += ["-tags", "android_llvm_next_flags"]
98
99    return [
100        "go",
101        "build",
102        "-o",
103        output_file,
104        "-ldflags",
105        " ".join(ldFlags),
106    ] + extra_args
107
108
109def read_version(build_dir):
110    version_path = os.path.join(build_dir, "VERSION")
111    if os.path.exists(version_path):
112        with open(version_path, "r", encoding="utf-8") as r:
113            return r.read()
114
115    last_commit_msg = subprocess.check_output(
116        ["git", "-C", build_dir, "log", "-1", "--pretty=%B"], encoding="utf-8"
117    )
118    # Use last found change id to support reverts as well.
119    change_ids = re.findall(r"Change-Id: (\w+)", last_commit_msg)
120    if not change_ids:
121        sys.exit("Couldn't find Change-Id in last commit message.")
122    return change_ids[-1]
123
124
125def main():
126    args = parse_args()
127    build_dir = os.path.dirname(__file__)
128
129    if args.version:
130        version = args.version
131    else:
132        version = read_version(build_dir)
133        if args.version_suffix:
134            version += args.version_suffix
135
136    # Note: Go does not support using absolute package names.
137    # So we run go inside the directory of the the build file.
138    output_file = os.path.abspath(args.output_file)
139    subprocess.check_call(
140        calc_go_args(args, version, build_dir, output_file), cwd=build_dir
141    )
142
143    # b/203821449: we're occasionally seeing very small (and non-functional)
144    # compiler-wrapper binaries on SDK builds. To help narrow down why, add a
145    # size check here. Locally, the wrapper is 1.9MB, so warning on <1MB
146    # shouldn't flag false-positives.
147    size = os.path.getsize(output_file)
148    min_size_bytes = 1024 * 1024
149    if size < min_size_bytes:
150        raise ValueError(
151            f"Compiler wrapper is {size:,} bytes; expected at "
152            f"least {min_size_bytes:,}"
153        )
154
155
156if __name__ == "__main__":
157    main()
158