Skip to content

Commit c1fa840

Browse files
committed
Auto merge of #14461 - epage:actionable, r=Muscraft
fix(resolve): With `latest` message, differentiate actionable updates ### What does this PR try to resolve? Instead of always listing the absolutely latest version as a warning color, we now differentiate - compatible updates are always actionable - incompatible, direct deps are always actionable These get reported and made yellow while non-actionable messages are unstyled. ### How should we test and review this PR? I just used a broad stroke to say "compatible" in the message means "semver compatible" and use `^` - We could focus on "compatible with dependent version reqs" which is what will be most actionable but seeing if we can get away without having to track all in-coming version reqs. - We could be more nuanced in language but the more verbose we are, the more we take away from higher priority messages ### Additional information This is not intended as *the* solution for #13908 though it experiments with what to do for that. This is prep work for improved MSRV reporting where we will differentiate this further by only considering MSRV-compatible updates as actionable (or rustc-compatible when not using MSRV-aware reslver).
2 parents 33714c4 + 353cd87 commit c1fa840

File tree

7 files changed

+83
-37
lines changed

7 files changed

+83
-37
lines changed

src/cargo/ops/cargo_update.rs

+57-19
Original file line numberDiff line numberDiff line change
@@ -523,9 +523,8 @@ fn print_lockfile_generation(
523523
vec![]
524524
};
525525

526-
let package_id = change.package_id;
527526
let required_rust_version = report_required_rust_version(resolve, change);
528-
let latest = report_latest(&possibilities, package_id);
527+
let latest = report_latest(&possibilities, change);
529528
let note = required_rust_version.or(latest);
530529

531530
if let Some(note) = note {
@@ -587,9 +586,8 @@ fn print_lockfile_sync(
587586
vec![]
588587
};
589588

590-
let package_id = change.package_id;
591589
let required_rust_version = report_required_rust_version(resolve, change);
592-
let latest = report_latest(&possibilities, package_id);
590+
let latest = report_latest(&possibilities, change);
593591
let note = required_rust_version.or(latest).unwrap_or_default();
594592

595593
ws.gctx().shell().status_with_color(
@@ -641,9 +639,8 @@ fn print_lockfile_updates(
641639
PackageChangeKind::Added
642640
| PackageChangeKind::Upgraded
643641
| PackageChangeKind::Downgraded => {
644-
let package_id = change.package_id;
645642
let required_rust_version = report_required_rust_version(resolve, change);
646-
let latest = report_latest(&possibilities, package_id);
643+
let latest = report_latest(&possibilities, change);
647644
let note = required_rust_version.or(latest).unwrap_or_default();
648645

649646
ws.gctx().shell().status_with_color(
@@ -660,9 +657,8 @@ fn print_lockfile_updates(
660657
)?;
661658
}
662659
PackageChangeKind::Unchanged => {
663-
let package_id = change.package_id;
664660
let required_rust_version = report_required_rust_version(resolve, change);
665-
let latest = report_latest(&possibilities, package_id);
661+
let latest = report_latest(&possibilities, change);
666662
let note = required_rust_version.as_deref().or(latest.as_deref());
667663

668664
if let Some(note) = note {
@@ -754,23 +750,42 @@ fn report_required_rust_version(resolve: &Resolve, change: &PackageChange) -> Op
754750
))
755751
}
756752

757-
fn report_latest(possibilities: &[IndexSummary], package: PackageId) -> Option<String> {
758-
if !package.source_id().is_registry() {
753+
fn report_latest(possibilities: &[IndexSummary], change: &PackageChange) -> Option<String> {
754+
let package_id = change.package_id;
755+
if !package_id.source_id().is_registry() {
759756
return None;
760757
}
761758

762-
possibilities
759+
let version_req = package_id.version().to_caret_req();
760+
if let Some(version) = possibilities
763761
.iter()
764762
.map(|s| s.as_summary())
765-
.filter(|s| is_latest(s.version(), package.version()))
763+
.filter(|s| package_id.version() != s.version() && version_req.matches(s.version()))
766764
.map(|s| s.version().clone())
767765
.max()
768-
.map(format_latest)
769-
}
766+
{
767+
let warn = style::WARN;
768+
let report = format!(" {warn}(latest compatible: v{version}){warn:#}");
769+
return Some(report);
770+
}
771+
772+
if let Some(version) = possibilities
773+
.iter()
774+
.map(|s| s.as_summary())
775+
.filter(|s| is_latest(s.version(), package_id.version()))
776+
.map(|s| s.version().clone())
777+
.max()
778+
{
779+
let warn = if change.is_transitive.unwrap_or(true) {
780+
Default::default()
781+
} else {
782+
style::WARN
783+
};
784+
let report = format!(" {warn}(latest: v{version}){warn:#}");
785+
return Some(report);
786+
}
770787

771-
fn format_latest(version: semver::Version) -> String {
772-
let warn = style::WARN;
773-
format!(" {warn}(latest: v{version}){warn:#}")
788+
None
774789
}
775790

776791
fn is_latest(candidate: &semver::Version, current: &semver::Version) -> bool {
@@ -803,13 +818,14 @@ struct PackageChange {
803818
previous_id: Option<PackageId>,
804819
kind: PackageChangeKind,
805820
is_member: Option<bool>,
821+
is_transitive: Option<bool>,
806822
required_rust_version: Option<PartialVersion>,
807823
}
808824

809825
impl PackageChange {
810826
pub fn new(ws: &Workspace<'_>, resolve: &Resolve) -> IndexMap<PackageId, Self> {
811827
let diff = PackageDiff::new(resolve);
812-
Self::with_diff(diff, ws)
828+
Self::with_diff(diff, ws, resolve)
813829
}
814830

815831
pub fn diff(
@@ -818,12 +834,13 @@ impl PackageChange {
818834
resolve: &Resolve,
819835
) -> IndexMap<PackageId, Self> {
820836
let diff = PackageDiff::diff(previous_resolve, resolve);
821-
Self::with_diff(diff, ws)
837+
Self::with_diff(diff, ws, resolve)
822838
}
823839

824840
fn with_diff(
825841
diff: impl Iterator<Item = PackageDiff>,
826842
ws: &Workspace<'_>,
843+
resolve: &Resolve,
827844
) -> IndexMap<PackageId, Self> {
828845
let member_ids: HashSet<_> = ws.members().map(|p| p.package_id()).collect();
829846

@@ -842,35 +859,41 @@ impl PackageChange {
842859
PackageChangeKind::Upgraded
843860
};
844861
let is_member = Some(member_ids.contains(&package_id));
862+
let is_transitive = Some(true);
845863
let change = Self {
846864
package_id,
847865
previous_id: Some(previous_id),
848866
kind,
849867
is_member,
868+
is_transitive,
850869
required_rust_version: None,
851870
};
852871
changes.insert(change.package_id, change);
853872
} else {
854873
for package_id in diff.removed {
855874
let kind = PackageChangeKind::Removed;
856875
let is_member = None;
876+
let is_transitive = None;
857877
let change = Self {
858878
package_id,
859879
previous_id: None,
860880
kind,
861881
is_member,
882+
is_transitive,
862883
required_rust_version: None,
863884
};
864885
changes.insert(change.package_id, change);
865886
}
866887
for package_id in diff.added {
867888
let kind = PackageChangeKind::Added;
868889
let is_member = Some(member_ids.contains(&package_id));
890+
let is_transitive = Some(true);
869891
let change = Self {
870892
package_id,
871893
previous_id: None,
872894
kind,
873895
is_member,
896+
is_transitive,
874897
required_rust_version: None,
875898
};
876899
changes.insert(change.package_id, change);
@@ -879,17 +902,32 @@ impl PackageChange {
879902
for package_id in diff.unchanged {
880903
let kind = PackageChangeKind::Unchanged;
881904
let is_member = Some(member_ids.contains(&package_id));
905+
let is_transitive = Some(true);
882906
let change = Self {
883907
package_id,
884908
previous_id: None,
885909
kind,
886910
is_member,
911+
is_transitive,
887912
required_rust_version: None,
888913
};
889914
changes.insert(change.package_id, change);
890915
}
891916
}
892917

918+
for member_id in &member_ids {
919+
let Some(change) = changes.get_mut(member_id) else {
920+
continue;
921+
};
922+
change.is_transitive = Some(false);
923+
for (direct_dep_id, _) in resolve.deps(*member_id) {
924+
let Some(change) = changes.get_mut(&direct_dep_id) else {
925+
continue;
926+
};
927+
change.is_transitive = Some(false);
928+
}
929+
}
930+
893931
changes
894932
}
895933

src/cargo/util/semver_ext.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,26 @@ use std::fmt::{self, Display};
55
pub trait VersionExt {
66
fn is_prerelease(&self) -> bool;
77

8-
fn to_exact_req(&self) -> VersionReq;
8+
fn to_req(&self, op: Op) -> VersionReq;
9+
10+
fn to_exact_req(&self) -> VersionReq {
11+
self.to_req(Op::Exact)
12+
}
13+
14+
fn to_caret_req(&self) -> VersionReq {
15+
self.to_req(Op::Caret)
16+
}
917
}
1018

1119
impl VersionExt for Version {
1220
fn is_prerelease(&self) -> bool {
1321
!self.pre.is_empty()
1422
}
1523

16-
fn to_exact_req(&self) -> VersionReq {
24+
fn to_req(&self, op: Op) -> VersionReq {
1725
VersionReq {
1826
comparators: vec![Comparator {
19-
op: Op::Exact,
27+
op,
2028
major: self.major,
2129
minor: Some(self.minor),
2230
patch: Some(self.patch),

tests/testsuite/direct_minimal_versions.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fn simple() {
3333
.with_stderr_data(str![[r#"
3434
[UPDATING] `dummy-registry` index
3535
[LOCKING] 1 package
36-
[ADDING] dep v1.0.0 (latest: v1.1.0)
36+
[ADDING] dep v1.0.0 (latest compatible: v1.1.0)
3737
3838
"#]])
3939
.run();
@@ -122,7 +122,7 @@ fn yanked() {
122122
.with_stderr_data(str![[r#"
123123
[UPDATING] `dummy-registry` index
124124
[LOCKING] 1 package
125-
[ADDING] dep v1.1.0 (latest: v1.2.0)
125+
[ADDING] dep v1.1.0 (latest compatible: v1.2.0)
126126
127127
"#]])
128128
.run();
@@ -176,7 +176,7 @@ fn indirect() {
176176
.with_stderr_data(str![[r#"
177177
[UPDATING] `dummy-registry` index
178178
[LOCKING] 2 packages
179-
[ADDING] direct v1.0.0 (latest: v1.1.0)
179+
[ADDING] direct v1.0.0 (latest compatible: v1.1.0)
180180
181181
"#]])
182182
.run();

tests/testsuite/minimal_versions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fn minimal_version_cli() {
3535
.with_stderr_data(str![[r#"
3636
[UPDATING] `dummy-registry` index
3737
[LOCKING] 1 package to earliest compatible version
38-
[ADDING] dep v1.0.0 (latest: v1.1.0)
38+
[ADDING] dep v1.0.0 (latest compatible: v1.1.0)
3939
4040
"#]])
4141
.run();

tests/testsuite/rust_version.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ foo v0.0.1 ([ROOT]/foo)
242242
.with_stderr_data(str![[r#"
243243
[UPDATING] `dummy-registry` index
244244
[LOCKING] 2 packages to latest Rust 1.60.0 compatible versions
245-
[ADDING] newer-and-older v1.5.0 (latest: v1.6.0)
245+
[ADDING] newer-and-older v1.5.0 (latest compatible: v1.6.0)
246246
[ADDING] only-newer v1.6.0 (requires Rust 1.65.0)
247247
248248
"#]])
@@ -319,7 +319,7 @@ foo v0.0.1 ([ROOT]/foo)
319319
.with_stderr_data(str![[r#"
320320
[UPDATING] `dummy-registry` index
321321
[LOCKING] 2 packages to latest Rust 1.60.0 compatible versions
322-
[ADDING] newer-and-older v1.5.0 (latest: v1.6.0)
322+
[ADDING] newer-and-older v1.5.0 (latest compatible: v1.6.0)
323323
[ADDING] only-newer v1.6.0 (requires Rust 1.2345)
324324
325325
"#]])
@@ -490,7 +490,7 @@ higher v0.0.1 ([ROOT]/foo)
490490
.with_stderr_data(str![[r#"
491491
[UPDATING] `dummy-registry` index
492492
[LOCKING] 2 packages to latest Rust 1.50.0 compatible versions
493-
[ADDING] newer-and-older v1.5.0 (latest: v1.6.0)
493+
[ADDING] newer-and-older v1.5.0 (latest compatible: v1.6.0)
494494
[ADDING] only-newer v1.6.0 (requires Rust 1.65.0)
495495
496496
"#]])
@@ -619,7 +619,7 @@ fn resolve_edition2024() {
619619
.with_stderr_data(str![[r#"
620620
[UPDATING] `dummy-registry` index
621621
[LOCKING] 2 packages to latest Rust 1.60.0 compatible versions
622-
[ADDING] newer-and-older v1.5.0 (latest: v1.6.0)
622+
[ADDING] newer-and-older v1.5.0 (latest compatible: v1.6.0)
623623
[ADDING] only-newer v1.6.0 (requires Rust 1.65.0)
624624
625625
"#]])
@@ -723,7 +723,7 @@ fn resolve_v3() {
723723
.with_stderr_data(str![[r#"
724724
[UPDATING] `dummy-registry` index
725725
[LOCKING] 2 packages to latest Rust 1.60.0 compatible versions
726-
[ADDING] newer-and-older v1.5.0 (latest: v1.6.0)
726+
[ADDING] newer-and-older v1.5.0 (latest compatible: v1.6.0)
727727
[ADDING] only-newer v1.6.0 (requires Rust 1.65.0)
728728
729729
"#]])
@@ -871,7 +871,7 @@ fn update_msrv_resolve() {
871871
.with_stderr_data(str![[r#"
872872
[UPDATING] `dummy-registry` index
873873
[LOCKING] 1 package to latest Rust 1.60.0 compatible version
874-
[ADDING] bar v1.5.0 (latest: v1.6.0)
874+
[ADDING] bar v1.5.0 (latest compatible: v1.6.0)
875875
876876
"#]])
877877
.run();
@@ -932,7 +932,7 @@ fn update_precise_overrides_msrv_resolver() {
932932
.with_stderr_data(str![[r#"
933933
[UPDATING] `dummy-registry` index
934934
[LOCKING] 1 package to latest Rust 1.60.0 compatible version
935-
[ADDING] bar v1.5.0 (latest: v1.6.0)
935+
[ADDING] bar v1.5.0 (latest compatible: v1.6.0)
936936
937937
"#]])
938938
.run();
@@ -1019,7 +1019,7 @@ foo v0.0.1 ([ROOT]/foo)
10191019
.with_stderr_data(str![[r#"
10201020
[UPDATING] `dummy-registry` index
10211021
[LOCKING] 2 packages to latest Rust 1.60.0 compatible versions
1022-
[ADDING] newer-and-older v1.5.0 (latest: v1.6.0)
1022+
[ADDING] newer-and-older v1.5.0 (latest compatible: v1.6.0)
10231023
[ADDING] only-newer v1.6.0 (requires Rust 1.65.0)
10241024
[DOWNLOADING] crates ...
10251025
[DOWNLOADED] newer-and-older v1.5.0 (registry `dummy-registry`)

tests/testsuite/update.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1534,7 +1534,7 @@ fn report_behind() {
15341534
[UPDATING] `dummy-registry` index
15351535
[LOCKING] 1 package to latest compatible version
15361536
[UPDATING] breaking v0.1.0 -> v0.1.1 (latest: v0.2.0)
1537-
[UNCHANGED] pre v1.0.0-alpha.0 (latest: v1.0.0-alpha.1)
1537+
[UNCHANGED] pre v1.0.0-alpha.0 (latest compatible: v1.0.0-alpha.1)
15381538
[UNCHANGED] two-ver v0.1.0 (latest: v0.2.0)
15391539
[NOTE] to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`
15401540
[WARNING] not updating lockfile due to dry run
@@ -1559,7 +1559,7 @@ fn report_behind() {
15591559
[UPDATING] `dummy-registry` index
15601560
[LOCKING] 0 packages to latest compatible versions
15611561
[UNCHANGED] breaking v0.1.1 (latest: v0.2.0)
1562-
[UNCHANGED] pre v1.0.0-alpha.0 (latest: v1.0.0-alpha.1)
1562+
[UNCHANGED] pre v1.0.0-alpha.0 (latest compatible: v1.0.0-alpha.1)
15631563
[UNCHANGED] two-ver v0.1.0 (latest: v0.2.0)
15641564
[NOTE] to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`
15651565
[WARNING] not updating lockfile due to dry run

tests/testsuite/workspaces.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ fn share_dependencies() {
697697
.with_stderr_data(str![[r#"
698698
[UPDATING] `dummy-registry` index
699699
[LOCKING] 1 package to latest compatible version
700-
[ADDING] dep1 v0.1.3 (latest: v0.1.8)
700+
[ADDING] dep1 v0.1.3 (latest compatible: v0.1.8)
701701
[DOWNLOADING] crates ...
702702
[DOWNLOADED] dep1 v0.1.3 (registry `dummy-registry`)
703703
[CHECKING] dep1 v0.1.3

0 commit comments

Comments
 (0)