Skip to content

Commit c2b02b3

Browse files
committed
Updates to future-incompatible reporting.
1 parent f2c22a3 commit c2b02b3

File tree

5 files changed

+373
-174
lines changed

5 files changed

+373
-174
lines changed

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ itertools = "0.10.0"
7373
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
7474
# for more information.
7575
rustc-workspace-hack = "1.0.0"
76-
rand = "0.8.3"
7776

7877
[target.'cfg(windows)'.dependencies]
7978
fwdansi = "1.1.0"

src/bin/cargo/commands/report.rs

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use crate::command_prelude::*;
2-
use anyhow::{anyhow, Context as _};
3-
use cargo::core::compiler::future_incompat::{OnDiskReport, FUTURE_INCOMPAT_FILE};
4-
use cargo::drop_eprint;
5-
use std::io::Read;
2+
use anyhow::anyhow;
3+
use cargo::core::compiler::future_incompat::OnDiskReports;
4+
use cargo::drop_println;
65

76
pub fn cli() -> App {
87
subcommand("report")
@@ -17,8 +16,7 @@ pub fn cli() -> App {
1716
"id",
1817
"identifier of the report generated by a Cargo command invocation",
1918
)
20-
.value_name("id")
21-
.required(true),
19+
.value_name("id"),
2220
),
2321
)
2422
}
@@ -35,31 +33,11 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
3533

3634
fn report_future_incompatibilies(config: &Config, args: &ArgMatches<'_>) -> CliResult {
3735
let ws = args.workspace(config)?;
38-
let report_file = ws.target_dir().open_ro(
39-
FUTURE_INCOMPAT_FILE,
40-
ws.config(),
41-
"Future incompatible report",
42-
)?;
43-
44-
let mut file_contents = String::new();
45-
report_file
46-
.file()
47-
.read_to_string(&mut file_contents)
48-
.with_context(|| "failed to read report")?;
49-
let on_disk_report: OnDiskReport =
50-
serde_json::from_str(&file_contents).with_context(|| "failed to load report")?;
51-
52-
let id = args.value_of("id").unwrap();
53-
if id != on_disk_report.id {
54-
return Err(anyhow!(
55-
"Expected an id of `{}`, but `{}` was provided on the command line. \
56-
Your report may have been overwritten by a different one.",
57-
on_disk_report.id,
58-
id
59-
)
60-
.into());
61-
}
62-
63-
drop_eprint!(config, "{}", on_disk_report.report);
36+
let reports = OnDiskReports::load(&ws)?;
37+
let id = args
38+
.value_of_u32("id")?
39+
.unwrap_or_else(|| reports.last_id());
40+
let report = reports.get_report(id, config)?;
41+
drop_println!(config, "{}", report);
6442
Ok(())
6543
}

src/cargo/core/compiler/future_incompat.rs

Lines changed: 173 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
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};
16
use serde::{Deserialize, Serialize};
7+
use std::io::{Read, Write};
28

39
/// The future incompatibility report, emitted by the compiler as a JSON message.
410
#[derive(serde::Deserialize)]
511
pub struct FutureIncompatReport {
612
pub future_incompat_report: Vec<FutureBreakageItem>,
713
}
814

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.
922
#[derive(Serialize, Deserialize)]
1023
pub struct FutureBreakageItem {
1124
/// The date at which this lint will become an error.
@@ -24,13 +37,166 @@ pub struct Diagnostic {
2437

2538
/// The filename in the top-level `target` directory where we store
2639
/// 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+
}
2855

56+
/// A single report for a given compilation session.
2957
#[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
36202
}

0 commit comments

Comments
 (0)