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