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