1
+ //! Support for future-incompatible warning reporting.
2
+
3
+ use crate :: core:: { Dependency , PackageId , Workspace } ;
4
+ use crate :: sources:: SourceConfigMap ;
5
+ use crate :: util:: { iter_join, CargoResult , Config } ;
6
+ use anyhow:: { bail, format_err, Context } ;
1
7
use serde:: { Deserialize , Serialize } ;
8
+ use std:: collections:: { BTreeSet , HashMap , HashSet } ;
9
+ use std:: fmt:: Write as _;
10
+ use std:: io:: { Read , Write } ;
11
+
12
+ pub const REPORT_PREAMBLE : & str = "\
13
+ The following warnings were discovered during the build. These warnings are an
14
+ indication that the packages contain code that will become an error in a
15
+ future release of Rust. These warnings typically cover changes to close
16
+ soundness problems, unintended or undocumented behavior, or critical problems
17
+ that cannot be fixed in a backwards-compatible fashion, and are not expected
18
+ to be in wide use.
19
+
20
+ Each warning should contain a link for more information on what the warning
21
+ means and how to resolve it.
22
+ " ;
23
+
24
+ /// Current version of the on-disk format.
25
+ const ON_DISK_VERSION : u32 = 0 ;
2
26
3
27
/// The future incompatibility report, emitted by the compiler as a JSON message.
4
28
#[ derive( serde:: Deserialize ) ]
5
29
pub struct FutureIncompatReport {
6
30
pub future_incompat_report : Vec < FutureBreakageItem > ,
7
31
}
8
32
33
+ /// Structure used for collecting reports in-memory.
34
+ pub struct FutureIncompatReportPackage {
35
+ pub package_id : PackageId ,
36
+ pub items : Vec < FutureBreakageItem > ,
37
+ }
38
+
39
+ /// A single future-incompatible warning emitted by rustc.
9
40
#[ derive( Serialize , Deserialize ) ]
10
41
pub struct FutureBreakageItem {
11
42
/// The date at which this lint will become an error.
@@ -24,13 +55,234 @@ pub struct Diagnostic {
24
55
25
56
/// The filename in the top-level `target` directory where we store
26
57
/// the report
27
- pub const FUTURE_INCOMPAT_FILE : & str = ".future-incompat-report.json" ;
58
+ const FUTURE_INCOMPAT_FILE : & str = ".future-incompat-report.json" ;
59
+ /// Max number of reports to save on disk.
60
+ const MAX_REPORTS : usize = 5 ;
28
61
62
+ /// The structure saved to disk containing the reports.
29
63
#[ derive( Serialize , Deserialize ) ]
30
- pub struct OnDiskReport {
31
- // A Cargo-generated id used to detect when a report has been overwritten
32
- pub id : String ,
33
- // Cannot be a &str, since Serde needs
34
- // to be able to un-escape the JSON
35
- pub report : String ,
64
+ pub struct OnDiskReports {
65
+ /// A schema version number, to handle older cargo's from trying to read
66
+ /// something that they don't understand.
67
+ version : u32 ,
68
+ /// The report ID to use for the next report to save.
69
+ next_id : u32 ,
70
+ /// Available reports.
71
+ reports : Vec < OnDiskReport > ,
72
+ }
73
+
74
+ /// A single report for a given compilation session.
75
+ #[ derive( Serialize , Deserialize ) ]
76
+ struct OnDiskReport {
77
+ /// Unique reference to the report for the `--id` CLI flag.
78
+ id : u32 ,
79
+ /// Report, suitable for printing to the console.
80
+ report : String ,
81
+ }
82
+
83
+ impl Default for OnDiskReports {
84
+ fn default ( ) -> OnDiskReports {
85
+ OnDiskReports {
86
+ version : ON_DISK_VERSION ,
87
+ next_id : 1 ,
88
+ reports : Vec :: new ( ) ,
89
+ }
90
+ }
91
+ }
92
+
93
+ impl OnDiskReports {
94
+ /// Saves a new report.
95
+ pub fn save_report (
96
+ ws : & Workspace < ' _ > ,
97
+ per_package_reports : & [ FutureIncompatReportPackage ] ,
98
+ ) -> OnDiskReports {
99
+ let mut current_reports = match Self :: load ( ws) {
100
+ Ok ( r) => r,
101
+ Err ( e) => {
102
+ log:: debug!(
103
+ "saving future-incompatible reports failed to load current reports: {:?}" ,
104
+ e
105
+ ) ;
106
+ OnDiskReports :: default ( )
107
+ }
108
+ } ;
109
+ let report = OnDiskReport {
110
+ id : current_reports. next_id ,
111
+ report : render_report ( ws, per_package_reports) ,
112
+ } ;
113
+ current_reports. next_id += 1 ;
114
+ current_reports. reports . push ( report) ;
115
+ if current_reports. reports . len ( ) > MAX_REPORTS {
116
+ current_reports. reports . remove ( 0 ) ;
117
+ }
118
+ let on_disk = serde_json:: to_vec ( & current_reports) . unwrap ( ) ;
119
+ if let Err ( e) = ws
120
+ . target_dir ( )
121
+ . open_rw (
122
+ FUTURE_INCOMPAT_FILE ,
123
+ ws. config ( ) ,
124
+ "Future incompatibility report" ,
125
+ )
126
+ . and_then ( |file| {
127
+ let mut file = file. file ( ) ;
128
+ file. set_len ( 0 ) ?;
129
+ file. write_all ( & on_disk) ?;
130
+ Ok ( ( ) )
131
+ } )
132
+ {
133
+ crate :: display_warning_with_error (
134
+ "failed to write on-disk future incompatible report" ,
135
+ & e,
136
+ & mut ws. config ( ) . shell ( ) ,
137
+ ) ;
138
+ }
139
+ current_reports
140
+ }
141
+
142
+ /// Loads the on-disk reports.
143
+ pub fn load ( ws : & Workspace < ' _ > ) -> CargoResult < OnDiskReports > {
144
+ let report_file = match ws. target_dir ( ) . open_ro (
145
+ FUTURE_INCOMPAT_FILE ,
146
+ ws. config ( ) ,
147
+ "Future incompatible report" ,
148
+ ) {
149
+ Ok ( r) => r,
150
+ Err ( e) => {
151
+ if let Some ( io_err) = e. downcast_ref :: < std:: io:: Error > ( ) {
152
+ if io_err. kind ( ) == std:: io:: ErrorKind :: NotFound {
153
+ bail ! ( "no reports are currently available" ) ;
154
+ }
155
+ }
156
+ return Err ( e) ;
157
+ }
158
+ } ;
159
+
160
+ let mut file_contents = String :: new ( ) ;
161
+ report_file
162
+ . file ( )
163
+ . read_to_string ( & mut file_contents)
164
+ . with_context ( || "failed to read report" ) ?;
165
+ let on_disk_reports: OnDiskReports =
166
+ serde_json:: from_str ( & file_contents) . with_context ( || "failed to load report" ) ?;
167
+ if on_disk_reports. version != ON_DISK_VERSION {
168
+ bail ! ( "unable to read reports; reports were saved from a future version of Cargo" ) ;
169
+ }
170
+ Ok ( on_disk_reports)
171
+ }
172
+
173
+ /// Returns the most recent report ID.
174
+ pub fn last_id ( & self ) -> u32 {
175
+ self . reports . last ( ) . map ( |r| r. id ) . unwrap ( )
176
+ }
177
+
178
+ pub fn get_report ( & self , id : u32 , config : & Config ) -> CargoResult < String > {
179
+ let report = self . reports . iter ( ) . find ( |r| r. id == id) . ok_or_else ( || {
180
+ let available = iter_join ( self . reports . iter ( ) . map ( |r| r. id . to_string ( ) ) , ", " ) ;
181
+ format_err ! (
182
+ "could not find report with ID {}\n \
183
+ Available IDs are: {}",
184
+ id,
185
+ available
186
+ )
187
+ } ) ?;
188
+ let report = if config. shell ( ) . err_supports_color ( ) {
189
+ report. report . clone ( )
190
+ } else {
191
+ strip_ansi_escapes:: strip ( & report. report )
192
+ . map ( |v| String :: from_utf8 ( v) . expect ( "utf8" ) )
193
+ . expect ( "strip should never fail" )
194
+ } ;
195
+ Ok ( report)
196
+ }
197
+ }
198
+
199
+ fn render_report (
200
+ ws : & Workspace < ' _ > ,
201
+ per_package_reports : & [ FutureIncompatReportPackage ] ,
202
+ ) -> String {
203
+ let mut per_package_reports: Vec < _ > = per_package_reports. iter ( ) . collect ( ) ;
204
+ per_package_reports. sort_by_key ( |r| r. package_id ) ;
205
+ let mut rendered = String :: new ( ) ;
206
+ for per_package in & per_package_reports {
207
+ rendered. push_str ( & format ! (
208
+ "The package `{}` currently triggers the following future \
209
+ incompatibility lints:\n ",
210
+ per_package. package_id
211
+ ) ) ;
212
+ for item in & per_package. items {
213
+ rendered. extend (
214
+ item. diagnostic
215
+ . rendered
216
+ . lines ( )
217
+ . map ( |l| format ! ( "> {}\n " , l) ) ,
218
+ ) ;
219
+ }
220
+ rendered. push ( '\n' ) ;
221
+ }
222
+ if let Some ( s) = render_suggestions ( ws, & per_package_reports) {
223
+ rendered. push_str ( & s) ;
224
+ }
225
+ rendered
226
+ }
227
+
228
+ fn render_suggestions (
229
+ ws : & Workspace < ' _ > ,
230
+ per_package_reports : & [ & FutureIncompatReportPackage ] ,
231
+ ) -> Option < String > {
232
+ // This in general ignores all errors since this is opportunistic.
233
+ let _lock = ws. config ( ) . acquire_package_cache_lock ( ) . ok ( ) ?;
234
+ // Create a set of updated registry sources.
235
+ let map = SourceConfigMap :: new ( ws. config ( ) ) . ok ( ) ?;
236
+ let package_ids: BTreeSet < _ > = per_package_reports
237
+ . iter ( )
238
+ . map ( |r| r. package_id )
239
+ . filter ( |pkg_id| pkg_id. source_id ( ) . is_registry ( ) )
240
+ . collect ( ) ;
241
+ let source_ids: HashSet < _ > = package_ids
242
+ . iter ( )
243
+ . map ( |pkg_id| pkg_id. source_id ( ) )
244
+ . collect ( ) ;
245
+ let mut sources: HashMap < _ , _ > = source_ids
246
+ . into_iter ( )
247
+ . filter_map ( |sid| {
248
+ let source = map. load ( sid, & HashSet :: new ( ) ) . ok ( ) ?;
249
+ Some ( ( sid, source) )
250
+ } )
251
+ . collect ( ) ;
252
+ // Query the sources for new versions.
253
+ let mut suggestions = String :: new ( ) ;
254
+ for pkg_id in package_ids {
255
+ let source = match sources. get_mut ( & pkg_id. source_id ( ) ) {
256
+ Some ( s) => s,
257
+ None => continue ,
258
+ } ;
259
+ let dep = Dependency :: parse ( pkg_id. name ( ) , None , pkg_id. source_id ( ) ) . ok ( ) ?;
260
+ let summaries = source. query_vec ( & dep) . ok ( ) ?;
261
+ let versions = itertools:: sorted (
262
+ summaries
263
+ . iter ( )
264
+ . map ( |summary| summary. version ( ) )
265
+ . filter ( |version| * version > pkg_id. version ( ) ) ,
266
+ ) ;
267
+ let versions = versions. map ( |version| version. to_string ( ) ) ;
268
+ let versions = iter_join ( versions, ", " ) ;
269
+ if !versions. is_empty ( ) {
270
+ writeln ! (
271
+ suggestions,
272
+ "{} has the following newer versions available: {}" ,
273
+ pkg_id, versions
274
+ )
275
+ . unwrap ( ) ;
276
+ }
277
+ }
278
+ if suggestions. is_empty ( ) {
279
+ None
280
+ } else {
281
+ Some ( format ! (
282
+ "The following packages appear to have newer versions available.\n \
283
+ You may want to consider updating them to a newer version to see if the \
284
+ issue has been fixed.\n \n {}",
285
+ suggestions
286
+ ) )
287
+ }
36
288
}
0 commit comments