]> git.proxmox.com Git - rustc.git/blobdiff - src/tools/tidy/src/deps.rs
New upstream version 1.27.1+dfsg1
[rustc.git] / src / tools / tidy / src / deps.rs
index f572ad9cd020407de8ae154e879aca54f3d657fe..9a87fcb00d526f4cc683cfbbf98b2bbc9446d0b7 100644 (file)
 
 //! Check license of third-party deps by inspecting src/vendor
 
+use std::collections::{BTreeSet, HashSet};
 use std::fs::File;
 use std::io::Read;
 use std::path::Path;
+use std::process::Command;
+
+use serde_json;
 
 static LICENSES: &'static [&'static str] = &[
     "MIT/Apache-2.0",
@@ -24,43 +28,204 @@ static LICENSES: &'static [&'static str] = &[
     "Unlicense/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).
+/// 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] = &[
-    "mdbook", // MPL2, mdbook
-    "openssl", // BSD+advertising clause, cargo, mdbook
-    "pest", // MPL2, mdbook via handlebars
-    "thread-id", // Apache-2.0, mdbook
+    "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
+    "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
+];
+
+/// Which crates to check against the whitelist?
+static WHITELIST_CRATES: &'static [CrateVersion] = &[
+    CrateVersion("rustc", "0.0.0"),
+    CrateVersion("rustc_trans", "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"),
+    Crate("ar"),
+    Crate("atty"),
+    Crate("backtrace"),
+    Crate("backtrace-sys"),
+    Crate("bitflags"),
+    Crate("byteorder"),
+    Crate("cc"),
+    Crate("cfg-if"),
+    Crate("cmake"),
+    Crate("ena"),
+    Crate("env_logger"),
+    Crate("filetime"),
+    Crate("flate2"),
+    Crate("fuchsia-zircon"),
+    Crate("fuchsia-zircon-sys"),
+    Crate("getopts"),
+    Crate("humantime"),
+    Crate("jobserver"),
+    Crate("kernel32-sys"),
+    Crate("lazy_static"),
+    Crate("libc"),
+    Crate("log"),
+    Crate("log_settings"),
+    Crate("memchr"),
+    Crate("miniz-sys"),
+    Crate("num_cpus"),
+    Crate("owning_ref"),
+    Crate("parking_lot"),
+    Crate("parking_lot_core"),
+    Crate("quick-error"),
+    Crate("rand"),
+    Crate("redox_syscall"),
+    Crate("redox_termios"),
+    Crate("regex"),
+    Crate("regex-syntax"),
+    Crate("remove_dir_all"),
+    Crate("rustc-demangle"),
+    Crate("scoped-tls"),
+    Crate("smallvec"),
+    Crate("stable_deref_trait"),
+    Crate("tempdir"),
+    Crate("termcolor"),
+    Crate("terminon"),
+    Crate("termion"),
+    Crate("thread_local"),
+    Crate("ucd-util"),
+    Crate("unicode-width"),
+    Crate("unreachable"),
+    Crate("utf8-ranges"),
+    Crate("void"),
+    Crate("winapi"),
+    Crate("winapi-build"),
+    Crate("winapi-i686-pc-windows-gnu"),
+    Crate("winapi-x86_64-pc-windows-gnu"),
+    Crate("wincolor"),
+];
+
+// Some types for Serde to deserialize the output of `cargo metadata` to...
+
+#[derive(Deserialize)]
+struct Output {
+    resolve: Resolve,
+}
+
+#[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)]
+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> {
+    /// 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();
+        let path = parts.next().unwrap();
+
+        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.
+///
+/// Specifically, this checks that the license is correct.
 pub fn check(path: &Path, bad: &mut bool) {
+    // Check licences
     let path = path.join("vendor");
     assert!(path.exists(), "vendor directory missing");
     let mut saw_dir = false;
-    'next_path: for dir in t!(path.read_dir()) {
+    for dir in t!(path.read_dir()) {
         saw_dir = true;
         let dir = t!(dir);
 
         // skip our exceptions
-        for exception in EXCEPTIONS {
-            if dir.path()
+        if EXCEPTIONS.iter().any(|exception| {
+            dir.path()
                 .to_str()
                 .unwrap()
-                .contains(&format!("src/vendor/{}", exception)) {
-                continue 'next_path;
-            }
+                .contains(&format!("src/vendor/{}", exception))
+        }) {
+            continue;
         }
 
         let toml = dir.path().join("Cargo.toml");
-        if !check_license(&toml) {
-            *bad = true;
-        }
+        *bad = *bad || !check_license(&toml);
     }
     assert!(saw_dir, "no vendored source");
 }
 
+/// Checks the dependency of WHITELIST_CRATES at the given path. Changes `bad` to `true` if a check
+/// failed.
+///
+/// Specifically, this checks that the dependencies are on the WHITELIST.
+pub fn check_whitelist(path: &Path, cargo: &Path, bad: &mut bool) {
+    // Get dependencies from cargo metadata
+    let resolve = get_deps(path, cargo);
+
+    // Get the whitelist into a convenient form
+    let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
+
+    // Check dependencies
+    let mut visited = BTreeSet::new();
+    let mut unapproved = BTreeSet::new();
+    for &krate in WHITELIST_CRATES.iter() {
+        let mut bad = check_crate_whitelist(&whitelist, &resolve, &mut visited, krate, false);
+        unapproved.append(&mut bad);
+    }
+
+    if unapproved.len() > 0 {
+        println!("Dependencies not on the whitelist:");
+        for dep in unapproved {
+            println!("* {}", dep.id_str());
+        }
+        *bad = true;
+    }
+}
+
 fn check_license(path: &Path) -> bool {
     if !path.exists() {
         panic!("{} does not exist", path.display());
@@ -93,9 +258,71 @@ 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) {
-        let license = &line[f + 1 .. l];
+        let license = &line[f + 1..l];
         license.into()
     } else {
         "bad-license-parse".into()
     }
 }
+
+/// 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
+    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.
+fn check_crate_whitelist<'a, 'b>(
+    whitelist: &'a HashSet<Crate>,
+    resolve: &'a Resolve,
+    visited: &'b mut BTreeSet<CrateVersion<'a>>,
+    krate: CrateVersion<'a>,
+    must_be_on_whitelist: bool,
+) -> BTreeSet<Crate<'a>> {
+    // Will contain bad deps
+    let mut unapproved = BTreeSet::new();
+
+    // Check if we have already visited this crate
+    if visited.contains(&krate) {
+        return unapproved;
+    }
+
+    visited.insert(krate);
+
+    // 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() {
+        let (krate, is_path_dep) = CrateVersion::from_str(dep);
+
+        let mut bad = check_crate_whitelist(whitelist, resolve, visited, krate, !is_path_dep);
+        unapproved.append(&mut bad);
+    }
+
+    unapproved
+}