blob: 9e40fef444ecc4e64b3b31fa83b2567943a6e9ee [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 }
Brian Silverman5f6f2762022-08-13 19:30:05 -070075 match line.split_once('=') {
76 Some((key, value)) => {
Brian Silvermancc09f182022-03-09 15:40:20 -080077 command.env(key, value.replace("${pwd}", &exec_root.to_string_lossy()));
78 }
79 _ => {
80 return Err(
81 "error: Wrong environment file format, should not happen".to_owned()
82 )
83 }
84 }
85 }
86 } else {
87 return Err("error: Dependency environment file unreadable".to_owned());
88 }
89 }
90
Brian Silverman5f6f2762022-08-13 19:30:05 -070091 for tool_env_var in &["CC", "CXX", "LD"] {
92 if let Some(tool_path) = env::var_os(tool_env_var) {
93 command.env(tool_env_var, exec_root.join(tool_path));
Brian Silvermancc09f182022-03-09 15:40:20 -080094 }
95 }
96
97 if let Some(ar_path) = env::var_os("AR") {
98 // The default OSX toolchain uses libtool as ar_executable not ar.
99 // This doesn't work when used as $AR, so simply don't set it - tools will probably fall back to
100 // /usr/bin/ar which is probably good enough.
101 if Path::new(&ar_path).file_name() == Some("libtool".as_ref()) {
102 command.env_remove("AR");
103 } else {
104 command.env("AR", exec_root.join(ar_path));
105 }
106 }
107
Brian Silvermancc09f182022-03-09 15:40:20 -0800108 // replace env vars with a ${pwd} prefix with the exec_root
109 for (key, value) in env::vars() {
110 let exec_root_str = exec_root.to_str().expect("exec_root not in utf8");
111 if value.contains("${pwd}") {
112 env::set_var(key, value.replace("${pwd}", exec_root_str));
113 }
114 }
115
116 // Bazel does not support byte strings so in order to correctly represent `CARGO_ENCODED_RUSTFLAGS`
117 // the escaped `\x1f` sequences need to be unescaped
118 if let Ok(encoded_rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
119 command.env(
120 "CARGO_ENCODED_RUSTFLAGS",
121 encoded_rustflags.replace("\\x1f", "\x1f"),
122 );
123 }
124
125 let (buildrs_outputs, process_output) = BuildScriptOutput::outputs_from_command(&mut command)
126 .map_err(|process_output| {
127 format!(
128 "Build script process failed{}\n--stdout:\n{}\n--stderr:\n{}",
129 if let Some(exit_code) = process_output.status.code() {
130 format!(" with exit code {}", exit_code)
131 } else {
132 String::new()
133 },
134 String::from_utf8(process_output.stdout)
135 .expect("Failed to parse stdout of child process"),
136 String::from_utf8(process_output.stderr)
137 .expect("Failed to parse stdout of child process"),
138 )
139 })?;
140
141 write(
142 &env_file,
143 BuildScriptOutput::outputs_to_env(&buildrs_outputs, &exec_root.to_string_lossy())
144 .as_bytes(),
145 )
146 .unwrap_or_else(|_| panic!("Unable to write file {:?}", env_file));
147 write(
148 &output_dep_env_path,
149 BuildScriptOutput::outputs_to_dep_env(
150 &buildrs_outputs,
151 &crate_links,
152 &exec_root.to_string_lossy(),
153 )
154 .as_bytes(),
155 )
156 .unwrap_or_else(|_| panic!("Unable to write file {:?}", output_dep_env_path));
157 write(&stdout_path, process_output.stdout)
158 .unwrap_or_else(|_| panic!("Unable to write file {:?}", stdout_path));
159 write(&stderr_path, process_output.stderr)
160 .unwrap_or_else(|_| panic!("Unable to write file {:?}", stderr_path));
161
162 let CompileAndLinkFlags {
163 compile_flags,
164 link_flags,
165 link_search_paths,
166 } = BuildScriptOutput::outputs_to_flags(&buildrs_outputs, &exec_root.to_string_lossy());
167
168 write(&compile_flags_file, compile_flags.as_bytes())
169 .unwrap_or_else(|_| panic!("Unable to write file {:?}", compile_flags_file));
170 write(&link_flags_file, link_flags.as_bytes())
171 .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_flags_file));
172 write(&link_search_paths_file, link_search_paths.as_bytes())
173 .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_search_paths_file));
174 Ok(())
175}
176
177/// A representation of expected command line arguments.
178struct Options {
179 progname: String,
180 crate_links: String,
181 out_dir: String,
182 env_file: String,
183 compile_flags_file: String,
184 link_flags_file: String,
185 link_search_paths_file: String,
186 output_dep_env_path: String,
187 stdout_path: String,
188 stderr_path: String,
189 input_dep_env_paths: Vec<String>,
190}
191
192/// Parses positional comamnd line arguments into a well defined struct
193fn parse_args() -> Result<Options, String> {
194 let mut args = env::args().skip(1);
195
196 // TODO: we should consider an alternative to positional arguments.
197 match (args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next()) {
198 (
199 Some(progname),
200 Some(crate_links),
201 Some(out_dir),
202 Some(env_file),
203 Some(compile_flags_file),
204 Some(link_flags_file),
205 Some(link_search_paths_file),
206 Some(output_dep_env_path),
207 Some(stdout_path),
208 Some(stderr_path),
209 ) => {
210 Ok(Options{
211 progname,
212 crate_links,
213 out_dir,
214 env_file,
215 compile_flags_file,
216 link_flags_file,
217 link_search_paths_file,
218 output_dep_env_path,
219 stdout_path,
220 stderr_path,
221 input_dep_env_paths: args.collect(),
222 })
223 }
224 _ => {
225 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>>()))
226 }
227 }
228}
229
230fn get_target_env_vars<P: AsRef<Path>>(rustc: &P) -> Result<BTreeMap<String, String>, String> {
231 // As done by Cargo when constructing a cargo::core::compiler::build_context::target_info::TargetInfo.
232 let output = Command::new(rustc.as_ref())
233 .arg("--print=cfg")
234 .arg(format!(
235 "--target={}",
236 env::var("TARGET").expect("missing TARGET")
237 ))
238 .output()
239 .map_err(|err| format!("Error running rustc to get target information: {}", err))?;
240 if !output.status.success() {
241 return Err(format!(
242 "Error running rustc to get target information: {:?}",
243 output
244 ));
245 }
246 let stdout = std::str::from_utf8(&output.stdout)
247 .map_err(|err| format!("Non-UTF8 stdout from rustc: {:?}", err))?;
248
249 Ok(parse_rustc_cfg_output(stdout))
250}
251
252fn parse_rustc_cfg_output(stdout: &str) -> BTreeMap<String, String> {
253 let mut values = BTreeMap::new();
254
255 for line in stdout.lines() {
256 if line.starts_with("target_") && line.contains('=') {
Brian Silvermancc09f182022-03-09 15:40:20 -0800257 // UNWRAP: Verified that line contains = and split into exactly 2 parts.
Brian Silverman5f6f2762022-08-13 19:30:05 -0700258 let (key, value) = line.split_once('=').unwrap();
Brian Silvermancc09f182022-03-09 15:40:20 -0800259 if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
260 values
261 .entry(key)
262 .or_insert_with(Vec::new)
263 .push(value[1..(value.len() - 1)].to_owned());
264 }
265 } else if ["windows", "unix"].contains(&line) {
266 // the 'windows' or 'unix' line received from rustc will be turned
267 // into eg. CARGO_CFG_WINDOWS='' below
268 values.insert(line, vec![]);
269 }
270 }
271
272 values
273 .into_iter()
274 .map(|(key, value)| (format!("CARGO_CFG_{}", key.to_uppercase()), value.join(",")))
275 .collect()
276}
277
278fn main() {
279 std::process::exit(match run_buildrs() {
280 Ok(_) => 0,
281 Err(err) => {
282 // Neatly print errors
283 eprintln!("{}", err);
284 1
285 }
286 });
287}
288
289#[cfg(test)]
290mod test {
291 use super::*;
292
293 #[test]
294 fn rustc_cfg_parsing() {
295 let macos_output = r#"\
296debug_assertions
297target_arch="x86_64"
298target_endian="little"
299target_env=""
300target_family="unix"
301target_feature="fxsr"
302target_feature="sse"
303target_feature="sse2"
304target_feature="sse3"
305target_feature="ssse3"
306target_os="macos"
307target_pointer_width="64"
308target_vendor="apple"
309unix
310"#;
311 let tree = parse_rustc_cfg_output(macos_output);
312 assert_eq!(tree["CARGO_CFG_UNIX"], "");
313 assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "unix");
314
315 let windows_output = r#"\
316debug_assertions
317target_arch="x86_64"
318target_endian="little"
319target_env="msvc"
320target_family="windows"
321target_feature="fxsr"
322target_feature="sse"
323target_feature="sse2"
324target_os="windows"
325target_pointer_width="64"
326target_vendor="pc"
327windows
328"#;
329 let tree = parse_rustc_cfg_output(windows_output);
330 assert_eq!(tree["CARGO_CFG_WINDOWS"], "");
331 assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "windows");
332 }
333}