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