use std::collections::HashSet;
use std::fs::read_dir;
use std::path::Path;
use build_helper::ci::CiEnv;
use cargo_metadata::{Metadata, Package, PackageId};
#[rustfmt::skip]
const LICENSES: &[&str] = &[
"(MIT OR Apache-2.0) AND Unicode-DFS-2016", "0BSD OR MIT OR Apache-2.0", "0BSD",
"Apache-2.0 / MIT",
"Apache-2.0 OR ISC OR MIT",
"Apache-2.0 OR MIT",
"Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", "Apache-2.0",
"Apache-2.0/MIT",
"BSD-2-Clause OR Apache-2.0 OR MIT", "ISC",
"MIT / Apache-2.0",
"MIT AND (MIT OR Apache-2.0)",
"MIT AND Apache-2.0 WITH LLVM-exception AND (MIT OR Apache-2.0)", "MIT OR Apache-2.0 OR LGPL-2.1-or-later", "MIT OR Apache-2.0 OR Zlib", "MIT OR Apache-2.0",
"MIT OR Zlib OR Apache-2.0", "MIT",
"MIT/Apache-2.0",
"Unicode-3.0", "Unicode-DFS-2016", "Unlicense OR MIT",
"Unlicense/MIT",
"Zlib OR Apache-2.0 OR MIT", ];
type ExceptionList = &'static [(&'static str, &'static str)];
pub(crate) const WORKSPACES: &[(&str, ExceptionList, Option<(&[&str], &[&str])>, &[&str])] = &[
(".", EXCEPTIONS, Some((&["rustc-main"], PERMITTED_RUSTC_DEPENDENCIES)), &[]),
("library", EXCEPTIONS_STDLIB, Some((&["sysroot"], PERMITTED_STDLIB_DEPENDENCIES)), &[]),
(
"compiler/rustc_codegen_cranelift",
EXCEPTIONS_CRANELIFT,
Some((&["rustc_codegen_cranelift"], PERMITTED_CRANELIFT_DEPENDENCIES)),
&[],
),
("compiler/rustc_codegen_gcc", EXCEPTIONS_GCC, None, &[]),
("src/bootstrap", EXCEPTIONS_BOOTSTRAP, None, &[]),
("src/ci/docker/host-x86_64/test-various/uefi_qemu_test", EXCEPTIONS_UEFI_QEMU_TEST, None, &[]),
("src/etc/test-float-parse", EXCEPTIONS, None, &[]),
("src/tools/cargo", EXCEPTIONS_CARGO, None, &["src/tools/cargo"]),
("src/tools/rust-analyzer", EXCEPTIONS_RUST_ANALYZER, None, &[]),
("src/tools/rustbook", EXCEPTIONS_RUSTBOOK, None, &["src/doc/book", "src/doc/reference"]),
("src/tools/rustc-perf", EXCEPTIONS_RUSTC_PERF, None, &["src/tools/rustc-perf"]),
("src/tools/x", &[], None, &[]),
];
#[rustfmt::skip]
const EXCEPTIONS: ExceptionList = &[
("ar_archive_writer", "Apache-2.0 WITH LLVM-exception"), ("arrayref", "BSD-2-Clause"), ("blake3", "CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception"), ("colored", "MPL-2.0"), ("constant_time_eq", "CC0-1.0 OR MIT-0 OR Apache-2.0"), ("dissimilar", "Apache-2.0"), ("fluent-langneg", "Apache-2.0"), ("foldhash", "Zlib"), ("instant", "BSD-3-Clause"), ("mdbook", "MPL-2.0"), ("option-ext", "MPL-2.0"), ("rustc_apfloat", "Apache-2.0 WITH LLVM-exception"), ("ryu", "Apache-2.0 OR BSL-1.0"), ("self_cell", "Apache-2.0"), ("wasi-preview1-component-adapter-provider", "Apache-2.0 WITH LLVM-exception"), ];
#[rustfmt::skip]
const EXCEPTIONS_STDLIB: ExceptionList = &[
("fortanix-sgx-abi", "MPL-2.0"), ];
const EXCEPTIONS_CARGO: ExceptionList = &[
("arrayref", "BSD-2-Clause"),
("bitmaps", "MPL-2.0+"),
("blake3", "CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception"),
("bytesize", "Apache-2.0"),
("ciborium", "Apache-2.0"),
("ciborium-io", "Apache-2.0"),
("ciborium-ll", "Apache-2.0"),
("constant_time_eq", "CC0-1.0 OR MIT-0 OR Apache-2.0"),
("dunce", "CC0-1.0 OR MIT-0 OR Apache-2.0"),
("encoding_rs", "(Apache-2.0 OR MIT) AND BSD-3-Clause"),
("fiat-crypto", "MIT OR Apache-2.0 OR BSD-1-Clause"),
("im-rc", "MPL-2.0+"),
("normalize-line-endings", "Apache-2.0"),
("openssl", "Apache-2.0"),
("ryu", "Apache-2.0 OR BSL-1.0"), ("sha1_smol", "BSD-3-Clause"),
("similar", "Apache-2.0"),
("sized-chunks", "MPL-2.0+"),
("subtle", "BSD-3-Clause"),
("supports-hyperlinks", "Apache-2.0"),
("unicode-bom", "Apache-2.0"),
];
const EXCEPTIONS_RUST_ANALYZER: ExceptionList = &[
("dissimilar", "Apache-2.0"),
("notify", "CC0-1.0"),
("option-ext", "MPL-2.0"),
("pulldown-cmark-to-cmark", "Apache-2.0"),
("rustc_apfloat", "Apache-2.0 WITH LLVM-exception"),
("ryu", "Apache-2.0 OR BSL-1.0"), ("scip", "Apache-2.0"),
];
const EXCEPTIONS_RUSTC_PERF: ExceptionList = &[
("alloc-no-stdlib", "BSD-3-Clause"),
("alloc-stdlib", "BSD-3-Clause"),
("brotli", "BSD-3-Clause/MIT"),
("brotli-decompressor", "BSD-3-Clause/MIT"),
("encoding_rs", "(Apache-2.0 OR MIT) AND BSD-3-Clause"),
("inferno", "CDDL-1.0"),
("instant", "BSD-3-Clause"),
("ring", NON_STANDARD_LICENSE), ("ryu", "Apache-2.0 OR BSL-1.0"),
("snap", "BSD-3-Clause"),
("subtle", "BSD-3-Clause"),
];
const EXCEPTIONS_RUSTBOOK: ExceptionList = &[
("mdbook", "MPL-2.0"),
("ryu", "Apache-2.0 OR BSL-1.0"),
];
const EXCEPTIONS_CRANELIFT: ExceptionList = &[
("cranelift-bforest", "Apache-2.0 WITH LLVM-exception"),
("cranelift-bitset", "Apache-2.0 WITH LLVM-exception"),
("cranelift-codegen", "Apache-2.0 WITH LLVM-exception"),
("cranelift-codegen-meta", "Apache-2.0 WITH LLVM-exception"),
("cranelift-codegen-shared", "Apache-2.0 WITH LLVM-exception"),
("cranelift-control", "Apache-2.0 WITH LLVM-exception"),
("cranelift-entity", "Apache-2.0 WITH LLVM-exception"),
("cranelift-frontend", "Apache-2.0 WITH LLVM-exception"),
("cranelift-isle", "Apache-2.0 WITH LLVM-exception"),
("cranelift-jit", "Apache-2.0 WITH LLVM-exception"),
("cranelift-module", "Apache-2.0 WITH LLVM-exception"),
("cranelift-native", "Apache-2.0 WITH LLVM-exception"),
("cranelift-object", "Apache-2.0 WITH LLVM-exception"),
("mach2", "BSD-2-Clause OR MIT OR Apache-2.0"),
("regalloc2", "Apache-2.0 WITH LLVM-exception"),
("target-lexicon", "Apache-2.0 WITH LLVM-exception"),
("wasmtime-jit-icache-coherence", "Apache-2.0 WITH LLVM-exception"),
];
const EXCEPTIONS_GCC: ExceptionList = &[
("gccjit", "GPL-3.0"),
("gccjit_sys", "GPL-3.0"),
];
const EXCEPTIONS_BOOTSTRAP: ExceptionList = &[
("ryu", "Apache-2.0 OR BSL-1.0"), ];
const EXCEPTIONS_UEFI_QEMU_TEST: ExceptionList = &[
("r-efi", "MIT OR Apache-2.0 OR LGPL-2.1-or-later"), ];
const NON_STANDARD_LICENSE: &str = "NON_STANDARD_LICENSE";
const EXCEPTIONS_NON_STANDARD_LICENSE_DEPS: &[&str] = &[
"ring",
];
const PERMITTED_DEPS_LOCATION: &str = concat!(file!(), ":", line!());
const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
"adler2",
"ahash",
"aho-corasick",
"allocator-api2", "annotate-snippets",
"anstyle",
"ar_archive_writer",
"arrayref",
"arrayvec",
"autocfg",
"bitflags",
"blake3",
"block-buffer",
"byteorder", "cc",
"cfg-if",
"cfg_aliases",
"constant_time_eq",
"cpufeatures",
"crc32fast",
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-utils",
"crypto-common",
"ctrlc",
"darling",
"darling_core",
"darling_macro",
"datafrog",
"deranged",
"derive-where",
"derive_setters",
"digest",
"displaydoc",
"dissimilar",
"either",
"elsa",
"ena",
"equivalent",
"errno",
"expect-test",
"fallible-iterator", "fastrand",
"field-offset",
"flate2",
"fluent-bundle",
"fluent-langneg",
"fluent-syntax",
"fnv",
"foldhash",
"generic-array",
"getopts",
"getrandom",
"gimli",
"gsgdt",
"hashbrown",
"hermit-abi",
"icu_list",
"icu_list_data",
"icu_locid",
"icu_locid_transform",
"icu_locid_transform_data",
"icu_provider",
"icu_provider_adapters",
"icu_provider_macros",
"ident_case",
"indexmap",
"intl-memoizer",
"intl_pluralrules",
"itertools",
"itoa",
"jemalloc-sys",
"jobserver",
"lazy_static",
"leb128",
"libc",
"libloading",
"linux-raw-sys",
"litemap",
"lock_api",
"log",
"matchers",
"md-5",
"measureme",
"memchr",
"memmap2",
"memoffset",
"miniz_oxide",
"nix",
"nu-ansi-term",
"num-conv",
"num_cpus",
"object",
"odht",
"once_cell",
"overload",
"parking_lot",
"parking_lot_core",
"pathdiff",
"perf-event-open-sys",
"pin-project-lite",
"polonius-engine",
"portable-atomic", "powerfmt",
"ppv-lite86",
"proc-macro-hack",
"proc-macro2",
"psm",
"pulldown-cmark",
"pulldown-cmark-escape",
"punycode",
"quote",
"rand",
"rand_chacha",
"rand_core",
"rand_xoshiro",
"redox_syscall",
"regex",
"regex-automata",
"regex-syntax",
"rustc-demangle",
"rustc-hash",
"rustc-rayon",
"rustc-rayon-core",
"rustc-stable-hash",
"rustc_apfloat",
"rustc_version",
"rustix",
"ruzstd", "ryu",
"scoped-tls",
"scopeguard",
"self_cell",
"semver",
"serde",
"serde_derive",
"serde_json",
"sha1",
"sha2",
"sharded-slab",
"shlex",
"smallvec",
"stable_deref_trait",
"stacker",
"static_assertions",
"strsim",
"syn",
"synstructure",
"tempfile",
"termcolor",
"termize",
"thin-vec",
"thiserror",
"thiserror-impl",
"thorin-dwp",
"thread_local",
"time",
"time-core",
"time-macros",
"tinystr",
"tinyvec",
"tinyvec_macros",
"tracing",
"tracing-attributes",
"tracing-core",
"tracing-log",
"tracing-subscriber",
"tracing-tree",
"twox-hash",
"type-map",
"typenum",
"unic-langid",
"unic-langid-impl",
"unic-langid-macros",
"unic-langid-macros-impl",
"unicase",
"unicode-ident",
"unicode-normalization",
"unicode-properties",
"unicode-script",
"unicode-security",
"unicode-width",
"unicode-xid",
"valuable",
"version_check",
"wasi",
"wasm-encoder",
"wasmparser",
"winapi",
"winapi-i686-pc-windows-gnu",
"winapi-util",
"winapi-x86_64-pc-windows-gnu",
"windows",
"windows-core",
"windows-implement",
"windows-interface",
"windows-result",
"windows-sys",
"windows-targets",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"writeable",
"yoke",
"yoke-derive",
"zerocopy",
"zerocopy-derive",
"zerofrom",
"zerofrom-derive",
"zerovec",
"zerovec-derive",
];
const PERMITTED_STDLIB_DEPENDENCIES: &[&str] = &[
"addr2line",
"adler",
"allocator-api2",
"cc",
"cfg-if",
"compiler_builtins",
"dlmalloc",
"fortanix-sgx-abi",
"getopts",
"gimli",
"hashbrown",
"hermit-abi",
"libc",
"memchr",
"miniz_oxide",
"object",
"r-efi",
"r-efi-alloc",
"rand",
"rand_core",
"rand_xorshift",
"rustc-demangle",
"shlex",
"unicode-width",
"unwinding",
"wasi",
"windows-sys",
"windows-targets",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
];
const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
"ahash",
"anyhow",
"arbitrary",
"bitflags",
"bumpalo",
"cfg-if",
"cranelift-bforest",
"cranelift-bitset",
"cranelift-codegen",
"cranelift-codegen-meta",
"cranelift-codegen-shared",
"cranelift-control",
"cranelift-entity",
"cranelift-frontend",
"cranelift-isle",
"cranelift-jit",
"cranelift-module",
"cranelift-native",
"cranelift-object",
"crc32fast",
"equivalent",
"fallible-iterator",
"gimli",
"hashbrown",
"indexmap",
"libc",
"libloading",
"log",
"mach2",
"memchr",
"object",
"once_cell",
"proc-macro2",
"quote",
"regalloc2",
"region",
"rustc-hash",
"slice-group-by",
"smallvec",
"stable_deref_trait",
"syn",
"target-lexicon",
"unicode-ident",
"version_check",
"wasmtime-jit-icache-coherence",
"windows-sys",
"windows-targets",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"zerocopy",
"zerocopy-derive",
];
pub fn check(root: &Path, cargo: &Path, bad: &mut bool) {
let mut checked_runtime_licenses = false;
for &(workspace, exceptions, permitted_deps, submodules) in WORKSPACES {
if has_missing_submodule(root, submodules) {
continue;
}
if !root.join(workspace).join("Cargo.lock").exists() {
tidy_error!(bad, "the `{workspace}` workspace doesn't have a Cargo.lock");
continue;
}
let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.cargo_path(cargo)
.manifest_path(root.join(workspace).join("Cargo.toml"))
.features(cargo_metadata::CargoOpt::AllFeatures)
.other_options(vec!["--locked".to_owned()]);
let metadata = t!(cmd.exec());
check_license_exceptions(&metadata, exceptions, bad);
if let Some((crates, permitted_deps)) = permitted_deps {
check_permitted_dependencies(&metadata, workspace, permitted_deps, crates, bad);
}
if workspace == "library" {
check_runtime_license_exceptions(&metadata, bad);
checked_runtime_licenses = true;
}
}
assert!(checked_runtime_licenses);
}
pub fn has_missing_submodule(root: &Path, submodules: &[&str]) -> bool {
!CiEnv::is_ci()
&& submodules.iter().any(|submodule| {
read_dir(root.join(submodule)).unwrap().next().is_none()
})
}
fn check_runtime_license_exceptions(metadata: &Metadata, bad: &mut bool) {
for pkg in &metadata.packages {
if pkg.source.is_none() {
continue;
}
let license = match &pkg.license {
Some(license) => license,
None => {
tidy_error!(bad, "dependency `{}` does not define a license expression", pkg.id);
continue;
}
};
if !LICENSES.contains(&license.as_str()) {
if pkg.name == "fortanix-sgx-abi" && pkg.license.as_deref() == Some("MPL-2.0") {
continue;
}
tidy_error!(bad, "invalid license `{}` in `{}`", license, pkg.id);
}
}
}
fn check_license_exceptions(metadata: &Metadata, exceptions: &[(&str, &str)], bad: &mut bool) {
for (name, license) in exceptions {
if !metadata.packages.iter().any(|p| p.name == *name) {
tidy_error!(
bad,
"could not find exception package `{}`\n\
Remove from EXCEPTIONS list if it is no longer used.",
name
);
}
for pkg in metadata.packages.iter().filter(|p| p.name == *name) {
match &pkg.license {
None => {
if *license == NON_STANDARD_LICENSE
&& EXCEPTIONS_NON_STANDARD_LICENSE_DEPS.contains(&pkg.name.as_str())
{
continue;
}
tidy_error!(
bad,
"dependency exception `{}` does not declare a license expression",
pkg.id
);
}
Some(pkg_license) => {
if pkg_license.as_str() != *license {
println!("dependency exception `{name}` license has changed");
println!(" previously `{license}` now `{pkg_license}`");
println!(" update EXCEPTIONS for the new license");
*bad = true;
}
}
}
}
}
let exception_names: Vec<_> = exceptions.iter().map(|(name, _license)| *name).collect();
for pkg in &metadata.packages {
if pkg.source.is_none() {
continue;
}
if exception_names.contains(&pkg.name.as_str()) {
continue;
}
let license = match &pkg.license {
Some(license) => license,
None => {
tidy_error!(bad, "dependency `{}` does not define a license expression", pkg.id);
continue;
}
};
if !LICENSES.contains(&license.as_str()) {
tidy_error!(bad, "invalid license `{}` in `{}`", license, pkg.id);
}
}
}
fn check_permitted_dependencies(
metadata: &Metadata,
descr: &str,
permitted_dependencies: &[&'static str],
restricted_dependency_crates: &[&'static str],
bad: &mut bool,
) {
let mut has_permitted_dep_error = false;
let mut deps = HashSet::new();
for to_check in restricted_dependency_crates {
let to_check = pkg_from_name(metadata, to_check);
deps_of(metadata, &to_check.id, &mut deps);
}
for permitted in permitted_dependencies {
if !deps.iter().any(|dep_id| &pkg_from_id(metadata, dep_id).name == permitted) {
tidy_error!(
bad,
"could not find allowed package `{permitted}`\n\
Remove from PERMITTED_DEPENDENCIES list if it is no longer used.",
);
has_permitted_dep_error = true;
}
}
let permitted_dependencies: HashSet<_> = permitted_dependencies.iter().cloned().collect();
for dep in deps {
let dep = pkg_from_id(metadata, dep);
if dep.source.is_some() && !permitted_dependencies.contains(dep.name.as_str()) {
tidy_error!(bad, "Dependency for {descr} not explicitly permitted: {}", dep.id);
has_permitted_dep_error = true;
}
}
if has_permitted_dep_error {
eprintln!("Go to `{PERMITTED_DEPS_LOCATION}` for the list.");
}
}
fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
let mut i = metadata.packages.iter().filter(|p| p.name == name);
let result =
i.next().unwrap_or_else(|| panic!("could not find package `{name}` in package list"));
assert!(i.next().is_none(), "more than one package found for `{name}`");
result
}
fn pkg_from_id<'a>(metadata: &'a Metadata, id: &PackageId) -> &'a Package {
metadata.packages.iter().find(|p| &p.id == id).unwrap()
}
fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId, result: &mut HashSet<&'a PackageId>) {
if !result.insert(pkg_id) {
return;
}
let node = metadata
.resolve
.as_ref()
.unwrap()
.nodes
.iter()
.find(|n| &n.id == pkg_id)
.unwrap_or_else(|| panic!("could not find `{pkg_id}` in resolve"));
for dep in &node.deps {
deps_of(metadata, &dep.pkg, result);
}
}