1 //! Checks the licenses of third-party dependencies by inspecting vendors.
3 use std::collections::{BTreeSet, HashSet, HashMap};
6 use std::process::Command;
8 use serde::Deserialize;
11 const LICENSES: &[&str] = &[
23 /// These are exceptions to Rust's permissive licensing policy, and
24 /// should be considered bugs. Exceptions are only allowed in Rust
25 /// tooling. It is _crucial_ that no exception crates be dependencies
26 /// of the Rust runtime (std/test).
27 const EXCEPTIONS: &[&str] = &[
28 "mdbook", // MPL2, mdbook
29 "openssl", // BSD+advertising clause, cargo, mdbook
30 "pest", // MPL2, mdbook via handlebars
31 "arrayref", // BSD-2-Clause, mdbook via handlebars via pest
32 "thread-id", // Apache-2.0, mdbook
33 "toml-query", // MPL-2.0, mdbook
34 "is-match", // MPL-2.0, mdbook
35 "cssparser", // MPL-2.0, rustdoc
36 "smallvec", // MPL-2.0, rustdoc
37 "rdrand", // ISC, mdbook, rustfmt
38 "fuchsia-cprng", // BSD-3-Clause, mdbook, rustfmt
39 "fuchsia-zircon-sys", // BSD-3-Clause, rustdoc, rustc, cargo
40 "fuchsia-zircon", // BSD-3-Clause, rustdoc, rustc, cargo (jobserver & tempdir)
41 "cssparser-macros", // MPL-2.0, rustdoc
42 "selectors", // MPL-2.0, rustdoc
43 "clippy_lints", // MPL-2.0, rls
44 "colored", // MPL-2.0, rustfmt
45 "ordslice", // Apache-2.0, rls
46 "cloudabi", // BSD-2-Clause, (rls -> crossbeam-channel 0.2 -> rand 0.5)
47 "ryu", // Apache-2.0, rls/cargo/... (because of serde)
48 "bytesize", // Apache-2.0, cargo
49 "im-rc", // MPL-2.0+, cargo
50 "adler32", // BSD-3-Clause AND Zlib, cargo dep that isn't used
51 "fortanix-sgx-abi", // MPL-2.0+, libstd but only for `sgx` target
52 "constant_time_eq", // CC0-1.0, rustfmt
53 "utf8parse", // Apache-2.0 OR MIT, cargo via strip-ansi-escapes
54 "vte", // Apache-2.0 OR MIT, cargo via strip-ansi-escapes
55 "sized-chunks", // MPL-2.0+, cargo via im-rc
58 /// Which crates to check against the whitelist?
59 const WHITELIST_CRATES: &[CrateVersion<'_>] = &[
60 CrateVersion("rustc", "0.0.0"),
61 CrateVersion("rustc_codegen_llvm", "0.0.0"),
64 /// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
65 const WHITELIST: &[Crate<'_>] = &[
67 Crate("aho-corasick"),
68 Crate("annotate-snippets"),
74 Crate("backtrace-sys"),
80 Crate("chalk-engine"),
81 Crate("chalk-macros"),
84 Crate("compiler_builtins"),
87 Crate("crossbeam-deque"),
88 Crate("crossbeam-epoch"),
89 Crate("crossbeam-utils"),
96 Crate("fuchsia-zircon"),
97 Crate("fuchsia-zircon-sys"),
103 Crate("kernel32-sys"),
104 Crate("lazy_static"),
109 Crate("log_settings"),
115 Crate("miniz_oxide"),
116 Crate("miniz_oxide_c_api"),
120 Crate("parking_lot"),
121 Crate("parking_lot_core"),
123 Crate("polonius-engine"),
124 Crate("proc-macro2"),
125 Crate("quick-error"),
128 Crate("rand_chacha"),
133 Crate("rand_xorshift"),
134 Crate("redox_syscall"),
135 Crate("redox_termios"),
137 Crate("regex-syntax"),
138 Crate("remove_dir_all"),
139 Crate("rustc-demangle"),
141 Crate("rustc-rayon"),
142 Crate("rustc-rayon-core"),
143 Crate("rustc_version"),
147 Crate("semver-parser"),
149 Crate("serde_derive"),
151 Crate("stable_deref_trait"),
153 Crate("synstructure"),
158 Crate("thread_local"),
160 Crate("unicode-width"),
161 Crate("unicode-xid"),
162 Crate("unreachable"),
163 Crate("utf8-ranges"),
165 Crate("version_check"),
168 Crate("winapi-build"),
169 Crate("winapi-i686-pc-windows-gnu"),
170 Crate("winapi-util"),
171 Crate("winapi-x86_64-pc-windows-gnu"),
175 // Some types for Serde to deserialize the output of `cargo metadata` to.
177 #[derive(Deserialize)]
182 #[derive(Deserialize)]
184 nodes: Vec<ResolveNode>,
187 #[derive(Deserialize)]
190 dependencies: Vec<String>,
193 /// A unique identifier for a crate.
194 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
195 struct Crate<'a>(&'a str); // (name)
197 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
198 struct CrateVersion<'a>(&'a str, &'a str); // (name, version)
201 pub fn id_str(&self) -> String {
202 format!("{} ", self.0)
206 impl<'a> CrateVersion<'a> {
207 /// Returns the struct and whether or not the dependency is in-tree.
208 pub fn from_str(s: &'a str) -> (Self, bool) {
209 let mut parts = s.split(' ');
210 let name = parts.next().unwrap();
211 let version = parts.next().unwrap();
212 let path = parts.next().unwrap();
214 let is_path_dep = path.starts_with("(path+");
216 (CrateVersion(name, version), is_path_dep)
219 pub fn id_str(&self) -> String {
220 format!("{} {}", self.0, self.1)
224 impl<'a> From<CrateVersion<'a>> for Crate<'a> {
225 fn from(cv: CrateVersion<'a>) -> Crate<'a> {
230 /// Checks the dependency at the given path. Changes `bad` to `true` if a check failed.
232 /// Specifically, this checks that the license is correct.
233 pub fn check(path: &Path, bad: &mut bool) {
235 let path = path.join("../vendor");
236 assert!(path.exists(), "vendor directory missing");
237 let mut saw_dir = false;
238 for dir in t!(path.read_dir()) {
242 // Skip our exceptions.
243 let is_exception = EXCEPTIONS.iter().any(|exception| {
247 .contains(&format!("vendor/{}", exception))
253 let toml = dir.path().join("Cargo.toml");
254 *bad = !check_license(&toml) || *bad;
256 assert!(saw_dir, "no vendored source");
259 /// Checks the dependency of `WHITELIST_CRATES` at the given path. Changes `bad` to `true` if a
262 /// Specifically, this checks that the dependencies are on the `WHITELIST`.
263 pub fn check_whitelist(path: &Path, cargo: &Path, bad: &mut bool) {
264 // Get dependencies from Cargo metadata.
265 let resolve = get_deps(path, cargo);
267 // Get the whitelist in a convenient form.
268 let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
270 // Check dependencies.
271 let mut visited = BTreeSet::new();
272 let mut unapproved = BTreeSet::new();
273 for &krate in WHITELIST_CRATES.iter() {
274 let mut bad = check_crate_whitelist(&whitelist, &resolve, &mut visited, krate, false);
275 unapproved.append(&mut bad);
278 if !unapproved.is_empty() {
279 println!("Dependencies not on the whitelist:");
280 for dep in unapproved {
281 println!("* {}", dep.id_str());
286 check_crate_duplicate(&resolve, bad);
289 fn check_license(path: &Path) -> bool {
291 panic!("{} does not exist", path.display());
293 let contents = t!(fs::read_to_string(&path));
295 let mut found_license = false;
296 for line in contents.lines() {
297 if !line.starts_with("license") {
300 let license = extract_license(line);
301 if !LICENSES.contains(&&*license) {
302 println!("invalid license {} in {}", license, path.display());
305 found_license = true;
309 println!("no license in {}", path.display());
316 fn extract_license(line: &str) -> String {
317 let first_quote = line.find('"');
318 let last_quote = line.rfind('"');
319 if let (Some(f), Some(l)) = (first_quote, last_quote) {
320 let license = &line[f + 1..l];
323 "bad-license-parse".into()
327 /// Gets the dependencies of the crate at the given path using `cargo metadata`.
328 fn get_deps(path: &Path, cargo: &Path) -> Resolve {
329 // Run `cargo metadata` to get the set of dependencies.
330 let output = Command::new(cargo)
332 .arg("--format-version")
334 .arg("--manifest-path")
335 .arg(path.join("../Cargo.toml"))
337 .expect("Unable to run `cargo metadata`")
339 let output = String::from_utf8_lossy(&output);
340 let output: Output = serde_json::from_str(&output).unwrap();
345 /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
346 /// the whitelist. Returns a list of illegal dependencies.
347 fn check_crate_whitelist<'a>(
348 whitelist: &'a HashSet<Crate<'_>>,
349 resolve: &'a Resolve,
350 visited: &mut BTreeSet<CrateVersion<'a>>,
351 krate: CrateVersion<'a>,
352 must_be_on_whitelist: bool,
353 ) -> BTreeSet<Crate<'a>> {
354 // This will contain bad deps.
355 let mut unapproved = BTreeSet::new();
357 // Check if we have already visited this crate.
358 if visited.contains(&krate) {
362 visited.insert(krate);
364 // If this path is in-tree, we don't require it to be on the whitelist.
365 if must_be_on_whitelist {
366 // If this dependency is not on `WHITELIST`, add to bad set.
367 if !whitelist.contains(&krate.into()) {
368 unapproved.insert(krate.into());
372 // Do a DFS in the crate graph (it's a DAG, so we know we have no cycles!).
373 let to_check = resolve
376 .find(|n| n.id.starts_with(&krate.id_str()))
377 .expect("crate does not exist");
379 for dep in to_check.dependencies.iter() {
380 let (krate, is_path_dep) = CrateVersion::from_str(dep);
382 let mut bad = check_crate_whitelist(whitelist, resolve, visited, krate, !is_path_dep);
383 unapproved.append(&mut bad);
389 fn check_crate_duplicate(resolve: &Resolve, bad: &mut bool) {
390 const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
391 // These two crates take quite a long time to build, so don't allow two versions of them
392 // to accidentally sneak into our dependency graph, in order to ensure we keep our CI times
398 let mut name_to_id: HashMap<_, Vec<_>> = HashMap::new();
399 for node in resolve.nodes.iter() {
400 name_to_id.entry(node.id.split_whitespace().next().unwrap())
405 for name in FORBIDDEN_TO_HAVE_DUPLICATES {
406 if name_to_id[name].len() <= 1 {
409 println!("crate `{}` is duplicated in `Cargo.lock`", name);
410 for id in name_to_id[name].iter() {
411 println!(" * {}", id);