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