1use std::process::Command;
2
3use anyhow::{bail, Context, Result};
4use clap::{Parser, Subcommand};
5
6#[derive(Parser)]
7#[command(name = "cargo xtask")]
8struct Args {
9 #[command(subcommand)]
10 command: CliCommand,
11}
12
13#[derive(Subcommand)]
14enum CliCommand {
15 /// Runs `cargo clippy`.
16 Clippy(ClippyArgs),
17}
18
19fn main() -> Result<()> {
20 let args = Args::parse();
21
22 match args.command {
23 CliCommand::Clippy(args) => run_clippy(args),
24 }
25}
26
27#[derive(Parser)]
28struct ClippyArgs {
29 /// Automatically apply lint suggestions (`clippy --fix`).
30 #[arg(long)]
31 fix: bool,
32
33 /// The package to run Clippy against (`cargo -p <PACKAGE> clippy`).
34 #[arg(long, short)]
35 package: Option<String>,
36}
37
38fn run_clippy(args: ClippyArgs) -> Result<()> {
39 let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
40
41 let mut clippy_command = Command::new(&cargo);
42 clippy_command.arg("clippy");
43
44 if let Some(package) = args.package.as_ref() {
45 clippy_command.args(["--package", package]);
46 } else {
47 clippy_command.arg("--workspace");
48 }
49
50 clippy_command
51 .arg("--release")
52 .arg("--all-targets")
53 .arg("--all-features");
54
55 if args.fix {
56 clippy_command.arg("--fix");
57 }
58
59 clippy_command.arg("--");
60
61 // Deny all warnings.
62 // We don't do this yet on Windows, as it still has some warnings present.
63 // todo(windows)
64 #[cfg(not(target_os = "windows"))]
65 clippy_command.args(["--deny", "warnings"]);
66
67 eprintln!(
68 "running: {cargo} {}",
69 clippy_command
70 .get_args()
71 .map(|arg| arg.to_str().unwrap())
72 .collect::<Vec<_>>()
73 .join(" ")
74 );
75
76 let exit_status = clippy_command
77 .spawn()
78 .context("failed to spawn child process")?
79 .wait()
80 .context("failed to wait for child process")?;
81
82 if !exit_status.success() {
83 bail!("clippy failed: {}", exit_status);
84 }
85
86 Ok(())
87}