Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 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; |
| 24 | use std::process::Command; |
| 25 | |
| 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"); |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 35 | let manifest_dir = exec_root.join(manifest_dir_env); |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 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 | |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 51 | let out_dir_abs = exec_root.join(out_dir); |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 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 | |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 56 | 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 77 | let target_env_vars = |
| 78 | get_target_env_vars(&rustc_env).expect("Error getting target env vars from rustc"); |
| 79 | |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 80 | let mut command = Command::new(exec_root.join(progname)); |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 81 | 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 Silverman | 5f6f276 | 2022-08-13 19:30:05 -0700 | [diff] [blame] | 96 | match line.split_once('=') { |
| 97 | Some((key, value)) => { |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 98 | 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 Silverman | 5f6f276 | 2022-08-13 19:30:05 -0700 | [diff] [blame] | 112 | 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 115 | } |
| 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 129 | // 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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 151 | format!(" with exit code {exit_code}") |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 152 | } 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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 198 | fn 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)] |
| 206 | fn 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))] |
| 220 | fn 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 | |
| 226 | fn 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 234 | /// A representation of expected command line arguments. |
| 235 | struct 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 |
| 250 | fn 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 | |
| 287 | fn 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 Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 296 | .map_err(|err| format!("Error running rustc to get target information: {err}"))?; |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 297 | if !output.status.success() { |
| 298 | return Err(format!( |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 299 | "Error running rustc to get target information: {output:?}", |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 300 | )); |
| 301 | } |
| 302 | let stdout = std::str::from_utf8(&output.stdout) |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 303 | .map_err(|err| format!("Non-UTF8 stdout from rustc: {err:?}"))?; |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 304 | |
| 305 | Ok(parse_rustc_cfg_output(stdout)) |
| 306 | } |
| 307 | |
| 308 | fn 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 313 | // UNWRAP: Verified that line contains = and split into exactly 2 parts. |
Brian Silverman | 5f6f276 | 2022-08-13 19:30:05 -0700 | [diff] [blame] | 314 | let (key, value) = line.split_once('=').unwrap(); |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 315 | 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 | |
| 334 | fn main() { |
| 335 | std::process::exit(match run_buildrs() { |
| 336 | Ok(_) => 0, |
| 337 | Err(err) => { |
| 338 | // Neatly print errors |
Adam Snaider | 1c095c9 | 2023-07-08 02:09:58 -0400 | [diff] [blame^] | 339 | eprintln!("{err}"); |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 340 | 1 |
| 341 | } |
| 342 | }); |
| 343 | } |
| 344 | |
| 345 | #[cfg(test)] |
| 346 | mod test { |
| 347 | use super::*; |
| 348 | |
| 349 | #[test] |
| 350 | fn rustc_cfg_parsing() { |
| 351 | let macos_output = r#"\ |
| 352 | debug_assertions |
| 353 | target_arch="x86_64" |
| 354 | target_endian="little" |
| 355 | target_env="" |
| 356 | target_family="unix" |
| 357 | target_feature="fxsr" |
| 358 | target_feature="sse" |
| 359 | target_feature="sse2" |
| 360 | target_feature="sse3" |
| 361 | target_feature="ssse3" |
| 362 | target_os="macos" |
| 363 | target_pointer_width="64" |
| 364 | target_vendor="apple" |
| 365 | unix |
| 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#"\ |
| 372 | debug_assertions |
| 373 | target_arch="x86_64" |
| 374 | target_endian="little" |
| 375 | target_env="msvc" |
| 376 | target_family="windows" |
| 377 | target_feature="fxsr" |
| 378 | target_feature="sse" |
| 379 | target_feature="sse2" |
| 380 | target_os="windows" |
| 381 | target_pointer_width="64" |
| 382 | target_vendor="pc" |
| 383 | windows |
| 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 | } |