1use std::collections::{BTreeSet, HashMap, HashSet};
4use std::fmt::{Display, Formatter};
5use std::fs::{self, read_dir};
6use std::path::Path;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::{io, thread};
9
10use cargo_metadata::semver::Version;
11use cargo_metadata::{Metadata, Package, PackageId};
12
13use crate::diagnostics::{RunningCheck, TidyCtx};
14
15#[derive(Clone, Copy)]
16struct ListLocation {
17 path: &'static str,
18 line: u32,
19}
20
21impl Display for ListLocation {
22 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{}:{}", self.path, self.line)
24 }
25}
26
27macro_rules! location {
29 (+ $offset:literal) => {
30 ListLocation { path: file!(), line: line!() + $offset }
31 };
32}
33
34#[rustfmt::skip]
37const LICENSES: &[&str] = &[
38 "(MIT OR Apache-2.0) AND MIT",
40 "0BSD OR MIT OR Apache-2.0", "Apache-2.0 / MIT",
42 "Apache-2.0 OR ISC OR MIT",
43 "Apache-2.0 OR MIT",
44 "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", "Apache-2.0/MIT",
46 "BSD-2-Clause OR Apache-2.0 OR MIT", "BSD-2-Clause OR MIT OR Apache-2.0",
48 "BSD-3-Clause/MIT",
49 "CC0-1.0 OR MIT-0 OR Apache-2.0",
50 "ISC",
51 "MIT / Apache-2.0",
52 "MIT AND (MIT OR Apache-2.0)",
53 "MIT AND Apache-2.0 WITH LLVM-exception AND (MIT OR Apache-2.0)", "MIT OR Apache-2.0 OR BSD-1-Clause",
55 "MIT OR Apache-2.0 OR LGPL-2.1-or-later", "MIT OR Apache-2.0 OR Zlib", "MIT OR Apache-2.0",
58 "MIT OR Zlib OR Apache-2.0", "MIT",
60 "MIT/Apache-2.0",
61 "Unlicense OR MIT",
62 "Unlicense/MIT",
63 "Zlib", ];
66
67#[rustfmt::skip]
69const LICENSES_TOOLS: &[&str] = &[
70 "(Apache-2.0 OR MIT) AND BSD-3-Clause",
72 "(MIT OR Apache-2.0) AND Unicode-3.0", "(MIT OR Apache-2.0) AND Unicode-DFS-2016", "0BSD",
75 "Apache-2.0 AND ISC",
76 "Apache-2.0 OR BSL-1.0", "Apache-2.0 OR GPL-2.0-only",
78 "Apache-2.0 WITH LLVM-exception",
79 "Apache-2.0",
80 "BSD-2-Clause",
81 "BSD-3-Clause",
82 "CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception",
83 "CC0-1.0",
84 "Unicode-3.0", "Unicode-DFS-2016", "Zlib OR Apache-2.0 OR MIT", "Zlib",
88 ];
90
91type ExceptionList = &'static [(&'static str, &'static str)];
92
93#[derive(Clone, Copy)]
94pub(crate) struct WorkspaceInfo<'a> {
95 pub(crate) path: &'a str,
97 pub(crate) exceptions: ExceptionList,
99 allowed_deps: Option<PermittedDeps<'a>>,
102 pub(crate) submodules: &'a [&'a str],
104}
105
106#[derive(Clone, Copy)]
107struct PermittedDeps<'a> {
108 roots: Option<&'a [&'a str]>,
111 deps: &'a [&'a str],
113 deps_loc: ListLocation,
115}
116
117impl<'a> PermittedDeps<'a> {
118 const fn new(
119 roots: Option<&'a [&'a str]>,
120 deps: &'a [&'a str],
121 deps_loc: ListLocation,
122 ) -> Self {
123 Self { roots, deps, deps_loc }
124 }
125}
126
127const WORKSPACE_LOCATION: ListLocation = location!(+4);
128
129pub(crate) const WORKSPACES: &[WorkspaceInfo<'static>] = &[
132 WorkspaceInfo {
134 path: ".",
135 exceptions: EXCEPTIONS,
136 allowed_deps: Some(PermittedDeps::new(
137 Some(&["rustc-main"]),
138 PERMITTED_RUSTC_DEPENDENCIES,
139 PERMITTED_RUSTC_DEPS_LOCATION,
140 )),
141 submodules: &[],
142 },
143 WorkspaceInfo {
144 path: "library",
145 exceptions: EXCEPTIONS_STDLIB,
146 allowed_deps: Some(PermittedDeps::new(
147 None,
148 PERMITTED_STDLIB_DEPENDENCIES,
149 PERMITTED_STDLIB_DEPS_LOCATION,
150 )),
151 submodules: &[],
152 },
153 WorkspaceInfo {
154 path: "library/stdarch",
155 exceptions: EXCEPTIONS_STDARCH,
156 allowed_deps: None,
157 submodules: &[],
158 },
159 WorkspaceInfo {
160 path: "compiler/rustc_codegen_cranelift",
161 exceptions: EXCEPTIONS_CRANELIFT,
162 allowed_deps: Some(PermittedDeps::new(
163 None,
164 PERMITTED_CRANELIFT_DEPENDENCIES,
165 PERMITTED_CRANELIFT_DEPS_LOCATION,
166 )),
167 submodules: &[],
168 },
169 WorkspaceInfo {
170 path: "compiler/rustc_codegen_gcc",
171 exceptions: EXCEPTIONS_GCC,
172 allowed_deps: None,
173 submodules: &[],
174 },
175 WorkspaceInfo {
176 path: "src/bootstrap",
177 exceptions: EXCEPTIONS_BOOTSTRAP,
178 allowed_deps: None,
179 submodules: &[],
180 },
181 WorkspaceInfo {
182 path: "src/tools/cargo",
183 exceptions: EXCEPTIONS_CARGO,
184 allowed_deps: None,
185 submodules: &["src/tools/cargo"],
186 },
187 WorkspaceInfo {
199 path: "src/tools/rust-analyzer",
200 exceptions: EXCEPTIONS_RUST_ANALYZER,
201 allowed_deps: None,
202 submodules: &[],
203 },
204 WorkspaceInfo {
205 path: "src/tools/rustbook",
206 exceptions: EXCEPTIONS_RUSTBOOK,
207 allowed_deps: None,
208 submodules: &["src/doc/book", "src/doc/reference"],
209 },
210 WorkspaceInfo {
211 path: "src/tools/rustc-perf",
212 exceptions: EXCEPTIONS_RUSTC_PERF,
213 allowed_deps: None,
214 submodules: &["src/tools/rustc-perf"],
215 },
216 WorkspaceInfo {
217 path: "tests/run-make-cargo/uefi-qemu/uefi_qemu_test",
218 exceptions: EXCEPTIONS_UEFI_QEMU_TEST,
219 allowed_deps: None,
220 submodules: &[],
221 },
222];
223
224#[rustfmt::skip]
229const EXCEPTIONS: ExceptionList = &[
230 ("colored", "MPL-2.0"), ("option-ext", "MPL-2.0"), ];
235
236#[rustfmt::skip]
241const EXCEPTIONS_STDLIB: ExceptionList = &[
242 ("fortanix-sgx-abi", "MPL-2.0"), ];
246
247const EXCEPTIONS_CARGO: ExceptionList = &[
248 ("bitmaps", "MPL-2.0+"),
250 ("im-rc", "MPL-2.0+"),
251 ("sized-chunks", "MPL-2.0+"),
252 ];
254
255const EXCEPTIONS_RUST_ANALYZER: ExceptionList = &[
256 ("option-ext", "MPL-2.0"),
258 ];
260
261const EXCEPTIONS_RUSTC_PERF: ExceptionList = &[
262 ("aws-lc-rs", "ISC AND (Apache-2.0 OR ISC)"),
264 (
265 "aws-lc-sys",
266 "ISC AND (Apache-2.0 OR ISC) AND Apache-2.0 AND MIT AND BSD-3-Clause AND (Apache-2.0 OR ISC OR MIT) AND (Apache-2.0 OR ISC OR MIT-0)",
267 ),
268 ("brotli", "BSD-3-Clause AND MIT"),
269 ("fast-srgb8", "MIT OR Apache-2.0 OR CC0-1.0"),
270 ("inferno", "CDDL-1.0"),
271 ("option-ext", "MPL-2.0"),
272 ("wasite", "Apache-2.0 OR BSL-1.0 OR MIT"),
273 ("webpki-root-certs", "CDLA-Permissive-2.0"),
274 ("whoami", "Apache-2.0 OR BSL-1.0 OR MIT"),
275 ];
277
278const EXCEPTIONS_RUSTBOOK: ExceptionList = &[
279 ("font-awesome-as-a-crate", "CC-BY-4.0 AND MIT"),
281 ("mdbook-core", "MPL-2.0"),
282 ("mdbook-driver", "MPL-2.0"),
283 ("mdbook-html", "MPL-2.0"),
284 ("mdbook-markdown", "MPL-2.0"),
285 ("mdbook-preprocessor", "MPL-2.0"),
286 ("mdbook-renderer", "MPL-2.0"),
287 ("mdbook-summary", "MPL-2.0"),
288 ];
290
291const EXCEPTIONS_STDARCH: ExceptionList = &[];
292
293const EXCEPTIONS_CRANELIFT: ExceptionList = &[];
294
295const EXCEPTIONS_GCC: ExceptionList = &[
296 ("gccjit", "GPL-3.0"),
298 ("gccjit_sys", "GPL-3.0"),
299 ];
301
302const EXCEPTIONS_BOOTSTRAP: ExceptionList = &[];
303
304const EXCEPTIONS_UEFI_QEMU_TEST: ExceptionList = &[];
305
306const PERMITTED_RUSTC_DEPS_LOCATION: ListLocation = location!(+6);
307
308const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
313 "adler2",
315 "aho-corasick",
316 "allocator-api2", "annotate-snippets",
318 "anstream",
319 "anstyle",
320 "anstyle-parse",
321 "anstyle-query",
322 "anstyle-wincon",
323 "ar_archive_writer",
324 "arrayref",
325 "arrayvec",
326 "bitflags",
327 "blake3",
328 "block-buffer",
329 "block2",
330 "bstr",
331 "cc",
332 "cfg-if",
333 "cfg_aliases",
334 "colorchoice",
335 "constant_time_eq",
336 "cpufeatures",
337 "crc32fast",
338 "crossbeam-deque",
339 "crossbeam-epoch",
340 "crossbeam-utils",
341 "crypto-common",
342 "ctrlc",
343 "darling",
344 "darling_core",
345 "darling_macro",
346 "datafrog",
347 "derive-where",
348 "derive_setters",
349 "digest",
350 "dispatch2",
351 "displaydoc",
352 "dissimilar",
353 "dyn-clone",
354 "either",
355 "elsa",
356 "ena",
357 "equivalent",
358 "errno",
359 "expect-test",
360 "fastrand",
361 "find-msvc-tools",
362 "flate2",
363 "fluent-bundle",
364 "fluent-langneg",
365 "fluent-syntax",
366 "fnv",
367 "foldhash",
368 "generic-array",
369 "getopts",
370 "getrandom",
371 "gimli",
372 "gsgdt",
373 "hashbrown",
374 "icu_list",
375 "icu_locale_core",
376 "icu_locale_fallback",
377 "icu_locale_fallback_data",
378 "icu_provider",
379 "ident_case",
380 "indexmap",
381 "intl-memoizer",
382 "intl_pluralrules",
383 "is_terminal_polyfill",
384 "itertools",
385 "itoa",
386 "jiff",
387 "jiff-static",
388 "jiff-tzdb",
389 "jiff-tzdb-platform",
390 "jobserver",
391 "lazy_static",
392 "leb128fmt",
393 "libc",
394 "libloading",
395 "linux-raw-sys",
396 "litemap",
397 "lock_api",
398 "log",
399 "matchers",
400 "md-5",
401 "measureme",
402 "memchr",
403 "memmap2",
404 "miniz_oxide",
405 "nix",
406 "nu-ansi-term",
407 "objc2",
408 "objc2-encode",
409 "object",
410 "odht",
411 "once_cell",
412 "once_cell_polyfill",
413 "parking_lot",
414 "parking_lot_core",
415 "pathdiff",
416 "perf-event-open-sys",
417 "pin-project-lite",
418 "polonius-engine",
419 "portable-atomic", "portable-atomic-util",
421 "potential_utf",
422 "ppv-lite86",
423 "proc-macro-hack",
424 "proc-macro2",
425 "pulldown-cmark",
426 "pulldown-cmark-escape",
427 "punycode",
428 "quote",
429 "r-efi",
430 "rand",
431 "rand_chacha",
432 "rand_core",
433 "rand_xorshift", "rand_xoshiro",
435 "redox_syscall",
436 "ref-cast",
437 "ref-cast-impl",
438 "regex",
439 "regex-automata",
440 "regex-syntax",
441 "rustc-demangle",
442 "rustc-hash",
443 "rustc-literal-escaper",
444 "rustc-stable-hash",
445 "rustc_apfloat",
446 "rustix",
447 "ruzstd", "ryu",
449 "schemars",
450 "schemars_derive",
451 "scoped-tls",
452 "scopeguard",
453 "self_cell",
454 "semver",
455 "serde",
456 "serde_core",
457 "serde_derive",
458 "serde_derive_internals",
459 "serde_json",
460 "serde_path_to_error",
461 "sha1",
462 "sha2",
463 "sharded-slab",
464 "shlex",
465 "simd-adler32",
466 "smallvec",
467 "stable_deref_trait",
468 "static_assertions",
469 "strsim",
470 "syn",
471 "synstructure",
472 "tempfile",
473 "termize",
474 "thin-vec",
475 "thiserror",
476 "thiserror-impl",
477 "thorin-dwp",
478 "thread_local",
479 "tikv-jemalloc-sys",
480 "tinystr",
481 "tinyvec",
482 "tinyvec_macros",
483 "tracing",
484 "tracing-attributes",
485 "tracing-core",
486 "tracing-log",
487 "tracing-serde",
488 "tracing-subscriber",
489 "tracing-tree",
490 "twox-hash",
491 "type-map",
492 "typenum",
493 "unic-langid",
494 "unic-langid-impl",
495 "unic-langid-macros",
496 "unic-langid-macros-impl",
497 "unicase",
498 "unicode-ident",
499 "unicode-normalization",
500 "unicode-properties",
501 "unicode-script",
502 "unicode-security",
503 "unicode-width",
504 "utf8parse",
505 "valuable",
506 "version_check",
507 "wasi",
508 "wasm-encoder",
509 "wasmparser",
510 "windows",
511 "windows-collections",
512 "windows-core",
513 "windows-future",
514 "windows-implement",
515 "windows-interface",
516 "windows-link",
517 "windows-numerics",
518 "windows-result",
519 "windows-strings",
520 "windows-sys",
521 "windows-threading",
522 "wit-bindgen-rt@0.39.0", "writeable",
524 "yoke",
525 "yoke-derive",
526 "zerocopy",
527 "zerocopy-derive",
528 "zerofrom",
529 "zerofrom-derive",
530 "zerotrie",
531 "zerovec",
532 "zerovec-derive",
533 "zlib-rs",
534 ];
536
537const PERMITTED_STDLIB_DEPS_LOCATION: ListLocation = location!(+2);
538
539const PERMITTED_STDLIB_DEPENDENCIES: &[&str] = &[
540 "addr2line",
542 "adler2",
543 "cc",
544 "cfg-if",
545 "compiler_builtins",
546 "dlmalloc",
547 "find-msvc-tools", "foldhash", "fortanix-sgx-abi",
550 "getopts",
551 "gimli",
552 "hashbrown",
553 "hermit-abi",
554 "libc",
555 "memchr",
556 "miniz_oxide",
557 "moto-rt",
558 "object",
559 "r-efi",
560 "r-efi-alloc",
561 "rand",
562 "rand_core",
563 "rand_xorshift",
564 "rustc-demangle",
565 "rustc-literal-escaper",
566 "shlex",
567 "unwinding",
568 "vex-sdk",
569 "wasip1",
570 "wasip2",
571 "wasip3",
572 "windows-link",
573 "windows-sys@0.61.100", "wit-bindgen",
575 ];
577
578const PERMITTED_CRANELIFT_DEPS_LOCATION: ListLocation = location!(+2);
579
580const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
581 "allocator-api2",
583 "anyhow",
584 "arbitrary",
585 "bitflags",
586 "bumpalo",
587 "cfg-if",
588 "cranelift-assembler-x64",
589 "cranelift-assembler-x64-meta",
590 "cranelift-bforest",
591 "cranelift-bitset",
592 "cranelift-codegen",
593 "cranelift-codegen-meta",
594 "cranelift-codegen-shared",
595 "cranelift-control",
596 "cranelift-entity",
597 "cranelift-frontend",
598 "cranelift-isle",
599 "cranelift-jit",
600 "cranelift-module",
601 "cranelift-native",
602 "cranelift-object",
603 "cranelift-srcgen",
604 "crc32fast",
605 "equivalent",
606 "fnv",
607 "foldhash",
608 "gimli",
609 "hashbrown",
610 "heck",
611 "indexmap",
612 "libc",
613 "libloading",
614 "libm",
615 "log",
616 "mach2",
617 "memchr",
618 "memmap2",
619 "object",
620 "proc-macro2",
621 "quote",
622 "regalloc2",
623 "region",
624 "rustc-hash",
625 "serde",
626 "serde_core",
627 "serde_derive",
628 "smallvec",
629 "stable_deref_trait",
630 "syn",
631 "target-lexicon",
632 "unicode-ident",
633 "wasmtime-internal-core",
634 "wasmtime-internal-jit-icache-coherence",
635 "windows-link",
636 "windows-sys",
637 "windows-targets",
638 "windows_aarch64_gnullvm",
639 "windows_aarch64_msvc",
640 "windows_i686_gnu",
641 "windows_i686_gnullvm",
642 "windows_i686_msvc",
643 "windows_x86_64_gnu",
644 "windows_x86_64_gnullvm",
645 "windows_x86_64_msvc",
646 ];
648
649pub fn check(root: &Path, cargo: &Path, sem: &'_ crate::Semaphore, tidy_ctx: TidyCtx) {
654 let mut check = tidy_ctx.start_check("deps");
655 let bless = tidy_ctx.is_bless_enabled();
656 let is_ci = tidy_ctx.is_running_on_ci();
657
658 let checked_runtime_licenses = AtomicBool::new(false);
659
660 check_proc_macro_dep_list(root, cargo, bless, &mut check);
661
662 thread::scope(|s| {
663 for (i, WorkspaceInfo { path, exceptions, allowed_deps: crates_and_deps, submodules }) in
664 WORKSPACES.iter().enumerate()
665 {
666 let check = ✓
667 let checked_runtime_licenses = &checked_runtime_licenses;
668 let guard = if i > 0 { Some(sem.acquire()) } else { None };
672 s.spawn(move || {
673 if has_missing_submodule(root, submodules, is_ci) {
674 return;
675 }
676
677 if !root.join(path).join("Cargo.lock").exists() {
678 check.error(format!("the `{path}` workspace doesn't have a Cargo.lock"));
679 return;
680 }
681
682 let mut cmd = cargo_metadata::MetadataCommand::new();
683 cmd.cargo_path(cargo)
684 .manifest_path(root.join(path).join("Cargo.toml"))
685 .features(cargo_metadata::CargoOpt::AllFeatures)
686 .other_options(vec!["--locked".to_owned()]);
687 let metadata = t!(cmd.exec());
688
689 let absolute_root =
691 if *path == "." { root.to_path_buf() } else { t!(std::path::absolute(root.join(path))) };
692 let absolute_root_real = t!(std::path::absolute(&metadata.workspace_root));
693 if absolute_root_real != absolute_root {
694 check.error(format!("{path} is part of another workspace ({} != {}), remove from `WORKSPACES` ({WORKSPACE_LOCATION})", absolute_root.display(), absolute_root_real.display()));
695 }
696 check_license_exceptions(&metadata, path, exceptions, &check);
697 if let Some(PermittedDeps { roots, deps: permitted_deps, deps_loc }) = crates_and_deps {
698 let descr = roots.map_or(path, |roots| roots.get(0).unwrap_or(&path));
699 check_permitted_dependencies(
700 &metadata,
701 descr,
702 permitted_deps,
703 *roots,
704 *deps_loc,
705 &check,
706 );
707 }
708
709
710 if *path == "library" {
711 check_runtime_license_exceptions(&metadata, &check);
712 check_runtime_no_duplicate_dependencies(&metadata, &check);
713 check_runtime_no_proc_macros(&metadata, &check);
714 checked_runtime_licenses.store(true, Ordering::Relaxed);
715 }
716
717 let _guard = guard;
718 });
719 }
720 });
721
722 assert!(checked_runtime_licenses.load(Ordering::Relaxed));
725}
726
727fn check_proc_macro_dep_list(root: &Path, cargo: &Path, bless: bool, check: &mut RunningCheck) {
729 if std::env::var("RUSTC").is_err() {
730 panic!("tidy must be run under bootstrap (./x test tidy), not as a standalone command");
731 }
732 let mut cmd = cargo_metadata::MetadataCommand::new();
733 cmd.cargo_path(cargo)
734 .manifest_path(root.join("Cargo.toml"))
735 .features(cargo_metadata::CargoOpt::AllFeatures)
736 .other_options(vec!["--locked".to_owned()]);
737 let metadata = t!(cmd.exec());
738 let is_proc_macro_pkg = |pkg: &Package| pkg.targets.iter().any(|target| target.is_proc_macro());
739
740 let mut proc_macro_deps = HashSet::new();
741 for pkg in metadata.packages.iter().filter(|pkg| is_proc_macro_pkg(pkg)) {
742 deps_of(&metadata, &pkg.id, &mut proc_macro_deps);
743 }
744 proc_macro_deps.retain(|pkg| !is_proc_macro_pkg(&metadata[pkg]));
746 let proc_macro_deps = proc_macro_deps
751 .into_iter()
752 .filter_map(|dep| {
753 metadata[dep].targets.iter().find_map(|target| target.is_lib().then_some(&target.name))
754 })
755 .collect::<BTreeSet<_>>();
756
757 let expected = {
758 use std::fmt::Write;
759
760 const HEADER: &str = "\
761/// Do not update manually - use `./x.py test tidy --bless`
762/// Holds all direct and indirect dependencies of proc-macro crates in tree.
763/// See <https://github.com/rust-lang/rust/issues/134863>
764pub static CRATES: &[&str] = &[
765 // tidy-alphabetical-start
766";
767 const FOOTER: &str = " // tidy-alphabetical-end
768];
769";
770
771 let mut buf = String::with_capacity(4096);
772 buf.push_str(HEADER);
773 for dep in proc_macro_deps {
774 writeln!(buf, " {dep:?},").unwrap();
775 }
776 buf.push_str(FOOTER);
777 buf
778 };
779
780 const PROC_MACRO_DEPS_RS: &str = "src/bootstrap/src/utils/proc_macro_deps.rs";
781 let proc_macro_deps_rs_path = &root.join(PROC_MACRO_DEPS_RS);
782 let actual = match fs::read_to_string(proc_macro_deps_rs_path) {
783 Ok(actual) => actual,
784 Err(e) => {
785 if e.kind() == io::ErrorKind::NotFound {
786 check.error(format!(
787 "`{PROC_MACRO_DEPS_RS}` not found; has it been moved or renamed?"
788 ));
789 } else {
790 check.error(format!("`{PROC_MACRO_DEPS_RS}` could not be read: {e:?}"));
791 }
792 return;
793 }
794 };
795
796 if actual != expected {
797 if bless {
798 fs::write(proc_macro_deps_rs_path, &expected).unwrap();
799 } else {
800 let diff = similar::TextDiff::from_lines(&actual, &expected);
801 let mut unified = diff.unified_diff();
802 unified.header(PROC_MACRO_DEPS_RS, "(expected)");
803
804 check.error(format!("`{PROC_MACRO_DEPS_RS}` is not up-to-date:\n{unified}"));
805 check.message("Run `./x.py test tidy --bless` to regenerate the list");
806 }
807 }
808}
809
810pub fn has_missing_submodule(root: &Path, submodules: &[&str], is_ci: bool) -> bool {
814 !is_ci
815 && submodules.iter().any(|submodule| {
816 let path = root.join(submodule);
817 !path.exists()
818 || read_dir(path).unwrap().next().is_none()
820 })
821}
822
823fn check_runtime_license_exceptions(metadata: &Metadata, check: &RunningCheck) {
828 for pkg in &metadata.packages {
829 if pkg.source.is_none() {
830 continue;
832 }
833 let license = match &pkg.license {
834 Some(license) => license,
835 None => {
836 check
837 .error(format!("dependency `{}` does not define a license expression", pkg.id));
838 continue;
839 }
840 };
841 if !LICENSES.contains(&license.as_str()) {
842 if *pkg.name == "fortanix-sgx-abi" && pkg.license.as_deref() == Some("MPL-2.0") {
847 continue;
848 }
849
850 check.error(format!("invalid license `{}` in `{}`", license, pkg.id));
851 }
852 }
853}
854
855fn check_license_exceptions(
859 metadata: &Metadata,
860 workspace: &str,
861 exceptions: &[(&str, &str)],
862 check: &RunningCheck,
863) {
864 for (name, license) in exceptions {
866 if !metadata.packages.iter().any(|p| *p.name == *name) {
868 check.error(format!(
869 "could not find exception package `{name}` in workspace `{workspace}`\n\
870 Remove from EXCEPTIONS list if it is no longer used.",
871 ));
872 }
873 for pkg in metadata.packages.iter().filter(|p| *p.name == *name) {
875 match &pkg.license {
876 None => {
877 check.error(format!(
878 "dependency exception `{}` in workspace `{workspace}` does not declare a license expression",
879 pkg.id
880 ));
881 }
882 Some(pkg_license) => {
883 if pkg_license.as_str() != *license {
884 check.error(format!(r#"dependency exception `{name}` license in workspace `{workspace}` has changed
885 previously `{license}` now `{pkg_license}`
886 update EXCEPTIONS for the new license
887"#));
888 }
889 }
890 }
891 }
892 if LICENSES.contains(license) || LICENSES_TOOLS.contains(license) {
893 check.error(format!(
894 "dependency exception `{name}` is not necessary. `{license}` is an allowed license"
895 ));
896 }
897 }
898
899 let exception_names: Vec<_> = exceptions.iter().map(|(name, _license)| *name).collect();
900
901 for pkg in &metadata.packages {
903 if pkg.source.is_none() {
904 continue;
906 }
907 if exception_names.contains(&pkg.name.as_str()) {
908 continue;
909 }
910 let license = match &pkg.license {
911 Some(license) => license,
912 None => {
913 check.error(format!(
914 "dependency `{}` in workspace `{workspace}` does not define a license expression",
915 pkg.id
916 ));
917 continue;
918 }
919 };
920 if !LICENSES.contains(&license.as_str()) && !LICENSES_TOOLS.contains(&license.as_str()) {
921 check.error(format!(
922 "invalid license `{}` for package `{}` in workspace `{workspace}`",
923 license, pkg.id
924 ));
925 }
926 }
927}
928
929fn check_runtime_no_duplicate_dependencies(metadata: &Metadata, check: &RunningCheck) {
930 let mut seen_pkgs = HashSet::new();
931 for pkg in &metadata.packages {
932 if pkg.source.is_none() {
933 continue;
934 }
935
936 if !seen_pkgs.insert(&*pkg.name) {
937 check.error(format!(
938 "duplicate package `{}` is not allowed for the standard library",
939 pkg.name
940 ));
941 }
942 }
943}
944
945fn check_runtime_no_proc_macros(metadata: &Metadata, check: &RunningCheck) {
946 for pkg in &metadata.packages {
947 if pkg.targets.iter().any(|target| target.is_proc_macro()) {
948 check.error(format!(
949 "proc macro `{}` is not allowed as standard library dependency.\n\
950 Using proc macros in the standard library would break cross-compilation \
951 as proc-macros don't get shipped for the host tuple.",
952 pkg.name
953 ));
954 }
955 }
956}
957
958fn check_permitted_dependencies(
963 metadata: &Metadata,
964 descr: &str,
965 permitted_dependencies: &[&'static str],
966 restricted_dependency_crates: Option<&[&'static str]>,
967 permitted_location: ListLocation,
968 check: &RunningCheck,
969) {
970 let mut has_permitted_dep_error = false;
971 let mut deps = HashSet::new();
972 if let Some(restricted_dependency_crates) = restricted_dependency_crates {
973 for to_check in restricted_dependency_crates {
974 let to_check = pkg_from_name(metadata, to_check);
975 deps_of(metadata, &to_check.id, &mut deps);
976 }
977 } else {
978 for to_check in &metadata.packages {
979 deps_of(metadata, &to_check.id, &mut deps);
980 }
981 }
982
983 for permitted in permitted_dependencies {
985 fn compare(pkg: &Package, permitted: &str) -> bool {
986 if let Some((name, version)) = permitted.split_once("@") {
987 let Ok(version) = Version::parse(version) else {
988 return false;
989 };
990 *pkg.name == name && pkg.version == version
991 } else {
992 *pkg.name == permitted
993 }
994 }
995 if !deps.iter().any(|dep_id| compare(pkg_from_id(metadata, dep_id), permitted)) {
996 check.error(format!(
997 "could not find allowed package `{permitted}`\n\
998 Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
999 ));
1000 has_permitted_dep_error = true;
1001 }
1002 }
1003
1004 let permitted_dependencies: HashMap<_, _> = permitted_dependencies
1006 .iter()
1007 .map(|s| {
1008 if let Some((name, version)) = s.split_once('@') {
1009 (name, Version::parse(version).ok())
1010 } else {
1011 (*s, None)
1012 }
1013 })
1014 .collect();
1015
1016 for dep in deps {
1017 let dep = pkg_from_id(metadata, dep);
1018 if dep.source.is_some() {
1020 let is_eq = if let Some(version) = permitted_dependencies.get(dep.name.as_str()) {
1021 if let Some(version) = version { version == &dep.version } else { true }
1022 } else {
1023 false
1024 };
1025 if !is_eq {
1026 check.error(format!("Dependency for {descr} not explicitly permitted: {}", dep.id));
1027 has_permitted_dep_error = true;
1028 }
1029 }
1030 }
1031
1032 if has_permitted_dep_error {
1033 eprintln!("Go to `{}:{}` for the list.", permitted_location.path, permitted_location.line);
1034 }
1035}
1036
1037fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
1039 let mut i = metadata.packages.iter().filter(|p| *p.name == name);
1040 let result =
1041 i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
1042 assert!(i.next().is_none(), "more than one package found for `{name}`");
1043 result
1044}
1045
1046fn pkg_from_id<'a>(metadata: &'a Metadata, id: &PackageId) -> &'a Package {
1047 metadata.packages.iter().find(|p| &p.id == id).unwrap()
1048}
1049
1050fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId, result: &mut HashSet<&'a PackageId>) {
1052 if !result.insert(pkg_id) {
1053 return;
1054 }
1055 let node = metadata
1056 .resolve
1057 .as_ref()
1058 .unwrap()
1059 .nodes
1060 .iter()
1061 .find(|n| &n.id == pkg_id)
1062 .unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
1063 for dep in &node.deps {
1064 deps_of(metadata, &dep.pkg, result);
1065 }
1066}