1 //! Checks the licenses of third-party dependencies.
3 use cargo_metadata::{Metadata, Package, PackageId};
4 use std::collections::{BTreeSet, HashSet};
7 const LICENSES: &[&str] = &[
14 "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
20 /// These are exceptions to Rust's permissive licensing policy, and
21 /// should be considered bugs. Exceptions are only allowed in Rust
22 /// tooling. It is _crucial_ that no exception crates be dependencies
23 /// of the Rust runtime (std/test).
24 const EXCEPTIONS: &[&str] = &[
25 "mdbook", // MPL2, mdbook
26 "openssl", // BSD+advertising clause, cargo, mdbook
27 "pest", // MPL2, mdbook via handlebars
28 "arrayref", // BSD-2-Clause, mdbook via handlebars via pest
29 "thread-id", // Apache-2.0, mdbook
30 "toml-query", // MPL-2.0, mdbook
31 "toml-query_derive", // MPL-2.0, mdbook
32 "is-match", // MPL-2.0, mdbook
33 "cssparser", // MPL-2.0, rustdoc
34 "smallvec", // MPL-2.0, rustdoc
35 "rdrand", // ISC, mdbook, rustfmt
36 "fuchsia-cprng", // BSD-3-Clause, mdbook, rustfmt
37 "fuchsia-zircon-sys", // BSD-3-Clause, rustdoc, rustc, cargo
38 "fuchsia-zircon", // BSD-3-Clause, rustdoc, rustc, cargo (jobserver & tempdir)
39 "cssparser-macros", // MPL-2.0, rustdoc
40 "selectors", // MPL-2.0, rustdoc
41 "clippy_lints", // MPL-2.0, rls
42 "colored", // MPL-2.0, rustfmt
43 "ordslice", // Apache-2.0, rls
44 "cloudabi", // BSD-2-Clause, (rls -> crossbeam-channel 0.2 -> rand 0.5)
45 "ryu", // Apache-2.0, rls/cargo/... (because of serde)
46 "bytesize", // Apache-2.0, cargo
47 "im-rc", // MPL-2.0+, cargo
48 "adler32", // BSD-3-Clause AND Zlib, cargo dep that isn't used
49 "constant_time_eq", // CC0-1.0, rustfmt
50 "utf8parse", // Apache-2.0 OR MIT, cargo via strip-ansi-escapes
51 "vte", // Apache-2.0 OR MIT, cargo via strip-ansi-escapes
52 "sized-chunks", // MPL-2.0+, cargo via im-rc
53 "bitmaps", // MPL-2.0+, cargo via im-rc
54 // FIXME: this dependency violates the documentation comment above:
55 "fortanix-sgx-abi", // MPL-2.0+, libstd but only for `sgx` target
56 "dunce", // CC0-1.0 mdbook-linkcheck
57 "codespan-reporting", // Apache-2.0 mdbook-linkcheck
58 "codespan", // Apache-2.0 mdbook-linkcheck
59 "crossbeam-channel", // MIT/Apache-2.0 AND BSD-2-Clause, cargo
62 /// Which crates to check against the whitelist?
63 const WHITELIST_CRATES: &[&str] = &["rustc", "rustc_codegen_llvm"];
65 /// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
66 const WHITELIST: &[&str] = &[
102 "fuchsia-zircon-sys",
160 "stable_deref_trait",
170 "unicode-normalization",
183 "winapi-i686-pc-windows-gnu",
185 "winapi-x86_64-pc-windows-gnu",
190 /// Dependency checks.
192 /// `path` is path to the `src` directory, `cargo` is path to the cargo executable.
193 pub fn check(path: &Path, cargo: &Path, bad: &mut bool) {
194 let mut cmd = cargo_metadata::MetadataCommand::new();
195 cmd.cargo_path(cargo)
196 .manifest_path(path.parent().unwrap().join("Cargo.toml"))
197 .features(cargo_metadata::CargoOpt::AllFeatures);
198 let metadata = t!(cmd.exec());
199 check_exceptions(&metadata, bad);
200 check_whitelist(&metadata, bad);
201 check_crate_duplicate(&metadata, bad);
204 /// Check that all licenses are in the valid list in `LICENSES`.
206 /// Packages listed in `EXCEPTIONS` are allowed for tools.
207 fn check_exceptions(metadata: &Metadata, bad: &mut bool) {
208 for pkg in &metadata.packages {
209 if pkg.source.is_none() {
210 // No need to check local packages.
213 if EXCEPTIONS.contains(&pkg.name.as_str()) {
216 let license = match &pkg.license {
217 Some(license) => license,
219 println!("dependency `{}` does not define a license expression", pkg.id,);
224 if !LICENSES.contains(&license.as_str()) {
225 println!("invalid license `{}` in `{}`", license, pkg.id);
231 /// Checks the dependency of `WHITELIST_CRATES` at the given path. Changes `bad` to `true` if a
234 /// Specifically, this checks that the dependencies are on the `WHITELIST`.
235 fn check_whitelist(metadata: &Metadata, bad: &mut bool) {
236 // Get the whitelist in 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 pkg = pkg_from_name(metadata, krate);
244 let mut bad = check_crate_whitelist(&whitelist, metadata, &mut visited, pkg);
245 unapproved.append(&mut bad);
248 if !unapproved.is_empty() {
249 println!("Dependencies not on the whitelist:");
250 for dep in unapproved {
251 println!("* {}", dep);
257 /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
258 /// the whitelist. Returns a list of illegal dependencies.
259 fn check_crate_whitelist<'a>(
260 whitelist: &'a HashSet<&'static str>,
261 metadata: &'a Metadata,
262 visited: &mut BTreeSet<&'a PackageId>,
264 ) -> BTreeSet<&'a PackageId> {
265 // This will contain bad deps.
266 let mut unapproved = BTreeSet::new();
268 // Check if we have already visited this crate.
269 if visited.contains(&krate.id) {
273 visited.insert(&krate.id);
275 // If this path is in-tree, we don't require it to be on the whitelist.
276 if krate.source.is_some() {
277 // If this dependency is not on `WHITELIST`, add to bad set.
278 if !whitelist.contains(krate.name.as_str()) {
279 unapproved.insert(&krate.id);
283 // Do a DFS in the crate graph.
284 let to_check = deps_of(metadata, &krate.id);
286 for dep in to_check {
287 let mut bad = check_crate_whitelist(whitelist, metadata, visited, dep);
288 unapproved.append(&mut bad);
294 /// Prevents multiple versions of some expensive crates.
295 fn check_crate_duplicate(metadata: &Metadata, bad: &mut bool) {
296 const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
297 // These two crates take quite a long time to build, so don't allow two versions of them
298 // to accidentally sneak into our dependency graph, in order to ensure we keep our CI times
304 for &name in FORBIDDEN_TO_HAVE_DUPLICATES {
305 let matches: Vec<_> = metadata.packages.iter().filter(|pkg| pkg.name == name).collect();
306 match matches.len() {
309 "crate `{}` is missing, update `check_crate_duplicate` \
310 if it is no longer used",
318 "crate `{}` is duplicated in `Cargo.lock`, \
319 it is too expensive to build multiple times, \
320 so make sure only one version appears across all dependencies",
324 println!(" * {}", pkg.id);
332 /// Returns a list of dependencies for the given package.
333 fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId) -> Vec<&'a Package> {
340 .find(|n| &n.id == pkg_id)
341 .unwrap_or_else(|| panic!("could not find `{}` in resolve", pkg_id));
345 metadata.packages.iter().find(|pkg| pkg.id == dep.pkg).unwrap_or_else(|| {
346 panic!("could not find dep `{}` for pkg `{}` in resolve", dep.pkg, pkg_id)
352 /// Finds a package with the given name.
353 fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
354 let mut i = metadata.packages.iter().filter(|p| p.name == name);
356 i.next().unwrap_or_else(|| panic!("could not find package `{}` in package list", name));
357 assert!(i.next().is_none(), "more than one package found for `{}`", name);