]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/deps.rs
make `String::new()` const
[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 src/vendor
12
13 use std::collections::{BTreeSet, HashSet};
14 use std::fs::File;
15 use std::io::Read;
16 use std::path::Path;
17 use std::process::Command;
18
19 use serde_json;
20
21 static LICENSES: &'static [&'static str] = &[
22     "MIT/Apache-2.0",
23     "MIT / Apache-2.0",
24     "Apache-2.0/MIT",
25     "Apache-2.0 / MIT",
26     "MIT OR Apache-2.0",
27     "MIT",
28     "Unlicense/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 static EXCEPTIONS: &'static [&'static 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 ];
51
52 /// Which crates to check against the whitelist?
53 static WHITELIST_CRATES: &'static [CrateVersion] = &[
54     CrateVersion("rustc", "0.0.0"),
55     CrateVersion("rustc_trans", "0.0.0"),
56 ];
57
58 /// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
59 static WHITELIST: &'static [Crate] = &[
60     Crate("aho-corasick"),
61     Crate("ar"),
62     Crate("atty"),
63     Crate("backtrace"),
64     Crate("backtrace-sys"),
65     Crate("bitflags"),
66     Crate("byteorder"),
67     Crate("cc"),
68     Crate("cfg-if"),
69     Crate("cmake"),
70     Crate("ena"),
71     Crate("env_logger"),
72     Crate("filetime"),
73     Crate("flate2"),
74     Crate("fuchsia-zircon"),
75     Crate("fuchsia-zircon-sys"),
76     Crate("humantime"),
77     Crate("jobserver"),
78     Crate("kernel32-sys"),
79     Crate("lazy_static"),
80     Crate("libc"),
81     Crate("log"),
82     Crate("log_settings"),
83     Crate("memchr"),
84     Crate("miniz-sys"),
85     Crate("num_cpus"),
86     Crate("owning_ref"),
87     Crate("parking_lot"),
88     Crate("parking_lot_core"),
89     Crate("quick-error"),
90     Crate("rand"),
91     Crate("redox_syscall"),
92     Crate("redox_termios"),
93     Crate("regex"),
94     Crate("regex-syntax"),
95     Crate("remove_dir_all"),
96     Crate("rustc-demangle"),
97     Crate("scoped-tls"),
98     Crate("smallvec"),
99     Crate("stable_deref_trait"),
100     Crate("tempdir"),
101     Crate("termcolor"),
102     Crate("terminon"),
103     Crate("termion"),
104     Crate("thread_local"),
105     Crate("ucd-util"),
106     Crate("unicode-width"),
107     Crate("unreachable"),
108     Crate("utf8-ranges"),
109     Crate("void"),
110     Crate("winapi"),
111     Crate("winapi-build"),
112     Crate("winapi-i686-pc-windows-gnu"),
113     Crate("winapi-x86_64-pc-windows-gnu"),
114     Crate("wincolor"),
115 ];
116
117 // Some types for Serde to deserialize the output of `cargo metadata` to...
118
119 #[derive(Deserialize)]
120 struct Output {
121     resolve: Resolve,
122 }
123
124 #[derive(Deserialize)]
125 struct Resolve {
126     nodes: Vec<ResolveNode>,
127 }
128
129 #[derive(Deserialize)]
130 struct ResolveNode {
131     id: String,
132     dependencies: Vec<String>,
133 }
134
135 /// A unique identifier for a crate
136 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
137 struct Crate<'a>(&'a str); // (name,)
138
139 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
140 struct CrateVersion<'a>(&'a str, &'a str); // (name, version)
141
142 impl<'a> Crate<'a> {
143     pub fn id_str(&self) -> String {
144         format!("{} ", self.0)
145     }
146 }
147
148 impl<'a> CrateVersion<'a> {
149     /// Returns the struct and whether or not the dep is in-tree
150     pub fn from_str(s: &'a str) -> (Self, bool) {
151         let mut parts = s.split(" ");
152         let name = parts.next().unwrap();
153         let version = parts.next().unwrap();
154         let path = parts.next().unwrap();
155
156         let is_path_dep = path.starts_with("(path+");
157
158         (CrateVersion(name, version), is_path_dep)
159     }
160
161     pub fn id_str(&self) -> String {
162         format!("{} {}", self.0, self.1)
163     }
164 }
165
166 impl<'a> From<CrateVersion<'a>> for Crate<'a> {
167     fn from(cv: CrateVersion<'a>) -> Crate<'a> {
168         Crate(cv.0)
169     }
170 }
171
172 /// Checks the dependency at the given path. Changes `bad` to `true` if a check failed.
173 ///
174 /// Specifically, this checks that the license is correct.
175 pub fn check(path: &Path, bad: &mut bool) {
176     // Check licences
177     let path = path.join("vendor");
178     assert!(path.exists(), "vendor directory missing");
179     let mut saw_dir = false;
180     for dir in t!(path.read_dir()) {
181         saw_dir = true;
182         let dir = t!(dir);
183
184         // skip our exceptions
185         if EXCEPTIONS.iter().any(|exception| {
186             dir.path()
187                 .to_str()
188                 .unwrap()
189                 .contains(&format!("src/vendor/{}", exception))
190         }) {
191             continue;
192         }
193
194         let toml = dir.path().join("Cargo.toml");
195         *bad = *bad || !check_license(&toml);
196     }
197     assert!(saw_dir, "no vendored source");
198 }
199
200 /// Checks the dependency of WHITELIST_CRATES at the given path. Changes `bad` to `true` if a check
201 /// failed.
202 ///
203 /// Specifically, this checks that the dependencies are on the WHITELIST.
204 pub fn check_whitelist(path: &Path, cargo: &Path, bad: &mut bool) {
205     // Get dependencies from cargo metadata
206     let resolve = get_deps(path, cargo);
207
208     // Get the whitelist into a convenient form
209     let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
210
211     // Check dependencies
212     let mut visited = BTreeSet::new();
213     let mut unapproved = BTreeSet::new();
214     for &krate in WHITELIST_CRATES.iter() {
215         let mut bad = check_crate_whitelist(&whitelist, &resolve, &mut visited, krate, false);
216         unapproved.append(&mut bad);
217     }
218
219     if unapproved.len() > 0 {
220         println!("Dependencies not on the whitelist:");
221         for dep in unapproved {
222             println!("* {}", dep.id_str());
223         }
224         *bad = true;
225     }
226 }
227
228 fn check_license(path: &Path) -> bool {
229     if !path.exists() {
230         panic!("{} does not exist", path.display());
231     }
232     let mut contents = String::new();
233     t!(t!(File::open(path)).read_to_string(&mut contents));
234
235     let mut found_license = false;
236     for line in contents.lines() {
237         if !line.starts_with("license") {
238             continue;
239         }
240         let license = extract_license(line);
241         if !LICENSES.contains(&&*license) {
242             println!("invalid license {} in {}", license, path.display());
243             return false;
244         }
245         found_license = true;
246         break;
247     }
248     if !found_license {
249         println!("no license in {}", path.display());
250         return false;
251     }
252
253     true
254 }
255
256 fn extract_license(line: &str) -> String {
257     let first_quote = line.find('"');
258     let last_quote = line.rfind('"');
259     if let (Some(f), Some(l)) = (first_quote, last_quote) {
260         let license = &line[f + 1..l];
261         license.into()
262     } else {
263         "bad-license-parse".into()
264     }
265 }
266
267 /// Get the dependencies of the crate at the given path using `cargo metadata`.
268 fn get_deps(path: &Path, cargo: &Path) -> Resolve {
269     // Run `cargo metadata` to get the set of dependencies
270     let output = Command::new(cargo)
271         .arg("metadata")
272         .arg("--format-version")
273         .arg("1")
274         .arg("--manifest-path")
275         .arg(path.join("Cargo.toml"))
276         .output()
277         .expect("Unable to run `cargo metadata`")
278         .stdout;
279     let output = String::from_utf8_lossy(&output);
280     let output: Output = serde_json::from_str(&output).unwrap();
281
282     output.resolve
283 }
284
285 /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
286 /// the whitelist. Returns a list of illegal dependencies.
287 fn check_crate_whitelist<'a, 'b>(
288     whitelist: &'a HashSet<Crate>,
289     resolve: &'a Resolve,
290     visited: &'b mut BTreeSet<CrateVersion<'a>>,
291     krate: CrateVersion<'a>,
292     must_be_on_whitelist: bool,
293 ) -> BTreeSet<Crate<'a>> {
294     // Will contain bad deps
295     let mut unapproved = BTreeSet::new();
296
297     // Check if we have already visited this crate
298     if visited.contains(&krate) {
299         return unapproved;
300     }
301
302     visited.insert(krate);
303
304     // If this path is in-tree, we don't require it to be on the whitelist
305     if must_be_on_whitelist {
306         // If this dependency is not on the WHITELIST, add to bad set
307         if !whitelist.contains(&krate.into()) {
308             unapproved.insert(krate.into());
309         }
310     }
311
312     // Do a DFS in the crate graph (it's a DAG, so we know we have no cycles!)
313     let to_check = resolve
314         .nodes
315         .iter()
316         .find(|n| n.id.starts_with(&krate.id_str()))
317         .expect("crate does not exist");
318
319     for dep in to_check.dependencies.iter() {
320         let (krate, is_path_dep) = CrateVersion::from_str(dep);
321
322         let mut bad = check_crate_whitelist(whitelist, resolve, visited, krate, !is_path_dep);
323         unapproved.append(&mut bad);
324     }
325
326     unapproved
327 }