Skip to content

Commit 71cb59b

Browse files
committed
Future-incompat report: Add suggestions of newer versions.
1 parent 1f7141f commit 71cb59b

File tree

3 files changed

+163
-6
lines changed

3 files changed

+163
-6
lines changed

src/bin/cargo/commands/report.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::command_prelude::*;
22
use anyhow::anyhow;
3-
use cargo::core::compiler::future_incompat::OnDiskReports;
3+
use cargo::core::compiler::future_incompat::{OnDiskReports, REPORT_PREAMBLE};
44
use cargo::drop_println;
55

66
pub fn cli() -> App {
@@ -39,6 +39,7 @@ fn report_future_incompatibilies(config: &Config, args: &ArgMatches<'_>) -> CliR
3939
.value_of_u32("id")?
4040
.unwrap_or_else(|| reports.last_id());
4141
let report = reports.get_report(id, config)?;
42+
drop_println!(config, "{}", REPORT_PREAMBLE);
4243
drop_println!(config, "{}", report);
4344
Ok(())
4445
}

src/cargo/core/compiler/future_incompat.rs

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
//! Support for future-incompatible warning reporting.
22
3-
use crate::core::{PackageId, Workspace};
3+
use crate::core::{Dependency, PackageId, Workspace};
4+
use crate::sources::SourceConfigMap;
45
use crate::util::{iter_join, CargoResult, Config};
56
use anyhow::{bail, format_err, Context};
67
use serde::{Deserialize, Serialize};
8+
use std::collections::{BTreeSet, HashMap, HashSet};
9+
use std::fmt::Write as _;
710
use std::io::{Read, Write};
811

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+
924
/// The future incompatibility report, emitted by the compiler as a JSON message.
1025
#[derive(serde::Deserialize)]
1126
pub struct FutureIncompatReport {
@@ -90,7 +105,7 @@ impl OnDiskReports {
90105
};
91106
let report = OnDiskReport {
92107
id: current_reports.next_id,
93-
report: render_report(per_package_reports),
108+
report: render_report(ws, per_package_reports),
94109
};
95110
current_reports.next_id += 1;
96111
current_reports.reports.push(report);
@@ -178,11 +193,14 @@ impl OnDiskReports {
178193
}
179194
}
180195

181-
fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> String {
196+
fn render_report(
197+
ws: &Workspace<'_>,
198+
per_package_reports: &[FutureIncompatReportPackage],
199+
) -> String {
182200
let mut per_package_reports: Vec<_> = per_package_reports.iter().collect();
183201
per_package_reports.sort_by_key(|r| r.package_id);
184202
let mut rendered = String::new();
185-
for per_package in per_package_reports {
203+
for per_package in &per_package_reports {
186204
rendered.push_str(&format!(
187205
"The package `{}` currently triggers the following future \
188206
incompatibility lints:\n",
@@ -198,5 +216,75 @@ fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> String
198216
}
199217
rendered.push('\n');
200218
}
219+
if let Some(s) = render_suggestions(ws, &per_package_reports) {
220+
rendered.push_str(&s);
221+
}
201222
rendered
202223
}
224+
225+
fn render_suggestions(
226+
ws: &Workspace<'_>,
227+
per_package_reports: &[&FutureIncompatReportPackage],
228+
) -> Option<String> {
229+
// This in general ignores all errors since this is opportunistic.
230+
let _lock = ws.config().acquire_package_cache_lock().ok()?;
231+
// Create a set of updated registry sources.
232+
let map = SourceConfigMap::new(ws.config()).ok()?;
233+
let package_ids: BTreeSet<_> = per_package_reports
234+
.iter()
235+
.map(|r| r.package_id)
236+
.filter(|pkg_id| pkg_id.source_id().is_registry())
237+
.collect();
238+
let source_ids: HashSet<_> = package_ids
239+
.iter()
240+
.map(|pkg_id| pkg_id.source_id())
241+
.collect();
242+
let mut sources: HashMap<_, _> = source_ids
243+
.into_iter()
244+
.filter_map(|sid| {
245+
let unlocked = sid.clone().with_precise(None);
246+
let mut source = map.load(unlocked, &HashSet::new()).ok()?;
247+
// Ignore errors updating.
248+
if let Err(e) = source.update() {
249+
log::debug!("failed to update source: {:?}", e);
250+
}
251+
Some((sid, source))
252+
})
253+
.collect();
254+
// Query the sources for new versions.
255+
let mut suggestions = String::new();
256+
for pkg_id in package_ids {
257+
let source = match sources.get_mut(&pkg_id.source_id()) {
258+
Some(s) => s,
259+
None => continue,
260+
};
261+
let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?;
262+
let summaries = source.query_vec(&dep).ok()?;
263+
let versions = itertools::sorted(
264+
summaries
265+
.iter()
266+
.map(|summary| summary.version())
267+
.filter(|version| *version > pkg_id.version()),
268+
);
269+
let versions = versions.map(|version| version.to_string());
270+
let versions = iter_join(versions, ", ");
271+
if !versions.is_empty() {
272+
writeln!(
273+
suggestions,
274+
"{} has the following newer versions available: {}",
275+
pkg_id, versions
276+
)
277+
.unwrap();
278+
}
279+
}
280+
if suggestions.is_empty() {
281+
None
282+
} else {
283+
Some(format!(
284+
"The following packages appear to have newer versions available.\n\
285+
You may want to consider updating them to a newer version to see if the \
286+
issue has been fixed.\n\n{}",
287+
suggestions
288+
))
289+
}
290+
}

tests/testsuite/future_incompat_report.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,11 @@ fn test_multi_crate() {
195195
.exec_with_output()
196196
.unwrap();
197197
let output = std::str::from_utf8(&output.stdout).unwrap();
198-
let mut lines = output.lines();
198+
assert!(output.starts_with("The following warnings were discovered"));
199+
let mut lines = output
200+
.lines()
201+
// Skip the beginning of the per-package information.
202+
.skip_while(|line| !line.starts_with("The package"));
199203
for expected in &["first-dep v0.0.1", "second-dep v0.0.2"] {
200204
assert_eq!(
201205
&format!(
@@ -276,3 +280,67 @@ Available IDs are: 1
276280
)
277281
.run();
278282
}
283+
284+
#[cargo_test]
285+
fn suggestions_for_updates() {
286+
if !is_nightly() {
287+
return;
288+
}
289+
290+
Package::new("with_updates", "1.0.0")
291+
.file("src/lib.rs", FUTURE_EXAMPLE)
292+
.publish();
293+
Package::new("big_update", "1.0.0")
294+
.file("src/lib.rs", FUTURE_EXAMPLE)
295+
.publish();
296+
Package::new("without_updates", "1.0.0")
297+
.file("src/lib.rs", FUTURE_EXAMPLE)
298+
.publish();
299+
300+
let p = project()
301+
.file(
302+
"Cargo.toml",
303+
r#"
304+
[package]
305+
name = "foo"
306+
version = "0.1.0"
307+
308+
[dependencies]
309+
with_updates = "1"
310+
big_update = "1"
311+
without_updates = "1"
312+
"#,
313+
)
314+
.file("src/lib.rs", "")
315+
.build();
316+
317+
p.cargo("generate-lockfile").run();
318+
319+
Package::new("with_updates", "1.0.1")
320+
.file("src/lib.rs", "")
321+
.publish();
322+
Package::new("with_updates", "1.0.2")
323+
.file("src/lib.rs", "")
324+
.publish();
325+
Package::new("big_update", "2.0.0")
326+
.file("src/lib.rs", "")
327+
.publish();
328+
329+
p.cargo("check -Zfuture-incompat-report")
330+
.masquerade_as_nightly_cargo()
331+
.with_stderr_contains("[..]cargo report future-incompatibilities --id 1[..]")
332+
.run();
333+
334+
p.cargo("report future-incompatibilities")
335+
.masquerade_as_nightly_cargo()
336+
.with_stdout_contains(
337+
"\
338+
The following packages appear to have newer versions available.
339+
You may want to consider updating them to a newer version to see if the issue has been fixed.
340+
341+
big_update v1.0.0 has the following newer versions available: 2.0.0
342+
with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2
343+
",
344+
)
345+
.run();
346+
}

0 commit comments

Comments
 (0)