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 "foldhash", "fortanix-sgx-abi",
531 "getopts",
532 "gimli",
533 "hashbrown",
534 "hermit-abi",
535 "libc",
536 "memchr",
537 "miniz_oxide",
538 "moto-rt",
539 "object",
540 "r-efi",
541 "r-efi-alloc",
542 "rand",
543 "rand_core",
544 "rand_xorshift",
545 "rustc-demangle",
546 "rustc-literal-escaper",
547 "shlex",
548 "unwinding",
549 "vex-sdk",
550 "wasip1",
551 "wasip2",
552 "wasip3",
553 "windows-link",
554 "windows-sys@0.61.100", "wit-bindgen",
556 ];
558
559const PERMITTED_CRANELIFT_DEPS_LOCATION: ListLocation = location!(+2);
560
561const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
562 "allocator-api2",
564 "anyhow",
565 "arbitrary",
566 "bitflags",
567 "bumpalo",
568 "cfg-if",
569 "cranelift-assembler-x64",
570 "cranelift-assembler-x64-meta",
571 "cranelift-bforest",
572 "cranelift-bitset",
573 "cranelift-codegen",
574 "cranelift-codegen-meta",
575 "cranelift-codegen-shared",
576 "cranelift-control",
577 "cranelift-entity",
578 "cranelift-frontend",
579 "cranelift-isle",
580 "cranelift-jit",
581 "cranelift-module",
582 "cranelift-native",
583 "cranelift-object",
584 "cranelift-srcgen",
585 "crc32fast",
586 "equivalent",
587 "fnv",
588 "foldhash",
589 "gimli",
590 "hashbrown",
591 "heck",
592 "indexmap",
593 "libc",
594 "libloading",
595 "libm",
596 "log",
597 "mach2",
598 "memchr",
599 "memmap2",
600 "object",
601 "proc-macro2",
602 "quote",
603 "regalloc2",
604 "region",
605 "rustc-hash",
606 "serde",
607 "serde_core",
608 "serde_derive",
609 "smallvec",
610 "stable_deref_trait",
611 "syn",
612 "target-lexicon",
613 "unicode-ident",
614 "wasmtime-internal-core",
615 "wasmtime-internal-jit-icache-coherence",
616 "windows-link",
617 "windows-sys",
618 "windows-targets",
619 "windows_aarch64_gnullvm",
620 "windows_aarch64_msvc",
621 "windows_i686_gnu",
622 "windows_i686_gnullvm",
623 "windows_i686_msvc",
624 "windows_x86_64_gnu",
625 "windows_x86_64_gnullvm",
626 "windows_x86_64_msvc",
627 ];
629
630pub fn check(root: &Path, cargo: &Path, tidy_ctx: TidyCtx) {
635 let mut check = tidy_ctx.start_check("deps");
636 let bless = tidy_ctx.is_bless_enabled();
637
638 let mut checked_runtime_licenses = false;
639
640 check_proc_macro_dep_list(root, cargo, bless, &mut check);
641
642 for &WorkspaceInfo { path, exceptions, crates_and_deps, submodules } in WORKSPACES {
643 if has_missing_submodule(root, submodules, tidy_ctx.is_running_on_ci()) {
644 continue;
645 }
646
647 if !root.join(path).join("Cargo.lock").exists() {
648 check.error(format!("the `{path}` workspace doesn't have a Cargo.lock"));
649 continue;
650 }
651
652 let mut cmd = cargo_metadata::MetadataCommand::new();
653 cmd.cargo_path(cargo)
654 .manifest_path(root.join(path).join("Cargo.toml"))
655 .features(cargo_metadata::CargoOpt::AllFeatures)
656 .other_options(vec!["--locked".to_owned()]);
657 let metadata = t!(cmd.exec());
658
659 let absolute_root =
661 if path == "." { root.to_path_buf() } else { t!(std::path::absolute(root.join(path))) };
662 let absolute_root_real = t!(std::path::absolute(&metadata.workspace_root));
663 if absolute_root_real != absolute_root {
664 check.error(format!("{path} is part of another workspace ({} != {}), remove from `WORKSPACES` ({WORKSPACE_LOCATION})", absolute_root.display(), absolute_root_real.display()));
665 }
666 check_license_exceptions(&metadata, path, exceptions, &mut check);
667 if let Some((crates, permitted_deps, location)) = crates_and_deps {
668 let descr = crates.get(0).unwrap_or(&path);
669 check_permitted_dependencies(
670 &metadata,
671 descr,
672 permitted_deps,
673 crates,
674 location,
675 &mut check,
676 );
677 }
678
679 if path == "library" {
680 check_runtime_license_exceptions(&metadata, &mut check);
681 check_runtime_no_duplicate_dependencies(&metadata, &mut check);
682 check_runtime_no_proc_macros(&metadata, &mut check);
683 checked_runtime_licenses = true;
684 }
685 }
686
687 assert!(checked_runtime_licenses);
690}
691
692fn check_proc_macro_dep_list(root: &Path, cargo: &Path, bless: bool, check: &mut RunningCheck) {
694 if std::env::var("RUSTC").is_err() {
695 panic!("tidy must be run under bootstrap (./x test tidy), not as a standalone command");
696 }
697 let mut cmd = cargo_metadata::MetadataCommand::new();
698 cmd.cargo_path(cargo)
699 .manifest_path(root.join("Cargo.toml"))
700 .features(cargo_metadata::CargoOpt::AllFeatures)
701 .other_options(vec!["--locked".to_owned()]);
702 let metadata = t!(cmd.exec());
703 let is_proc_macro_pkg = |pkg: &Package| pkg.targets.iter().any(|target| target.is_proc_macro());
704
705 let mut proc_macro_deps = HashSet::new();
706 for pkg in metadata.packages.iter().filter(|pkg| is_proc_macro_pkg(pkg)) {
707 deps_of(&metadata, &pkg.id, &mut proc_macro_deps);
708 }
709 proc_macro_deps.retain(|pkg| !is_proc_macro_pkg(&metadata[pkg]));
711 let proc_macro_deps = proc_macro_deps
716 .into_iter()
717 .filter_map(|dep| {
718 metadata[dep].targets.iter().find_map(|target| target.is_lib().then_some(&target.name))
719 })
720 .collect::<BTreeSet<_>>();
721
722 let expected = {
723 use std::fmt::Write;
724
725 const HEADER: &str = "\
726/// Do not update manually - use `./x.py test tidy --bless`
727/// Holds all direct and indirect dependencies of proc-macro crates in tree.
728/// See <https://github.com/rust-lang/rust/issues/134863>
729pub static CRATES: &[&str] = &[
730 // tidy-alphabetical-start
731";
732 const FOOTER: &str = " // tidy-alphabetical-end
733];
734";
735
736 let mut buf = String::with_capacity(4096);
737 buf.push_str(HEADER);
738 for dep in proc_macro_deps {
739 writeln!(buf, " {dep:?},").unwrap();
740 }
741 buf.push_str(FOOTER);
742 buf
743 };
744
745 const PROC_MACRO_DEPS_RS: &str = "src/bootstrap/src/utils/proc_macro_deps.rs";
746 let proc_macro_deps_rs_path = &root.join(PROC_MACRO_DEPS_RS);
747 let actual = match fs::read_to_string(proc_macro_deps_rs_path) {
748 Ok(actual) => actual,
749 Err(e) => {
750 if e.kind() == io::ErrorKind::NotFound {
751 check.error(format!(
752 "`{PROC_MACRO_DEPS_RS}` not found; has it been moved or renamed?"
753 ));
754 } else {
755 check.error(format!("`{PROC_MACRO_DEPS_RS}` could not be read: {e:?}"));
756 }
757 return;
758 }
759 };
760
761 if actual != expected {
762 if bless {
763 fs::write(proc_macro_deps_rs_path, &expected).unwrap();
764 } else {
765 let diff = similar::TextDiff::from_lines(&actual, &expected);
766 let mut unified = diff.unified_diff();
767 unified.header(PROC_MACRO_DEPS_RS, "(expected)");
768
769 check.error(format!("`{PROC_MACRO_DEPS_RS}` is not up-to-date:\n{unified}"));
770 check.message("Run `./x.py test tidy --bless` to regenerate the list");
771 }
772 }
773}
774
775pub fn has_missing_submodule(root: &Path, submodules: &[&str], is_ci: bool) -> bool {
779 !is_ci
780 && submodules.iter().any(|submodule| {
781 let path = root.join(submodule);
782 !path.exists()
783 || read_dir(path).unwrap().next().is_none()
785 })
786}
787
788fn check_runtime_license_exceptions(metadata: &Metadata, check: &mut RunningCheck) {
793 for pkg in &metadata.packages {
794 if pkg.source.is_none() {
795 continue;
797 }
798 let license = match &pkg.license {
799 Some(license) => license,
800 None => {
801 check
802 .error(format!("dependency `{}` does not define a license expression", pkg.id));
803 continue;
804 }
805 };
806 if !LICENSES.contains(&license.as_str()) {
807 if *pkg.name == "fortanix-sgx-abi" && pkg.license.as_deref() == Some("MPL-2.0") {
812 continue;
813 }
814
815 check.error(format!("invalid license `{}` in `{}`", license, pkg.id));
816 }
817 }
818}
819
820fn check_license_exceptions(
824 metadata: &Metadata,
825 workspace: &str,
826 exceptions: &[(&str, &str)],
827 check: &mut RunningCheck,
828) {
829 for (name, license) in exceptions {
831 if !metadata.packages.iter().any(|p| *p.name == *name) {
833 check.error(format!(
834 "could not find exception package `{name}` in workspace `{workspace}`\n\
835 Remove from EXCEPTIONS list if it is no longer used.",
836 ));
837 }
838 for pkg in metadata.packages.iter().filter(|p| *p.name == *name) {
840 match &pkg.license {
841 None => {
842 check.error(format!(
843 "dependency exception `{}` in workspace `{workspace}` does not declare a license expression",
844 pkg.id
845 ));
846 }
847 Some(pkg_license) => {
848 if pkg_license.as_str() != *license {
849 check.error(format!(r#"dependency exception `{name}` license in workspace `{workspace}` has changed
850 previously `{license}` now `{pkg_license}`
851 update EXCEPTIONS for the new license
852"#));
853 }
854 }
855 }
856 }
857 if LICENSES.contains(license) || LICENSES_TOOLS.contains(license) {
858 check.error(format!(
859 "dependency exception `{name}` is not necessary. `{license}` is an allowed license"
860 ));
861 }
862 }
863
864 let exception_names: Vec<_> = exceptions.iter().map(|(name, _license)| *name).collect();
865
866 for pkg in &metadata.packages {
868 if pkg.source.is_none() {
869 continue;
871 }
872 if exception_names.contains(&pkg.name.as_str()) {
873 continue;
874 }
875 let license = match &pkg.license {
876 Some(license) => license,
877 None => {
878 check.error(format!(
879 "dependency `{}` in workspace `{workspace}` does not define a license expression",
880 pkg.id
881 ));
882 continue;
883 }
884 };
885 if !LICENSES.contains(&license.as_str()) && !LICENSES_TOOLS.contains(&license.as_str()) {
886 check.error(format!(
887 "invalid license `{}` for package `{}` in workspace `{workspace}`",
888 license, pkg.id
889 ));
890 }
891 }
892}
893
894fn check_runtime_no_duplicate_dependencies(metadata: &Metadata, check: &mut RunningCheck) {
895 let mut seen_pkgs = HashSet::new();
896 for pkg in &metadata.packages {
897 if pkg.source.is_none() {
898 continue;
899 }
900
901 if !seen_pkgs.insert(&*pkg.name) {
902 check.error(format!(
903 "duplicate package `{}` is not allowed for the standard library",
904 pkg.name
905 ));
906 }
907 }
908}
909
910fn check_runtime_no_proc_macros(metadata: &Metadata, check: &mut RunningCheck) {
911 for pkg in &metadata.packages {
912 if pkg.targets.iter().any(|target| target.is_proc_macro()) {
913 check.error(format!(
914 "proc macro `{}` is not allowed as standard library dependency.\n\
915 Using proc macros in the standard library would break cross-compilation \
916 as proc-macros don't get shipped for the host tuple.",
917 pkg.name
918 ));
919 }
920 }
921}
922
923fn check_permitted_dependencies(
928 metadata: &Metadata,
929 descr: &str,
930 permitted_dependencies: &[&'static str],
931 restricted_dependency_crates: &[&'static str],
932 permitted_location: ListLocation,
933 check: &mut RunningCheck,
934) {
935 let mut has_permitted_dep_error = false;
936 let mut deps = HashSet::new();
937 for to_check in restricted_dependency_crates {
938 let to_check = pkg_from_name(metadata, to_check);
939 deps_of(metadata, &to_check.id, &mut deps);
940 }
941
942 for permitted in permitted_dependencies {
944 fn compare(pkg: &Package, permitted: &str) -> bool {
945 if let Some((name, version)) = permitted.split_once("@") {
946 let Ok(version) = Version::parse(version) else {
947 return false;
948 };
949 *pkg.name == name && pkg.version == version
950 } else {
951 *pkg.name == permitted
952 }
953 }
954 if !deps.iter().any(|dep_id| compare(pkg_from_id(metadata, dep_id), permitted)) {
955 check.error(format!(
956 "could not find allowed package `{permitted}`\n\
957 Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
958 ));
959 has_permitted_dep_error = true;
960 }
961 }
962
963 let permitted_dependencies: HashMap<_, _> = permitted_dependencies
965 .iter()
966 .map(|s| {
967 if let Some((name, version)) = s.split_once('@') {
968 (name, Version::parse(version).ok())
969 } else {
970 (*s, None)
971 }
972 })
973 .collect();
974
975 for dep in deps {
976 let dep = pkg_from_id(metadata, dep);
977 if dep.source.is_some() {
979 let is_eq = if let Some(version) = permitted_dependencies.get(dep.name.as_str()) {
980 if let Some(version) = version { version == &dep.version } else { true }
981 } else {
982 false
983 };
984 if !is_eq {
985 check.error(format!("Dependency for {descr} not explicitly permitted: {}", dep.id));
986 has_permitted_dep_error = true;
987 }
988 }
989 }
990
991 if has_permitted_dep_error {
992 eprintln!("Go to `{}:{}` for the list.", permitted_location.path, permitted_location.line);
993 }
994}
995
996fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
998 let mut i = metadata.packages.iter().filter(|p| *p.name == name);
999 let result =
1000 i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
1001 assert!(i.next().is_none(), "more than one package found for `{name}`");
1002 result
1003}
1004
1005fn pkg_from_id<'a>(metadata: &'a Metadata, id: &PackageId) -> &'a Package {
1006 metadata.packages.iter().find(|p| &p.id == id).unwrap()
1007}
1008
1009fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId, result: &mut HashSet<&'a PackageId>) {
1011 if !result.insert(pkg_id) {
1012 return;
1013 }
1014 let node = metadata
1015 .resolve
1016 .as_ref()
1017 .unwrap()
1018 .nodes
1019 .iter()
1020 .find(|n| &n.id == pkg_id)
1021 .unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
1022 for dep in &node.deps {
1023 deps_of(metadata, &dep.pkg, result);
1024 }
1025}