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