]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/deps.rs
Auto merge of #54265 - arielb1:civilize-proc-macros, r=alexcrichton
[rust.git] / src / tools / tidy / src / deps.rs
1 // Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Check license of third-party deps by inspecting src/vendor
12
13 use std::collections::{BTreeSet, HashSet, HashMap};
14 use std::fs::File;
15 use std::io::Read;
16 use std::path::Path;
17 use std::process::Command;
18
19 use serde_json;
20
21 const LICENSES: &[&str] = &[
22     "MIT/Apache-2.0",
23     "MIT / Apache-2.0",
24     "Apache-2.0/MIT",
25     "Apache-2.0 / MIT",
26     "MIT OR Apache-2.0",
27     "MIT",
28     "Unlicense/MIT",
29     "Unlicense OR MIT",
30 ];
31
32 /// These are exceptions to Rust's permissive licensing policy, and
33 /// should be considered bugs. Exceptions are only allowed in Rust
34 /// tooling. It is _crucial_ that no exception crates be dependencies
35 /// of the Rust runtime (std / test).
36 const EXCEPTIONS: &[&str] = &[
37     "mdbook",             // MPL2, mdbook
38     "openssl",            // BSD+advertising clause, cargo, mdbook
39     "pest",               // MPL2, mdbook via handlebars
40     "thread-id",          // Apache-2.0, mdbook
41     "toml-query",         // MPL-2.0, mdbook
42     "is-match",           // MPL-2.0, mdbook
43     "cssparser",          // MPL-2.0, rustdoc
44     "smallvec",           // MPL-2.0, rustdoc
45     "fuchsia-zircon-sys", // BSD-3-Clause, rustdoc, rustc, cargo
46     "fuchsia-zircon",     // BSD-3-Clause, rustdoc, rustc, cargo (jobserver & tempdir)
47     "cssparser-macros",   // MPL-2.0, rustdoc
48     "selectors",          // MPL-2.0, rustdoc
49     "clippy_lints",       // MPL-2.0, rls
50     "colored",            // MPL-2.0, rustfmt
51     "ordslice",           // Apache-2.0, rls
52     "cloudabi",           // BSD-2-Clause, (rls -> crossbeam-channel 0.2 -> rand 0.5)
53     "ryu",                // Apache-2.0, rls/cargo/... (b/c of serde)
54     "bytesize",           // Apache-2.0, cargo
55 ];
56
57 /// Which crates to check against the whitelist?
58 const WHITELIST_CRATES: &[CrateVersion] = &[
59     CrateVersion("rustc", "0.0.0"),
60     CrateVersion("rustc_codegen_llvm", "0.0.0"),
61 ];
62
63 /// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
64 const WHITELIST: &[Crate] = &[
65     Crate("aho-corasick"),
66     Crate("arrayvec"),
67     Crate("atty"),
68     Crate("backtrace"),
69     Crate("backtrace-sys"),
70     Crate("bitflags"),
71     Crate("byteorder"),
72     Crate("cc"),
73     Crate("cfg-if"),
74     Crate("chalk-engine"),
75     Crate("chalk-macros"),
76     Crate("cloudabi"),
77     Crate("cmake"),
78     Crate("crossbeam-deque"),
79     Crate("crossbeam-epoch"),
80     Crate("crossbeam-utils"),
81     Crate("datafrog"),
82     Crate("either"),
83     Crate("ena"),
84     Crate("env_logger"),
85     Crate("filetime"),
86     Crate("flate2"),
87     Crate("fuchsia-zircon"),
88     Crate("fuchsia-zircon-sys"),
89     Crate("getopts"),
90     Crate("humantime"),
91     Crate("jobserver"),
92     Crate("kernel32-sys"),
93     Crate("lazy_static"),
94     Crate("libc"),
95     Crate("lock_api"),
96     Crate("log"),
97     Crate("log_settings"),
98     Crate("memchr"),
99     Crate("memmap"),
100     Crate("memoffset"),
101     Crate("miniz-sys"),
102     Crate("nodrop"),
103     Crate("num_cpus"),
104     Crate("owning_ref"),
105     Crate("parking_lot"),
106     Crate("parking_lot_core"),
107     Crate("pkg-config"),
108     Crate("polonius-engine"),
109     Crate("quick-error"),
110     Crate("rand"),
111     Crate("rand_core"),
112     Crate("redox_syscall"),
113     Crate("redox_termios"),
114     Crate("regex"),
115     Crate("regex-syntax"),
116     Crate("remove_dir_all"),
117     Crate("rustc-demangle"),
118     Crate("rustc-hash"),
119     Crate("rustc-rayon"),
120     Crate("rustc-rayon-core"),
121     Crate("scoped-tls"),
122     Crate("scopeguard"),
123     Crate("smallvec"),
124     Crate("stable_deref_trait"),
125     Crate("tempfile"),
126     Crate("termcolor"),
127     Crate("terminon"),
128     Crate("termion"),
129     Crate("thread_local"),
130     Crate("ucd-util"),
131     Crate("unicode-width"),
132     Crate("unreachable"),
133     Crate("utf8-ranges"),
134     Crate("version_check"),
135     Crate("void"),
136     Crate("winapi"),
137     Crate("winapi-build"),
138     Crate("winapi-i686-pc-windows-gnu"),
139     Crate("winapi-util"),
140     Crate("winapi-x86_64-pc-windows-gnu"),
141     Crate("wincolor"),
142 ];
143
144 // Some types for Serde to deserialize the output of `cargo metadata` to...
145
146 #[derive(Deserialize)]
147 struct Output {
148     resolve: Resolve,
149 }
150
151 #[derive(Deserialize)]
152 struct Resolve {
153     nodes: Vec<ResolveNode>,
154 }
155
156 #[derive(Deserialize)]
157 struct ResolveNode {
158     id: String,
159     dependencies: Vec<String>,
160 }
161
162 /// A unique identifier for a crate
163 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
164 struct Crate<'a>(&'a str); // (name,)
165
166 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
167 struct CrateVersion<'a>(&'a str, &'a str); // (name, version)
168
169 impl<'a> Crate<'a> {
170     pub fn id_str(&self) -> String {
171         format!("{} ", self.0)
172     }
173 }
174
175 impl<'a> CrateVersion<'a> {
176     /// Returns the struct and whether or not the dep is in-tree
177     pub fn from_str(s: &'a str) -> (Self, bool) {
178         let mut parts = s.split(' ');
179         let name = parts.next().unwrap();
180         let version = parts.next().unwrap();
181         let path = parts.next().unwrap();
182
183         let is_path_dep = path.starts_with("(path+");
184
185         (CrateVersion(name, version), is_path_dep)
186     }
187
188     pub fn id_str(&self) -> String {
189         format!("{} {}", self.0, self.1)
190     }
191 }
192
193 impl<'a> From<CrateVersion<'a>> for Crate<'a> {
194     fn from(cv: CrateVersion<'a>) -> Crate<'a> {
195         Crate(cv.0)
196     }
197 }
198
199 /// Checks the dependency at the given path. Changes `bad` to `true` if a check failed.
200 ///
201 /// Specifically, this checks that the license is correct.
202 pub fn check(path: &Path, bad: &mut bool) {
203     // Check licences
204     let path = path.join("vendor");
205     assert!(path.exists(), "vendor directory missing");
206     let mut saw_dir = false;
207     for dir in t!(path.read_dir()) {
208         saw_dir = true;
209         let dir = t!(dir);
210
211         // skip our exceptions
212         let is_exception = EXCEPTIONS.iter().any(|exception| {
213             dir.path()
214                 .to_str()
215                 .unwrap()
216                 .contains(&format!("src/vendor/{}", exception))
217         });
218         if is_exception {
219             continue;
220         }
221
222         let toml = dir.path().join("Cargo.toml");
223         *bad = *bad || !check_license(&toml);
224     }
225     assert!(saw_dir, "no vendored source");
226 }
227
228 /// Checks the dependency of WHITELIST_CRATES at the given path. Changes `bad` to `true` if a check
229 /// failed.
230 ///
231 /// Specifically, this checks that the dependencies are on the WHITELIST.
232 pub fn check_whitelist(path: &Path, cargo: &Path, bad: &mut bool) {
233     // Get dependencies from cargo metadata
234     let resolve = get_deps(path, cargo);
235
236     // Get the whitelist into a convenient form
237     let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
238
239     // Check dependencies
240     let mut visited = BTreeSet::new();
241     let mut unapproved = BTreeSet::new();
242     for &krate in WHITELIST_CRATES.iter() {
243         let mut bad = check_crate_whitelist(&whitelist, &resolve, &mut visited, krate, false);
244         unapproved.append(&mut bad);
245     }
246
247     if !unapproved.is_empty() {
248         println!("Dependencies not on the whitelist:");
249         for dep in unapproved {
250             println!("* {}", dep.id_str());
251         }
252         *bad = true;
253     }
254
255     check_crate_duplicate(&resolve, bad);
256 }
257
258 fn check_license(path: &Path) -> bool {
259     if !path.exists() {
260         panic!("{} does not exist", path.display());
261     }
262     let mut contents = String::new();
263     t!(t!(File::open(path)).read_to_string(&mut contents));
264
265     let mut found_license = false;
266     for line in contents.lines() {
267         if !line.starts_with("license") {
268             continue;
269         }
270         let license = extract_license(line);
271         if !LICENSES.contains(&&*license) {
272             println!("invalid license {} in {}", license, path.display());
273             return false;
274         }
275         found_license = true;
276         break;
277     }
278     if !found_license {
279         println!("no license in {}", path.display());
280         return false;
281     }
282
283     true
284 }
285
286 fn extract_license(line: &str) -> String {
287     let first_quote = line.find('"');
288     let last_quote = line.rfind('"');
289     if let (Some(f), Some(l)) = (first_quote, last_quote) {
290         let license = &line[f + 1..l];
291         license.into()
292     } else {
293         "bad-license-parse".into()
294     }
295 }
296
297 /// Get the dependencies of the crate at the given path using `cargo metadata`.
298 fn get_deps(path: &Path, cargo: &Path) -> Resolve {
299     // Run `cargo metadata` to get the set of dependencies
300     let output = Command::new(cargo)
301         .arg("metadata")
302         .arg("--format-version")
303         .arg("1")
304         .arg("--manifest-path")
305         .arg(path.join("Cargo.toml"))
306         .output()
307         .expect("Unable to run `cargo metadata`")
308         .stdout;
309     let output = String::from_utf8_lossy(&output);
310     let output: Output = serde_json::from_str(&output).unwrap();
311
312     output.resolve
313 }
314
315 /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
316 /// the whitelist. Returns a list of illegal dependencies.
317 fn check_crate_whitelist<'a, 'b>(
318     whitelist: &'a HashSet<Crate>,
319     resolve: &'a Resolve,
320     visited: &'b mut BTreeSet<CrateVersion<'a>>,
321     krate: CrateVersion<'a>,
322     must_be_on_whitelist: bool,
323 ) -> BTreeSet<Crate<'a>> {
324     // Will contain bad deps
325     let mut unapproved = BTreeSet::new();
326
327     // Check if we have already visited this crate
328     if visited.contains(&krate) {
329         return unapproved;
330     }
331
332     visited.insert(krate);
333
334     // If this path is in-tree, we don't require it to be on the whitelist
335     if must_be_on_whitelist {
336         // If this dependency is not on the WHITELIST, add to bad set
337         if !whitelist.contains(&krate.into()) {
338             unapproved.insert(krate.into());
339         }
340     }
341
342     // Do a DFS in the crate graph (it's a DAG, so we know we have no cycles!)
343     let to_check = resolve
344         .nodes
345         .iter()
346         .find(|n| n.id.starts_with(&krate.id_str()))
347         .expect("crate does not exist");
348
349     for dep in to_check.dependencies.iter() {
350         let (krate, is_path_dep) = CrateVersion::from_str(dep);
351
352         let mut bad = check_crate_whitelist(whitelist, resolve, visited, krate, !is_path_dep);
353         unapproved.append(&mut bad);
354     }
355
356     unapproved
357 }
358
359 fn check_crate_duplicate(resolve: &Resolve, bad: &mut bool) {
360     const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
361         // These two crates take quite a long time to build, let's not let two
362         // versions of them accidentally sneak into our dependency graph to
363         // ensure we keep our CI times under control
364         // "cargo", // FIXME(#53005)
365         "rustc-ap-syntax",
366     ];
367     let mut name_to_id: HashMap<_, Vec<_>> = HashMap::new();
368     for node in resolve.nodes.iter() {
369         name_to_id.entry(node.id.split_whitespace().next().unwrap())
370             .or_default()
371             .push(&node.id);
372     }
373
374     for name in FORBIDDEN_TO_HAVE_DUPLICATES {
375         if name_to_id[name].len() <= 1 {
376             continue
377         }
378         println!("crate `{}` is duplicated in `Cargo.lock`", name);
379         for id in name_to_id[name].iter() {
380             println!("  * {}", id);
381         }
382         *bad = true;
383     }
384 }