xref: /aosp_15_r20/external/bazelbuild-rules_rust/tools/rust_analyzer/main.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan use std::collections::HashMap;
2*d4726bddSHONG Yifan use std::env;
3*d4726bddSHONG Yifan use std::path::PathBuf;
4*d4726bddSHONG Yifan use std::process::Command;
5*d4726bddSHONG Yifan 
6*d4726bddSHONG Yifan use anyhow::anyhow;
7*d4726bddSHONG Yifan use clap::Parser;
8*d4726bddSHONG Yifan use gen_rust_project_lib::generate_crate_info;
9*d4726bddSHONG Yifan use gen_rust_project_lib::write_rust_project;
10*d4726bddSHONG Yifan 
11*d4726bddSHONG Yifan // TODO(david): This shells out to an expected rule in the workspace root //:rust_analyzer that the user must define.
12*d4726bddSHONG Yifan // It would be more convenient if it could automatically discover all the rust code in the workspace if this target
13*d4726bddSHONG Yifan // does not exist.
main() -> anyhow::Result<()>14*d4726bddSHONG Yifan fn main() -> anyhow::Result<()> {
15*d4726bddSHONG Yifan     env_logger::init();
16*d4726bddSHONG Yifan 
17*d4726bddSHONG Yifan     let config = parse_config()?;
18*d4726bddSHONG Yifan 
19*d4726bddSHONG Yifan     let workspace_root = config
20*d4726bddSHONG Yifan         .workspace
21*d4726bddSHONG Yifan         .as_ref()
22*d4726bddSHONG Yifan         .expect("failed to find workspace root, set with --workspace");
23*d4726bddSHONG Yifan 
24*d4726bddSHONG Yifan     let execution_root = config
25*d4726bddSHONG Yifan         .execution_root
26*d4726bddSHONG Yifan         .as_ref()
27*d4726bddSHONG Yifan         .expect("failed to find execution root, is --execution-root set correctly?");
28*d4726bddSHONG Yifan 
29*d4726bddSHONG Yifan     let output_base = config
30*d4726bddSHONG Yifan         .output_base
31*d4726bddSHONG Yifan         .as_ref()
32*d4726bddSHONG Yifan         .expect("failed to find output base, is -output-base set correctly?");
33*d4726bddSHONG Yifan 
34*d4726bddSHONG Yifan     let rules_rust_name = env!("ASPECT_REPOSITORY");
35*d4726bddSHONG Yifan 
36*d4726bddSHONG Yifan     // Generate the crate specs.
37*d4726bddSHONG Yifan     generate_crate_info(
38*d4726bddSHONG Yifan         &config.bazel,
39*d4726bddSHONG Yifan         workspace_root,
40*d4726bddSHONG Yifan         rules_rust_name,
41*d4726bddSHONG Yifan         &config.targets,
42*d4726bddSHONG Yifan     )?;
43*d4726bddSHONG Yifan 
44*d4726bddSHONG Yifan     // Use the generated files to write rust-project.json.
45*d4726bddSHONG Yifan     write_rust_project(
46*d4726bddSHONG Yifan         &config.bazel,
47*d4726bddSHONG Yifan         workspace_root,
48*d4726bddSHONG Yifan         &rules_rust_name,
49*d4726bddSHONG Yifan         &config.targets,
50*d4726bddSHONG Yifan         execution_root,
51*d4726bddSHONG Yifan         output_base,
52*d4726bddSHONG Yifan         workspace_root.join("rust-project.json"),
53*d4726bddSHONG Yifan     )?;
54*d4726bddSHONG Yifan 
55*d4726bddSHONG Yifan     Ok(())
56*d4726bddSHONG Yifan }
57*d4726bddSHONG Yifan 
58*d4726bddSHONG Yifan // Parse the configuration flags and supplement with bazel info as needed.
parse_config() -> anyhow::Result<Config>59*d4726bddSHONG Yifan fn parse_config() -> anyhow::Result<Config> {
60*d4726bddSHONG Yifan     let mut config = Config::parse();
61*d4726bddSHONG Yifan 
62*d4726bddSHONG Yifan     if config.workspace.is_some() && config.execution_root.is_some() {
63*d4726bddSHONG Yifan         return Ok(config);
64*d4726bddSHONG Yifan     }
65*d4726bddSHONG Yifan 
66*d4726bddSHONG Yifan     // We need some info from `bazel info`. Fetch it now.
67*d4726bddSHONG Yifan     let mut bazel_info_command = Command::new(&config.bazel);
68*d4726bddSHONG Yifan     bazel_info_command
69*d4726bddSHONG Yifan         .env_remove("BAZELISK_SKIP_WRAPPER")
70*d4726bddSHONG Yifan         .env_remove("BUILD_WORKING_DIRECTORY")
71*d4726bddSHONG Yifan         .env_remove("BUILD_WORKSPACE_DIRECTORY")
72*d4726bddSHONG Yifan         .arg("info");
73*d4726bddSHONG Yifan     if let Some(workspace) = &config.workspace {
74*d4726bddSHONG Yifan         bazel_info_command.current_dir(workspace);
75*d4726bddSHONG Yifan     }
76*d4726bddSHONG Yifan 
77*d4726bddSHONG Yifan     // Execute bazel info.
78*d4726bddSHONG Yifan     let output = bazel_info_command.output()?;
79*d4726bddSHONG Yifan     if !output.status.success() {
80*d4726bddSHONG Yifan         return Err(anyhow!(
81*d4726bddSHONG Yifan             "Failed to run `bazel info` ({:?}): {}",
82*d4726bddSHONG Yifan             output.status,
83*d4726bddSHONG Yifan             String::from_utf8_lossy(&output.stderr)
84*d4726bddSHONG Yifan         ));
85*d4726bddSHONG Yifan     }
86*d4726bddSHONG Yifan 
87*d4726bddSHONG Yifan     // Extract the output.
88*d4726bddSHONG Yifan     let output = String::from_utf8_lossy(output.stdout.as_slice());
89*d4726bddSHONG Yifan     let bazel_info = output
90*d4726bddSHONG Yifan         .trim()
91*d4726bddSHONG Yifan         .split('\n')
92*d4726bddSHONG Yifan         .map(|line| line.split_at(line.find(':').expect("missing `:` in bazel info output")))
93*d4726bddSHONG Yifan         .map(|(k, v)| (k, (v[1..]).trim()))
94*d4726bddSHONG Yifan         .collect::<HashMap<_, _>>();
95*d4726bddSHONG Yifan 
96*d4726bddSHONG Yifan     if config.workspace.is_none() {
97*d4726bddSHONG Yifan         config.workspace = bazel_info.get("workspace").map(Into::into);
98*d4726bddSHONG Yifan     }
99*d4726bddSHONG Yifan     if config.execution_root.is_none() {
100*d4726bddSHONG Yifan         config.execution_root = bazel_info.get("execution_root").map(Into::into);
101*d4726bddSHONG Yifan     }
102*d4726bddSHONG Yifan     if config.output_base.is_none() {
103*d4726bddSHONG Yifan         config.output_base = bazel_info.get("output_base").map(Into::into);
104*d4726bddSHONG Yifan     }
105*d4726bddSHONG Yifan 
106*d4726bddSHONG Yifan     Ok(config)
107*d4726bddSHONG Yifan }
108*d4726bddSHONG Yifan 
109*d4726bddSHONG Yifan #[derive(Debug, Parser)]
110*d4726bddSHONG Yifan struct Config {
111*d4726bddSHONG Yifan     /// The path to the Bazel workspace directory. If not specified, uses the result of `bazel info workspace`.
112*d4726bddSHONG Yifan     #[clap(long, env = "BUILD_WORKSPACE_DIRECTORY")]
113*d4726bddSHONG Yifan     workspace: Option<PathBuf>,
114*d4726bddSHONG Yifan 
115*d4726bddSHONG Yifan     /// The path to the Bazel execution root. If not specified, uses the result of `bazel info execution_root`.
116*d4726bddSHONG Yifan     #[clap(long)]
117*d4726bddSHONG Yifan     execution_root: Option<PathBuf>,
118*d4726bddSHONG Yifan 
119*d4726bddSHONG Yifan     /// The path to the Bazel output user root. If not specified, uses the result of `bazel info output_base`.
120*d4726bddSHONG Yifan     #[clap(long, env = "OUTPUT_BASE")]
121*d4726bddSHONG Yifan     output_base: Option<PathBuf>,
122*d4726bddSHONG Yifan 
123*d4726bddSHONG Yifan     /// The path to a Bazel binary
124*d4726bddSHONG Yifan     #[clap(long, default_value = "bazel")]
125*d4726bddSHONG Yifan     bazel: PathBuf,
126*d4726bddSHONG Yifan 
127*d4726bddSHONG Yifan     /// Space separated list of target patterns that comes after all other args.
128*d4726bddSHONG Yifan     #[clap(default_value = "@//...")]
129*d4726bddSHONG Yifan     targets: Vec<String>,
130*d4726bddSHONG Yifan }
131