Skip to content

Commit dbc1682

Browse files
committed
Treat lockfile as outdated if (empty) extras are added
1 parent 88aa6e2 commit dbc1682

File tree

4 files changed

+289
-25
lines changed

4 files changed

+289
-25
lines changed

crates/uv-resolver/src/lock/mod.rs

Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use url::Url;
2020

2121
use uv_cache_key::RepositoryUrl;
2222
use uv_configuration::BuildOptions;
23-
use uv_distribution::{DistributionDatabase, FlatRequiresDist, RequiresDist};
23+
use uv_distribution::{DistributionDatabase, FlatRequiresDist};
2424
use uv_distribution_filename::{
2525
BuildTag, DistExtension, ExtensionError, SourceDistExtension, WheelFilename,
2626
};
@@ -580,6 +580,12 @@ impl Lock {
580580
self
581581
}
582582

583+
/// Returns `true` if this [`Lock`] includes `provides-extra` metadata.
584+
pub fn supports_provides_extra(&self) -> bool {
585+
// `provides-extra` was added in Version 1 Revision 1.
586+
(self.version(), self.revision()) >= (1, 1)
587+
}
588+
583589
/// Returns the lockfile version.
584590
pub fn version(&self) -> u32 {
585591
self.version
@@ -1020,30 +1026,55 @@ impl Lock {
10201026
dist
10211027
}
10221028

1023-
/// Return a [`SatisfiesResult`] if the given [`RequiresDist`] does not match the [`Package`].
1029+
/// Return a [`SatisfiesResult`] if the given extras do not match the [`Package`] metadata.
1030+
fn satisfies_provides_extra<'lock>(
1031+
&self,
1032+
provides_extra: Vec<ExtraName>,
1033+
package: &'lock Package,
1034+
) -> SatisfiesResult<'lock> {
1035+
if !self.supports_provides_extra() {
1036+
return SatisfiesResult::Satisfied;
1037+
}
1038+
1039+
let expected: BTreeSet<_> = provides_extra.iter().collect();
1040+
let actual: BTreeSet<_> = package.metadata.provides_extras.iter().collect();
1041+
1042+
if expected != actual {
1043+
let expected = provides_extra.into_iter().collect();
1044+
return SatisfiesResult::MismatchedPackageProvidesExtra(
1045+
&package.id.name,
1046+
package.id.version.as_ref(),
1047+
expected,
1048+
actual,
1049+
);
1050+
}
1051+
1052+
SatisfiesResult::Satisfied
1053+
}
1054+
1055+
/// Return a [`SatisfiesResult`] if the given requirements do not match the [`Package`] metadata.
1056+
#[allow(clippy::unused_self)]
10241057
fn satisfies_requires_dist<'lock>(
1025-
metadata: RequiresDist,
1058+
&self,
1059+
requires_dist: Vec<Requirement>,
1060+
dependency_groups: BTreeMap<GroupName, Vec<Requirement>>,
10261061
package: &'lock Package,
10271062
root: &Path,
10281063
) -> Result<SatisfiesResult<'lock>, LockError> {
10291064
// Special-case: if the version is dynamic, compare the flattened requirements.
10301065
let flattened = if package.is_dynamic() {
10311066
Some(
1032-
FlatRequiresDist::from_requirements(
1033-
metadata.requires_dist.clone(),
1034-
&package.id.name,
1035-
)
1036-
.into_iter()
1037-
.map(|requirement| normalize_requirement(requirement, root))
1038-
.collect::<Result<BTreeSet<_>, _>>()?,
1067+
FlatRequiresDist::from_requirements(requires_dist.clone(), &package.id.name)
1068+
.into_iter()
1069+
.map(|requirement| normalize_requirement(requirement, root))
1070+
.collect::<Result<BTreeSet<_>, _>>()?,
10391071
)
10401072
} else {
10411073
None
10421074
};
10431075

10441076
// Validate the `requires-dist` metadata.
1045-
let expected: BTreeSet<_> = metadata
1046-
.requires_dist
1077+
let expected: BTreeSet<_> = requires_dist
10471078
.into_iter()
10481079
.map(|requirement| normalize_requirement(requirement, root))
10491080
.collect::<Result<_, _>>()?;
@@ -1065,8 +1096,7 @@ impl Lock {
10651096
}
10661097

10671098
// Validate the `dependency-groups` metadata.
1068-
let expected: BTreeMap<GroupName, BTreeSet<Requirement>> = metadata
1069-
.dependency_groups
1099+
let expected: BTreeMap<GroupName, BTreeSet<Requirement>> = dependency_groups
10701100
.into_iter()
10711101
.filter(|(_, requirements)| !requirements.is_empty())
10721102
.map(|(group, requirements)| {
@@ -1402,8 +1432,19 @@ impl Lock {
14021432
));
14031433
}
14041434

1435+
// Validate the `provides-extras` metadata.
1436+
match self.satisfies_provides_extra(metadata.provides_extras, package) {
1437+
SatisfiesResult::Satisfied => {}
1438+
result => return Ok(result),
1439+
}
1440+
14051441
// Validate that the requirements are unchanged.
1406-
match Self::satisfies_requires_dist(RequiresDist::from(metadata), package, root)? {
1442+
match self.satisfies_requires_dist(
1443+
metadata.requires_dist,
1444+
metadata.dependency_groups,
1445+
package,
1446+
root,
1447+
)? {
14071448
SatisfiesResult::Satisfied => {}
14081449
result => return Ok(result),
14091450
}
@@ -1432,21 +1473,30 @@ impl Lock {
14321473
return false;
14331474
}
14341475

1476+
// Validate that the extras are unchanged.
1477+
if let SatisfiesResult::Satisfied = self.satisfies_provides_extra(metadata.provides_extras, package, ) {
1478+
debug!("Static `provides-extra` for `{}` is up-to-date", package.id);
1479+
} else {
1480+
debug!("Static `provides-extra` for `{}` is out-of-date; falling back to distribution database", package.id);
1481+
return false;
1482+
}
1483+
14351484
// Validate that the requirements are unchanged.
1436-
match Self::satisfies_requires_dist(metadata, package, root) {
1485+
match self.satisfies_requires_dist(metadata.requires_dist, metadata.dependency_groups, package, root) {
14371486
Ok(SatisfiesResult::Satisfied) => {
14381487
debug!("Static `requires-dist` for `{}` is up-to-date", package.id);
1439-
true
14401488
},
14411489
Ok(..) => {
14421490
debug!("Static `requires-dist` for `{}` is out-of-date; falling back to distribution database", package.id);
1443-
false
1491+
return false;
14441492
},
14451493
Err(..) => {
14461494
debug!("Static `requires-dist` for `{}` is invalid; falling back to distribution database", package.id);
1447-
false
1495+
return false;
14481496
},
14491497
}
1498+
1499+
true
14501500
});
14511501

14521502
// If the `requires-dist` metadata matches the requirements, we're done; otherwise,
@@ -1504,9 +1554,16 @@ impl Lock {
15041554
return Ok(SatisfiesResult::MismatchedDynamic(&package.id.name, true));
15051555
}
15061556

1557+
// Validate that the extras are unchanged.
1558+
match self.satisfies_provides_extra(metadata.provides_extras, package) {
1559+
SatisfiesResult::Satisfied => {}
1560+
result => return Ok(result),
1561+
}
1562+
15071563
// Validate that the requirements are unchanged.
1508-
match Self::satisfies_requires_dist(
1509-
RequiresDist::from(metadata),
1564+
match self.satisfies_requires_dist(
1565+
metadata.requires_dist,
1566+
metadata.dependency_groups,
15101567
package,
15111568
root,
15121569
)? {
@@ -1519,8 +1576,6 @@ impl Lock {
15191576
}
15201577

15211578
// Recurse.
1522-
// TODO(charlie): Do we care about extras here, or any other fields on the `Dependency`?
1523-
// Should we instead recurse on `requires_dist`?
15241579
for dep in &package.dependencies {
15251580
if seen.insert(&dep.package_id) {
15261581
let dep_dist = self.find_by_id(&dep.package_id);
@@ -1608,6 +1663,13 @@ pub enum SatisfiesResult<'lock> {
16081663
BTreeSet<Requirement>,
16091664
BTreeSet<Requirement>,
16101665
),
1666+
/// A package in the lockfile contains different `provides-extra` metadata than expected.
1667+
MismatchedPackageProvidesExtra(
1668+
&'lock PackageName,
1669+
Option<&'lock Version>,
1670+
BTreeSet<ExtraName>,
1671+
BTreeSet<&'lock ExtraName>,
1672+
),
16111673
/// A package in the lockfile contains different `dependency-groups` metadata than expected.
16121674
MismatchedPackageDependencyGroups(
16131675
&'lock PackageName,

crates/uv/src/commands/project/install_target.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,7 @@ impl<'lock> InstallTarget<'lock> {
259259
Self::Project { lock, .. }
260260
| Self::Workspace { lock, .. }
261261
| Self::NonProjectWorkspace { lock, .. } => {
262-
// `provides-extra` was added in Version 1 Revision 1.
263-
if (lock.version(), lock.revision()) < (1, 1) {
262+
if !lock.supports_provides_extra() {
264263
return Ok(());
265264
}
266265

crates/uv/src/commands/project/lock.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,20 @@ impl ValidatedLock {
11231123
}
11241124
Ok(Self::Preferable(lock))
11251125
}
1126+
SatisfiesResult::MismatchedPackageProvidesExtra(name, version, expected, actual) => {
1127+
if let Some(version) = version {
1128+
debug!(
1129+
"Ignoring existing lockfile due to mismatched extras for: `{name}=={version}`\n Requested: {:?}\n Existing: {:?}",
1130+
expected, actual
1131+
);
1132+
} else {
1133+
debug!(
1134+
"Ignoring existing lockfile due to mismatched extras for: `{name}`\n Requested: {:?}\n Existing: {:?}",
1135+
expected, actual
1136+
);
1137+
}
1138+
Ok(Self::Preferable(lock))
1139+
}
11261140
SatisfiesResult::MissingVersion(name) => {
11271141
debug!("Ignoring existing lockfile due to missing version: `{name}`");
11281142
Ok(Self::Preferable(lock))

0 commit comments

Comments
 (0)