Squashed 'third_party/rules_rust/' content from commit bf59038ca
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/cargo/cargo_build_script_runner/bin.rs b/cargo/cargo_build_script_runner/bin.rs
new file mode 100644
index 0000000..58f0363
--- /dev/null
+++ b/cargo/cargo_build_script_runner/bin.rs
@@ -0,0 +1,345 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// A simple wrapper around a build_script execution to generate file to reuse
+// by rust_library/rust_binary.
+extern crate cargo_build_script_output_parser;
+
+use cargo_build_script_output_parser::{BuildScriptOutput, CompileAndLinkFlags};
+use std::collections::BTreeMap;
+use std::env;
+use std::fs::{create_dir_all, read_to_string, write};
+use std::path::Path;
+use std::process::Command;
+
+fn run_buildrs() -> Result<(), String> {
+ // We use exec_root.join rather than std::fs::canonicalize, to avoid resolving symlinks, as
+ // some execution strategies and remote execution environments may use symlinks in ways which
+ // canonicalizing them may break them, e.g. by having input files be symlinks into a /cas
+ // directory - resolving these may cause tools which inspect $0, or try to resolve files
+ // relative to themselves, to fail.
+ let exec_root = env::current_dir().expect("Failed to get current directory");
+ let manifest_dir_env = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR was not set");
+ let rustc_env = env::var("RUSTC").expect("RUSTC was not set");
+ let manifest_dir = exec_root.join(&manifest_dir_env);
+ let rustc = exec_root.join(&rustc_env);
+ let Options {
+ 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,
+ } = parse_args()?;
+
+ let out_dir_abs = exec_root.join(&out_dir);
+ // For some reason Google's RBE does not create the output directory, force create it.
+ create_dir_all(&out_dir_abs)
+ .unwrap_or_else(|_| panic!("Failed to make output directory: {:?}", out_dir_abs));
+
+ let target_env_vars =
+ get_target_env_vars(&rustc_env).expect("Error getting target env vars from rustc");
+
+ let mut command = Command::new(exec_root.join(&progname));
+ command
+ .current_dir(&manifest_dir)
+ .envs(target_env_vars)
+ .env("OUT_DIR", out_dir_abs)
+ .env("CARGO_MANIFEST_DIR", manifest_dir)
+ .env("RUSTC", rustc)
+ .env("RUST_BACKTRACE", "full");
+
+ for dep_env_path in input_dep_env_paths.iter() {
+ if let Ok(contents) = read_to_string(dep_env_path) {
+ for line in contents.split('\n') {
+ // split on empty contents will still produce a single empty string in iterable.
+ if line.is_empty() {
+ continue;
+ }
+ let mut key_val = line.splitn(2, '=');
+ match (key_val.next(), key_val.next()) {
+ (Some(key), Some(value)) => {
+ command.env(key, value.replace("${pwd}", &exec_root.to_string_lossy()));
+ }
+ _ => {
+ return Err(
+ "error: Wrong environment file format, should not happen".to_owned()
+ )
+ }
+ }
+ }
+ } else {
+ return Err("error: Dependency environment file unreadable".to_owned());
+ }
+ }
+
+ for compiler_env_var in &["CC", "CXX"] {
+ if let Some(compiler_path) = env::var_os(compiler_env_var) {
+ let mut compiler_path = exec_root.join(compiler_path).into_os_string();
+ if let Some(sysroot_path) = env::var_os("SYSROOT") {
+ compiler_path.push(" --sysroot=");
+ compiler_path.push(&exec_root.join(sysroot_path));
+ }
+ command.env(compiler_env_var, compiler_path);
+ }
+ }
+
+ if let Some(ar_path) = env::var_os("AR") {
+ // The default OSX toolchain uses libtool as ar_executable not ar.
+ // This doesn't work when used as $AR, so simply don't set it - tools will probably fall back to
+ // /usr/bin/ar which is probably good enough.
+ if Path::new(&ar_path).file_name() == Some("libtool".as_ref()) {
+ command.env_remove("AR");
+ } else {
+ command.env("AR", exec_root.join(ar_path));
+ }
+ }
+
+ if let Some(ld_path) = env::var_os("LD") {
+ command.env("LD", exec_root.join(ld_path));
+ }
+
+ // replace env vars with a ${pwd} prefix with the exec_root
+ for (key, value) in env::vars() {
+ let exec_root_str = exec_root.to_str().expect("exec_root not in utf8");
+ if value.contains("${pwd}") {
+ env::set_var(key, value.replace("${pwd}", exec_root_str));
+ }
+ }
+
+ // Bazel does not support byte strings so in order to correctly represent `CARGO_ENCODED_RUSTFLAGS`
+ // the escaped `\x1f` sequences need to be unescaped
+ if let Ok(encoded_rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
+ command.env(
+ "CARGO_ENCODED_RUSTFLAGS",
+ encoded_rustflags.replace("\\x1f", "\x1f"),
+ );
+ }
+
+ let (buildrs_outputs, process_output) = BuildScriptOutput::outputs_from_command(&mut command)
+ .map_err(|process_output| {
+ format!(
+ "Build script process failed{}\n--stdout:\n{}\n--stderr:\n{}",
+ if let Some(exit_code) = process_output.status.code() {
+ format!(" with exit code {}", exit_code)
+ } else {
+ String::new()
+ },
+ String::from_utf8(process_output.stdout)
+ .expect("Failed to parse stdout of child process"),
+ String::from_utf8(process_output.stderr)
+ .expect("Failed to parse stdout of child process"),
+ )
+ })?;
+
+ write(
+ &env_file,
+ BuildScriptOutput::outputs_to_env(&buildrs_outputs, &exec_root.to_string_lossy())
+ .as_bytes(),
+ )
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", env_file));
+ write(
+ &output_dep_env_path,
+ BuildScriptOutput::outputs_to_dep_env(
+ &buildrs_outputs,
+ &crate_links,
+ &exec_root.to_string_lossy(),
+ )
+ .as_bytes(),
+ )
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", output_dep_env_path));
+ write(&stdout_path, process_output.stdout)
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", stdout_path));
+ write(&stderr_path, process_output.stderr)
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", stderr_path));
+
+ let CompileAndLinkFlags {
+ compile_flags,
+ link_flags,
+ link_search_paths,
+ } = BuildScriptOutput::outputs_to_flags(&buildrs_outputs, &exec_root.to_string_lossy());
+
+ write(&compile_flags_file, compile_flags.as_bytes())
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", compile_flags_file));
+ write(&link_flags_file, link_flags.as_bytes())
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_flags_file));
+ write(&link_search_paths_file, link_search_paths.as_bytes())
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_search_paths_file));
+ Ok(())
+}
+
+/// A representation of expected command line arguments.
+struct Options {
+ progname: String,
+ crate_links: String,
+ out_dir: String,
+ env_file: String,
+ compile_flags_file: String,
+ link_flags_file: String,
+ link_search_paths_file: String,
+ output_dep_env_path: String,
+ stdout_path: String,
+ stderr_path: String,
+ input_dep_env_paths: Vec<String>,
+}
+
+/// Parses positional comamnd line arguments into a well defined struct
+fn parse_args() -> Result<Options, String> {
+ let mut args = env::args().skip(1);
+
+ // TODO: we should consider an alternative to positional arguments.
+ match (args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next()) {
+ (
+ Some(progname),
+ Some(crate_links),
+ Some(out_dir),
+ Some(env_file),
+ Some(compile_flags_file),
+ Some(link_flags_file),
+ Some(link_search_paths_file),
+ Some(output_dep_env_path),
+ Some(stdout_path),
+ Some(stderr_path),
+ ) => {
+ Ok(Options{
+ 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: args.collect(),
+ })
+ }
+ _ => {
+ 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>>()))
+ }
+ }
+}
+
+fn get_target_env_vars<P: AsRef<Path>>(rustc: &P) -> Result<BTreeMap<String, String>, String> {
+ // As done by Cargo when constructing a cargo::core::compiler::build_context::target_info::TargetInfo.
+ let output = Command::new(rustc.as_ref())
+ .arg("--print=cfg")
+ .arg(format!(
+ "--target={}",
+ env::var("TARGET").expect("missing TARGET")
+ ))
+ .output()
+ .map_err(|err| format!("Error running rustc to get target information: {}", err))?;
+ if !output.status.success() {
+ return Err(format!(
+ "Error running rustc to get target information: {:?}",
+ output
+ ));
+ }
+ let stdout = std::str::from_utf8(&output.stdout)
+ .map_err(|err| format!("Non-UTF8 stdout from rustc: {:?}", err))?;
+
+ Ok(parse_rustc_cfg_output(stdout))
+}
+
+fn parse_rustc_cfg_output(stdout: &str) -> BTreeMap<String, String> {
+ let mut values = BTreeMap::new();
+
+ for line in stdout.lines() {
+ if line.starts_with("target_") && line.contains('=') {
+ let mut parts = line.splitn(2, '=');
+ // UNWRAP: Verified that line contains = and split into exactly 2 parts.
+ let key = parts.next().unwrap();
+ let value = parts.next().unwrap();
+ if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
+ values
+ .entry(key)
+ .or_insert_with(Vec::new)
+ .push(value[1..(value.len() - 1)].to_owned());
+ }
+ } else if ["windows", "unix"].contains(&line) {
+ // the 'windows' or 'unix' line received from rustc will be turned
+ // into eg. CARGO_CFG_WINDOWS='' below
+ values.insert(line, vec![]);
+ }
+ }
+
+ values
+ .into_iter()
+ .map(|(key, value)| (format!("CARGO_CFG_{}", key.to_uppercase()), value.join(",")))
+ .collect()
+}
+
+fn main() {
+ std::process::exit(match run_buildrs() {
+ Ok(_) => 0,
+ Err(err) => {
+ // Neatly print errors
+ eprintln!("{}", err);
+ 1
+ }
+ });
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn rustc_cfg_parsing() {
+ let macos_output = r#"\
+debug_assertions
+target_arch="x86_64"
+target_endian="little"
+target_env=""
+target_family="unix"
+target_feature="fxsr"
+target_feature="sse"
+target_feature="sse2"
+target_feature="sse3"
+target_feature="ssse3"
+target_os="macos"
+target_pointer_width="64"
+target_vendor="apple"
+unix
+"#;
+ let tree = parse_rustc_cfg_output(macos_output);
+ assert_eq!(tree["CARGO_CFG_UNIX"], "");
+ assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "unix");
+
+ let windows_output = r#"\
+debug_assertions
+target_arch="x86_64"
+target_endian="little"
+target_env="msvc"
+target_family="windows"
+target_feature="fxsr"
+target_feature="sse"
+target_feature="sse2"
+target_os="windows"
+target_pointer_width="64"
+target_vendor="pc"
+windows
+"#;
+ let tree = parse_rustc_cfg_output(windows_output);
+ assert_eq!(tree["CARGO_CFG_WINDOWS"], "");
+ assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "windows");
+ }
+}