| //! 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); | |
| } |