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 "leb128",
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 "serde",
436 "serde_core",
437 "serde_derive",
438 "serde_derive_internals",
439 "serde_json",
440 "serde_path_to_error",
441 "sha1",
442 "sha2",
443 "sharded-slab",
444 "shlex",
445 "simd-adler32",
446 "smallvec",
447 "stable_deref_trait",
448 "static_assertions",
449 "strsim",
450 "syn",
451 "synstructure",
452 "tempfile",
453 "termize",
454 "thin-vec",
455 "thiserror",
456 "thiserror-impl",
457 "thorin-dwp",
458 "thread_local",
459 "tikv-jemalloc-sys",
460 "tinystr",
461 "tinyvec",
462 "tinyvec_macros",
463 "tracing",
464 "tracing-attributes",
465 "tracing-core",
466 "tracing-log",
467 "tracing-serde",
468 "tracing-subscriber",
469 "tracing-tree",
470 "twox-hash",
471 "type-map",
472 "typenum",
473 "unic-langid",
474 "unic-langid-impl",
475 "unic-langid-macros",
476 "unic-langid-macros-impl",
477 "unicase",
478 "unicode-ident",
479 "unicode-normalization",
480 "unicode-properties",
481 "unicode-script",
482 "unicode-security",
483 "unicode-width",
484 "utf8_iter",
485 "utf8parse",
486 "valuable",
487 "version_check",
488 "wasi",
489 "wasm-encoder",
490 "wasmparser",
491 "windows",
492 "windows-collections",
493 "windows-core",
494 "windows-future",
495 "windows-implement",
496 "windows-interface",
497 "windows-link",
498 "windows-numerics",
499 "windows-result",
500 "windows-strings",
501 "windows-sys",
502 "windows-threading",
503 "wit-bindgen-rt@0.39.0", "writeable",
505 "yoke",
506 "yoke-derive",
507 "zerocopy",
508 "zerocopy-derive",
509 "zerofrom",
510 "zerofrom-derive",
511 "zerotrie",
512 "zerovec",
513 "zerovec-derive",
514 "zlib-rs",
515 ];
517
518const PERMITTED_STDLIB_DEPS_LOCATION: ListLocation = location!(+2);
519
520const PERMITTED_STDLIB_DEPENDENCIES: &[&str] = &[
521 "addr2line",
523 "adler2",
524 "cc",
525 "cfg-if",
526 "compiler_builtins",
527 "dlmalloc",
528 "foldhash", "fortanix-sgx-abi",
530 "getopts",
531 "gimli",
532 "hashbrown",
533 "hermit-abi",
534 "libc",
535 "memchr",
536 "miniz_oxide",
537 "moto-rt",
538 "object",
539 "r-efi",
540 "r-efi-alloc",
541 "rand",
542 "rand_core",
543 "rand_xorshift",
544 "rustc-demangle",
545 "rustc-literal-escaper",
546 "shlex",
547 "unwinding",
548 "vex-sdk",
549 "wasip1",
550 "wasip2",
551 "wasip3",
552 "windows-link",
553 "windows-sys@0.61.100", "wit-bindgen",
555 ];
557
558const PERMITTED_CRANELIFT_DEPS_LOCATION: ListLocation = location!(+2);
559
560const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
561 "allocator-api2",
563 "anyhow",
564 "arbitrary",
565 "bitflags",
566 "bumpalo",
567 "cfg-if",
568 "cranelift-assembler-x64",
569 "cranelift-assembler-x64-meta",
570 "cranelift-bforest",
571 "cranelift-bitset",
572 "cranelift-codegen",
573 "cranelift-codegen-meta",
574 "cranelift-codegen-shared",
575 "cranelift-control",
576 "cranelift-entity",
577 "cranelift-frontend",
578 "cranelift-isle",
579 "cranelift-jit",
580 "cranelift-module",
581 "cranelift-native",
582 "cranelift-object",
583 "cranelift-srcgen",
584 "crc32fast",
585 "equivalent",
586 "fnv",
587 "foldhash",
588 "gimli",
589 "hashbrown",
590 "heck",
591 "indexmap",
592 "libc",
593 "libloading",
594 "libm",
595 "log",
596 "mach2",
597 "memchr",
598 "memmap2",
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 if std::env::var("RUSTC").is_err() {
694 panic!("tidy must be run under bootstrap (./x test tidy), not as a standalone command");
695 }
696 let mut cmd = cargo_metadata::MetadataCommand::new();
697 cmd.cargo_path(cargo)
698 .manifest_path(root.join("Cargo.toml"))
699 .features(cargo_metadata::CargoOpt::AllFeatures)
700 .other_options(vec!["--locked".to_owned()]);
701 let metadata = t!(cmd.exec());
702 let is_proc_macro_pkg = |pkg: &Package| pkg.targets.iter().any(|target| target.is_proc_macro());
703
704 let mut proc_macro_deps = HashSet::new();
705 for pkg in metadata.packages.iter().filter(|pkg| is_proc_macro_pkg(pkg)) {
706 deps_of(&metadata, &pkg.id, &mut proc_macro_deps);
707 }
708 proc_macro_deps.retain(|pkg| !is_proc_macro_pkg(&metadata[pkg]));
710 let proc_macro_deps = proc_macro_deps
715 .into_iter()
716 .filter_map(|dep| {
717 metadata[dep].targets.iter().find_map(|target| target.is_lib().then_some(&target.name))
718 })
719 .collect::<BTreeSet<_>>();
720
721 let expected = {
722 use std::fmt::Write;
723
724 const HEADER: &str = "\
725/// Do not update manually - use `./x.py test tidy --bless`
726/// Holds all direct and indirect dependencies of proc-macro crates in tree.
727/// See <https://github.com/rust-lang/rust/issues/134863>
728pub static CRATES: &[&str] = &[
729 // tidy-alphabetical-start
730";
731 const FOOTER: &str = " // tidy-alphabetical-end
732];
733";
734
735 let mut buf = String::with_capacity(4096);
736 buf.push_str(HEADER);
737 for dep in proc_macro_deps {
738 writeln!(buf, " {dep:?},").unwrap();
739 }
740 buf.push_str(FOOTER);
741 buf
742 };
743
744 const PROC_MACRO_DEPS_RS: &str = "src/bootstrap/src/utils/proc_macro_deps.rs";
745 let proc_macro_deps_rs_path = &root.join(PROC_MACRO_DEPS_RS);
746 let actual = match fs::read_to_string(proc_macro_deps_rs_path) {
747 Ok(actual) => actual,
748 Err(e) => {
749 if e.kind() == io::ErrorKind::NotFound {
750 check.error(format!(
751 "`{PROC_MACRO_DEPS_RS}` not found; has it been moved or renamed?"
752 ));
753 } else {
754 check.error(format!("`{PROC_MACRO_DEPS_RS}` could not be read: {e:?}"));
755 }
756 return;
757 }
758 };
759
760 if actual != expected {
761 if bless {
762 fs::write(proc_macro_deps_rs_path, &expected).unwrap();
763 } else {
764 let diff = similar::TextDiff::from_lines(&actual, &expected);
765 let mut unified = diff.unified_diff();
766 unified.header(PROC_MACRO_DEPS_RS, "(expected)");
767
768 check.error(format!("`{PROC_MACRO_DEPS_RS}` is not up-to-date:\n{unified}"));
769 check.message("Run `./x.py test tidy --bless` to regenerate the list");
770 }
771 }
772}
773
774pub fn has_missing_submodule(root: &Path, submodules: &[&str], is_ci: bool) -> bool {
778 !is_ci
779 && submodules.iter().any(|submodule| {
780 let path = root.join(submodule);
781 !path.exists()
782 || read_dir(path).unwrap().next().is_none()
784 })
785}
786
787fn check_runtime_license_exceptions(metadata: &Metadata, check: &mut RunningCheck) {
792 for pkg in &metadata.packages {
793 if pkg.source.is_none() {
794 continue;
796 }
797 let license = match &pkg.license {
798 Some(license) => license,
799 None => {
800 check
801 .error(format!("dependency `{}` does not define a license expression", pkg.id));
802 continue;
803 }
804 };
805 if !LICENSES.contains(&license.as_str()) {
806 if *pkg.name == "fortanix-sgx-abi" && pkg.license.as_deref() == Some("MPL-2.0") {
811 continue;
812 }
813
814 check.error(format!("invalid license `{}` in `{}`", license, pkg.id));
815 }
816 }
817}
818
819fn check_license_exceptions(
823 metadata: &Metadata,
824 workspace: &str,
825 exceptions: &[(&str, &str)],
826 check: &mut RunningCheck,
827) {
828 for (name, license) in exceptions {
830 if !metadata.packages.iter().any(|p| *p.name == *name) {
832 check.error(format!(
833 "could not find exception package `{name}` in workspace `{workspace}`\n\
834 Remove from EXCEPTIONS list if it is no longer used.",
835 ));
836 }
837 for pkg in metadata.packages.iter().filter(|p| *p.name == *name) {
839 match &pkg.license {
840 None => {
841 check.error(format!(
842 "dependency exception `{}` in workspace `{workspace}` does not declare a license expression",
843 pkg.id
844 ));
845 }
846 Some(pkg_license) => {
847 if pkg_license.as_str() != *license {
848 check.error(format!(r#"dependency exception `{name}` license in workspace `{workspace}` has changed
849 previously `{license}` now `{pkg_license}`
850 update EXCEPTIONS for the new license
851"#));
852 }
853 }
854 }
855 }
856 if LICENSES.contains(license) || LICENSES_TOOLS.contains(license) {
857 check.error(format!(
858 "dependency exception `{name}` is not necessary. `{license}` is an allowed license"
859 ));
860 }
861 }
862
863 let exception_names: Vec<_> = exceptions.iter().map(|(name, _license)| *name).collect();
864
865 for pkg in &metadata.packages {
867 if pkg.source.is_none() {
868 continue;
870 }
871 if exception_names.contains(&pkg.name.as_str()) {
872 continue;
873 }
874 let license = match &pkg.license {
875 Some(license) => license,
876 None => {
877 check.error(format!(
878 "dependency `{}` in workspace `{workspace}` does not define a license expression",
879 pkg.id
880 ));
881 continue;
882 }
883 };
884 if !LICENSES.contains(&license.as_str()) && !LICENSES_TOOLS.contains(&license.as_str()) {
885 check.error(format!(
886 "invalid license `{}` for package `{}` in workspace `{workspace}`",
887 license, pkg.id
888 ));
889 }
890 }
891}
892
893fn check_runtime_no_duplicate_dependencies(metadata: &Metadata, check: &mut RunningCheck) {
894 let mut seen_pkgs = HashSet::new();
895 for pkg in &metadata.packages {
896 if pkg.source.is_none() {
897 continue;
898 }
899
900 if !seen_pkgs.insert(&*pkg.name) {
901 check.error(format!(
902 "duplicate package `{}` is not allowed for the standard library",
903 pkg.name
904 ));
905 }
906 }
907}
908
909fn check_runtime_no_proc_macros(metadata: &Metadata, check: &mut RunningCheck) {
910 for pkg in &metadata.packages {
911 if pkg.targets.iter().any(|target| target.is_proc_macro()) {
912 check.error(format!(
913 "proc macro `{}` is not allowed as standard library dependency.\n\
914 Using proc macros in the standard library would break cross-compilation \
915 as proc-macros don't get shipped for the host tuple.",
916 pkg.name
917 ));
918 }
919 }
920}
921
922fn check_permitted_dependencies(
927 metadata: &Metadata,
928 descr: &str,
929 permitted_dependencies: &[&'static str],
930 restricted_dependency_crates: &[&'static str],
931 permitted_location: ListLocation,
932 check: &mut RunningCheck,
933) {
934 let mut has_permitted_dep_error = false;
935 let mut deps = HashSet::new();
936 for to_check in restricted_dependency_crates {
937 let to_check = pkg_from_name(metadata, to_check);
938 deps_of(metadata, &to_check.id, &mut deps);
939 }
940
941 for permitted in permitted_dependencies {
943 fn compare(pkg: &Package, permitted: &str) -> bool {
944 if let Some((name, version)) = permitted.split_once("@") {
945 let Ok(version) = Version::parse(version) else {
946 return false;
947 };
948 *pkg.name == name && pkg.version == version
949 } else {
950 *pkg.name == permitted
951 }
952 }
953 if !deps.iter().any(|dep_id| compare(pkg_from_id(metadata, dep_id), permitted)) {
954 check.error(format!(
955 "could not find allowed package `{permitted}`\n\
956 Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
957 ));
958 has_permitted_dep_error = true;
959 }
960 }
961
962 let permitted_dependencies: HashMap<_, _> = permitted_dependencies
964 .iter()
965 .map(|s| {
966 if let Some((name, version)) = s.split_once('@') {
967 (name, Version::parse(version).ok())
968 } else {
969 (*s, None)
970 }
971 })
972 .collect();
973
974 for dep in deps {
975 let dep = pkg_from_id(metadata, dep);
976 if dep.source.is_some() {
978 let is_eq = if let Some(version) = permitted_dependencies.get(dep.name.as_str()) {
979 if let Some(version) = version { version == &dep.version } else { true }
980 } else {
981 false
982 };
983 if !is_eq {
984 check.error(format!("Dependency for {descr} not explicitly permitted: {}", dep.id));
985 has_permitted_dep_error = true;
986 }
987 }
988 }
989
990 if has_permitted_dep_error {
991 eprintln!("Go to `{}:{}` for the list.", permitted_location.path, permitted_location.line);
992 }
993}
994
995fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
997 let mut i = metadata.packages.iter().filter(|p| *p.name == name);
998 let result =
999 i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
1000 assert!(i.next().is_none(), "more than one package found for `{name}`");
1001 result
1002}
1003
1004fn pkg_from_id<'a>(metadata: &'a Metadata, id: &PackageId) -> &'a Package {
1005 metadata.packages.iter().find(|p| &p.id == id).unwrap()
1006}
1007
1008fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId, result: &mut HashSet<&'a PackageId>) {
1010 if !result.insert(pkg_id) {
1011 return;
1012 }
1013 let node = metadata
1014 .resolve
1015 .as_ref()
1016 .unwrap()
1017 .nodes
1018 .iter()
1019 .find(|n| &n.id == pkg_id)
1020 .unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
1021 for dep in &node.deps {
1022 deps_of(metadata, &dep.pkg, result);
1023 }
1024}