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