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