10
10
11
11
//! Check license of third-party deps by inspecting src/vendor
12
12
13
+ use std:: collections:: { BTreeSet , HashSet } ;
13
14
use std:: fs:: File ;
14
15
use std:: io:: Read ;
15
16
use std:: path:: Path ;
17
+ use std:: process:: Command ;
18
+
19
+ use serde_json;
16
20
17
21
static LICENSES : & ' static [ & ' static str ] = & [
18
22
"MIT/Apache-2.0" ,
@@ -24,52 +28,182 @@ static LICENSES: &'static [&'static str] = &[
24
28
"Unlicense/MIT" ,
25
29
] ;
26
30
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).
31
35
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
40
44
"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" ) ,
45
55
] ;
46
56
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.
47
154
pub fn check ( path : & Path , bad : & mut bool ) {
155
+ // Check licences
48
156
let path = path. join ( "vendor" ) ;
49
157
assert ! ( path. exists( ) , "vendor directory missing" ) ;
50
158
let mut saw_dir = false ;
51
- ' next_path : for dir in t ! ( path. read_dir( ) ) {
159
+ for dir in t ! ( path. read_dir( ) ) {
52
160
saw_dir = true ;
53
161
let dir = t ! ( dir) ;
54
162
55
163
// skip our exceptions
56
- for exception in EXCEPTIONS {
57
- if dir. path ( )
164
+ if EXCEPTIONS . iter ( ) . any ( |exception| {
165
+ dir. path ( )
58
166
. to_str ( )
59
167
. unwrap ( )
60
- . contains ( & format ! ( "src/vendor/{}" , exception) ) {
61
- continue ' next_path ;
62
- }
168
+ . contains ( & format ! ( "src/vendor/{}" , exception) )
169
+ } ) {
170
+ continue ;
63
171
}
64
172
65
173
let toml = dir. path ( ) . join ( "Cargo.toml" ) ;
66
- if !check_license ( & toml) {
67
- * bad = true ;
68
- }
174
+ * bad = * bad || !check_license ( & toml) ;
69
175
}
70
176
assert ! ( saw_dir, "no vendored source" ) ;
71
177
}
72
178
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
+
73
207
fn check_license ( path : & Path ) -> bool {
74
208
if !path. exists ( ) {
75
209
panic ! ( "{} does not exist" , path. display( ) ) ;
@@ -102,9 +236,71 @@ fn extract_license(line: &str) -> String {
102
236
let first_quote = line. find ( '"' ) ;
103
237
let last_quote = line. rfind ( '"' ) ;
104
238
if let ( Some ( f) , Some ( l) ) = ( first_quote, last_quote) {
105
- let license = & line[ f + 1 .. l] ;
239
+ let license = & line[ f + 1 .. l] ;
106
240
license. into ( )
107
241
} else {
108
242
"bad-license-parse" . into ( )
109
243
}
110
244
}
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