@@ -14,9 +14,28 @@ use std::collections::{HashMap, HashSet};
14
14
15
15
pub type AllowedKinds = HashSet < DepKind > ;
16
16
17
+ #[ derive( serde:: Deserialize , Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Ord ) ]
18
+ #[ serde( rename_all = "lowercase" ) ]
19
+ /// Lint levels
20
+ ///
21
+ /// Note that order is important here
22
+ pub enum LintLevel {
23
+ // Allow isn't mentioned as the unused dependencies message
24
+ // isn't emitted if the lint is set to allow.
25
+ Warn ,
26
+ Deny ,
27
+ Forbid ,
28
+ }
29
+
30
+ #[ derive( serde:: Deserialize , Debug ) ]
31
+ pub struct UnusedExterns {
32
+ lint_level : LintLevel ,
33
+ unused_extern_names : Vec < String > ,
34
+ }
35
+
17
36
#[ derive( Default , Clone ) ]
18
37
struct State {
19
- /// All externs of a root unit.
38
+ /// All externs passed to units
20
39
externs : HashMap < InternedString , Option < Dependency > > ,
21
40
/// The used externs so far.
22
41
/// The DepKind is included so that we can tell when
@@ -28,6 +47,8 @@ struct State {
28
47
#[ derive( Clone ) ]
29
48
pub struct UnusedDepState {
30
49
states : HashMap < ( PackageId , Option < DepKind > ) , State > ,
50
+ /// The worst encountered lint level so far
51
+ worst_lint_level : LintLevel ,
31
52
/// Tracking for which units we have received reports from.
32
53
///
33
54
/// When we didn't receive reports, e.g. because of an error,
@@ -152,6 +173,7 @@ impl UnusedDepState {
152
173
153
174
Self {
154
175
states,
176
+ worst_lint_level : LintLevel :: Warn ,
155
177
reports_obtained : HashSet :: new ( ) ,
156
178
}
157
179
}
@@ -161,15 +183,18 @@ impl UnusedDepState {
161
183
& mut self ,
162
184
unit_deps : & [ UnitDep ] ,
163
185
unit : & Unit ,
164
- unused_externs : Vec < String > ,
186
+ unused_externs : UnusedExterns ,
165
187
) {
166
188
self . reports_obtained . insert ( unit. clone ( ) ) ;
189
+ self . worst_lint_level = self . worst_lint_level . max ( unused_externs. lint_level ) ;
190
+
167
191
let usable_deps_iter = unit_deps
168
192
. iter ( )
169
193
// compare with similar check in extern_args
170
194
. filter ( |dep| dep. unit . target . is_linkable ( ) && !dep. unit . mode . is_doc ( ) ) ;
171
195
172
196
let unused_externs_set = unused_externs
197
+ . unused_extern_names
173
198
. iter ( )
174
199
. map ( |ex| InternedString :: new ( ex) )
175
200
. collect :: < HashSet < InternedString > > ( ) ;
@@ -220,89 +245,108 @@ impl UnusedDepState {
220
245
allowed_kinds_or_late
221
246
) ;
222
247
223
- // Sort the states to have a consistent output
224
- let mut states_sorted = self . states . iter ( ) . collect :: < Vec < _ > > ( ) ;
225
- states_sorted. sort_by_key ( |( k, _v) | k. clone ( ) ) ;
226
- for ( ( pkg_id, dep_kind) , state) in states_sorted. iter ( ) {
227
- let outstanding_reports = state
228
- . reports_needed_by
229
- . iter ( )
230
- . filter ( |report| !self . reports_obtained . contains ( report) )
231
- . collect :: < Vec < _ > > ( ) ;
232
- if !outstanding_reports. is_empty ( ) {
233
- trace ! ( "Supressing unused deps warning of pkg {} v{} mode '{}dep' due to outstanding reports {:?}" , pkg_id. name( ) , pkg_id. version( ) , dep_kind_desc( * dep_kind) ,
248
+ let mut error_count = 0 ;
249
+ {
250
+ let mut emit_lint: Box < dyn FnMut ( String ) -> CargoResult < ( ) > > =
251
+ if self . worst_lint_level == LintLevel :: Warn {
252
+ Box :: new ( |msg| config. shell ( ) . warn ( msg) )
253
+ } else {
254
+ Box :: new ( |msg| {
255
+ error_count += 1 ;
256
+ config. shell ( ) . error ( msg)
257
+ } )
258
+ } ;
259
+
260
+ // Sort the states to have a consistent output
261
+ let mut states_sorted = self . states . iter ( ) . collect :: < Vec < _ > > ( ) ;
262
+ states_sorted. sort_by_key ( |( k, _v) | k. clone ( ) ) ;
263
+ for ( ( pkg_id, dep_kind) , state) in states_sorted. iter ( ) {
264
+ let outstanding_reports = state
265
+ . reports_needed_by
266
+ . iter ( )
267
+ . filter ( |report| !self . reports_obtained . contains ( report) )
268
+ . collect :: < Vec < _ > > ( ) ;
269
+ if !outstanding_reports. is_empty ( ) {
270
+ trace ! ( "Supressing unused deps warning of pkg {} v{} mode '{}dep' due to outstanding reports {:?}" , pkg_id. name( ) , pkg_id. version( ) , dep_kind_desc( * dep_kind) ,
234
271
outstanding_reports. iter( ) . map( |unit|
235
272
unit_desc( unit) ) . collect:: <Vec <_>>( ) ) ;
236
273
237
- // Some compilations errored without printing the unused externs.
238
- // Don't print the warning in order to reduce false positive
239
- // spam during errors.
240
- continue ;
241
- }
242
- // Sort the externs to have a consistent output
243
- let mut externs_sorted = state. externs . iter ( ) . collect :: < Vec < _ > > ( ) ;
244
- externs_sorted. sort_by_key ( |( k, _v) | k. clone ( ) ) ;
245
- for ( ext, dependency) in externs_sorted. iter ( ) {
246
- let dep_kind = if let Some ( dep_kind) = dep_kind {
247
- dep_kind
248
- } else {
249
- // Internal dep_kind isn't interesting to us
250
- continue ;
251
- } ;
252
- if state. used_externs . contains ( & ( * * ext, * dep_kind) ) {
253
- // The dependency is used
274
+ // Some compilations errored without printing the unused externs.
275
+ // Don't print the warning in order to reduce false positive
276
+ // spam during errors.
254
277
continue ;
255
278
}
256
- // Implicitly added dependencies (in the same crate) aren't interesting
257
- let dependency = if let Some ( dependency ) = dependency {
258
- dependency
259
- } else {
260
- continue ;
261
- } ;
262
- if let Some ( allowed_kinds ) = allowed_kinds_or_late {
263
- if !allowed_kinds . contains ( dep_kind) {
264
- // We can't warn for dependencies of this target kind
265
- // as we aren't compiling all the units
266
- // that use the dependency kind
267
- trace ! ( "Supressing unused deps warning of {} in pkg {} v{} as mode '{}dep' not allowed" , dependency. name_in_toml ( ) , pkg_id . name ( ) , pkg_id . version ( ) , dep_kind_desc ( Some ( * dep_kind ) ) ) ;
279
+ // Sort the externs to have a consistent output
280
+ let mut externs_sorted = state . externs . iter ( ) . collect :: < Vec < _ > > ( ) ;
281
+ externs_sorted . sort_by_key ( | ( k , _v ) | k . clone ( ) ) ;
282
+ for ( ext , dependency ) in externs_sorted . iter ( ) {
283
+ let dep_kind = if let Some ( dep_kind ) = dep_kind {
284
+ dep_kind
285
+ } else {
286
+ // Internal dep_kind isn't interesting to us
287
+ continue ;
288
+ } ;
289
+ if state . used_externs . contains ( & ( * * ext , * dep_kind ) ) {
290
+ // The dependency is used
268
291
continue ;
269
292
}
270
- } else {
271
- }
272
- if dependency. name_in_toml ( ) . starts_with ( "_" ) {
273
- // Dependencies starting with an underscore
274
- // are marked as ignored
275
- trace ! (
276
- "Supressing unused deps warning of {} in pkg {} v{} due to name" ,
277
- dependency. name_in_toml( ) ,
278
- pkg_id. name( ) ,
279
- pkg_id. version( )
280
- ) ;
281
- continue ;
282
- }
283
- if dep_kind == & DepKind :: Normal
284
- && state. used_externs . contains ( & ( * * ext, DepKind :: Development ) )
285
- {
286
- // The dependency is used but only by dev targets,
287
- // which means it should be a dev-dependency instead
288
- config. shell ( ) . warn ( format ! (
289
- "dependency {} in package {} v{} is only used by dev targets" ,
293
+ // Implicitly added dependencies (in the same crate) aren't interesting
294
+ let dependency = if let Some ( dependency) = dependency {
295
+ dependency
296
+ } else {
297
+ continue ;
298
+ } ;
299
+ if let Some ( allowed_kinds) = allowed_kinds_or_late {
300
+ if !allowed_kinds. contains ( dep_kind) {
301
+ // We can't warn for dependencies of this target kind
302
+ // as we aren't compiling all the units
303
+ // that use the dependency kind
304
+ trace ! ( "Supressing unused deps warning of {} in pkg {} v{} as mode '{}dep' not allowed" , dependency. name_in_toml( ) , pkg_id. name( ) , pkg_id. version( ) , dep_kind_desc( Some ( * dep_kind) ) ) ;
305
+ continue ;
306
+ }
307
+ } else {
308
+ }
309
+ if dependency. name_in_toml ( ) . starts_with ( "_" ) {
310
+ // Dependencies starting with an underscore
311
+ // are marked as ignored
312
+ trace ! (
313
+ "Supressing unused deps warning of {} in pkg {} v{} due to name" ,
314
+ dependency. name_in_toml( ) ,
315
+ pkg_id. name( ) ,
316
+ pkg_id. version( )
317
+ ) ;
318
+ continue ;
319
+ }
320
+ if dep_kind == & DepKind :: Normal
321
+ && state. used_externs . contains ( & ( * * ext, DepKind :: Development ) )
322
+ {
323
+ // The dependency is used but only by dev targets,
324
+ // which means it should be a dev-dependency instead
325
+ emit_lint ( format ! (
326
+ "dependency {} in package {} v{} is only used by dev targets" ,
327
+ dependency. name_in_toml( ) ,
328
+ pkg_id. name( ) ,
329
+ pkg_id. version( )
330
+ ) ) ?;
331
+ continue ;
332
+ }
333
+
334
+ emit_lint ( format ! (
335
+ "unused {}dependency {} in package {} v{}" ,
336
+ dep_kind_desc( Some ( * dep_kind) ) ,
290
337
dependency. name_in_toml( ) ,
291
338
pkg_id. name( ) ,
292
339
pkg_id. version( )
293
340
) ) ?;
294
- continue ;
295
341
}
296
-
297
- config. shell ( ) . warn ( format ! (
298
- "unused {}dependency {} in package {} v{}" ,
299
- dep_kind_desc( Some ( * dep_kind) ) ,
300
- dependency. name_in_toml( ) ,
301
- pkg_id. name( ) ,
302
- pkg_id. version( )
303
- ) ) ?;
304
342
}
305
343
}
344
+ if error_count > 0 {
345
+ anyhow:: bail!(
346
+ "exiting because of {} unused dependencies error(s)" ,
347
+ error_count
348
+ ) ;
349
+ }
306
350
Ok ( ( ) )
307
351
}
308
352
}
0 commit comments