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