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"); |
| 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 Silverman | 5f6f276 | 2022-08-13 19:30:05 -0700 | [diff] [blame^] | 75 | match line.split_once('=') { |
| 76 | Some((key, value)) => { |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 77 | 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 Silverman | 5f6f276 | 2022-08-13 19:30:05 -0700 | [diff] [blame^] | 91 | 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 94 | } |
| 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 108 | // 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. |
| 178 | struct 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 |
| 193 | fn 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 | |
| 230 | fn 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 | |
| 252 | fn 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 Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 257 | // UNWRAP: Verified that line contains = and split into exactly 2 parts. |
Brian Silverman | 5f6f276 | 2022-08-13 19:30:05 -0700 | [diff] [blame^] | 258 | let (key, value) = line.split_once('=').unwrap(); |
Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame] | 259 | 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 | |
| 278 | fn 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)] |
| 290 | mod test { |
| 291 | use super::*; |
| 292 | |
| 293 | #[test] |
| 294 | fn rustc_cfg_parsing() { |
| 295 | let macos_output = r#"\ |
| 296 | debug_assertions |
| 297 | target_arch="x86_64" |
| 298 | target_endian="little" |
| 299 | target_env="" |
| 300 | target_family="unix" |
| 301 | target_feature="fxsr" |
| 302 | target_feature="sse" |
| 303 | target_feature="sse2" |
| 304 | target_feature="sse3" |
| 305 | target_feature="ssse3" |
| 306 | target_os="macos" |
| 307 | target_pointer_width="64" |
| 308 | target_vendor="apple" |
| 309 | unix |
| 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#"\ |
| 316 | debug_assertions |
| 317 | target_arch="x86_64" |
| 318 | target_endian="little" |
| 319 | target_env="msvc" |
| 320 | target_family="windows" |
| 321 | target_feature="fxsr" |
| 322 | target_feature="sse" |
| 323 | target_feature="sse2" |
| 324 | target_os="windows" |
| 325 | target_pointer_width="64" |
| 326 | target_vendor="pc" |
| 327 | windows |
| 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 | } |