1use std::io::{self, BufReader, Write};
2use std::path::Path;
3use std::process::{Command, Stdio};
4use std::{env, str};
5
6use anyhow::{bail, Result};
7use cargo_metadata::camino::Utf8Component;
8use cargo_metadata::{Artifact, CargoOpt, Message, Metadata, MetadataCommand};
9use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand};
10use rustc_cfg::Cfg;
11
12pub use tool::Tool;
13
14mod llvm;
15mod postprocess;
16mod rustc;
17mod tool;
18
19fn search<'p>(path: &'p Path, file: &str) -> Option<&'p Path> {
21 path.ancestors().find(|dir| dir.join(file).exists())
22}
23
24fn parse<T>(path: &Path) -> Result<T>
25where
26 T: for<'de> serde::Deserialize<'de>,
27{
28 use std::fs::File;
29 use std::io::Read;
30 use toml::de;
31
32 let mut s = String::new();
33 File::open(path)?.read_to_string(&mut s)?;
34 Ok(de::from_str(&s)?)
35}
36
37pub struct Context {
40 cfg: Cfg,
41 target: String,
43}
44
45impl Context {
46 fn from_artifact(metadata: Metadata, artifact: &Artifact) -> Result<Self> {
49 let target_path = artifact.filenames[0].strip_prefix(metadata.target_directory)?;
58 let target_name = if let Some(Utf8Component::Normal(path)) = target_path.components().next()
59 {
60 if path == "debug" || path == "release" {
62 rustc_version::version_meta()?.host
64 } else {
65 path.to_string()
67 }
68 } else {
69 unreachable!();
70 };
71
72 Self::from_target_name(&target_name)
73 }
74
75 fn from_flag(metadata: Metadata, target_flag: Option<&str>) -> Result<Self> {
78 let host_target_name = rustc_version::version_meta()?.host;
79
80 let mut config_target_name = None;
82 let config: toml::Value;
83
84 if let Some(path) = search(metadata.workspace_root.as_std_path(), ".cargo/config") {
85 config = parse(&path.join(".cargo/config"))?;
86 config_target_name = config
87 .get("build")
88 .and_then(|build| build.get("target"))
89 .and_then(|target| target.as_str());
90 }
91
92 let target_name = target_flag
94 .or(config_target_name)
95 .unwrap_or(&host_target_name);
96
97 Self::from_target_name(target_name)
98 }
99
100 fn from_target_name(target_name: &str) -> Result<Self> {
101 let cfg = Cfg::of(target_name)?;
102
103 Ok(Context {
104 cfg,
105 target: target_name.to_string(),
106 })
107 }
108}
109
110enum BuildType<'a> {
111 Any,
112 Bin(&'a str),
113 Example(&'a str),
114 Test(&'a str),
115 Bench(&'a str),
116 Lib,
117}
118
119impl BuildType<'_> {
120 fn matches(&self, artifact: &Artifact) -> bool {
121 match self {
122 BuildType::Bin(target_name)
123 | BuildType::Example(target_name)
124 | BuildType::Test(target_name)
125 | BuildType::Bench(target_name) => {
126 artifact.target.name == *target_name && artifact.executable.is_some()
127 }
128 BuildType::Any => artifact
133 .target
134 .kind
135 .iter()
136 .any(|s| s == "bin" || s == "example"),
137 BuildType::Lib => artifact.target.kind.iter().any(|s| {
139 s != "bin" && s != "example" && s != "test" && s != "custom-build" && s != "bench"
140 }),
141 }
142 }
143}
144
145fn args(tool: Tool, examples: Option<&str>) -> ArgMatches {
146 let name = tool.name();
147 let about = format!("Proxy for the `llvm-{name}` tool shipped with the Rust toolchain.");
148 let after_help = format!(
149 "\
150The arguments specified *after* the `--` will be passed to the proxied tool invocation.
151
152To see all the flags the proxied tool accepts run `cargo-{} -- --help`.{}",
153 name,
154 examples.unwrap_or("")
155 );
156
157 let app = ClapCommand::new(format!("cargo-{name}"))
158 .about(about)
159 .version(env!("CARGO_PKG_VERSION"))
160 .args(&[
163 Arg::new("binary-name").hide(true),
164 Arg::new("verbose")
165 .long("verbose")
166 .short('v')
167 .action(ArgAction::Count)
168 .help("Use verbose output (-vv cargo verbose or -vvv for build.rs output)"),
169 Arg::new("args")
170 .last(true)
171 .num_args(1..)
172 .help("The arguments to be proxied to the tool"),
173 ])
174 .after_help(after_help);
175
176 if tool.needs_build() {
177 app.args(&[
178 Arg::new("quiet")
179 .long("quiet")
180 .short('q')
181 .action(ArgAction::SetTrue)
182 .help("Don't print build output from `cargo build`"),
183 Arg::new("package")
184 .long("package")
185 .short('p')
186 .value_name("SPEC")
187 .help("Package to build (see `cargo help pkgid`)"),
188 Arg::new("jobs")
189 .long("jobs")
190 .short('j')
191 .value_name("N")
192 .help("Number of parallel jobs, defaults to # of CPUs"),
193 Arg::new("lib")
194 .long("lib")
195 .action(ArgAction::SetTrue)
196 .conflicts_with_all(["bin", "example", "test", "bench"])
197 .help("Build only this package's library"),
198 Arg::new("bin")
199 .long("bin")
200 .value_name("NAME")
201 .conflicts_with_all(["lib", "example", "test", "bench"])
202 .help("Build only the specified binary"),
203 Arg::new("example")
204 .long("example")
205 .value_name("NAME")
206 .conflicts_with_all(["lib", "bin", "test", "bench"])
207 .help("Build only the specified example"),
208 Arg::new("test")
209 .long("test")
210 .value_name("NAME")
211 .conflicts_with_all(["lib", "bin", "example", "bench"])
212 .help("Build only the specified test target"),
213 Arg::new("bench")
214 .long("bench")
215 .value_name("NAME")
216 .conflicts_with_all(["lib", "bin", "example", "test"])
217 .help("Build only the specified bench target"),
218 Arg::new("release")
219 .long("release")
220 .action(ArgAction::SetTrue)
221 .help("Build artifacts in release mode, with optimizations"),
222 Arg::new("profile")
223 .long("profile")
224 .value_name("PROFILE-NAME")
225 .help("Build artifacts with the specified profile"),
226 Arg::new("manifest-path")
227 .long("manifest-path")
228 .help("Path to Cargo.tom"),
229 Arg::new("features")
230 .long("features")
231 .short('F')
232 .value_name("FEATURES")
233 .help("Space-separated list of features to activate"),
234 Arg::new("all-features")
235 .long("all-features")
236 .action(ArgAction::SetTrue)
237 .help("Activate all available features"),
238 Arg::new("no-default-features")
239 .long("no-default-features")
240 .action(ArgAction::SetTrue)
241 .help("Do not activate the `default` feature"),
242 Arg::new("target")
243 .long("target")
244 .value_name("TRIPLE")
245 .help("Target triple for which the code is compiled"),
246 Arg::new("config")
247 .long("config")
248 .value_name("CONFIG")
249 .help("Override a configuration value"),
250 Arg::new("color")
251 .long("color")
252 .action(ArgAction::Set)
253 .value_parser(clap::builder::PossibleValuesParser::new([
254 "auto", "always", "never",
255 ]))
256 .help("Coloring: auto, always, never"),
257 Arg::new("frozen")
258 .long("frozen")
259 .action(ArgAction::SetTrue)
260 .help("Require Cargo.lock and cache are up to date"),
261 Arg::new("locked")
262 .long("locked")
263 .action(ArgAction::SetTrue)
264 .help("Require Cargo.lock is up to date"),
265 Arg::new("offline")
266 .long("offline")
267 .action(ArgAction::SetTrue)
268 .help("Run without accessing the network"),
269 Arg::new("unstable-features")
270 .short('Z')
271 .action(ArgAction::Append)
272 .value_name("FLAG")
273 .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details"),
274 ])
275 .get_matches()
276 } else {
277 app.get_matches()
278 }
279}
280
281pub fn run(tool: Tool, matches: ArgMatches) -> Result<i32> {
282 let mut metadata_command = MetadataCommand::new();
283 if let Some(features) = matches.get_many::<String>("features") {
284 metadata_command.features(CargoOpt::SomeFeatures(
285 features.map(|s| s.to_owned()).collect(),
286 ));
287 }
288 if matches.get_flag("no-default-features") {
289 metadata_command.features(CargoOpt::NoDefaultFeatures);
290 }
291 if matches.get_flag("all-features") {
292 metadata_command.features(CargoOpt::AllFeatures);
293 }
294 if let Some(path) = matches.get_one::<String>("manifest-path") {
295 metadata_command.manifest_path(path);
296 }
297 let metadata = metadata_command.exec()?;
298 if metadata.workspace_members.is_empty() {
299 bail!("Unable to find workspace members");
300 }
301
302 let mut tool_args = vec![];
303 if let Some(args) = matches.get_many::<String>("args") {
304 tool_args.extend(args.map(|s| s.as_str()));
305 }
306
307 let tool_help = tool_args.first() == Some(&"--help");
308
309 let target_artifact = if tool.needs_build() && !tool_help {
310 cargo_build(&matches, &metadata)?
311 } else {
312 None
313 };
314
315 let mut lltool = Command::new(format!("rust-{}", tool.name()));
316
317 if tool == Tool::Objdump {
318 let ctxt = if let Some(artifact) = &target_artifact {
319 Context::from_artifact(metadata, artifact)?
320 } else {
321 Context::from_flag(
322 metadata,
323 matches.get_one::<String>("target").map(|s| s.as_str()),
324 )?
325 };
326
327 let arch_name = llvm::arch_name(&ctxt.cfg, &ctxt.target);
328
329 if arch_name == "thumb" {
330 lltool.args(["--triple", &ctxt.target]);
333 } else {
334 lltool.args(&[format!("--arch-name={arch_name}")]);
335 }
336 }
337
338 if let Tool::Readobj = tool {
340 lltool.arg("--elf-output-style=GNU");
343 }
344
345 if tool.needs_build() {
346 if let Some(artifact) = &target_artifact {
348 let file = match &artifact.executable {
349 Some(val) => val,
351 None => &artifact.filenames[0],
357 };
358
359 match tool {
360 Tool::Ar | Tool::As | Tool::Cov | Tool::Lld | Tool::Profdata => {}
362 Tool::Objdump | Tool::Nm | Tool::Readobj | Tool::Size => {
367 lltool
368 .current_dir(file.parent().unwrap())
369 .arg(file.file_name().unwrap());
370 }
371 Tool::Objcopy | Tool::Strip => {
372 lltool.arg(file);
373 }
374 }
375 }
376 }
377
378 lltool.args(&tool_args);
380
381 if matches.get_count("verbose") > 0 {
382 eprintln!("{lltool:?}");
383 }
384
385 let stdout = io::stdout();
386 let mut stdout = stdout.lock();
387
388 let output = lltool.stderr(Stdio::inherit()).output()?;
389
390 let processed_output = match tool {
392 Tool::Ar
393 | Tool::As
394 | Tool::Cov
395 | Tool::Lld
396 | Tool::Objcopy
397 | Tool::Profdata
398 | Tool::Strip => output.stdout.into(),
399 Tool::Nm | Tool::Objdump | Tool::Readobj => postprocess::demangle(&output.stdout),
400 Tool::Size => postprocess::size(&output.stdout),
401 };
402
403 stdout.write_all(&processed_output)?;
404
405 if output.status.success() {
406 Ok(0)
407 } else {
408 Ok(output.status.code().unwrap_or(1))
409 }
410}
411
412fn cargo_build(matches: &ArgMatches, metadata: &Metadata) -> Result<Option<Artifact>> {
413 let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
414 let mut cargo = Command::new(cargo);
415 cargo.arg("build");
416
417 let (build_type, verbose) = cargo_build_args(matches, &mut cargo);
418 let quiet = matches.get_flag("quiet");
419
420 cargo.arg("--message-format=json-diagnostic-rendered-ansi");
421 cargo.stdout(Stdio::piped());
422
423 if verbose > 0 {
424 eprintln!("{cargo:?}");
425 }
426
427 let mut child = cargo.spawn()?;
428 let stdout = BufReader::new(child.stdout.take().expect("Pipe to cargo process failed"));
429
430 let messages = Message::parse_stream(stdout).collect::<Vec<_>>();
432
433 let status = child.wait()?;
434
435 let mut target_artifact: Option<Artifact> = None;
436 for message in messages {
437 match message? {
438 Message::CompilerArtifact(artifact) => {
439 if metadata.workspace_members.contains(&artifact.package_id)
440 && build_type.matches(&artifact)
441 {
442 if target_artifact.is_some() {
443 bail!("Can only have one matching artifact but found several");
444 }
445
446 target_artifact = Some(artifact);
447 }
448 }
449 Message::CompilerMessage(msg) => {
450 if !quiet || verbose > 1 {
451 if let Some(rendered) = msg.message.rendered {
452 print!("{rendered}");
453 }
454 }
455 }
456 _ => (),
457 }
458 }
459
460 if !status.success() {
461 bail!("Failed to parse crate metadata");
462 }
463
464 if target_artifact.is_none() {
465 bail!("Could not determine the wanted artifact");
466 }
467
468 Ok(target_artifact)
469}
470
471fn cargo_build_args<'a>(matches: &'a ArgMatches, cargo: &mut Command) -> (BuildType<'a>, u64) {
472 if matches.get_flag("quiet") {
473 cargo.arg("--quiet");
474 }
475
476 if let Some(package) = matches.get_one::<String>("package") {
477 cargo.arg("--package");
478 cargo.arg(package);
479 }
480
481 if let Some(config) = matches.get_many::<String>("config") {
482 for c in config {
483 cargo.args(["--config", c]);
484 }
485 }
486
487 if let Some(jobs) = matches.get_one::<String>("jobs") {
488 cargo.arg("-j");
489 cargo.arg(jobs);
490 }
491
492 let build_type = if matches.get_flag("lib") {
493 cargo.args(["--lib"]);
494 BuildType::Lib
495 } else if let Some(bin_name) = matches.get_one::<String>("bin") {
496 cargo.args(["--bin", bin_name]);
497 BuildType::Bin(bin_name)
498 } else if let Some(example_name) = matches.get_one::<String>("example") {
499 cargo.args(["--example", example_name]);
500 BuildType::Example(example_name)
501 } else if let Some(test_name) = matches.get_one::<String>("test") {
502 cargo.args(["--test", test_name]);
503 BuildType::Test(test_name)
504 } else if let Some(bench_name) = matches.get_one::<String>("bench") {
505 cargo.args(["--bench", bench_name]);
506 BuildType::Bench(bench_name)
507 } else {
508 BuildType::Any
509 };
510
511 if matches.get_flag("release") {
512 cargo.arg("--release");
513 }
514
515 if let Some(profile) = matches.get_one::<String>("profile") {
516 cargo.arg("--profile");
517 cargo.arg(profile);
518 }
519
520 if let Some(manifest_path) = matches.get_one::<String>("manifest-path") {
521 cargo.args(["--manifest-path", manifest_path]);
522 }
523
524 if let Some(features) = matches.get_many::<String>("features") {
525 for feature in features {
526 cargo.args(["--features", feature]);
527 }
528 }
529 if matches.get_flag("no-default-features") {
530 cargo.arg("--no-default-features");
531 }
532 if matches.get_flag("all-features") {
533 cargo.arg("--all-features");
534 }
535
536 if let Some(target) = matches.get_one::<String>("target") {
539 cargo.args(["--target", target]);
540 }
541
542 let verbose = matches.get_count("verbose") as u64;
543 if verbose > 1 {
544 cargo.arg(format!("-{}", "v".repeat((verbose - 1) as usize)));
545 }
546
547 if let Some(color) = matches.get_one::<String>("color") {
548 cargo.arg("--color");
549 cargo.arg(color);
550 }
551
552 if matches.get_flag("frozen") {
553 cargo.arg("--frozen");
554 }
555
556 if matches.get_flag("locked") {
557 cargo.arg("--locked");
558 }
559
560 if matches.get_flag("offline") {
561 cargo.arg("--offline");
562 }
563
564 if let Some(unstable_features) = matches.get_many::<String>("unstable-features") {
565 for unstable_feature in unstable_features {
566 cargo.args(["-Z", unstable_feature]);
567 }
568 }
569
570 (build_type, verbose)
571}