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