//! Checks the licenses of third-party dependencies.
-use cargo_metadata::{Metadata, Package, PackageId};
+use cargo_metadata::{Metadata, Package, PackageId, Resolve};
use std::collections::{BTreeSet, HashSet};
use std::path::Path;
+/// These are licenses that are allowed for all crates, including the runtime,
+/// rustc, tools, etc.
const LICENSES: &[&str] = &[
"MIT/Apache-2.0",
"MIT / Apache-2.0",
/// should be considered bugs. Exceptions are only allowed in Rust
/// tooling. It is _crucial_ that no exception crates be dependencies
/// of the Rust runtime (std/test).
-const EXCEPTIONS: &[&str] = &[
- "mdbook", // MPL2, mdbook
- "openssl", // BSD+advertising clause, cargo, mdbook
- "pest", // MPL2, mdbook via handlebars
- "arrayref", // BSD-2-Clause, mdbook via handlebars via pest
- "thread-id", // Apache-2.0, mdbook
- "toml-query", // MPL-2.0, mdbook
- "toml-query_derive", // MPL-2.0, mdbook
- "is-match", // MPL-2.0, mdbook
- "cssparser", // MPL-2.0, rustdoc
- "smallvec", // MPL-2.0, rustdoc
- "rdrand", // ISC, mdbook, rustfmt
- "fuchsia-cprng", // BSD-3-Clause, mdbook, rustfmt
- "fuchsia-zircon-sys", // BSD-3-Clause, rustdoc, rustc, cargo
- "fuchsia-zircon", // BSD-3-Clause, rustdoc, rustc, cargo (jobserver & tempdir)
- "cssparser-macros", // MPL-2.0, rustdoc
- "selectors", // MPL-2.0, rustdoc
- "clippy_lints", // MPL-2.0, rls
- "colored", // MPL-2.0, rustfmt
- "ordslice", // Apache-2.0, rls
- "cloudabi", // BSD-2-Clause, (rls -> crossbeam-channel 0.2 -> rand 0.5)
- "ryu", // Apache-2.0, rls/cargo/... (because of serde)
- "bytesize", // Apache-2.0, cargo
- "im-rc", // MPL-2.0+, cargo
- "adler32", // BSD-3-Clause AND Zlib, cargo dep that isn't used
- "constant_time_eq", // CC0-1.0, rustfmt
- "utf8parse", // Apache-2.0 OR MIT, cargo via strip-ansi-escapes
- "vte", // Apache-2.0 OR MIT, cargo via strip-ansi-escapes
- "sized-chunks", // MPL-2.0+, cargo via im-rc
- "bitmaps", // MPL-2.0+, cargo via im-rc
+const EXCEPTIONS: &[(&str, &str)] = &[
+ ("mdbook", "MPL-2.0"), // mdbook
+ ("openssl", "Apache-2.0"), // cargo, mdbook
+ ("arrayref", "BSD-2-Clause"), // mdbook via handlebars via pest
+ ("toml-query", "MPL-2.0"), // mdbook
+ ("toml-query_derive", "MPL-2.0"), // mdbook
+ ("is-match", "MPL-2.0"), // mdbook
+ ("rdrand", "ISC"), // mdbook, rustfmt
+ ("fuchsia-cprng", "BSD-3-Clause"), // mdbook, rustfmt
+ ("fuchsia-zircon-sys", "BSD-3-Clause"), // rustdoc, rustc, cargo
+ ("fuchsia-zircon", "BSD-3-Clause"), // rustdoc, rustc, cargo (jobserver & tempdir)
+ ("colored", "MPL-2.0"), // rustfmt
+ ("ordslice", "Apache-2.0"), // rls
+ ("cloudabi", "BSD-2-Clause"), // (rls -> crossbeam-channel 0.2 -> rand 0.5)
+ ("ryu", "Apache-2.0 OR BSL-1.0"), // rls/cargo/... (because of serde)
+ ("bytesize", "Apache-2.0"), // cargo
+ ("im-rc", "MPL-2.0+"), // cargo
+ ("adler32", "BSD-3-Clause AND Zlib"), // cargo dep that isn't used
+ ("constant_time_eq", "CC0-1.0"), // rustfmt
+ ("sized-chunks", "MPL-2.0+"), // cargo via im-rc
+ ("bitmaps", "MPL-2.0+"), // cargo via im-rc
// FIXME: this dependency violates the documentation comment above:
- "fortanix-sgx-abi", // MPL-2.0+, libstd but only for `sgx` target
- "dunce", // CC0-1.0 mdbook-linkcheck
- "codespan-reporting", // Apache-2.0 mdbook-linkcheck
- "codespan", // Apache-2.0 mdbook-linkcheck
- "crossbeam-channel", // MIT/Apache-2.0 AND BSD-2-Clause, cargo
+ ("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target
+ ("dunce", "CC0-1.0"), // mdbook-linkcheck
+ ("codespan-reporting", "Apache-2.0"), // mdbook-linkcheck
+ ("codespan", "Apache-2.0"), // mdbook-linkcheck
+ ("crossbeam-channel", "MIT/Apache-2.0 AND BSD-2-Clause"), // cargo
];
+/// These are the root crates that are part of the runtime. The licenses for
+/// these and all their dependencies *must not* be in the exception list.
+const RUNTIME_CRATES: &[&str] = &["std", "core", "alloc", "test", "panic_abort", "panic_unwind"];
+
/// Which crates to check against the whitelist?
const WHITELIST_CRATES: &[&str] = &["rustc", "rustc_codegen_llvm"];
/// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
+///
+/// This list is here to provide a speed-bump to adding a new dependency to
+/// rustc. Please check with the compiler team before adding an entry.
const WHITELIST: &[&str] = &[
"adler32",
"aho-corasick",
"backtrace",
"backtrace-sys",
"bitflags",
- "build_const",
"byteorder",
"c2-chacha",
"cc",
"cloudabi",
"cmake",
"compiler_builtins",
- "crc",
"crc32fast",
"crossbeam-deque",
"crossbeam-epoch",
"memchr",
"memmap",
"memoffset",
- "miniz-sys",
"miniz_oxide",
- "miniz_oxide_c_api",
"nodrop",
"num_cpus",
- "owning_ref",
"parking_lot",
"parking_lot_core",
"pkg-config",
"synstructure",
"tempfile",
"termcolor",
- "terminon",
"termion",
"termize",
"thread_local",
"unicode-security",
"unicode-width",
"unicode-xid",
- "unreachable",
"utf8-ranges",
"vcpkg",
"version_check",
- "void",
"wasi",
"winapi",
"winapi-build",
///
/// Packages listed in `EXCEPTIONS` are allowed for tools.
fn check_exceptions(metadata: &Metadata, bad: &mut bool) {
+ // Validate the EXCEPTIONS list hasn't changed.
+ for (name, license) in EXCEPTIONS {
+ // Check that the package actually exists.
+ if !metadata.packages.iter().any(|p| p.name == *name) {
+ println!(
+ "could not find exception package `{}`\n\
+ Remove from EXCEPTIONS list if it is no longer used.",
+ name
+ );
+ *bad = true;
+ }
+ // Check that the license hasn't changed.
+ for pkg in metadata.packages.iter().filter(|p| p.name == *name) {
+ if pkg.name == "fuchsia-cprng" {
+ // This package doesn't declare a license expression. Manual
+ // inspection of the license file is necessary, which appears
+ // to be BSD-3-Clause.
+ assert!(pkg.license.is_none());
+ continue;
+ }
+ match &pkg.license {
+ None => {
+ println!(
+ "dependency exception `{}` does not declare a license expression",
+ pkg.id
+ );
+ *bad = true;
+ }
+ Some(pkg_license) => {
+ if pkg_license.as_str() != *license {
+ println!("dependency exception `{}` license has changed", name);
+ println!(" previously `{}` now `{}`", license, pkg_license);
+ println!(" update EXCEPTIONS for the new license");
+ *bad = true;
+ }
+ }
+ }
+ }
+ }
+
+ let exception_names: Vec<_> = EXCEPTIONS.iter().map(|(name, _license)| *name).collect();
+ let runtime_ids = compute_runtime_crates(metadata);
+
+ // Check if any package does not have a valid license.
for pkg in &metadata.packages {
if pkg.source.is_none() {
// No need to check local packages.
continue;
}
- if EXCEPTIONS.contains(&pkg.name.as_str()) {
+ if !runtime_ids.contains(&pkg.id) && exception_names.contains(&pkg.name.as_str()) {
continue;
}
let license = match &pkg.license {
}
};
if !LICENSES.contains(&license.as_str()) {
+ if pkg.name == "fortanix-sgx-abi" {
+ // This is a specific exception because SGX is considered
+ // "third party". See
+ // https://github.com/rust-lang/rust/issues/62620 for more. In
+ // general, these should never be added.
+ continue;
+ }
println!("invalid license `{}` in `{}`", license, pkg.id);
*bad = true;
}
///
/// Specifically, this checks that the dependencies are on the `WHITELIST`.
fn check_whitelist(metadata: &Metadata, bad: &mut bool) {
+ // Check that the WHITELIST does not have unused entries.
+ for name in WHITELIST {
+ if !metadata.packages.iter().any(|p| p.name == *name) {
+ println!(
+ "could not find whitelisted package `{}`\n\
+ Remove from WHITELIST list if it is no longer used.",
+ name
+ );
+ *bad = true;
+ }
+ }
// Get the whitelist in a convenient form.
let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
/// Returns a list of dependencies for the given package.
fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId) -> Vec<&'a Package> {
- let node = metadata
- .resolve
- .as_ref()
- .unwrap()
+ let resolve = metadata.resolve.as_ref().unwrap();
+ let node = resolve
.nodes
.iter()
.find(|n| &n.id == pkg_id)
assert!(i.next().is_none(), "more than one package found for `{}`", name);
result
}
+
+/// Finds all the packages that are in the rust runtime.
+fn compute_runtime_crates<'a>(metadata: &'a Metadata) -> HashSet<&'a PackageId> {
+ let resolve = metadata.resolve.as_ref().unwrap();
+ let mut result = HashSet::new();
+ for name in RUNTIME_CRATES {
+ let id = &pkg_from_name(metadata, name).id;
+ normal_deps_of_r(resolve, id, &mut result);
+ }
+ result
+}
+
+/// Recursively find all normal dependencies.
+fn normal_deps_of_r<'a>(
+ resolve: &'a Resolve,
+ pkg_id: &'a PackageId,
+ result: &mut HashSet<&'a PackageId>,
+) {
+ if !result.insert(pkg_id) {
+ return;
+ }
+ let node = resolve
+ .nodes
+ .iter()
+ .find(|n| &n.id == pkg_id)
+ .unwrap_or_else(|| panic!("could not find `{}` in resolve", pkg_id));
+ // Don't care about dev-dependencies.
+ // Build dependencies *shouldn't* matter unless they do some kind of
+ // codegen. For now we'll assume they don't.
+ let deps = node.deps.iter().filter(|node_dep| {
+ node_dep
+ .dep_kinds
+ .iter()
+ .any(|kind_info| kind_info.kind == cargo_metadata::DependencyKind::Normal)
+ });
+ for dep in deps {
+ normal_deps_of_r(resolve, &dep.pkg, result);
+ }
+}