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/tools/rustdoc/rustdoc_test_writer.rs b/tools/rustdoc/rustdoc_test_writer.rs
new file mode 100644
index 0000000..6803e8b
--- /dev/null
+++ b/tools/rustdoc/rustdoc_test_writer.rs
@@ -0,0 +1,205 @@
+//! A utility for writing scripts for use as test executables intended to match the
+//! subcommands of Bazel build actions so `rustdoc --test`, which builds and tests
+//! code in a single call, can be run as a test target in a hermetic manner.
+
+use std::cmp::Reverse;
+use std::collections::{BTreeMap, BTreeSet};
+use std::env;
+use std::fs;
+use std::path::{Path, PathBuf};
+
+#[derive(Debug)]
+struct Options {
+    /// A list of environment variable keys to parse from the build action env.
+    env_keys: BTreeSet<String>,
+
+    /// A list of substrings to strip from [Options::action_argv].
+    strip_substrings: Vec<String>,
+
+    /// The path where the script should be written.
+    output: PathBuf,
+
+    /// The `argv` of the configured rustdoc build action.
+    action_argv: Vec<String>,
+}
+
+/// Parse command line arguments
+fn parse_args() -> Options {
+    let args: Vec<String> = env::args().into_iter().collect();
+    let (writer_args, action_args) = {
+        let split = args
+            .iter()
+            .position(|arg| arg == "--")
+            .expect("Unable to find split identifier `--`");
+
+        // Converting each set into a vector makes them easier to parse in
+        // the absence of nightly features
+        let (writer, action) = args.split_at(split);
+        (writer.to_vec(), action.to_vec())
+    };
+
+    // Remove the leading `--` which is expected to be the first
+    // item in `action_args`
+    debug_assert_eq!(action_args[0], "--");
+    let action_argv = action_args[1..].to_vec();
+
+    let output = writer_args
+        .iter()
+        .find(|arg| arg.starts_with("--output="))
+        .and_then(|arg| arg.splitn(2, '=').last())
+        .map(PathBuf::from)
+        .expect("Missing `--output` argument");
+
+    let (strip_substring_args, writer_args): (Vec<String>, Vec<String>) = writer_args
+        .into_iter()
+        .partition(|arg| arg.starts_with("--strip_substring="));
+
+    let mut strip_substrings: Vec<String> = strip_substring_args
+        .into_iter()
+        .map(|arg| {
+            arg.splitn(2, '=')
+                .last()
+                .expect("--strip_substring arguments must have assignments using `=`")
+                .to_owned()
+        })
+        .collect();
+
+    // Strip substrings should always be in reverse order of the length of each
+    // string so when filtering we know that the longer strings are checked
+    // first in order to avoid cases where shorter strings might match longer ones.
+    strip_substrings.sort_by_key(|b| Reverse(b.len()));
+    strip_substrings.dedup();
+
+    let env_keys = writer_args
+        .into_iter()
+        .filter(|arg| arg.starts_with("--action_env="))
+        .map(|arg| {
+            arg.splitn(2, '=')
+                .last()
+                .expect("--env arguments must have assignments using `=`")
+                .to_owned()
+        })
+        .collect();
+
+    Options {
+        env_keys,
+        strip_substrings,
+        output,
+        action_argv,
+    }
+}
+
+/// Write a unix compatible test runner
+fn write_test_runner_unix(
+    path: &Path,
+    env: &BTreeMap<String, String>,
+    argv: &[String],
+    strip_substrings: &[String],
+) {
+    let mut content = vec![
+        "#!/usr/bin/env bash".to_owned(),
+        "".to_owned(),
+        "exec env - \\".to_owned(),
+    ];
+
+    content.extend(env.iter().map(|(key, val)| format!("{}='{}' \\", key, val)));
+
+    let argv_str = argv
+        .iter()
+        // Remove any substrings found in the argument
+        .map(|arg| {
+            let mut stripped_arg = arg.to_owned();
+            strip_substrings
+                .iter()
+                .for_each(|substring| stripped_arg = stripped_arg.replace(substring, ""));
+            stripped_arg
+        })
+        .map(|arg| format!("'{}'", arg))
+        .collect::<Vec<String>>()
+        .join(" ");
+
+    content.extend(vec![argv_str, "".to_owned()]);
+
+    fs::write(path, content.join("\n")).expect("Failed to write test runner");
+}
+
+/// Write a windows compatible test runner
+fn write_test_runner_windows(
+    path: &Path,
+    env: &BTreeMap<String, String>,
+    argv: &[String],
+    strip_substrings: &[String],
+) {
+    let env_str = env
+        .iter()
+        .map(|(key, val)| format!("$env:{}='{}'", key, val))
+        .collect::<Vec<String>>()
+        .join(" ; ");
+
+    let argv_str = argv
+        .iter()
+        // Remove any substrings found in the argument
+        .map(|arg| {
+            let mut stripped_arg = arg.to_owned();
+            strip_substrings
+                .iter()
+                .for_each(|substring| stripped_arg = stripped_arg.replace(substring, ""));
+            stripped_arg
+        })
+        .map(|arg| format!("'{}'", arg))
+        .collect::<Vec<String>>()
+        .join(" ");
+
+    let content = vec![
+        "@ECHO OFF".to_owned(),
+        "".to_owned(),
+        format!("powershell.exe -c \"{} ; & {}\"", env_str, argv_str),
+        "".to_owned(),
+    ];
+
+    fs::write(path, content.join("\n")).expect("Failed to write test runner");
+}
+
+#[cfg(target_family = "unix")]
+fn set_executable(path: &Path) {
+    use std::os::unix::prelude::PermissionsExt;
+
+    let mut perm = fs::metadata(path)
+        .expect("Failed to get test runner metadata")
+        .permissions();
+
+    perm.set_mode(0o755);
+    fs::set_permissions(path, perm).expect("Failed to set permissions on test runner");
+}
+
+#[cfg(target_family = "windows")]
+fn set_executable(_path: &Path) {
+    // Windows determines whether or not a file is executable via the PATHEXT
+    // environment variable. This function is a no-op for this platform.
+}
+
+fn write_test_runner(
+    path: &Path,
+    env: &BTreeMap<String, String>,
+    argv: &[String],
+    strip_substrings: &[String],
+) {
+    if cfg!(target_family = "unix") {
+        write_test_runner_unix(path, env, argv, strip_substrings);
+    } else if cfg!(target_family = "windows") {
+        write_test_runner_windows(path, env, argv, strip_substrings);
+    }
+
+    set_executable(path);
+}
+
+fn main() {
+    let opt = parse_args();
+
+    let env: BTreeMap<String, String> = env::vars()
+        .into_iter()
+        .filter(|(key, _)| opt.env_keys.iter().any(|k| k == key))
+        .collect();
+
+    write_test_runner(&opt.output, &env, &opt.action_argv, &opt.strip_substrings);
+}