use std::collections::HashMap; use std::env; use std::path::PathBuf; use std::process::Command; use anyhow::anyhow; use clap::Parser; use gen_rust_project_lib::generate_crate_info; use gen_rust_project_lib::write_rust_project; // 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 output_base = config .output_base .as_ref() .expect("failed to find output base, is -output-base 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, output_base, workspace_root.join("rust-project.json"), )?; Ok(()) } // Parse the configuration flags and supplement with bazel info as needed. fn parse_config() -> anyhow::Result { let mut config = Config::parse(); 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 .env_remove("BAZELISK_SKIP_WRAPPER") .env_remove("BUILD_WORKING_DIRECTORY") .env_remove("BUILD_WORKSPACE_DIRECTORY") .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::>(); 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); } if config.output_base.is_none() { config.output_base = bazel_info.get("output_base").map(Into::into); } Ok(config) } #[derive(Debug, Parser)] struct Config { /// The path to the Bazel workspace directory. If not specified, uses the result of `bazel info workspace`. #[clap(long, env = "BUILD_WORKSPACE_DIRECTORY")] workspace: Option, /// The path to the Bazel execution root. If not specified, uses the result of `bazel info execution_root`. #[clap(long)] execution_root: Option, /// The path to the Bazel output user root. If not specified, uses the result of `bazel info output_base`. #[clap(long, env = "OUTPUT_BASE")] output_base: Option, /// The path to a Bazel binary #[clap(long, default_value = "bazel")] bazel: PathBuf, /// Space separated list of target patterns that comes after all other args. #[clap(default_value = "@//...")] targets: Vec, }