]> git.lizzy.rs Git - rust.git/blob - src/main.rs
remove nondeterminism by adjusting thresholds
[rust.git] / src / main.rs
1 // error-pattern:yummy
2 #![feature(box_syntax)]
3 #![feature(rustc_private)]
4 #![feature(static_in_const)]
5
6 #![allow(unknown_lints, missing_docs_in_private_items)]
7
8 extern crate clippy_lints;
9 extern crate getopts;
10 extern crate rustc;
11 extern crate rustc_driver;
12 extern crate rustc_errors;
13 extern crate rustc_plugin;
14 extern crate syntax;
15
16 use rustc_driver::{driver, CompilerCalls, RustcDefaultCalls, Compilation};
17 use rustc::session::{config, Session};
18 use rustc::session::config::{Input, ErrorOutputType};
19 use std::path::PathBuf;
20 use std::process::{self, Command};
21 use syntax::ast;
22 use std::io::{self, Write};
23
24 use clippy_lints::utils::cargo;
25
26 struct ClippyCompilerCalls {
27     default: RustcDefaultCalls,
28     run_lints: bool,
29 }
30
31 impl ClippyCompilerCalls {
32     fn new(run_lints: bool) -> Self {
33         ClippyCompilerCalls {
34             default: RustcDefaultCalls,
35             run_lints: run_lints,
36         }
37     }
38 }
39
40 impl<'a> CompilerCalls<'a> for ClippyCompilerCalls {
41     fn early_callback(
42         &mut self, matches: &getopts::Matches, sopts: &config::Options, cfg: &ast::CrateConfig,
43         descriptions: &rustc_errors::registry::Registry, output: ErrorOutputType
44     ) -> Compilation {
45         self.default.early_callback(matches, sopts, cfg, descriptions, output)
46     }
47     fn no_input(
48         &mut self, matches: &getopts::Matches, sopts: &config::Options, cfg: &ast::CrateConfig, odir: &Option<PathBuf>,
49         ofile: &Option<PathBuf>, descriptions: &rustc_errors::registry::Registry
50     ) -> Option<(Input, Option<PathBuf>)> {
51         self.default.no_input(matches, sopts, cfg, odir, ofile, descriptions)
52     }
53     fn late_callback(
54         &mut self, matches: &getopts::Matches, sess: &Session, input: &Input, odir: &Option<PathBuf>,
55         ofile: &Option<PathBuf>
56     ) -> Compilation {
57         self.default.late_callback(matches, sess, input, odir, ofile)
58     }
59     fn build_controller(&mut self, sess: &Session, matches: &getopts::Matches) -> driver::CompileController<'a> {
60         let mut control = self.default.build_controller(sess, matches);
61
62         if self.run_lints {
63             let old = std::mem::replace(&mut control.after_parse.callback, box |_| {});
64             control.after_parse.callback = Box::new(move |state| {
65                 {
66                     let mut registry = rustc_plugin::registry::Registry::new(state.session,
67                                                                              state.krate
68                                                                                  .as_ref()
69                                                                                  .expect("at this compilation stage \
70                                                                                           the krate must be parsed")
71                                                                                  .span);
72                     registry.args_hidden = Some(Vec::new());
73                     clippy_lints::register_plugins(&mut registry);
74
75                     let rustc_plugin::registry::Registry { early_lint_passes,
76                                                            late_lint_passes,
77                                                            lint_groups,
78                                                            llvm_passes,
79                                                            attributes,
80                                                            mir_passes,
81                                                            .. } = registry;
82                     let sess = &state.session;
83                     let mut ls = sess.lint_store.borrow_mut();
84                     for pass in early_lint_passes {
85                         ls.register_early_pass(Some(sess), true, pass);
86                     }
87                     for pass in late_lint_passes {
88                         ls.register_late_pass(Some(sess), true, pass);
89                     }
90
91                     for (name, to) in lint_groups {
92                         ls.register_group(Some(sess), true, name, to);
93                     }
94
95                     sess.plugin_llvm_passes.borrow_mut().extend(llvm_passes);
96                     sess.mir_passes.borrow_mut().extend(mir_passes);
97                     sess.plugin_attributes.borrow_mut().extend(attributes);
98                 }
99                 old(state);
100             });
101         }
102
103         control
104     }
105 }
106
107 use std::path::Path;
108
109 const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
110
111 Usage:
112     cargo clippy [options] [--] [<opts>...]
113
114 Common options:
115     -h, --help               Print this message
116     --features               Features to compile for the package
117     -V, --version            Print version info and exit
118
119 Other options are the same as `cargo rustc`.
120
121 To allow or deny a lint from the command line you can use `cargo clippy --`
122 with:
123
124     -W --warn OPT       Set lint warnings
125     -A --allow OPT      Set lint allowed
126     -D --deny OPT       Set lint denied
127     -F --forbid OPT     Set lint forbidden
128
129 The feature `cargo-clippy` is automatically defined for convenience. You can use
130 it to allow or deny lints from the code, eg.:
131
132     #[cfg_attr(feature = "cargo-clippy", allow(needless_lifetimes))]
133 "#;
134
135 #[allow(print_stdout)]
136 fn show_help() {
137     println!("{}", CARGO_CLIPPY_HELP);
138 }
139
140 #[allow(print_stdout)]
141 fn show_version() {
142     println!("{}", env!("CARGO_PKG_VERSION"));
143 }
144
145 pub fn main() {
146     use std::env;
147
148     if env::var("CLIPPY_DOGFOOD").map(|_| true).unwrap_or(false) {
149         panic!("yummy");
150     }
151
152     // Check for version and help flags even when invoked as 'cargo-clippy'
153     if std::env::args().any(|a| a == "--help" || a == "-h") {
154         show_help();
155         return;
156     }
157     if std::env::args().any(|a| a == "--version" || a == "-V") {
158         show_version();
159         return;
160     }
161
162     let dep_path = env::current_dir().expect("current dir is not readable").join("target").join("debug").join("deps");
163
164     if let Some("clippy") = std::env::args().nth(1).as_ref().map(AsRef::as_ref) {
165         // this arm is executed on the initial call to `cargo clippy`
166
167         let manifest_path_arg = std::env::args().skip(2).find(|val| val.starts_with("--manifest-path="));
168
169         let mut metadata = if let Ok(metadata) = cargo::metadata(manifest_path_arg.as_ref().map(AsRef::as_ref)) {
170             metadata
171         } else {
172             let _ = io::stderr().write_fmt(format_args!("error: Could not obtain cargo metadata."));
173             process::exit(101);
174         };
175
176         assert_eq!(metadata.version, 1);
177
178         let manifest_path = manifest_path_arg.map(|arg| PathBuf::from(Path::new(&arg["--manifest-path=".len()..])));
179
180         let current_dir = std::env::current_dir();
181
182         let package_index = metadata.packages
183             .iter()
184             .position(|package| {
185                 let package_manifest_path = Path::new(&package.manifest_path);
186                 if let Some(ref manifest_path) = manifest_path {
187                     package_manifest_path == manifest_path
188                 } else {
189                     let current_dir = current_dir.as_ref().expect("could not read current directory");
190                     let package_manifest_directory = package_manifest_path.parent()
191                         .expect("could not find parent directory of package manifest");
192                     package_manifest_directory == current_dir
193                 }
194             })
195             .expect("could not find matching package");
196         let package = metadata.packages.remove(package_index);
197         for target in package.targets {
198             let args = std::env::args().skip(2);
199             if let Some(first) = target.kind.get(0) {
200                 if target.kind.len() > 1 || first.ends_with("lib") {
201                     if let Err(code) = process(std::iter::once("--lib".to_owned()).chain(args), &dep_path) {
202                         std::process::exit(code);
203                     }
204                 } else if ["bin", "example", "test", "bench"].contains(&&**first) {
205                     if let Err(code) = process(vec![format!("--{}", first), target.name].into_iter().chain(args),
206                                                &dep_path) {
207                         std::process::exit(code);
208                     }
209                 }
210             } else {
211                 panic!("badly formatted cargo metadata: target::kind is an empty array");
212             }
213         }
214     } else {
215         // this arm is executed when cargo-clippy runs `cargo rustc` with the `RUSTC` env var set to itself
216
217         let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
218         let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
219         let sys_root = if let (Some(home), Some(toolchain)) = (home, toolchain) {
220             format!("{}/toolchains/{}", home, toolchain)
221         } else {
222             option_env!("SYSROOT")
223                 .map(|s| s.to_owned())
224                 .or(Command::new("rustc")
225                     .arg("--print")
226                     .arg("sysroot")
227                     .output()
228                     .ok()
229                     .and_then(|out| String::from_utf8(out.stdout).ok())
230                     .map(|s| s.trim().to_owned()))
231                 .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust")
232         };
233
234         // this conditional check for the --sysroot flag is there so users can call `cargo-clippy` directly
235         // without having to pass --sysroot or anything
236         let mut args: Vec<String> = if env::args().any(|s| s == "--sysroot") {
237             env::args().collect()
238         } else {
239             env::args().chain(Some("--sysroot".to_owned())).chain(Some(sys_root)).collect()
240         };
241
242         // this check ensures that dependencies are built but not linted and the final crate is
243         // linted but not built
244         let clippy_enabled = env::args().any(|s| s == "-Zno-trans");
245
246         if clippy_enabled {
247             args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-clippy""#.to_owned()]);
248         }
249
250         let mut ccc = ClippyCompilerCalls::new(clippy_enabled);
251         let (result, _) = rustc_driver::run_compiler(&args, &mut ccc, None, None);
252
253         if let Err(err_count) = result {
254             if err_count > 0 {
255                 std::process::exit(1);
256             }
257         }
258     }
259 }
260
261 fn process<P, I>(old_args: I, dep_path: P) -> Result<(), i32>
262     where P: AsRef<Path>,
263           I: Iterator<Item = String>
264 {
265
266     let mut args = vec!["rustc".to_owned()];
267
268     let mut found_dashes = false;
269     for arg in old_args {
270         found_dashes |= arg == "--";
271         args.push(arg);
272     }
273     if !found_dashes {
274         args.push("--".to_owned());
275     }
276     args.push("-L".to_owned());
277     args.push(dep_path.as_ref().to_string_lossy().into_owned());
278     args.push("-Zno-trans".to_owned());
279     args.push("--cfg".to_owned());
280     args.push(r#"feature="cargo-clippy""#.to_owned());
281
282     let path = std::env::current_exe().expect("current executable path invalid");
283     let exit_status = std::process::Command::new("cargo")
284         .args(&args)
285         .env("RUSTC", path)
286         .spawn()
287         .expect("could not run cargo")
288         .wait()
289         .expect("failed to wait for cargo?");
290
291     if exit_status.success() {
292         Ok(())
293     } else {
294         Err(exit_status.code().unwrap_or(-1))
295     }
296 }