blob: 58f03638311672049c1e7bcd50ff8c61707b1a79 [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001// Copyright 2018 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// A simple wrapper around a build_script execution to generate file to reuse
16// by rust_library/rust_binary.
17extern crate cargo_build_script_output_parser;
18
19use cargo_build_script_output_parser::{BuildScriptOutput, CompileAndLinkFlags};
20use std::collections::BTreeMap;
21use std::env;
22use std::fs::{create_dir_all, read_to_string, write};
23use std::path::Path;
24use std::process::Command;
25
26fn run_buildrs() -> Result<(), String> {
27 // We use exec_root.join rather than std::fs::canonicalize, to avoid resolving symlinks, as
28 // some execution strategies and remote execution environments may use symlinks in ways which
29 // canonicalizing them may break them, e.g. by having input files be symlinks into a /cas
30 // directory - resolving these may cause tools which inspect $0, or try to resolve files
31 // relative to themselves, to fail.
32 let exec_root = env::current_dir().expect("Failed to get current directory");
33 let manifest_dir_env = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR was not set");
34 let rustc_env = env::var("RUSTC").expect("RUSTC was not set");
35 let manifest_dir = exec_root.join(&manifest_dir_env);
36 let rustc = exec_root.join(&rustc_env);
37 let Options {
38 progname,
39 crate_links,
40 out_dir,
41 env_file,
42 compile_flags_file,
43 link_flags_file,
44 link_search_paths_file,
45 output_dep_env_path,
46 stdout_path,
47 stderr_path,
48 input_dep_env_paths,
49 } = parse_args()?;
50
51 let out_dir_abs = exec_root.join(&out_dir);
52 // For some reason Google's RBE does not create the output directory, force create it.
53 create_dir_all(&out_dir_abs)
54 .unwrap_or_else(|_| panic!("Failed to make output directory: {:?}", out_dir_abs));
55
56 let target_env_vars =
57 get_target_env_vars(&rustc_env).expect("Error getting target env vars from rustc");
58
59 let mut command = Command::new(exec_root.join(&progname));
60 command
61 .current_dir(&manifest_dir)
62 .envs(target_env_vars)
63 .env("OUT_DIR", out_dir_abs)
64 .env("CARGO_MANIFEST_DIR", manifest_dir)
65 .env("RUSTC", rustc)
66 .env("RUST_BACKTRACE", "full");
67
68 for dep_env_path in input_dep_env_paths.iter() {
69 if let Ok(contents) = read_to_string(dep_env_path) {
70 for line in contents.split('\n') {
71 // split on empty contents will still produce a single empty string in iterable.
72 if line.is_empty() {
73 continue;
74 }
75 let mut key_val = line.splitn(2, '=');
76 match (key_val.next(), key_val.next()) {
77 (Some(key), Some(value)) => {
78 command.env(key, value.replace("${pwd}", &exec_root.to_string_lossy()));
79 }
80 _ => {
81 return Err(
82 "error: Wrong environment file format, should not happen".to_owned()
83 )
84 }
85 }
86 }
87 } else {
88 return Err("error: Dependency environment file unreadable".to_owned());
89 }
90 }
91
92 for compiler_env_var in &["CC", "CXX"] {
93 if let Some(compiler_path) = env::var_os(compiler_env_var) {
94 let mut compiler_path = exec_root.join(compiler_path).into_os_string();
95 if let Some(sysroot_path) = env::var_os("SYSROOT") {
96 compiler_path.push(" --sysroot=");
97 compiler_path.push(&exec_root.join(sysroot_path));
98 }
99 command.env(compiler_env_var, compiler_path);
100 }
101 }
102
103 if let Some(ar_path) = env::var_os("AR") {
104 // The default OSX toolchain uses libtool as ar_executable not ar.
105 // This doesn't work when used as $AR, so simply don't set it - tools will probably fall back to
106 // /usr/bin/ar which is probably good enough.
107 if Path::new(&ar_path).file_name() == Some("libtool".as_ref()) {
108 command.env_remove("AR");
109 } else {
110 command.env("AR", exec_root.join(ar_path));
111 }
112 }
113
114 if let Some(ld_path) = env::var_os("LD") {
115 command.env("LD", exec_root.join(ld_path));
116 }
117
118 // replace env vars with a ${pwd} prefix with the exec_root
119 for (key, value) in env::vars() {
120 let exec_root_str = exec_root.to_str().expect("exec_root not in utf8");
121 if value.contains("${pwd}") {
122 env::set_var(key, value.replace("${pwd}", exec_root_str));
123 }
124 }
125
126 // Bazel does not support byte strings so in order to correctly represent `CARGO_ENCODED_RUSTFLAGS`
127 // the escaped `\x1f` sequences need to be unescaped
128 if let Ok(encoded_rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
129 command.env(
130 "CARGO_ENCODED_RUSTFLAGS",
131 encoded_rustflags.replace("\\x1f", "\x1f"),
132 );
133 }
134
135 let (buildrs_outputs, process_output) = BuildScriptOutput::outputs_from_command(&mut command)
136 .map_err(|process_output| {
137 format!(
138 "Build script process failed{}\n--stdout:\n{}\n--stderr:\n{}",
139 if let Some(exit_code) = process_output.status.code() {
140 format!(" with exit code {}", exit_code)
141 } else {
142 String::new()
143 },
144 String::from_utf8(process_output.stdout)
145 .expect("Failed to parse stdout of child process"),
146 String::from_utf8(process_output.stderr)
147 .expect("Failed to parse stdout of child process"),
148 )
149 })?;
150
151 write(
152 &env_file,
153 BuildScriptOutput::outputs_to_env(&buildrs_outputs, &exec_root.to_string_lossy())
154 .as_bytes(),
155 )
156 .unwrap_or_else(|_| panic!("Unable to write file {:?}", env_file));
157 write(
158 &output_dep_env_path,
159 BuildScriptOutput::outputs_to_dep_env(
160 &buildrs_outputs,
161 &crate_links,
162 &exec_root.to_string_lossy(),
163 )
164 .as_bytes(),
165 )
166 .unwrap_or_else(|_| panic!("Unable to write file {:?}", output_dep_env_path));
167 write(&stdout_path, process_output.stdout)
168 .unwrap_or_else(|_| panic!("Unable to write file {:?}", stdout_path));
169 write(&stderr_path, process_output.stderr)
170 .unwrap_or_else(|_| panic!("Unable to write file {:?}", stderr_path));
171
172 let CompileAndLinkFlags {
173 compile_flags,
174 link_flags,
175 link_search_paths,
176 } = BuildScriptOutput::outputs_to_flags(&buildrs_outputs, &exec_root.to_string_lossy());
177
178 write(&compile_flags_file, compile_flags.as_bytes())
179 .unwrap_or_else(|_| panic!("Unable to write file {:?}", compile_flags_file));
180 write(&link_flags_file, link_flags.as_bytes())
181 .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_flags_file));
182 write(&link_search_paths_file, link_search_paths.as_bytes())
183 .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_search_paths_file));
184 Ok(())
185}
186
187/// A representation of expected command line arguments.
188struct Options {
189 progname: String,
190 crate_links: String,
191 out_dir: String,
192 env_file: String,
193 compile_flags_file: String,
194 link_flags_file: String,
195 link_search_paths_file: String,
196 output_dep_env_path: String,
197 stdout_path: String,
198 stderr_path: String,
199 input_dep_env_paths: Vec<String>,
200}
201
202/// Parses positional comamnd line arguments into a well defined struct
203fn parse_args() -> Result<Options, String> {
204 let mut args = env::args().skip(1);
205
206 // TODO: we should consider an alternative to positional arguments.
207 match (args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next()) {
208 (
209 Some(progname),
210 Some(crate_links),
211 Some(out_dir),
212 Some(env_file),
213 Some(compile_flags_file),
214 Some(link_flags_file),
215 Some(link_search_paths_file),
216 Some(output_dep_env_path),
217 Some(stdout_path),
218 Some(stderr_path),
219 ) => {
220 Ok(Options{
221 progname,
222 crate_links,
223 out_dir,
224 env_file,
225 compile_flags_file,
226 link_flags_file,
227 link_search_paths_file,
228 output_dep_env_path,
229 stdout_path,
230 stderr_path,
231 input_dep_env_paths: args.collect(),
232 })
233 }
234 _ => {
235 Err(format!("Usage: $0 progname crate_links out_dir env_file compile_flags_file link_flags_file link_search_paths_file output_dep_env_path stdout_path stderr_path input_dep_env_paths[arg1...argn]\nArguments passed: {:?}", args.collect::<Vec<String>>()))
236 }
237 }
238}
239
240fn get_target_env_vars<P: AsRef<Path>>(rustc: &P) -> Result<BTreeMap<String, String>, String> {
241 // As done by Cargo when constructing a cargo::core::compiler::build_context::target_info::TargetInfo.
242 let output = Command::new(rustc.as_ref())
243 .arg("--print=cfg")
244 .arg(format!(
245 "--target={}",
246 env::var("TARGET").expect("missing TARGET")
247 ))
248 .output()
249 .map_err(|err| format!("Error running rustc to get target information: {}", err))?;
250 if !output.status.success() {
251 return Err(format!(
252 "Error running rustc to get target information: {:?}",
253 output
254 ));
255 }
256 let stdout = std::str::from_utf8(&output.stdout)
257 .map_err(|err| format!("Non-UTF8 stdout from rustc: {:?}", err))?;
258
259 Ok(parse_rustc_cfg_output(stdout))
260}
261
262fn parse_rustc_cfg_output(stdout: &str) -> BTreeMap<String, String> {
263 let mut values = BTreeMap::new();
264
265 for line in stdout.lines() {
266 if line.starts_with("target_") && line.contains('=') {
267 let mut parts = line.splitn(2, '=');
268 // UNWRAP: Verified that line contains = and split into exactly 2 parts.
269 let key = parts.next().unwrap();
270 let value = parts.next().unwrap();
271 if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
272 values
273 .entry(key)
274 .or_insert_with(Vec::new)
275 .push(value[1..(value.len() - 1)].to_owned());
276 }
277 } else if ["windows", "unix"].contains(&line) {
278 // the 'windows' or 'unix' line received from rustc will be turned
279 // into eg. CARGO_CFG_WINDOWS='' below
280 values.insert(line, vec![]);
281 }
282 }
283
284 values
285 .into_iter()
286 .map(|(key, value)| (format!("CARGO_CFG_{}", key.to_uppercase()), value.join(",")))
287 .collect()
288}
289
290fn main() {
291 std::process::exit(match run_buildrs() {
292 Ok(_) => 0,
293 Err(err) => {
294 // Neatly print errors
295 eprintln!("{}", err);
296 1
297 }
298 });
299}
300
301#[cfg(test)]
302mod test {
303 use super::*;
304
305 #[test]
306 fn rustc_cfg_parsing() {
307 let macos_output = r#"\
308debug_assertions
309target_arch="x86_64"
310target_endian="little"
311target_env=""
312target_family="unix"
313target_feature="fxsr"
314target_feature="sse"
315target_feature="sse2"
316target_feature="sse3"
317target_feature="ssse3"
318target_os="macos"
319target_pointer_width="64"
320target_vendor="apple"
321unix
322"#;
323 let tree = parse_rustc_cfg_output(macos_output);
324 assert_eq!(tree["CARGO_CFG_UNIX"], "");
325 assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "unix");
326
327 let windows_output = r#"\
328debug_assertions
329target_arch="x86_64"
330target_endian="little"
331target_env="msvc"
332target_family="windows"
333target_feature="fxsr"
334target_feature="sse"
335target_feature="sse2"
336target_os="windows"
337target_pointer_width="64"
338target_vendor="pc"
339windows
340"#;
341 let tree = parse_rustc_cfg_output(windows_output);
342 assert_eq!(tree["CARGO_CFG_WINDOWS"], "");
343 assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "windows");
344 }
345}