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