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