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