1
+ //! Support for future-incompatible warning reporting.
2
+
3
+ use crate :: core:: { PackageId , Workspace } ;
4
+ use crate :: util:: { iter_join, CargoResult , Config } ;
5
+ use anyhow:: { bail, format_err, Context } ;
1
6
use serde:: { Deserialize , Serialize } ;
7
+ use std:: io:: { Read , Write } ;
2
8
3
9
/// The future incompatibility report, emitted by the compiler as a JSON message.
4
10
#[ derive( serde:: Deserialize ) ]
5
11
pub struct FutureIncompatReport {
6
12
pub future_incompat_report : Vec < FutureBreakageItem > ,
7
13
}
8
14
15
+ /// Structure used for collecting reports in-memory.
16
+ pub struct FutureIncompatReportPackage {
17
+ pub package_id : PackageId ,
18
+ pub items : Vec < FutureBreakageItem > ,
19
+ }
20
+
21
+ /// A single future-incompatible warning emitted by rustc.
9
22
#[ derive( Serialize , Deserialize ) ]
10
23
pub struct FutureBreakageItem {
11
24
/// The date at which this lint will become an error.
@@ -24,13 +37,166 @@ pub struct Diagnostic {
24
37
25
38
/// The filename in the top-level `target` directory where we store
26
39
/// the report
27
- pub const FUTURE_INCOMPAT_FILE : & str = ".future-incompat-report.json" ;
40
+ const FUTURE_INCOMPAT_FILE : & str = ".future-incompat-report.json" ;
41
+ /// Max number of reports to save on disk.
42
+ const MAX_REPORTS : usize = 5 ;
43
+
44
+ /// The structure saved to disk containing the reports.
45
+ #[ derive( Serialize , Deserialize ) ]
46
+ pub struct OnDiskReports {
47
+ /// A schema version number, to handle older cargo's from trying to read
48
+ /// something that they don't understand.
49
+ version : u32 ,
50
+ /// The report ID to use for the next report to save.
51
+ next_id : u32 ,
52
+ /// Available reports.
53
+ reports : Vec < OnDiskReport > ,
54
+ }
28
55
56
+ /// A single report for a given compilation session.
29
57
#[ 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 ,
58
+ struct OnDiskReport {
59
+ /// Unique reference to the report for the `--id` CLI flag.
60
+ id : u32 ,
61
+ /// Report, suitable for printing to the console.
62
+ report : String ,
63
+ }
64
+
65
+ impl Default for OnDiskReports {
66
+ fn default ( ) -> OnDiskReports {
67
+ OnDiskReports {
68
+ version : 0 ,
69
+ next_id : 1 ,
70
+ reports : Vec :: new ( ) ,
71
+ }
72
+ }
73
+ }
74
+
75
+ impl OnDiskReports {
76
+ /// Saves a new report.
77
+ pub fn save_report (
78
+ ws : & Workspace < ' _ > ,
79
+ per_package_reports : & [ FutureIncompatReportPackage ] ,
80
+ ) -> OnDiskReports {
81
+ let mut current_reports = match Self :: load ( ws) {
82
+ Ok ( r) => r,
83
+ Err ( e) => {
84
+ log:: debug!(
85
+ "saving future-incompatible reports failed to load current reports: {:?}" ,
86
+ e
87
+ ) ;
88
+ OnDiskReports :: default ( )
89
+ }
90
+ } ;
91
+ let report = OnDiskReport {
92
+ id : current_reports. next_id ,
93
+ report : render_report ( per_package_reports) ,
94
+ } ;
95
+ current_reports. next_id += 1 ;
96
+ current_reports. reports . push ( report) ;
97
+ if current_reports. reports . len ( ) > MAX_REPORTS {
98
+ current_reports. reports . remove ( 0 ) ;
99
+ }
100
+ let on_disk = serde_json:: to_vec ( & current_reports) . unwrap ( ) ;
101
+ if let Err ( e) = ws
102
+ . target_dir ( )
103
+ . open_rw (
104
+ FUTURE_INCOMPAT_FILE ,
105
+ ws. config ( ) ,
106
+ "Future incompatibility report" ,
107
+ )
108
+ . and_then ( |file| {
109
+ let mut file = file. file ( ) ;
110
+ file. set_len ( 0 ) ?;
111
+ file. write_all ( & on_disk) ?;
112
+ Ok ( ( ) )
113
+ } )
114
+ {
115
+ crate :: display_warning_with_error (
116
+ "failed to write on-disk future incompatible report" ,
117
+ & e,
118
+ & mut ws. config ( ) . shell ( ) ,
119
+ ) ;
120
+ }
121
+ current_reports
122
+ }
123
+
124
+ /// Loads the on-disk reports.
125
+ pub fn load ( ws : & Workspace < ' _ > ) -> CargoResult < OnDiskReports > {
126
+ let report_file = match ws. target_dir ( ) . open_ro (
127
+ FUTURE_INCOMPAT_FILE ,
128
+ ws. config ( ) ,
129
+ "Future incompatible report" ,
130
+ ) {
131
+ Ok ( r) => r,
132
+ Err ( e) => {
133
+ if let Some ( io_err) = e. downcast_ref :: < std:: io:: Error > ( ) {
134
+ if io_err. kind ( ) == std:: io:: ErrorKind :: NotFound {
135
+ bail ! ( "no reports are currently available" ) ;
136
+ }
137
+ }
138
+ return Err ( e) ;
139
+ }
140
+ } ;
141
+
142
+ let mut file_contents = String :: new ( ) ;
143
+ report_file
144
+ . file ( )
145
+ . read_to_string ( & mut file_contents)
146
+ . with_context ( || "failed to read report" ) ?;
147
+ let on_disk_reports: OnDiskReports =
148
+ serde_json:: from_str ( & file_contents) . with_context ( || "failed to load report" ) ?;
149
+ if on_disk_reports. version != 0 {
150
+ bail ! ( "unable to read reports; reports were saved from a future version of Cargo" ) ;
151
+ }
152
+ Ok ( on_disk_reports)
153
+ }
154
+
155
+ /// Returns the most recent report ID.
156
+ pub fn last_id ( & self ) -> u32 {
157
+ self . reports . last ( ) . map ( |r| r. id ) . unwrap ( )
158
+ }
159
+
160
+ pub fn get_report ( & self , id : u32 , config : & Config ) -> CargoResult < String > {
161
+ let report = self . reports . iter ( ) . find ( |r| r. id == id) . ok_or_else ( || {
162
+ let available = iter_join ( self . reports . iter ( ) . map ( |r| r. id . to_string ( ) ) , ", " ) ;
163
+ format_err ! (
164
+ "could not find report with ID {}\n \
165
+ Available IDs are: {}",
166
+ id,
167
+ available
168
+ )
169
+ } ) ?;
170
+ let report = if config. shell ( ) . err_supports_color ( ) {
171
+ report. report . clone ( )
172
+ } else {
173
+ strip_ansi_escapes:: strip ( & report. report )
174
+ . map ( |v| String :: from_utf8 ( v) . expect ( "utf8" ) )
175
+ . expect ( "strip should never fail" )
176
+ } ;
177
+ Ok ( report)
178
+ }
179
+ }
180
+
181
+ fn render_report ( per_package_reports : & [ FutureIncompatReportPackage ] ) -> String {
182
+ let mut per_package_reports: Vec < _ > = per_package_reports. iter ( ) . collect ( ) ;
183
+ per_package_reports. sort_by_key ( |r| r. package_id ) ;
184
+ let mut rendered = String :: new ( ) ;
185
+ for per_package in per_package_reports {
186
+ rendered. push_str ( & format ! (
187
+ "The package `{}` currently triggers the following future \
188
+ incompatibility lints:\n ",
189
+ per_package. package_id
190
+ ) ) ;
191
+ for item in & per_package. items {
192
+ rendered. extend (
193
+ item. diagnostic
194
+ . rendered
195
+ . lines ( )
196
+ . map ( |l| format ! ( "> {}\n " , l) ) ,
197
+ ) ;
198
+ }
199
+ rendered. push ( '\n' ) ;
200
+ }
201
+ rendered
36
202
}
0 commit comments