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/rust_analyzer/main.rs b/tools/rust_analyzer/main.rs
new file mode 100644
index 0000000..2bad0e9
--- /dev/null
+++ b/tools/rust_analyzer/main.rs
@@ -0,0 +1,120 @@
+use std::collections::HashMap;
+use std::env;
+use std::path::PathBuf;
+use std::process::Command;
+
+use anyhow::anyhow;
+use gen_rust_project_lib::generate_crate_info;
+use gen_rust_project_lib::write_rust_project;
+use structopt::StructOpt;
+
+// TODO(david): This shells out to an expected rule in the workspace root //:rust_analyzer that the user must define.
+// It would be more convenient if it could automatically discover all the rust code in the workspace if this target
+// does not exist.
+fn main() -> anyhow::Result<()> {
+    env_logger::init();
+
+    let config = parse_config()?;
+
+    let workspace_root = config
+        .workspace
+        .as_ref()
+        .expect("failed to find workspace root, set with --workspace");
+
+    let execution_root = config
+        .execution_root
+        .as_ref()
+        .expect("failed to find execution root, is --execution-root set correctly?");
+
+    let rules_rust_name = env!("ASPECT_REPOSITORY");
+
+    // Generate the crate specs.
+    generate_crate_info(
+        &config.bazel,
+        &workspace_root,
+        &rules_rust_name,
+        &config.targets,
+    )?;
+
+    // Use the generated files to write rust-project.json.
+    write_rust_project(
+        &config.bazel,
+        &workspace_root,
+        &rules_rust_name,
+        &config.targets,
+        &execution_root,
+        &workspace_root.join("rust-project.json"),
+    )?;
+
+    Ok(())
+}
+
+// Parse the configuration flags and supplement with bazel info as needed.
+fn parse_config() -> anyhow::Result<Config> {
+    let mut config = Config::from_args();
+
+    // Ensure we know the workspace. If we are under `bazel run`, the
+    // BUILD_WORKSPACE_DIR environment variable will be present.
+    if config.workspace.is_none() {
+        if let Some(ws_dir) = env::var_os("BUILD_WORKSPACE_DIRECTORY") {
+            config.workspace = Some(PathBuf::from(ws_dir));
+        }
+    }
+
+    if config.workspace.is_some() && config.execution_root.is_some() {
+        return Ok(config);
+    }
+
+    // We need some info from `bazel info`. Fetch it now.
+    let mut bazel_info_command = Command::new(&config.bazel);
+    bazel_info_command.arg("info");
+    if let Some(workspace) = &config.workspace {
+        bazel_info_command.current_dir(workspace);
+    }
+
+    // Execute bazel info.
+    let output = bazel_info_command.output()?;
+    if !output.status.success() {
+        return Err(anyhow!(
+            "Failed to run `bazel info` ({:?}): {}",
+            output.status,
+            String::from_utf8_lossy(&output.stderr)
+        ));
+    }
+
+    // Extract the output.
+    let output = String::from_utf8_lossy(output.stdout.as_slice());
+    let bazel_info = output
+        .trim()
+        .split('\n')
+        .map(|line| line.split_at(line.find(':').expect("missing `:` in bazel info output")))
+        .map(|(k, v)| (k, (&v[1..]).trim()))
+        .collect::<HashMap<_, _>>();
+
+    if config.workspace.is_none() {
+        config.workspace = bazel_info.get("workspace").map(Into::into);
+    }
+    if config.execution_root.is_none() {
+        config.execution_root = bazel_info.get("execution_root").map(Into::into);
+    }
+
+    Ok(config)
+}
+
+#[derive(Debug, StructOpt)]
+struct Config {
+    // If not specified, uses the result of `bazel info workspace`.
+    #[structopt(long)]
+    workspace: Option<PathBuf>,
+
+    // If not specified, uses the result of `bazel info execution_root`.
+    #[structopt(long)]
+    execution_root: Option<PathBuf>,
+
+    #[structopt(long, default_value = "bazel")]
+    bazel: PathBuf,
+
+    // Space separated list of target patterns that comes after all other args.
+    #[structopt(default_value = "@//...")]
+    targets: Vec<String>,
+}