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