]> git.lizzy.rs Git - rust.git/blob - src/main.rs
Version bump
[rust.git] / src / main.rs
1 // error-pattern:yummy
2 #![feature(box_syntax)]
3 #![feature(rustc_private)]
4 #![allow(unknown_lints, missing_docs_in_private_items)]
5
6 use std::collections::HashMap;
7 use std::process;
8 use std::io::{self, Write};
9
10 extern crate cargo_metadata;
11
12 use std::path::{Path, PathBuf};
13
14 const CARGO_CLIPPY_HELP: &str = r#"Checks a package to catch common mistakes and improve your Rust code.
15
16 Usage:
17     cargo clippy [options] [--] [<opts>...]
18
19 Common options:
20     -h, --help               Print this message
21     --features               Features to compile for the package
22     -V, --version            Print version info and exit
23     --all                    Run over all packages in the current workspace
24
25 Other options are the same as `cargo rustc`.
26
27 To allow or deny a lint from the command line you can use `cargo clippy --`
28 with:
29
30     -W --warn OPT       Set lint warnings
31     -A --allow OPT      Set lint allowed
32     -D --deny OPT       Set lint denied
33     -F --forbid OPT     Set lint forbidden
34
35 The feature `cargo-clippy` is automatically defined for convenience. You can use
36 it to allow or deny lints from the code, eg.:
37
38     #[cfg_attr(feature = "cargo-clippy", allow(needless_lifetimes))]
39 "#;
40
41 #[allow(print_stdout)]
42 fn show_help() {
43     println!("{}", CARGO_CLIPPY_HELP);
44 }
45
46 #[allow(print_stdout)]
47 fn show_version() {
48     println!("{}", env!("CARGO_PKG_VERSION"));
49 }
50
51 pub fn main() {
52     // Check for version and help flags even when invoked as 'cargo-clippy'
53     if std::env::args().any(|a| a == "--help" || a == "-h") {
54         show_help();
55         return;
56     }
57     if std::env::args().any(|a| a == "--version" || a == "-V") {
58         show_version();
59         return;
60     }
61
62     let mut manifest_path_arg = std::env::args()
63         .skip(2)
64         .skip_while(|val| !val.starts_with("--manifest-path"));
65     let manifest_path_arg = manifest_path_arg.next().and_then(|val| {
66         if val == "--manifest-path" {
67             manifest_path_arg.next()
68         } else if val.starts_with("--manifest-path=") {
69             Some(val["--manifest-path=".len()..].to_owned())
70         } else {
71             None
72         }
73     });
74
75     let mut metadata = if let Ok(metadata) = cargo_metadata::metadata(manifest_path_arg.as_ref().map(AsRef::as_ref)) {
76         metadata
77     } else {
78         println!(
79             "{:?}",
80             cargo_metadata::metadata(manifest_path_arg.as_ref().map(AsRef::as_ref))
81         );
82         let _ = io::stderr().write_fmt(format_args!("error: Could not obtain cargo metadata.\n"));
83         process::exit(101);
84     };
85
86     let manifest_path = manifest_path_arg.map(|arg| {
87         PathBuf::from(arg)
88             .canonicalize()
89             .expect("manifest path could not be canonicalized")
90     });
91
92     let packages = if std::env::args().any(|a| a == "--all") {
93         metadata.packages
94     } else {
95         let package_index = {
96             if let Some(manifest_path) = manifest_path {
97                 metadata.packages.iter().position(|package| {
98                     let package_manifest_path = Path::new(&package.manifest_path)
99                         .canonicalize()
100                         .expect("package manifest path could not be canonicalized");
101                     package_manifest_path == manifest_path
102                 })
103             } else {
104                 let package_manifest_paths: HashMap<_, _> = metadata
105                     .packages
106                     .iter()
107                     .enumerate()
108                     .map(|(i, package)| {
109                         let package_manifest_path = Path::new(&package.manifest_path)
110                             .parent()
111                             .expect("could not find parent directory of package manifest")
112                             .canonicalize()
113                             .expect("package directory cannot be canonicalized");
114                         (package_manifest_path, i)
115                     })
116                     .collect();
117
118                 let current_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")
119                     .expect("CARGO_MANIFEST_DIR not set"))
120                     .canonicalize()
121                     .expect("manifest directory cannot be canonicalized");
122
123                 let mut current_path: &Path = &current_dir;
124
125                 // This gets the most-recent parent (the one that takes the fewest `cd ..`s to
126                 // reach).
127                 loop {
128                     if let Some(&package_index) = package_manifest_paths.get(current_path) {
129                         break Some(package_index);
130                     } else {
131                         // We'll never reach the filesystem root, because to get to this point in the
132                         // code
133                         // the call to `cargo_metadata::metadata` must have succeeded. So it's okay to
134                         // unwrap the current path's parent.
135                         current_path = current_path
136                             .parent()
137                             .unwrap_or_else(|| panic!("could not find parent of path {}", current_path.display()));
138                     }
139                 }
140             }
141         }.expect("could not find matching package");
142
143         vec![metadata.packages.remove(package_index)]
144     };
145
146     for package in packages {
147         let manifest_path = package.manifest_path;
148
149         for target in package.targets {
150             let args = std::env::args()
151                 .skip(2)
152                 .filter(|a| a != "--all" && !a.starts_with("--manifest-path="));
153
154             let args = std::iter::once(format!("--manifest-path={}", manifest_path)).chain(args);
155             if let Some(first) = target.kind.get(0) {
156                 if target.kind.len() > 1 || first.ends_with("lib") {
157                     println!("lib: {}", target.name);
158                     if let Err(code) = process(std::iter::once("--lib".to_owned()).chain(args)) {
159                         std::process::exit(code);
160                     }
161                 } else if ["bin", "example", "test", "bench"].contains(&&**first) {
162                     println!("{}: {}", first, target.name);
163                     if let Err(code) = process(
164                         vec![format!("--{}", first), target.name]
165                             .into_iter()
166                             .chain(args),
167                     ) {
168                         std::process::exit(code);
169                     }
170                 }
171             } else {
172                 panic!("badly formatted cargo metadata: target::kind is an empty array");
173             }
174         }
175     }
176 }
177
178 fn process<I>(old_args: I) -> Result<(), i32>
179 where
180     I: Iterator<Item = String>,
181 {
182     let mut args = vec!["rustc".to_owned()];
183
184     let mut found_dashes = false;
185     for arg in old_args {
186         found_dashes |= arg == "--";
187         args.push(arg);
188     }
189     if !found_dashes {
190         args.push("--".to_owned());
191     }
192     args.push("--emit=metadata".to_owned());
193     args.push("--cfg".to_owned());
194     args.push(r#"feature="cargo-clippy""#.to_owned());
195
196     let mut path = std::env::current_exe()
197         .expect("current executable path invalid")
198         .with_file_name("clippy-driver");
199     if cfg!(windows) {
200         path.set_extension("exe");
201     }
202     let exit_status = std::process::Command::new("cargo")
203         .args(&args)
204         .env("RUSTC_WRAPPER", path)
205         .spawn()
206         .expect("could not run cargo")
207         .wait()
208         .expect("failed to wait for cargo?");
209
210     if exit_status.success() {
211         Ok(())
212     } else {
213         Err(exit_status.code().unwrap_or(-1))
214     }
215 }