1 //! Checks the licenses of third-party dependencies by inspecting vendors.
3 use std
::collections
::{BTreeSet, HashSet, HashMap}
;
6 use std
::process
::Command
;
8 use serde
::Deserialize
;
11 const LICENSES
: &[&str] = &[
18 "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", // wasi license
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] = &[
29 "mdbook", // MPL2, mdbook
30 "openssl", // BSD+advertising clause, cargo, mdbook
31 "pest", // MPL2, mdbook via handlebars
32 "arrayref", // BSD-2-Clause, mdbook via handlebars via pest
33 "thread-id", // Apache-2.0, mdbook
34 "toml-query", // MPL-2.0, mdbook
35 "is-match", // MPL-2.0, mdbook
36 "cssparser", // MPL-2.0, rustdoc
37 "smallvec", // MPL-2.0, rustdoc
38 "rdrand", // ISC, mdbook, rustfmt
39 "fuchsia-cprng", // BSD-3-Clause, mdbook, rustfmt
40 "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 "colored", // MPL-2.0, rustfmt
46 "ordslice", // Apache-2.0, rls
47 "cloudabi", // BSD-2-Clause, (rls -> crossbeam-channel 0.2 -> rand 0.5)
48 "ryu", // Apache-2.0, rls/cargo/... (because of serde)
49 "bytesize", // Apache-2.0, cargo
50 "im-rc", // MPL-2.0+, cargo
51 "adler32", // BSD-3-Clause AND Zlib, cargo dep that isn't used
52 "constant_time_eq", // CC0-1.0, rustfmt
53 "utf8parse", // Apache-2.0 OR MIT, cargo via strip-ansi-escapes
54 "vte", // Apache-2.0 OR MIT, cargo via strip-ansi-escapes
55 "sized-chunks", // MPL-2.0+, cargo via im-rc
56 "bitmaps", // MPL-2.0+, cargo via im-rc
57 // FIXME: this dependency violates the documentation comment above:
58 "fortanix-sgx-abi", // MPL-2.0+, libstd but only for `sgx` target
59 "dunce", // CC0-1.0 mdbook-linkcheck
60 "codespan-reporting", // Apache-2.0 mdbook-linkcheck
61 "codespan", // Apache-2.0 mdbook-linkcheck
64 /// Which crates to check against the whitelist?
65 const WHITELIST_CRATES
: &[CrateVersion
<'_
>] = &[
66 CrateVersion("rustc", "0.0.0"),
67 CrateVersion("rustc_codegen_llvm", "0.0.0"),
70 /// Whitelist of crates rustc is allowed to depend on. Avoid adding to the list if possible.
71 const WHITELIST
: &[Crate
<'_
>] = &[
73 Crate("aho-corasick"),
74 Crate("annotate-snippets"),
80 Crate("backtrace-sys"),
87 Crate("chalk-engine"),
88 Crate("chalk-macros"),
91 Crate("compiler_builtins"),
94 Crate("crossbeam-deque"),
95 Crate("crossbeam-epoch"),
96 Crate("crossbeam-queue"),
97 Crate("crossbeam-utils"),
105 Crate("fortanix-sgx-abi"),
106 Crate("fuchsia-zircon"),
107 Crate("fuchsia-zircon-sys"),
115 Crate("kernel32-sys"),
116 Crate("lazy_static"),
121 Crate("log_settings"),
127 Crate("miniz_oxide"),
128 Crate("miniz_oxide_c_api"),
132 Crate("parking_lot"),
133 Crate("parking_lot_core"),
135 Crate("polonius-engine"),
137 Crate("proc-macro2"),
139 Crate("quick-error"),
142 Crate("rand_chacha"),
147 Crate("rand_xorshift"),
148 Crate("redox_syscall"),
149 Crate("redox_termios"),
151 Crate("regex-syntax"),
152 Crate("remove_dir_all"),
153 Crate("rustc-demangle"),
155 Crate("rustc-rayon"),
156 Crate("rustc-rayon-core"),
157 Crate("rustc_version"),
161 Crate("semver-parser"),
163 Crate("serde_derive"),
165 Crate("stable_deref_trait"),
167 Crate("synstructure"),
173 Crate("thread_local"),
175 Crate("unicode-width"),
176 Crate("unicode-xid"),
177 Crate("unreachable"),
178 Crate("utf8-ranges"),
180 Crate("version_check"),
184 Crate("winapi-build"),
185 Crate("winapi-i686-pc-windows-gnu"),
186 Crate("winapi-util"),
187 Crate("winapi-x86_64-pc-windows-gnu"),
192 // Some types for Serde to deserialize the output of `cargo metadata` to.
194 #[derive(Deserialize)]
199 #[derive(Deserialize)]
201 nodes
: Vec
<ResolveNode
>,
204 #[derive(Deserialize)]
207 dependencies
: Vec
<String
>,
210 /// A unique identifier for a crate.
211 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
212 struct Crate
<'a
>(&'a
str); // (name)
214 #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug, Hash)]
215 struct CrateVersion
<'a
>(&'a
str, &'a
str); // (name, version)
218 pub fn id_str(&self) -> String
{
219 format
!("{} ", self.0)
223 impl<'a
> CrateVersion
<'a
> {
224 /// Returns the struct and whether or not the dependency is in-tree.
225 pub fn from_str(s
: &'a
str) -> (Self, bool
) {
226 let mut parts
= s
.split(' '
);
227 let name
= parts
.next().unwrap();
228 let version
= parts
.next().unwrap();
229 let path
= parts
.next().unwrap();
231 let is_path_dep
= path
.starts_with("(path+");
233 (CrateVersion(name
, version
), is_path_dep
)
236 pub fn id_str(&self) -> String
{
237 format
!("{} {}", self.0, self.1)
241 impl<'a
> From
<CrateVersion
<'a
>> for Crate
<'a
> {
242 fn from(cv
: CrateVersion
<'a
>) -> Crate
<'a
> {
247 /// Checks the dependency at the given path. Changes `bad` to `true` if a check failed.
249 /// Specifically, this checks that the license is correct.
250 pub fn check(path
: &Path
, bad
: &mut bool
) {
252 let path
= path
.join("../vendor");
253 assert
!(path
.exists(), "vendor directory missing");
254 let mut saw_dir
= false;
255 for dir
in t
!(path
.read_dir()) {
259 // Skip our exceptions.
260 let is_exception
= EXCEPTIONS
.iter().any(|exception
| {
264 .contains(&format
!("vendor/{}", exception
))
270 let toml
= dir
.path().join("Cargo.toml");
271 *bad
= !check_license(&toml
) || *bad
;
273 assert
!(saw_dir
, "no vendored source");
276 /// Checks the dependency of `WHITELIST_CRATES` at the given path. Changes `bad` to `true` if a
279 /// Specifically, this checks that the dependencies are on the `WHITELIST`.
280 pub fn check_whitelist(path
: &Path
, cargo
: &Path
, bad
: &mut bool
) {
281 // Get dependencies from Cargo metadata.
282 let resolve
= get_deps(path
, cargo
);
284 // Get the whitelist in a convenient form.
285 let whitelist
: HashSet
<_
> = WHITELIST
.iter().cloned().collect();
287 // Check dependencies.
288 let mut visited
= BTreeSet
::new();
289 let mut unapproved
= BTreeSet
::new();
290 for &krate
in WHITELIST_CRATES
.iter() {
291 let mut bad
= check_crate_whitelist(&whitelist
, &resolve
, &mut visited
, krate
, false);
292 unapproved
.append(&mut bad
);
295 if !unapproved
.is_empty() {
296 println
!("Dependencies not on the whitelist:");
297 for dep
in unapproved
{
298 println
!("* {}", dep
.id_str());
303 check_crate_duplicate(&resolve
, bad
);
306 fn check_license(path
: &Path
) -> bool
{
308 panic
!("{} does not exist", path
.display());
310 let contents
= t
!(fs
::read_to_string(&path
));
312 let mut found_license
= false;
313 for line
in contents
.lines() {
314 if !line
.starts_with("license") {
317 let license
= extract_license(line
);
318 if !LICENSES
.contains(&&*license
) {
319 println
!("invalid license {} in {}", license
, path
.display());
322 found_license
= true;
326 println
!("no license in {}", path
.display());
333 fn extract_license(line
: &str) -> String
{
334 let first_quote
= line
.find('
"');
335 let last_quote = line.rfind('"'
);
336 if let (Some(f
), Some(l
)) = (first_quote
, last_quote
) {
337 let license
= &line
[f
+ 1..l
];
340 "bad-license-parse".into()
344 /// Gets the dependencies of the crate at the given path using `cargo metadata`.
345 fn get_deps(path
: &Path
, cargo
: &Path
) -> Resolve
{
346 // Run `cargo metadata` to get the set of dependencies.
347 let output
= Command
::new(cargo
)
349 .arg("--format-version")
351 .arg("--manifest-path")
352 .arg(path
.join("../Cargo.toml"))
354 .expect("Unable to run `cargo metadata`")
356 let output
= String
::from_utf8_lossy(&output
);
357 let output
: Output
= serde_json
::from_str(&output
).unwrap();
362 /// Checks the dependencies of the given crate from the given cargo metadata to see if they are on
363 /// the whitelist. Returns a list of illegal dependencies.
364 fn check_crate_whitelist
<'a
>(
365 whitelist
: &'a HashSet
<Crate
<'_
>>,
366 resolve
: &'a Resolve
,
367 visited
: &mut BTreeSet
<CrateVersion
<'a
>>,
368 krate
: CrateVersion
<'a
>,
369 must_be_on_whitelist
: bool
,
370 ) -> BTreeSet
<Crate
<'a
>> {
371 // This will contain bad deps.
372 let mut unapproved
= BTreeSet
::new();
374 // Check if we have already visited this crate.
375 if visited
.contains(&krate
) {
379 visited
.insert(krate
);
381 // If this path is in-tree, we don't require it to be on the whitelist.
382 if must_be_on_whitelist
{
383 // If this dependency is not on `WHITELIST`, add to bad set.
384 if !whitelist
.contains(&krate
.into()) {
385 unapproved
.insert(krate
.into());
389 // Do a DFS in the crate graph (it's a DAG, so we know we have no cycles!).
390 let to_check
= resolve
393 .find(|n
| n
.id
.starts_with(&krate
.id_str()))
394 .expect("crate does not exist");
396 for dep
in to_check
.dependencies
.iter() {
397 let (krate
, is_path_dep
) = CrateVersion
::from_str(dep
);
399 let mut bad
= check_crate_whitelist(whitelist
, resolve
, visited
, krate
, !is_path_dep
);
400 unapproved
.append(&mut bad
);
406 fn check_crate_duplicate(resolve
: &Resolve
, bad
: &mut bool
) {
407 const FORBIDDEN_TO_HAVE_DUPLICATES
: &[&str] = &[
408 // These two crates take quite a long time to build, so don't allow two versions of them
409 // to accidentally sneak into our dependency graph, in order to ensure we keep our CI times
415 let mut name_to_id
: HashMap
<_
, Vec
<_
>> = HashMap
::new();
416 for node
in resolve
.nodes
.iter() {
417 name_to_id
.entry(node
.id
.split_whitespace().next().unwrap())
422 for name
in FORBIDDEN_TO_HAVE_DUPLICATES
{
423 if name_to_id
[name
].len() <= 1 {
426 println
!("crate `{}` is duplicated in `Cargo.lock`", name
);
427 for id
in name_to_id
[name
].iter() {
428 println
!(" * {}", id
);