1use std::{
2 env, fs,
3 path::PathBuf,
4 process::{Command, Stdio, exit},
5};
6
7use cargo_metadata::camino::Utf8Path;
8
9use super::plugin::{PLUGIN_ARGS, RustcPlugin};
10use crate::CrateFilter;
11
12pub const RUN_ON_ALL_CRATES: &str = "RUSTC_PLUGIN_ALL_TARGETS";
13pub const SPECIFIC_CRATE: &str = "SPECIFIC_CRATE";
14pub const SPECIFIC_TARGET: &str = "SPECIFIC_TARGET";
15pub const CARGO_VERBOSE: &str = "CARGO_VERBOSE";
16
17pub fn cli_main<T: RustcPlugin>(plugin: T) {
19 if env::args().any(|arg| arg == "-V") {
20 println!("{}", plugin.version());
21 return;
22 }
23
24 let metadata = cargo_metadata::MetadataCommand::new()
25 .no_deps()
26 .other_options(["--all-features".to_string(), "--offline".to_string()])
27 .exec()
28 .unwrap();
29 let plugin_subdir = format!("plugin-{}", env!("RUSTC_CHANNEL"));
30 let target_dir = metadata.target_directory.join(plugin_subdir);
31
32 let args = plugin.args(&target_dir);
33
34 let mut cmd = Command::new("cargo");
35 cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit());
36
37 let mut path = env::current_exe()
38 .expect("current executable path invalid")
39 .with_file_name(plugin.driver_name().as_ref());
40
41 if cfg!(windows) {
42 path.set_extension("exe");
43 }
44
45 cmd
46 .env("RUSTC_WORKSPACE_WRAPPER", path)
47 .args(["check", "--target-dir"])
48 .arg(&target_dir);
49
50 if env::var(CARGO_VERBOSE).is_ok() {
51 cmd.arg("-vv");
52 } else {
53 cmd.arg("-q");
54 }
55
56 let workspace_members = metadata
57 .workspace_members
58 .iter()
59 .map(|pkg_id| {
60 metadata
61 .packages
62 .iter()
63 .find(|pkg| &pkg.id == pkg_id)
64 .unwrap()
65 })
66 .collect::<Vec<_>>();
67
68 match args.filter {
69 CrateFilter::CrateContainingFile(file_path) => {
70 only_run_on_file(&mut cmd, file_path, &workspace_members, &target_dir);
71 }
72 CrateFilter::AllCrates | CrateFilter::OnlyWorkspace => {
73 cmd.arg("--all");
74 match args.filter {
75 CrateFilter::AllCrates => {
76 cmd.env(RUN_ON_ALL_CRATES, "");
77 }
78 CrateFilter::OnlyWorkspace => {}
79 CrateFilter::CrateContainingFile(_) => unreachable!(),
80 }
81 }
82 }
83
84 let args_str = serde_json::to_string(&args.args).unwrap();
85 log::debug!("{PLUGIN_ARGS}={args_str}");
86 cmd.env(PLUGIN_ARGS, args_str);
87
88 if workspace_members.iter().any(|pkg| pkg.name == "rustc-main") {
91 cmd.env("CFG_RELEASE", "");
92 }
93
94 plugin.modify_cargo(&mut cmd, &args.args);
95
96 let exit_status = cmd.status().expect("failed to wait for cargo?");
97
98 exit(exit_status.code().unwrap_or(-1));
99}
100
101fn only_run_on_file(
102 cmd: &mut Command,
103 file_path: PathBuf,
104 workspace_members: &[&cargo_metadata::Package],
105 target_dir: &Utf8Path,
106) {
107 let file_path = file_path.canonicalize().unwrap();
109
110 let mut matching = workspace_members
112 .iter()
113 .filter_map(|pkg| {
114 let targets = pkg
115 .targets
116 .iter()
117 .filter(|target| {
118 let src_path = target.src_path.canonicalize().unwrap();
119 log::trace!("Package {} has src path {}", pkg.name, src_path.display());
120 file_path.starts_with(src_path.parent().unwrap())
121 })
122 .collect::<Vec<_>>();
123
124 let target = (match targets.len() {
125 0 => None,
126 1 => Some(targets[0]),
127 _ => {
128 let stem = file_path.file_stem().unwrap().to_string_lossy();
131 let name_matches_stem = targets
132 .clone()
133 .into_iter()
134 .find(|target| target.name == stem);
135
136 name_matches_stem.or_else(|| {
138 let only_bin = targets
139 .iter()
140 .all(|target| !target.kind.contains(&"lib".into()));
141 if only_bin {
146 targets
147 .into_iter()
148 .find(|target| target.kind.contains(&"bin".into()))
149 } else {
150 let kind = (if stem == "main" { "bin" } else { "lib" }).to_string();
151 targets
152 .into_iter()
153 .find(|target| target.kind.contains(&kind))
154 }
155 })
156 }
157 })?;
158
159 Some((pkg, target))
160 })
161 .collect::<Vec<_>>();
162 let (pkg, target) = match matching.len() {
163 0 => panic!("Could not find target for path: {}", file_path.display()),
164 1 => matching.remove(0),
165 _ => panic!("Too many matching targets: {matching:?}"),
166 };
167
168 cmd.arg("-p").arg(format!("{}:{}", pkg.name, pkg.version));
170
171 enum CompileKind {
173 Lib,
174 Bin,
175 Example,
176 Test,
177 Bench,
178 ProcMacro,
179 }
180
181 let kind_str = &target.kind[0];
184 let kind = match kind_str.as_str() {
185 "lib" | "rlib" | "dylib" | "staticlib" | "cdylib" => CompileKind::Lib,
186 "bin" => CompileKind::Bin,
187 "proc-macro" => CompileKind::ProcMacro,
188 "example" => CompileKind::Example,
189 "test" => CompileKind::Test,
190 "bench" => CompileKind::Bench,
191 _ => unreachable!("unexpected cargo crate type: {kind_str}"),
192 };
193
194 match kind {
195 CompileKind::Lib => {
196 let deps_dir = target_dir.join("debug").join("deps");
199 if let Ok(entries) = fs::read_dir(deps_dir) {
200 let prefix = format!("lib{}", pkg.name.replace('-', "_"));
201 for entry in entries {
202 let path = entry.unwrap().path();
203 if let Some(file_name) = path.file_name()
204 && file_name.to_string_lossy().starts_with(&prefix)
205 {
206 fs::remove_file(path).unwrap();
207 }
208 }
209 }
210
211 cmd.arg("--lib");
212 }
213 CompileKind::Bin => {
214 cmd.args(["--bin", &target.name]);
215 }
216 CompileKind::ProcMacro => {}
217 CompileKind::Example => {
218 cmd.args(["--example", &target.name]);
219 }
220 CompileKind::Test => {
221 cmd.args(["--test", &target.name]);
222 }
223 CompileKind::Bench => {
224 cmd.args(["--bench", &target.name]);
225 }
226 }
227
228 cmd.env(
229 SPECIFIC_CRATE,
230 match kind {
231 CompileKind::Lib => &pkg.name,
232 CompileKind::Bin => &pkg.name,
233 CompileKind::Example => &target.name,
234 CompileKind::Test => &target.name,
235 CompileKind::Bench => &target.name,
236 CompileKind::ProcMacro => &pkg.name,
237 }
238 .replace('-', "_"),
239 );
240 cmd.env(SPECIFIC_TARGET, match kind {
241 CompileKind::Bench | CompileKind::Example => "bin",
242 _ => kind_str,
243 });
244
245 log::debug!(
246 "Package: {}, target kind {}, target name {}",
247 pkg.name,
248 kind_str,
249 target.name
250 );
251}