1- use std:: collections:: BTreeMap ;
1+ use petgraph:: { Directed , Graph } ;
2+
23use uv_distribution_filename:: DistExtension ;
34use uv_normalize:: { ExtraName , GroupName , PackageName } ;
5+ use uv_pep508:: MarkerTree ;
46use uv_pypi_types:: { HashDigest , RequirementSource } ;
57
68use crate :: { BuiltDist , Diagnostic , Dist , Name , ResolvedDist , SourceDist } ;
79
810/// A set of packages pinned at specific versions.
911#[ derive( Debug , Default , Clone ) ]
1012pub struct Resolution {
11- packages : BTreeMap < PackageName , ResolvedDist > ,
12- hashes : BTreeMap < PackageName , Vec < HashDigest > > ,
13+ graph : Graph < Node , Edge , Directed > ,
1314 diagnostics : Vec < ResolutionDiagnostic > ,
1415}
1516
1617impl Resolution {
1718 /// Create a new resolution from the given pinned packages.
18- pub fn new (
19- packages : BTreeMap < PackageName , ResolvedDist > ,
20- hashes : BTreeMap < PackageName , Vec < HashDigest > > ,
21- diagnostics : Vec < ResolutionDiagnostic > ,
22- ) -> Self {
19+ pub fn new ( graph : Graph < Node , Edge , Directed > ) -> Self {
2320 Self {
24- packages,
25- hashes,
26- diagnostics,
21+ graph,
22+ diagnostics : Vec :: new ( ) ,
2723 }
2824 }
2925
30- /// Return the hashes for the given package name, if they exist.
31- pub fn get_hashes ( & self , package_name : & PackageName ) -> & [ HashDigest ] {
32- self . hashes . get ( package_name) . map_or ( & [ ] , Vec :: as_slice)
26+ /// Return the underlying graph of the resolution.
27+ pub fn graph ( & self ) -> & Graph < Node , Edge , Directed > {
28+ & self . graph
29+ }
30+
31+ /// Add [`Diagnostics`] to the resolution.
32+ #[ must_use]
33+ pub fn with_diagnostics ( mut self , diagnostics : Vec < ResolutionDiagnostic > ) -> Self {
34+ self . diagnostics . extend ( diagnostics) ;
35+ self
3336 }
3437
35- /// Iterate over the [`PackageName`] entities in this resolution.
36- pub fn packages ( & self ) -> impl Iterator < Item = & PackageName > {
37- self . packages . keys ( )
38+ /// Return the hashes for the given package name, if they exist.
39+ pub fn hashes ( & self ) -> impl Iterator < Item = ( & ResolvedDist , & [ HashDigest ] ) > {
40+ self . graph
41+ . node_indices ( )
42+ . filter_map ( move |node| match & self . graph [ node] {
43+ Node :: Dist {
44+ dist,
45+ hashes,
46+ install,
47+ ..
48+ } if * install => Some ( ( dist, hashes. as_slice ( ) ) ) ,
49+ _ => None ,
50+ } )
3851 }
3952
4053 /// Iterate over the [`ResolvedDist`] entities in this resolution.
4154 pub fn distributions ( & self ) -> impl Iterator < Item = & ResolvedDist > {
42- self . packages . values ( )
55+ self . graph
56+ . raw_nodes ( )
57+ . iter ( )
58+ . filter_map ( |node| match & node. weight {
59+ Node :: Dist { dist, install, .. } if * install => Some ( dist) ,
60+ _ => None ,
61+ } )
4362 }
4463
4564 /// Return the number of distributions in this resolution.
4665 pub fn len ( & self ) -> usize {
47- self . packages . len ( )
66+ self . distributions ( ) . count ( )
4867 }
4968
5069 /// Return `true` if there are no pinned packages in this resolution.
5170 pub fn is_empty ( & self ) -> bool {
52- self . packages . is_empty ( )
71+ self . distributions ( ) . next ( ) . is_none ( )
5372 }
5473
5574 /// Return the [`ResolutionDiagnostic`]s that were produced during resolution.
@@ -59,44 +78,31 @@ impl Resolution {
5978
6079 /// Filter the resolution to only include packages that match the given predicate.
6180 #[ must_use]
62- pub fn filter ( self , predicate : impl Fn ( & ResolvedDist ) -> bool ) -> Self {
63- let packages = self
64- . packages
65- . into_iter ( )
66- . filter ( |( _, dist) | predicate ( dist) )
67- . collect :: < BTreeMap < _ , _ > > ( ) ;
68- let hashes = self
69- . hashes
70- . into_iter ( )
71- . filter ( |( name, _) | packages. contains_key ( name) )
72- . collect ( ) ;
73- let diagnostics = self . diagnostics . clone ( ) ;
74- Self {
75- packages,
76- hashes,
77- diagnostics,
81+ pub fn filter ( mut self , predicate : impl Fn ( & ResolvedDist ) -> bool ) -> Self {
82+ for node in self . graph . node_weights_mut ( ) {
83+ if let Node :: Dist { dist, install, .. } = node {
84+ if !predicate ( dist) {
85+ * install = false ;
86+ }
87+ }
7888 }
89+ self
7990 }
8091
8192 /// Map over the resolved distributions in this resolution.
93+ ///
94+ /// For efficiency, the map function should return `None` if the resolved distribution is
95+ /// unchanged.
8296 #[ must_use]
83- pub fn map ( self , predicate : impl Fn ( ResolvedDist ) -> ResolvedDist ) -> Self {
84- let packages = self
85- . packages
86- . into_iter ( )
87- . map ( |( name, dist) | ( name, predicate ( dist) ) )
88- . collect :: < BTreeMap < _ , _ > > ( ) ;
89- let hashes = self
90- . hashes
91- . into_iter ( )
92- . filter ( |( name, _) | packages. contains_key ( name) )
93- . collect ( ) ;
94- let diagnostics = self . diagnostics . clone ( ) ;
95- Self {
96- packages,
97- hashes,
98- diagnostics,
97+ pub fn map ( mut self , predicate : impl Fn ( & ResolvedDist ) -> Option < ResolvedDist > ) -> Self {
98+ for node in self . graph . node_weights_mut ( ) {
99+ if let Node :: Dist { dist, .. } = node {
100+ if let Some ( transformed) = predicate ( dist) {
101+ * dist = transformed;
102+ }
103+ }
99104 }
105+ self
100106 }
101107}
102108
@@ -166,15 +172,48 @@ impl Diagnostic for ResolutionDiagnostic {
166172 }
167173}
168174
175+ /// A node in the resolution, along with whether its been filtered out.
176+ ///
177+ /// This is similar to [`ResolutionGraph`], but represents a resolution for a single platform.
178+ ///
179+ /// We retain filtered nodes as we still need to be able to trace dependencies through the graph
180+ /// (e.g., to determine why a package was install in the resolution).
181+ #[ derive( Debug , Clone ) ]
182+ pub enum Node {
183+ Root ,
184+ Dist {
185+ dist : ResolvedDist ,
186+ hashes : Vec < HashDigest > ,
187+ install : bool ,
188+ } ,
189+ }
190+
191+ /// An edge in the resolution graph, along with the marker that must be satisfied to traverse it.
192+ #[ derive( Debug , Clone ) ]
193+ pub enum Edge {
194+ Prod ( MarkerTree ) ,
195+ Optional ( ExtraName , MarkerTree ) ,
196+ Dev ( GroupName , MarkerTree ) ,
197+ }
198+
199+ impl Edge {
200+ /// Return the [`MarkerTree`] for this edge.
201+ pub fn marker ( & self ) -> & MarkerTree {
202+ match self {
203+ Self :: Prod ( marker) => marker,
204+ Self :: Optional ( _, marker) => marker,
205+ Self :: Dev ( _, marker) => marker,
206+ }
207+ }
208+ }
209+
169210impl From < & ResolvedDist > for RequirementSource {
170211 fn from ( resolved_dist : & ResolvedDist ) -> Self {
171212 match resolved_dist {
172- ResolvedDist :: Installable { dist, .. } => match dist {
213+ ResolvedDist :: Installable { dist, version } => match dist {
173214 Dist :: Built ( BuiltDist :: Registry ( wheels) ) => RequirementSource :: Registry {
174215 specifier : uv_pep440:: VersionSpecifiers :: from (
175- uv_pep440:: VersionSpecifier :: equals_version (
176- wheels. best_wheel ( ) . filename . version . clone ( ) ,
177- ) ,
216+ uv_pep440:: VersionSpecifier :: equals_version ( version. clone ( ) ) ,
178217 ) ,
179218 index : Some ( wheels. best_wheel ( ) . index . url ( ) . clone ( ) ) ,
180219 } ,
0 commit comments