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.arg("--release").arg("--all-features");
51
52 if args.fix {
53 clippy_command.arg("--fix");
54 }
55
56 clippy_command.arg("--");
57
58 // Deny all warnings.
59 // We don't do this yet on Windows, as it still has some warnings present.
60 // todo(windows)
61 #[cfg(not(target_os = "windows"))]
62 clippy_command.args(["--deny", "warnings"]);
63
64 eprintln!(
65 "running: {cargo} {}",
66 clippy_command
67 .get_args()
68 .map(|arg| arg.to_str().unwrap())
69 .collect::<Vec<_>>()
70 .join(" ")
71 );
72
73 let exit_status = clippy_command
74 .spawn()
75 .context("failed to spawn child process")?
76 .wait()
77 .context("failed to wait for child process")?;
78
79 if !exit_status.success() {
80 bail!("clippy failed: {}", exit_status);
81 }
82
83 Ok(())
84}