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