]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/deps.rs
Add 'compiler/rustc_smir/' from commit '9abcb5c7b574cf316eb23d3f469187bb86ba3019'
[rust.git] / src / tools / tidy / src / deps.rs
1 //! Checks the licenses of third-party dependencies.
2
3 use cargo_metadata::{Metadata, Package, PackageId, Resolve};
4 use std::collections::{BTreeSet, HashSet};
5 use std::path::Path;
6
7 /// These are licenses that are allowed for all crates, including the runtime,
8 /// rustc, tools, etc.
9 const LICENSES: &[&str] = &[
10     "MIT/Apache-2.0",
11     "MIT / Apache-2.0",
12     "Apache-2.0/MIT",
13     "Apache-2.0 / MIT",
14     "MIT OR Apache-2.0",
15     "Apache-2.0 OR MIT",
16     "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
17     "MIT",
18     "ISC",
19     "Unlicense/MIT",
20     "Unlicense OR MIT",
21     "0BSD OR MIT OR Apache-2.0", // adler license
22     "Zlib OR Apache-2.0 OR MIT", // tinyvec
23 ];
24
25 /// These are exceptions to Rust's permissive licensing policy, and
26 /// should be considered bugs. Exceptions are only allowed in Rust
27 /// tooling. It is _crucial_ that no exception crates be dependencies
28 /// of the Rust runtime (std/test).
29 const EXCEPTIONS: &[(&str, &str)] = &[
30     ("mdbook", "MPL-2.0"),            // mdbook
31     ("openssl", "Apache-2.0"),        // cargo, mdbook
32     ("colored", "MPL-2.0"),           // rustfmt
33     ("ordslice", "Apache-2.0"),       // rls
34     ("ryu", "Apache-2.0 OR BSL-1.0"), // rls/cargo/... (because of serde)
35     ("bytesize", "Apache-2.0"),       // cargo
36     ("im-rc", "MPL-2.0+"),            // cargo
37     ("sized-chunks", "MPL-2.0+"),     // cargo via im-rc
38     ("bitmaps", "MPL-2.0+"),          // cargo via im-rc
39     ("instant", "BSD-3-Clause"),      // rustc_driver/tracing-subscriber/parking_lot
40     ("snap", "BSD-3-Clause"),         // rustc
41     ("fluent-langneg", "Apache-2.0"), // rustc (fluent translations)
42     ("self_cell", "Apache-2.0"),      // rustc (fluent translations)
43     // FIXME: this dependency violates the documentation comment above:
44     ("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target
45     ("dunce", "CC0-1.0"),            // cargo (dev dependency)
46     ("similar", "Apache-2.0"),       // cargo (dev dependency)
47     ("normalize-line-endings", "Apache-2.0"), // cargo (dev dependency)
48 ];
49
50 const EXCEPTIONS_CRANELIFT: &[(&str, &str)] = &[
51     ("cranelift-bforest", "Apache-2.0 WITH LLVM-exception"),
52     ("cranelift-codegen", "Apache-2.0 WITH LLVM-exception"),
53     ("cranelift-codegen-meta", "Apache-2.0 WITH LLVM-exception"),
54     ("cranelift-codegen-shared", "Apache-2.0 WITH LLVM-exception"),
55     ("cranelift-entity", "Apache-2.0 WITH LLVM-exception"),
56     ("cranelift-frontend", "Apache-2.0 WITH LLVM-exception"),
57     ("cranelift-jit", "Apache-2.0 WITH LLVM-exception"),
58     ("cranelift-module", "Apache-2.0 WITH LLVM-exception"),
59     ("cranelift-native", "Apache-2.0 WITH LLVM-exception"),
60     ("cranelift-object", "Apache-2.0 WITH LLVM-exception"),
61     ("mach", "BSD-2-Clause"),
62     ("regalloc", "Apache-2.0 WITH LLVM-exception"),
63     ("target-lexicon", "Apache-2.0 WITH LLVM-exception"),
64 ];
65
66 /// These are the root crates that are part of the runtime. The licenses for
67 /// these and all their dependencies *must not* be in the exception list.
68 const RUNTIME_CRATES: &[&str] = &["std", "core", "alloc", "test", "panic_abort", "panic_unwind"];
69
70 /// Crates whose dependencies must be explicitly permitted.
71 const RESTRICTED_DEPENDENCY_CRATES: &[&str] = &["rustc_driver", "rustc_codegen_llvm"];
72
73 /// Crates rustc is allowed to depend on. Avoid adding to the list if possible.
74 ///
75 /// This list is here to provide a speed-bump to adding a new dependency to
76 /// rustc. Please check with the compiler team before adding an entry.
77 const PERMITTED_DEPENDENCIES: &[&str] = &[
78     "addr2line",
79     "adler",
80     "ahash",
81     "aho-corasick",
82     "annotate-snippets",
83     "ansi_term",
84     "arrayvec",
85     "atty",
86     "autocfg",
87     "bitflags",
88     "block-buffer",
89     "block-padding",
90     "byte-tools",
91     "byteorder",
92     "cc",
93     "cfg-if",
94     "chalk-derive",
95     "chalk-engine",
96     "chalk-ir",
97     "chalk-solve",
98     "chrono",
99     "cmake",
100     "compiler_builtins",
101     "cpufeatures",
102     "crc32fast",
103     "crossbeam-channel",
104     "crossbeam-deque",
105     "crossbeam-epoch",
106     "crossbeam-utils",
107     "crypto-common",
108     "cstr",
109     "datafrog",
110     "difference",
111     "digest",
112     "dlmalloc",
113     "either",
114     "ena",
115     "env_logger",
116     "expect-test",
117     "fake-simd",
118     "fallible-iterator", // dependency of `thorin`
119     "filetime",
120     "fixedbitset",
121     "flate2",
122     "fluent-bundle",
123     "fluent-langneg",
124     "fluent-syntax",
125     "fortanix-sgx-abi",
126     "generic-array",
127     "getopts",
128     "getrandom",
129     "gimli",
130     "gsgdt",
131     "hashbrown",
132     "hermit-abi",
133     "humantime",
134     "if_chain",
135     "indexmap",
136     "instant",
137     "intl-memoizer",
138     "intl_pluralrules",
139     "itertools",
140     "itoa",
141     "jobserver",
142     "lazy_static",
143     "libc",
144     "libloading",
145     "libz-sys",
146     "lock_api",
147     "log",
148     "matchers",
149     "md-5",
150     "measureme",
151     "memchr",
152     "memmap2",
153     "memoffset",
154     "miniz_oxide",
155     "num-integer",
156     "num-traits",
157     "num_cpus",
158     "object",
159     "odht",
160     "once_cell",
161     "opaque-debug",
162     "parking_lot",
163     "parking_lot_core",
164     "pathdiff",
165     "perf-event-open-sys",
166     "petgraph",
167     "pin-project-lite",
168     "pkg-config",
169     "polonius-engine",
170     "ppv-lite86",
171     "proc-macro-hack",
172     "proc-macro2",
173     "psm",
174     "punycode",
175     "quick-error",
176     "quote",
177     "rand",
178     "rand_chacha",
179     "rand_core",
180     "rand_hc",
181     "rand_xorshift",
182     "rand_xoshiro",
183     "redox_syscall",
184     "regex",
185     "regex-automata",
186     "regex-syntax",
187     "remove_dir_all",
188     "rls-data",
189     "rls-span",
190     "rustc-demangle",
191     "rustc-hash",
192     "rustc-rayon",
193     "rustc-rayon-core",
194     "rustc_version",
195     "ryu",
196     "scoped-tls",
197     "scopeguard",
198     "self_cell",
199     "semver",
200     "serde",
201     "serde_derive",
202     "serde_json",
203     "sha-1",
204     "sha2",
205     "sharded-slab",
206     "smallvec",
207     "snap",
208     "stable_deref_trait",
209     "stacker",
210     "syn",
211     "synstructure",
212     "tempfile",
213     "termcolor",
214     "termize",
215     "thiserror",
216     "thiserror-impl",
217     "thorin-dwp",
218     "thread_local",
219     "time",
220     "tinystr",
221     "tinyvec",
222     "tracing",
223     "tracing-attributes",
224     "tracing-core",
225     "tracing-log",
226     "tracing-subscriber",
227     "tracing-tree",
228     "type-map",
229     "typenum",
230     "unic-char-property",
231     "unic-char-range",
232     "unic-common",
233     "unic-emoji-char",
234     "unic-langid",
235     "unic-langid-impl",
236     "unic-langid-macros",
237     "unic-langid-macros-impl",
238     "unic-ucd-version",
239     "unicode-normalization",
240     "unicode-script",
241     "unicode-security",
242     "unicode-width",
243     "unicode-xid",
244     "vcpkg",
245     "version_check",
246     "wasi",
247     "winapi",
248     "winapi-i686-pc-windows-gnu",
249     "winapi-util",
250     "winapi-x86_64-pc-windows-gnu",
251     // this is a false-positive: it's only used by rustfmt, but because it's enabled through a
252     // feature, tidy thinks it's used by rustc as well.
253     "yansi-term",
254 ];
255
256 const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
257     "anyhow",
258     "ar",
259     "autocfg",
260     "bitflags",
261     "cfg-if",
262     "cranelift-bforest",
263     "cranelift-codegen",
264     "cranelift-codegen-meta",
265     "cranelift-codegen-shared",
266     "cranelift-entity",
267     "cranelift-frontend",
268     "cranelift-jit",
269     "cranelift-module",
270     "cranelift-native",
271     "cranelift-object",
272     "crc32fast",
273     "gimli",
274     "hashbrown",
275     "indexmap",
276     "libc",
277     "libloading",
278     "log",
279     "mach",
280     "memchr",
281     "object",
282     "once_cell",
283     "regalloc",
284     "region",
285     "rustc-hash",
286     "smallvec",
287     "target-lexicon",
288     "winapi",
289     "winapi-i686-pc-windows-gnu",
290     "winapi-x86_64-pc-windows-gnu",
291 ];
292
293 const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
294     // These two crates take quite a long time to build, so don't allow two versions of them
295     // to accidentally sneak into our dependency graph, in order to ensure we keep our CI times
296     // under control.
297     "cargo",
298 ];
299
300 /// Dependency checks.
301 ///
302 /// `root` is path to the directory with the root `Cargo.toml` (for the workspace). `cargo` is path
303 /// to the cargo executable.
304 pub fn check(root: &Path, cargo: &Path, bad: &mut bool) {
305     let mut cmd = cargo_metadata::MetadataCommand::new();
306     cmd.cargo_path(cargo)
307         .manifest_path(root.join("Cargo.toml"))
308         .features(cargo_metadata::CargoOpt::AllFeatures);
309     let metadata = t!(cmd.exec());
310     let runtime_ids = compute_runtime_crates(&metadata);
311     check_exceptions(&metadata, EXCEPTIONS, runtime_ids, bad);
312     check_dependencies(&metadata, PERMITTED_DEPENDENCIES, RESTRICTED_DEPENDENCY_CRATES, bad);
313     check_crate_duplicate(&metadata, FORBIDDEN_TO_HAVE_DUPLICATES, bad);
314     check_rustfix(&metadata, bad);
315
316     // Check rustc_codegen_cranelift independently as it has it's own workspace.
317     let mut cmd = cargo_metadata::MetadataCommand::new();
318     cmd.cargo_path(cargo)
319         .manifest_path(root.join("compiler/rustc_codegen_cranelift/Cargo.toml"))
320         .features(cargo_metadata::CargoOpt::AllFeatures);
321     let metadata = t!(cmd.exec());
322     let runtime_ids = HashSet::new();
323     check_exceptions(&metadata, EXCEPTIONS_CRANELIFT, runtime_ids, bad);
324     check_dependencies(
325         &metadata,
326         PERMITTED_CRANELIFT_DEPENDENCIES,
327         &["rustc_codegen_cranelift"],
328         bad,
329     );
330     check_crate_duplicate(&metadata, &[], bad);
331 }
332
333 /// Check that all licenses are in the valid list in `LICENSES`.
334 ///
335 /// Packages listed in `EXCEPTIONS` are allowed for tools.
336 fn check_exceptions(
337     metadata: &Metadata,
338     exceptions: &[(&str, &str)],
339     runtime_ids: HashSet<&PackageId>,
340     bad: &mut bool,
341 ) {
342     // Validate the EXCEPTIONS list hasn't changed.
343     for (name, license) in exceptions {
344         // Check that the package actually exists.
345         if !metadata.packages.iter().any(|p| p.name == *name) {
346             tidy_error!(
347                 bad,
348                 "could not find exception package `{}`\n\
349                 Remove from EXCEPTIONS list if it is no longer used.",
350                 name
351             );
352         }
353         // Check that the license hasn't changed.
354         for pkg in metadata.packages.iter().filter(|p| p.name == *name) {
355             match &pkg.license {
356                 None => {
357                     tidy_error!(
358                         bad,
359                         "dependency exception `{}` does not declare a license expression",
360                         pkg.id
361                     );
362                 }
363                 Some(pkg_license) => {
364                     if pkg_license.as_str() != *license {
365                         println!("dependency exception `{name}` license has changed");
366                         println!("    previously `{license}` now `{pkg_license}`");
367                         println!("    update EXCEPTIONS for the new license");
368                         *bad = true;
369                     }
370                 }
371             }
372         }
373     }
374
375     let exception_names: Vec<_> = exceptions.iter().map(|(name, _license)| *name).collect();
376
377     // Check if any package does not have a valid license.
378     for pkg in &metadata.packages {
379         if pkg.source.is_none() {
380             // No need to check local packages.
381             continue;
382         }
383         if !runtime_ids.contains(&pkg.id) && exception_names.contains(&pkg.name.as_str()) {
384             continue;
385         }
386         let license = match &pkg.license {
387             Some(license) => license,
388             None => {
389                 tidy_error!(bad, "dependency `{}` does not define a license expression", pkg.id);
390                 continue;
391             }
392         };
393         if !LICENSES.contains(&license.as_str()) {
394             if pkg.name == "fortanix-sgx-abi" {
395                 // This is a specific exception because SGX is considered
396                 // "third party". See
397                 // https://github.com/rust-lang/rust/issues/62620 for more. In
398                 // general, these should never be added.
399                 continue;
400             }
401             tidy_error!(bad, "invalid license `{}` in `{}`", license, pkg.id);
402         }
403     }
404 }
405
406 /// Checks the dependency of `RESTRICTED_DEPENDENCY_CRATES` at the given path. Changes `bad` to
407 /// `true` if a check failed.
408 ///
409 /// Specifically, this checks that the dependencies are on the `PERMITTED_DEPENDENCIES`.
410 fn check_dependencies(
411     metadata: &Metadata,
412     permitted_dependencies: &[&'static str],
413     restricted_dependency_crates: &[&'static str],
414     bad: &mut bool,
415 ) {
416     // Check that the PERMITTED_DEPENDENCIES does not have unused entries.
417     for name in permitted_dependencies {
418         if !metadata.packages.iter().any(|p| p.name == *name) {
419             tidy_error!(
420                 bad,
421                 "could not find allowed package `{}`\n\
422                 Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
423                 name
424             );
425         }
426     }
427     // Get the list in a convenient form.
428     let permitted_dependencies: HashSet<_> = permitted_dependencies.iter().cloned().collect();
429
430     // Check dependencies.
431     let mut visited = BTreeSet::new();
432     let mut unapproved = BTreeSet::new();
433     for &krate in restricted_dependency_crates.iter() {
434         let pkg = pkg_from_name(metadata, krate);
435         let mut bad =
436             check_crate_dependencies(&permitted_dependencies, metadata, &mut visited, pkg);
437         unapproved.append(&mut bad);
438     }
439
440     if !unapproved.is_empty() {
441         tidy_error!(bad, "Dependencies not explicitly permitted:");
442         for dep in unapproved {
443             println!("* {dep}");
444         }
445     }
446 }
447
448 /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
449 /// the list of permitted dependencies. Returns a list of disallowed dependencies.
450 fn check_crate_dependencies<'a>(
451     permitted_dependencies: &'a HashSet<&'static str>,
452     metadata: &'a Metadata,
453     visited: &mut BTreeSet<&'a PackageId>,
454     krate: &'a Package,
455 ) -> BTreeSet<&'a PackageId> {
456     // This will contain bad deps.
457     let mut unapproved = BTreeSet::new();
458
459     // Check if we have already visited this crate.
460     if visited.contains(&krate.id) {
461         return unapproved;
462     }
463
464     visited.insert(&krate.id);
465
466     // If this path is in-tree, we don't require it to be explicitly permitted.
467     if krate.source.is_some() {
468         // If this dependency is not on `PERMITTED_DEPENDENCIES`, add to bad set.
469         if !permitted_dependencies.contains(krate.name.as_str()) {
470             unapproved.insert(&krate.id);
471         }
472     }
473
474     // Do a DFS in the crate graph.
475     let to_check = deps_of(metadata, &krate.id);
476
477     for dep in to_check {
478         let mut bad = check_crate_dependencies(permitted_dependencies, metadata, visited, dep);
479         unapproved.append(&mut bad);
480     }
481
482     unapproved
483 }
484
485 /// Prevents multiple versions of some expensive crates.
486 fn check_crate_duplicate(
487     metadata: &Metadata,
488     forbidden_to_have_duplicates: &[&str],
489     bad: &mut bool,
490 ) {
491     for &name in forbidden_to_have_duplicates {
492         let matches: Vec<_> = metadata.packages.iter().filter(|pkg| pkg.name == name).collect();
493         match matches.len() {
494             0 => {
495                 tidy_error!(
496                     bad,
497                     "crate `{}` is missing, update `check_crate_duplicate` \
498                     if it is no longer used",
499                     name
500                 );
501             }
502             1 => {}
503             _ => {
504                 tidy_error!(
505                     bad,
506                     "crate `{}` is duplicated in `Cargo.lock`, \
507                     it is too expensive to build multiple times, \
508                     so make sure only one version appears across all dependencies",
509                     name
510                 );
511                 for pkg in matches {
512                     println!("  * {}", pkg.id);
513                 }
514             }
515         }
516     }
517 }
518
519 /// Returns a list of dependencies for the given package.
520 fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId) -> Vec<&'a Package> {
521     let resolve = metadata.resolve.as_ref().unwrap();
522     let node = resolve
523         .nodes
524         .iter()
525         .find(|n| &n.id == pkg_id)
526         .unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
527     node.deps
528         .iter()
529         .map(|dep| {
530             metadata.packages.iter().find(|pkg| pkg.id == dep.pkg).unwrap_or_else(|| {
531                 panic!("could not find dep `{}` for pkg `{}` in resolve", dep.pkg, pkg_id)
532             })
533         })
534         .collect()
535 }
536
537 /// Finds a package with the given name.
538 fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
539     let mut i = metadata.packages.iter().filter(|p| p.name == name);
540     let result =
541         i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
542     assert!(i.next().is_none(), "more than one package found for `{name}`");
543     result
544 }
545
546 /// Finds all the packages that are in the rust runtime.
547 fn compute_runtime_crates<'a>(metadata: &'a Metadata) -> HashSet<&'a PackageId> {
548     let resolve = metadata.resolve.as_ref().unwrap();
549     let mut result = HashSet::new();
550     for name in RUNTIME_CRATES {
551         let id = &pkg_from_name(metadata, name).id;
552         normal_deps_of_r(resolve, id, &mut result);
553     }
554     result
555 }
556
557 /// Recursively find all normal dependencies.
558 fn normal_deps_of_r<'a>(
559     resolve: &'a Resolve,
560     pkg_id: &'a PackageId,
561     result: &mut HashSet<&'a PackageId>,
562 ) {
563     if !result.insert(pkg_id) {
564         return;
565     }
566     let node = resolve
567         .nodes
568         .iter()
569         .find(|n| &n.id == pkg_id)
570         .unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
571     for dep in &node.deps {
572         normal_deps_of_r(resolve, &dep.pkg, result);
573     }
574 }
575
576 fn check_rustfix(metadata: &Metadata, bad: &mut bool) {
577     let cargo = pkg_from_name(metadata, "cargo");
578     let compiletest = pkg_from_name(metadata, "compiletest");
579     let cargo_deps = deps_of(metadata, &cargo.id);
580     let compiletest_deps = deps_of(metadata, &compiletest.id);
581     let cargo_rustfix = cargo_deps.iter().find(|p| p.name == "rustfix").unwrap();
582     let compiletest_rustfix = compiletest_deps.iter().find(|p| p.name == "rustfix").unwrap();
583     if cargo_rustfix.version != compiletest_rustfix.version {
584         tidy_error!(
585             bad,
586             "cargo's rustfix version {} does not match compiletest's rustfix version {}\n\
587              rustfix should be kept in sync, update the cargo side first, and then update \
588              compiletest along with cargo.",
589             cargo_rustfix.version,
590             compiletest_rustfix.version
591         );
592     }
593 }