]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/deps.rs
17c36b80f3a7c6fa492a26e3460827db4f8fbb9a
[rust.git] / src / tools / tidy / src / deps.rs
1 //! Checks the licenses of third-party dependencies by inspecting vendors.
2
3 use std::collections::{BTreeSet, HashSet, HashMap};
4 use std::fs;
5 use std::path::Path;
6 use std::process::Command;
7
8 use serde_json;
9
10 const LICENSES: &[&str] = &[
11     "MIT/Apache-2.0",
12     "MIT / Apache-2.0",
13     "Apache-2.0/MIT",
14     "Apache-2.0 / MIT",
15     "MIT OR Apache-2.0",
16     "MIT",
17     "Unlicense/MIT",
18     "Unlicense OR MIT",
19 ];
20
21 /// These are exceptions to Rust's permissive licensing policy, and
22 /// should be considered bugs. Exceptions are only allowed in Rust
23 /// tooling. It is _crucial_ that no exception crates be dependencies
24 /// of the Rust runtime (std/test).
25 const EXCEPTIONS: &[&str] = &[
26     "mdbook",             // MPL2, mdbook
27     "openssl",            // BSD+advertising clause, cargo, mdbook
28     "pest",               // MPL2, mdbook via handlebars
29     "arrayref",           // BSD-2-Clause, mdbook via handlebars via pest
30     "thread-id",          // Apache-2.0, mdbook
31     "toml-query",         // MPL-2.0, mdbook
32     "is-match",           // MPL-2.0, mdbook
33     "cssparser",          // MPL-2.0, rustdoc
34     "smallvec",           // MPL-2.0, rustdoc
35     "fuchsia-zircon-sys", // BSD-3-Clause, rustdoc, rustc, cargo
36     "fuchsia-zircon",     // BSD-3-Clause, rustdoc, rustc, cargo (jobserver & tempdir)
37     "cssparser-macros",   // MPL-2.0, rustdoc
38     "selectors",          // MPL-2.0, rustdoc
39     "clippy_lints",       // MPL-2.0, rls
40     "colored",            // MPL-2.0, rustfmt
41     "ordslice",           // Apache-2.0, rls
42     "cloudabi",           // BSD-2-Clause, (rls -> crossbeam-channel 0.2 -> rand 0.5)
43     "ryu",                // Apache-2.0, rls/cargo/... (because of serde)
44     "bytesize",           // Apache-2.0, cargo
45     "im-rc",              // MPL-2.0+, cargo
46     "adler32",            // BSD-3-Clause AND Zlib, cargo dep that isn't used
47     "fortanix-sgx-abi",   // MPL-2.0+, libstd but only for `sgx` target
48 ];
49
50 /// Which crates to check against the whitelist?
51 const WHITELIST_CRATES: &[CrateVersion] = &[
52     CrateVersion("rustc", "0.0.0"),
53     CrateVersion("rustc_codegen_llvm", "0.0.0"),
54 ];
55
56 /// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
57 const WHITELIST: &[Crate] = &[
58     Crate("adler32"),
59     Crate("aho-corasick"),
60     Crate("arrayvec"),
61     Crate("atty"),
62     Crate("backtrace"),
63     Crate("backtrace-sys"),
64     Crate("bitflags"),
65     Crate("build_const"),
66     Crate("byteorder"),
67     Crate("cc"),
68     Crate("cfg-if"),
69     Crate("chalk-engine"),
70     Crate("chalk-macros"),
71     Crate("cloudabi"),
72     Crate("cmake"),
73     Crate("compiler_builtins"),
74     Crate("crc"),
75     Crate("crc32fast"),
76     Crate("crossbeam-deque"),
77     Crate("crossbeam-epoch"),
78     Crate("crossbeam-utils"),
79     Crate("datafrog"),
80     Crate("either"),
81     Crate("ena"),
82     Crate("env_logger"),
83     Crate("filetime"),
84     Crate("flate2"),
85     Crate("fuchsia-zircon"),
86     Crate("fuchsia-zircon-sys"),
87     Crate("getopts"),
88     Crate("humantime"),
89     Crate("jobserver"),
90     Crate("kernel32-sys"),
91     Crate("lazy_static"),
92     Crate("libc"),
93     Crate("libz-sys"),
94     Crate("lock_api"),
95     Crate("log"),
96     Crate("log_settings"),
97     Crate("memchr"),
98     Crate("memmap"),
99     Crate("memoffset"),
100     Crate("miniz-sys"),
101     Crate("miniz_oxide"),
102     Crate("miniz_oxide_c_api"),
103     Crate("nodrop"),
104     Crate("num_cpus"),
105     Crate("owning_ref"),
106     Crate("parking_lot"),
107     Crate("parking_lot_core"),
108     Crate("pkg-config"),
109     Crate("polonius-engine"),
110     Crate("proc-macro2"),
111     Crate("quick-error"),
112     Crate("quote"),
113     Crate("rand"),
114     Crate("rand_chacha"),
115     Crate("rand_core"),
116     Crate("rand_hc"),
117     Crate("rand_isaac"),
118     Crate("rand_pcg"),
119     Crate("rand_xorshift"),
120     Crate("redox_syscall"),
121     Crate("redox_termios"),
122     Crate("regex"),
123     Crate("regex-syntax"),
124     Crate("remove_dir_all"),
125     Crate("rustc-demangle"),
126     Crate("rustc-hash"),
127     Crate("rustc-rayon"),
128     Crate("rustc-rayon-core"),
129     Crate("rustc_version"),
130     Crate("scoped-tls"),
131     Crate("scopeguard"),
132     Crate("semver"),
133     Crate("semver-parser"),
134     Crate("serde"),
135     Crate("serde_derive"),
136     Crate("smallvec"),
137     Crate("stable_deref_trait"),
138     Crate("syn"),
139     Crate("tempfile"),
140     Crate("termcolor"),
141     Crate("terminon"),
142     Crate("termion"),
143     Crate("thread_local"),
144     Crate("ucd-util"),
145     Crate("unicode-width"),
146     Crate("unicode-xid"),
147     Crate("unreachable"),
148     Crate("utf8-ranges"),
149     Crate("vcpkg"),
150     Crate("version_check"),
151     Crate("void"),
152     Crate("winapi"),
153     Crate("winapi-build"),
154     Crate("winapi-i686-pc-windows-gnu"),
155     Crate("winapi-util"),
156     Crate("winapi-x86_64-pc-windows-gnu"),
157     Crate("wincolor"),
158 ];
159
160 // Some types for Serde to deserialize the output of `cargo metadata` to.
161
162 #[derive(Deserialize)]
163 struct Output {
164     resolve: Resolve,
165 }
166
167 #[derive(Deserialize)]
168 struct Resolve {
169     nodes: Vec<ResolveNode>,
170 }
171
172 #[derive(Deserialize)]
173 struct ResolveNode {
174     id: String,
175     dependencies: Vec<String>,
176 }
177
178 /// A unique identifier for a crate.
179 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
180 struct Crate<'a>(&'a str); // (name)
181
182 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
183 struct CrateVersion<'a>(&'a str, &'a str); // (name, version)
184
185 impl<'a> Crate<'a> {
186     pub fn id_str(&self) -> String {
187         format!("{} ", self.0)
188     }
189 }
190
191 impl<'a> CrateVersion<'a> {
192     /// Returns the struct and whether or not the dependency is in-tree.
193     pub fn from_str(s: &'a str) -> (Self, bool) {
194         let mut parts = s.split(' ');
195         let name = parts.next().unwrap();
196         let version = parts.next().unwrap();
197         let path = parts.next().unwrap();
198
199         let is_path_dep = path.starts_with("(path+");
200
201         (CrateVersion(name, version), is_path_dep)
202     }
203
204     pub fn id_str(&self) -> String {
205         format!("{} {}", self.0, self.1)
206     }
207 }
208
209 impl<'a> From<CrateVersion<'a>> for Crate<'a> {
210     fn from(cv: CrateVersion<'a>) -> Crate<'a> {
211         Crate(cv.0)
212     }
213 }
214
215 /// Checks the dependency at the given path. Changes `bad` to `true` if a check failed.
216 ///
217 /// Specifically, this checks that the license is correct.
218 pub fn check(path: &Path, bad: &mut bool) {
219     // Check licences.
220     let path = path.join("../vendor");
221     assert!(path.exists(), "vendor directory missing");
222     let mut saw_dir = false;
223     for dir in t!(path.read_dir()) {
224         saw_dir = true;
225         let dir = t!(dir);
226
227         // Skip our exceptions.
228         let is_exception = EXCEPTIONS.iter().any(|exception| {
229             dir.path()
230                 .to_str()
231                 .unwrap()
232                 .contains(&format!("vendor/{}", exception))
233         });
234         if is_exception {
235             continue;
236         }
237
238         let toml = dir.path().join("Cargo.toml");
239         *bad = *bad || !check_license(&toml);
240     }
241     assert!(saw_dir, "no vendored source");
242 }
243
244 /// Checks the dependency of `WHITELIST_CRATES` at the given path. Changes `bad` to `true` if a
245 /// check failed.
246 ///
247 /// Specifically, this checks that the dependencies are on the `WHITELIST`.
248 pub fn check_whitelist(path: &Path, cargo: &Path, bad: &mut bool) {
249     // Get dependencies from Cargo metadata.
250     let resolve = get_deps(path, cargo);
251
252     // Get the whitelist in a convenient form.
253     let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
254
255     // Check dependencies.
256     let mut visited = BTreeSet::new();
257     let mut unapproved = BTreeSet::new();
258     for &krate in WHITELIST_CRATES.iter() {
259         let mut bad = check_crate_whitelist(&whitelist, &resolve, &mut visited, krate, false);
260         unapproved.append(&mut bad);
261     }
262
263     if !unapproved.is_empty() {
264         println!("Dependencies not on the whitelist:");
265         for dep in unapproved {
266             println!("* {}", dep.id_str());
267         }
268         *bad = true;
269     }
270
271     check_crate_duplicate(&resolve, bad);
272 }
273
274 fn check_license(path: &Path) -> bool {
275     if !path.exists() {
276         panic!("{} does not exist", path.display());
277     }
278     let contents = t!(fs::read_to_string(&path));
279
280     let mut found_license = false;
281     for line in contents.lines() {
282         if !line.starts_with("license") {
283             continue;
284         }
285         let license = extract_license(line);
286         if !LICENSES.contains(&&*license) {
287             println!("invalid license {} in {}", license, path.display());
288             return false;
289         }
290         found_license = true;
291         break;
292     }
293     if !found_license {
294         println!("no license in {}", path.display());
295         return false;
296     }
297
298     true
299 }
300
301 fn extract_license(line: &str) -> String {
302     let first_quote = line.find('"');
303     let last_quote = line.rfind('"');
304     if let (Some(f), Some(l)) = (first_quote, last_quote) {
305         let license = &line[f + 1..l];
306         license.into()
307     } else {
308         "bad-license-parse".into()
309     }
310 }
311
312 /// Gets the dependencies of the crate at the given path using `cargo metadata`.
313 fn get_deps(path: &Path, cargo: &Path) -> Resolve {
314     // Run `cargo metadata` to get the set of dependencies.
315     let output = Command::new(cargo)
316         .arg("metadata")
317         .arg("--format-version")
318         .arg("1")
319         .arg("--manifest-path")
320         .arg(path.join("../Cargo.toml"))
321         .output()
322         .expect("Unable to run `cargo metadata`")
323         .stdout;
324     let output = String::from_utf8_lossy(&output);
325     let output: Output = serde_json::from_str(&output).unwrap();
326
327     output.resolve
328 }
329
330 /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
331 /// the whitelist. Returns a list of illegal dependencies.
332 fn check_crate_whitelist<'a, 'b>(
333     whitelist: &'a HashSet<Crate>,
334     resolve: &'a Resolve,
335     visited: &'b mut BTreeSet<CrateVersion<'a>>,
336     krate: CrateVersion<'a>,
337     must_be_on_whitelist: bool,
338 ) -> BTreeSet<Crate<'a>> {
339     // This will contain bad deps.
340     let mut unapproved = BTreeSet::new();
341
342     // Check if we have already visited this crate.
343     if visited.contains(&krate) {
344         return unapproved;
345     }
346
347     visited.insert(krate);
348
349     // If this path is in-tree, we don't require it to be on the whitelist.
350     if must_be_on_whitelist {
351         // If this dependency is not on `WHITELIST`, add to bad set.
352         if !whitelist.contains(&krate.into()) {
353             unapproved.insert(krate.into());
354         }
355     }
356
357     // Do a DFS in the crate graph (it's a DAG, so we know we have no cycles!).
358     let to_check = resolve
359         .nodes
360         .iter()
361         .find(|n| n.id.starts_with(&krate.id_str()))
362         .expect("crate does not exist");
363
364     for dep in to_check.dependencies.iter() {
365         let (krate, is_path_dep) = CrateVersion::from_str(dep);
366
367         let mut bad = check_crate_whitelist(whitelist, resolve, visited, krate, !is_path_dep);
368         unapproved.append(&mut bad);
369     }
370
371     unapproved
372 }
373
374 fn check_crate_duplicate(resolve: &Resolve, bad: &mut bool) {
375     const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
376         // These two crates take quite a long time to build, so don't allow two versions of them
377         // to accidentally sneak into our dependency graph, in order to ensure we keep our CI times
378         // under control.
379
380         // "cargo", // FIXME(#53005)
381         "rustc-ap-syntax",
382     ];
383     let mut name_to_id: HashMap<_, Vec<_>> = HashMap::new();
384     for node in resolve.nodes.iter() {
385         name_to_id.entry(node.id.split_whitespace().next().unwrap())
386             .or_default()
387             .push(&node.id);
388     }
389
390     for name in FORBIDDEN_TO_HAVE_DUPLICATES {
391         if name_to_id[name].len() <= 1 {
392             continue
393         }
394         println!("crate `{}` is duplicated in `Cargo.lock`", name);
395         for id in name_to_id[name].iter() {
396             println!("  * {}", id);
397         }
398         *bad = true;
399     }
400 }