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 "wasip1",
543 "wasip2",
544 "windows-link",
545 "windows-sys",
546 "windows-targets",
547 "windows_aarch64_gnullvm",
548 "windows_aarch64_msvc",
549 "windows_i686_gnu",
550 "windows_i686_gnullvm",
551 "windows_i686_msvc",
552 "windows_x86_64_gnu",
553 "windows_x86_64_gnullvm",
554 "windows_x86_64_msvc",
555 "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 "object",
600 "proc-macro2",
601 "quote",
602 "regalloc2",
603 "region",
604 "rustc-hash",
605 "serde",
606 "serde_core",
607 "serde_derive",
608 "smallvec",
609 "stable_deref_trait",
610 "syn",
611 "target-lexicon",
612 "unicode-ident",
613 "wasmtime-internal-core",
614 "wasmtime-internal-jit-icache-coherence",
615 "windows-link",
616 "windows-sys",
617 "windows-targets",
618 "windows_aarch64_gnullvm",
619 "windows_aarch64_msvc",
620 "windows_i686_gnu",
621 "windows_i686_gnullvm",
622 "windows_i686_msvc",
623 "windows_x86_64_gnu",
624 "windows_x86_64_gnullvm",
625 "windows_x86_64_msvc",
626 ];
628
629pub fn check(root: &Path, cargo: &Path, tidy_ctx: TidyCtx) {
634 let mut check = tidy_ctx.start_check("deps");
635 let bless = tidy_ctx.is_bless_enabled();
636
637 let mut checked_runtime_licenses = false;
638
639 check_proc_macro_dep_list(root, cargo, bless, &mut check);
640
641 for &WorkspaceInfo { path, exceptions, crates_and_deps, submodules } in WORKSPACES {
642 if has_missing_submodule(root, submodules, tidy_ctx.is_running_on_ci()) {
643 continue;
644 }
645
646 if !root.join(path).join("Cargo.lock").exists() {
647 check.error(format!("the `{path}` workspace doesn't have a Cargo.lock"));
648 continue;
649 }
650
651 let mut cmd = cargo_metadata::MetadataCommand::new();
652 cmd.cargo_path(cargo)
653 .manifest_path(root.join(path).join("Cargo.toml"))
654 .features(cargo_metadata::CargoOpt::AllFeatures)
655 .other_options(vec!["--locked".to_owned()]);
656 let metadata = t!(cmd.exec());
657
658 let absolute_root =
660 if path == "." { root.to_path_buf() } else { t!(std::path::absolute(root.join(path))) };
661 let absolute_root_real = t!(std::path::absolute(&metadata.workspace_root));
662 if absolute_root_real != absolute_root {
663 check.error(format!("{path} is part of another workspace ({} != {}), remove from `WORKSPACES` ({WORKSPACE_LOCATION})", absolute_root.display(), absolute_root_real.display()));
664 }
665 check_license_exceptions(&metadata, path, exceptions, &mut check);
666 if let Some((crates, permitted_deps, location)) = crates_and_deps {
667 let descr = crates.get(0).unwrap_or(&path);
668 check_permitted_dependencies(
669 &metadata,
670 descr,
671 permitted_deps,
672 crates,
673 location,
674 &mut check,
675 );
676 }
677
678 if path == "library" {
679 check_runtime_license_exceptions(&metadata, &mut check);
680 check_runtime_no_duplicate_dependencies(&metadata, &mut check);
681 check_runtime_no_proc_macros(&metadata, &mut check);
682 checked_runtime_licenses = true;
683 }
684 }
685
686 assert!(checked_runtime_licenses);
689}
690
691fn check_proc_macro_dep_list(root: &Path, cargo: &Path, bless: bool, check: &mut RunningCheck) {
693 let mut cmd = cargo_metadata::MetadataCommand::new();
694 cmd.cargo_path(cargo)
695 .manifest_path(root.join("Cargo.toml"))
696 .features(cargo_metadata::CargoOpt::AllFeatures)
697 .other_options(vec!["--locked".to_owned()]);
698 let metadata = t!(cmd.exec());
699 let is_proc_macro_pkg = |pkg: &Package| pkg.targets.iter().any(|target| target.is_proc_macro());
700
701 let mut proc_macro_deps = HashSet::new();
702 for pkg in metadata.packages.iter().filter(|pkg| is_proc_macro_pkg(pkg)) {
703 deps_of(&metadata, &pkg.id, &mut proc_macro_deps);
704 }
705 proc_macro_deps.retain(|pkg| !is_proc_macro_pkg(&metadata[pkg]));
707
708 let proc_macro_deps: HashSet<_> =
709 proc_macro_deps.into_iter().map(|dep| metadata[dep].name.as_ref()).collect();
710 let expected = proc_macro_deps::CRATES.iter().copied().collect::<HashSet<_>>();
711
712 let needs_blessing = proc_macro_deps.difference(&expected).next().is_some()
713 || expected.difference(&proc_macro_deps).next().is_some();
714
715 if needs_blessing && bless {
716 let mut proc_macro_deps: Vec<_> = proc_macro_deps.into_iter().collect();
717 proc_macro_deps.sort();
718 let mut file = File::create(root.join("src/bootstrap/src/utils/proc_macro_deps.rs"))
719 .expect("`proc_macro_deps` should exist");
720 writeln!(
721 &mut file,
722 "/// Do not update manually - use `./x.py test tidy --bless`
723/// Holds all direct and indirect dependencies of proc-macro crates in tree.
724/// See <https://github.com/rust-lang/rust/issues/134863>
725pub static CRATES: &[&str] = &[
726 // tidy-alphabetical-start"
727 )
728 .unwrap();
729 for dep in proc_macro_deps {
730 writeln!(&mut file, " {dep:?},").unwrap();
731 }
732 writeln!(
733 &mut file,
734 " // tidy-alphabetical-end
735];"
736 )
737 .unwrap();
738 } else {
739 let mut error_found = false;
740
741 for missing in proc_macro_deps.difference(&expected) {
742 error_found = true;
743 check.error(format!(
744 "proc-macro crate dependency `{missing}` is not registered in `src/bootstrap/src/utils/proc_macro_deps.rs`",
745 ));
746 }
747 for extra in expected.difference(&proc_macro_deps) {
748 error_found = true;
749 check.error(format!(
750 "`{extra}` is registered in `src/bootstrap/src/utils/proc_macro_deps.rs`, but is not a proc-macro crate dependency",
751 ));
752 }
753 if error_found {
754 check.message("Run `./x.py test tidy --bless` to regenerate the list");
755 }
756 }
757}
758
759pub fn has_missing_submodule(root: &Path, submodules: &[&str], is_ci: bool) -> bool {
763 !is_ci
764 && submodules.iter().any(|submodule| {
765 let path = root.join(submodule);
766 !path.exists()
767 || read_dir(path).unwrap().next().is_none()
769 })
770}
771
772fn check_runtime_license_exceptions(metadata: &Metadata, check: &mut RunningCheck) {
777 for pkg in &metadata.packages {
778 if pkg.source.is_none() {
779 continue;
781 }
782 let license = match &pkg.license {
783 Some(license) => license,
784 None => {
785 check
786 .error(format!("dependency `{}` does not define a license expression", pkg.id));
787 continue;
788 }
789 };
790 if !LICENSES.contains(&license.as_str()) {
791 if *pkg.name == "fortanix-sgx-abi" && pkg.license.as_deref() == Some("MPL-2.0") {
796 continue;
797 }
798
799 check.error(format!("invalid license `{}` in `{}`", license, pkg.id));
800 }
801 }
802}
803
804fn check_license_exceptions(
808 metadata: &Metadata,
809 workspace: &str,
810 exceptions: &[(&str, &str)],
811 check: &mut RunningCheck,
812) {
813 for (name, license) in exceptions {
815 if !metadata.packages.iter().any(|p| *p.name == *name) {
817 check.error(format!(
818 "could not find exception package `{name}` in workspace `{workspace}`\n\
819 Remove from EXCEPTIONS list if it is no longer used.",
820 ));
821 }
822 for pkg in metadata.packages.iter().filter(|p| *p.name == *name) {
824 match &pkg.license {
825 None => {
826 check.error(format!(
827 "dependency exception `{}` in workspace `{workspace}` does not declare a license expression",
828 pkg.id
829 ));
830 }
831 Some(pkg_license) => {
832 if pkg_license.as_str() != *license {
833 check.error(format!(r#"dependency exception `{name}` license in workspace `{workspace}` has changed
834 previously `{license}` now `{pkg_license}`
835 update EXCEPTIONS for the new license
836"#));
837 }
838 }
839 }
840 }
841 if LICENSES.contains(license) || LICENSES_TOOLS.contains(license) {
842 check.error(format!(
843 "dependency exception `{name}` is not necessary. `{license}` is an allowed license"
844 ));
845 }
846 }
847
848 let exception_names: Vec<_> = exceptions.iter().map(|(name, _license)| *name).collect();
849
850 for pkg in &metadata.packages {
852 if pkg.source.is_none() {
853 continue;
855 }
856 if exception_names.contains(&pkg.name.as_str()) {
857 continue;
858 }
859 let license = match &pkg.license {
860 Some(license) => license,
861 None => {
862 check.error(format!(
863 "dependency `{}` in workspace `{workspace}` does not define a license expression",
864 pkg.id
865 ));
866 continue;
867 }
868 };
869 if !LICENSES.contains(&license.as_str()) && !LICENSES_TOOLS.contains(&license.as_str()) {
870 check.error(format!(
871 "invalid license `{}` for package `{}` in workspace `{workspace}`",
872 license, pkg.id
873 ));
874 }
875 }
876}
877
878fn check_runtime_no_duplicate_dependencies(metadata: &Metadata, check: &mut RunningCheck) {
879 let mut seen_pkgs = HashSet::new();
880 for pkg in &metadata.packages {
881 if pkg.source.is_none() {
882 continue;
883 }
884
885 if !seen_pkgs.insert(&*pkg.name) {
886 check.error(format!(
887 "duplicate package `{}` is not allowed for the standard library",
888 pkg.name
889 ));
890 }
891 }
892}
893
894fn check_runtime_no_proc_macros(metadata: &Metadata, check: &mut RunningCheck) {
895 for pkg in &metadata.packages {
896 if pkg.targets.iter().any(|target| target.is_proc_macro()) {
897 check.error(format!(
898 "proc macro `{}` is not allowed as standard library dependency.\n\
899 Using proc macros in the standard library would break cross-compilation \
900 as proc-macros don't get shipped for the host tuple.",
901 pkg.name
902 ));
903 }
904 }
905}
906
907fn check_permitted_dependencies(
912 metadata: &Metadata,
913 descr: &str,
914 permitted_dependencies: &[&'static str],
915 restricted_dependency_crates: &[&'static str],
916 permitted_location: ListLocation,
917 check: &mut RunningCheck,
918) {
919 let mut has_permitted_dep_error = false;
920 let mut deps = HashSet::new();
921 for to_check in restricted_dependency_crates {
922 let to_check = pkg_from_name(metadata, to_check);
923 deps_of(metadata, &to_check.id, &mut deps);
924 }
925
926 for permitted in permitted_dependencies {
928 fn compare(pkg: &Package, permitted: &str) -> bool {
929 if let Some((name, version)) = permitted.split_once("@") {
930 let Ok(version) = Version::parse(version) else {
931 return false;
932 };
933 *pkg.name == name && pkg.version == version
934 } else {
935 *pkg.name == permitted
936 }
937 }
938 if !deps.iter().any(|dep_id| compare(pkg_from_id(metadata, dep_id), permitted)) {
939 check.error(format!(
940 "could not find allowed package `{permitted}`\n\
941 Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
942 ));
943 has_permitted_dep_error = true;
944 }
945 }
946
947 let permitted_dependencies: HashMap<_, _> = permitted_dependencies
949 .iter()
950 .map(|s| {
951 if let Some((name, version)) = s.split_once('@') {
952 (name, Version::parse(version).ok())
953 } else {
954 (*s, None)
955 }
956 })
957 .collect();
958
959 for dep in deps {
960 let dep = pkg_from_id(metadata, dep);
961 if dep.source.is_some() {
963 let is_eq = if let Some(version) = permitted_dependencies.get(dep.name.as_str()) {
964 if let Some(version) = version { version == &dep.version } else { true }
965 } else {
966 false
967 };
968 if !is_eq {
969 check.error(format!("Dependency for {descr} not explicitly permitted: {}", dep.id));
970 has_permitted_dep_error = true;
971 }
972 }
973 }
974
975 if has_permitted_dep_error {
976 eprintln!("Go to `{}:{}` for the list.", permitted_location.path, permitted_location.line);
977 }
978}
979
980fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
982 let mut i = metadata.packages.iter().filter(|p| *p.name == name);
983 let result =
984 i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
985 assert!(i.next().is_none(), "more than one package found for `{name}`");
986 result
987}
988
989fn pkg_from_id<'a>(metadata: &'a Metadata, id: &PackageId) -> &'a Package {
990 metadata.packages.iter().find(|p| &p.id == id).unwrap()
991}
992
993fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId, result: &mut HashSet<&'a PackageId>) {
995 if !result.insert(pkg_id) {
996 return;
997 }
998 let node = metadata
999 .resolve
1000 .as_ref()
1001 .unwrap()
1002 .nodes
1003 .iter()
1004 .find(|n| &n.id == pkg_id)
1005 .unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
1006 for dep in &node.deps {
1007 deps_of(metadata, &dep.pkg, result);
1008 }
1009}