1010
1111//! Check license of third-party deps by inspecting src/vendor
1212
13+ use std:: collections:: { BTreeSet , HashSet } ;
1314use std:: fs:: File ;
1415use std:: io:: Read ;
1516use std:: path:: Path ;
17+ use std:: process:: Command ;
18+
19+ use serde_json;
1620
1721static 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).
3135static 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.
47154pub 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+
73207fn 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+ }
0 commit comments