@@ -20,7 +20,7 @@ use url::Url;
2020
2121use uv_cache_key:: RepositoryUrl ;
2222use uv_configuration:: BuildOptions ;
23- use uv_distribution:: { DistributionDatabase , FlatRequiresDist , RequiresDist } ;
23+ use uv_distribution:: { DistributionDatabase , FlatRequiresDist } ;
2424use 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 ,
0 commit comments