Skip to content

Commit a29b0b7

Browse files
Jess Wassmeta-codesync[bot]
authored andcommitted
Broaden owning edge to semver lane from version
Summary: Previous edge narrowing was keyed by the exact parent package version. That kept lockfile choices stable for an unchanged parent, but a semver-compatible parent update could lose its prior dependency lane and re-evaluate a broad dependency range from scratch. Broaden the parent side of the previous-edge key to the package's semver compatibility lane, using the same semver lane model used for dependency requirement narrowing. This lets a semver-compatible update of package A continue to reuse A's previous narrowed dependency lane, while semver-incompatible parent updates remain isolated. Add coverage for `a 1.0.0` to `a 1.0.1` retaining the previous `b 1.x` lane for a broad `b >=1, <3` dependency. Reviewed By: dtolnay Differential Revision: D116650994 fbshipit-source-id: a4aab7427e3bd9eff114603e87fb3ac912c9efd8
1 parent ed340dc commit a29b0b7

2 files changed

Lines changed: 117 additions & 33 deletions

File tree

src/cargo/deterministic_resolve.rs

Lines changed: 101 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ use semver::VersionReq;
3939
use crate::Paths;
4040
use crate::fixups::ResolverDependencyFixup;
4141
use crate::fixups::resolver_fixups_for_package;
42+
use crate::semver_ext::CompatibilityLane;
4243
use crate::semver_ext::version_bounds_subset;
44+
use crate::semver_ext::version_compatibility_lane;
4345
use crate::semver_ext::version_req_bounds;
4446
use crate::semver_ext::version_req_is_broad;
4547
use crate::semver_ext::version_req_to_compatibility_lane;
@@ -64,7 +66,7 @@ struct DependencyEdgeKey {
6466
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
6567
struct PackageKey {
6668
name: String,
67-
version: Version,
69+
compatibility_lane: CompatibilityLane,
6870
source: SourceKey,
6971
}
7072

@@ -162,9 +164,11 @@ impl<'gctx, S: CargoSource> DeterministicSource<'gctx, S> {
162164
}
163165
}
164166

165-
let narrowed_req = if let Some(previous) =
166-
self.previous_locked_edge(parent, &dependency, &effective_req)
167-
{
167+
let previous = match self.previous_locked_edge(parent, &dependency, &effective_req) {
168+
Ok(previous) => previous,
169+
Err(err) => return Poll::Ready(Err(err)),
170+
};
171+
let narrowed_req = if let Some(previous) = previous {
168172
match version_req_to_compatibility_lane(&effective_req, previous.version()) {
169173
Ok(req) => req,
170174
Err(err) => return Poll::Ready(Err(err)),
@@ -227,16 +231,17 @@ impl<'gctx, S: CargoSource> DeterministicSource<'gctx, S> {
227231
parent: PackageId,
228232
dependency: &Dependency,
229233
effective_req: &VersionReq,
230-
) -> Option<PackageId> {
231-
let previous = self
232-
.context
233-
.previous_edges
234-
.get(&dependency_edge_key(parent, dependency))?;
234+
) -> anyhow::Result<Option<PackageId>> {
235+
let key = dependency_edge_key(parent, dependency)?;
236+
let previous = self.context.previous_edges.get(&key);
237+
let Some(previous) = previous else {
238+
return Ok(None);
239+
};
235240
if previous.name() == dependency.package_name() && effective_req.matches(previous.version())
236241
{
237-
Some(*previous)
242+
Ok(Some(*previous))
238243
} else {
239-
None
244+
Ok(None)
240245
}
241246
}
242247

@@ -335,31 +340,34 @@ fn query_candidate_versions(
335340
Poll::Ready(Ok(candidate_versions_from_summaries(candidates)))
336341
}
337342

338-
fn dependency_edge_key(parent: PackageId, dependency: &Dependency) -> DependencyEdgeKey {
339-
DependencyEdgeKey {
340-
parent: package_key(parent),
343+
fn dependency_edge_key(
344+
parent: PackageId,
345+
dependency: &Dependency,
346+
) -> anyhow::Result<DependencyEdgeKey> {
347+
Ok(DependencyEdgeKey {
348+
parent: package_key(parent)?,
341349
dependency_name_in_toml: dependency.name_in_toml().as_str().to_owned(),
342350
dependency_source: source_key(dependency.source_id()),
343-
}
351+
})
344352
}
345353

346354
fn selected_package_edge_key(
347355
parent: PackageId,
348356
selected_dependency: PackageId,
349-
) -> DependencyEdgeKey {
350-
DependencyEdgeKey {
351-
parent: package_key(parent),
357+
) -> anyhow::Result<DependencyEdgeKey> {
358+
Ok(DependencyEdgeKey {
359+
parent: package_key(parent)?,
352360
dependency_name_in_toml: selected_dependency.name().as_str().to_owned(),
353361
dependency_source: source_key(selected_dependency.source_id()),
354-
}
362+
})
355363
}
356364

357-
fn package_key(package: PackageId) -> PackageKey {
358-
PackageKey {
365+
fn package_key(package: PackageId) -> anyhow::Result<PackageKey> {
366+
Ok(PackageKey {
359367
name: package.name().as_str().to_owned(),
360-
version: package.version().clone(),
368+
compatibility_lane: version_compatibility_lane(package.version())?,
361369
source: source_key(package.source_id()),
362-
}
370+
})
363371
}
364372

365373
fn source_key(source_id: SourceId) -> SourceKey {
@@ -491,6 +499,7 @@ pub(crate) fn resolve_ws_deterministically_with_original_sources<'gctx>(
491499
let previous_edges = previous_resolve
492500
.as_ref()
493501
.map(previous_dependency_edges)
502+
.transpose()?
494503
.unwrap_or_default();
495504
let root_patch_summaries = root_patch_summaries(workspace, &source_config)?;
496505
let mut source_ids = deterministic_source_ids(workspace, previous_resolve.as_ref(), gctx)?;
@@ -775,24 +784,27 @@ fn validate_version_req_subset(narrowed: &VersionReq, original: &VersionReq) ->
775784

776785
fn previous_dependency_edges(
777786
previous_resolve: &cargo::core::resolver::Resolve,
778-
) -> BTreeMap<DependencyEdgeKey, PackageId> {
787+
) -> anyhow::Result<BTreeMap<DependencyEdgeKey, PackageId>> {
779788
let mut edges = BTreeMap::new();
780789
for parent in previous_resolve.iter() {
781790
for (selected_dependency, dependencies) in previous_resolve.deps(parent) {
782791
let mut inserted_dependency = false;
783792
for dependency in dependencies {
784793
inserted_dependency = true;
785-
edges.insert(dependency_edge_key(parent, dependency), selected_dependency);
794+
edges.insert(
795+
dependency_edge_key(parent, dependency)?,
796+
selected_dependency,
797+
);
786798
}
787799
if !inserted_dependency {
788800
edges.insert(
789-
selected_package_edge_key(parent, selected_dependency),
801+
selected_package_edge_key(parent, selected_dependency)?,
790802
selected_dependency,
791803
);
792804
}
793805
}
794806
}
795-
edges
807+
Ok(edges)
796808
}
797809

798810
#[cfg(test)]
@@ -1970,11 +1982,11 @@ narrow_to = "1"
19701982
);
19711983
context.previous_edges = BTreeMap::from([
19721984
(
1973-
super::dependency_edge_key(parent, &alpha_01x),
1985+
super::dependency_edge_key(parent, &alpha_01x).unwrap(),
19741986
PackageId::try_new("alpha", "0.1.0", source_id).unwrap(),
19751987
),
19761988
(
1977-
super::dependency_edge_key(parent, &alpha_02x),
1989+
super::dependency_edge_key(parent, &alpha_02x).unwrap(),
19781990
PackageId::try_new("alpha", "0.2.0", source_id).unwrap(),
19791991
),
19801992
]);
@@ -2015,6 +2027,64 @@ narrow_to = "1"
20152027
assert_eq!(dependency_reqs["alpha-02x"], ">=0.2.0, <0.3.0");
20162028
}
20172029

2030+
#[test]
2031+
fn test_previous_locked_edges_apply_across_parent_semver_lane() {
2032+
let tempdir = tempfile::tempdir().unwrap();
2033+
let cargo_home = tempdir.path().join(".cargo");
2034+
2035+
let shell = cargo::core::Shell::new();
2036+
let mut gctx =
2037+
cargo::GlobalContext::new(shell, tempdir.path().to_owned(), cargo_home.clone());
2038+
gctx.configure(0, true, None, false, false, false, &None, &[], &[])
2039+
.unwrap();
2040+
let source_id = SourceId::crates_io(&gctx).unwrap();
2041+
let source_config = SourceConfigMap::new(&gctx).unwrap();
2042+
let previous_parent = PackageId::try_new("a", "1.0.0", source_id).unwrap();
2043+
let previous_dependency = dependency("b", ">=1, <3", source_id);
2044+
let mut context = deterministic_source_context(
2045+
source_config,
2046+
tempdir.path().to_owned(),
2047+
[source_id],
2048+
Rc::new(RefCell::new(BTreeSet::new())),
2049+
);
2050+
context.previous_edges = BTreeMap::from([(
2051+
super::dependency_edge_key(previous_parent, &previous_dependency).unwrap(),
2052+
PackageId::try_new("b", "1.2.3", source_id).unwrap(),
2053+
)]);
2054+
let mut source = DeterministicSource::new(
2055+
RecordingSource::new(
2056+
source_id,
2057+
vec![
2058+
summary_with_deps(
2059+
"a",
2060+
"1.0.1",
2061+
source_id,
2062+
vec![dependency("b", ">=1, <3", source_id)],
2063+
),
2064+
summary("b", "1.2.3", source_id),
2065+
summary("b", "2.0.0", source_id),
2066+
],
2067+
),
2068+
context,
2069+
);
2070+
2071+
let mut rewritten_req = None;
2072+
let result = source.query(
2073+
&dependency("a", "=1.0.1", source_id),
2074+
QueryKind::Exact,
2075+
&mut |summary| {
2076+
rewritten_req = Some(
2077+
summary.as_summary().dependencies()[0]
2078+
.version_req()
2079+
.to_string(),
2080+
);
2081+
},
2082+
);
2083+
2084+
assert!(matches!(result, Poll::Ready(Ok(()))));
2085+
assert_eq!(rewritten_req.as_deref(), Some(">=1.0.0, <2.0.0"));
2086+
}
2087+
20182088
#[test]
20192089
fn test_previous_locked_edges_treat_crates_io_registry_and_sparse_as_same_source() {
20202090
let tempdir = tempfile::tempdir().unwrap();
@@ -2038,7 +2108,7 @@ narrow_to = "1"
20382108
Rc::new(RefCell::new(BTreeSet::new())),
20392109
);
20402110
context.previous_edges = BTreeMap::from([(
2041-
super::dependency_edge_key(previous_parent, &previous_dependency),
2111+
super::dependency_edge_key(previous_parent, &previous_dependency).unwrap(),
20422112
PackageId::try_new("reqwest", "0.12.28", registry_source_id).unwrap(),
20432113
)]);
20442114
let mut source = DeterministicSource::new(
@@ -2112,7 +2182,7 @@ narrow_to = "1"
21122182
[sparse_source_id],
21132183
Rc::new(RefCell::new(BTreeSet::new())),
21142184
);
2115-
context.previous_edges = super::previous_dependency_edges(&previous_resolve);
2185+
context.previous_edges = super::previous_dependency_edges(&previous_resolve).unwrap();
21162186
let mut source = DeterministicSource::new(
21172187
RecordingSource::new(
21182188
sparse_source_id,

src/semver_ext.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ pub(crate) struct VersionReqBounds {
1616
upper: Option<VersionBound>,
1717
}
1818

19+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
20+
pub(crate) struct CompatibilityLane {
21+
lower: Version,
22+
upper: Version,
23+
}
24+
1925
impl VersionReqBounds {
2026
fn empty() -> Self {
2127
Self {
@@ -282,12 +288,20 @@ fn compatibility_bounds(version: &Version) -> anyhow::Result<VersionReqBounds> {
282288
}
283289

284290
fn compatibility_lane_bounds(version: &Version) -> anyhow::Result<VersionReqBounds> {
291+
let lane = version_compatibility_lane(version)?;
285292
Ok(VersionReqBounds::range(
286-
VersionBound::inclusive(semver_compatibility_lower_bound(version)),
287-
VersionBound::exclusive(semver_compatibility_upper_bound(version)?),
293+
VersionBound::inclusive(lane.lower),
294+
VersionBound::exclusive(lane.upper),
288295
))
289296
}
290297

298+
pub(crate) fn version_compatibility_lane(version: &Version) -> anyhow::Result<CompatibilityLane> {
299+
Ok(CompatibilityLane {
300+
lower: semver_compatibility_lower_bound(version),
301+
upper: semver_compatibility_upper_bound(version)?,
302+
})
303+
}
304+
291305
fn semver_compatibility_lower_bound(version: &Version) -> Version {
292306
if !version.pre.is_empty() {
293307
return version.clone();

0 commit comments

Comments
 (0)