1 // Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Check license of third-party deps by inspecting src/vendor
13 use std::collections::{BTreeSet, HashSet, HashMap};
17 use std::process::Command;
21 const LICENSES: &[&str] = &[
32 /// These are exceptions to Rust's permissive licensing policy, and
33 /// should be considered bugs. Exceptions are only allowed in Rust
34 /// tooling. It is _crucial_ that no exception crates be dependencies
35 /// of the Rust runtime (std / test).
36 const EXCEPTIONS: &[&str] = &[
37 "mdbook", // MPL2, mdbook
38 "openssl", // BSD+advertising clause, cargo, mdbook
39 "pest", // MPL2, mdbook via handlebars
40 "thread-id", // Apache-2.0, mdbook
41 "toml-query", // MPL-2.0, mdbook
42 "is-match", // MPL-2.0, mdbook
43 "cssparser", // MPL-2.0, rustdoc
44 "smallvec", // MPL-2.0, rustdoc
45 "fuchsia-zircon-sys", // BSD-3-Clause, rustdoc, rustc, cargo
46 "fuchsia-zircon", // BSD-3-Clause, rustdoc, rustc, cargo (jobserver & tempdir)
47 "cssparser-macros", // MPL-2.0, rustdoc
48 "selectors", // MPL-2.0, rustdoc
49 "clippy_lints", // MPL-2.0, rls
50 "colored", // MPL-2.0, rustfmt
51 "ordslice", // Apache-2.0, rls
52 "cloudabi", // BSD-2-Clause, (rls -> crossbeam-channel 0.2 -> rand 0.5)
53 "ryu", // Apache-2.0, rls/cargo/... (b/c of serde)
54 "bytesize", // Apache-2.0, cargo
57 /// Which crates to check against the whitelist?
58 const WHITELIST_CRATES: &[CrateVersion] = &[
59 CrateVersion("rustc", "0.0.0"),
60 CrateVersion("rustc_codegen_llvm", "0.0.0"),
63 /// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
64 const WHITELIST: &[Crate] = &[
65 Crate("aho-corasick"),
69 Crate("backtrace-sys"),
74 Crate("chalk-engine"),
75 Crate("chalk-macros"),
78 Crate("crossbeam-deque"),
79 Crate("crossbeam-epoch"),
80 Crate("crossbeam-utils"),
87 Crate("fuchsia-zircon"),
88 Crate("fuchsia-zircon-sys"),
92 Crate("kernel32-sys"),
97 Crate("log_settings"),
105 Crate("parking_lot"),
106 Crate("parking_lot_core"),
108 Crate("polonius-engine"),
109 Crate("quick-error"),
112 Crate("redox_syscall"),
113 Crate("redox_termios"),
115 Crate("regex-syntax"),
116 Crate("remove_dir_all"),
117 Crate("rustc-demangle"),
119 Crate("rustc-rayon"),
120 Crate("rustc-rayon-core"),
124 Crate("stable_deref_trait"),
129 Crate("thread_local"),
131 Crate("unicode-width"),
132 Crate("unreachable"),
133 Crate("utf8-ranges"),
134 Crate("version_check"),
137 Crate("winapi-build"),
138 Crate("winapi-i686-pc-windows-gnu"),
139 Crate("winapi-util"),
140 Crate("winapi-x86_64-pc-windows-gnu"),
144 // Some types for Serde to deserialize the output of `cargo metadata` to...
146 #[derive(Deserialize)]
151 #[derive(Deserialize)]
153 nodes: Vec<ResolveNode>,
156 #[derive(Deserialize)]
159 dependencies: Vec<String>,
162 /// A unique identifier for a crate
163 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
164 struct Crate<'a>(&'a str); // (name,)
166 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
167 struct CrateVersion<'a>(&'a str, &'a str); // (name, version)
170 pub fn id_str(&self) -> String {
171 format!("{} ", self.0)
175 impl<'a> CrateVersion<'a> {
176 /// Returns the struct and whether or not the dep is in-tree
177 pub fn from_str(s: &'a str) -> (Self, bool) {
178 let mut parts = s.split(' ');
179 let name = parts.next().unwrap();
180 let version = parts.next().unwrap();
181 let path = parts.next().unwrap();
183 let is_path_dep = path.starts_with("(path+");
185 (CrateVersion(name, version), is_path_dep)
188 pub fn id_str(&self) -> String {
189 format!("{} {}", self.0, self.1)
193 impl<'a> From<CrateVersion<'a>> for Crate<'a> {
194 fn from(cv: CrateVersion<'a>) -> Crate<'a> {
199 /// Checks the dependency at the given path. Changes `bad` to `true` if a check failed.
201 /// Specifically, this checks that the license is correct.
202 pub fn check(path: &Path, bad: &mut bool) {
204 let path = path.join("vendor");
205 assert!(path.exists(), "vendor directory missing");
206 let mut saw_dir = false;
207 for dir in t!(path.read_dir()) {
211 // skip our exceptions
212 let is_exception = EXCEPTIONS.iter().any(|exception| {
216 .contains(&format!("src/vendor/{}", exception))
222 let toml = dir.path().join("Cargo.toml");
223 *bad = *bad || !check_license(&toml);
225 assert!(saw_dir, "no vendored source");
228 /// Checks the dependency of WHITELIST_CRATES at the given path. Changes `bad` to `true` if a check
231 /// Specifically, this checks that the dependencies are on the WHITELIST.
232 pub fn check_whitelist(path: &Path, cargo: &Path, bad: &mut bool) {
233 // Get dependencies from cargo metadata
234 let resolve = get_deps(path, cargo);
236 // Get the whitelist into a convenient form
237 let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
239 // Check dependencies
240 let mut visited = BTreeSet::new();
241 let mut unapproved = BTreeSet::new();
242 for &krate in WHITELIST_CRATES.iter() {
243 let mut bad = check_crate_whitelist(&whitelist, &resolve, &mut visited, krate, false);
244 unapproved.append(&mut bad);
247 if !unapproved.is_empty() {
248 println!("Dependencies not on the whitelist:");
249 for dep in unapproved {
250 println!("* {}", dep.id_str());
255 check_crate_duplicate(&resolve, bad);
258 fn check_license(path: &Path) -> bool {
260 panic!("{} does not exist", path.display());
262 let mut contents = String::new();
263 t!(t!(File::open(path)).read_to_string(&mut contents));
265 let mut found_license = false;
266 for line in contents.lines() {
267 if !line.starts_with("license") {
270 let license = extract_license(line);
271 if !LICENSES.contains(&&*license) {
272 println!("invalid license {} in {}", license, path.display());
275 found_license = true;
279 println!("no license in {}", path.display());
286 fn extract_license(line: &str) -> String {
287 let first_quote = line.find('"');
288 let last_quote = line.rfind('"');
289 if let (Some(f), Some(l)) = (first_quote, last_quote) {
290 let license = &line[f + 1..l];
293 "bad-license-parse".into()
297 /// Get the dependencies of the crate at the given path using `cargo metadata`.
298 fn get_deps(path: &Path, cargo: &Path) -> Resolve {
299 // Run `cargo metadata` to get the set of dependencies
300 let output = Command::new(cargo)
302 .arg("--format-version")
304 .arg("--manifest-path")
305 .arg(path.join("Cargo.toml"))
307 .expect("Unable to run `cargo metadata`")
309 let output = String::from_utf8_lossy(&output);
310 let output: Output = serde_json::from_str(&output).unwrap();
315 /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
316 /// the whitelist. Returns a list of illegal dependencies.
317 fn check_crate_whitelist<'a, 'b>(
318 whitelist: &'a HashSet<Crate>,
319 resolve: &'a Resolve,
320 visited: &'b mut BTreeSet<CrateVersion<'a>>,
321 krate: CrateVersion<'a>,
322 must_be_on_whitelist: bool,
323 ) -> BTreeSet<Crate<'a>> {
324 // Will contain bad deps
325 let mut unapproved = BTreeSet::new();
327 // Check if we have already visited this crate
328 if visited.contains(&krate) {
332 visited.insert(krate);
334 // If this path is in-tree, we don't require it to be on the whitelist
335 if must_be_on_whitelist {
336 // If this dependency is not on the WHITELIST, add to bad set
337 if !whitelist.contains(&krate.into()) {
338 unapproved.insert(krate.into());
342 // Do a DFS in the crate graph (it's a DAG, so we know we have no cycles!)
343 let to_check = resolve
346 .find(|n| n.id.starts_with(&krate.id_str()))
347 .expect("crate does not exist");
349 for dep in to_check.dependencies.iter() {
350 let (krate, is_path_dep) = CrateVersion::from_str(dep);
352 let mut bad = check_crate_whitelist(whitelist, resolve, visited, krate, !is_path_dep);
353 unapproved.append(&mut bad);
359 fn check_crate_duplicate(resolve: &Resolve, bad: &mut bool) {
360 const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
361 // These two crates take quite a long time to build, let's not let two
362 // versions of them accidentally sneak into our dependency graph to
363 // ensure we keep our CI times under control
364 // "cargo", // FIXME(#53005)
367 let mut name_to_id: HashMap<_, Vec<_>> = HashMap::new();
368 for node in resolve.nodes.iter() {
369 name_to_id.entry(node.id.split_whitespace().next().unwrap())
374 for name in FORBIDDEN_TO_HAVE_DUPLICATES {
375 if name_to_id[name].len() <= 1 {
378 println!("crate `{}` is duplicated in `Cargo.lock`", name);
379 for id in name_to_id[name].iter() {
380 println!(" * {}", id);