]> git.lizzy.rs Git - rust.git/blob - src/main.rs
Pass Clippy args also trough RUSTFLAGS
[rust.git] / src / main.rs
1 #![feature(bool_to_option)]
2 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
3 // warn on lints, that are included in `rust-lang/rust`s bootstrap
4 #![warn(rust_2018_idioms, unused_lifetimes)]
5
6 use rustc_tools_util::VersionInfo;
7 use std::env;
8 use std::ffi::OsString;
9 use std::path::PathBuf;
10 use std::process::{self, Command};
11
12 const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
13
14 Usage:
15     cargo clippy [options] [--] [<opts>...]
16
17 Common options:
18     -h, --help               Print this message
19     -V, --version            Print version info and exit
20
21 Other options are the same as `cargo check`.
22
23 To allow or deny a lint from the command line you can use `cargo clippy --`
24 with:
25
26     -W --warn OPT       Set lint warnings
27     -A --allow OPT      Set lint allowed
28     -D --deny OPT       Set lint denied
29     -F --forbid OPT     Set lint forbidden
30
31 You can use tool lints to allow or deny lints from your code, eg.:
32
33     #[allow(clippy::needless_lifetimes)]
34 "#;
35
36 fn show_help() {
37     println!("{}", CARGO_CLIPPY_HELP);
38 }
39
40 fn show_version() {
41     let version_info = rustc_tools_util::get_version_info!();
42     println!("{}", version_info);
43 }
44
45 pub fn main() {
46     // Check for version and help flags even when invoked as 'cargo-clippy'
47     if env::args().any(|a| a == "--help" || a == "-h") {
48         show_help();
49         return;
50     }
51
52     if env::args().any(|a| a == "--version" || a == "-V") {
53         show_version();
54         return;
55     }
56
57     if let Err(code) = process(env::args().skip(2)) {
58         process::exit(code);
59     }
60 }
61
62 struct ClippyCmd {
63     unstable_options: bool,
64     cargo_subcommand: &'static str,
65     args: Vec<String>,
66     rustflags: Option<String>,
67     clippy_args: Option<String>,
68 }
69
70 impl ClippyCmd {
71     fn new<I>(mut old_args: I, rustflags: Option<String>) -> Self
72     where
73         I: Iterator<Item = String>,
74     {
75         let mut cargo_subcommand = "check";
76         let mut unstable_options = false;
77         let mut args = vec![];
78
79         for arg in old_args.by_ref() {
80             match arg.as_str() {
81                 "--fix" => {
82                     cargo_subcommand = "fix";
83                     continue;
84                 },
85                 "--" => break,
86                 // Cover -Zunstable-options and -Z unstable-options
87                 s if s.ends_with("unstable-options") => unstable_options = true,
88                 _ => {},
89             }
90
91             args.push(arg);
92         }
93
94         if cargo_subcommand == "fix" && !unstable_options {
95             panic!("Usage of `--fix` requires `-Z unstable-options`");
96         }
97
98         // Run the dogfood tests directly on nightly cargo. This is required due
99         // to a bug in rustup.rs when running cargo on custom toolchains. See issue #3118.
100         if env::var_os("CLIPPY_DOGFOOD").is_some() && cfg!(windows) {
101             args.insert(0, "+nightly".to_string());
102         }
103
104         let mut clippy_args = old_args.collect::<Vec<String>>().join(" ");
105         if cargo_subcommand == "fix" && !clippy_args.contains("--no-deps") {
106             clippy_args = format!("{} --no-deps", clippy_args);
107         }
108
109         let has_args = !clippy_args.is_empty();
110         ClippyCmd {
111             unstable_options,
112             cargo_subcommand,
113             args,
114             rustflags: has_args
115                 .then(|| rustflags.map_or_else(|| clippy_args.clone(), |flags| format!("{} {}", clippy_args, flags))),
116             clippy_args: has_args.then_some(clippy_args),
117         }
118     }
119
120     fn path_env(&self) -> &'static str {
121         if self.unstable_options {
122             "RUSTC_WORKSPACE_WRAPPER"
123         } else {
124             "RUSTC_WRAPPER"
125         }
126     }
127
128     fn path() -> PathBuf {
129         let mut path = env::current_exe()
130             .expect("current executable path invalid")
131             .with_file_name("clippy-driver");
132
133         if cfg!(windows) {
134             path.set_extension("exe");
135         }
136
137         path
138     }
139
140     fn target_dir() -> Option<(&'static str, OsString)> {
141         env::var_os("CLIPPY_DOGFOOD")
142             .map(|_| {
143                 env::var_os("CARGO_MANIFEST_DIR").map_or_else(
144                     || std::ffi::OsString::from("clippy_dogfood"),
145                     |d| {
146                         std::path::PathBuf::from(d)
147                             .join("target")
148                             .join("dogfood")
149                             .into_os_string()
150                     },
151                 )
152             })
153             .map(|p| ("CARGO_TARGET_DIR", p))
154     }
155
156     fn into_std_cmd(self) -> Command {
157         let mut cmd = Command::new("cargo");
158
159         cmd.env(self.path_env(), Self::path())
160             .envs(ClippyCmd::target_dir())
161             .arg(self.cargo_subcommand)
162             .args(&self.args);
163
164         // HACK: pass Clippy args to the driver *also* through RUSTFLAGS.
165         // This guarantees that new builds will be triggered when Clippy flags change.
166         if let (Some(clippy_args), Some(rustflags)) = (self.clippy_args, self.rustflags) {
167             cmd.env("CLIPPY_ARGS", clippy_args);
168             cmd.env("RUSTFLAGS", rustflags);
169         }
170
171         cmd
172     }
173 }
174
175 fn process<I>(old_args: I) -> Result<(), i32>
176 where
177     I: Iterator<Item = String>,
178 {
179     let cmd = ClippyCmd::new(old_args, env::var("RUSTFLAGS").ok());
180
181     let mut cmd = cmd.into_std_cmd();
182
183     let exit_status = cmd
184         .spawn()
185         .expect("could not run cargo")
186         .wait()
187         .expect("failed to wait for cargo?");
188
189     if exit_status.success() {
190         Ok(())
191     } else {
192         Err(exit_status.code().unwrap_or(-1))
193     }
194 }
195
196 #[cfg(test)]
197 mod tests {
198     use super::ClippyCmd;
199
200     #[test]
201     #[should_panic]
202     fn fix_without_unstable() {
203         let args = "cargo clippy --fix".split_whitespace().map(ToString::to_string);
204         let _ = ClippyCmd::new(args, None);
205     }
206
207     #[test]
208     fn fix_unstable() {
209         let args = "cargo clippy --fix -Zunstable-options"
210             .split_whitespace()
211             .map(ToString::to_string);
212         let cmd = ClippyCmd::new(args, None);
213
214         assert_eq!("fix", cmd.cargo_subcommand);
215         assert_eq!("RUSTC_WORKSPACE_WRAPPER", cmd.path_env());
216         assert!(cmd.args.iter().any(|arg| arg.ends_with("unstable-options")));
217     }
218
219     #[test]
220     fn fix_implies_no_deps() {
221         let args = "cargo clippy --fix -Zunstable-options"
222             .split_whitespace()
223             .map(ToString::to_string);
224         let cmd = ClippyCmd::new(args, None);
225
226         assert!(cmd.clippy_args.unwrap().contains("--no-deps"));
227     }
228
229     #[test]
230     fn no_deps_not_duplicated_with_fix() {
231         let args = "cargo clippy --fix -Zunstable-options -- --no-deps"
232             .split_whitespace()
233             .map(ToString::to_string);
234         let cmd = ClippyCmd::new(args, None);
235
236         assert_eq!(1, cmd.clippy_args.unwrap().matches("--no-deps").count());
237     }
238
239     #[test]
240     fn check() {
241         let args = "cargo clippy".split_whitespace().map(ToString::to_string);
242         let cmd = ClippyCmd::new(args, None);
243
244         assert_eq!("check", cmd.cargo_subcommand);
245         assert_eq!("RUSTC_WRAPPER", cmd.path_env());
246     }
247
248     #[test]
249     fn check_unstable() {
250         let args = "cargo clippy -Zunstable-options"
251             .split_whitespace()
252             .map(ToString::to_string);
253         let cmd = ClippyCmd::new(args, None);
254
255         assert_eq!("check", cmd.cargo_subcommand);
256         assert_eq!("RUSTC_WORKSPACE_WRAPPER", cmd.path_env());
257     }
258
259     #[test]
260     fn clippy_args_into_rustflags() {
261         let args = "cargo clippy -- -W clippy::as_conversions"
262             .split_whitespace()
263             .map(ToString::to_string);
264         let rustflags = None;
265         let cmd = ClippyCmd::new(args, rustflags);
266
267         assert_eq!("-W clippy::as_conversions", cmd.rustflags.unwrap());
268     }
269
270     #[test]
271     fn clippy_args_respect_existing_rustflags() {
272         let args = "cargo clippy -- -D clippy::await_holding_lock"
273             .split_whitespace()
274             .map(ToString::to_string);
275         let rustflags = Some(r#"--cfg feature="some_feat""#.into());
276         let cmd = ClippyCmd::new(args, rustflags);
277
278         assert_eq!(
279             r#"-D clippy::await_holding_lock --cfg feature="some_feat""#,
280             cmd.rustflags.unwrap()
281         );
282     }
283
284     #[test]
285     fn no_env_change_if_no_clippy_args() {
286         let args = "cargo clippy".split_whitespace().map(ToString::to_string);
287         let rustflags = Some(r#"--cfg feature="some_feat""#.into());
288         let cmd = ClippyCmd::new(args, rustflags);
289
290         assert!(cmd.clippy_args.is_none());
291         assert!(cmd.rustflags.is_none());
292     }
293
294     #[test]
295     fn no_env_change_if_no_clippy_args_nor_rustflags() {
296         let args = "cargo clippy".split_whitespace().map(ToString::to_string);
297         let rustflags = None;
298         let cmd = ClippyCmd::new(args, rustflags);
299
300         assert!(cmd.clippy_args.is_none());
301         assert!(cmd.rustflags.is_none());
302     }
303 }