blob: 1f44d5e91cba365e2727d3c887f409410743fdac [file] [log] [blame]
use anyhow::anyhow;
use anyhow::Context;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::ErrorKind;
use std::path::PathBuf;
use std::process::Command;
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<()> {
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 bazel_bin = config
.bazel_bin
.as_ref()
.expect("failed to find execution root, is --bazel-bin set correctly?");
build_rust_project_target(&config);
let label = label::analyze(&config.bazel_analyzer_target)
.with_context(|| "Cannot parse --bazel-analyzer-target")?;
let mut generated_rust_project = bazel_bin.clone();
if let Some(repository_name) = label.repository_name {
generated_rust_project = generated_rust_project
.join("external")
.join(repository_name);
}
for package in label.packages() {
generated_rust_project = generated_rust_project.join(package);
}
generated_rust_project = generated_rust_project.join("rust-project.json");
let workspace_rust_project = workspace_root.join("rust-project.json");
// The generated_rust_project has a template string we must replace with the workspace name.
let generated_json = fs::read_to_string(&generated_rust_project)
.expect("failed to read generated rust-project.json");
// Try to remove the existing rust-project.json. It's OK if the file doesn't exist.
match fs::remove_file(&workspace_rust_project) {
Ok(_) => {}
Err(err) if err.kind() == ErrorKind::NotFound => {}
Err(err) => panic!("Unexpected error removing old rust-project.json: {}", err),
}
// Write the new rust-project.json file.
fs::write(
workspace_rust_project,
generated_json.replace("__EXEC_ROOT__", &execution_root.to_string_lossy()),
)
.expect("failed to write workspace rust-project.json");
Ok(())
}
fn build_rust_project_target(config: &Config) {
let output = Command::new(&config.bazel)
.current_dir(config.workspace.as_ref().unwrap())
.arg("build")
.arg(&config.bazel_analyzer_target)
.output()
.expect("failed to execute bazel process");
if !output.status.success() {
panic!(
"bazel build failed:({}) of {:?}:\n{}",
output.status,
&config.bazel_analyzer_target,
String::from_utf8_lossy(&output.stderr)
);
}
}
// 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);
}
if config.bazel_bin.is_none() {
config.bazel_bin = bazel_info.get("bazel-bin").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>,
// If not specified, uses the result of `bazel info bazel-bin`.
#[structopt(long)]
bazel_bin: Option<PathBuf>,
#[structopt(long, default_value = "bazel")]
bazel: PathBuf,
#[structopt(long, default_value = "//:rust_analyzer")]
bazel_analyzer_target: String,
}