blob: 2bad0e93969b29d6edf112f7f37c6edbc08a1d0c [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001use std::collections::HashMap;
2use std::env;
3use std::path::PathBuf;
4use std::process::Command;
5
6use anyhow::anyhow;
7use gen_rust_project_lib::generate_crate_info;
8use gen_rust_project_lib::write_rust_project;
9use structopt::StructOpt;
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.
14fn 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 rules_rust_name = env!("ASPECT_REPOSITORY");
30
31 // Generate the crate specs.
32 generate_crate_info(
33 &config.bazel,
34 &workspace_root,
35 &rules_rust_name,
36 &config.targets,
37 )?;
38
39 // Use the generated files to write rust-project.json.
40 write_rust_project(
41 &config.bazel,
42 &workspace_root,
43 &rules_rust_name,
44 &config.targets,
45 &execution_root,
46 &workspace_root.join("rust-project.json"),
47 )?;
48
49 Ok(())
50}
51
52// Parse the configuration flags and supplement with bazel info as needed.
53fn parse_config() -> anyhow::Result<Config> {
54 let mut config = Config::from_args();
55
56 // Ensure we know the workspace. If we are under `bazel run`, the
57 // BUILD_WORKSPACE_DIR environment variable will be present.
58 if config.workspace.is_none() {
59 if let Some(ws_dir) = env::var_os("BUILD_WORKSPACE_DIRECTORY") {
60 config.workspace = Some(PathBuf::from(ws_dir));
61 }
62 }
63
64 if config.workspace.is_some() && config.execution_root.is_some() {
65 return Ok(config);
66 }
67
68 // We need some info from `bazel info`. Fetch it now.
69 let mut bazel_info_command = Command::new(&config.bazel);
70 bazel_info_command.arg("info");
71 if let Some(workspace) = &config.workspace {
72 bazel_info_command.current_dir(workspace);
73 }
74
75 // Execute bazel info.
76 let output = bazel_info_command.output()?;
77 if !output.status.success() {
78 return Err(anyhow!(
79 "Failed to run `bazel info` ({:?}): {}",
80 output.status,
81 String::from_utf8_lossy(&output.stderr)
82 ));
83 }
84
85 // Extract the output.
86 let output = String::from_utf8_lossy(output.stdout.as_slice());
87 let bazel_info = output
88 .trim()
89 .split('\n')
90 .map(|line| line.split_at(line.find(':').expect("missing `:` in bazel info output")))
91 .map(|(k, v)| (k, (&v[1..]).trim()))
92 .collect::<HashMap<_, _>>();
93
94 if config.workspace.is_none() {
95 config.workspace = bazel_info.get("workspace").map(Into::into);
96 }
97 if config.execution_root.is_none() {
98 config.execution_root = bazel_info.get("execution_root").map(Into::into);
99 }
100
101 Ok(config)
102}
103
104#[derive(Debug, StructOpt)]
105struct Config {
106 // If not specified, uses the result of `bazel info workspace`.
107 #[structopt(long)]
108 workspace: Option<PathBuf>,
109
110 // If not specified, uses the result of `bazel info execution_root`.
111 #[structopt(long)]
112 execution_root: Option<PathBuf>,
113
114 #[structopt(long, default_value = "bazel")]
115 bazel: PathBuf,
116
117 // Space separated list of target patterns that comes after all other args.
118 #[structopt(default_value = "@//...")]
119 targets: Vec<String>,
120}