tidy/
deps.rs

1//! Checks the licenses of third-party dependencies.
2
3use std::collections::{HashMap, HashSet};
4use std::fs::{File, read_dir};
5use std::io::Write;
6use std::path::Path;
7
8use build_helper::ci::CiEnv;
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/// These are licenses that are allowed for all crates, including the runtime,
18/// rustc, tools, etc.
19#[rustfmt::skip]
20const LICENSES: &[&str] = &[
21    // tidy-alphabetical-start
22    "0BSD OR MIT OR Apache-2.0",                           // adler2 license
23    "Apache-2.0 / MIT",
24    "Apache-2.0 OR ISC OR MIT",
25    "Apache-2.0 OR MIT",
26    "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
27    "Apache-2.0/MIT",
28    "BSD-2-Clause OR Apache-2.0 OR MIT",                   // zerocopy
29    "BSD-2-Clause OR MIT OR Apache-2.0",
30    "BSD-3-Clause/MIT",
31    "CC0-1.0 OR MIT-0 OR Apache-2.0",
32    "ISC",
33    "MIT / Apache-2.0",
34    "MIT AND (MIT OR Apache-2.0)",
35    "MIT AND Apache-2.0 WITH LLVM-exception AND (MIT OR Apache-2.0)", // compiler-builtins
36    "MIT OR Apache-2.0 OR BSD-1-Clause",
37    "MIT OR Apache-2.0 OR LGPL-2.1-or-later",              // r-efi, r-efi-alloc; LGPL is not acceptable, but we use it under MIT OR Apache-2.0
38    "MIT OR Apache-2.0 OR Zlib",                           // tinyvec_macros
39    "MIT OR Apache-2.0",
40    "MIT OR Zlib OR Apache-2.0",                           // miniz_oxide
41    "MIT",
42    "MIT/Apache-2.0",
43    "Unlicense OR MIT",
44    "Unlicense/MIT",
45    "Zlib",                                                // foldhash (FIXME: see PERMITTED_STDLIB_DEPENDENCIES)
46    // tidy-alphabetical-end
47];
48
49/// These are licenses that are allowed for rustc, tools, etc. But not for the runtime!
50#[rustfmt::skip]
51const LICENSES_TOOLS: &[&str] = &[
52    // tidy-alphabetical-start
53    "(Apache-2.0 OR MIT) AND BSD-3-Clause",
54    "(MIT OR Apache-2.0) AND Unicode-3.0",                 // unicode_ident (1.0.14)
55    "(MIT OR Apache-2.0) AND Unicode-DFS-2016",            // unicode_ident (1.0.12)
56    "0BSD",
57    "Apache-2.0 AND ISC",
58    "Apache-2.0 OR BSL-1.0",  // BSL is not acceptable, but we use it under Apache-2.0
59    "Apache-2.0 OR GPL-2.0-only",
60    "Apache-2.0 WITH LLVM-exception",
61    "Apache-2.0",
62    "BSD-2-Clause",
63    "BSD-3-Clause",
64    "CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception",
65    "CC0-1.0",
66    "Unicode-3.0",                                         // icu4x
67    "Unicode-DFS-2016",                                    // tinystr
68    "Zlib OR Apache-2.0 OR MIT",                           // tinyvec
69    "Zlib",
70    // tidy-alphabetical-end
71];
72
73type ExceptionList = &'static [(&'static str, &'static str)];
74
75#[derive(Clone, Copy)]
76pub(crate) struct WorkspaceInfo<'a> {
77    /// Path to the directory containing the workspace root Cargo.toml file.
78    pub(crate) path: &'a str,
79    /// The list of license exceptions.
80    pub(crate) exceptions: ExceptionList,
81    /// Optionally:
82    /// * A list of crates for which dependencies need to be explicitly allowed.
83    /// * The list of allowed dependencies.
84    /// * The source code location of the allowed dependencies list
85    crates_and_deps: Option<(&'a [&'a str], &'a [&'a str], ListLocation)>,
86    /// Submodules required for the workspace
87    pub(crate) submodules: &'a [&'a str],
88}
89
90/// The workspaces to check for licensing and optionally permitted dependencies.
91// FIXME auto detect all cargo workspaces
92pub(crate) const WORKSPACES: &[WorkspaceInfo<'static>] = &[
93    // The root workspace has to be first for check_rustfix to work.
94    WorkspaceInfo {
95        path: ".",
96        exceptions: EXCEPTIONS,
97        crates_and_deps: Some((
98            &["rustc-main"],
99            PERMITTED_RUSTC_DEPENDENCIES,
100            PERMITTED_RUSTC_DEPS_LOCATION,
101        )),
102        submodules: &[],
103    },
104    WorkspaceInfo {
105        path: "library",
106        exceptions: EXCEPTIONS_STDLIB,
107        crates_and_deps: Some((
108            &["sysroot"],
109            PERMITTED_STDLIB_DEPENDENCIES,
110            PERMITTED_STDLIB_DEPS_LOCATION,
111        )),
112        submodules: &[],
113    },
114    WorkspaceInfo {
115        path: "compiler/rustc_codegen_cranelift",
116        exceptions: EXCEPTIONS_CRANELIFT,
117        crates_and_deps: Some((
118            &["rustc_codegen_cranelift"],
119            PERMITTED_CRANELIFT_DEPENDENCIES,
120            PERMITTED_CRANELIFT_DEPS_LOCATION,
121        )),
122        submodules: &[],
123    },
124    WorkspaceInfo {
125        path: "compiler/rustc_codegen_gcc",
126        exceptions: EXCEPTIONS_GCC,
127        crates_and_deps: None,
128        submodules: &[],
129    },
130    WorkspaceInfo {
131        path: "src/bootstrap",
132        exceptions: EXCEPTIONS_BOOTSTRAP,
133        crates_and_deps: None,
134        submodules: &[],
135    },
136    WorkspaceInfo {
137        path: "src/tools/cargo",
138        exceptions: EXCEPTIONS_CARGO,
139        crates_and_deps: None,
140        submodules: &["src/tools/cargo"],
141    },
142    // FIXME uncomment once all deps are vendored
143    //  WorkspaceInfo {
144    //      path: "src/tools/miri/test-cargo-miri",
145    //      crates_and_deps: None
146    //      submodules: &[],
147    //  },
148    // WorkspaceInfo {
149    //      path: "src/tools/miri/test_dependencies",
150    //      crates_and_deps: None,
151    //      submodules: &[],
152    //  }
153    WorkspaceInfo {
154        path: "src/tools/rust-analyzer",
155        exceptions: EXCEPTIONS_RUST_ANALYZER,
156        crates_and_deps: None,
157        submodules: &[],
158    },
159    WorkspaceInfo {
160        path: "src/tools/rustbook",
161        exceptions: EXCEPTIONS_RUSTBOOK,
162        crates_and_deps: None,
163        submodules: &["src/doc/book", "src/doc/reference"],
164    },
165    WorkspaceInfo {
166        path: "src/tools/rustc-perf",
167        exceptions: EXCEPTIONS_RUSTC_PERF,
168        crates_and_deps: None,
169        submodules: &["src/tools/rustc-perf"],
170    },
171    WorkspaceInfo {
172        path: "src/tools/test-float-parse",
173        exceptions: EXCEPTIONS,
174        crates_and_deps: None,
175        submodules: &[],
176    },
177    WorkspaceInfo {
178        path: "tests/run-make-cargo/uefi-qemu/uefi_qemu_test",
179        exceptions: EXCEPTIONS_UEFI_QEMU_TEST,
180        crates_and_deps: None,
181        submodules: &[],
182    },
183];
184
185/// These are exceptions to Rust's permissive licensing policy, and
186/// should be considered bugs. Exceptions are only allowed in Rust
187/// tooling. It is _crucial_ that no exception crates be dependencies
188/// of the Rust runtime (std/test).
189#[rustfmt::skip]
190const EXCEPTIONS: ExceptionList = &[
191    // tidy-alphabetical-start
192    ("colored", "MPL-2.0"),                                  // rustfmt
193    ("option-ext", "MPL-2.0"),                               // cargo-miri (via `directories`)
194    // tidy-alphabetical-end
195];
196
197/// These are exceptions to Rust's permissive licensing policy, and
198/// should be considered bugs. Exceptions are only allowed in Rust
199/// tooling. It is _crucial_ that no exception crates be dependencies
200/// of the Rust runtime (std/test).
201#[rustfmt::skip]
202const EXCEPTIONS_STDLIB: ExceptionList = &[
203    // tidy-alphabetical-start
204    ("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target. FIXME: this dependency violates the documentation comment above.
205    // tidy-alphabetical-end
206];
207
208const EXCEPTIONS_CARGO: ExceptionList = &[
209    // tidy-alphabetical-start
210    ("bitmaps", "MPL-2.0+"),
211    ("im-rc", "MPL-2.0+"),
212    ("sized-chunks", "MPL-2.0+"),
213    // tidy-alphabetical-end
214];
215
216const EXCEPTIONS_RUST_ANALYZER: ExceptionList = &[
217    // tidy-alphabetical-start
218    ("option-ext", "MPL-2.0"),
219    // tidy-alphabetical-end
220];
221
222const EXCEPTIONS_RUSTC_PERF: ExceptionList = &[
223    // tidy-alphabetical-start
224    ("inferno", "CDDL-1.0"),
225    ("option-ext", "MPL-2.0"),
226    // tidy-alphabetical-end
227];
228
229const EXCEPTIONS_RUSTBOOK: ExceptionList = &[
230    // tidy-alphabetical-start
231    ("cssparser", "MPL-2.0"),
232    ("cssparser-macros", "MPL-2.0"),
233    ("dtoa-short", "MPL-2.0"),
234    ("mdbook", "MPL-2.0"),
235    // tidy-alphabetical-end
236];
237
238const EXCEPTIONS_CRANELIFT: ExceptionList = &[];
239
240const EXCEPTIONS_GCC: ExceptionList = &[
241    // tidy-alphabetical-start
242    ("gccjit", "GPL-3.0"),
243    ("gccjit_sys", "GPL-3.0"),
244    // tidy-alphabetical-end
245];
246
247const EXCEPTIONS_BOOTSTRAP: ExceptionList = &[];
248
249const EXCEPTIONS_UEFI_QEMU_TEST: ExceptionList = &[];
250
251#[derive(Clone, Copy)]
252struct ListLocation {
253    path: &'static str,
254    line: u32,
255}
256
257/// Creates a [`ListLocation`] for the current location (with an additional offset to the actual list start);
258macro_rules! location {
259    (+ $offset:literal) => {
260        ListLocation { path: file!(), line: line!() + $offset }
261    };
262}
263
264const PERMITTED_RUSTC_DEPS_LOCATION: ListLocation = location!(+6);
265
266/// Crates rustc is allowed to depend on. Avoid adding to the list if possible.
267///
268/// This list is here to provide a speed-bump to adding a new dependency to
269/// rustc. Please check with the compiler team before adding an entry.
270const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
271    // tidy-alphabetical-start
272    "adler2",
273    "aho-corasick",
274    "allocator-api2", // FIXME: only appears in Cargo.lock due to https://github.com/rust-lang/cargo/issues/10801
275    "annotate-snippets",
276    "anstream",
277    "anstyle",
278    "anstyle-parse",
279    "anstyle-query",
280    "anstyle-wincon",
281    "ar_archive_writer",
282    "arrayref",
283    "arrayvec",
284    "bitflags",
285    "blake3",
286    "block-buffer",
287    "block2",
288    "bstr",
289    "cc",
290    "cfg-if",
291    "cfg_aliases",
292    "colorchoice",
293    "constant_time_eq",
294    "cpufeatures",
295    "crc32fast",
296    "crossbeam-deque",
297    "crossbeam-epoch",
298    "crossbeam-utils",
299    "crypto-common",
300    "ctrlc",
301    "darling",
302    "darling_core",
303    "darling_macro",
304    "datafrog",
305    "derive-where",
306    "derive_setters",
307    "digest",
308    "dispatch2",
309    "displaydoc",
310    "dissimilar",
311    "dyn-clone",
312    "either",
313    "elsa",
314    "ena",
315    "equivalent",
316    "errno",
317    "expect-test",
318    "fallible-iterator", // dependency of `thorin`
319    "fastrand",
320    "find-msvc-tools",
321    "flate2",
322    "fluent-bundle",
323    "fluent-langneg",
324    "fluent-syntax",
325    "fnv",
326    "foldhash",
327    "generic-array",
328    "getopts",
329    "getrandom",
330    "gimli",
331    "gsgdt",
332    "hashbrown",
333    "icu_collections",
334    "icu_list",
335    "icu_locale",
336    "icu_locale_core",
337    "icu_locale_data",
338    "icu_provider",
339    "ident_case",
340    "indexmap",
341    "intl-memoizer",
342    "intl_pluralrules",
343    "is_terminal_polyfill",
344    "itertools",
345    "itoa",
346    "jiff",
347    "jiff-static",
348    "jobserver",
349    "lazy_static",
350    "leb128",
351    "libc",
352    "libloading",
353    "linux-raw-sys",
354    "litemap",
355    "lock_api",
356    "log",
357    "matchers",
358    "md-5",
359    "measureme",
360    "memchr",
361    "memmap2",
362    "miniz_oxide",
363    "nix",
364    "nu-ansi-term",
365    "objc2",
366    "objc2-encode",
367    "object",
368    "odht",
369    "once_cell",
370    "once_cell_polyfill",
371    "parking_lot",
372    "parking_lot_core",
373    "pathdiff",
374    "perf-event-open-sys",
375    "pin-project-lite",
376    "polonius-engine",
377    "portable-atomic", // dependency for platforms doesn't support `AtomicU64` in std
378    "portable-atomic-util",
379    "potential_utf",
380    "ppv-lite86",
381    "proc-macro-hack",
382    "proc-macro2",
383    "psm",
384    "pulldown-cmark",
385    "pulldown-cmark-escape",
386    "punycode",
387    "quote",
388    "r-efi",
389    "rand",
390    "rand_chacha",
391    "rand_core",
392    "rand_xorshift", // dependency for doc-tests in rustc_thread_pool
393    "rand_xoshiro",
394    "redox_syscall",
395    "ref-cast",
396    "ref-cast-impl",
397    "regex",
398    "regex-automata",
399    "regex-syntax",
400    "rustc-demangle",
401    "rustc-hash",
402    "rustc-literal-escaper",
403    "rustc-stable-hash",
404    "rustc_apfloat",
405    "rustix",
406    "ruzstd", // via object in thorin-dwp
407    "ryu",
408    "schemars",
409    "schemars_derive",
410    "scoped-tls",
411    "scopeguard",
412    "self_cell",
413    "serde",
414    "serde_core",
415    "serde_derive",
416    "serde_derive_internals",
417    "serde_json",
418    "serde_path_to_error",
419    "sha1",
420    "sha2",
421    "sharded-slab",
422    "shlex",
423    "simd-adler32",
424    "smallvec",
425    "stable_deref_trait",
426    "stacker",
427    "static_assertions",
428    "strsim",
429    "syn",
430    "synstructure",
431    "tempfile",
432    "termize",
433    "thin-vec",
434    "thiserror",
435    "thiserror-impl",
436    "thorin-dwp",
437    "thread_local",
438    "tikv-jemalloc-sys",
439    "tinystr",
440    "tinyvec",
441    "tinyvec_macros",
442    "tracing",
443    "tracing-attributes",
444    "tracing-core",
445    "tracing-log",
446    "tracing-subscriber",
447    "tracing-tree",
448    "twox-hash",
449    "type-map",
450    "typenum",
451    "unic-langid",
452    "unic-langid-impl",
453    "unic-langid-macros",
454    "unic-langid-macros-impl",
455    "unicase",
456    "unicode-ident",
457    "unicode-normalization",
458    "unicode-properties",
459    "unicode-script",
460    "unicode-security",
461    "unicode-width",
462    "unicode-xid",
463    "utf8parse",
464    "valuable",
465    "version_check",
466    "wasi",
467    "wasm-encoder",
468    "wasmparser",
469    "windows",
470    "windows-collections",
471    "windows-core",
472    "windows-future",
473    "windows-implement",
474    "windows-interface",
475    "windows-link",
476    "windows-numerics",
477    "windows-result",
478    "windows-strings",
479    "windows-sys",
480    "windows-targets",
481    "windows-threading",
482    "windows_aarch64_gnullvm",
483    "windows_aarch64_msvc",
484    "windows_i686_gnu",
485    "windows_i686_gnullvm",
486    "windows_i686_msvc",
487    "windows_x86_64_gnu",
488    "windows_x86_64_gnullvm",
489    "windows_x86_64_msvc",
490    "wit-bindgen-rt@0.39.0", // pinned to a specific version due to using a binary blob: <https://github.com/rust-lang/rust/pull/136395#issuecomment-2692769062>
491    "writeable",
492    "yoke",
493    "yoke-derive",
494    "zerocopy",
495    "zerocopy-derive",
496    "zerofrom",
497    "zerofrom-derive",
498    "zerotrie",
499    "zerovec",
500    "zerovec-derive",
501    // tidy-alphabetical-end
502];
503
504const PERMITTED_STDLIB_DEPS_LOCATION: ListLocation = location!(+2);
505
506const PERMITTED_STDLIB_DEPENDENCIES: &[&str] = &[
507    // tidy-alphabetical-start
508    "addr2line",
509    "adler2",
510    "cc",
511    "cfg-if",
512    "compiler_builtins",
513    "dlmalloc",
514    "foldhash", // FIXME: only appears in Cargo.lock due to https://github.com/rust-lang/cargo/issues/10801
515    "fortanix-sgx-abi",
516    "getopts",
517    "gimli",
518    "hashbrown",
519    "hermit-abi",
520    "libc",
521    "memchr",
522    "miniz_oxide",
523    "moto-rt",
524    "object",
525    "r-efi",
526    "r-efi-alloc",
527    "rand",
528    "rand_core",
529    "rand_xorshift",
530    "rustc-demangle",
531    "rustc-literal-escaper",
532    "shlex",
533    "unwinding",
534    "vex-sdk",
535    "wasi",
536    "windows-link",
537    "windows-sys",
538    "windows-targets",
539    "windows_aarch64_gnullvm",
540    "windows_aarch64_msvc",
541    "windows_i686_gnu",
542    "windows_i686_gnullvm",
543    "windows_i686_msvc",
544    "windows_x86_64_gnu",
545    "windows_x86_64_gnullvm",
546    "windows_x86_64_msvc",
547    "wit-bindgen",
548    // tidy-alphabetical-end
549];
550
551const PERMITTED_CRANELIFT_DEPS_LOCATION: ListLocation = location!(+2);
552
553const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
554    // tidy-alphabetical-start
555    "allocator-api2",
556    "anyhow",
557    "arbitrary",
558    "bitflags",
559    "bumpalo",
560    "cfg-if",
561    "cranelift-assembler-x64",
562    "cranelift-assembler-x64-meta",
563    "cranelift-bforest",
564    "cranelift-bitset",
565    "cranelift-codegen",
566    "cranelift-codegen-meta",
567    "cranelift-codegen-shared",
568    "cranelift-control",
569    "cranelift-entity",
570    "cranelift-frontend",
571    "cranelift-isle",
572    "cranelift-jit",
573    "cranelift-module",
574    "cranelift-native",
575    "cranelift-object",
576    "cranelift-srcgen",
577    "crc32fast",
578    "equivalent",
579    "fallible-iterator",
580    "foldhash",
581    "gimli",
582    "hashbrown",
583    "heck",
584    "indexmap",
585    "libc",
586    "libloading",
587    "libm",
588    "log",
589    "mach2",
590    "memchr",
591    "object",
592    "proc-macro2",
593    "quote",
594    "regalloc2",
595    "region",
596    "rustc-hash",
597    "serde",
598    "serde_derive",
599    "smallvec",
600    "stable_deref_trait",
601    "syn",
602    "target-lexicon",
603    "unicode-ident",
604    "wasmtime-internal-jit-icache-coherence",
605    "wasmtime-internal-math",
606    "windows-link",
607    "windows-sys",
608    "windows-targets",
609    "windows_aarch64_gnullvm",
610    "windows_aarch64_msvc",
611    "windows_i686_gnu",
612    "windows_i686_gnullvm",
613    "windows_i686_msvc",
614    "windows_x86_64_gnu",
615    "windows_x86_64_gnullvm",
616    "windows_x86_64_msvc",
617    // tidy-alphabetical-end
618];
619
620/// Dependency checks.
621///
622/// `root` is path to the directory with the root `Cargo.toml` (for the workspace). `cargo` is path
623/// to the cargo executable.
624pub fn check(root: &Path, cargo: &Path, tidy_ctx: TidyCtx) {
625    let mut check = tidy_ctx.start_check("deps");
626    let bless = tidy_ctx.is_bless_enabled();
627
628    let mut checked_runtime_licenses = false;
629
630    check_proc_macro_dep_list(root, cargo, bless, &mut check);
631
632    for &WorkspaceInfo { path, exceptions, crates_and_deps, submodules } in WORKSPACES {
633        if has_missing_submodule(root, submodules) {
634            continue;
635        }
636
637        if !root.join(path).join("Cargo.lock").exists() {
638            check.error(format!("the `{path}` workspace doesn't have a Cargo.lock"));
639            continue;
640        }
641
642        let mut cmd = cargo_metadata::MetadataCommand::new();
643        cmd.cargo_path(cargo)
644            .manifest_path(root.join(path).join("Cargo.toml"))
645            .features(cargo_metadata::CargoOpt::AllFeatures)
646            .other_options(vec!["--locked".to_owned()]);
647        let metadata = t!(cmd.exec());
648
649        check_license_exceptions(&metadata, path, exceptions, &mut check);
650        if let Some((crates, permitted_deps, location)) = crates_and_deps {
651            let descr = crates.get(0).unwrap_or(&path);
652            check_permitted_dependencies(
653                &metadata,
654                descr,
655                permitted_deps,
656                crates,
657                location,
658                &mut check,
659            );
660        }
661
662        if path == "library" {
663            check_runtime_license_exceptions(&metadata, &mut check);
664            check_runtime_no_duplicate_dependencies(&metadata, &mut check);
665            check_runtime_no_proc_macros(&metadata, &mut check);
666            checked_runtime_licenses = true;
667        }
668    }
669
670    // Sanity check to ensure we don't accidentally remove the workspace containing the runtime
671    // crates.
672    assert!(checked_runtime_licenses);
673}
674
675/// Ensure the list of proc-macro crate transitive dependencies is up to date
676fn check_proc_macro_dep_list(root: &Path, cargo: &Path, bless: bool, check: &mut RunningCheck) {
677    let mut cmd = cargo_metadata::MetadataCommand::new();
678    cmd.cargo_path(cargo)
679        .manifest_path(root.join("Cargo.toml"))
680        .features(cargo_metadata::CargoOpt::AllFeatures)
681        .other_options(vec!["--locked".to_owned()]);
682    let metadata = t!(cmd.exec());
683    let is_proc_macro_pkg = |pkg: &Package| pkg.targets.iter().any(|target| target.is_proc_macro());
684
685    let mut proc_macro_deps = HashSet::new();
686    for pkg in metadata.packages.iter().filter(|pkg| is_proc_macro_pkg(pkg)) {
687        deps_of(&metadata, &pkg.id, &mut proc_macro_deps);
688    }
689    // Remove the proc-macro crates themselves
690    proc_macro_deps.retain(|pkg| !is_proc_macro_pkg(&metadata[pkg]));
691
692    let proc_macro_deps: HashSet<_> =
693        proc_macro_deps.into_iter().map(|dep| metadata[dep].name.as_ref()).collect();
694    let expected = proc_macro_deps::CRATES.iter().copied().collect::<HashSet<_>>();
695
696    let needs_blessing = proc_macro_deps.difference(&expected).next().is_some()
697        || expected.difference(&proc_macro_deps).next().is_some();
698
699    if needs_blessing && bless {
700        let mut proc_macro_deps: Vec<_> = proc_macro_deps.into_iter().collect();
701        proc_macro_deps.sort();
702        let mut file = File::create(root.join("src/bootstrap/src/utils/proc_macro_deps.rs"))
703            .expect("`proc_macro_deps` should exist");
704        writeln!(
705            &mut file,
706            "/// Do not update manually - use `./x.py test tidy --bless`
707/// Holds all direct and indirect dependencies of proc-macro crates in tree.
708/// See <https://github.com/rust-lang/rust/issues/134863>
709pub static CRATES: &[&str] = &[
710    // tidy-alphabetical-start"
711        )
712        .unwrap();
713        for dep in proc_macro_deps {
714            writeln!(&mut file, "    {dep:?},").unwrap();
715        }
716        writeln!(
717            &mut file,
718            "    // tidy-alphabetical-end
719];"
720        )
721        .unwrap();
722    } else {
723        let mut error_found = false;
724
725        for missing in proc_macro_deps.difference(&expected) {
726            error_found = true;
727            check.error(format!(
728                "proc-macro crate dependency `{missing}` is not registered in `src/bootstrap/src/utils/proc_macro_deps.rs`",
729            ));
730        }
731        for extra in expected.difference(&proc_macro_deps) {
732            error_found = true;
733            check.error(format!(
734                "`{extra}` is registered in `src/bootstrap/src/utils/proc_macro_deps.rs`, but is not a proc-macro crate dependency",
735            ));
736        }
737        if error_found {
738            check.message("Run `./x.py test tidy --bless` to regenerate the list");
739        }
740    }
741}
742
743/// Used to skip a check if a submodule is not checked out, and not in a CI environment.
744///
745/// This helps prevent enforcing developers to fetch submodules for tidy.
746pub fn has_missing_submodule(root: &Path, submodules: &[&str]) -> bool {
747    !CiEnv::is_ci()
748        && submodules.iter().any(|submodule| {
749            let path = root.join(submodule);
750            !path.exists()
751            // If the directory is empty, we can consider it as an uninitialized submodule.
752            || read_dir(path).unwrap().next().is_none()
753        })
754}
755
756/// Check that all licenses of runtime dependencies are in the valid list in `LICENSES`.
757///
758/// Unlike for tools we don't allow exceptions to the `LICENSES` list for the runtime with the sole
759/// exception of `fortanix-sgx-abi` which is only used on x86_64-fortanix-unknown-sgx.
760fn check_runtime_license_exceptions(metadata: &Metadata, check: &mut RunningCheck) {
761    for pkg in &metadata.packages {
762        if pkg.source.is_none() {
763            // No need to check local packages.
764            continue;
765        }
766        let license = match &pkg.license {
767            Some(license) => license,
768            None => {
769                check
770                    .error(format!("dependency `{}` does not define a license expression", pkg.id));
771                continue;
772            }
773        };
774        if !LICENSES.contains(&license.as_str()) {
775            // This is a specific exception because SGX is considered "third party".
776            // See https://github.com/rust-lang/rust/issues/62620 for more.
777            // In general, these should never be added and this exception
778            // should not be taken as precedent for any new target.
779            if *pkg.name == "fortanix-sgx-abi" && pkg.license.as_deref() == Some("MPL-2.0") {
780                continue;
781            }
782
783            check.error(format!("invalid license `{}` in `{}`", license, pkg.id));
784        }
785    }
786}
787
788/// Check that all licenses of tool dependencies are in the valid list in `LICENSES`.
789///
790/// Packages listed in `exceptions` are allowed for tools.
791fn check_license_exceptions(
792    metadata: &Metadata,
793    workspace: &str,
794    exceptions: &[(&str, &str)],
795    check: &mut RunningCheck,
796) {
797    // Validate the EXCEPTIONS list hasn't changed.
798    for (name, license) in exceptions {
799        // Check that the package actually exists.
800        if !metadata.packages.iter().any(|p| *p.name == *name) {
801            check.error(format!(
802                "could not find exception package `{name}` in workspace `{workspace}`\n\
803                Remove from EXCEPTIONS list if it is no longer used.",
804            ));
805        }
806        // Check that the license hasn't changed.
807        for pkg in metadata.packages.iter().filter(|p| *p.name == *name) {
808            match &pkg.license {
809                None => {
810                    check.error(format!(
811                        "dependency exception `{}` in workspace `{workspace}` does not declare a license expression",
812                        pkg.id
813                    ));
814                }
815                Some(pkg_license) => {
816                    if pkg_license.as_str() != *license {
817                        check.error(format!(r#"dependency exception `{name}` license in workspace `{workspace}` has changed
818    previously `{license}` now `{pkg_license}`
819    update EXCEPTIONS for the new license
820"#));
821                    }
822                }
823            }
824        }
825        if LICENSES.contains(license) || LICENSES_TOOLS.contains(license) {
826            check.error(format!(
827                "dependency exception `{name}` is not necessary. `{license}` is an allowed license"
828            ));
829        }
830    }
831
832    let exception_names: Vec<_> = exceptions.iter().map(|(name, _license)| *name).collect();
833
834    // Check if any package does not have a valid license.
835    for pkg in &metadata.packages {
836        if pkg.source.is_none() {
837            // No need to check local packages.
838            continue;
839        }
840        if exception_names.contains(&pkg.name.as_str()) {
841            continue;
842        }
843        let license = match &pkg.license {
844            Some(license) => license,
845            None => {
846                check.error(format!(
847                    "dependency `{}` in workspace `{workspace}` does not define a license expression",
848                    pkg.id
849                ));
850                continue;
851            }
852        };
853        if !LICENSES.contains(&license.as_str()) && !LICENSES_TOOLS.contains(&license.as_str()) {
854            check.error(format!(
855                "invalid license `{}` for package `{}` in workspace `{workspace}`",
856                license, pkg.id
857            ));
858        }
859    }
860}
861
862fn check_runtime_no_duplicate_dependencies(metadata: &Metadata, check: &mut RunningCheck) {
863    let mut seen_pkgs = HashSet::new();
864    for pkg in &metadata.packages {
865        if pkg.source.is_none() {
866            continue;
867        }
868
869        // Skip the `wasi` crate here which the standard library explicitly
870        // depends on two version of (one for the `wasm32-wasip1` target and
871        // another for the `wasm32-wasip2` target).
872        if pkg.name.to_string() != "wasi" && !seen_pkgs.insert(&*pkg.name) {
873            check.error(format!(
874                "duplicate package `{}` is not allowed for the standard library",
875                pkg.name
876            ));
877        }
878    }
879}
880
881fn check_runtime_no_proc_macros(metadata: &Metadata, check: &mut RunningCheck) {
882    for pkg in &metadata.packages {
883        if pkg.targets.iter().any(|target| target.is_proc_macro()) {
884            check.error(format!(
885                "proc macro `{}` is not allowed as standard library dependency.\n\
886                Using proc macros in the standard library would break cross-compilation \
887                as proc-macros don't get shipped for the host tuple.",
888                pkg.name
889            ));
890        }
891    }
892}
893
894/// Checks the dependency of `restricted_dependency_crates` at the given path. Changes `bad` to
895/// `true` if a check failed.
896///
897/// Specifically, this checks that the dependencies are on the `permitted_dependencies`.
898fn check_permitted_dependencies(
899    metadata: &Metadata,
900    descr: &str,
901    permitted_dependencies: &[&'static str],
902    restricted_dependency_crates: &[&'static str],
903    permitted_location: ListLocation,
904    check: &mut RunningCheck,
905) {
906    let mut has_permitted_dep_error = false;
907    let mut deps = HashSet::new();
908    for to_check in restricted_dependency_crates {
909        let to_check = pkg_from_name(metadata, to_check);
910        deps_of(metadata, &to_check.id, &mut deps);
911    }
912
913    // Check that the PERMITTED_DEPENDENCIES does not have unused entries.
914    for permitted in permitted_dependencies {
915        fn compare(pkg: &Package, permitted: &str) -> bool {
916            if let Some((name, version)) = permitted.split_once("@") {
917                let Ok(version) = Version::parse(version) else {
918                    return false;
919                };
920                *pkg.name == name && pkg.version == version
921            } else {
922                *pkg.name == permitted
923            }
924        }
925        if !deps.iter().any(|dep_id| compare(pkg_from_id(metadata, dep_id), permitted)) {
926            check.error(format!(
927                "could not find allowed package `{permitted}`\n\
928                Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
929            ));
930            has_permitted_dep_error = true;
931        }
932    }
933
934    // Get in a convenient form.
935    let permitted_dependencies: HashMap<_, _> = permitted_dependencies
936        .iter()
937        .map(|s| {
938            if let Some((name, version)) = s.split_once('@') {
939                (name, Version::parse(version).ok())
940            } else {
941                (*s, None)
942            }
943        })
944        .collect();
945
946    for dep in deps {
947        let dep = pkg_from_id(metadata, dep);
948        // If this path is in-tree, we don't require it to be explicitly permitted.
949        if dep.source.is_some() {
950            let is_eq = if let Some(version) = permitted_dependencies.get(dep.name.as_str()) {
951                if let Some(version) = version { version == &dep.version } else { true }
952            } else {
953                false
954            };
955            if !is_eq {
956                check.error(format!("Dependency for {descr} not explicitly permitted: {}", dep.id));
957                has_permitted_dep_error = true;
958            }
959        }
960    }
961
962    if has_permitted_dep_error {
963        eprintln!("Go to `{}:{}` for the list.", permitted_location.path, permitted_location.line);
964    }
965}
966
967/// Finds a package with the given name.
968fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
969    let mut i = metadata.packages.iter().filter(|p| *p.name == name);
970    let result =
971        i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
972    assert!(i.next().is_none(), "more than one package found for `{name}`");
973    result
974}
975
976fn pkg_from_id<'a>(metadata: &'a Metadata, id: &PackageId) -> &'a Package {
977    metadata.packages.iter().find(|p| &p.id == id).unwrap()
978}
979
980/// Recursively find all dependencies.
981fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId, result: &mut HashSet<&'a PackageId>) {
982    if !result.insert(pkg_id) {
983        return;
984    }
985    let node = metadata
986        .resolve
987        .as_ref()
988        .unwrap()
989        .nodes
990        .iter()
991        .find(|n| &n.id == pkg_id)
992        .unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
993    for dep in &node.deps {
994        deps_of(metadata, &dep.pkg, result);
995    }
996}