]>
Commit | Line | Data |
---|---|---|
ba9703b0 | 1 | //! Checks the licenses of third-party dependencies. |
476ff2be | 2 | |
ba9703b0 XL |
3 | use cargo_metadata::{Metadata, Package, PackageId, Resolve}; |
4 | use std::collections::{BTreeSet, HashSet}; | |
476ff2be SL |
5 | use std::path::Path; |
6 | ||
ba9703b0 XL |
7 | /// These are licenses that are allowed for all crates, including the runtime, |
8 | /// rustc, tools, etc. | |
b7449926 | 9 | const LICENSES: &[&str] = &[ |
8bb4bdeb XL |
10 | "MIT/Apache-2.0", |
11 | "MIT / Apache-2.0", | |
12 | "Apache-2.0/MIT", | |
3b2f2976 | 13 | "Apache-2.0 / MIT", |
8bb4bdeb | 14 | "MIT OR Apache-2.0", |
416331ca | 15 | "Apache-2.0 OR MIT", |
e1599b0c | 16 | "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license |
8bb4bdeb XL |
17 | "MIT", |
18 | "Unlicense/MIT", | |
8faf50e0 | 19 | "Unlicense OR MIT", |
3dfed10e XL |
20 | "0BSD OR MIT OR Apache-2.0", // adler license |
21 | "Zlib OR Apache-2.0 OR MIT", // tinyvec | |
8bb4bdeb XL |
22 | ]; |
23 | ||
0531ce1d XL |
24 | /// These are exceptions to Rust's permissive licensing policy, and |
25 | /// should be considered bugs. Exceptions are only allowed in Rust | |
26 | /// tooling. It is _crucial_ that no exception crates be dependencies | |
9fa01778 | 27 | /// of the Rust runtime (std/test). |
ba9703b0 | 28 | const EXCEPTIONS: &[(&str, &str)] = &[ |
3dfed10e XL |
29 | ("mdbook", "MPL-2.0"), // mdbook |
30 | ("openssl", "Apache-2.0"), // cargo, mdbook | |
31 | ("fuchsia-zircon-sys", "BSD-3-Clause"), // rustdoc, rustc, cargo | |
32 | ("fuchsia-zircon", "BSD-3-Clause"), // rustdoc, rustc, cargo (jobserver & tempdir) | |
33 | ("colored", "MPL-2.0"), // rustfmt | |
34 | ("ordslice", "Apache-2.0"), // rls | |
35 | ("cloudabi", "BSD-2-Clause"), // (rls -> crossbeam-channel 0.2 -> rand 0.5) | |
36 | ("ryu", "Apache-2.0 OR BSL-1.0"), // rls/cargo/... (because of serde) | |
37 | ("bytesize", "Apache-2.0"), // cargo | |
38 | ("im-rc", "MPL-2.0+"), // cargo | |
39 | ("constant_time_eq", "CC0-1.0"), // rustfmt | |
40 | ("sized-chunks", "MPL-2.0+"), // cargo via im-rc | |
41 | ("bitmaps", "MPL-2.0+"), // cargo via im-rc | |
42 | ("crossbeam-queue", "MIT/Apache-2.0 AND BSD-2-Clause"), // rls via rayon | |
43 | ("arrayref", "BSD-2-Clause"), // cargo-miri/directories/.../rust-argon2 (redox) | |
44 | ("instant", "BSD-3-Clause"), // rustc_driver/tracing-subscriber/parking_lot | |
1b1a35ee | 45 | ("snap", "BSD-3-Clause"), // rustc |
416331ca | 46 | // FIXME: this dependency violates the documentation comment above: |
ba9703b0 | 47 | ("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target |
0531ce1d XL |
48 | ]; |
49 | ||
ba9703b0 XL |
50 | /// These are the root crates that are part of the runtime. The licenses for |
51 | /// these and all their dependencies *must not* be in the exception list. | |
52 | const RUNTIME_CRATES: &[&str] = &["std", "core", "alloc", "test", "panic_abort", "panic_unwind"]; | |
53 | ||
f035d41b XL |
54 | /// Crates whose dependencies must be explicitly permitted. |
55 | const RESTRICTED_DEPENDENCY_CRATES: &[&str] = &["rustc_middle", "rustc_codegen_llvm"]; | |
476ff2be | 56 | |
f035d41b | 57 | /// Crates rustc is allowed to depend on. Avoid adding to the list if possible. |
ba9703b0 XL |
58 | /// |
59 | /// This list is here to provide a speed-bump to adding a new dependency to | |
60 | /// rustc. Please check with the compiler team before adding an entry. | |
f035d41b | 61 | const PERMITTED_DEPENDENCIES: &[&str] = &[ |
3dfed10e XL |
62 | "addr2line", |
63 | "adler", | |
ba9703b0 XL |
64 | "aho-corasick", |
65 | "annotate-snippets", | |
66 | "ansi_term", | |
67 | "arrayvec", | |
68 | "atty", | |
69 | "autocfg", | |
ba9703b0 XL |
70 | "bitflags", |
71 | "block-buffer", | |
72 | "block-padding", | |
ba9703b0 | 73 | "byteorder", |
3dfed10e | 74 | "byte-tools", |
ba9703b0 XL |
75 | "cc", |
76 | "cfg-if", | |
f9f354fc | 77 | "chalk-derive", |
f9f354fc | 78 | "chalk-ir", |
ba9703b0 XL |
79 | "cloudabi", |
80 | "cmake", | |
81 | "compiler_builtins", | |
29967ef6 | 82 | "cpuid-bool", |
ba9703b0 XL |
83 | "crc32fast", |
84 | "crossbeam-deque", | |
85 | "crossbeam-epoch", | |
86 | "crossbeam-queue", | |
87 | "crossbeam-utils", | |
6a06907d | 88 | "cstr", |
ba9703b0 | 89 | "datafrog", |
3dfed10e | 90 | "difference", |
ba9703b0 XL |
91 | "digest", |
92 | "dlmalloc", | |
93 | "either", | |
94 | "ena", | |
95 | "env_logger", | |
3dfed10e | 96 | "expect-test", |
ba9703b0 XL |
97 | "fake-simd", |
98 | "filetime", | |
99 | "flate2", | |
100 | "fortanix-sgx-abi", | |
101 | "fuchsia-zircon", | |
102 | "fuchsia-zircon-sys", | |
103 | "generic-array", | |
104 | "getopts", | |
105 | "getrandom", | |
3dfed10e | 106 | "gimli", |
fc512014 | 107 | "gsgdt", |
ba9703b0 XL |
108 | "hashbrown", |
109 | "hermit-abi", | |
110 | "humantime", | |
111 | "indexmap", | |
3dfed10e | 112 | "instant", |
ba9703b0 XL |
113 | "itertools", |
114 | "jobserver", | |
115 | "kernel32-sys", | |
116 | "lazy_static", | |
117 | "libc", | |
118 | "libz-sys", | |
119 | "lock_api", | |
120 | "log", | |
3dfed10e | 121 | "maybe-uninit", |
ba9703b0 XL |
122 | "md-5", |
123 | "measureme", | |
124 | "memchr", | |
125 | "memmap", | |
6a06907d | 126 | "memmap2", |
ba9703b0 XL |
127 | "memoffset", |
128 | "miniz_oxide", | |
ba9703b0 | 129 | "num_cpus", |
3dfed10e | 130 | "object", |
f9f354fc | 131 | "once_cell", |
ba9703b0 XL |
132 | "opaque-debug", |
133 | "parking_lot", | |
134 | "parking_lot_core", | |
3dfed10e | 135 | "pathdiff", |
6a06907d XL |
136 | "perf-event-open-sys", |
137 | "pin-project-lite", | |
ba9703b0 XL |
138 | "pkg-config", |
139 | "polonius-engine", | |
140 | "ppv-lite86", | |
141 | "proc-macro2", | |
f9f354fc | 142 | "psm", |
ba9703b0 XL |
143 | "punycode", |
144 | "quick-error", | |
145 | "quote", | |
146 | "rand", | |
147 | "rand_chacha", | |
148 | "rand_core", | |
149 | "rand_hc", | |
ba9703b0 XL |
150 | "rand_pcg", |
151 | "rand_xorshift", | |
152 | "redox_syscall", | |
ba9703b0 XL |
153 | "regex", |
154 | "regex-syntax", | |
155 | "remove_dir_all", | |
156 | "rustc-demangle", | |
157 | "rustc-hash", | |
158 | "rustc-rayon", | |
159 | "rustc-rayon-core", | |
160 | "rustc_version", | |
161 | "scoped-tls", | |
162 | "scopeguard", | |
163 | "semver", | |
164 | "semver-parser", | |
165 | "serde", | |
166 | "serde_derive", | |
167 | "sha-1", | |
29967ef6 | 168 | "sha2", |
ba9703b0 | 169 | "smallvec", |
1b1a35ee | 170 | "snap", |
ba9703b0 | 171 | "stable_deref_trait", |
f9f354fc | 172 | "stacker", |
ba9703b0 XL |
173 | "syn", |
174 | "synstructure", | |
175 | "tempfile", | |
176 | "termcolor", | |
ba9703b0 XL |
177 | "termize", |
178 | "thread_local", | |
3dfed10e XL |
179 | "tracing", |
180 | "tracing-attributes", | |
181 | "tracing-core", | |
ba9703b0 | 182 | "typenum", |
ba9703b0 XL |
183 | "unicode-normalization", |
184 | "unicode-script", | |
185 | "unicode-security", | |
186 | "unicode-width", | |
187 | "unicode-xid", | |
ba9703b0 XL |
188 | "vcpkg", |
189 | "version_check", | |
190 | "wasi", | |
191 | "winapi", | |
192 | "winapi-build", | |
193 | "winapi-i686-pc-windows-gnu", | |
194 | "winapi-util", | |
195 | "winapi-x86_64-pc-windows-gnu", | |
0531ce1d XL |
196 | ]; |
197 | ||
ba9703b0 XL |
198 | /// Dependency checks. |
199 | /// | |
3dfed10e XL |
200 | /// `root` is path to the directory with the root `Cargo.toml` (for the workspace). `cargo` is path |
201 | /// to the cargo executable. | |
202 | pub fn check(root: &Path, cargo: &Path, bad: &mut bool) { | |
ba9703b0 XL |
203 | let mut cmd = cargo_metadata::MetadataCommand::new(); |
204 | cmd.cargo_path(cargo) | |
3dfed10e | 205 | .manifest_path(root.join("Cargo.toml")) |
ba9703b0 XL |
206 | .features(cargo_metadata::CargoOpt::AllFeatures); |
207 | let metadata = t!(cmd.exec()); | |
208 | check_exceptions(&metadata, bad); | |
f035d41b | 209 | check_dependencies(&metadata, bad); |
ba9703b0 | 210 | check_crate_duplicate(&metadata, bad); |
0531ce1d XL |
211 | } |
212 | ||
ba9703b0 XL |
213 | /// Check that all licenses are in the valid list in `LICENSES`. |
214 | /// | |
215 | /// Packages listed in `EXCEPTIONS` are allowed for tools. | |
216 | fn check_exceptions(metadata: &Metadata, bad: &mut bool) { | |
217 | // Validate the EXCEPTIONS list hasn't changed. | |
218 | for (name, license) in EXCEPTIONS { | |
219 | // Check that the package actually exists. | |
220 | if !metadata.packages.iter().any(|p| p.name == *name) { | |
fc512014 XL |
221 | tidy_error!( |
222 | bad, | |
ba9703b0 XL |
223 | "could not find exception package `{}`\n\ |
224 | Remove from EXCEPTIONS list if it is no longer used.", | |
225 | name | |
226 | ); | |
ba9703b0 XL |
227 | } |
228 | // Check that the license hasn't changed. | |
229 | for pkg in metadata.packages.iter().filter(|p| p.name == *name) { | |
230 | if pkg.name == "fuchsia-cprng" { | |
231 | // This package doesn't declare a license expression. Manual | |
232 | // inspection of the license file is necessary, which appears | |
233 | // to be BSD-3-Clause. | |
234 | assert!(pkg.license.is_none()); | |
235 | continue; | |
236 | } | |
237 | match &pkg.license { | |
238 | None => { | |
fc512014 XL |
239 | tidy_error!( |
240 | bad, | |
ba9703b0 XL |
241 | "dependency exception `{}` does not declare a license expression", |
242 | pkg.id | |
243 | ); | |
ba9703b0 XL |
244 | } |
245 | Some(pkg_license) => { | |
246 | if pkg_license.as_str() != *license { | |
3dfed10e XL |
247 | if *name == "crossbeam-queue" |
248 | && *license == "MIT/Apache-2.0 AND BSD-2-Clause" | |
249 | { | |
250 | // We have two versions of crossbeam-queue and both | |
251 | // are fine. | |
252 | continue; | |
253 | } | |
254 | ||
ba9703b0 XL |
255 | println!("dependency exception `{}` license has changed", name); |
256 | println!(" previously `{}` now `{}`", license, pkg_license); | |
257 | println!(" update EXCEPTIONS for the new license"); | |
258 | *bad = true; | |
259 | } | |
260 | } | |
261 | } | |
262 | } | |
0531ce1d | 263 | } |
0531ce1d | 264 | |
ba9703b0 XL |
265 | let exception_names: Vec<_> = EXCEPTIONS.iter().map(|(name, _license)| *name).collect(); |
266 | let runtime_ids = compute_runtime_crates(metadata); | |
8bb4bdeb | 267 | |
ba9703b0 XL |
268 | // Check if any package does not have a valid license. |
269 | for pkg in &metadata.packages { | |
270 | if pkg.source.is_none() { | |
271 | // No need to check local packages. | |
0531ce1d | 272 | continue; |
8bb4bdeb | 273 | } |
ba9703b0 XL |
274 | if !runtime_ids.contains(&pkg.id) && exception_names.contains(&pkg.name.as_str()) { |
275 | continue; | |
276 | } | |
277 | let license = match &pkg.license { | |
278 | Some(license) => license, | |
279 | None => { | |
fc512014 | 280 | tidy_error!(bad, "dependency `{}` does not define a license expression", pkg.id); |
ba9703b0 XL |
281 | continue; |
282 | } | |
283 | }; | |
284 | if !LICENSES.contains(&license.as_str()) { | |
285 | if pkg.name == "fortanix-sgx-abi" { | |
286 | // This is a specific exception because SGX is considered | |
287 | // "third party". See | |
288 | // https://github.com/rust-lang/rust/issues/62620 for more. In | |
289 | // general, these should never be added. | |
290 | continue; | |
291 | } | |
fc512014 | 292 | tidy_error!(bad, "invalid license `{}` in `{}`", license, pkg.id); |
ba9703b0 | 293 | } |
476ff2be | 294 | } |
476ff2be SL |
295 | } |
296 | ||
f035d41b XL |
297 | /// Checks the dependency of `RESTRICTED_DEPENDENCY_CRATES` at the given path. Changes `bad` to |
298 | /// `true` if a check failed. | |
0531ce1d | 299 | /// |
f035d41b XL |
300 | /// Specifically, this checks that the dependencies are on the `PERMITTED_DEPENDENCIES`. |
301 | fn check_dependencies(metadata: &Metadata, bad: &mut bool) { | |
302 | // Check that the PERMITTED_DEPENDENCIES does not have unused entries. | |
303 | for name in PERMITTED_DEPENDENCIES { | |
ba9703b0 | 304 | if !metadata.packages.iter().any(|p| p.name == *name) { |
fc512014 XL |
305 | tidy_error!( |
306 | bad, | |
f035d41b XL |
307 | "could not find allowed package `{}`\n\ |
308 | Remove from PERMITTED_DEPENDENCIES list if it is no longer used.", | |
ba9703b0 XL |
309 | name |
310 | ); | |
ba9703b0 XL |
311 | } |
312 | } | |
f035d41b XL |
313 | // Get the list in a convenient form. |
314 | let permitted_dependencies: HashSet<_> = PERMITTED_DEPENDENCIES.iter().cloned().collect(); | |
0531ce1d | 315 | |
9fa01778 | 316 | // Check dependencies. |
0531ce1d XL |
317 | let mut visited = BTreeSet::new(); |
318 | let mut unapproved = BTreeSet::new(); | |
f035d41b | 319 | for &krate in RESTRICTED_DEPENDENCY_CRATES.iter() { |
ba9703b0 | 320 | let pkg = pkg_from_name(metadata, krate); |
f035d41b XL |
321 | let mut bad = |
322 | check_crate_dependencies(&permitted_dependencies, metadata, &mut visited, pkg); | |
0531ce1d XL |
323 | unapproved.append(&mut bad); |
324 | } | |
325 | ||
b7449926 | 326 | if !unapproved.is_empty() { |
fc512014 | 327 | tidy_error!(bad, "Dependencies not explicitly permitted:"); |
0531ce1d | 328 | for dep in unapproved { |
ba9703b0 | 329 | println!("* {}", dep); |
0531ce1d | 330 | } |
0531ce1d | 331 | } |
0531ce1d XL |
332 | } |
333 | ||
334 | /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on | |
f035d41b XL |
335 | /// the list of permitted dependencies. Returns a list of disallowed dependencies. |
336 | fn check_crate_dependencies<'a>( | |
337 | permitted_dependencies: &'a HashSet<&'static str>, | |
ba9703b0 XL |
338 | metadata: &'a Metadata, |
339 | visited: &mut BTreeSet<&'a PackageId>, | |
340 | krate: &'a Package, | |
341 | ) -> BTreeSet<&'a PackageId> { | |
9fa01778 | 342 | // This will contain bad deps. |
0531ce1d XL |
343 | let mut unapproved = BTreeSet::new(); |
344 | ||
9fa01778 | 345 | // Check if we have already visited this crate. |
ba9703b0 | 346 | if visited.contains(&krate.id) { |
0531ce1d XL |
347 | return unapproved; |
348 | } | |
349 | ||
ba9703b0 | 350 | visited.insert(&krate.id); |
0531ce1d | 351 | |
f035d41b | 352 | // If this path is in-tree, we don't require it to be explicitly permitted. |
ba9703b0 | 353 | if krate.source.is_some() { |
f035d41b XL |
354 | // If this dependency is not on `PERMITTED_DEPENDENCIES`, add to bad set. |
355 | if !permitted_dependencies.contains(krate.name.as_str()) { | |
ba9703b0 | 356 | unapproved.insert(&krate.id); |
0531ce1d XL |
357 | } |
358 | } | |
359 | ||
ba9703b0 XL |
360 | // Do a DFS in the crate graph. |
361 | let to_check = deps_of(metadata, &krate.id); | |
0531ce1d | 362 | |
ba9703b0 | 363 | for dep in to_check { |
f035d41b | 364 | let mut bad = check_crate_dependencies(permitted_dependencies, metadata, visited, dep); |
0531ce1d XL |
365 | unapproved.append(&mut bad); |
366 | } | |
367 | ||
368 | unapproved | |
369 | } | |
b7449926 | 370 | |
ba9703b0 XL |
371 | /// Prevents multiple versions of some expensive crates. |
372 | fn check_crate_duplicate(metadata: &Metadata, bad: &mut bool) { | |
b7449926 | 373 | const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[ |
9fa01778 XL |
374 | // These two crates take quite a long time to build, so don't allow two versions of them |
375 | // to accidentally sneak into our dependency graph, in order to ensure we keep our CI times | |
376 | // under control. | |
532ac7d7 | 377 | "cargo", |
ba9703b0 | 378 | "rustc-ap-rustc_ast", |
b7449926 | 379 | ]; |
b7449926 | 380 | |
ba9703b0 XL |
381 | for &name in FORBIDDEN_TO_HAVE_DUPLICATES { |
382 | let matches: Vec<_> = metadata.packages.iter().filter(|pkg| pkg.name == name).collect(); | |
383 | match matches.len() { | |
384 | 0 => { | |
fc512014 XL |
385 | tidy_error!( |
386 | bad, | |
ba9703b0 XL |
387 | "crate `{}` is missing, update `check_crate_duplicate` \ |
388 | if it is no longer used", | |
389 | name | |
390 | ); | |
ba9703b0 XL |
391 | } |
392 | 1 => {} | |
393 | _ => { | |
fc512014 XL |
394 | tidy_error!( |
395 | bad, | |
ba9703b0 XL |
396 | "crate `{}` is duplicated in `Cargo.lock`, \ |
397 | it is too expensive to build multiple times, \ | |
398 | so make sure only one version appears across all dependencies", | |
399 | name | |
400 | ); | |
401 | for pkg in matches { | |
402 | println!(" * {}", pkg.id); | |
403 | } | |
ba9703b0 | 404 | } |
b7449926 | 405 | } |
ba9703b0 XL |
406 | } |
407 | } | |
408 | ||
409 | /// Returns a list of dependencies for the given package. | |
410 | fn deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId) -> Vec<&'a Package> { | |
411 | let resolve = metadata.resolve.as_ref().unwrap(); | |
412 | let node = resolve | |
413 | .nodes | |
414 | .iter() | |
415 | .find(|n| &n.id == pkg_id) | |
416 | .unwrap_or_else(|| panic!("could not find `{}` in resolve", pkg_id)); | |
417 | node.deps | |
418 | .iter() | |
419 | .map(|dep| { | |
420 | metadata.packages.iter().find(|pkg| pkg.id == dep.pkg).unwrap_or_else(|| { | |
421 | panic!("could not find dep `{}` for pkg `{}` in resolve", dep.pkg, pkg_id) | |
422 | }) | |
423 | }) | |
424 | .collect() | |
425 | } | |
426 | ||
427 | /// Finds a package with the given name. | |
428 | fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package { | |
429 | let mut i = metadata.packages.iter().filter(|p| p.name == name); | |
430 | let result = | |
431 | i.next().unwrap_or_else(|| panic!("could not find package `{}` in package list", name)); | |
432 | assert!(i.next().is_none(), "more than one package found for `{}`", name); | |
433 | result | |
434 | } | |
435 | ||
436 | /// Finds all the packages that are in the rust runtime. | |
437 | fn compute_runtime_crates<'a>(metadata: &'a Metadata) -> HashSet<&'a PackageId> { | |
438 | let resolve = metadata.resolve.as_ref().unwrap(); | |
439 | let mut result = HashSet::new(); | |
440 | for name in RUNTIME_CRATES { | |
441 | let id = &pkg_from_name(metadata, name).id; | |
442 | normal_deps_of_r(resolve, id, &mut result); | |
443 | } | |
444 | result | |
445 | } | |
446 | ||
447 | /// Recursively find all normal dependencies. | |
448 | fn normal_deps_of_r<'a>( | |
449 | resolve: &'a Resolve, | |
450 | pkg_id: &'a PackageId, | |
451 | result: &mut HashSet<&'a PackageId>, | |
452 | ) { | |
453 | if !result.insert(pkg_id) { | |
454 | return; | |
455 | } | |
456 | let node = resolve | |
457 | .nodes | |
458 | .iter() | |
459 | .find(|n| &n.id == pkg_id) | |
460 | .unwrap_or_else(|| panic!("could not find `{}` in resolve", pkg_id)); | |
461 | // Don't care about dev-dependencies. | |
462 | // Build dependencies *shouldn't* matter unless they do some kind of | |
463 | // codegen. For now we'll assume they don't. | |
464 | let deps = node.deps.iter().filter(|node_dep| { | |
465 | node_dep | |
466 | .dep_kinds | |
467 | .iter() | |
468 | .any(|kind_info| kind_info.kind == cargo_metadata::DependencyKind::Normal) | |
469 | }); | |
470 | for dep in deps { | |
471 | normal_deps_of_r(resolve, &dep.pkg, result); | |
b7449926 XL |
472 | } |
473 | } |