4949//! The current scheduling algorithm is relatively primitive and could likely be
5050//! improved.
5151
52- use std:: cell:: Cell ;
52+ use std:: cell:: { Cell , RefCell } ;
5353use std:: collections:: { BTreeMap , BTreeSet , HashMap , HashSet } ;
54+ use std:: fmt:: Write as _;
5455use std:: io;
5556use std:: marker;
5657use std:: sync:: Arc ;
@@ -125,6 +126,14 @@ struct DrainState<'cfg> {
125126
126127 queue : DependencyQueue < Unit , Artifact , Job > ,
127128 messages : Arc < Queue < Message > > ,
129+ /// Diagnostic deduplication support.
130+ diag_dedupe : DiagDedupe < ' cfg > ,
131+ /// Count of warnings, used to print a summary after the job succeeds.
132+ ///
133+ /// First value is the total number of warnings, and the second value is
134+ /// the number that were suppressed because they were duplicates of a
135+ /// previous warning.
136+ warning_count : HashMap < JobId , ( usize , usize ) > ,
128137 active : HashMap < JobId , Unit > ,
129138 compiled : HashSet < PackageId > ,
130139 documented : HashSet < PackageId > ,
@@ -174,7 +183,7 @@ impl std::fmt::Display for JobId {
174183///
175184/// The job may execute on either a dedicated thread or the main thread. If the job executes on the
176185/// main thread, the `output` field must be set to prevent a deadlock.
177- pub struct JobState < ' a > {
186+ pub struct JobState < ' a , ' cfg > {
178187 /// Channel back to the main thread to coordinate messages and such.
179188 ///
180189 /// When the `output` field is `Some`, care must be taken to avoid calling `push_bounded` on
@@ -191,7 +200,7 @@ pub struct JobState<'a> {
191200 /// interleaved. In the future, it may be wrapped in a `Mutex` instead. In this case
192201 /// interleaving is still prevented as the lock would be held for the whole printing of an
193202 /// output message.
194- output : Option < & ' a Config > ,
203+ output : Option < & ' a DiagDedupe < ' cfg > > ,
195204
196205 /// The job id that this state is associated with, used when sending
197206 /// messages back to the main thread.
@@ -207,6 +216,36 @@ pub struct JobState<'a> {
207216 _marker : marker:: PhantomData < & ' a ( ) > ,
208217}
209218
219+ /// Handler for deduplicating diagnostics.
220+ struct DiagDedupe < ' cfg > {
221+ seen : RefCell < HashSet < u64 > > ,
222+ config : & ' cfg Config ,
223+ }
224+
225+ impl < ' cfg > DiagDedupe < ' cfg > {
226+ fn new ( config : & ' cfg Config ) -> Self {
227+ DiagDedupe {
228+ seen : RefCell :: new ( HashSet :: new ( ) ) ,
229+ config,
230+ }
231+ }
232+
233+ /// Emits a diagnostic message.
234+ ///
235+ /// Returns `true` if the message was emitted, or `false` if it was
236+ /// suppressed for being a duplicate.
237+ fn emit_diag ( & self , diag : & str ) -> CargoResult < bool > {
238+ let h = util:: hash_u64 ( diag) ;
239+ if !self . seen . borrow_mut ( ) . insert ( h) {
240+ return Ok ( false ) ;
241+ }
242+ let mut shell = self . config . shell ( ) ;
243+ shell. print_ansi_stderr ( diag. as_bytes ( ) ) ?;
244+ shell. err ( ) . write_all ( b"\n " ) ?;
245+ Ok ( true )
246+ }
247+ }
248+
210249/// Possible artifacts that can be produced by compilations, used as edge values
211250/// in the dependency graph.
212251///
@@ -232,6 +271,15 @@ enum Message {
232271 BuildPlanMsg ( String , ProcessBuilder , Arc < Vec < OutputFile > > ) ,
233272 Stdout ( String ) ,
234273 Stderr ( String ) ,
274+ Diagnostic {
275+ id : JobId ,
276+ level : String ,
277+ diag : String ,
278+ } ,
279+ WarningCount {
280+ id : JobId ,
281+ emitted : bool ,
282+ } ,
235283 FixDiagnostic ( diagnostic_server:: Message ) ,
236284 Token ( io:: Result < Acquired > ) ,
237285 Finish ( JobId , Artifact , CargoResult < ( ) > ) ,
@@ -244,7 +292,7 @@ enum Message {
244292 ReleaseToken ( JobId ) ,
245293}
246294
247- impl < ' a > JobState < ' a > {
295+ impl < ' a , ' cfg > JobState < ' a , ' cfg > {
248296 pub fn running ( & self , cmd : & ProcessBuilder ) {
249297 self . messages . push ( Message :: Run ( self . id , cmd. to_string ( ) ) ) ;
250298 }
@@ -260,17 +308,17 @@ impl<'a> JobState<'a> {
260308 }
261309
262310 pub fn stdout ( & self , stdout : String ) -> CargoResult < ( ) > {
263- if let Some ( config ) = self . output {
264- writeln ! ( config. shell( ) . out( ) , "{}" , stdout) ?;
311+ if let Some ( dedupe ) = self . output {
312+ writeln ! ( dedupe . config. shell( ) . out( ) , "{}" , stdout) ?;
265313 } else {
266314 self . messages . push_bounded ( Message :: Stdout ( stdout) ) ;
267315 }
268316 Ok ( ( ) )
269317 }
270318
271319 pub fn stderr ( & self , stderr : String ) -> CargoResult < ( ) > {
272- if let Some ( config ) = self . output {
273- let mut shell = config. shell ( ) ;
320+ if let Some ( dedupe ) = self . output {
321+ let mut shell = dedupe . config . shell ( ) ;
274322 shell. print_ansi_stderr ( stderr. as_bytes ( ) ) ?;
275323 shell. err ( ) . write_all ( b"\n " ) ?;
276324 } else {
@@ -279,6 +327,25 @@ impl<'a> JobState<'a> {
279327 Ok ( ( ) )
280328 }
281329
330+ pub fn emit_diag ( & self , level : String , diag : String ) -> CargoResult < ( ) > {
331+ if let Some ( dedupe) = self . output {
332+ let emitted = dedupe. emit_diag ( & diag) ?;
333+ if level == "warning" {
334+ self . messages . push ( Message :: WarningCount {
335+ id : self . id ,
336+ emitted,
337+ } ) ;
338+ }
339+ } else {
340+ self . messages . push_bounded ( Message :: Diagnostic {
341+ id : self . id ,
342+ level,
343+ diag,
344+ } ) ;
345+ }
346+ Ok ( ( ) )
347+ }
348+
282349 /// A method used to signal to the coordinator thread that the rmeta file
283350 /// for an rlib has been produced. This is only called for some rmeta
284351 /// builds when required, and can be called at any time before a job ends.
@@ -410,6 +477,8 @@ impl<'cfg> JobQueue<'cfg> {
410477 // typical messages. If you change this, please update the test
411478 // caching_large_output, too.
412479 messages : Arc :: new ( Queue :: new ( 100 ) ) ,
480+ diag_dedupe : DiagDedupe :: new ( cx. bcx . config ) ,
481+ warning_count : HashMap :: new ( ) ,
413482 active : HashMap :: new ( ) ,
414483 compiled : HashSet :: new ( ) ,
415484 documented : HashSet :: new ( ) ,
@@ -563,6 +632,15 @@ impl<'cfg> DrainState<'cfg> {
563632 shell. print_ansi_stderr ( err. as_bytes ( ) ) ?;
564633 shell. err ( ) . write_all ( b"\n " ) ?;
565634 }
635+ Message :: Diagnostic { id, level, diag } => {
636+ let emitted = self . diag_dedupe . emit_diag ( & diag) ?;
637+ if level == "warning" {
638+ self . bump_warning_count ( id, emitted) ;
639+ }
640+ }
641+ Message :: WarningCount { id, emitted } => {
642+ self . bump_warning_count ( id, emitted) ;
643+ }
566644 Message :: FixDiagnostic ( msg) => {
567645 self . print . print ( & msg) ?;
568646 }
@@ -586,6 +664,7 @@ impl<'cfg> DrainState<'cfg> {
586664 self . tokens . extend ( rustc_tokens) ;
587665 }
588666 self . to_send_clients . remove ( & id) ;
667+ self . report_warning_count ( cx. bcx . config , id) ;
589668 self . active . remove ( & id) . unwrap ( )
590669 }
591670 // ... otherwise if it hasn't finished we leave it
@@ -936,7 +1015,7 @@ impl<'cfg> DrainState<'cfg> {
9361015 let fresh = job. freshness ( ) ;
9371016 let rmeta_required = cx. rmeta_required ( unit) ;
9381017
939- let doit = move |state : JobState < ' _ > | {
1018+ let doit = move |state : JobState < ' _ , ' _ > | {
9401019 let mut sender = FinishOnDrop {
9411020 messages : & state. messages ,
9421021 id,
@@ -992,7 +1071,7 @@ impl<'cfg> DrainState<'cfg> {
9921071 doit ( JobState {
9931072 id,
9941073 messages,
995- output : Some ( cx . bcx . config ) ,
1074+ output : Some ( & self . diag_dedupe ) ,
9961075 rmeta_required : Cell :: new ( rmeta_required) ,
9971076 _marker : marker:: PhantomData ,
9981077 } ) ;
@@ -1044,6 +1123,44 @@ impl<'cfg> DrainState<'cfg> {
10441123 Ok ( ( ) )
10451124 }
10461125
1126+ fn bump_warning_count ( & mut self , id : JobId , emitted : bool ) {
1127+ let cnts = self . warning_count . entry ( id) . or_default ( ) ;
1128+ cnts. 0 += 1 ;
1129+ if !emitted {
1130+ cnts. 1 += 1 ;
1131+ }
1132+ }
1133+
1134+ /// Displays a final report of the warnings emitted by a particular job.
1135+ fn report_warning_count ( & mut self , config : & Config , id : JobId ) {
1136+ let count = match self . warning_count . remove ( & id) {
1137+ Some ( count) => count,
1138+ None => return ,
1139+ } ;
1140+ let unit = & self . active [ & id] ;
1141+ let mut message = format ! ( "`{}` ({}" , unit. pkg. name( ) , unit. target. description_named( ) ) ;
1142+ if unit. mode . is_rustc_test ( ) && !( unit. target . is_test ( ) || unit. target . is_bench ( ) ) {
1143+ message. push_str ( " test" ) ;
1144+ } else if unit. mode . is_doc_test ( ) {
1145+ message. push_str ( " doctest" ) ;
1146+ } else if unit. mode . is_doc ( ) {
1147+ message. push_str ( " doc" ) ;
1148+ }
1149+ message. push_str ( ") generated " ) ;
1150+ match count. 0 {
1151+ 1 => message. push_str ( "1 warning" ) ,
1152+ n => drop ( write ! ( message, "{} warnings" , n) ) ,
1153+ } ;
1154+ match count. 1 {
1155+ 0 => { }
1156+ 1 => message. push_str ( " (1 duplicate)" ) ,
1157+ n => drop ( write ! ( message, " ({} duplicates)" , n) ) ,
1158+ }
1159+ // Errors are ignored here because it is tricky to handle them
1160+ // correctly, and they aren't important.
1161+ drop ( config. shell ( ) . warn ( message) ) ;
1162+ }
1163+
10471164 fn finish (
10481165 & mut self ,
10491166 id : JobId ,
0 commit comments