@@ -37,7 +37,10 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
37
37
use uv_pep440:: { release_specifiers_to_ranges, Version , MIN_VERSION } ;
38
38
use uv_pep508:: MarkerTree ;
39
39
use uv_platform_tags:: Tags ;
40
- use uv_pypi_types:: { ConflictingGroupList , Requirement , ResolutionMetadata , VerbatimParsedUrl } ;
40
+ use uv_pypi_types:: {
41
+ ConflictingGroup , ConflictingGroupList , ConflictingGroupRef , Requirement , ResolutionMetadata ,
42
+ VerbatimParsedUrl ,
43
+ } ;
41
44
use uv_types:: { BuildContext , HashStrategy , InstalledPackagesProvider } ;
42
45
use uv_warnings:: warn_user_once;
43
46
@@ -1560,6 +1563,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
1560
1563
) {
1561
1564
return None ;
1562
1565
}
1566
+ if !env. included_by_group (
1567
+ ConflictingGroupRef :: from ( ( & requirement. name , source_extra) ) ,
1568
+ ) {
1569
+ return None ;
1570
+ }
1563
1571
}
1564
1572
None => {
1565
1573
if !requirement. evaluate_markers ( env. marker_environment ( ) , & [ ] ) {
@@ -1648,6 +1656,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
1648
1656
) {
1649
1657
return None ;
1650
1658
}
1659
+ if !env. included_by_group (
1660
+ ConflictingGroupRef :: from ( ( & requirement. name , source_extra) ) ,
1661
+ ) {
1662
+ return None ;
1663
+ }
1651
1664
}
1652
1665
None => {
1653
1666
if !constraint. evaluate_markers ( env. marker_environment ( ) , & [ ] ) {
@@ -2740,10 +2753,7 @@ impl Forks {
2740
2753
) -> Forks {
2741
2754
let python_marker = python_requirement. to_marker_tree ( ) ;
2742
2755
2743
- let mut forks = vec ! [ Fork {
2744
- dependencies: vec![ ] ,
2745
- env: env. clone( ) ,
2746
- } ] ;
2756
+ let mut forks = vec ! [ Fork :: new( env. clone( ) ) ] ;
2747
2757
let mut diverging_packages = BTreeSet :: new ( ) ;
2748
2758
for ( name, mut deps) in name_to_deps {
2749
2759
assert ! ( !deps. is_empty( ) , "every name has at least one dependency" ) ;
@@ -2775,7 +2785,7 @@ impl Forks {
2775
2785
let markers = dep. package . marker ( ) . cloned ( ) . unwrap_or ( MarkerTree :: TRUE ) ;
2776
2786
for fork in & mut forks {
2777
2787
if fork. env . included_by_marker ( & markers) {
2778
- fork. dependencies . push ( dep. clone ( ) ) ;
2788
+ fork. add_dependency ( dep. clone ( ) ) ;
2779
2789
}
2780
2790
}
2781
2791
continue ;
@@ -2793,7 +2803,7 @@ impl Forks {
2793
2803
// Or, if the markers are always true, then we just
2794
2804
// add the dependency to every fork unconditionally.
2795
2805
for fork in & mut forks {
2796
- fork. dependencies . push ( dep. clone ( ) ) ;
2806
+ fork. add_dependency ( dep. clone ( ) ) ;
2797
2807
}
2798
2808
continue ;
2799
2809
}
@@ -2818,7 +2828,7 @@ impl Forks {
2818
2828
// specifically created to exclude this dependency,
2819
2829
// so this isn't always true!
2820
2830
if forker. included ( & new_fork. env ) {
2821
- new_fork. dependencies . push ( dep. clone ( ) ) ;
2831
+ new_fork. add_dependency ( dep. clone ( ) ) ;
2822
2832
}
2823
2833
// Filter out any forks we created that are disjoint with our
2824
2834
// Python requirement.
@@ -2830,6 +2840,62 @@ impl Forks {
2830
2840
forks = new;
2831
2841
}
2832
2842
}
2843
+ // When there is a conflicting group configuration, we need
2844
+ // to potentially add more forks. Each fork added contains an
2845
+ // exclusion list of conflicting groups where dependencies with
2846
+ // the corresponding package and extra name are forcefully
2847
+ // excluded from that group.
2848
+ //
2849
+ // We specifically iterate on conflicting groups and
2850
+ // potentially re-generate all forks for each one. We do it
2851
+ // this way in case there are multiple sets of conflicting
2852
+ // groups that impact the forks here.
2853
+ //
2854
+ // For example, if we have conflicting groups {x1, x2} and {x3,
2855
+ // x4}, we need to make sure the forks generated from one set
2856
+ // also account for the other set.
2857
+ for groups in conflicting_groups. iter ( ) {
2858
+ let mut new = vec ! [ ] ;
2859
+ for fork in std:: mem:: take ( & mut forks) {
2860
+ let mut has_conflicting_dependency = false ;
2861
+ for group in groups. iter ( ) {
2862
+ if fork. contains_conflicting_group ( group. as_ref ( ) ) {
2863
+ has_conflicting_dependency = true ;
2864
+ break ;
2865
+ }
2866
+ }
2867
+ if !has_conflicting_dependency {
2868
+ new. push ( fork) ;
2869
+ continue ;
2870
+ }
2871
+
2872
+ // Create a fork that excludes ALL extras.
2873
+ let mut fork_none = fork. clone ( ) ;
2874
+ for group in groups. iter ( ) {
2875
+ fork_none = fork_none. exclude ( [ group. clone ( ) ] ) ;
2876
+ }
2877
+ new. push ( fork_none) ;
2878
+
2879
+ // Now create a fork for each conflicting group, where
2880
+ // that fork excludes every *other* conflicting group.
2881
+ //
2882
+ // So if we have conflicting extras foo, bar and baz,
2883
+ // then this creates three forks: one that excludes
2884
+ // {foo, bar}, one that excludes {foo, baz} and one
2885
+ // that excludes {bar, baz}.
2886
+ for ( i, _) in groups. iter ( ) . enumerate ( ) {
2887
+ let fork_allows_group = fork. clone ( ) . exclude (
2888
+ groups
2889
+ . iter ( )
2890
+ . enumerate ( )
2891
+ . filter ( |& ( j, _) | i != j)
2892
+ . map ( |( _, group) | group. clone ( ) ) ,
2893
+ ) ;
2894
+ new. push ( fork_allows_group) ;
2895
+ }
2896
+ }
2897
+ forks = new;
2898
+ }
2833
2899
Forks {
2834
2900
forks,
2835
2901
diverging_packages,
@@ -2846,7 +2912,7 @@ impl Forks {
2846
2912
/// have the same name and because the marker expressions are disjoint,
2847
2913
/// a fork occurs. One fork will contain `a<2` but not `a>=2`, while
2848
2914
/// the other fork will contain `a>=2` but not `a<2`.
2849
- #[ derive( Clone , Debug , Eq , PartialEq ) ]
2915
+ #[ derive( Clone , Debug ) ]
2850
2916
struct Fork {
2851
2917
/// The list of dependencies for this fork, guaranteed to be conflict
2852
2918
/// free. (i.e., There are no two packages with the same name with
@@ -2857,6 +2923,12 @@ struct Fork {
2857
2923
/// it should be impossible for a package with a marker expression that is
2858
2924
/// disjoint from the marker expression on this fork to be added.
2859
2925
dependencies : Vec < PubGrubDependency > ,
2926
+ /// The conflicting groups in this fork.
2927
+ ///
2928
+ /// This exists to make some access patterns more efficient. Namely,
2929
+ /// it makes it easy to check whether there's a dependency with a
2930
+ /// particular conflicting group in this fork.
2931
+ conflicting_groups : FxHashMap < PackageName , FxHashSet < ExtraName > > ,
2860
2932
/// The resolver environment for this fork.
2861
2933
///
2862
2934
/// Principally, this corresponds to the markers in this for. So in the
@@ -2872,26 +2944,86 @@ struct Fork {
2872
2944
}
2873
2945
2874
2946
impl Fork {
2875
- /*
2876
- fn intersect(&mut self, markers: MarkerTree) {
2877
- self.markers.and(markers);
2947
+ /// Create a new fork with no dependencies with the given resolver
2948
+ /// environment.
2949
+ fn new ( env : ResolverEnvironment ) -> Fork {
2950
+ Fork {
2951
+ dependencies : vec ! [ ] ,
2952
+ conflicting_groups : FxHashMap :: default ( ) ,
2953
+ env,
2954
+ }
2955
+ }
2956
+
2957
+ /// Add a dependency to this fork.
2958
+ fn add_dependency ( & mut self , dep : PubGrubDependency ) {
2959
+ if let Some ( conflicting_group) = dep. package . conflicting_group ( ) {
2960
+ self . conflicting_groups
2961
+ . entry ( conflicting_group. package ( ) . clone ( ) )
2962
+ . or_default ( )
2963
+ . insert ( conflicting_group. extra ( ) . clone ( ) ) ;
2964
+ }
2965
+ self . dependencies . push ( dep) ;
2966
+ }
2967
+
2968
+ /// Sets the resolver environment to the one given.
2969
+ ///
2970
+ /// Any dependency in this fork that does not satisfy the given environment
2971
+ /// is removed.
2972
+ fn set_env ( & mut self , env : ResolverEnvironment ) {
2973
+ self . env = env;
2878
2974
self . dependencies . retain ( |dep| {
2879
2975
let Some ( markers) = dep. package . marker ( ) else {
2880
2976
return true ;
2881
2977
} ;
2882
- !self.markers.is_disjoint(markers)
2978
+ if self . env . included_by_marker ( markers) {
2979
+ return true ;
2980
+ }
2981
+ if let Some ( conflicting_group) = dep. package . conflicting_group ( ) {
2982
+ if let Some ( set) = self . conflicting_groups . get_mut ( conflicting_group. package ( ) ) {
2983
+ set. remove ( conflicting_group. extra ( ) ) ;
2984
+ }
2985
+ }
2986
+ false
2883
2987
} ) ;
2884
2988
}
2885
- */
2886
2989
2887
- fn set_env ( & mut self , env : ResolverEnvironment ) {
2888
- self . env = env;
2990
+ /// Returns true if any of the dependencies in this fork contain a
2991
+ /// dependency with the given package and extra values.
2992
+ fn contains_conflicting_group ( & self , group : ConflictingGroupRef < ' _ > ) -> bool {
2993
+ self . conflicting_groups
2994
+ . get ( group. package ( ) )
2995
+ . map ( |set| set. contains ( group. extra ( ) ) )
2996
+ . unwrap_or ( false )
2997
+ }
2998
+
2999
+ /// Exclude the given groups from this fork.
3000
+ ///
3001
+ /// This removes all dependencies matching the given conflicting groups.
3002
+ fn exclude ( mut self , groups : impl IntoIterator < Item = ConflictingGroup > ) -> Fork {
3003
+ self . env = self . env . exclude_by_group ( groups) ;
2889
3004
self . dependencies . retain ( |dep| {
2890
- let Some ( markers ) = dep. package . marker ( ) else {
3005
+ let Some ( conflicting_group ) = dep. package . conflicting_group ( ) else {
2891
3006
return true ;
2892
3007
} ;
2893
- self . env . included ( markers)
3008
+ if self . env . included_by_group ( conflicting_group) {
3009
+ return true ;
3010
+ }
3011
+ if let Some ( conflicting_group) = dep. package . conflicting_group ( ) {
3012
+ if let Some ( set) = self . conflicting_groups . get_mut ( conflicting_group. package ( ) ) {
3013
+ set. remove ( conflicting_group. extra ( ) ) ;
3014
+ }
3015
+ }
3016
+ false
2894
3017
} ) ;
3018
+ self
3019
+ }
3020
+ }
3021
+
3022
+ impl Eq for Fork { }
3023
+
3024
+ impl PartialEq for Fork {
3025
+ fn eq ( & self , other : & Fork ) -> bool {
3026
+ self . dependencies == other. dependencies && self . env == other. env
2895
3027
}
2896
3028
}
2897
3029
0 commit comments