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