Skip to content

Commit 1733a61

Browse files
committed
Auto merge of #48456 - mark-i-m:whitelist, r=alexcrichton
Whitelist rustc dependencies As per https://internals.rust-lang.org/t/rustc-dependency-policy/6537/8?u=nikomatsakis cc @alexcrichton @nikomatsakis
2 parents c933440 + e5d2920 commit 1733a61

File tree

6 files changed

+243
-27
lines changed

6 files changed

+243
-27
lines changed

src/Cargo.lock

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bootstrap/test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ impl Step for Tidy {
528528
println!("tidy check ({})", host);
529529
let mut cmd = builder.tool_cmd(Tool::Tidy);
530530
cmd.arg(build.src.join("src"));
531+
cmd.arg(&build.initial_cargo);
531532
if !build.config.vendor {
532533
cmd.arg("--no-vendor");
533534
}

src/tools/tidy/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
name = "tidy"
33
version = "0.1.0"
44
authors = ["Alex Crichton <[email protected]>"]
5+
6+
[dependencies]
7+
serde = "1.0.8"
8+
serde_derive = "1.0.8"
9+
serde_json = "1.0.2"

src/tools/tidy/src/deps.rs

+222-26
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010

1111
//! Check license of third-party deps by inspecting src/vendor
1212
13+
use std::collections::{BTreeSet, HashSet};
1314
use std::fs::File;
1415
use std::io::Read;
1516
use std::path::Path;
17+
use std::process::Command;
18+
19+
use serde_json;
1620

1721
static LICENSES: &'static [&'static str] = &[
1822
"MIT/Apache-2.0",
@@ -24,52 +28,182 @@ static LICENSES: &'static [&'static str] = &[
2428
"Unlicense/MIT",
2529
];
2630

27-
// These are exceptions to Rust's permissive licensing policy, and
28-
// should be considered bugs. Exceptions are only allowed in Rust
29-
// tooling. It is _crucial_ that no exception crates be dependencies
30-
// of the Rust runtime (std / test).
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).
3135
static EXCEPTIONS: &'static [&'static str] = &[
32-
"mdbook", // MPL2, mdbook
33-
"openssl", // BSD+advertising clause, cargo, mdbook
34-
"pest", // MPL2, mdbook via handlebars
35-
"thread-id", // Apache-2.0, mdbook
36-
"toml-query", // MPL-2.0, mdbook
37-
"is-match", // MPL-2.0, mdbook
38-
"cssparser", // MPL-2.0, rustdoc
39-
"smallvec", // MPL-2.0, rustdoc
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
4044
"fuchsia-zircon-sys", // BSD-3-Clause, rustdoc, rustc, cargo
41-
"fuchsia-zircon", // BSD-3-Clause, rustdoc, rustc, cargo (jobserver & tempdir)
42-
"cssparser-macros", // MPL-2.0, rustdoc
43-
"selectors", // MPL-2.0, rustdoc
44-
"clippy_lints", // MPL-2.0 rls
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+
];
50+
51+
/// Which crates to check against the whitelist?
52+
static WHITELIST_CRATES: &'static [CrateVersion] = &[
53+
CrateVersion("rustc", "0.0.0"),
54+
CrateVersion("rustc_trans", "0.0.0"),
4555
];
4656

57+
/// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
58+
static WHITELIST: &'static [Crate] = &[
59+
Crate("ar"),
60+
Crate("backtrace"),
61+
Crate("backtrace-sys"),
62+
Crate("bitflags"),
63+
Crate("byteorder"),
64+
Crate("cc"),
65+
Crate("cfg-if"),
66+
Crate("cmake"),
67+
Crate("ena"),
68+
Crate("filetime"),
69+
Crate("flate2"),
70+
Crate("fuchsia-zircon"),
71+
Crate("fuchsia-zircon-sys"),
72+
Crate("jobserver"),
73+
Crate("kernel32-sys"),
74+
Crate("lazy_static"),
75+
Crate("libc"),
76+
Crate("log"),
77+
Crate("log_settings"),
78+
Crate("miniz-sys"),
79+
Crate("num_cpus"),
80+
Crate("owning_ref"),
81+
Crate("parking_lot"),
82+
Crate("parking_lot_core"),
83+
Crate("rand"),
84+
Crate("redox_syscall"),
85+
Crate("rustc-demangle"),
86+
Crate("smallvec"),
87+
Crate("stable_deref_trait"),
88+
Crate("tempdir"),
89+
Crate("unicode-width"),
90+
Crate("winapi"),
91+
Crate("winapi-build"),
92+
Crate("winapi-i686-pc-windows-gnu"),
93+
Crate("winapi-x86_64-pc-windows-gnu"),
94+
];
95+
96+
// Some types for Serde to deserialize the output of `cargo metadata` to...
97+
98+
#[derive(Deserialize)]
99+
struct Output {
100+
resolve: Resolve,
101+
}
102+
103+
#[derive(Deserialize)]
104+
struct Resolve {
105+
nodes: Vec<ResolveNode>,
106+
}
107+
108+
#[derive(Deserialize)]
109+
struct ResolveNode {
110+
id: String,
111+
dependencies: Vec<String>,
112+
}
113+
114+
/// A unique identifier for a crate
115+
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
116+
struct Crate<'a>(&'a str); // (name,)
117+
118+
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
119+
struct CrateVersion<'a>(&'a str, &'a str); // (name, version)
120+
121+
impl<'a> Crate<'a> {
122+
pub fn id_str(&self) -> String {
123+
format!("{} ", self.0)
124+
}
125+
}
126+
127+
impl<'a> CrateVersion<'a> {
128+
/// Returns the struct and whether or not the dep is in-tree
129+
pub fn from_str(s: &'a str) -> (Self, bool) {
130+
let mut parts = s.split(" ");
131+
let name = parts.next().unwrap();
132+
let version = parts.next().unwrap();
133+
let path = parts.next().unwrap();
134+
135+
let is_path_dep = path.starts_with("(path+");
136+
137+
(CrateVersion(name, version), is_path_dep)
138+
}
139+
140+
pub fn id_str(&self) -> String {
141+
format!("{} {}", self.0, self.1)
142+
}
143+
}
144+
145+
impl<'a> From<CrateVersion<'a>> for Crate<'a> {
146+
fn from(cv: CrateVersion<'a>) -> Crate<'a> {
147+
Crate(cv.0)
148+
}
149+
}
150+
151+
/// Checks the dependency at the given path. Changes `bad` to `true` if a check failed.
152+
///
153+
/// Specifically, this checks that the license is correct.
47154
pub fn check(path: &Path, bad: &mut bool) {
155+
// Check licences
48156
let path = path.join("vendor");
49157
assert!(path.exists(), "vendor directory missing");
50158
let mut saw_dir = false;
51-
'next_path: for dir in t!(path.read_dir()) {
159+
for dir in t!(path.read_dir()) {
52160
saw_dir = true;
53161
let dir = t!(dir);
54162

55163
// skip our exceptions
56-
for exception in EXCEPTIONS {
57-
if dir.path()
164+
if EXCEPTIONS.iter().any(|exception| {
165+
dir.path()
58166
.to_str()
59167
.unwrap()
60-
.contains(&format!("src/vendor/{}", exception)) {
61-
continue 'next_path;
62-
}
168+
.contains(&format!("src/vendor/{}", exception))
169+
}) {
170+
continue;
63171
}
64172

65173
let toml = dir.path().join("Cargo.toml");
66-
if !check_license(&toml) {
67-
*bad = true;
68-
}
174+
*bad = *bad || !check_license(&toml);
69175
}
70176
assert!(saw_dir, "no vendored source");
71177
}
72178

179+
/// Checks the dependency of WHITELIST_CRATES at the given path. Changes `bad` to `true` if a check
180+
/// failed.
181+
///
182+
/// Specifically, this checks that the dependencies are on the WHITELIST.
183+
pub fn check_whitelist(path: &Path, cargo: &Path, bad: &mut bool) {
184+
// Get dependencies from cargo metadata
185+
let resolve = get_deps(path, cargo);
186+
187+
// Get the whitelist into a convenient form
188+
let whitelist: HashSet<_> = WHITELIST.iter().cloned().collect();
189+
190+
// Check dependencies
191+
let mut visited = BTreeSet::new();
192+
let mut unapproved = BTreeSet::new();
193+
for &krate in WHITELIST_CRATES.iter() {
194+
let mut bad = check_crate_whitelist(&whitelist, &resolve, &mut visited, krate, false);
195+
unapproved.append(&mut bad);
196+
}
197+
198+
if unapproved.len() > 0 {
199+
println!("Dependencies not on the whitelist:");
200+
for dep in unapproved {
201+
println!("* {}", dep.id_str());
202+
}
203+
*bad = true;
204+
}
205+
}
206+
73207
fn check_license(path: &Path) -> bool {
74208
if !path.exists() {
75209
panic!("{} does not exist", path.display());
@@ -102,9 +236,71 @@ fn extract_license(line: &str) -> String {
102236
let first_quote = line.find('"');
103237
let last_quote = line.rfind('"');
104238
if let (Some(f), Some(l)) = (first_quote, last_quote) {
105-
let license = &line[f + 1 .. l];
239+
let license = &line[f + 1..l];
106240
license.into()
107241
} else {
108242
"bad-license-parse".into()
109243
}
110244
}
245+
246+
/// Get the dependencies of the crate at the given path using `cargo metadata`.
247+
fn get_deps(path: &Path, cargo: &Path) -> Resolve {
248+
// Run `cargo metadata` to get the set of dependencies
249+
let output = Command::new(cargo)
250+
.arg("metadata")
251+
.arg("--format-version")
252+
.arg("1")
253+
.arg("--manifest-path")
254+
.arg(path.join("Cargo.toml"))
255+
.output()
256+
.expect("Unable to run `cargo metadata`")
257+
.stdout;
258+
let output = String::from_utf8_lossy(&output);
259+
let output: Output = serde_json::from_str(&output).unwrap();
260+
261+
output.resolve
262+
}
263+
264+
/// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
265+
/// the whitelist. Returns a list of illegal dependencies.
266+
fn check_crate_whitelist<'a, 'b>(
267+
whitelist: &'a HashSet<Crate>,
268+
resolve: &'a Resolve,
269+
visited: &'b mut BTreeSet<CrateVersion<'a>>,
270+
krate: CrateVersion<'a>,
271+
must_be_on_whitelist: bool,
272+
) -> BTreeSet<Crate<'a>> {
273+
// Will contain bad deps
274+
let mut unapproved = BTreeSet::new();
275+
276+
// Check if we have already visited this crate
277+
if visited.contains(&krate) {
278+
return unapproved;
279+
}
280+
281+
visited.insert(krate);
282+
283+
// If this path is in-tree, we don't require it to be on the whitelist
284+
if must_be_on_whitelist {
285+
// If this dependency is not on the WHITELIST, add to bad set
286+
if !whitelist.contains(&krate.into()) {
287+
unapproved.insert(krate.into());
288+
}
289+
}
290+
291+
// Do a DFS in the crate graph (it's a DAG, so we know we have no cycles!)
292+
let to_check = resolve
293+
.nodes
294+
.iter()
295+
.find(|n| n.id.starts_with(&krate.id_str()))
296+
.expect("crate does not exist");
297+
298+
for dep in to_check.dependencies.iter() {
299+
let (krate, is_path_dep) = CrateVersion::from_str(dep);
300+
301+
let mut bad = check_crate_whitelist(whitelist, resolve, visited, krate, !is_path_dep);
302+
unapproved.append(&mut bad);
303+
}
304+
305+
unapproved
306+
}

src/tools/tidy/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
1616
#![deny(warnings)]
1717

18+
extern crate serde;
19+
extern crate serde_json;
20+
#[macro_use]
21+
extern crate serde_derive;
22+
1823
use std::fs;
1924

2025
use std::path::Path;

src/tools/tidy/src/main.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ use std::path::PathBuf;
2424
use std::env;
2525

2626
fn main() {
27-
let path = env::args_os().skip(1).next().expect("need an argument");
27+
let path = env::args_os().skip(1).next().expect("need path to src");
2828
let path = PathBuf::from(path);
2929

30+
let cargo = env::args_os().skip(2).next().expect("need path to cargo");
31+
let cargo = PathBuf::from(cargo);
32+
3033
let args: Vec<String> = env::args().skip(1).collect();
3134

3235
let mut bad = false;
@@ -41,6 +44,7 @@ fn main() {
4144
if !args.iter().any(|s| *s == "--no-vendor") {
4245
deps::check(&path, &mut bad);
4346
}
47+
deps::check_whitelist(&path, &cargo, &mut bad);
4448

4549
if bad {
4650
eprintln!("some tidy checks failed");

0 commit comments

Comments
 (0)