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

380 lines
11 KiB
Rust
Raw Normal View History

2016-12-11 00:27:42 +01:00
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Check license of third-party deps by inspecting src/vendor
use std::collections::{BTreeSet, HashSet, HashMap};
2016-12-11 00:27:42 +01:00
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::process::Command;
use serde_json;
2016-12-11 00:27:42 +01:00
static LICENSES: &'static [&'static 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",
"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
/// of the Rust runtime (std / test).
static EXCEPTIONS: &'static [&'static str] = &[
2018-02-23 01:59:04 +01:00
"mdbook", // MPL2, mdbook
"openssl", // BSD+advertising clause, cargo, mdbook
"pest", // MPL2, mdbook via handlebars
"thread-id", // Apache-2.0, mdbook
"toml-query", // MPL-2.0, mdbook
"is-match", // MPL-2.0, mdbook
"cssparser", // MPL-2.0, rustdoc
"smallvec", // MPL-2.0, rustdoc
"fuchsia-zircon-sys", // BSD-3-Clause, rustdoc, rustc, cargo
2018-02-23 01:59:04 +01:00
"fuchsia-zircon", // BSD-3-Clause, rustdoc, rustc, cargo (jobserver & tempdir)
"cssparser-macros", // MPL-2.0, rustdoc
"selectors", // MPL-2.0, rustdoc
2018-04-26 05:10:11 +02:00
"clippy_lints", // MPL-2.0, rls
"colored", // MPL-2.0, rustfmt
2018-07-06 02:34:00 +02:00
"ordslice", // Apache-2.0, rls
2018-07-29 05:54:27 +02:00
"cloudabi", // BSD-2-Clause, (rls -> crossbeam-channel 0.2 -> rand 0.5)
"ryu", // Apache-2.0, rls/cargo/... (b/c of serde)
2016-12-11 00:27:42 +01:00
];
/// Which crates to check against the whitelist?
static WHITELIST_CRATES: &'static [CrateVersion] = &[
CrateVersion("rustc", "0.0.0"),
2018-05-08 15:10:16 +02:00
CrateVersion("rustc_codegen_llvm", "0.0.0"),
];
/// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
static WHITELIST: &'static [Crate] = &[
Crate("aho-corasick"),
2018-04-26 00:49:52 +02:00
Crate("arrayvec"),
Crate("atty"),
2018-03-02 04:22:06 +01:00
Crate("backtrace"),
Crate("backtrace-sys"),
Crate("bitflags"),
Crate("byteorder"),
Crate("cc"),
Crate("cfg-if"),
Crate("chalk-engine"),
Crate("chalk-macros"),
Crate("cloudabi"),
2018-03-02 04:22:06 +01:00
Crate("cmake"),
2018-04-26 00:49:52 +02:00
Crate("crossbeam-deque"),
Crate("crossbeam-epoch"),
Crate("crossbeam-utils"),
2018-05-28 18:54:11 +02:00
Crate("datafrog"),
2018-04-26 00:49:52 +02:00
Crate("either"),
2018-03-02 19:15:02 +01:00
Crate("ena"),
Crate("env_logger"),
2018-03-02 04:22:06 +01:00
Crate("filetime"),
Crate("flate2"),
Crate("fuchsia-zircon"),
Crate("fuchsia-zircon-sys"),
2018-04-08 13:44:29 +02:00
Crate("getopts"),
Crate("humantime"),
2018-03-02 04:22:06 +01:00
Crate("jobserver"),
Crate("kernel32-sys"),
Crate("lazy_static"),
Crate("libc"),
Crate("log"),
Crate("log_settings"),
Crate("memchr"),
2018-04-26 00:49:52 +02:00
Crate("memoffset"),
2018-03-02 04:22:06 +01:00
Crate("miniz-sys"),
2018-04-26 00:49:52 +02:00
Crate("nodrop"),
2018-03-02 04:22:06 +01:00
Crate("num_cpus"),
Crate("owning_ref"),
Crate("parking_lot"),
Crate("parking_lot_core"),
2018-05-24 23:52:01 +02:00
Crate("polonius-engine"),
Crate("pkg-config"),
Crate("quick-error"),
2018-03-02 04:22:06 +01:00
Crate("rand"),
Crate("rand_core"),
2018-03-02 04:22:06 +01:00
Crate("redox_syscall"),
Crate("redox_termios"),
Crate("regex"),
Crate("regex-syntax"),
Crate("remove_dir_all"),
2018-03-02 04:22:06 +01:00
Crate("rustc-demangle"),
Crate("rustc-hash"),
2018-04-26 00:49:52 +02:00
Crate("rustc-rayon"),
Crate("rustc-rayon-core"),
Crate("scoped-tls"),
2018-04-26 00:49:52 +02:00
Crate("scopeguard"),
2018-03-02 04:22:06 +01:00
Crate("smallvec"),
Crate("stable_deref_trait"),
2018-05-13 15:26:55 +02:00
Crate("tempfile"),
Crate("termcolor"),
Crate("terminon"),
Crate("termion"),
Crate("thread_local"),
Crate("ucd-util"),
2018-03-02 04:22:06 +01:00
Crate("unicode-width"),
Crate("unreachable"),
Crate("utf8-ranges"),
Crate("version_check"),
Crate("void"),
2018-03-02 04:22:06 +01:00
Crate("winapi"),
Crate("winapi-build"),
Crate("winapi-i686-pc-windows-gnu"),
Crate("winapi-x86_64-pc-windows-gnu"),
Crate("wincolor"),
2018-02-24 01:25:21 +01:00
];
2018-02-23 02:57:55 +01:00
// Some types for Serde to deserialize the output of `cargo metadata` to...
#[derive(Deserialize)]
struct Output {
resolve: Resolve,
2018-02-24 01:01:51 +01:00
}
#[derive(Deserialize)]
struct Resolve {
nodes: Vec<ResolveNode>,
}
#[derive(Deserialize)]
struct ResolveNode {
id: String,
dependencies: Vec<String>,
}
/// A unique identifier for a crate
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
2018-02-27 19:02:54 +01:00
struct Crate<'a>(&'a str); // (name,)
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
struct CrateVersion<'a>(&'a str, &'a str); // (name, version)
impl<'a> Crate<'a> {
pub fn id_str(&self) -> String {
format!("{} ", self.0)
}
}
impl<'a> CrateVersion<'a> {
2018-02-28 21:12:15 +01:00
/// Returns the struct and whether or not the dep is in-tree
pub fn from_str(s: &'a str) -> (Self, bool) {
let mut parts = s.split(' ');
let name = parts.next().unwrap();
let version = parts.next().unwrap();
2018-02-28 21:12:15 +01:00
let path = parts.next().unwrap();
2018-02-28 21:12:15 +01:00
let is_path_dep = path.starts_with("(path+");
(CrateVersion(name, version), is_path_dep)
}
pub fn id_str(&self) -> String {
format!("{} {}", self.0, self.1)
}
}
impl<'a> From<CrateVersion<'a>> for Crate<'a> {
fn from(cv: CrateVersion<'a>) -> Crate<'a> {
Crate(cv.0)
}
}
/// Checks the dependency at the given path. Changes `bad` to `true` if a check failed.
///
2018-02-24 01:01:51 +01:00
/// Specifically, this checks that the license is correct.
2016-12-11 00:27:42 +01:00
pub fn check(path: &Path, bad: &mut bool) {
// Check licences
2016-12-11 00:27:42 +01:00
let path = path.join("vendor");
assert!(path.exists(), "vendor directory missing");
let mut saw_dir = false;
for dir in t!(path.read_dir()) {
2016-12-11 00:27:42 +01:00
saw_dir = true;
let dir = t!(dir);
// skip our exceptions
if EXCEPTIONS.iter().any(|exception| {
dir.path()
.to_str()
.unwrap()
2018-02-23 01:59:04 +01:00
.contains(&format!("src/vendor/{}", exception))
}) {
continue;
}
2016-12-11 00:27:42 +01:00
let toml = dir.path().join("Cargo.toml");
*bad = *bad || !check_license(&toml);
2016-12-11 00:27:42 +01:00
}
assert!(saw_dir, "no vendored source");
2018-02-24 01:01:51 +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
///
/// Specifically, this checks that the dependencies are on the WHITELIST.
2018-02-26 18:05:43 +01:00
pub fn check_whitelist(path: &Path, cargo: &Path, bad: &mut bool) {
// Get dependencies from cargo metadata
let resolve = get_deps(path, cargo);
2018-02-24 01:01:51 +01:00
// Get the whitelist into a convenient form
let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
// 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() {
2018-02-28 23:28:30 +01:00
let mut bad = check_crate_whitelist(&whitelist, &resolve, &mut visited, krate, false);
unapproved.append(&mut bad);
}
2018-02-24 01:01:51 +01:00
if unapproved.len() > 0 {
println!("Dependencies not on the whitelist:");
for dep in unapproved {
2018-02-27 19:02:54 +01:00
println!("* {}", dep.id_str());
2018-02-24 01:01:51 +01:00
}
*bad = true;
}
check_crate_duplicate(&resolve, bad);
2016-12-11 00:27:42 +01:00
}
fn check_license(path: &Path) -> bool {
if !path.exists() {
panic!("{} does not exist", path.display());
}
let mut contents = String::new();
t!(t!(File::open(path)).read_to_string(&mut contents));
let mut found_license = false;
for line in contents.lines() {
if !line.starts_with("license") {
continue;
}
let license = extract_license(line);
if !LICENSES.contains(&&*license) {
println!("invalid license {} in {}", license, path.display());
return false;
}
found_license = true;
break;
}
if !found_license {
println!("no license in {}", path.display());
return false;
}
true
}
fn extract_license(line: &str) -> String {
let first_quote = line.find('"');
let last_quote = line.rfind('"');
if let (Some(f), Some(l)) = (first_quote, last_quote) {
2018-02-23 01:59:04 +01:00
let license = &line[f + 1..l];
2016-12-11 00:27:42 +01:00
license.into()
} else {
"bad-license-parse".into()
}
}
2018-02-23 02:57:55 +01:00
/// Get the dependencies of the crate at the given path using `cargo metadata`.
fn get_deps(path: &Path, cargo: &Path) -> Resolve {
// Run `cargo metadata` to get the set of dependencies
2018-02-26 18:05:43 +01:00
let output = Command::new(cargo)
.arg("metadata")
.arg("--format-version")
.arg("1")
.arg("--manifest-path")
.arg(path.join("Cargo.toml"))
.output()
.expect("Unable to run `cargo metadata`")
.stdout;
let output = String::from_utf8_lossy(&output);
let output: Output = serde_json::from_str(&output).unwrap();
output.resolve
}
/// 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.
2018-02-27 19:02:54 +01:00
fn check_crate_whitelist<'a, 'b>(
whitelist: &'a HashSet<Crate>,
resolve: &'a Resolve,
visited: &'b mut BTreeSet<CrateVersion<'a>>,
krate: CrateVersion<'a>,
2018-02-28 23:28:30 +01:00
must_be_on_whitelist: bool,
2018-02-27 19:02:54 +01:00
) -> BTreeSet<Crate<'a>> {
// Will contain bad deps
2018-02-27 19:02:54 +01:00
let mut unapproved = BTreeSet::new();
// Check if we have already visited this crate
if visited.contains(&krate) {
return unapproved;
}
visited.insert(krate);
2018-02-28 23:28:30 +01:00
// If this path is in-tree, we don't require it to be on the whitelist
if must_be_on_whitelist {
// If this dependency is not on the WHITELIST, add to bad set
if !whitelist.contains(&krate.into()) {
unapproved.insert(krate.into());
}
}
// Do a DFS in the crate graph (it's a DAG, so we know we have no cycles!)
let to_check = resolve
.nodes
.iter()
.find(|n| n.id.starts_with(&krate.id_str()))
.expect("crate does not exist");
for dep in to_check.dependencies.iter() {
2018-02-28 21:12:15 +01:00
let (krate, is_path_dep) = CrateVersion::from_str(dep);
2018-02-28 23:28:30 +01:00
let mut bad = check_crate_whitelist(whitelist, resolve, visited, krate, !is_path_dep);
unapproved.append(&mut bad);
}
unapproved
}
fn check_crate_duplicate(resolve: &Resolve, bad: &mut bool) {
const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
// These two crates take quite a long time to build, let's not let two
// versions of them accidentally sneak into our dependency graph to
// ensure we keep our CI times under control
// "cargo", // FIXME(#53005)
"rustc-ap-syntax",
];
let mut name_to_id: HashMap<_, Vec<_>> = HashMap::new();
for node in resolve.nodes.iter() {
name_to_id.entry(node.id.split_whitespace().next().unwrap())
.or_default()
.push(&node.id);
}
for name in FORBIDDEN_TO_HAVE_DUPLICATES {
if name_to_id[name].len() <= 1 {
continue
}
println!("crate `{}` is duplicated in `Cargo.lock`", name);
for id in name_to_id[name].iter() {
println!(" * {}", id);
}
*bad = true;
}
}