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/rustfmt/BUILD.bazel b/tools/rustfmt/BUILD.bazel
new file mode 100644
index 0000000..1456eb1
--- /dev/null
+++ b/tools/rustfmt/BUILD.bazel
@@ -0,0 +1,65 @@
+load("//rust:defs.bzl", "rust_binary", "rust_clippy", "rust_library")
+load("//tools:tool_utils.bzl", "aspect_repository")
+
+package(default_visibility = ["//visibility:public"])
+
+exports_files([
+    "rustfmt.toml",
+    "rustfmt_utils.bzl",
+])
+
+rust_library(
+    name = "rustfmt_lib",
+    srcs = glob(
+        ["srcs/**/*.rs"],
+        exclude = ["srcs/**/*main.rs"],
+    ),
+    data = [
+        "//:rustfmt.toml",
+        "//rust/toolchain:current_exec_rustfmt_files",
+    ],
+    edition = "2018",
+    rustc_env = {
+        "RUSTFMT": "$(rootpath //rust/toolchain:current_exec_rustfmt_files)",
+        "RUSTFMT_CONFIG": "$(rootpath //:rustfmt.toml)",
+    },
+)
+
+rust_binary(
+    name = "rustfmt",
+    srcs = [
+        "srcs/main.rs",
+    ],
+    data = [
+        "//:rustfmt.toml",
+    ],
+    edition = "2018",
+    rustc_env = {
+        "ASPECT_REPOSITORY": aspect_repository(),
+    },
+    deps = [
+        ":rustfmt_lib",
+        "//util/label",
+    ],
+)
+
+rust_binary(
+    name = "rustfmt_test",
+    srcs = [
+        "srcs/test_main.rs",
+    ],
+    edition = "2018",
+    deps = [
+        ":rustfmt_lib",
+        "//tools/runfiles",
+    ],
+)
+
+rust_clippy(
+    name = "rustfmt_clippy",
+    testonly = True,
+    visibility = ["//visibility:private"],
+    deps = [
+        ":rustfmt",
+    ],
+)
diff --git a/tools/rustfmt/rustfmt.toml b/tools/rustfmt/rustfmt.toml
new file mode 100644
index 0000000..44bdbf2
--- /dev/null
+++ b/tools/rustfmt/rustfmt.toml
@@ -0,0 +1 @@
+# rustfmt options: https://rust-lang.github.io/rustfmt/
diff --git a/tools/rustfmt/srcs/lib.rs b/tools/rustfmt/srcs/lib.rs
new file mode 100644
index 0000000..ad2e86a
--- /dev/null
+++ b/tools/rustfmt/srcs/lib.rs
@@ -0,0 +1,74 @@
+use std::env;
+use std::fs;
+use std::path::{Path, PathBuf};
+
+/// The expected extension of rustfmt manifest files generated by `rustfmt_aspect`.
+pub const RUSTFMT_MANIFEST_EXTENSION: &str = "rustfmt";
+
+/// Generate an absolute path to a file without resolving symlinks
+fn absolutify_existing<T: AsRef<Path>>(path: &T) -> std::io::Result<PathBuf> {
+    let absolute_path = if path.as_ref().is_absolute() {
+        path.as_ref().to_owned()
+    } else {
+        std::env::current_dir()
+            .expect("Failed to get working directory")
+            .join(path)
+    };
+    std::fs::metadata(&absolute_path).map(|_| absolute_path)
+}
+
+/// A struct containing details used for executing rustfmt.
+#[derive(Debug)]
+pub struct RustfmtConfig {
+    /// The rustfmt binary from the currently active toolchain
+    pub rustfmt: PathBuf,
+
+    /// The rustfmt config file containing rustfmt settings.
+    /// https://rust-lang.github.io/rustfmt/
+    pub config: PathBuf,
+}
+
+/// Parse command line arguments and environment variables to
+/// produce config data for running rustfmt.
+pub fn parse_rustfmt_config() -> RustfmtConfig {
+    RustfmtConfig {
+        rustfmt: absolutify_existing(&env!("RUSTFMT")).expect("Unable to find rustfmt binary"),
+        config: absolutify_existing(&env!("RUSTFMT_CONFIG"))
+            .expect("Unable to find rustfmt config file"),
+    }
+}
+
+/// A struct of target specific information for use in running `rustfmt`.
+#[derive(Debug)]
+pub struct RustfmtManifest {
+    /// The Rust edition of the Bazel target
+    pub edition: String,
+
+    /// A list of all (non-generated) source files for formatting.
+    pub sources: Vec<String>,
+}
+
+/// Parse rustfmt flags from a manifest generated by builds using `rustfmt_aspect`.
+pub fn parse_rustfmt_manifest(manifest: &Path) -> RustfmtManifest {
+    let content = fs::read_to_string(manifest)
+        .unwrap_or_else(|_| panic!("Failed to read rustfmt manifest: {}", manifest.display()));
+
+    let mut lines: Vec<String> = content
+        .split('\n')
+        .into_iter()
+        .filter(|s| !s.is_empty())
+        .map(|s| s.to_owned())
+        .collect();
+
+    let edition = lines
+        .pop()
+        .expect("There should always be at least 1 line in the manifest");
+    edition
+        .parse::<i32>()
+        .expect("The edition should be a numeric value. eg `2018`.");
+
+    RustfmtManifest {
+        edition,
+        sources: lines,
+    }
+}
diff --git a/tools/rustfmt/srcs/main.rs b/tools/rustfmt/srcs/main.rs
new file mode 100644
index 0000000..4e4419f
--- /dev/null
+++ b/tools/rustfmt/srcs/main.rs
@@ -0,0 +1,185 @@
+use std::env;
+use std::path::PathBuf;
+use std::process::{Command, Stdio};
+use std::str;
+
+fn main() {
+    // Gather all command line and environment settings
+    let options = parse_args();
+
+    // Gather a list of all formattable targets
+    let targets = query_rustfmt_targets(&options);
+
+    // Run rustfmt on these targets
+    apply_rustfmt(&options, &targets);
+}
+
+/// Perform a `bazel` query to determine a list of Bazel targets which are to be formatted.
+fn query_rustfmt_targets(options: &Config) -> Vec<String> {
+    // Determine what packages to query
+    let scope = match options.packages.is_empty() {
+        true => "//...:all".to_owned(),
+        false => {
+            // Check to see if all the provided packages are actually targets
+            let is_all_targets = options
+                .packages
+                .iter()
+                .all(|pkg| match label::analyze(pkg) {
+                    Ok(tgt) => tgt.name != "all",
+                    Err(_) => false,
+                });
+
+            // Early return if a list of targets and not packages were provided
+            if is_all_targets {
+                return options.packages.clone();
+            }
+
+            options.packages.join(" + ")
+        }
+    };
+
+    let query_args = vec![
+        "query".to_owned(),
+        format!(
+            r#"kind('{types}', {scope}) except attr(tags, 'norustfmt', kind('{types}', {scope}))"#,
+            types = "^rust_",
+            scope = scope
+        ),
+    ];
+
+    let child = Command::new(&options.bazel)
+        .current_dir(&options.workspace)
+        .args(query_args)
+        .stdout(Stdio::piped())
+        .stderr(Stdio::inherit())
+        .spawn()
+        .expect("Failed to spawn bazel query command");
+
+    let output = child
+        .wait_with_output()
+        .expect("Failed to wait on spawned command");
+
+    if !output.status.success() {
+        std::process::exit(output.status.code().unwrap_or(1));
+    }
+
+    str::from_utf8(&output.stdout)
+        .expect("Invalid stream from command")
+        .split('\n')
+        .filter(|line| !line.is_empty())
+        .map(|line| line.to_string())
+        .collect()
+}
+
+/// Build a list of Bazel targets using the `rustfmt_aspect` to produce the
+/// arguments to use when formatting the sources of those targets.
+fn generate_rustfmt_target_manifests(options: &Config, targets: &[String]) {
+    let build_args = vec![
+        "build".to_owned(),
+        format!(
+            "--aspects={}//rust:defs.bzl%rustfmt_aspect",
+            env!("ASPECT_REPOSITORY")
+        ),
+        "--output_groups=rustfmt_manifest".to_owned(),
+    ];
+
+    let child = Command::new(&options.bazel)
+        .current_dir(&options.workspace)
+        .args(build_args)
+        .args(targets)
+        .stdout(Stdio::piped())
+        .stderr(Stdio::inherit())
+        .spawn()
+        .expect("Failed to spawn command");
+
+    let output = child
+        .wait_with_output()
+        .expect("Failed to wait on spawned command");
+
+    if !output.status.success() {
+        std::process::exit(output.status.code().unwrap_or(1));
+    }
+}
+
+/// Run rustfmt on a set of Bazel targets
+fn apply_rustfmt(options: &Config, targets: &[String]) {
+    // Ensure the targets are first built and a manifest containing `rustfmt`
+    // arguments are generated before formatting source files.
+    generate_rustfmt_target_manifests(options, targets);
+
+    for target in targets.iter() {
+        // Replace any `:` characters and strip leading slashes
+        let target_path = target.replace(':', "/").trim_start_matches('/').to_owned();
+
+        // Find a manifest for the current target. Not all targets will have one
+        let manifest = options.workspace.join("bazel-bin").join(format!(
+            "{}.{}",
+            &target_path,
+            rustfmt_lib::RUSTFMT_MANIFEST_EXTENSION,
+        ));
+
+        if !manifest.exists() {
+            continue;
+        }
+
+        // Load the manifest containing rustfmt arguments
+        let rustfmt_config = rustfmt_lib::parse_rustfmt_manifest(&manifest);
+
+        // Ignore any targets which do not have source files. This can
+        // occur in cases where all source files are generated.
+        if rustfmt_config.sources.is_empty() {
+            continue;
+        }
+
+        // Run rustfmt
+        let status = Command::new(&options.rustfmt_config.rustfmt)
+            .current_dir(&options.workspace)
+            .arg("--edition")
+            .arg(rustfmt_config.edition)
+            .arg("--config-path")
+            .arg(&options.rustfmt_config.config)
+            .args(rustfmt_config.sources)
+            .status()
+            .expect("Failed to run rustfmt");
+
+        if !status.success() {
+            std::process::exit(status.code().unwrap_or(1));
+        }
+    }
+}
+
+/// A struct containing details used for executing rustfmt.
+#[derive(Debug)]
+struct Config {
+    /// The path of the Bazel workspace root.
+    pub workspace: PathBuf,
+
+    /// The Bazel executable to use for builds and queries.
+    pub bazel: PathBuf,
+
+    /// Information about the current rustfmt binary to run.
+    pub rustfmt_config: rustfmt_lib::RustfmtConfig,
+
+    /// Optionally, users can pass a list of targets/packages/scopes
+    /// (eg `//my:target` or `//my/pkg/...`) to control the targets
+    /// to be formatted. If empty, all targets in the workspace will
+    /// be formatted.
+    pub packages: Vec<String>,
+}
+
+/// Parse command line arguments and environment variables to
+/// produce config data for running rustfmt.
+fn parse_args() -> Config {
+    Config{
+        workspace: PathBuf::from(
+            env::var("BUILD_WORKSPACE_DIRECTORY")
+            .expect("The environment variable BUILD_WORKSPACE_DIRECTORY is required for finding the workspace root")
+        ),
+        bazel: PathBuf::from(
+            env::var("BAZEL_REAL")
+            .unwrap_or_else(|_| "bazel".to_owned())
+        ),
+        rustfmt_config: rustfmt_lib::parse_rustfmt_config(),
+        packages: env::args().skip(1).collect(),
+    }
+}
diff --git a/tools/rustfmt/srcs/test_main.rs b/tools/rustfmt/srcs/test_main.rs
new file mode 100644
index 0000000..ea2adba
--- /dev/null
+++ b/tools/rustfmt/srcs/test_main.rs
@@ -0,0 +1,95 @@
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+fn main() {
+    // Gather all and environment settings
+    let options = parse_args();
+
+    // Perform rustfmt for each manifest available
+    run_rustfmt(&options);
+}
+
+/// Run rustfmt on a set of Bazel targets
+fn run_rustfmt(options: &Config) {
+    // In order to ensure the test parses all sources, we separately
+    // track whether or not a failure has occured when checking formatting.
+    let mut is_failure: bool = false;
+
+    for manifest in options.manifests.iter() {
+        // Ignore any targets which do not have source files. This can
+        // occur in cases where all source files are generated.
+        if manifest.sources.is_empty() {
+            continue;
+        }
+
+        // Run rustfmt
+        let status = Command::new(&options.rustfmt_config.rustfmt)
+            .arg("--check")
+            .arg("--edition")
+            .arg(&manifest.edition)
+            .arg("--config-path")
+            .arg(&options.rustfmt_config.config)
+            .args(&manifest.sources)
+            .status()
+            .expect("Failed to run rustfmt");
+
+        if !status.success() {
+            is_failure = true;
+        }
+    }
+
+    if is_failure {
+        std::process::exit(1);
+    }
+}
+
+/// A struct containing details used for executing rustfmt.
+#[derive(Debug)]
+struct Config {
+    /// Information about the current rustfmt binary to run.
+    pub rustfmt_config: rustfmt_lib::RustfmtConfig,
+
+    /// A list of manifests containing information about sources
+    /// to check using rustfmt.
+    pub manifests: Vec<rustfmt_lib::RustfmtManifest>,
+}
+
+/// Parse the runfiles of the current executable for manifests generated
+/// but the `rustfmt_aspect` aspect.
+fn find_manifests(dir: &Path, manifests: &mut Vec<PathBuf>) {
+    if dir.is_dir() {
+        for entry in fs::read_dir(dir).expect("Failed to read directory contents") {
+            let entry = entry.expect("Failed to read directory entry");
+            let path = entry.path();
+            if path.is_dir() {
+                find_manifests(&path, manifests);
+            } else if let Some(ext) = path.extension() {
+                if ext == rustfmt_lib::RUSTFMT_MANIFEST_EXTENSION {
+                    manifests.extend(vec![path]);
+                }
+            }
+        }
+    }
+}
+
+/// Parse settings from the environment into a config struct
+fn parse_args() -> Config {
+    let mut manifests: Vec<PathBuf> = Vec::new();
+    find_manifests(
+        &runfiles::find_runfiles_dir().expect("Failed to find runfiles directory"),
+        &mut manifests,
+    );
+
+    if manifests.is_empty() {
+        panic!("No manifests were found");
+    }
+
+    Config {
+        rustfmt_config: rustfmt_lib::parse_rustfmt_config(),
+        manifests: manifests
+            .iter()
+            .map(|manifest| rustfmt_lib::parse_rustfmt_manifest(manifest))
+            .collect(),
+    }
+}