From d5538c3869b5116d55802a789ce863e3746984a5 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Fri, 27 Aug 2021 13:16:17 -0500 Subject: [PATCH 01/13] Make future-incompat-report output more user-friendly When the user enables `--future-incompat-report`, we now display a high-level summary of the problem, as well as several suggestions for fixing the affected crates. The command `cargo report future-incompatibilities` now takes a `--crate` option, which can be used to display a report (including the actual lint messages) for a single crate. When this option is not used, we display the report for all crates. --- src/bin/cargo/commands/report.rs | 7 +- src/cargo/core/compiler/future_incompat.rs | 132 ++++++------------ src/cargo/core/compiler/job_queue.rs | 155 +++++++++++++++++++-- tests/testsuite/future_incompat_report.rs | 48 +++++-- 4 files changed, 232 insertions(+), 110 deletions(-) diff --git a/src/bin/cargo/commands/report.rs b/src/bin/cargo/commands/report.rs index 6906f62282c..a55641a4d62 100644 --- a/src/bin/cargo/commands/report.rs +++ b/src/bin/cargo/commands/report.rs @@ -18,6 +18,10 @@ pub fn cli() -> App { "identifier of the report generated by a Cargo command invocation", ) .value_name("id"), + ) + .arg( + opt("crate", "identifier of the crate to display a report for") + .value_name("crate"), ), ) } @@ -38,7 +42,8 @@ fn report_future_incompatibilies(config: &Config, args: &ArgMatches<'_>) -> CliR let id = args .value_of_u32("id")? .unwrap_or_else(|| reports.last_id()); - let report = reports.get_report(id, config)?; + let krate = args.value_of("crate"); + let report = reports.get_report(id, config, krate)?; drop_println!(config, "{}", REPORT_PREAMBLE); drop(config.shell().print_ansi_stdout(report.as_bytes())); Ok(()) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index d9638deddac..642c0a8f681 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -1,12 +1,10 @@ //! Support for future-incompatible warning reporting. -use crate::core::{Dependency, PackageId, Workspace}; -use crate::sources::SourceConfigMap; +use crate::core::{PackageId, Workspace}; use crate::util::{iter_join, CargoResult, Config}; use anyhow::{bail, format_err, Context}; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeSet, HashMap, HashSet}; -use std::fmt::Write as _; +use std::collections::BTreeMap; use std::io::{Read, Write}; pub const REPORT_PREAMBLE: &str = "\ @@ -78,7 +76,10 @@ struct OnDiskReport { /// Unique reference to the report for the `--id` CLI flag. id: u32, /// Report, suitable for printing to the console. - report: String, + /// Maps crate names to the corresponding report + /// We use a `BTreeMap` so that the iteration order + /// is stable across multiple runs of `cargo` + per_crate: BTreeMap, } impl Default for OnDiskReports { @@ -109,7 +110,7 @@ impl OnDiskReports { }; let report = OnDiskReport { id: current_reports.next_id, - report: render_report(ws, per_package_reports), + per_crate: render_report(per_package_reports), }; current_reports.next_id += 1; current_reports.reports.push(report); @@ -176,7 +177,12 @@ impl OnDiskReports { self.reports.last().map(|r| r.id).unwrap() } - pub fn get_report(&self, id: u32, config: &Config) -> CargoResult { + pub fn get_report( + &self, + id: u32, + config: &Config, + package: Option<&str>, + ) -> CargoResult { let report = self.reports.iter().find(|r| r.id == id).ok_or_else(|| { let available = iter_join(self.reports.iter().map(|r| r.id.to_string()), ", "); format_err!( @@ -186,25 +192,45 @@ impl OnDiskReports { available ) })?; - let report = if config.shell().err_supports_color() { - report.report.clone() + let to_display = if let Some(package) = package { + report + .per_crate + .get(package) + .ok_or_else(|| { + format_err!( + "could not find package with ID `{}`\n + Available packages are: {}\n + Omit the `--crate` flag to display a report for all crates", + package, + iter_join(report.per_crate.keys(), ", ") + ) + })? + .clone() } else { - strip_ansi_escapes::strip(&report.report) + report + .per_crate + .values() + .cloned() + .collect::>() + .join("\n") + }; + let to_display = if config.shell().err_supports_color() { + to_display + } else { + strip_ansi_escapes::strip(&to_display) .map(|v| String::from_utf8(v).expect("utf8")) .expect("strip should never fail") }; - Ok(report) + Ok(to_display) } } -fn render_report( - ws: &Workspace<'_>, - per_package_reports: &[FutureIncompatReportPackage], -) -> String { - let mut per_package_reports: Vec<_> = per_package_reports.iter().collect(); - per_package_reports.sort_by_key(|r| r.package_id); - let mut rendered = String::new(); - for per_package in &per_package_reports { +fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMap { + let mut report: BTreeMap = BTreeMap::new(); + for per_package in per_package_reports { + let rendered = report + .entry(per_package.package_id.to_string()) + .or_default(); rendered.push_str(&format!( "The package `{}` currently triggers the following future \ incompatibility lints:\n", @@ -218,72 +244,6 @@ fn render_report( .map(|l| format!("> {}\n", l)), ); } - rendered.push('\n'); - } - if let Some(s) = render_suggestions(ws, &per_package_reports) { - rendered.push_str(&s); - } - rendered -} - -fn render_suggestions( - ws: &Workspace<'_>, - per_package_reports: &[&FutureIncompatReportPackage], -) -> Option { - // This in general ignores all errors since this is opportunistic. - let _lock = ws.config().acquire_package_cache_lock().ok()?; - // Create a set of updated registry sources. - let map = SourceConfigMap::new(ws.config()).ok()?; - let package_ids: BTreeSet<_> = per_package_reports - .iter() - .map(|r| r.package_id) - .filter(|pkg_id| pkg_id.source_id().is_registry()) - .collect(); - let source_ids: HashSet<_> = package_ids - .iter() - .map(|pkg_id| pkg_id.source_id()) - .collect(); - let mut sources: HashMap<_, _> = source_ids - .into_iter() - .filter_map(|sid| { - let source = map.load(sid, &HashSet::new()).ok()?; - Some((sid, source)) - }) - .collect(); - // Query the sources for new versions. - let mut suggestions = String::new(); - for pkg_id in package_ids { - let source = match sources.get_mut(&pkg_id.source_id()) { - Some(s) => s, - None => continue, - }; - let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?; - let summaries = source.query_vec(&dep).ok()?; - let versions = itertools::sorted( - summaries - .iter() - .map(|summary| summary.version()) - .filter(|version| *version > pkg_id.version()), - ); - let versions = versions.map(|version| version.to_string()); - let versions = iter_join(versions, ", "); - if !versions.is_empty() { - writeln!( - suggestions, - "{} has the following newer versions available: {}", - pkg_id, versions - ) - .unwrap(); - } - } - if suggestions.is_empty() { - None - } else { - Some(format!( - "The following packages appear to have newer versions available.\n\ - You may want to consider updating them to a newer version to see if the \ - issue has been fixed.\n\n{}", - suggestions - )) } + report } diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index c7f008972e0..152d8392185 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -75,11 +75,13 @@ use crate::core::compiler::future_incompat::{ FutureBreakageItem, FutureIncompatReportPackage, OnDiskReports, }; use crate::core::resolver::ResolveBehavior; +use crate::core::{Dependency, Workspace}; use crate::core::{PackageId, Shell, TargetKind}; +use crate::sources::SourceConfigMap; use crate::util::diagnostic_server::{self, DiagnosticPrinter}; use crate::util::machine_message::{self, Message as _}; use crate::util::CargoResult; -use crate::util::{self, internal, profile}; +use crate::util::{self, internal, iter_join, profile}; use crate::util::{Config, DependencyQueue, Progress, ProgressStyle, Queue}; /// This structure is backed by the `DependencyQueue` type and manages the @@ -911,15 +913,12 @@ impl<'cfg> DrainState<'cfg> { } // Get a list of unique and sorted package name/versions. - let package_vers: BTreeSet<_> = self + let package_ids: BTreeSet<_> = self .per_package_future_incompat_reports .iter() .map(|r| r.package_id) .collect(); - let package_vers: Vec<_> = package_vers - .into_iter() - .map(|pid| pid.to_string()) - .collect(); + let package_vers: Vec<_> = package_ids.iter().map(|pid| pid.to_string()).collect(); if should_display_message || bcx.build_config.future_incompat_report { drop(bcx.config.shell().warn(&format!( @@ -934,8 +933,76 @@ impl<'cfg> DrainState<'cfg> { let report_id = on_disk_reports.last_id(); if bcx.build_config.future_incompat_report { - let rendered = on_disk_reports.get_report(report_id, bcx.config).unwrap(); - drop(bcx.config.shell().print_ansi_stderr(rendered.as_bytes())); + let upstream_info = package_ids + .iter() + .map(|package_id| { + let manifest = bcx.packages.get_one(*package_id).unwrap().manifest(); + format!( + " + - {name} + - Repository: {url} + - Detailed warning command: `cargo report future-incompatibilities --id {id} --crate '{name}'", + name = package_id, + url = manifest + .metadata() + .repository + .as_deref() + .unwrap_or(""), + id = report_id, + ) + }) + .collect::>() + .join("\n"); + + let (compat, incompat) = + get_updates(bcx.ws, &package_ids).unwrap_or((String::new(), String::new())); + + let compat_message = if !compat.is_empty() { + format!( + " +- Some affected dependencies have minor or patch version updates available: +{compat}", + compat = compat + ) + } else { + String::new() + }; + + let incompat_message = if !incompat.is_empty() { + format!( + " +- If a minor dependency update does not help, you can try updating to a new + major version of those dependencies. You have to do this manually: +{incompat} + ", + incompat = incompat + ) + } else { + String::new() + }; + + drop(bcx.config.shell().note(&format!( + " +To solve this problem, you can try the following approaches: + +{compat_message} +{incompat_message} +- If the issue is not solved by updating the dependencies, a fix has to be + implemented by those dependencies. You can help with that by notifying the + maintainers of this problem (e.g. by creating a bug report) or by proposing a + fix to the maintainers (e.g. by creating a pull request): + {upstream_info} + +- If waiting for an upstream fix is not an option, you can use the `[patch]` + section in `Cargo.toml` to use your own version of the dependency. For more + information, see: + https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section + ", + upstream_info = upstream_info, + compat_message = compat_message, + incompat_message = incompat_message + ))); + drop(bcx.config.shell().note(&format!( "this report can be shown with `cargo report \ future-incompatibilities -Z future-incompat-report --id {}`", @@ -1283,3 +1350,75 @@ feature resolver. Try updating to diesel 1.4.8 to fix this error. Ok(()) } } + +// Returns a pair (compatible_updates, incompatible_updates), +// of semver-compatible and semver-incompatible update versions, +// respectively. +fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option<(String, String)> { + // This in general ignores all errors since this is opportunistic. + let _lock = ws.config().acquire_package_cache_lock().ok()?; + // Create a set of updated registry sources. + let map = SourceConfigMap::new(ws.config()).ok()?; + let package_ids: BTreeSet<_> = package_ids + .iter() + .filter(|pkg_id| pkg_id.source_id().is_registry()) + .collect(); + let source_ids: HashSet<_> = package_ids + .iter() + .map(|pkg_id| pkg_id.source_id()) + .collect(); + let mut sources: HashMap<_, _> = source_ids + .into_iter() + .filter_map(|sid| { + let source = map.load(sid, &HashSet::new()).ok()?; + Some((sid, source)) + }) + .collect(); + // Query the sources for new versions. + let mut compatible = String::new(); + let mut incompatible = String::new(); + for pkg_id in package_ids { + let source = match sources.get_mut(&pkg_id.source_id()) { + Some(s) => s, + None => continue, + }; + let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?; + let summaries = source.query_vec(&dep).ok()?; + let (mut compatible_versions, mut incompatible_versions): (Vec<_>, Vec<_>) = summaries + .iter() + .map(|summary| summary.version()) + .filter(|version| *version > pkg_id.version()) + .partition(|version| version.major == pkg_id.version().major); + compatible_versions.sort(); + incompatible_versions.sort(); + + let compatible_versions = compatible_versions + .into_iter() + .map(|version| version.to_string()); + let compatible_versions = iter_join(compatible_versions, ", "); + + let incompatible_versions = incompatible_versions + .into_iter() + .map(|version| version.to_string()); + let incompatible_versions = iter_join(incompatible_versions, ", "); + + if !compatible_versions.is_empty() { + writeln!( + compatible, + "{} has the following newer versions available: {}", + pkg_id, compatible_versions + ) + .unwrap(); + } + + if !incompatible_versions.is_empty() { + writeln!( + incompatible, + "{} has the following newer versions available: {}", + pkg_id, incompatible_versions + ) + .unwrap(); + } + } + Some((compatible, incompatible)) +} diff --git a/tests/testsuite/future_incompat_report.rs b/tests/testsuite/future_incompat_report.rs index 7edd89b5c0a..69a8e5511a2 100644 --- a/tests/testsuite/future_incompat_report.rs +++ b/tests/testsuite/future_incompat_report.rs @@ -161,7 +161,7 @@ frequency = 'never' .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_contains(FUTURE_OUTPUT) .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: foo v0.0.0 [..]") - .with_stderr_contains("The package `foo v0.0.0 ([..])` currently triggers the following future incompatibility lints:") + .with_stderr_contains(" - foo v0.0.0[..]") .run(); } } @@ -202,16 +202,30 @@ fn test_multi_crate() { .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_does_not_contain(FUTURE_OUTPUT) .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: first-dep v0.0.1, second-dep v0.0.2") - // Check that we don't have the 'triggers' message shown at the bottom of this loop + // Check that we don't have the 'triggers' message shown at the bottom of this loop, + // and that we don't explain how to show a per-package report .with_stderr_does_not_contain("[..]triggers[..]") + .with_stderr_does_not_contain("[..]--crate[..]") .run(); p.cargo(command).arg("-Zunstable-options").arg("-Zfuture-incompat-report").arg("--future-incompat-report") .masquerade_as_nightly_cargo() .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: first-dep v0.0.1, second-dep v0.0.2") - .with_stderr_contains("The package `first-dep v0.0.1` currently triggers the following future incompatibility lints:") - .with_stderr_contains("The package `second-dep v0.0.2` currently triggers the following future incompatibility lints:") + .with_stderr_contains(" - first-dep v0.0.1") + .with_stderr_contains(" - second-dep v0.0.2") + .run(); + + p.cargo("report future-incompatibilities").arg("--crate").arg("first-dep v0.0.1").arg("-Zunstable-options").arg("-Zfuture-incompat-report") + .masquerade_as_nightly_cargo() + .with_stdout_contains("The package `first-dep v0.0.1` currently triggers the following future incompatibility lints:") + .with_stdout_contains(FUTURE_OUTPUT) + .run(); + + p.cargo("report future-incompatibilities").arg("--crate").arg("second-dep v0.0.2").arg("-Zunstable-options").arg("-Zfuture-incompat-report") + .masquerade_as_nightly_cargo() + .with_stdout_contains("The package `second-dep v0.0.2` currently triggers the following future incompatibility lints:") + .with_stdout_contains(FUTURE_OUTPUT) .run(); } @@ -252,6 +266,7 @@ fn test_multi_crate() { .exec_with_output() .unwrap(); let output = std::str::from_utf8(&output.stdout).unwrap(); + //if true { panic!("Got output: \n{}", output) } assert!(output.starts_with("The following warnings were discovered")); let mut lines = output .lines() @@ -263,7 +278,9 @@ fn test_multi_crate() { "The package `{}` currently triggers the following future incompatibility lints:", expected ), - lines.next().unwrap() + lines.next().unwrap(), + "Bad output:\n{}", + output ); let mut count = 0; while let Some(line) = lines.next() { @@ -383,6 +400,9 @@ fn suggestions_for_updates() { Package::new("with_updates", "1.0.2") .file("src/lib.rs", "") .publish(); + Package::new("with_updates", "3.0.1") + .file("src/lib.rs", "") + .publish(); Package::new("big_update", "2.0.0") .file("src/lib.rs", "") .publish(); @@ -396,21 +416,19 @@ fn suggestions_for_updates() { // in a long while?). p.cargo("update -p without_updates").run(); - p.cargo("check -Zfuture-incompat-report") + p.cargo("check -Zfuture-incompat-report -Zunstable-options --future-incompat-report") .masquerade_as_nightly_cargo() .env("RUSTFLAGS", "-Zfuture-incompat-test") - .with_stderr_contains("[..]cargo report future-incompatibilities --id 1[..]") - .run(); - - p.cargo("report future-incompatibilities") - .masquerade_as_nightly_cargo() - .with_stdout_contains( + .with_stderr_contains( "\ -The following packages appear to have newer versions available. -You may want to consider updating them to a newer version to see if the issue has been fixed. +- Some affected dependencies have minor or patch version updates available: +with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2 + +- If a minor dependency update does not help, you can try updating to a new + major version of those dependencies. You have to do this manually: big_update v1.0.0 has the following newer versions available: 2.0.0 -with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2 +with_updates v1.0.0 has the following newer versions available: 3.0.1 ", ) .run(); From 20340e1e79507b5b7cfc1b2496c9fb3c6d6b0892 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 4 Oct 2021 19:20:23 -0500 Subject: [PATCH 02/13] Rename arg and adjust tests --- src/bin/cargo/commands/report.rs | 7 ++----- tests/testsuite/future_incompat_report.rs | 9 ++++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/bin/cargo/commands/report.rs b/src/bin/cargo/commands/report.rs index a55641a4d62..c4010d4e472 100644 --- a/src/bin/cargo/commands/report.rs +++ b/src/bin/cargo/commands/report.rs @@ -19,10 +19,7 @@ pub fn cli() -> App { ) .value_name("id"), ) - .arg( - opt("crate", "identifier of the crate to display a report for") - .value_name("crate"), - ), + .arg_package("Package to display a report for") ) } @@ -42,7 +39,7 @@ fn report_future_incompatibilies(config: &Config, args: &ArgMatches<'_>) -> CliR let id = args .value_of_u32("id")? .unwrap_or_else(|| reports.last_id()); - let krate = args.value_of("crate"); + let krate = args.value_of("package"); let report = reports.get_report(id, config, krate)?; drop_println!(config, "{}", REPORT_PREAMBLE); drop(config.shell().print_ansi_stdout(report.as_bytes())); diff --git a/tests/testsuite/future_incompat_report.rs b/tests/testsuite/future_incompat_report.rs index 69a8e5511a2..f6bec72c4c5 100644 --- a/tests/testsuite/future_incompat_report.rs +++ b/tests/testsuite/future_incompat_report.rs @@ -205,7 +205,8 @@ fn test_multi_crate() { // Check that we don't have the 'triggers' message shown at the bottom of this loop, // and that we don't explain how to show a per-package report .with_stderr_does_not_contain("[..]triggers[..]") - .with_stderr_does_not_contain("[..]--crate[..]") + .with_stderr_does_not_contain("[..]--package[..]") + .with_stderr_does_not_contain("[..]-p[..]") .run(); p.cargo(command).arg("-Zunstable-options").arg("-Zfuture-incompat-report").arg("--future-incompat-report") @@ -216,16 +217,18 @@ fn test_multi_crate() { .with_stderr_contains(" - second-dep v0.0.2") .run(); - p.cargo("report future-incompatibilities").arg("--crate").arg("first-dep v0.0.1").arg("-Zunstable-options").arg("-Zfuture-incompat-report") + p.cargo("report future-incompatibilities").arg("--package").arg("first-dep v0.0.1").arg("-Zunstable-options").arg("-Zfuture-incompat-report") .masquerade_as_nightly_cargo() .with_stdout_contains("The package `first-dep v0.0.1` currently triggers the following future incompatibility lints:") .with_stdout_contains(FUTURE_OUTPUT) + .with_stdout_does_not_contain("[..]second-dep[..]") .run(); - p.cargo("report future-incompatibilities").arg("--crate").arg("second-dep v0.0.2").arg("-Zunstable-options").arg("-Zfuture-incompat-report") + p.cargo("report future-incompatibilities").arg("--package").arg("second-dep v0.0.2").arg("-Zunstable-options").arg("-Zfuture-incompat-report") .masquerade_as_nightly_cargo() .with_stdout_contains("The package `second-dep v0.0.2` currently triggers the following future incompatibility lints:") .with_stdout_contains(FUTURE_OUTPUT) + .with_stdout_does_not_contain("[..]first-dep[..]") .run(); } From 315d605bb326742b2f7e05208a3250838ade4f3c Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Fri, 8 Oct 2021 15:09:05 -0500 Subject: [PATCH 03/13] Rename to per_package --- src/cargo/core/compiler/future_incompat.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index 642c0a8f681..a30e82a92e2 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -76,10 +76,10 @@ struct OnDiskReport { /// Unique reference to the report for the `--id` CLI flag. id: u32, /// Report, suitable for printing to the console. - /// Maps crate names to the corresponding report + /// Maps package names to the corresponding report /// We use a `BTreeMap` so that the iteration order /// is stable across multiple runs of `cargo` - per_crate: BTreeMap, + per_package: BTreeMap, } impl Default for OnDiskReports { @@ -110,7 +110,7 @@ impl OnDiskReports { }; let report = OnDiskReport { id: current_reports.next_id, - per_crate: render_report(per_package_reports), + per_package: render_report(per_package_reports), }; current_reports.next_id += 1; current_reports.reports.push(report); @@ -194,7 +194,7 @@ impl OnDiskReports { })?; let to_display = if let Some(package) = package { report - .per_crate + .per_package .get(package) .ok_or_else(|| { format_err!( @@ -202,13 +202,13 @@ impl OnDiskReports { Available packages are: {}\n Omit the `--crate` flag to display a report for all crates", package, - iter_join(report.per_crate.keys(), ", ") + iter_join(report.per_package.keys(), ", ") ) })? .clone() } else { report - .per_crate + .per_package .values() .cloned() .collect::>() From ce259228cd5722d575c749eef94c570e06a57c23 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Fri, 8 Oct 2021 15:19:18 -0500 Subject: [PATCH 04/13] Change package spec --- src/cargo/core/compiler/future_incompat.rs | 3 ++- src/cargo/core/compiler/job_queue.rs | 2 +- tests/testsuite/future_incompat_report.rs | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index a30e82a92e2..6510a4f4ad2 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -228,8 +228,9 @@ impl OnDiskReports { fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMap { let mut report: BTreeMap = BTreeMap::new(); for per_package in per_package_reports { + let package_spec = format!("{}:{}", per_package.package_id.name(), per_package.package_id.version()); let rendered = report - .entry(per_package.package_id.to_string()) + .entry(package_spec) .or_default(); rendered.push_str(&format!( "The package `{}` currently triggers the following future \ diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 152d8392185..64bb17835b6 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -942,7 +942,7 @@ impl<'cfg> DrainState<'cfg> { - {name} - Repository: {url} - Detailed warning command: `cargo report future-incompatibilities --id {id} --crate '{name}'", - name = package_id, + name = format!("{}:{}", package_id.name(), package_id.version()), url = manifest .metadata() .repository diff --git a/tests/testsuite/future_incompat_report.rs b/tests/testsuite/future_incompat_report.rs index f6bec72c4c5..91da0c56714 100644 --- a/tests/testsuite/future_incompat_report.rs +++ b/tests/testsuite/future_incompat_report.rs @@ -161,7 +161,7 @@ frequency = 'never' .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_contains(FUTURE_OUTPUT) .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: foo v0.0.0 [..]") - .with_stderr_contains(" - foo v0.0.0[..]") + .with_stderr_contains(" - foo:0.0.0[..]") .run(); } } @@ -213,18 +213,18 @@ fn test_multi_crate() { .masquerade_as_nightly_cargo() .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_contains("warning: the following packages contain code that will be rejected by a future version of Rust: first-dep v0.0.1, second-dep v0.0.2") - .with_stderr_contains(" - first-dep v0.0.1") - .with_stderr_contains(" - second-dep v0.0.2") + .with_stderr_contains(" - first-dep:0.0.1") + .with_stderr_contains(" - second-dep:0.0.2") .run(); - p.cargo("report future-incompatibilities").arg("--package").arg("first-dep v0.0.1").arg("-Zunstable-options").arg("-Zfuture-incompat-report") + p.cargo("report future-incompatibilities").arg("--package").arg("first-dep:0.0.1").arg("-Zunstable-options").arg("-Zfuture-incompat-report") .masquerade_as_nightly_cargo() .with_stdout_contains("The package `first-dep v0.0.1` currently triggers the following future incompatibility lints:") .with_stdout_contains(FUTURE_OUTPUT) .with_stdout_does_not_contain("[..]second-dep[..]") .run(); - p.cargo("report future-incompatibilities").arg("--package").arg("second-dep v0.0.2").arg("-Zunstable-options").arg("-Zfuture-incompat-report") + p.cargo("report future-incompatibilities").arg("--package").arg("second-dep:0.0.2").arg("-Zunstable-options").arg("-Zfuture-incompat-report") .masquerade_as_nightly_cargo() .with_stdout_contains("The package `second-dep v0.0.2` currently triggers the following future incompatibility lints:") .with_stdout_contains(FUTURE_OUTPUT) From 49ae189b3b29f37518a32fce1cbfbdcc60557c2d Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Fri, 8 Oct 2021 15:28:35 -0500 Subject: [PATCH 05/13] Combined newer versions into single list --- src/cargo/core/compiler/job_queue.rs | 73 +++++++---------------- tests/testsuite/future_incompat_report.rs | 9 +-- 2 files changed, 22 insertions(+), 60 deletions(-) diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 64bb17835b6..d1e61e89ce8 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -954,28 +954,15 @@ impl<'cfg> DrainState<'cfg> { .collect::>() .join("\n"); - let (compat, incompat) = - get_updates(bcx.ws, &package_ids).unwrap_or((String::new(), String::new())); + let updated_versions = + get_updates(bcx.ws, &package_ids).unwrap_or(String::new()); - let compat_message = if !compat.is_empty() { + let update_message = if !updated_versions.is_empty() { format!( " -- Some affected dependencies have minor or patch version updates available: -{compat}", - compat = compat - ) - } else { - String::new() - }; - - let incompat_message = if !incompat.is_empty() { - format!( - " -- If a minor dependency update does not help, you can try updating to a new - major version of those dependencies. You have to do this manually: -{incompat} - ", - incompat = incompat +- Some affected dependencies have updates available: +{updated_versions}", + updated_versions = updated_versions ) } else { String::new() @@ -985,8 +972,7 @@ impl<'cfg> DrainState<'cfg> { " To solve this problem, you can try the following approaches: -{compat_message} -{incompat_message} +{update_message} - If the issue is not solved by updating the dependencies, a fix has to be implemented by those dependencies. You can help with that by notifying the maintainers of this problem (e.g. by creating a bug report) or by proposing a @@ -999,8 +985,7 @@ To solve this problem, you can try the following approaches: https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section ", upstream_info = upstream_info, - compat_message = compat_message, - incompat_message = incompat_message + update_message = update_message, ))); drop(bcx.config.shell().note(&format!( @@ -1354,7 +1339,7 @@ feature resolver. Try updating to diesel 1.4.8 to fix this error. // Returns a pair (compatible_updates, incompatible_updates), // of semver-compatible and semver-incompatible update versions, // respectively. -fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option<(String, String)> { +fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option { // This in general ignores all errors since this is opportunistic. let _lock = ws.config().acquire_package_cache_lock().ok()?; // Create a set of updated registry sources. @@ -1375,8 +1360,7 @@ fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option< }) .collect(); // Query the sources for new versions. - let mut compatible = String::new(); - let mut incompatible = String::new(); + let mut updates = String::new(); for pkg_id in package_ids { let source = match sources.get_mut(&pkg_id.source_id()) { Some(s) => s, @@ -1384,41 +1368,24 @@ fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option< }; let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?; let summaries = source.query_vec(&dep).ok()?; - let (mut compatible_versions, mut incompatible_versions): (Vec<_>, Vec<_>) = summaries - .iter() - .map(|summary| summary.version()) + let mut updated_versions: Vec<_> = summaries.iter().map(|summary| summary.version()) .filter(|version| *version > pkg_id.version()) - .partition(|version| version.major == pkg_id.version().major); - compatible_versions.sort(); - incompatible_versions.sort(); - - let compatible_versions = compatible_versions - .into_iter() - .map(|version| version.to_string()); - let compatible_versions = iter_join(compatible_versions, ", "); + .collect(); + updated_versions.sort(); - let incompatible_versions = incompatible_versions + let updated_versions = iter_join(updated_versions .into_iter() - .map(|version| version.to_string()); - let incompatible_versions = iter_join(incompatible_versions, ", "); - - if !compatible_versions.is_empty() { - writeln!( - compatible, - "{} has the following newer versions available: {}", - pkg_id, compatible_versions - ) - .unwrap(); - } + .map(|version| version.to_string()), + ", "); - if !incompatible_versions.is_empty() { + if !updated_versions.is_empty() { writeln!( - incompatible, + updates, "{} has the following newer versions available: {}", - pkg_id, incompatible_versions + pkg_id, updated_versions ) .unwrap(); } } - Some((compatible, incompatible)) + Some(updates) } diff --git a/tests/testsuite/future_incompat_report.rs b/tests/testsuite/future_incompat_report.rs index 91da0c56714..539ffce5c0a 100644 --- a/tests/testsuite/future_incompat_report.rs +++ b/tests/testsuite/future_incompat_report.rs @@ -424,14 +424,9 @@ fn suggestions_for_updates() { .env("RUSTFLAGS", "-Zfuture-incompat-test") .with_stderr_contains( "\ -- Some affected dependencies have minor or patch version updates available: -with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2 - - -- If a minor dependency update does not help, you can try updating to a new - major version of those dependencies. You have to do this manually: +- Some affected dependencies have updates available: big_update v1.0.0 has the following newer versions available: 2.0.0 -with_updates v1.0.0 has the following newer versions available: 3.0.1 +with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2, 3.0.1 ", ) .run(); From 5afcce6b3ea4f0c10e10d31aaa336f8de3db9014 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Fri, 8 Oct 2021 15:38:27 -0500 Subject: [PATCH 06/13] Run fmt --- src/bin/cargo/commands/report.rs | 2 +- src/cargo/core/compiler/future_incompat.rs | 10 ++++++---- src/cargo/core/compiler/job_queue.rs | 17 ++++++++++------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/bin/cargo/commands/report.rs b/src/bin/cargo/commands/report.rs index c4010d4e472..cf2bbf91fc9 100644 --- a/src/bin/cargo/commands/report.rs +++ b/src/bin/cargo/commands/report.rs @@ -19,7 +19,7 @@ pub fn cli() -> App { ) .value_name("id"), ) - .arg_package("Package to display a report for") + .arg_package("Package to display a report for"), ) } diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index 6510a4f4ad2..7772442f38b 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -228,10 +228,12 @@ impl OnDiskReports { fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMap { let mut report: BTreeMap = BTreeMap::new(); for per_package in per_package_reports { - let package_spec = format!("{}:{}", per_package.package_id.name(), per_package.package_id.version()); - let rendered = report - .entry(package_spec) - .or_default(); + let package_spec = format!( + "{}:{}", + per_package.package_id.name(), + per_package.package_id.version() + ); + let rendered = report.entry(package_spec).or_default(); rendered.push_str(&format!( "The package `{}` currently triggers the following future \ incompatibility lints:\n", diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index d1e61e89ce8..a913271a60f 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -954,8 +954,7 @@ impl<'cfg> DrainState<'cfg> { .collect::>() .join("\n"); - let updated_versions = - get_updates(bcx.ws, &package_ids).unwrap_or(String::new()); + let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new()); let update_message = if !updated_versions.is_empty() { format!( @@ -1368,15 +1367,19 @@ fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option< }; let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?; let summaries = source.query_vec(&dep).ok()?; - let mut updated_versions: Vec<_> = summaries.iter().map(|summary| summary.version()) + let mut updated_versions: Vec<_> = summaries + .iter() + .map(|summary| summary.version()) .filter(|version| *version > pkg_id.version()) .collect(); updated_versions.sort(); - let updated_versions = iter_join(updated_versions - .into_iter() - .map(|version| version.to_string()), - ", "); + let updated_versions = iter_join( + updated_versions + .into_iter() + .map(|version| version.to_string()), + ", ", + ); if !updated_versions.is_empty() { writeln!( From 704540d629d0f0b9c637b2367744a589ca3b1fbe Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Fri, 8 Oct 2021 15:44:22 -0500 Subject: [PATCH 07/13] Use double-quotes for package spec --- src/cargo/core/compiler/job_queue.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index a913271a60f..9a126d0ddb6 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -941,7 +941,7 @@ impl<'cfg> DrainState<'cfg> { " - {name} - Repository: {url} - - Detailed warning command: `cargo report future-incompatibilities --id {id} --crate '{name}'", + - Detailed warning command: `cargo report future-incompatibilities --id {id} --crate \"{name}\"", name = format!("{}:{}", package_id.name(), package_id.version()), url = manifest .metadata() From 6f18507092bc6e0ede00394f7d1080633f9f6752 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Tue, 12 Oct 2021 16:48:56 -0500 Subject: [PATCH 08/13] Display update message in report --- src/cargo/core/compiler/future_incompat.rs | 14 +++++++-- src/cargo/core/compiler/job_queue.rs | 36 ++++++++++++---------- tests/testsuite/future_incompat_report.rs | 21 ++++++++----- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index 7772442f38b..8955f1ed058 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -75,6 +75,9 @@ pub struct OnDiskReports { struct OnDiskReport { /// Unique reference to the report for the `--id` CLI flag. id: u32, + /// A (possibly empty) message describing which affected + /// packages have newer versions available + update_message: String, /// Report, suitable for printing to the console. /// Maps package names to the corresponding report /// We use a `BTreeMap` so that the iteration order @@ -96,6 +99,7 @@ impl OnDiskReports { /// Saves a new report. pub fn save_report( ws: &Workspace<'_>, + update_message: String, per_package_reports: &[FutureIncompatReportPackage], ) -> OnDiskReports { let mut current_reports = match Self::load(ws) { @@ -110,6 +114,7 @@ impl OnDiskReports { }; let report = OnDiskReport { id: current_reports.next_id, + update_message, per_package: render_report(per_package_reports), }; current_reports.next_id += 1; @@ -192,7 +197,10 @@ impl OnDiskReports { available ) })?; - let to_display = if let Some(package) = package { + + let mut to_display = report.update_message.clone(); + + let package_report = if let Some(package) = package { report .per_package .get(package) @@ -205,7 +213,7 @@ impl OnDiskReports { iter_join(report.per_package.keys(), ", ") ) })? - .clone() + .to_string() } else { report .per_package @@ -214,6 +222,8 @@ impl OnDiskReports { .collect::>() .join("\n") }; + to_display += &package_report; + let to_display = if config.shell().err_supports_color() { to_display } else { diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 9a126d0ddb6..55710682029 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -928,8 +928,26 @@ impl<'cfg> DrainState<'cfg> { ))); } - let on_disk_reports = - OnDiskReports::save_report(bcx.ws, &self.per_package_future_incompat_reports); + let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new()); + + let update_message = if !updated_versions.is_empty() { + format!( + " +- Some affected dependencies have newer versions available. +You may want to consider updating them to a newer version to see if the issue has been fixed. + +{updated_versions}\n", + updated_versions = updated_versions + ) + } else { + String::new() + }; + + let on_disk_reports = OnDiskReports::save_report( + bcx.ws, + update_message.clone(), + &self.per_package_future_incompat_reports, + ); let report_id = on_disk_reports.last_id(); if bcx.build_config.future_incompat_report { @@ -953,20 +971,6 @@ impl<'cfg> DrainState<'cfg> { }) .collect::>() .join("\n"); - - let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new()); - - let update_message = if !updated_versions.is_empty() { - format!( - " -- Some affected dependencies have updates available: -{updated_versions}", - updated_versions = updated_versions - ) - } else { - String::new() - }; - drop(bcx.config.shell().note(&format!( " To solve this problem, you can try the following approaches: diff --git a/tests/testsuite/future_incompat_report.rs b/tests/testsuite/future_incompat_report.rs index 539ffce5c0a..a11c38cd61c 100644 --- a/tests/testsuite/future_incompat_report.rs +++ b/tests/testsuite/future_incompat_report.rs @@ -419,15 +419,22 @@ fn suggestions_for_updates() { // in a long while?). p.cargo("update -p without_updates").run(); + let update_message = "\ +- Some affected dependencies have newer versions available. +You may want to consider updating them to a newer version to see if the issue has been fixed. + +big_update v1.0.0 has the following newer versions available: 2.0.0 +with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2, 3.0.1 +"; + p.cargo("check -Zfuture-incompat-report -Zunstable-options --future-incompat-report") .masquerade_as_nightly_cargo() .env("RUSTFLAGS", "-Zfuture-incompat-test") - .with_stderr_contains( - "\ -- Some affected dependencies have updates available: -big_update v1.0.0 has the following newer versions available: 2.0.0 -with_updates v1.0.0 has the following newer versions available: 1.0.1, 1.0.2, 3.0.1 -", - ) + .with_stderr_contains(update_message) .run(); + + p.cargo("report future-incompatibilities") + .masquerade_as_nightly_cargo() + .with_stdout_contains(update_message) + .run() } From f57be6f680650c6946254184c56c73a73c5a5784 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sat, 16 Oct 2021 12:10:02 -0500 Subject: [PATCH 09/13] Adjust message and move code to future_incompat module --- src/cargo/core/compiler/future_incompat.rs | 192 ++++++++++++++++++++- src/cargo/core/compiler/job_queue.rs | 187 +------------------- 2 files changed, 193 insertions(+), 186 deletions(-) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index 8955f1ed058..d8a901cd32a 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -1,10 +1,13 @@ //! Support for future-incompatible warning reporting. -use crate::core::{PackageId, Workspace}; +use crate::core::compiler::BuildContext; +use crate::core::{Dependency, PackageId, Workspace}; +use crate::sources::SourceConfigMap; use crate::util::{iter_join, CargoResult, Config}; use anyhow::{bail, format_err, Context}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::fmt::Write as _; use std::io::{Read, Write}; pub const REPORT_PREAMBLE: &str = "\ @@ -208,7 +211,7 @@ impl OnDiskReports { format_err!( "could not find package with ID `{}`\n Available packages are: {}\n - Omit the `--crate` flag to display a report for all crates", + Omit the `--package` flag to display a report for all packages", package, iter_join(report.per_package.keys(), ", ") ) @@ -260,3 +263,186 @@ fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMa } report } + +// Returns a pair (compatible_updates, incompatible_updates), +// of semver-compatible and semver-incompatible update versions, +// respectively. +fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option { + // This in general ignores all errors since this is opportunistic. + let _lock = ws.config().acquire_package_cache_lock().ok()?; + // Create a set of updated registry sources. + let map = SourceConfigMap::new(ws.config()).ok()?; + let package_ids: BTreeSet<_> = package_ids + .iter() + .filter(|pkg_id| pkg_id.source_id().is_registry()) + .collect(); + let source_ids: HashSet<_> = package_ids + .iter() + .map(|pkg_id| pkg_id.source_id()) + .collect(); + let mut sources: HashMap<_, _> = source_ids + .into_iter() + .filter_map(|sid| { + let source = map.load(sid, &HashSet::new()).ok()?; + Some((sid, source)) + }) + .collect(); + // Query the sources for new versions. + let mut updates = String::new(); + for pkg_id in package_ids { + let source = match sources.get_mut(&pkg_id.source_id()) { + Some(s) => s, + None => continue, + }; + let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?; + let summaries = source.query_vec(&dep).ok()?; + let mut updated_versions: Vec<_> = summaries + .iter() + .map(|summary| summary.version()) + .filter(|version| *version > pkg_id.version()) + .collect(); + updated_versions.sort(); + + let updated_versions = iter_join( + updated_versions + .into_iter() + .map(|version| version.to_string()), + ", ", + ); + + if !updated_versions.is_empty() { + writeln!( + updates, + "{} has the following newer versions available: {}", + pkg_id, updated_versions + ) + .unwrap(); + } + } + Some(updates) +} + +pub fn render_message( + bcx: &BuildContext<'_, '_>, + per_package_future_incompat_reports: &[FutureIncompatReportPackage], +) { + if !bcx.config.cli_unstable().future_incompat_report { + return; + } + let should_display_message = match bcx.config.future_incompat_config() { + Ok(config) => config.should_display_message(), + Err(e) => { + crate::display_warning_with_error( + "failed to read future-incompat config from disk", + &e, + &mut bcx.config.shell(), + ); + true + } + }; + + if per_package_future_incompat_reports.is_empty() { + // Explicitly passing a command-line flag overrides + // `should_display_message` from the config file + if bcx.build_config.future_incompat_report { + drop( + bcx.config + .shell() + .note("0 dependencies had future-incompatible warnings"), + ); + } + return; + } + + // Get a list of unique and sorted package name/versions. + let package_ids: BTreeSet<_> = per_package_future_incompat_reports + .iter() + .map(|r| r.package_id) + .collect(); + let package_vers: Vec<_> = package_ids.iter().map(|pid| pid.to_string()).collect(); + + if should_display_message || bcx.build_config.future_incompat_report { + drop(bcx.config.shell().warn(&format!( + "the following packages contain code that will be rejected by a future \ + version of Rust: {}", + package_vers.join(", ") + ))); + } + + let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new()); + + let update_message = if !updated_versions.is_empty() { + format!( + " +- Some affected dependencies have newer versions available. +You may want to consider updating them to a newer version to see if the issue has been fixed. + +{updated_versions}\n", + updated_versions = updated_versions + ) + } else { + String::new() + }; + + let on_disk_reports = OnDiskReports::save_report( + bcx.ws, + update_message.clone(), + per_package_future_incompat_reports, + ); + let report_id = on_disk_reports.last_id(); + + if bcx.build_config.future_incompat_report { + let upstream_info = package_ids + .iter() + .map(|package_id| { + let manifest = bcx.packages.get_one(*package_id).unwrap().manifest(); + format!( + " + - {name} + - Repository: {url} + - Detailed warning command: `cargo report future-incompatibilities --id {id} --crate \"{name}\"", + name = format!("{}:{}", package_id.name(), package_id.version()), + url = manifest + .metadata() + .repository + .as_deref() + .unwrap_or(""), + id = report_id, + ) + }) + .collect::>() + .join("\n"); + drop(bcx.config.shell().note(&format!( + " +To solve this problem, you can try the following approaches: + +{update_message} +- If the issue is not solved by updating the dependencies, a fix has to be +implemented by those dependencies. You can help with that by notifying the +maintainers of this problem (e.g. by creating a bug report) or by proposing a +fix to the maintainers (e.g. by creating a pull request): +{upstream_info} + +- If waiting for an upstream fix is not an option, you can use the `[patch]` +section in `Cargo.toml` to use your own version of the dependency. For more +information, see: +https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section + ", + upstream_info = upstream_info, + update_message = update_message, + ))); + + drop(bcx.config.shell().note(&format!( + "this report can be shown with `cargo report \ + future-incompatibilities -Z future-incompat-report --id {}`", + report_id + ))); + } else if should_display_message { + drop(bcx.config.shell().note(&format!( + "to see what the problems were, use the option \ + `--future-incompat-report`, or run `cargo report \ + future-incompatibilities --id {}`", + report_id + ))); + } +} diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 55710682029..46db6d4a671 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -50,7 +50,7 @@ //! improved. use std::cell::{Cell, RefCell}; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Write as _; use std::io; use std::marker; @@ -72,16 +72,14 @@ use super::job::{ use super::timings::Timings; use super::{BuildContext, BuildPlan, CompileMode, Context, Unit}; use crate::core::compiler::future_incompat::{ - FutureBreakageItem, FutureIncompatReportPackage, OnDiskReports, + self, FutureBreakageItem, FutureIncompatReportPackage, }; use crate::core::resolver::ResolveBehavior; -use crate::core::{Dependency, Workspace}; use crate::core::{PackageId, Shell, TargetKind}; -use crate::sources::SourceConfigMap; use crate::util::diagnostic_server::{self, DiagnosticPrinter}; use crate::util::machine_message::{self, Message as _}; use crate::util::CargoResult; -use crate::util::{self, internal, iter_join, profile}; +use crate::util::{self, internal, profile}; use crate::util::{Config, DependencyQueue, Progress, ProgressStyle, Queue}; /// This structure is backed by the `DependencyQueue` type and manages the @@ -884,126 +882,7 @@ impl<'cfg> DrainState<'cfg> { } fn emit_future_incompat(&mut self, bcx: &BuildContext<'_, '_>) { - if !bcx.config.cli_unstable().future_incompat_report { - return; - } - let should_display_message = match bcx.config.future_incompat_config() { - Ok(config) => config.should_display_message(), - Err(e) => { - crate::display_warning_with_error( - "failed to read future-incompat config from disk", - &e, - &mut bcx.config.shell(), - ); - true - } - }; - - if self.per_package_future_incompat_reports.is_empty() { - // Explicitly passing a command-line flag overrides - // `should_display_message` from the config file - if bcx.build_config.future_incompat_report { - drop( - bcx.config - .shell() - .note("0 dependencies had future-incompatible warnings"), - ); - } - return; - } - - // Get a list of unique and sorted package name/versions. - let package_ids: BTreeSet<_> = self - .per_package_future_incompat_reports - .iter() - .map(|r| r.package_id) - .collect(); - let package_vers: Vec<_> = package_ids.iter().map(|pid| pid.to_string()).collect(); - - if should_display_message || bcx.build_config.future_incompat_report { - drop(bcx.config.shell().warn(&format!( - "the following packages contain code that will be rejected by a future \ - version of Rust: {}", - package_vers.join(", ") - ))); - } - - let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new()); - - let update_message = if !updated_versions.is_empty() { - format!( - " -- Some affected dependencies have newer versions available. -You may want to consider updating them to a newer version to see if the issue has been fixed. - -{updated_versions}\n", - updated_versions = updated_versions - ) - } else { - String::new() - }; - - let on_disk_reports = OnDiskReports::save_report( - bcx.ws, - update_message.clone(), - &self.per_package_future_incompat_reports, - ); - let report_id = on_disk_reports.last_id(); - - if bcx.build_config.future_incompat_report { - let upstream_info = package_ids - .iter() - .map(|package_id| { - let manifest = bcx.packages.get_one(*package_id).unwrap().manifest(); - format!( - " - - {name} - - Repository: {url} - - Detailed warning command: `cargo report future-incompatibilities --id {id} --crate \"{name}\"", - name = format!("{}:{}", package_id.name(), package_id.version()), - url = manifest - .metadata() - .repository - .as_deref() - .unwrap_or(""), - id = report_id, - ) - }) - .collect::>() - .join("\n"); - drop(bcx.config.shell().note(&format!( - " -To solve this problem, you can try the following approaches: - -{update_message} -- If the issue is not solved by updating the dependencies, a fix has to be - implemented by those dependencies. You can help with that by notifying the - maintainers of this problem (e.g. by creating a bug report) or by proposing a - fix to the maintainers (e.g. by creating a pull request): - {upstream_info} - -- If waiting for an upstream fix is not an option, you can use the `[patch]` - section in `Cargo.toml` to use your own version of the dependency. For more - information, see: - https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section - ", - upstream_info = upstream_info, - update_message = update_message, - ))); - - drop(bcx.config.shell().note(&format!( - "this report can be shown with `cargo report \ - future-incompatibilities -Z future-incompat-report --id {}`", - report_id - ))); - } else if should_display_message { - drop(bcx.config.shell().note(&format!( - "to see what the problems were, use the option \ - `--future-incompat-report`, or run `cargo report \ - future-incompatibilities --id {}`", - report_id - ))); - } + future_incompat::render_message(bcx, &self.per_package_future_incompat_reports); } fn handle_error( @@ -1338,61 +1217,3 @@ feature resolver. Try updating to diesel 1.4.8 to fix this error. Ok(()) } } - -// Returns a pair (compatible_updates, incompatible_updates), -// of semver-compatible and semver-incompatible update versions, -// respectively. -fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option { - // This in general ignores all errors since this is opportunistic. - let _lock = ws.config().acquire_package_cache_lock().ok()?; - // Create a set of updated registry sources. - let map = SourceConfigMap::new(ws.config()).ok()?; - let package_ids: BTreeSet<_> = package_ids - .iter() - .filter(|pkg_id| pkg_id.source_id().is_registry()) - .collect(); - let source_ids: HashSet<_> = package_ids - .iter() - .map(|pkg_id| pkg_id.source_id()) - .collect(); - let mut sources: HashMap<_, _> = source_ids - .into_iter() - .filter_map(|sid| { - let source = map.load(sid, &HashSet::new()).ok()?; - Some((sid, source)) - }) - .collect(); - // Query the sources for new versions. - let mut updates = String::new(); - for pkg_id in package_ids { - let source = match sources.get_mut(&pkg_id.source_id()) { - Some(s) => s, - None => continue, - }; - let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?; - let summaries = source.query_vec(&dep).ok()?; - let mut updated_versions: Vec<_> = summaries - .iter() - .map(|summary| summary.version()) - .filter(|version| *version > pkg_id.version()) - .collect(); - updated_versions.sort(); - - let updated_versions = iter_join( - updated_versions - .into_iter() - .map(|version| version.to_string()), - ", ", - ); - - if !updated_versions.is_empty() { - writeln!( - updates, - "{} has the following newer versions available: {}", - pkg_id, updated_versions - ) - .unwrap(); - } - } - Some(updates) -} From 085f04adf9191e3299363e263a92fe8a35fd2ebf Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 18 Oct 2021 11:42:52 -0500 Subject: [PATCH 10/13] Adjust suggestions --- src/cargo/core/compiler/future_incompat.rs | 2 +- src/cargo/core/compiler/job_queue.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index d8a901cd32a..626e2afb499 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -400,7 +400,7 @@ You may want to consider updating them to a newer version to see if the issue ha " - {name} - Repository: {url} - - Detailed warning command: `cargo report future-incompatibilities --id {id} --crate \"{name}\"", + - Detailed warning command: `cargo report future-incompatibilities --id {id} --package {name}`", name = format!("{}:{}", package_id.name(), package_id.version()), url = manifest .metadata() diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 46db6d4a671..8064cecd241 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -871,7 +871,7 @@ impl<'cfg> DrainState<'cfg> { if !cx.bcx.build_config.build_plan { // It doesn't really matter if this fails. drop(cx.bcx.config.shell().status("Finished", message)); - self.emit_future_incompat(cx.bcx); + future_incompat::render_message(cx.bcx, &self.per_package_future_incompat_reports); } None @@ -881,10 +881,6 @@ impl<'cfg> DrainState<'cfg> { } } - fn emit_future_incompat(&mut self, bcx: &BuildContext<'_, '_>) { - future_incompat::render_message(bcx, &self.per_package_future_incompat_reports); - } - fn handle_error( &self, shell: &mut Shell, From 958f2fc962a2e31d1078d4a18f0031318eea9950 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 18 Oct 2021 12:01:07 -0500 Subject: [PATCH 11/13] Display more information in report --- src/cargo/core/compiler/future_incompat.rs | 109 +++++++++++---------- tests/testsuite/future_incompat_report.rs | 4 +- 2 files changed, 59 insertions(+), 54 deletions(-) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index 626e2afb499..1c06182ebfc 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -78,9 +78,9 @@ pub struct OnDiskReports { struct OnDiskReport { /// Unique reference to the report for the `--id` CLI flag. id: u32, - /// A (possibly empty) message describing which affected - /// packages have newer versions available - update_message: String, + /// A message describing suggestions for fixing the + /// reported issues + suggestion_message: String, /// Report, suitable for printing to the console. /// Maps package names to the corresponding report /// We use a `BTreeMap` so that the iteration order @@ -101,31 +101,22 @@ impl Default for OnDiskReports { impl OnDiskReports { /// Saves a new report. pub fn save_report( + mut self, ws: &Workspace<'_>, - update_message: String, + suggestion_message: String, per_package_reports: &[FutureIncompatReportPackage], - ) -> OnDiskReports { - let mut current_reports = match Self::load(ws) { - Ok(r) => r, - Err(e) => { - log::debug!( - "saving future-incompatible reports failed to load current reports: {:?}", - e - ); - OnDiskReports::default() - } - }; + ) { let report = OnDiskReport { - id: current_reports.next_id, - update_message, + id: self.next_id, + suggestion_message, per_package: render_report(per_package_reports), }; - current_reports.next_id += 1; - current_reports.reports.push(report); - if current_reports.reports.len() > MAX_REPORTS { - current_reports.reports.remove(0); + self.next_id += 1; + self.reports.push(report); + if self.reports.len() > MAX_REPORTS { + self.reports.remove(0); } - let on_disk = serde_json::to_vec(¤t_reports).unwrap(); + let on_disk = serde_json::to_vec(&self).unwrap(); if let Err(e) = ws .target_dir() .open_rw( @@ -146,7 +137,6 @@ impl OnDiskReports { &mut ws.config().shell(), ); } - current_reports } /// Loads the on-disk reports. @@ -201,7 +191,8 @@ impl OnDiskReports { ) })?; - let mut to_display = report.update_message.clone(); + let mut to_display = report.suggestion_message.clone(); + to_display += "\n"; let package_report = if let Some(package) = package { report @@ -248,8 +239,7 @@ fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMa ); let rendered = report.entry(package_spec).or_default(); rendered.push_str(&format!( - "The package `{}` currently triggers the following future \ - incompatibility lints:\n", +"The package `{}` currently triggers the following future incompatibility lints:\n", per_package.package_id )); for item in &per_package.items { @@ -354,6 +344,19 @@ pub fn render_message( return; } + let current_reports = match OnDiskReports::load(bcx.ws) { + Ok(r) => r, + Err(e) => { + log::debug!( + "saving future-incompatible reports failed to load current reports: {:?}", + e + ); + OnDiskReports::default() + } + }; + let report_id = current_reports.next_id; + + // Get a list of unique and sorted package name/versions. let package_ids: BTreeSet<_> = per_package_future_incompat_reports .iter() @@ -384,35 +387,28 @@ You may want to consider updating them to a newer version to see if the issue ha String::new() }; - let on_disk_reports = OnDiskReports::save_report( - bcx.ws, - update_message.clone(), - per_package_future_incompat_reports, - ); - let report_id = on_disk_reports.last_id(); - - if bcx.build_config.future_incompat_report { - let upstream_info = package_ids - .iter() - .map(|package_id| { - let manifest = bcx.packages.get_one(*package_id).unwrap().manifest(); - format!( - " + let upstream_info = package_ids + .iter() + .map(|package_id| { + let manifest = bcx.packages.get_one(*package_id).unwrap().manifest(); + format!( + " - {name} - Repository: {url} - Detailed warning command: `cargo report future-incompatibilities --id {id} --package {name}`", - name = format!("{}:{}", package_id.name(), package_id.version()), - url = manifest - .metadata() - .repository - .as_deref() - .unwrap_or(""), - id = report_id, - ) - }) - .collect::>() + name = format!("{}:{}", package_id.name(), package_id.version()), + url = manifest + .metadata() + .repository + .as_deref() + .unwrap_or(""), + id = report_id, + ) + }) + .collect::>() .join("\n"); - drop(bcx.config.shell().note(&format!( + + let suggestion_message = format!( " To solve this problem, you can try the following approaches: @@ -430,8 +426,17 @@ https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch ", upstream_info = upstream_info, update_message = update_message, - ))); + ); + + current_reports.save_report( + bcx.ws, + suggestion_message.clone(), + per_package_future_incompat_reports, + ); + + if bcx.build_config.future_incompat_report { + drop(bcx.config.shell().note(&suggestion_message)); drop(bcx.config.shell().note(&format!( "this report can be shown with `cargo report \ future-incompatibilities -Z future-incompat-report --id {}`", diff --git a/tests/testsuite/future_incompat_report.rs b/tests/testsuite/future_incompat_report.rs index a11c38cd61c..dde63560074 100644 --- a/tests/testsuite/future_incompat_report.rs +++ b/tests/testsuite/future_incompat_report.rs @@ -221,14 +221,14 @@ fn test_multi_crate() { .masquerade_as_nightly_cargo() .with_stdout_contains("The package `first-dep v0.0.1` currently triggers the following future incompatibility lints:") .with_stdout_contains(FUTURE_OUTPUT) - .with_stdout_does_not_contain("[..]second-dep[..]") + .with_stdout_does_not_contain("[..]second-dep-0.0.2/src[..]") .run(); p.cargo("report future-incompatibilities").arg("--package").arg("second-dep:0.0.2").arg("-Zunstable-options").arg("-Zfuture-incompat-report") .masquerade_as_nightly_cargo() .with_stdout_contains("The package `second-dep v0.0.2` currently triggers the following future incompatibility lints:") .with_stdout_contains(FUTURE_OUTPUT) - .with_stdout_does_not_contain("[..]first-dep[..]") + .with_stdout_does_not_contain("[..]first-dep-0.0.1/src[..]") .run(); } From 5c3b38086fe8baccfd6eb8e3c70d2426821e6607 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 18 Oct 2021 12:06:23 -0500 Subject: [PATCH 12/13] Adjust comments --- src/cargo/core/compiler/future_incompat.rs | 11 +++++++---- src/cargo/core/compiler/job_queue.rs | 2 +- tests/testsuite/future_incompat_report.rs | 1 - 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index 1c06182ebfc..a8af2d5ee9e 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -254,9 +254,9 @@ fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMa report } -// Returns a pair (compatible_updates, incompatible_updates), -// of semver-compatible and semver-incompatible update versions, -// respectively. +/// Returns a user-readable message explaining which of +/// the packages in `package_ids` have updates available. +/// This is best-effort - if an error occurs, `None` will be returned. fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option { // This in general ignores all errors since this is opportunistic. let _lock = ws.config().acquire_package_cache_lock().ok()?; @@ -312,7 +312,10 @@ fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet) -> Option< Some(updates) } -pub fn render_message( +/// Writes a future-incompat report to disk, using the per-package +/// reports gathered during the build. If requested by the user, +/// a message is also displayed in the build output. +pub fn save_and_display_report( bcx: &BuildContext<'_, '_>, per_package_future_incompat_reports: &[FutureIncompatReportPackage], ) { diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index 8064cecd241..ed9c20fed36 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -871,7 +871,7 @@ impl<'cfg> DrainState<'cfg> { if !cx.bcx.build_config.build_plan { // It doesn't really matter if this fails. drop(cx.bcx.config.shell().status("Finished", message)); - future_incompat::render_message(cx.bcx, &self.per_package_future_incompat_reports); + future_incompat::save_and_display_report(cx.bcx, &self.per_package_future_incompat_reports); } None diff --git a/tests/testsuite/future_incompat_report.rs b/tests/testsuite/future_incompat_report.rs index dde63560074..8c10f06f0a3 100644 --- a/tests/testsuite/future_incompat_report.rs +++ b/tests/testsuite/future_incompat_report.rs @@ -269,7 +269,6 @@ fn test_multi_crate() { .exec_with_output() .unwrap(); let output = std::str::from_utf8(&output.stdout).unwrap(); - //if true { panic!("Got output: \n{}", output) } assert!(output.starts_with("The following warnings were discovered")); let mut lines = output .lines() From 7ffba677fd0f0c29d076df74ab354596c4aa7164 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 18 Oct 2021 12:06:32 -0500 Subject: [PATCH 13/13] Run fmt --- src/cargo/core/compiler/future_incompat.rs | 12 +++++------- src/cargo/core/compiler/job_queue.rs | 5 ++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/cargo/core/compiler/future_incompat.rs b/src/cargo/core/compiler/future_incompat.rs index a8af2d5ee9e..94d70209c45 100644 --- a/src/cargo/core/compiler/future_incompat.rs +++ b/src/cargo/core/compiler/future_incompat.rs @@ -239,7 +239,7 @@ fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMa ); let rendered = report.entry(package_spec).or_default(); rendered.push_str(&format!( -"The package `{}` currently triggers the following future incompatibility lints:\n", + "The package `{}` currently triggers the following future incompatibility lints:\n", per_package.package_id )); for item in &per_package.items { @@ -359,7 +359,6 @@ pub fn save_and_display_report( }; let report_id = current_reports.next_id; - // Get a list of unique and sorted package name/versions. let package_ids: BTreeSet<_> = per_package_future_incompat_reports .iter() @@ -409,10 +408,10 @@ You may want to consider updating them to a newer version to see if the issue ha ) }) .collect::>() - .join("\n"); + .join("\n"); let suggestion_message = format!( - " + " To solve this problem, you can try the following approaches: {update_message} @@ -427,11 +426,10 @@ section in `Cargo.toml` to use your own version of the dependency. For more information, see: https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section ", - upstream_info = upstream_info, - update_message = update_message, + upstream_info = upstream_info, + update_message = update_message, ); - current_reports.save_report( bcx.ws, suggestion_message.clone(), diff --git a/src/cargo/core/compiler/job_queue.rs b/src/cargo/core/compiler/job_queue.rs index ed9c20fed36..322205d6ebb 100644 --- a/src/cargo/core/compiler/job_queue.rs +++ b/src/cargo/core/compiler/job_queue.rs @@ -871,7 +871,10 @@ impl<'cfg> DrainState<'cfg> { if !cx.bcx.build_config.build_plan { // It doesn't really matter if this fails. drop(cx.bcx.config.shell().status("Finished", message)); - future_incompat::save_and_display_report(cx.bcx, &self.per_package_future_incompat_reports); + future_incompat::save_and_display_report( + cx.bcx, + &self.per_package_future_incompat_reports, + ); } None