blob: f21a30d00d12c616ed5975506397047826da7cec [file] [log] [blame]
//! Validates a rust-project.json file by invoking rust-analyzer
use clap::Parser;
use env_logger::Builder;
use log::{debug, error, info, LevelFilter};
use runfiles::{rlocation, Runfiles};
use std::env;
use std::path::PathBuf;
use std::process::{exit, Command};
#[derive(Parser, Debug)]
#[command(
author,
version,
about = "Validates a rust-project.json file using rust-analyzer"
)]
struct Args {
/// Path to the rust-project.json file to validate
project: Option<PathBuf>,
/// Verbose output from rust-analyzer
#[arg(short, long)]
verbose: bool,
}
fn main() {
let args = Args::parse();
Builder::from_default_env()
.filter_level(LevelFilter::Info)
.init();
// Get working directory - use BUILD_WORKSPACE_DIRECTORY if set (running via bazel run),
// otherwise use current directory
let cwd = match env::var("BUILD_WORKING_DIRECTORY") {
Ok(var) => PathBuf::from(var),
Err(_) => env::current_dir().unwrap_or_else(|e| {
error!("Failed to get current directory: {}", e);
exit(1);
}),
};
debug!("Working directory: {:?}", cwd);
// If args.project is relative, make it absolute by joining to cwd
let project_path = match args.project {
Some(path) => {
if path.is_relative() {
cwd.join(&path)
} else {
path
}
}
None => match env::var("BUILD_WORKSPACE_DIRECTORY") {
Ok(var) => PathBuf::from(var).join("rust-project.json"),
Err(_) => cwd.join("rust-project.json"),
},
};
debug!("Project path: {:?}", project_path);
if !project_path.exists() {
error!("rust-project.json not found at {:?}", project_path);
exit(1);
}
info!("Validating rust-project.json: {}", project_path.display());
// Use runfiles to locate the rust-analyzer binary
let r = Runfiles::create().unwrap_or_else(|e| {
error!("Failed to create runfiles: {}", e);
exit(1);
});
let rust_analyzer_path =
rlocation!(r, env!("RUST_ANALYZER_RLOCATIONPATH")).unwrap_or_else(|| {
error!("Could not locate rust-analyzer binary");
error!("Rlocationpath: {}", env!("RUST_ANALYZER_RLOCATIONPATH"));
exit(1);
});
if !rust_analyzer_path.exists() {
error!(
"rust-analyzer binary not found at: {}",
rust_analyzer_path.display()
);
exit(1);
}
debug!("Using rust-analyzer: {}", rust_analyzer_path.display());
// Run rust-analyzer diagnostics to validate the project
let project_dir = project_path.parent().unwrap_or_else(|| {
error!("Could not determine project directory");
exit(1);
});
let mut cmd = Command::new(&rust_analyzer_path);
cmd.arg("diagnostics")
.arg(project_dir)
.env("RUST_PROJECT_JSON", &project_path);
if args.verbose {
cmd.arg("-v");
}
debug!("Running: {:?}", cmd);
let output = cmd.output().unwrap_or_else(|e| {
error!("Failed to execute rust-analyzer: {}", e);
exit(1);
});
// Check stderr for JSON deserialization errors
let stderr = String::from_utf8_lossy(&output.stderr);
// Look for specific JSON/deserialization errors
if stderr.contains("Failed to deserialize")
|| stderr.contains("unknown variant")
|| stderr.contains("Failed to load the project")
{
error!("rust-project.json has format errors:");
// Only show the relevant error lines
for line in stderr.lines() {
if line.contains("Failed to")
|| line.contains("unknown variant")
|| line.contains("ERROR")
{
error!("{}", line);
}
}
exit(1);
}
// If rust-analyzer ran diagnostics (even with code errors), the JSON is valid
// We don't care about code analysis errors, only JSON format errors
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.is_empty() {
debug!("rust-analyzer output:");
debug!("{}", stdout);
}
info!("rust-project.json validation successful!");
exit(0);
}