blob: df4b2f9bdebd25fa24cd81eab2bd37d41fc443b2 [file] [log] [blame]
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<Config> {
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::<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);
}
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<PathBuf>,
/// The path to the Bazel execution root. If not specified, uses the result of `bazel info execution_root`.
#[clap(long)]
execution_root: Option<PathBuf>,
/// 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<PathBuf>,
/// 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<String>,
}