]> git.lizzy.rs Git - rust.git/blob - src/main.rs
Merge pull request #1896 from Arnavion/fix-workspace
[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| PathBuf::from(Path::new(&arg["--manifest-path=".len()..])));
195
196         let package_index = {
197                 if let Some(ref manifest_path) = manifest_path {
198                     metadata.packages.iter().position(|package| {
199                         let package_manifest_path = Path::new(&package.manifest_path);
200                         package_manifest_path == manifest_path
201                     })
202                 } else {
203                     let package_manifest_paths: HashMap<_, _> =
204                         metadata.packages.iter()
205                         .enumerate()
206                         .map(|(i, package)| {
207                             let package_manifest_path = Path::new(&package.manifest_path)
208                                 .parent()
209                                 .expect("could not find parent directory of package manifest")
210                                 .canonicalize()
211                                 .expect("package directory cannot be canonicalized");
212                             (package_manifest_path, i)
213                         })
214                         .collect();
215
216                     let current_dir = std::env::current_dir()
217                         .expect("could not read current directory")
218                         .canonicalize()
219                         .expect("current directory cannot be canonicalized");
220
221                     let mut current_path: &Path = &current_dir;
222
223                     // This gets the most-recent parent (the one that takes the fewest `cd ..`s to
224                     // reach).
225                     loop {
226                         if let Some(&package_index) = package_manifest_paths.get(current_path) {
227                             break Some(package_index);
228                         }
229                         else {
230                             // We'll never reach the filesystem root, because to get to this point in the code
231                             // the call to `cargo_metadata::metadata` must have succeeded. So it's okay to
232                             // unwrap the current path's parent.
233                             current_path = current_path
234                                 .parent()
235                                 .unwrap_or_else(|| panic!("could not find parent of path {}", current_path.display()));
236                         }
237                     }
238                 }
239             }
240             .expect("could not find matching package");
241
242         let package = metadata.packages.remove(package_index);
243         for target in package.targets {
244             let args = std::env::args().skip(2);
245             if let Some(first) = target.kind.get(0) {
246                 if target.kind.len() > 1 || first.ends_with("lib") {
247                     if let Err(code) = process(std::iter::once("--lib".to_owned()).chain(args)) {
248                         std::process::exit(code);
249                     }
250                 } else if ["bin", "example", "test", "bench"].contains(&&**first) {
251                     if let Err(code) = process(vec![format!("--{}", first), target.name]
252                                                    .into_iter()
253                                                    .chain(args)) {
254                         std::process::exit(code);
255                     }
256                 }
257             } else {
258                 panic!("badly formatted cargo metadata: target::kind is an empty array");
259             }
260         }
261     } else {
262         // this arm is executed when cargo-clippy runs `cargo rustc` with the `RUSTC`
263         // env var set to itself
264
265         let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
266         let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
267         let sys_root = if let (Some(home), Some(toolchain)) = (home, toolchain) {
268             format!("{}/toolchains/{}", home, toolchain)
269         } else {
270             option_env!("SYSROOT")
271                 .map(|s| s.to_owned())
272                 .or_else(|| {
273                     Command::new("rustc")
274                         .arg("--print")
275                         .arg("sysroot")
276                         .output()
277                         .ok()
278                         .and_then(|out| String::from_utf8(out.stdout).ok())
279                         .map(|s| s.trim().to_owned())
280                 })
281                 .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust")
282         };
283
284         rustc_driver::in_rustc_thread(|| {
285             // this conditional check for the --sysroot flag is there so users can call
286             // `cargo-clippy` directly
287             // without having to pass --sysroot or anything
288             let mut args: Vec<String> = if env::args().any(|s| s == "--sysroot") {
289                 env::args().collect()
290             } else {
291                 env::args()
292                     .chain(Some("--sysroot".to_owned()))
293                     .chain(Some(sys_root))
294                     .collect()
295             };
296
297             // this check ensures that dependencies are built but not linted and the final
298             // crate is
299             // linted but not built
300             let clippy_enabled = env::args().any(|s| s == "--emit=metadata");
301
302             if clippy_enabled {
303                 args.extend_from_slice(&["--cfg".to_owned(), r#"feature="cargo-clippy""#.to_owned()]);
304             }
305
306             let mut ccc = ClippyCompilerCalls::new(clippy_enabled);
307             let (result, _) = rustc_driver::run_compiler(&args, &mut ccc, None, None);
308             if let Err(CompileIncomplete::Errored(_)) = result {
309                 std::process::exit(1);
310             }
311         })
312                 .expect("rustc_thread failed");
313     }
314 }
315
316 fn process<I>(old_args: I) -> Result<(), i32>
317     where I: Iterator<Item = String>
318 {
319
320     let mut args = vec!["rustc".to_owned()];
321
322     let mut found_dashes = false;
323     for arg in old_args {
324         found_dashes |= arg == "--";
325         args.push(arg);
326     }
327     if !found_dashes {
328         args.push("--".to_owned());
329     }
330     args.push("--emit=metadata".to_owned());
331     args.push("--cfg".to_owned());
332     args.push(r#"feature="cargo-clippy""#.to_owned());
333
334     let path = std::env::current_exe().expect("current executable path invalid");
335     let exit_status = std::process::Command::new("cargo")
336         .args(&args)
337         .env("RUSTC", path)
338         .spawn()
339         .expect("could not run cargo")
340         .wait()
341         .expect("failed to wait for cargo?");
342
343     if exit_status.success() {
344         Ok(())
345     } else {
346         Err(exit_status.code().unwrap_or(-1))
347     }
348 }