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