@@ -19,11 +19,17 @@ import (
1919// returns the minimal closure of PackageInfos needed to satisfy all Requires.
2020func ResolvePackageInfos (requested []provider.PackageInfo , all []provider.PackageInfo ) ([]provider.PackageInfo , error ) {
2121
22- // Build a map for fast lookup by package name
22+ // Build a map for fast lookup by package name and version
23+ byNameVer := make (map [string ]provider.PackageInfo , len (all ))
2324 byName := make (map [string ]provider.PackageInfo , len (all ))
24- // Build a map for lookup by provided virtual package name
2525 byProvides := make (map [string ]provider.PackageInfo )
2626 for _ , pi := range all {
27+ key := pi .Name
28+ if pi .Version != "" {
29+ // contruct key as "name=version" for exact matches
30+ key = fmt .Sprintf ("%s=%s" , pi .Name , pi .Version )
31+ byNameVer [key ] = pi
32+ }
2733 byName [pi .Name ] = pi
2834 for _ , prov := range pi .Provides {
2935 byProvides [prov ] = pi
@@ -35,15 +41,23 @@ func ResolvePackageInfos(requested []provider.PackageInfo, all []provider.Packag
3541 // Start with the requested packages
3642 queue := make ([]provider.PackageInfo , 0 , len (requested ))
3743 for _ , pi := range requested {
38- if _ , ok := byName [pi .Name ]; ! ok {
39- // Try to resolve via Provides
40- if provPkg , ok := byProvides [pi .Name ]; ok {
41- queue = append (queue , provPkg )
44+ key := pi .Name
45+ if pi .Version != "" {
46+ key = fmt .Sprintf ("%s=%s" , pi .Name , pi .Version )
47+ if pkg , ok := byNameVer [key ]; ok {
48+ queue = append (queue , pkg )
4249 continue
4350 }
44- return nil , fmt .Errorf ("requested package %q not in repo listing" , pi .Name )
4551 }
46- queue = append (queue , pi )
52+ if pkg , ok := byName [pi .Name ]; ok {
53+ queue = append (queue , pkg )
54+ continue
55+ }
56+ if provPkg , ok := byProvides [pi .Name ]; ok {
57+ queue = append (queue , provPkg )
58+ continue
59+ }
60+ return nil , fmt .Errorf ("requested package %q not in repo listing" , pi .Name )
4761 }
4862
4963 result := make ([]provider.PackageInfo , 0 )
@@ -60,22 +74,83 @@ func ResolvePackageInfos(requested []provider.PackageInfo, all []provider.Packag
6074
6175 // Traverse dependencies (Requires)
6276 for _ , dep := range cur .Requires {
63- // Remove version constraints if present, e.g. "foo (> = 1.2)" -> "foo"
77+ // dep may be "foo (>= 1.2)" or "foo (= 1.2)" or just "foo"
6478 depName := dep
65- if idx := strings .Index (dep , " " ); idx > 0 {
66- depName = dep [:idx ]
79+ depVersion := ""
80+ // handles "|" in package names, e.g. "perl | perl-base"
81+ // currently only the first part is used, TODO: handle multiple parts
82+ if idx := strings .Index (depName , "|" ); idx > 0 {
83+ depName = strings .TrimSpace (depName [:idx ])
6784 }
6885 // Remove architecture qualifiers, e.g. "perl:any" -> "perl"
6986 if idx := strings .Index (depName , ":" ); idx > 0 {
7087 depName = depName [:idx ]
7188 }
89+ // Check for version constraint
90+ if idx := strings .Index (depName , "(" ); idx > 0 {
91+ name := strings .TrimSpace (depName [:idx ])
92+ verPart := strings .TrimSpace (depName [idx :])
93+ verPart = strings .Trim (verPart , "() " )
94+ // Only enforce exact version if constraint is "="
95+ if strings .HasPrefix (verPart , "=" ) {
96+ depVersion = strings .TrimSpace (strings .TrimPrefix (verPart , "=" ))
97+ depName = name
98+ } else {
99+ depName = name
100+ }
101+ } else if idx := strings .Index (depName , " " ); idx > 0 {
102+ depName = depName [:idx ]
103+ }
72104 depName = strings .TrimSpace (depName )
73105 if depName == "" {
74106 continue
75107 }
76108 if _ , seen := neededSet [depName ]; seen {
77109 continue
78110 }
111+ // If version is enforced, match by name+version
112+ if depVersion != "" {
113+ // Try exact version first
114+ key := fmt .Sprintf ("%s=%s" , depName , depVersion )
115+ if depPkg , ok := byNameVer [key ]; ok {
116+ // exact version match found
117+ queue = append (queue , depPkg )
118+ continue
119+ }
120+ // Try to find any package with higher version using Debian version semantics
121+ var found * provider.PackageInfo
122+ for _ , pi := range all {
123+ if pi .Name == depName {
124+ // Use Debian version comparison
125+ cmp , err := compareDebianVersions (pi .Version , depVersion )
126+ if err != nil {
127+ return nil , fmt .Errorf ("failed to compare versions: %v" , err )
128+ }
129+ if cmp >= 0 {
130+ // sorting by version, pick the lowest one that satisfies the constraint
131+ if found == nil {
132+ tmp := pi
133+ found = & tmp
134+ } else {
135+ // Pick the lowest version that satisfies the constraint
136+ cmp2 , err := compareDebianVersions (pi .Version , found .Version )
137+ if err != nil {
138+ return nil , fmt .Errorf ("failed to compare versions: %v" , err )
139+ }
140+ if cmp2 < 0 {
141+ tmp := pi
142+ found = & tmp
143+ }
144+ }
145+ }
146+ }
147+ }
148+ if found != nil {
149+ queue = append (queue , * found )
150+ continue
151+ }
152+ return nil , fmt .Errorf ("dependency %q (version %q or higher) required by %q not found in repo" , depName , depVersion , cur .Name )
153+ }
79154 if depPkg , ok := byName [depName ]; ok {
80155 queue = append (queue , depPkg )
81156 } else if provPkg , ok := byProvides [depName ]; ok {
@@ -203,6 +278,8 @@ func ParsePrimary(baseURL string, pkggz string, releaseFile string, releaseSign
203278 switch key {
204279 case "Package" :
205280 pkg .Name = val
281+ case "Version" :
282+ pkg .Version = val
206283 case "Depends" :
207284 // Split dependencies by comma and trim spaces
208285 deps := strings .Split (val , "," )
@@ -251,3 +328,109 @@ func getFullUrl(filePath string, baseUrl string) (string, error) {
251328 fullURL := fmt .Sprintf ("%s/%s" , strings .TrimSuffix (baseUrl , "/" ), filePath )
252329 return fullURL , nil
253330}
331+
332+ // compareDebianVersions compares two Debian version strings.
333+ // Returns -1 if a < b, 0 if a == b, 1 if a > b.
334+ func compareDebianVersions (a , b string ) (int , error ) {
335+ // Helper to split epoch
336+ splitEpoch := func (ver string ) (epoch int , rest string ) {
337+ parts := strings .SplitN (ver , ":" , 2 )
338+ if len (parts ) == 2 {
339+ fmt .Sscanf (parts [0 ], "%d" , & epoch )
340+ rest = parts [1 ]
341+ } else {
342+ epoch = 0
343+ rest = ver
344+ }
345+ return
346+ }
347+
348+ // Helper to get next segment (numeric or non-numeric)
349+ nextSegment := func (s string ) (seg string , rest string , numeric bool ) {
350+ if s == "" {
351+ return "" , "" , false
352+ }
353+ if s [0 ] >= '0' && s [0 ] <= '9' {
354+ i := 0
355+ for i < len (s ) && s [i ] >= '0' && s [i ] <= '9' {
356+ i ++
357+ }
358+ return s [:i ], s [i :], true
359+ }
360+ i := 0
361+ for i < len (s ) && (s [i ] < '0' || s [i ] > '9' ) {
362+ i ++
363+ }
364+ return s [:i ], s [i :], false
365+ }
366+
367+ // Handle epoch
368+ epochA , restA := splitEpoch (a )
369+ epochB , restB := splitEpoch (b )
370+ if epochA < epochB {
371+ return - 1 , nil
372+ }
373+ if epochA > epochB {
374+ return 1 , nil
375+ }
376+
377+ // Compare the rest
378+ sa , sb := restA , restB
379+ for sa != "" || sb != "" {
380+ // Handle tilde (~)
381+ if len (sa ) > 0 && sa [0 ] == '~' {
382+ if len (sb ) == 0 || sb [0 ] != '~' {
383+ return - 1 , nil
384+ }
385+ sa = sa [1 :]
386+ sb = sb [1 :]
387+ continue
388+ }
389+ if len (sb ) > 0 && sb [0 ] == '~' {
390+ return 1 , nil
391+ }
392+
393+ segA , restA , numA := nextSegment (sa )
394+ segB , restB , numB := nextSegment (sb )
395+
396+ if segA == "" && segB == "" {
397+ sa , sb = restA , restB
398+ continue
399+ }
400+
401+ if numA && numB {
402+ // Remove leading zeros
403+ segA = strings .TrimLeft (segA , "0" )
404+ segB = strings .TrimLeft (segB , "0" )
405+ // Compare by length
406+ if len (segA ) > len (segB ) {
407+ return 1 , nil
408+ }
409+ if len (segA ) < len (segB ) {
410+ return - 1 , nil
411+ }
412+ // Compare lexicographically
413+ if segA > segB {
414+ return 1 , nil
415+ }
416+ if segA < segB {
417+ return - 1 , nil
418+ }
419+ } else if ! numA && ! numB {
420+ if segA > segB {
421+ return 1 , nil
422+ }
423+ if segA < segB {
424+ return - 1 , nil
425+ }
426+ } else {
427+ // Numeric segments are always less than non-numeric
428+ if numA {
429+ return - 1 , nil
430+ }
431+ return 1 , nil
432+ }
433+ sa , sb = restA , restB
434+ }
435+ return 0 , nil
436+ }
0 commit comments