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