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