]> git.proxmox.com Git - rustc.git/blob - src/tools/tidy/src/deps.rs
New upstream version 1.68.2+dfsg1
[rustc.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 }