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