@@ -15,24 +15,21 @@ type Dependency interface {
15
15
}
16
16
17
17
// Release represents a release, it must provide methods to return Name, Version and Dependencies
18
- type Release interface {
18
+ type Release [ D Dependency ] interface {
19
19
GetName () string
20
20
GetVersion () * Version
21
- GetDependencies () []Dependency
21
+ GetDependencies () []D
22
22
}
23
23
24
- func match (r Release , dep Dependency ) bool {
25
- return r .GetName () == dep .GetName () && dep .GetConstraint ().Match (r .GetVersion ())
26
- }
27
-
28
- // Releases is a list of Release
29
- type Releases []Release
24
+ // Releases is a list of Release of the same package (all releases with
25
+ // the same Name but different Version)
26
+ type Releases [R Release [D ], D Dependency ] []R
30
27
31
- // FilterBy return a subset of the Releases matching the provided Dependency
32
- func (set Releases ) FilterBy (dep Dependency ) Releases {
33
- res := [] Release {}
28
+ // FilterBy return a subset of the Releases matching the provided Constraint
29
+ func (set Releases [ R , D ] ) FilterBy (c Constraint ) Releases [ R , D ] {
30
+ var res Releases [ R , D ]
34
31
for _ , r := range set {
35
- if match ( r , dep ) {
32
+ if c . Match ( r . GetVersion () ) {
36
33
res = append (res , r )
37
34
}
38
35
}
@@ -41,108 +38,138 @@ func (set Releases) FilterBy(dep Dependency) Releases {
41
38
42
39
// SortDescent sort the Releases in this set in descending order (the lastest
43
40
// release is the first)
44
- func (set Releases ) SortDescent () {
41
+ func (set Releases [ R , D ] ) SortDescent () {
45
42
sort .Slice (set , func (i , j int ) bool {
46
43
return set [i ].GetVersion ().GreaterThan (set [j ].GetVersion ())
47
44
})
48
45
}
49
46
50
- // Archive contains all Releases set to consider for dependency resolution
51
- type Archive struct {
52
- Releases map [string ]Releases
47
+ // Resolver is a container with references to all Releases to consider for
48
+ // dependency resolution
49
+ type Resolver [R Release [D ], D Dependency ] struct {
50
+ releases map [string ]Releases [R , D ]
51
+
52
+ // resolver state
53
+ solution map [string ]R
54
+ depsToProcess []D
55
+ problematicDeps map [dependencyHash ]int
53
56
}
54
57
55
- // Resolve will try to depp-resolve dependencies from the Release passed as
56
- // arguent using a backtracking algorithm.
57
- func (ar * Archive ) Resolve (release Release ) []Release {
58
- mainDep := & bareDependency {
59
- name : release .GetName (),
60
- version : release .GetVersion (),
58
+ // NewResolver creates a new archive
59
+ func NewResolver [R Release [D ], D Dependency ]() * Resolver [R , D ] {
60
+ return & Resolver [R , D ]{
61
+ releases : map [string ]Releases [R , D ]{},
61
62
}
62
- return ar .resolve (map [string ]Release {}, []Dependency {mainDep }, map [Dependency ]int {})
63
63
}
64
64
65
- type bareDependency struct {
66
- name string
67
- version * Version
65
+ // AddRelease adds a release to this archive
66
+ func (ar * Resolver [R , D ]) AddRelease (rel R ) {
67
+ relName := rel .GetName ()
68
+ ar .releases [relName ] = append (ar .releases [relName ], rel )
68
69
}
69
70
70
- func (b * bareDependency ) GetName () string {
71
- return b .name
71
+ // AddReleases adds all the releases to this archive
72
+ func (ar * Resolver [R , D ]) AddReleases (rels ... R ) {
73
+ for _ , rel := range rels {
74
+ relName := rel .GetName ()
75
+ ar .releases [relName ] = append (ar .releases [relName ], rel )
76
+ }
72
77
}
73
78
74
- func (b * bareDependency ) GetConstraint () Constraint {
75
- return & Equals {Version : b .version }
79
+ // Resolve will try to depp-resolve dependencies from the Release passed as
80
+ // arguent using a backtracking algorithm. This function is NOT thread-safe.
81
+ func (ar * Resolver [R , D ]) Resolve (release R ) Releases [R , D ] {
82
+ // Initial empty state of the resolver
83
+ ar .solution = map [string ]R {}
84
+ ar .depsToProcess = []D {}
85
+ ar .problematicDeps = map [dependencyHash ]int {}
86
+
87
+ // Check if the release is in the archive
88
+ if len (ar .releases [release .GetName ()].FilterBy (& Equals {Version : release .GetVersion ()})) == 0 {
89
+ return nil
90
+ }
91
+
92
+ // Add the requested release to the solution and proceed
93
+ // with the dependencies resolution
94
+ ar .solution [release .GetName ()] = release
95
+ ar .depsToProcess = append (ar .depsToProcess , release .GetDependencies ()... )
96
+ return ar .resolve ()
76
97
}
77
98
78
- func (b * bareDependency ) String () string {
79
- return b .GetName () + b .GetConstraint ().String ()
99
+ type dependencyHash string
100
+
101
+ func hashDependency [D Dependency ](dep D ) dependencyHash {
102
+ return dependencyHash (dep .GetName () + "/" + dep .GetConstraint ().String ())
80
103
}
81
104
82
- func (ar * Archive ) resolve (solution map [ string ] Release , depsToProcess [] Dependency , problematicDeps map [ Dependency ] int ) [] Release {
83
- debug ("deps to process: %s" , depsToProcess )
84
- if len (depsToProcess ) == 0 {
105
+ func (ar * Resolver [ R , D ] ) resolve () Releases [ R , D ] {
106
+ debug ("deps to process: %s" , ar . depsToProcess )
107
+ if len (ar . depsToProcess ) == 0 {
85
108
debug ("All dependencies have been resolved." )
86
- res := [] Release {}
87
- for _ , v := range solution {
109
+ var res Releases [ R , D ]
110
+ for _ , v := range ar . solution {
88
111
res = append (res , v )
89
112
}
90
113
return res
91
114
}
92
115
93
116
// Pick the first dependency in the deps to process
94
- dep := depsToProcess [0 ]
117
+ dep := ar . depsToProcess [0 ]
95
118
depName := dep .GetName ()
96
119
debug ("Considering next dep: %s" , depName )
97
120
98
121
// If a release is already picked in the solution check if it match the dep
99
- if existingRelease , has := solution [depName ]; has {
100
- if match ( existingRelease , dep ) {
122
+ if existingRelease , has := ar . solution [depName ]; has {
123
+ if dep . GetConstraint (). Match ( existingRelease . GetVersion () ) {
101
124
debug ("%s already in solution and matching" , existingRelease )
102
- return ar .resolve (solution , depsToProcess [1 :], problematicDeps )
125
+ oldDepsToProcess := ar .depsToProcess
126
+ ar .depsToProcess = ar .depsToProcess [1 :]
127
+ if res := ar .resolve (); res != nil {
128
+ return res
129
+ }
130
+ ar .depsToProcess = oldDepsToProcess
131
+ return nil
103
132
}
104
133
debug ("%s already in solution do not match... rollingback" , existingRelease )
105
134
return nil
106
135
}
107
136
108
137
// Otherwise start backtracking the dependency
109
- releases := ar .Releases [ dep . GetName () ].FilterBy (dep )
138
+ releases := ar .releases [ depName ].FilterBy (dep . GetConstraint () )
110
139
111
140
// Consider the latest versions first
112
141
releases .SortDescent ()
113
-
114
- findMissingDeps := func (deps []Dependency ) Dependency {
115
- for _ , dep := range deps {
116
- if _ , ok := ar .Releases [dep .GetName ()]; ! ok {
117
- return dep
118
- }
119
- }
120
- return nil
121
- }
122
-
123
142
debug ("releases matching criteria: %s" , releases )
143
+
144
+ backtracking_loop:
124
145
for _ , release := range releases {
125
- deps := release .GetDependencies ()
126
- debug ("try with %s %s" , release , deps )
146
+ releaseDeps := release .GetDependencies ()
147
+ debug ("try with %s %s" , release , releaseDeps )
127
148
128
- if missingDep := findMissingDeps (deps ); missingDep != nil {
129
- debug ("%s did not work, becuase his dependency %s does not exists" , release , missingDep .GetName ())
130
- continue
149
+ for _ , releaseDep := range releaseDeps {
150
+ if _ , ok := ar .releases [releaseDep .GetName ()]; ! ok {
151
+ debug ("%s did not work, becuase his dependency %s does not exists" , release , releaseDep .GetName ())
152
+ continue backtracking_loop
153
+ }
131
154
}
132
155
133
- solution [depName ] = release
134
- newDepsToProcess := append (depsToProcess [1 :], deps ... )
156
+ ar .solution [depName ] = release
157
+ oldDepsToProcess := ar .depsToProcess
158
+ ar .depsToProcess = append (ar .depsToProcess [1 :], releaseDeps ... )
135
159
// bubble up problematics deps so they are processed first
136
- sort .Slice (newDepsToProcess , func (i , j int ) bool {
137
- return problematicDeps [newDepsToProcess [i ]] > problematicDeps [newDepsToProcess [j ]]
160
+ sort .Slice (ar .depsToProcess , func (i , j int ) bool {
161
+ ci := hashDependency (ar .depsToProcess [i ])
162
+ cj := hashDependency (ar .depsToProcess [j ])
163
+ return ar .problematicDeps [ci ] > ar .problematicDeps [cj ]
138
164
})
139
- if res := ar .resolve (solution , newDepsToProcess , problematicDeps ); res != nil {
165
+ if res := ar .resolve (); res != nil {
140
166
return res
141
167
}
168
+ ar .depsToProcess = oldDepsToProcess
142
169
debug ("%s did not work..." , release )
143
- delete (solution , depName )
170
+ delete (ar . solution , depName )
144
171
}
145
172
146
- problematicDeps [dep ]++
173
+ ar . problematicDeps [hashDependency ( dep ) ]++
147
174
return nil
148
175
}
0 commit comments