1 //! Checks the licenses of third-party dependencies.
3 use build_helper
::ci
::CiEnv
;
4 use cargo_metadata
::{Metadata, Package, PackageId}
;
5 use std
::collections
::HashSet
;
9 /// These are licenses that are allowed for all crates, including the runtime,
10 /// rustc, tools, etc.
12 const LICENSES
: &[&str] = &[
13 // tidy-alphabetical-start
14 "(MIT OR Apache-2.0) AND Unicode-DFS-2016", // unicode_ident
15 "0BSD OR MIT OR Apache-2.0", // adler license
18 "Apache-2.0 OR ISC OR MIT",
20 "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
23 "BSD-2-Clause OR Apache-2.0 OR MIT", // zerocopy
26 "MIT OR Apache-2.0 OR LGPL-2.1-or-later", // r-efi, r-efi-alloc
27 "MIT OR Apache-2.0 OR Zlib", // tinyvec_macros
29 "MIT OR Zlib OR Apache-2.0", // miniz_oxide
32 "Unicode-3.0", // icu4x
33 "Unicode-DFS-2016", // tinystr
36 "Zlib OR Apache-2.0 OR MIT", // tinyvec
37 // tidy-alphabetical-end
40 type ExceptionList
= &'
static [(&'
static str, &'
static str)];
42 /// The workspaces to check for licensing and optionally permitted dependencies.
44 /// Each entry consists of a tuple with the following elements:
46 /// * The path to the workspace root Cargo.toml file.
47 /// * The list of license exceptions.
48 /// * Optionally a tuple of:
49 /// * A list of crates for which dependencies need to be explicitly allowed.
50 /// * The list of allowed dependencies.
51 // FIXME auto detect all cargo workspaces
52 pub(crate) const WORKSPACES
: &[(&str, ExceptionList
, Option
<(&[&str], &[&str])>)] = &[
53 // The root workspace has to be first for check_rustfix to work.
54 (".", EXCEPTIONS
, Some((&["rustc-main"], PERMITTED_RUSTC_DEPENDENCIES
))),
55 // Outside of the alphabetical section because rustfmt formats it using multiple lines.
57 "compiler/rustc_codegen_cranelift",
59 Some((&["rustc_codegen_cranelift"], PERMITTED_CRANELIFT_DEPENDENCIES
)),
61 // tidy-alphabetical-start
62 ("compiler/rustc_codegen_gcc", EXCEPTIONS_GCC
, None
),
63 //("library/backtrace", &[], None), // FIXME uncomment once rust-lang/backtrace#562 has been synced back to the rust repo
64 //("library/portable-simd", &[], None), // FIXME uncomment once rust-lang/portable-simd#363 has been synced back to the rust repo
65 //("library/stdarch", EXCEPTIONS_STDARCH, None), // FIXME uncomment once rust-lang/stdarch#1462 has been synced back to the rust repo
66 ("src/bootstrap", EXCEPTIONS_BOOTSTRAP
, None
),
67 ("src/ci/docker/host-x86_64/test-various/uefi_qemu_test", EXCEPTIONS_UEFI_QEMU_TEST
, None
),
68 //("src/etc/test-float-parse", &[], None), // FIXME uncomment once all deps are vendored
69 ("src/tools/cargo", EXCEPTIONS_CARGO
, None
),
70 //("src/tools/miri/test-cargo-miri", &[], None), // FIXME uncomment once all deps are vendored
71 //("src/tools/miri/test_dependencies", &[], None), // FIXME uncomment once all deps are vendored
72 ("src/tools/rust-analyzer", EXCEPTIONS_RUST_ANALYZER
, None
),
73 ("src/tools/rustc-perf", EXCEPTIONS_RUSTC_PERF
, None
),
74 ("src/tools/x", &[], None
),
75 // tidy-alphabetical-end
78 /// These are exceptions to Rust's permissive licensing policy, and
79 /// should be considered bugs. Exceptions are only allowed in Rust
80 /// tooling. It is _crucial_ that no exception crates be dependencies
81 /// of the Rust runtime (std/test).
83 const EXCEPTIONS
: ExceptionList
= &[
84 // tidy-alphabetical-start
85 ("ar_archive_writer", "Apache-2.0 WITH LLVM-exception"), // rustc
86 ("colored", "MPL-2.0"), // rustfmt
87 ("dissimilar", "Apache-2.0"), // rustdoc, rustc_lexer (few tests) via expect-test, (dev deps)
88 ("fluent-langneg", "Apache-2.0"), // rustc (fluent translations)
89 ("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target. FIXME: this dependency violates the documentation comment above.
90 ("instant", "BSD-3-Clause"), // rustc_driver/tracing-subscriber/parking_lot
91 ("mdbook", "MPL-2.0"), // mdbook
92 ("option-ext", "MPL-2.0"), // cargo-miri (via `directories`)
93 ("rustc_apfloat", "Apache-2.0 WITH LLVM-exception"), // rustc (license is the same as LLVM uses)
94 ("ryu", "Apache-2.0 OR BSL-1.0"), // BSL is not acceptble, but we use it under Apache-2.0 // cargo/... (because of serde)
95 ("self_cell", "Apache-2.0"), // rustc (fluent translations)
96 ("snap", "BSD-3-Clause"), // rustc
97 ("wasm-encoder", "Apache-2.0 WITH LLVM-exception"), // rustc
98 ("wasm-metadata", "Apache-2.0 WITH LLVM-exception"), // rustc
99 ("wasmparser", "Apache-2.0 WITH LLVM-exception"), // rustc
100 ("wast", "Apache-2.0 WITH LLVM-exception"), // rustc
101 ("wat", "Apache-2.0 WITH LLVM-exception"), // rustc
102 ("wit-component", "Apache-2.0 WITH LLVM-exception"), // rustc
103 ("wit-parser", "Apache-2.0 WITH LLVM-exception"), // rustc
104 // tidy-alphabetical-end
107 // FIXME uncomment once rust-lang/stdarch#1462 lands
109 const EXCEPTIONS_STDARCH: ExceptionList = &[
110 // tidy-alphabetical-start
111 ("ryu", "Apache-2.0 OR BSL-1.0"), // BSL is not acceptble, but we use it under Apache-2.0
112 ("wasmparser", "Apache-2.0 WITH LLVM-exception"),
113 ("wasmprinter", "Apache-2.0 WITH LLVM-exception"),
114 // tidy-alphabetical-end
118 const EXCEPTIONS_CARGO
: ExceptionList
= &[
119 // tidy-alphabetical-start
120 ("bitmaps", "MPL-2.0+"),
121 ("bytesize", "Apache-2.0"),
122 ("ciborium", "Apache-2.0"),
123 ("ciborium-io", "Apache-2.0"),
124 ("ciborium-ll", "Apache-2.0"),
125 ("dunce", "CC0-1.0 OR MIT-0 OR Apache-2.0"),
126 ("encoding_rs", "(Apache-2.0 OR MIT) AND BSD-3-Clause"),
127 ("fiat-crypto", "MIT OR Apache-2.0 OR BSD-1-Clause"),
128 ("im-rc", "MPL-2.0+"),
129 ("normalize-line-endings", "Apache-2.0"),
130 ("openssl", "Apache-2.0"),
131 ("ryu", "Apache-2.0 OR BSL-1.0"), // BSL is not acceptble, but we use it under Apache-2.0
132 ("sha1_smol", "BSD-3-Clause"),
133 ("similar", "Apache-2.0"),
134 ("sized-chunks", "MPL-2.0+"),
135 ("subtle", "BSD-3-Clause"),
136 ("supports-hyperlinks", "Apache-2.0"),
137 ("unicode-bom", "Apache-2.0"),
138 // tidy-alphabetical-end
141 const EXCEPTIONS_RUST_ANALYZER
: ExceptionList
= &[
142 // tidy-alphabetical-start
143 ("dissimilar", "Apache-2.0"),
144 ("notify", "CC0-1.0"),
145 ("option-ext", "MPL-2.0"),
146 ("pulldown-cmark-to-cmark", "Apache-2.0"),
147 ("rustc_apfloat", "Apache-2.0 WITH LLVM-exception"),
148 ("ryu", "Apache-2.0 OR BSL-1.0"), // BSL is not acceptble, but we use it under Apache-2.0
149 ("scip", "Apache-2.0"),
150 ("snap", "BSD-3-Clause"),
151 // tidy-alphabetical-end
154 const EXCEPTIONS_RUSTC_PERF
: ExceptionList
= &[
155 // tidy-alphabetical-start
156 ("alloc-no-stdlib", "BSD-3-Clause"),
157 ("alloc-stdlib", "BSD-3-Clause"),
158 ("brotli", "BSD-3-Clause/MIT"),
159 ("brotli-decompressor", "BSD-3-Clause/MIT"),
160 ("encoding_rs", "(Apache-2.0 OR MIT) AND BSD-3-Clause"),
161 ("inferno", "CDDL-1.0"),
162 ("instant", "BSD-3-Clause"),
163 ("ring", NON_STANDARD_LICENSE
), // see EXCEPTIONS_NON_STANDARD_LICENSE_DEPS for more.
164 ("ryu", "Apache-2.0 OR BSL-1.0"),
165 ("snap", "BSD-3-Clause"),
166 ("subtle", "BSD-3-Clause"),
167 // tidy-alphabetical-end
170 const EXCEPTIONS_CRANELIFT
: ExceptionList
= &[
171 // tidy-alphabetical-start
172 ("cranelift-bforest", "Apache-2.0 WITH LLVM-exception"),
173 ("cranelift-codegen", "Apache-2.0 WITH LLVM-exception"),
174 ("cranelift-codegen-meta", "Apache-2.0 WITH LLVM-exception"),
175 ("cranelift-codegen-shared", "Apache-2.0 WITH LLVM-exception"),
176 ("cranelift-control", "Apache-2.0 WITH LLVM-exception"),
177 ("cranelift-entity", "Apache-2.0 WITH LLVM-exception"),
178 ("cranelift-frontend", "Apache-2.0 WITH LLVM-exception"),
179 ("cranelift-isle", "Apache-2.0 WITH LLVM-exception"),
180 ("cranelift-jit", "Apache-2.0 WITH LLVM-exception"),
181 ("cranelift-module", "Apache-2.0 WITH LLVM-exception"),
182 ("cranelift-native", "Apache-2.0 WITH LLVM-exception"),
183 ("cranelift-object", "Apache-2.0 WITH LLVM-exception"),
184 ("mach", "BSD-2-Clause"),
185 ("regalloc2", "Apache-2.0 WITH LLVM-exception"),
186 ("target-lexicon", "Apache-2.0 WITH LLVM-exception"),
187 ("wasmtime-jit-icache-coherence", "Apache-2.0 WITH LLVM-exception"),
188 // tidy-alphabetical-end
191 const EXCEPTIONS_GCC
: ExceptionList
= &[
192 // tidy-alphabetical-start
193 ("gccjit", "GPL-3.0"),
194 ("gccjit_sys", "GPL-3.0"),
195 // tidy-alphabetical-end
198 const EXCEPTIONS_BOOTSTRAP
: ExceptionList
= &[
199 ("ryu", "Apache-2.0 OR BSL-1.0"), // through serde. BSL is not acceptble, but we use it under Apache-2.0
202 const EXCEPTIONS_UEFI_QEMU_TEST
: ExceptionList
= &[
203 ("r-efi", "MIT OR Apache-2.0 OR LGPL-2.1-or-later"), // LGPL is not acceptible, but we use it under MIT OR Apache-2.0
206 /// Placeholder for non-standard license file.
207 const NON_STANDARD_LICENSE
: &str = "NON_STANDARD_LICENSE";
209 /// These dependencies have non-standard licenses but are genenrally permitted.
210 const EXCEPTIONS_NON_STANDARD_LICENSE_DEPS
: &[&str] = &[
211 // `ring` is included because it is an optional dependency of `hyper`,
212 // which is a training data in rustc-perf for optimized build.
213 // The license of it is generally `ISC AND MIT AND OpenSSL`,
214 // though the `package.license` field is not set.
216 // See https://github.com/briansmith/ring/issues/902
220 /// These are the root crates that are part of the runtime. The licenses for
221 /// these and all their dependencies *must not* be in the exception list.
222 const RUNTIME_CRATES
: &[&str] = &["std", "core", "alloc", "test", "panic_abort", "panic_unwind"];
224 const PERMITTED_DEPS_LOCATION
: &str = concat
!(file
!(), ":", line
!());
226 /// Crates rustc is allowed to depend on. Avoid adding to the list if possible.
228 /// This list is here to provide a speed-bump to adding a new dependency to
229 /// rustc. Please check with the compiler team before adding an entry.
230 const PERMITTED_RUSTC_DEPENDENCIES
: &[&str] = &[
231 // tidy-alphabetical-start
236 "allocator-api2", // FIXME: only appears in Cargo.lock due to https://github.com/rust-lang/cargo/issues/10801
244 "byteorder", // via ruzstd in object in thorin-dwp
275 "fallible-iterator", // dependency of `thorin`
294 "icu_locid_transform",
295 "icu_locid_transform_data",
297 "icu_provider_adapters",
298 "icu_provider_macros",
333 "perf-event-open-sys",
336 "portable-atomic", // dependency for platforms doesn't support `AtomicU64` in std
343 "pulldown-cmark-escape",
365 "ruzstd", // via object in thorin-dwp
380 "stable_deref_trait",
401 "tracing-attributes",
404 "tracing-subscriber",
411 "unic-langid-macros",
412 "unic-langid-macros-impl",
415 "unicode-normalization",
416 "unicode-properties",
428 "winapi-i686-pc-windows-gnu",
430 "winapi-x86_64-pc-windows-gnu",
435 "windows_aarch64_gnullvm",
436 "windows_aarch64_msvc",
438 "windows_i686_gnullvm",
440 "windows_x86_64_gnu",
441 "windows_x86_64_gnullvm",
442 "windows_x86_64_msvc",
452 // tidy-alphabetical-end
455 const PERMITTED_CRANELIFT_DEPENDENCIES
: &[&str] = &[
456 // tidy-alphabetical-start
465 "cranelift-codegen-meta",
466 "cranelift-codegen-shared",
469 "cranelift-frontend",
495 "stable_deref_trait",
500 "wasmtime-jit-icache-coherence",
502 "winapi-i686-pc-windows-gnu",
503 "winapi-x86_64-pc-windows-gnu",
506 "windows_aarch64_gnullvm",
507 "windows_aarch64_msvc",
509 "windows_i686_gnullvm",
511 "windows_x86_64_gnu",
512 "windows_x86_64_gnullvm",
513 "windows_x86_64_msvc",
516 // tidy-alphabetical-end
519 /// Dependency checks.
521 /// `root` is path to the directory with the root `Cargo.toml` (for the workspace). `cargo` is path
522 /// to the cargo executable.
523 pub fn check(root
: &Path
, cargo
: &Path
, bad
: &mut bool
) {
524 let mut checked_runtime_licenses
= false;
526 let submodules
= build_helper
::util
::parse_gitmodules(root
);
527 for &(workspace
, exceptions
, permitted_deps
) in WORKSPACES
{
528 // Skip if it's a submodule, not in a CI environment, and not initialized.
530 // This prevents enforcing developers to fetch submodules for tidy.
531 if submodules
.contains(&workspace
.into())
533 // If the directory is empty, we can consider it as an uninitialized submodule.
534 && read_dir(root
.join(workspace
)).unwrap().next().is_none()
539 if !root
.join(workspace
).join("Cargo.lock").exists() {
540 tidy_error
!(bad
, "the `{workspace}` workspace doesn't have a Cargo.lock");
544 let mut cmd
= cargo_metadata
::MetadataCommand
::new();
545 cmd
.cargo_path(cargo
)
546 .manifest_path(root
.join(workspace
).join("Cargo.toml"))
547 .features(cargo_metadata
::CargoOpt
::AllFeatures
)
548 .other_options(vec
!["--locked".to_owned()]);
549 let metadata
= t
!(cmd
.exec());
551 check_license_exceptions(&metadata
, exceptions
, bad
);
552 if let Some((crates
, permitted_deps
)) = permitted_deps
{
553 check_permitted_dependencies(&metadata
, workspace
, permitted_deps
, crates
, bad
);
556 if workspace
== "." {
557 let runtime_ids
= compute_runtime_crates(&metadata
);
558 check_runtime_license_exceptions(&metadata
, runtime_ids
, bad
);
559 checked_runtime_licenses
= true;
563 // Sanity check to ensure we don't accidentally remove the workspace containing the runtime
565 assert
!(checked_runtime_licenses
);
568 /// Check that all licenses of runtime dependencies are in the valid list in `LICENSES`.
570 /// Unlike for tools we don't allow exceptions to the `LICENSES` list for the runtime with the sole
571 /// exception of `fortanix-sgx-abi` which is only used on x86_64-fortanix-unknown-sgx.
572 fn check_runtime_license_exceptions(
574 runtime_ids
: HashSet
<&PackageId
>,
577 for pkg
in &metadata
.packages
{
578 if !runtime_ids
.contains(&pkg
.id
) {
579 // Only checking dependencies of runtime libraries here.
582 if pkg
.source
.is_none() {
583 // No need to check local packages.
586 let license
= match &pkg
.license
{
587 Some(license
) => license
,
589 tidy_error
!(bad
, "dependency `{}` does not define a license expression", pkg
.id
);
593 if !LICENSES
.contains(&license
.as_str()) {
594 // This is a specific exception because SGX is considered "third party".
595 // See https://github.com/rust-lang/rust/issues/62620 for more.
596 // In general, these should never be added and this exception
597 // should not be taken as precedent for any new target.
598 if pkg
.name
== "fortanix-sgx-abi" && pkg
.license
.as_deref() == Some("MPL-2.0") {
602 // This exception is due to the fact that the feature set of the
603 // `object` crate is different between rustc and libstd. In the
604 // standard library only a conservative set of features are enabled
605 // which notably does not include the `wasm` feature which pulls in
606 // this dependency. In the compiler, however, the `wasm` feature is
607 // enabled. This exception is intended to be here so long as the
608 // `EXCEPTIONS` above contains `wasmparser`, but once that goes away
609 // this can be removed.
610 if pkg
.name
== "wasmparser"
611 && pkg
.license
.as_deref() == Some("Apache-2.0 WITH LLVM-exception")
616 tidy_error
!(bad
, "invalid license `{}` in `{}`", license
, pkg
.id
);
621 /// Check that all licenses of tool dependencies are in the valid list in `LICENSES`.
623 /// Packages listed in `exceptions` are allowed for tools.
624 fn check_license_exceptions(metadata
: &Metadata
, exceptions
: &[(&str, &str)], bad
: &mut bool
) {
625 // Validate the EXCEPTIONS list hasn't changed.
626 for (name
, license
) in exceptions
{
627 // Check that the package actually exists.
628 if !metadata
.packages
.iter().any(|p
| p
.name
== *name
) {
631 "could not find exception package `{}`\n\
632 Remove from EXCEPTIONS list if it is no longer used.",
636 // Check that the license hasn't changed.
637 for pkg
in metadata
.packages
.iter().filter(|p
| p
.name
== *name
) {
640 if *license
== NON_STANDARD_LICENSE
641 && EXCEPTIONS_NON_STANDARD_LICENSE_DEPS
.contains(&pkg
.name
.as_str())
647 "dependency exception `{}` does not declare a license expression",
651 Some(pkg_license
) => {
652 if pkg_license
.as_str() != *license
{
653 println
!("dependency exception `{name}` license has changed");
654 println
!(" previously `{license}` now `{pkg_license}`");
655 println
!(" update EXCEPTIONS for the new license");
663 let exception_names
: Vec
<_
> = exceptions
.iter().map(|(name
, _license
)| *name
).collect();
665 // Check if any package does not have a valid license.
666 for pkg
in &metadata
.packages
{
667 if pkg
.source
.is_none() {
668 // No need to check local packages.
671 if exception_names
.contains(&pkg
.name
.as_str()) {
674 let license
= match &pkg
.license
{
675 Some(license
) => license
,
677 tidy_error
!(bad
, "dependency `{}` does not define a license expression", pkg
.id
);
681 if !LICENSES
.contains(&license
.as_str()) {
682 tidy_error
!(bad
, "invalid license `{}` in `{}`", license
, pkg
.id
);
687 /// Checks the dependency of `restricted_dependency_crates` at the given path. Changes `bad` to
688 /// `true` if a check failed.
690 /// Specifically, this checks that the dependencies are on the `permitted_dependencies`.
691 fn check_permitted_dependencies(
694 permitted_dependencies
: &[&'
static str],
695 restricted_dependency_crates
: &[&'
static str],
698 let mut has_permitted_dep_error
= false;
699 let mut deps
= HashSet
::new();
700 for to_check
in restricted_dependency_crates
{
701 let to_check
= pkg_from_name(metadata
, to_check
);
702 deps_of(metadata
, &to_check
.id
, &mut deps
);
705 // Check that the PERMITTED_DEPENDENCIES does not have unused entries.
706 for permitted
in permitted_dependencies
{
707 if !deps
.iter().any(|dep_id
| &pkg_from_id(metadata
, dep_id
).name
== permitted
) {
710 "could not find allowed package `{permitted}`\n\
711 Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
713 has_permitted_dep_error
= true;
717 // Get in a convenient form.
718 let permitted_dependencies
: HashSet
<_
> = permitted_dependencies
.iter().cloned().collect();
721 let dep
= pkg_from_id(metadata
, dep
);
722 // If this path is in-tree, we don't require it to be explicitly permitted.
723 if dep
.source
.is_some() && !permitted_dependencies
.contains(dep
.name
.as_str()) {
724 tidy_error
!(bad
, "Dependency for {descr} not explicitly permitted: {}", dep
.id
);
725 has_permitted_dep_error
= true;
729 if has_permitted_dep_error
{
730 eprintln
!("Go to `{PERMITTED_DEPS_LOCATION}` for the list.");
734 /// Finds a package with the given name.
735 fn pkg_from_name
<'a
>(metadata
: &'a Metadata
, name
: &'
static str) -> &'a Package
{
736 let mut i
= metadata
.packages
.iter().filter(|p
| p
.name
== name
);
738 i
.next().unwrap_or_else(|| panic
!("could not find package `{name}` in package list"));
739 assert
!(i
.next().is_none(), "more than one package found for `{name}`");
743 fn pkg_from_id
<'a
>(metadata
: &'a Metadata
, id
: &PackageId
) -> &'a Package
{
744 metadata
.packages
.iter().find(|p
| &p
.id
== id
).unwrap()
747 /// Finds all the packages that are in the rust runtime.
748 fn compute_runtime_crates
<'a
>(metadata
: &'a Metadata
) -> HashSet
<&'a PackageId
> {
749 let mut result
= HashSet
::new();
750 for name
in RUNTIME_CRATES
{
751 let id
= &pkg_from_name(metadata
, name
).id
;
752 deps_of(metadata
, id
, &mut result
);
757 /// Recursively find all dependencies.
758 fn deps_of
<'a
>(metadata
: &'a Metadata
, pkg_id
: &'a PackageId
, result
: &mut HashSet
<&'a PackageId
>) {
759 if !result
.insert(pkg_id
) {
768 .find(|n
| &n
.id
== pkg_id
)
769 .unwrap_or_else(|| panic
!("could not find `{pkg_id}` in resolve"));
770 for dep
in &node
.deps
{
771 deps_of(metadata
, &dep
.pkg
, result
);