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