rust/src/tools/tidy/src/deps.rs

395 lines
12 KiB
Rust
Raw Normal View History

//! Checks the licenses of third-party dependencies.
2016-12-11 00:27:42 +01:00
use cargo_metadata::{Metadata, Package, PackageId};
use std::collections::{BTreeSet, HashSet};
2016-12-11 00:27:42 +01:00
use std::path::Path;
const LICENSES: &[&str] = &[
"MIT/Apache-2.0",
rustbuild: Add support for compiling Cargo This commit adds support to rustbuild for compiling Cargo as part of the release process. Previously rustbuild would simply download a Cargo snapshot and repackage it. With this change we should be able to turn off artifacts from the rust-lang/cargo repository and purely rely on the artifacts Cargo produces here. The infrastructure added here is intended to be extensible to other components, such as the RLS. It won't exactly be a one-line addition, but the addition of Cargo didn't require too much hooplah anyway. The process for release Cargo will now look like: * The rust-lang/rust repository has a Cargo submodule which is used to build a Cargo to pair with the rust-lang/rust release * Periodically we'll update the cargo submodule as necessary on rust-lang/rust's master branch * When branching beta we'll create a new branch of Cargo (as we do today), and the first commit to the beta branch will be to update the Cargo submodule to this exact revision. * When branching stable, we'll ensure that the Cargo submodule is updated and then make a stable release. Backports to Cargo will look like: * Send a PR to cargo's master branch * Send a PR to cargo's release branch (e.g. rust-1.16.0) * Send a PR to rust-lang/rust's beta branch updating the submodule * Eventually send a PR to rust-lang/rust's master branch updating the submodule For reference, the process to add a new component to the rust-lang/rust release would look like: * Add `$foo` as a submodule in `src/tools` * Add a `tool-$foo` step which compiles `$foo` with the specified compiler, likely mirroring what Cargo does. * Add a `dist-$foo` step which uses `src/tools/$foo` and the `tool-$foo` output to create a rust-installer package for `$foo` likely mirroring what Cargo does. * Update the `dist-extended` step with a new dependency on `dist-$foo` * Update `src/tools/build-manifest` for the new component.
2017-02-16 00:57:06 +01:00
"MIT / Apache-2.0",
"Apache-2.0/MIT",
"Apache-2.0 / MIT",
"MIT OR Apache-2.0",
"Apache-2.0 OR MIT",
"Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
"MIT",
"Unlicense/MIT",
"Unlicense OR MIT",
];
/// These are exceptions to Rust's permissive licensing policy, and
/// should be considered bugs. Exceptions are only allowed in Rust
/// tooling. It is _crucial_ that no exception crates be dependencies
2019-01-11 17:32:31 +01:00
/// of the Rust runtime (std/test).
2020-02-24 21:54:32 +01:00
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
2019-06-21 17:35:48 +02:00
// FIXME: this dependency violates the documentation comment above:
2020-02-24 21:54:32 +01:00
("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
2016-12-11 00:27:42 +01:00
];
/// 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.
const WHITELIST: &[&str] = &[
"adler32",
"aho-corasick",
"annotate-snippets",
"ansi_term",
"arrayvec",
"atty",
"autocfg",
"backtrace",
"backtrace-sys",
"bitflags",
"byteorder",
"c2-chacha",
"cc",
"cfg-if",
"chalk-engine",
"chalk-macros",
"cloudabi",
"cmake",
"compiler_builtins",
"crc32fast",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
"datafrog",
"dlmalloc",
"either",
"ena",
"env_logger",
"filetime",
"flate2",
"fortanix-sgx-abi",
"fuchsia-zircon",
"fuchsia-zircon-sys",
"getopts",
"getrandom",
"hashbrown",
"humantime",
"indexmap",
"itertools",
"jobserver",
"kernel32-sys",
"lazy_static",
"libc",
"libz-sys",
"lock_api",
"log",
"log_settings",
"measureme",
"memchr",
"memmap",
"memoffset",
"miniz_oxide",
"nodrop",
"num_cpus",
"parking_lot",
"parking_lot_core",
"pkg-config",
"polonius-engine",
"ppv-lite86",
"proc-macro2",
"punycode",
"quick-error",
"quote",
"rand",
"rand_chacha",
"rand_core",
"rand_hc",
"rand_isaac",
"rand_pcg",
"rand_xorshift",
"redox_syscall",
"redox_termios",
"regex",
"regex-syntax",
"remove_dir_all",
"rustc-demangle",
"rustc-hash",
"rustc-rayon",
"rustc-rayon-core",
"rustc_version",
"scoped-tls",
"scopeguard",
"semver",
"semver-parser",
"serde",
"serde_derive",
"smallvec",
"stable_deref_trait",
"syn",
"synstructure",
"tempfile",
"termcolor",
"termion",
"termize",
"thread_local",
"ucd-util",
"unicode-normalization",
"unicode-script",
"unicode-security",
"unicode-width",
"unicode-xid",
"utf8-ranges",
"vcpkg",
"version_check",
"wasi",
"winapi",
"winapi-build",
"winapi-i686-pc-windows-gnu",
"winapi-util",
"winapi-x86_64-pc-windows-gnu",
"wincolor",
"hermit-abi",
2018-02-24 01:25:21 +01:00
];
/// Dependency checks.
///
/// `path` is path to the `src` directory, `cargo` is path to the cargo executable.
pub fn check(path: &Path, cargo: &Path, bad: &mut bool) {
let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.cargo_path(cargo)
.manifest_path(path.parent().unwrap().join("Cargo.toml"))
.features(cargo_metadata::CargoOpt::AllFeatures);
let metadata = t!(cmd.exec());
check_exceptions(&metadata, bad);
check_whitelist(&metadata, bad);
check_crate_duplicate(&metadata, bad);
}
/// Check that all licenses are in the valid list in `LICENSES`.
///
/// Packages listed in `EXCEPTIONS` are allowed for tools.
fn check_exceptions(metadata: &Metadata, bad: &mut bool) {
2020-02-24 21:54:32 +01:00
// 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.",
2020-02-24 21:54:32 +01:00
name
);
*bad = true;
}
2020-02-24 21:54:32 +01:00
// 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;
}
}
}
}
}
2020-02-24 21:54:32 +01:00
let exception_names: Vec<_> = EXCEPTIONS.iter().map(|(name, _license)| *name).collect();
// 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;
}
2020-02-24 21:54:32 +01:00
if exception_names.contains(&pkg.name.as_str()) {
continue;
}
let license = match &pkg.license {
Some(license) => license,
None => {
println!("dependency `{}` does not define a license expression", pkg.id,);
*bad = true;
continue;
}
};
if !LICENSES.contains(&license.as_str()) {
println!("invalid license `{}` in `{}`", license, pkg.id);
*bad = true;
}
2016-12-11 00:27:42 +01:00
}
2018-02-24 01:01:51 +01:00
}
2019-01-11 17:32:31 +01:00
/// Checks the dependency of `WHITELIST_CRATES` at the given path. Changes `bad` to `true` if a
/// check failed.
2018-02-24 01:01:51 +01:00
///
2019-01-11 17:32:31 +01:00
/// 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;
}
}
2019-01-11 17:32:31 +01:00
// Get the whitelist in a convenient form.
let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
2019-01-11 17:32:31 +01:00
// Check dependencies.
2018-02-27 19:02:54 +01:00
let mut visited = BTreeSet::new();
let mut unapproved = BTreeSet::new();
for &krate in WHITELIST_CRATES.iter() {
let pkg = pkg_from_name(metadata, krate);
let mut bad = check_crate_whitelist(&whitelist, metadata, &mut visited, pkg);
unapproved.append(&mut bad);
}
2018-02-24 01:01:51 +01:00
if !unapproved.is_empty() {
2018-02-24 01:01:51 +01:00
println!("Dependencies not on the whitelist:");
for dep in unapproved {
println!("* {}", dep);
2018-02-24 01:01:51 +01:00
}
*bad = true;
}
}
/// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
/// the whitelist. Returns a list of illegal dependencies.
2019-03-01 10:14:09 +01:00
fn check_crate_whitelist<'a>(
whitelist: &'a HashSet<&'static str>,
metadata: &'a Metadata,
visited: &mut BTreeSet<&'a PackageId>,
krate: &'a Package,
) -> BTreeSet<&'a PackageId> {
2019-01-11 17:32:31 +01:00
// This will contain bad deps.
2018-02-27 19:02:54 +01:00
let mut unapproved = BTreeSet::new();
2019-01-11 17:32:31 +01:00
// Check if we have already visited this crate.
if visited.contains(&krate.id) {
2018-02-27 19:02:54 +01:00
return unapproved;
}
visited.insert(&krate.id);
2019-01-11 17:32:31 +01:00
// If this path is in-tree, we don't require it to be on the whitelist.
if krate.source.is_some() {
2019-01-11 17:32:31 +01:00
// If this dependency is not on `WHITELIST`, add to bad set.
if !whitelist.contains(krate.name.as_str()) {
unapproved.insert(&krate.id);
2018-02-28 23:28:30 +01:00
}
}
// Do a DFS in the crate graph.
let to_check = deps_of(metadata, &krate.id);
for dep in to_check {
let mut bad = check_crate_whitelist(whitelist, metadata, visited, dep);
2018-02-28 23:28:30 +01:00
unapproved.append(&mut bad);
}
unapproved
}
/// Prevents multiple versions of some expensive crates.
fn check_crate_duplicate(metadata: &Metadata, bad: &mut bool) {
const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
2019-01-11 17:32:31 +01:00
// These two crates take quite a long time to build, so don't allow two versions of them
// to accidentally sneak into our dependency graph, in order to ensure we keep our CI times
// under control.
"cargo",
"rustc-ap-syntax",
];
for &name in FORBIDDEN_TO_HAVE_DUPLICATES {
let matches: Vec<_> = metadata.packages.iter().filter(|pkg| pkg.name == name).collect();
match matches.len() {
0 => {
println!(
"crate `{}` is missing, update `check_crate_duplicate` \
if it is no longer used",
name
);
*bad = true;
}
1 => {}
_ => {
println!(
"crate `{}` is duplicated in `Cargo.lock`, \
it is too expensive to build multiple times, \
so make sure only one version appears across all dependencies",
name
);
for pkg in matches {
println!(" * {}", pkg.id);
}
*bad = true;
}
}
}
}
/// 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()
.nodes
.iter()
.find(|n| &n.id == pkg_id)
.unwrap_or_else(|| panic!("could not find `{}` in resolve", pkg_id));
node.deps
.iter()
.map(|dep| {
metadata.packages.iter().find(|pkg| pkg.id == dep.pkg).unwrap_or_else(|| {
panic!("could not find dep `{}` for pkg `{}` in resolve", dep.pkg, pkg_id)
})
})
.collect()
}
/// Finds a package with the given name.
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 `{}` in package list", name));
assert!(i.next().is_none(), "more than one package found for `{}`", name);
result
}