]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/deps.rs
tidy: Use cargo_metadata for license checks.
[rust.git] / src / tools / tidy / src / deps.rs
1 //! Checks the licenses of third-party dependencies.
2
3 use cargo_metadata::{Metadata, Package, PackageId};
4 use std::collections::{BTreeSet, HashSet};
5 use std::path::Path;
6
7 const LICENSES: &[&str] = &[
8     "MIT/Apache-2.0",
9     "MIT / Apache-2.0",
10     "Apache-2.0/MIT",
11     "Apache-2.0 / MIT",
12     "MIT OR Apache-2.0",
13     "Apache-2.0 OR MIT",
14     "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
15     "MIT",
16     "Unlicense/MIT",
17     "Unlicense OR MIT",
18 ];
19
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
60 ];
61
62 /// Which crates to check against the whitelist?
63 const WHITELIST_CRATES: &[&str] = &["rustc", "rustc_codegen_llvm"];
64
65 /// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
66 const WHITELIST: &[&str] = &[
67     "adler32",
68     "aho-corasick",
69     "annotate-snippets",
70     "ansi_term",
71     "arrayvec",
72     "atty",
73     "autocfg",
74     "backtrace",
75     "backtrace-sys",
76     "bitflags",
77     "build_const",
78     "byteorder",
79     "c2-chacha",
80     "cc",
81     "cfg-if",
82     "chalk-engine",
83     "chalk-macros",
84     "cloudabi",
85     "cmake",
86     "compiler_builtins",
87     "crc",
88     "crc32fast",
89     "crossbeam-deque",
90     "crossbeam-epoch",
91     "crossbeam-queue",
92     "crossbeam-utils",
93     "datafrog",
94     "dlmalloc",
95     "either",
96     "ena",
97     "env_logger",
98     "filetime",
99     "flate2",
100     "fortanix-sgx-abi",
101     "fuchsia-zircon",
102     "fuchsia-zircon-sys",
103     "getopts",
104     "getrandom",
105     "hashbrown",
106     "humantime",
107     "indexmap",
108     "itertools",
109     "jobserver",
110     "kernel32-sys",
111     "lazy_static",
112     "libc",
113     "libz-sys",
114     "lock_api",
115     "log",
116     "log_settings",
117     "measureme",
118     "memchr",
119     "memmap",
120     "memoffset",
121     "miniz-sys",
122     "miniz_oxide",
123     "miniz_oxide_c_api",
124     "nodrop",
125     "num_cpus",
126     "owning_ref",
127     "parking_lot",
128     "parking_lot_core",
129     "pkg-config",
130     "polonius-engine",
131     "ppv-lite86",
132     "proc-macro2",
133     "punycode",
134     "quick-error",
135     "quote",
136     "rand",
137     "rand_chacha",
138     "rand_core",
139     "rand_hc",
140     "rand_isaac",
141     "rand_pcg",
142     "rand_xorshift",
143     "redox_syscall",
144     "redox_termios",
145     "regex",
146     "regex-syntax",
147     "remove_dir_all",
148     "rustc-demangle",
149     "rustc-hash",
150     "rustc-rayon",
151     "rustc-rayon-core",
152     "rustc_version",
153     "scoped-tls",
154     "scopeguard",
155     "semver",
156     "semver-parser",
157     "serde",
158     "serde_derive",
159     "smallvec",
160     "stable_deref_trait",
161     "syn",
162     "synstructure",
163     "tempfile",
164     "termcolor",
165     "terminon",
166     "termion",
167     "termize",
168     "thread_local",
169     "ucd-util",
170     "unicode-normalization",
171     "unicode-script",
172     "unicode-security",
173     "unicode-width",
174     "unicode-xid",
175     "unreachable",
176     "utf8-ranges",
177     "vcpkg",
178     "version_check",
179     "void",
180     "wasi",
181     "winapi",
182     "winapi-build",
183     "winapi-i686-pc-windows-gnu",
184     "winapi-util",
185     "winapi-x86_64-pc-windows-gnu",
186     "wincolor",
187     "hermit-abi",
188 ];
189
190 /// Dependency checks.
191 ///
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);
202 }
203
204 /// Check that all licenses are in the valid list in `LICENSES`.
205 ///
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.
211             continue;
212         }
213         if EXCEPTIONS.contains(&pkg.name.as_str()) {
214             continue;
215         }
216         let license = match &pkg.license {
217             Some(license) => license,
218             None => {
219                 println!("dependency `{}` does not define a license expression", pkg.id,);
220                 *bad = true;
221                 continue;
222             }
223         };
224         if !LICENSES.contains(&license.as_str()) {
225             println!("invalid license `{}` in `{}`", license, pkg.id);
226             *bad = true;
227         }
228     }
229 }
230
231 /// Checks the dependency of `WHITELIST_CRATES` at the given path. Changes `bad` to `true` if a
232 /// check failed.
233 ///
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();
238
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);
246     }
247
248     if !unapproved.is_empty() {
249         println!("Dependencies not on the whitelist:");
250         for dep in unapproved {
251             println!("* {}", dep);
252         }
253         *bad = true;
254     }
255 }
256
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>,
263     krate: &'a Package,
264 ) -> BTreeSet<&'a PackageId> {
265     // This will contain bad deps.
266     let mut unapproved = BTreeSet::new();
267
268     // Check if we have already visited this crate.
269     if visited.contains(&krate.id) {
270         return unapproved;
271     }
272
273     visited.insert(&krate.id);
274
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);
280         }
281     }
282
283     // Do a DFS in the crate graph.
284     let to_check = deps_of(metadata, &krate.id);
285
286     for dep in to_check {
287         let mut bad = check_crate_whitelist(whitelist, metadata, visited, dep);
288         unapproved.append(&mut bad);
289     }
290
291     unapproved
292 }
293
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
299         // under control.
300         "cargo",
301         "rustc-ap-syntax",
302     ];
303
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() {
307             0 => {
308                 println!(
309                     "crate `{}` is missing, update `check_crate_duplicate` \
310                     if it is no longer used",
311                     name
312                 );
313                 *bad = true;
314             }
315             1 => {}
316             _ => {
317                 println!(
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",
321                     name
322                 );
323                 for pkg in matches {
324                     println!("  * {}", pkg.id);
325                 }
326                 *bad = true;
327             }
328         }
329     }
330 }
331
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> {
334     let node = metadata
335         .resolve
336         .as_ref()
337         .unwrap()
338         .nodes
339         .iter()
340         .find(|n| &n.id == pkg_id)
341         .unwrap_or_else(|| panic!("could not find `{}` in resolve", pkg_id));
342     node.deps
343         .iter()
344         .map(|dep| {
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)
347             })
348         })
349         .collect()
350 }
351
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);
355     let result =
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);
358     result
359 }