blob: b7b78c2ceeeacec1617fbae5f71493df8b4c5942 [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");
Adam Snaider1c095c92023-07-08 02:09:58 -040035 let manifest_dir = exec_root.join(manifest_dir_env);
Brian Silvermancc09f182022-03-09 15:40:20 -080036 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
Adam Snaider1c095c92023-07-08 02:09:58 -040051 let out_dir_abs = exec_root.join(out_dir);
Brian Silvermancc09f182022-03-09 15:40:20 -080052 // 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
Adam Snaider1c095c92023-07-08 02:09:58 -040056 if should_symlink_exec_root() {
57 // Symlink the execroot to the manifest_dir so that we can use relative paths in the arguments.
58 let exec_root_paths = std::fs::read_dir(&exec_root)
59 .map_err(|err| format!("Failed while listing exec root: {err:?}"))?;
60 for path in exec_root_paths {
61 let path = path
62 .map_err(|err| {
63 format!("Failed while getting path from exec root listing: {err:?}")
64 })?
65 .path();
66
67 let file_name = path
68 .file_name()
69 .ok_or_else(|| "Failed while getting file name".to_string())?;
70 let link = manifest_dir.join(file_name);
71
72 symlink_if_not_exists(&path, &link)
73 .map_err(|err| format!("Failed to symlink {path:?} to {link:?}: {err}"))?;
74 }
75 }
76
Brian Silvermancc09f182022-03-09 15:40:20 -080077 let target_env_vars =
78 get_target_env_vars(&rustc_env).expect("Error getting target env vars from rustc");
79
Adam Snaider1c095c92023-07-08 02:09:58 -040080 let mut command = Command::new(exec_root.join(progname));
Brian Silvermancc09f182022-03-09 15:40:20 -080081 command
82 .current_dir(&manifest_dir)
83 .envs(target_env_vars)
84 .env("OUT_DIR", out_dir_abs)
85 .env("CARGO_MANIFEST_DIR", manifest_dir)
86 .env("RUSTC", rustc)
87 .env("RUST_BACKTRACE", "full");
88
89 for dep_env_path in input_dep_env_paths.iter() {
90 if let Ok(contents) = read_to_string(dep_env_path) {
91 for line in contents.split('\n') {
92 // split on empty contents will still produce a single empty string in iterable.
93 if line.is_empty() {
94 continue;
95 }
Brian Silverman5f6f2762022-08-13 19:30:05 -070096 match line.split_once('=') {
97 Some((key, value)) => {
Brian Silvermancc09f182022-03-09 15:40:20 -080098 command.env(key, value.replace("${pwd}", &exec_root.to_string_lossy()));
99 }
100 _ => {
101 return Err(
102 "error: Wrong environment file format, should not happen".to_owned()
103 )
104 }
105 }
106 }
107 } else {
108 return Err("error: Dependency environment file unreadable".to_owned());
109 }
110 }
111
Brian Silverman5f6f2762022-08-13 19:30:05 -0700112 for tool_env_var in &["CC", "CXX", "LD"] {
113 if let Some(tool_path) = env::var_os(tool_env_var) {
114 command.env(tool_env_var, exec_root.join(tool_path));
Brian Silvermancc09f182022-03-09 15:40:20 -0800115 }
116 }
117
118 if let Some(ar_path) = env::var_os("AR") {
119 // The default OSX toolchain uses libtool as ar_executable not ar.
120 // This doesn't work when used as $AR, so simply don't set it - tools will probably fall back to
121 // /usr/bin/ar which is probably good enough.
122 if Path::new(&ar_path).file_name() == Some("libtool".as_ref()) {
123 command.env_remove("AR");
124 } else {
125 command.env("AR", exec_root.join(ar_path));
126 }
127 }
128
Brian Silvermancc09f182022-03-09 15:40:20 -0800129 // replace env vars with a ${pwd} prefix with the exec_root
130 for (key, value) in env::vars() {
131 let exec_root_str = exec_root.to_str().expect("exec_root not in utf8");
132 if value.contains("${pwd}") {
133 env::set_var(key, value.replace("${pwd}", exec_root_str));
134 }
135 }
136
137 // Bazel does not support byte strings so in order to correctly represent `CARGO_ENCODED_RUSTFLAGS`
138 // the escaped `\x1f` sequences need to be unescaped
139 if let Ok(encoded_rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
140 command.env(
141 "CARGO_ENCODED_RUSTFLAGS",
142 encoded_rustflags.replace("\\x1f", "\x1f"),
143 );
144 }
145
146 let (buildrs_outputs, process_output) = BuildScriptOutput::outputs_from_command(&mut command)
147 .map_err(|process_output| {
148 format!(
149 "Build script process failed{}\n--stdout:\n{}\n--stderr:\n{}",
150 if let Some(exit_code) = process_output.status.code() {
Adam Snaider1c095c92023-07-08 02:09:58 -0400151 format!(" with exit code {exit_code}")
Brian Silvermancc09f182022-03-09 15:40:20 -0800152 } else {
153 String::new()
154 },
155 String::from_utf8(process_output.stdout)
156 .expect("Failed to parse stdout of child process"),
157 String::from_utf8(process_output.stderr)
158 .expect("Failed to parse stdout of child process"),
159 )
160 })?;
161
162 write(
163 &env_file,
164 BuildScriptOutput::outputs_to_env(&buildrs_outputs, &exec_root.to_string_lossy())
165 .as_bytes(),
166 )
167 .unwrap_or_else(|_| panic!("Unable to write file {:?}", env_file));
168 write(
169 &output_dep_env_path,
170 BuildScriptOutput::outputs_to_dep_env(
171 &buildrs_outputs,
172 &crate_links,
173 &exec_root.to_string_lossy(),
174 )
175 .as_bytes(),
176 )
177 .unwrap_or_else(|_| panic!("Unable to write file {:?}", output_dep_env_path));
178 write(&stdout_path, process_output.stdout)
179 .unwrap_or_else(|_| panic!("Unable to write file {:?}", stdout_path));
180 write(&stderr_path, process_output.stderr)
181 .unwrap_or_else(|_| panic!("Unable to write file {:?}", stderr_path));
182
183 let CompileAndLinkFlags {
184 compile_flags,
185 link_flags,
186 link_search_paths,
187 } = BuildScriptOutput::outputs_to_flags(&buildrs_outputs, &exec_root.to_string_lossy());
188
189 write(&compile_flags_file, compile_flags.as_bytes())
190 .unwrap_or_else(|_| panic!("Unable to write file {:?}", compile_flags_file));
191 write(&link_flags_file, link_flags.as_bytes())
192 .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_flags_file));
193 write(&link_search_paths_file, link_search_paths.as_bytes())
194 .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_search_paths_file));
195 Ok(())
196}
197
Adam Snaider1c095c92023-07-08 02:09:58 -0400198fn should_symlink_exec_root() -> bool {
199 env::var("RULES_RUST_SYMLINK_EXEC_ROOT")
200 .map(|s| s == "1")
201 .unwrap_or(false)
202}
203
204/// Create a symlink from `link` to `original` if `link` doesn't already exist.
205#[cfg(windows)]
206fn symlink_if_not_exists(original: &Path, link: &Path) -> Result<(), String> {
207 if original.is_dir() {
208 std::os::windows::fs::symlink_dir(original, link)
209 .or_else(swallow_already_exists)
210 .map_err(|err| format!("Failed to create directory symlink: {err}"))
211 } else {
212 std::os::windows::fs::symlink_file(original, link)
213 .or_else(swallow_already_exists)
214 .map_err(|err| format!("Failed to create file symlink: {err}"))
215 }
216}
217
218/// Create a symlink from `link` to `original` if `link` doesn't already exist.
219#[cfg(not(windows))]
220fn symlink_if_not_exists(original: &Path, link: &Path) -> Result<(), String> {
221 std::os::unix::fs::symlink(original, link)
222 .or_else(swallow_already_exists)
223 .map_err(|err| format!("Failed to create symlink: {err}"))
224}
225
226fn swallow_already_exists(err: std::io::Error) -> std::io::Result<()> {
227 if err.kind() == std::io::ErrorKind::AlreadyExists {
228 Ok(())
229 } else {
230 Err(err)
231 }
232}
233
Brian Silvermancc09f182022-03-09 15:40:20 -0800234/// A representation of expected command line arguments.
235struct Options {
236 progname: String,
237 crate_links: String,
238 out_dir: String,
239 env_file: String,
240 compile_flags_file: String,
241 link_flags_file: String,
242 link_search_paths_file: String,
243 output_dep_env_path: String,
244 stdout_path: String,
245 stderr_path: String,
246 input_dep_env_paths: Vec<String>,
247}
248
249/// Parses positional comamnd line arguments into a well defined struct
250fn parse_args() -> Result<Options, String> {
251 let mut args = env::args().skip(1);
252
253 // TODO: we should consider an alternative to positional arguments.
254 match (args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next()) {
255 (
256 Some(progname),
257 Some(crate_links),
258 Some(out_dir),
259 Some(env_file),
260 Some(compile_flags_file),
261 Some(link_flags_file),
262 Some(link_search_paths_file),
263 Some(output_dep_env_path),
264 Some(stdout_path),
265 Some(stderr_path),
266 ) => {
267 Ok(Options{
268 progname,
269 crate_links,
270 out_dir,
271 env_file,
272 compile_flags_file,
273 link_flags_file,
274 link_search_paths_file,
275 output_dep_env_path,
276 stdout_path,
277 stderr_path,
278 input_dep_env_paths: args.collect(),
279 })
280 }
281 _ => {
282 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>>()))
283 }
284 }
285}
286
287fn get_target_env_vars<P: AsRef<Path>>(rustc: &P) -> Result<BTreeMap<String, String>, String> {
288 // As done by Cargo when constructing a cargo::core::compiler::build_context::target_info::TargetInfo.
289 let output = Command::new(rustc.as_ref())
290 .arg("--print=cfg")
291 .arg(format!(
292 "--target={}",
293 env::var("TARGET").expect("missing TARGET")
294 ))
295 .output()
Adam Snaider1c095c92023-07-08 02:09:58 -0400296 .map_err(|err| format!("Error running rustc to get target information: {err}"))?;
Brian Silvermancc09f182022-03-09 15:40:20 -0800297 if !output.status.success() {
298 return Err(format!(
Adam Snaider1c095c92023-07-08 02:09:58 -0400299 "Error running rustc to get target information: {output:?}",
Brian Silvermancc09f182022-03-09 15:40:20 -0800300 ));
301 }
302 let stdout = std::str::from_utf8(&output.stdout)
Adam Snaider1c095c92023-07-08 02:09:58 -0400303 .map_err(|err| format!("Non-UTF8 stdout from rustc: {err:?}"))?;
Brian Silvermancc09f182022-03-09 15:40:20 -0800304
305 Ok(parse_rustc_cfg_output(stdout))
306}
307
308fn parse_rustc_cfg_output(stdout: &str) -> BTreeMap<String, String> {
309 let mut values = BTreeMap::new();
310
311 for line in stdout.lines() {
312 if line.starts_with("target_") && line.contains('=') {
Brian Silvermancc09f182022-03-09 15:40:20 -0800313 // UNWRAP: Verified that line contains = and split into exactly 2 parts.
Brian Silverman5f6f2762022-08-13 19:30:05 -0700314 let (key, value) = line.split_once('=').unwrap();
Brian Silvermancc09f182022-03-09 15:40:20 -0800315 if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
316 values
317 .entry(key)
318 .or_insert_with(Vec::new)
319 .push(value[1..(value.len() - 1)].to_owned());
320 }
321 } else if ["windows", "unix"].contains(&line) {
322 // the 'windows' or 'unix' line received from rustc will be turned
323 // into eg. CARGO_CFG_WINDOWS='' below
324 values.insert(line, vec![]);
325 }
326 }
327
328 values
329 .into_iter()
330 .map(|(key, value)| (format!("CARGO_CFG_{}", key.to_uppercase()), value.join(",")))
331 .collect()
332}
333
334fn main() {
335 std::process::exit(match run_buildrs() {
336 Ok(_) => 0,
337 Err(err) => {
338 // Neatly print errors
Adam Snaider1c095c92023-07-08 02:09:58 -0400339 eprintln!("{err}");
Brian Silvermancc09f182022-03-09 15:40:20 -0800340 1
341 }
342 });
343}
344
345#[cfg(test)]
346mod test {
347 use super::*;
348
349 #[test]
350 fn rustc_cfg_parsing() {
351 let macos_output = r#"\
352debug_assertions
353target_arch="x86_64"
354target_endian="little"
355target_env=""
356target_family="unix"
357target_feature="fxsr"
358target_feature="sse"
359target_feature="sse2"
360target_feature="sse3"
361target_feature="ssse3"
362target_os="macos"
363target_pointer_width="64"
364target_vendor="apple"
365unix
366"#;
367 let tree = parse_rustc_cfg_output(macos_output);
368 assert_eq!(tree["CARGO_CFG_UNIX"], "");
369 assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "unix");
370
371 let windows_output = r#"\
372debug_assertions
373target_arch="x86_64"
374target_endian="little"
375target_env="msvc"
376target_family="windows"
377target_feature="fxsr"
378target_feature="sse"
379target_feature="sse2"
380target_os="windows"
381target_pointer_width="64"
382target_vendor="pc"
383windows
384"#;
385 let tree = parse_rustc_cfg_output(windows_output);
386 assert_eq!(tree["CARGO_CFG_WINDOWS"], "");
387 assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "windows");
388 }
389}