Skip to content

Commit 266a5ef

Browse files
committed
Auto merge of #13561 - epage:msrv-report, r=weihanglo
feat: Report some dependency changes on any command ### What does this PR try to resolve? The goal is to help users be informed when they are held back by either MSRV or semver. Fixes #13539 ### How should we test and review this PR? ### Additional information
2 parents d438c80 + 4ab2797 commit 266a5ef

File tree

166 files changed

+1327
-309
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

166 files changed

+1327
-309
lines changed

crates/cargo-test-support/src/compare.rs

+1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ fn substitute_macros(input: &str) -> String {
204204
("[SCRAPING]", " Scraping"),
205205
("[FRESH]", " Fresh"),
206206
("[DIRTY]", " Dirty"),
207+
("[LOCKING]", " Locking"),
207208
("[UPDATING]", " Updating"),
208209
("[ADDING]", " Adding"),
209210
("[REMOVING]", " Removing"),

src/cargo/ops/cargo_generate_lockfile.rs

+201-30
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use crate::util::cache_lock::CacheLockMode;
1010
use crate::util::context::GlobalContext;
1111
use crate::util::style;
1212
use crate::util::CargoResult;
13-
use anstyle::Style;
1413
use std::cmp::Ordering;
1514
use std::collections::{BTreeMap, HashSet};
1615
use tracing::debug;
@@ -26,17 +25,19 @@ pub struct UpdateOptions<'a> {
2625

2726
pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
2827
let mut registry = PackageRegistry::new(ws.gctx())?;
28+
let previous_resolve = None;
2929
let mut resolve = ops::resolve_with_previous(
3030
&mut registry,
3131
ws,
3232
&CliFeatures::new_all(true),
3333
HasDevUnits::Yes,
34-
None,
34+
previous_resolve,
3535
None,
3636
&[],
3737
true,
3838
)?;
3939
ops::write_pkg_lockfile(ws, &mut resolve)?;
40+
print_lockfile_changes(ws.gctx(), previous_resolve, &resolve, &mut registry)?;
4041
Ok(())
4142
}
4243

@@ -154,7 +155,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
154155
true,
155156
)?;
156157

157-
print_lockfile_update(opts.gctx, &previous_resolve, &resolve, &mut registry)?;
158+
print_lockfile_updates(opts.gctx, &previous_resolve, &resolve, &mut registry)?;
158159
if opts.dry_run {
159160
opts.gctx
160161
.shell()
@@ -165,30 +166,177 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
165166
Ok(())
166167
}
167168

168-
fn print_lockfile_update(
169+
pub fn print_lockfile_changes(
170+
gctx: &GlobalContext,
171+
previous_resolve: Option<&Resolve>,
172+
resolve: &Resolve,
173+
registry: &mut PackageRegistry<'_>,
174+
) -> CargoResult<()> {
175+
if let Some(previous_resolve) = previous_resolve {
176+
print_lockfile_sync(gctx, previous_resolve, resolve, registry)
177+
} else {
178+
print_lockfile_generation(gctx, resolve, registry)
179+
}
180+
}
181+
182+
fn print_lockfile_generation(
183+
gctx: &GlobalContext,
184+
resolve: &Resolve,
185+
registry: &mut PackageRegistry<'_>,
186+
) -> CargoResult<()> {
187+
let mut shell = gctx.shell();
188+
189+
let diff = PackageDiff::new(&resolve);
190+
let num_pkgs: usize = diff.iter().map(|d| d.added.len()).sum();
191+
if num_pkgs <= 1 {
192+
// just ourself, nothing worth reporting
193+
return Ok(());
194+
}
195+
shell.status("Locking", format!("{num_pkgs} packages"))?;
196+
197+
for diff in diff {
198+
fn format_latest(version: semver::Version) -> String {
199+
let warn = style::WARN;
200+
format!(" {warn}(latest: v{version}){warn:#}")
201+
}
202+
let possibilities = if let Some(query) = diff.alternatives_query() {
203+
loop {
204+
match registry.query_vec(&query, QueryKind::Exact) {
205+
std::task::Poll::Ready(res) => {
206+
break res?;
207+
}
208+
std::task::Poll::Pending => registry.block_until_ready()?,
209+
}
210+
}
211+
} else {
212+
vec![]
213+
};
214+
215+
for package in diff.added.iter() {
216+
let latest = if !possibilities.is_empty() {
217+
possibilities
218+
.iter()
219+
.map(|s| s.as_summary())
220+
.filter(|s| is_latest(s.version(), package.version()))
221+
.map(|s| s.version().clone())
222+
.max()
223+
.map(format_latest)
224+
} else {
225+
None
226+
};
227+
228+
if let Some(latest) = latest {
229+
shell.status_with_color("Adding", format!("{package}{latest}"), &style::NOTE)?;
230+
}
231+
}
232+
}
233+
234+
Ok(())
235+
}
236+
237+
fn print_lockfile_sync(
169238
gctx: &GlobalContext,
170239
previous_resolve: &Resolve,
171240
resolve: &Resolve,
172241
registry: &mut PackageRegistry<'_>,
173242
) -> CargoResult<()> {
174-
// Summarize what is changing for the user.
175-
let print_change = |status: &str, msg: String, color: &Style| {
176-
gctx.shell().status_with_color(status, msg, color)
177-
};
243+
let mut shell = gctx.shell();
244+
245+
let diff = PackageDiff::diff(&previous_resolve, &resolve);
246+
let num_pkgs: usize = diff.iter().map(|d| d.added.len()).sum();
247+
if num_pkgs == 0 {
248+
return Ok(());
249+
}
250+
let plural = if num_pkgs == 1 { "" } else { "s" };
251+
shell.status("Locking", format!("{num_pkgs} package{plural}"))?;
252+
253+
for diff in diff {
254+
fn format_latest(version: semver::Version) -> String {
255+
let warn = style::WARN;
256+
format!(" {warn}(latest: v{version}){warn:#}")
257+
}
258+
let possibilities = if let Some(query) = diff.alternatives_query() {
259+
loop {
260+
match registry.query_vec(&query, QueryKind::Exact) {
261+
std::task::Poll::Ready(res) => {
262+
break res?;
263+
}
264+
std::task::Poll::Pending => registry.block_until_ready()?,
265+
}
266+
}
267+
} else {
268+
vec![]
269+
};
270+
271+
if let Some((removed, added)) = diff.change() {
272+
let latest = if !possibilities.is_empty() {
273+
possibilities
274+
.iter()
275+
.map(|s| s.as_summary())
276+
.filter(|s| is_latest(s.version(), added.version()))
277+
.map(|s| s.version().clone())
278+
.max()
279+
.map(format_latest)
280+
} else {
281+
None
282+
}
283+
.unwrap_or_default();
284+
285+
let msg = if removed.source_id().is_git() {
286+
format!(
287+
"{removed} -> #{}",
288+
&added.source_id().precise_git_fragment().unwrap()[..8],
289+
)
290+
} else {
291+
format!("{removed} -> v{}{latest}", added.version())
292+
};
293+
294+
// If versions differ only in build metadata, we call it an "update"
295+
// regardless of whether the build metadata has gone up or down.
296+
// This metadata is often stuff like git commit hashes, which are
297+
// not meaningfully ordered.
298+
if removed.version().cmp_precedence(added.version()) == Ordering::Greater {
299+
shell.status_with_color("Downgrading", msg, &style::WARN)?;
300+
} else {
301+
shell.status_with_color("Updating", msg, &style::GOOD)?;
302+
}
303+
} else {
304+
for package in diff.added.iter() {
305+
let latest = if !possibilities.is_empty() {
306+
possibilities
307+
.iter()
308+
.map(|s| s.as_summary())
309+
.filter(|s| is_latest(s.version(), package.version()))
310+
.map(|s| s.version().clone())
311+
.max()
312+
.map(format_latest)
313+
} else {
314+
None
315+
}
316+
.unwrap_or_default();
317+
318+
shell.status_with_color("Adding", format!("{package}{latest}"), &style::NOTE)?;
319+
}
320+
}
321+
}
322+
323+
Ok(())
324+
}
325+
326+
pub fn print_lockfile_updates(
327+
gctx: &GlobalContext,
328+
previous_resolve: &Resolve,
329+
resolve: &Resolve,
330+
registry: &mut PackageRegistry<'_>,
331+
) -> CargoResult<()> {
332+
let mut shell = gctx.shell();
333+
178334
let mut unchanged_behind = 0;
179335
for diff in PackageDiff::diff(&previous_resolve, &resolve) {
180336
fn format_latest(version: semver::Version) -> String {
181337
let warn = style::WARN;
182338
format!(" {warn}(latest: v{version}){warn:#}")
183339
}
184-
fn is_latest(candidate: &semver::Version, current: &semver::Version) -> bool {
185-
current < candidate
186-
// Only match pre-release if major.minor.patch are the same
187-
&& (candidate.pre.is_empty()
188-
|| (candidate.major == current.major
189-
&& candidate.minor == current.minor
190-
&& candidate.patch == current.patch))
191-
}
192340
let possibilities = if let Some(query) = diff.alternatives_query() {
193341
loop {
194342
match registry.query_vec(&query, QueryKind::Exact) {
@@ -230,13 +378,13 @@ fn print_lockfile_update(
230378
// This metadata is often stuff like git commit hashes, which are
231379
// not meaningfully ordered.
232380
if removed.version().cmp_precedence(added.version()) == Ordering::Greater {
233-
print_change("Downgrading", msg, &style::WARN)?;
381+
shell.status_with_color("Downgrading", msg, &style::WARN)?;
234382
} else {
235-
print_change("Updating", msg, &style::GOOD)?;
383+
shell.status_with_color("Updating", msg, &style::GOOD)?;
236384
}
237385
} else {
238386
for package in diff.removed.iter() {
239-
print_change("Removing", format!("{package}"), &style::ERROR)?;
387+
shell.status_with_color("Removing", format!("{package}"), &style::ERROR)?;
240388
}
241389
for package in diff.added.iter() {
242390
let latest = if !possibilities.is_empty() {
@@ -252,7 +400,7 @@ fn print_lockfile_update(
252400
}
253401
.unwrap_or_default();
254402

255-
print_change("Adding", format!("{package}{latest}"), &style::NOTE)?;
403+
shell.status_with_color("Adding", format!("{package}{latest}"), &style::NOTE)?;
256404
}
257405
}
258406
for package in &diff.unchanged {
@@ -270,8 +418,8 @@ fn print_lockfile_update(
270418

271419
if let Some(latest) = latest {
272420
unchanged_behind += 1;
273-
if gctx.shell().verbosity() == Verbosity::Verbose {
274-
gctx.shell().status_with_color(
421+
if shell.verbosity() == Verbosity::Verbose {
422+
shell.status_with_color(
275423
"Unchanged",
276424
format!("{package}{latest}"),
277425
&anstyle::Style::new().bold(),
@@ -280,13 +428,13 @@ fn print_lockfile_update(
280428
}
281429
}
282430
}
283-
if gctx.shell().verbosity() == Verbosity::Verbose {
284-
gctx.shell().note(
431+
if shell.verbosity() == Verbosity::Verbose {
432+
shell.note(
285433
"to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`",
286434
)?;
287435
} else {
288436
if 0 < unchanged_behind {
289-
gctx.shell().note(format!(
437+
shell.note(format!(
290438
"pass `--verbose` to see {unchanged_behind} unchanged dependencies behind latest"
291439
))?;
292440
}
@@ -295,6 +443,15 @@ fn print_lockfile_update(
295443
Ok(())
296444
}
297445

446+
fn is_latest(candidate: &semver::Version, current: &semver::Version) -> bool {
447+
current < candidate
448+
// Only match pre-release if major.minor.patch are the same
449+
&& (candidate.pre.is_empty()
450+
|| (candidate.major == current.major
451+
&& candidate.minor == current.minor
452+
&& candidate.patch == current.patch))
453+
}
454+
298455
fn fill_with_deps<'a>(
299456
resolve: &'a Resolve,
300457
dep: PackageId,
@@ -319,11 +476,21 @@ pub struct PackageDiff {
319476
}
320477

321478
impl PackageDiff {
322-
pub fn diff(previous_resolve: &Resolve, resolve: &Resolve) -> Vec<Self> {
323-
fn key(dep: PackageId) -> (&'static str, SourceId) {
324-
(dep.name().as_str(), dep.source_id())
479+
pub fn new(resolve: &Resolve) -> Vec<Self> {
480+
let mut changes = BTreeMap::new();
481+
let empty = Self::default();
482+
for dep in resolve.iter() {
483+
changes
484+
.entry(Self::key(dep))
485+
.or_insert_with(|| empty.clone())
486+
.added
487+
.push(dep);
325488
}
326489

490+
changes.into_iter().map(|(_, v)| v).collect()
491+
}
492+
493+
pub fn diff(previous_resolve: &Resolve, resolve: &Resolve) -> Vec<Self> {
327494
fn vec_subset(a: &[PackageId], b: &[PackageId]) -> Vec<PackageId> {
328495
a.iter().filter(|a| !contains_id(b, a)).cloned().collect()
329496
}
@@ -364,14 +531,14 @@ impl PackageDiff {
364531
let empty = Self::default();
365532
for dep in previous_resolve.iter() {
366533
changes
367-
.entry(key(dep))
534+
.entry(Self::key(dep))
368535
.or_insert_with(|| empty.clone())
369536
.removed
370537
.push(dep);
371538
}
372539
for dep in resolve.iter() {
373540
changes
374-
.entry(key(dep))
541+
.entry(Self::key(dep))
375542
.or_insert_with(|| empty.clone())
376543
.added
377544
.push(dep);
@@ -397,6 +564,10 @@ impl PackageDiff {
397564
changes.into_iter().map(|(_, v)| v).collect()
398565
}
399566

567+
fn key(dep: PackageId) -> (&'static str, SourceId) {
568+
(dep.name().as_str(), dep.source_id())
569+
}
570+
400571
/// Guess if a package upgraded/downgraded
401572
///
402573
/// All `PackageDiff` knows is that entries were added/removed within [`Resolve`].

0 commit comments

Comments
 (0)