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