Skip to content

Commit 9f1049e

Browse files
committed
Implemented dependency-templated resolver
1 parent e8b9735 commit 9f1049e

File tree

2 files changed

+89
-66
lines changed

2 files changed

+89
-66
lines changed

resolver.go

+65-48
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,21 @@ type Dependency interface {
1515
}
1616

1717
// Release represents a release, it must provide methods to return Name, Version and Dependencies
18-
type Release interface {
18+
type Release[D Dependency] interface {
1919
GetName() string
2020
GetVersion() *Version
21-
GetDependencies() []Dependency
21+
GetDependencies() []D
2222
}
2323

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
3027

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]
3431
for _, r := range set {
35-
if match(r, dep) {
32+
if c.Match(r.GetVersion()) {
3633
res = append(res, r)
3734
}
3835
}
@@ -41,49 +38,69 @@ func (set Releases) FilterBy(dep Dependency) Releases {
4138

4239
// SortDescent sort the Releases in this set in descending order (the lastest
4340
// release is the first)
44-
func (set Releases) SortDescent() {
41+
func (set Releases[R, D]) SortDescent() {
4542
sort.Slice(set, func(i, j int) bool {
4643
return set[i].GetVersion().GreaterThan(set[j].GetVersion())
4744
})
4845
}
4946

5047
// Archive contains all Releases set to consider for dependency resolution
51-
type Archive struct {
52-
Releases map[string]Releases
48+
type Archive[R Release[D], D Dependency] struct {
49+
releases map[string]Releases[R, D]
5350
}
5451

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(),
52+
// NewArchive creates a new archive
53+
func NewArchive[R Release[D], D Dependency]() *Archive[R, D] {
54+
return &Archive[R, D]{
55+
releases: map[string]Releases[R, D]{},
6156
}
62-
return ar.resolve(map[string]Release{}, []Dependency{mainDep}, map[Dependency]int{})
6357
}
6458

65-
type bareDependency struct {
66-
name string
67-
version *Version
59+
// AddRelease adds a release to this archive
60+
func (ar *Archive[R, D]) AddRelease(rel R) {
61+
relName := rel.GetName()
62+
ar.releases[relName] = append(ar.releases[relName], rel)
6863
}
6964

70-
func (b *bareDependency) GetName() string {
71-
return b.name
65+
// AddReleases adds all the releases to this archive
66+
func (ar *Archive[R, D]) AddReleases(rels ...R) {
67+
for _, rel := range rels {
68+
relName := rel.GetName()
69+
ar.releases[relName] = append(ar.releases[relName], rel)
70+
}
7271
}
7372

74-
func (b *bareDependency) GetConstraint() Constraint {
75-
return &Equals{Version: b.version}
73+
// Resolve will try to depp-resolve dependencies from the Release passed as
74+
// arguent using a backtracking algorithm.
75+
func (ar *Archive[R, D]) Resolve(release R) Releases[R, D] {
76+
// Initial empty state of the resolver
77+
solution := map[string]R{}
78+
depsToProcess := []D{}
79+
problematicDeps := map[dependencyHash]int{}
80+
81+
// Check if the release is in the archive
82+
if len(ar.releases[release.GetName()].FilterBy(&Equals{Version: release.GetVersion()})) == 0 {
83+
return nil
84+
}
85+
86+
// Add the requested release to the solution and proceed
87+
// with the dependencies resolution
88+
solution[release.GetName()] = release
89+
depsToProcess = append(depsToProcess, release.GetDependencies()...)
90+
return ar.resolve(solution, depsToProcess, problematicDeps)
7691
}
7792

78-
func (b *bareDependency) String() string {
79-
return b.GetName() + b.GetConstraint().String()
93+
type dependencyHash string
94+
95+
func hashDependency[D Dependency](dep D) dependencyHash {
96+
return dependencyHash(dep.GetName() + "/" + dep.GetConstraint().String())
8097
}
8198

82-
func (ar *Archive) resolve(solution map[string]Release, depsToProcess []Dependency, problematicDeps map[Dependency]int) []Release {
99+
func (ar *Archive[R, D]) resolve(solution map[string]R, depsToProcess []D, problematicDeps map[dependencyHash]int) Releases[R, D] {
83100
debug("deps to process: %s", depsToProcess)
84101
if len(depsToProcess) == 0 {
85102
debug("All dependencies have been resolved.")
86-
res := []Release{}
103+
var res Releases[R, D]
87104
for _, v := range solution {
88105
res = append(res, v)
89106
}
@@ -97,7 +114,7 @@ func (ar *Archive) resolve(solution map[string]Release, depsToProcess []Dependen
97114

98115
// If a release is already picked in the solution check if it match the dep
99116
if existingRelease, has := solution[depName]; has {
100-
if match(existingRelease, dep) {
117+
if dep.GetConstraint().Match(existingRelease.GetVersion()) {
101118
debug("%s already in solution and matching", existingRelease)
102119
return ar.resolve(solution, depsToProcess[1:], problematicDeps)
103120
}
@@ -106,35 +123,35 @@ func (ar *Archive) resolve(solution map[string]Release, depsToProcess []Dependen
106123
}
107124

108125
// Otherwise start backtracking the dependency
109-
releases := ar.Releases[dep.GetName()].FilterBy(dep)
126+
releases := ar.releases[dep.GetName()].FilterBy(dep.GetConstraint())
110127

111128
// Consider the latest versions first
112129
releases.SortDescent()
113130

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-
123131
debug("releases matching criteria: %s", releases)
124132
for _, release := range releases {
125133
deps := release.GetDependencies()
126134
debug("try with %s %s", release, deps)
127135

128-
if missingDep := findMissingDeps(deps); missingDep != nil {
129-
debug("%s did not work, becuase his dependency %s does not exists", release, missingDep.GetName())
136+
missingDep := false
137+
for _, dep := range deps {
138+
if _, ok := ar.releases[dep.GetName()]; !ok {
139+
debug("%s did not work, becuase his dependency %s does not exists", release, dep.GetName())
140+
missingDep = true
141+
break
142+
}
143+
}
144+
if missingDep {
130145
continue
131146
}
132147

133148
solution[depName] = release
134149
newDepsToProcess := append(depsToProcess[1:], deps...)
135150
// bubble up problematics deps so they are processed first
136151
sort.Slice(newDepsToProcess, func(i, j int) bool {
137-
return problematicDeps[newDepsToProcess[i]] > problematicDeps[newDepsToProcess[j]]
152+
ci := hashDependency(newDepsToProcess[i])
153+
cj := hashDependency(newDepsToProcess[j])
154+
return problematicDeps[ci] > problematicDeps[cj]
138155
})
139156
if res := ar.resolve(solution, newDepsToProcess, problematicDeps); res != nil {
140157
return res
@@ -143,6 +160,6 @@ func (ar *Archive) resolve(solution map[string]Release, depsToProcess []Dependen
143160
delete(solution, depName)
144161
}
145162

146-
problematicDeps[dep]++
163+
problematicDeps[hashDependency(dep)]++
147164
return nil
148165
}

resolver_test.go

+24-18
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ type customDep struct {
1919
cond Constraint
2020
}
2121

22+
// GetName return the name of the dependency (implements the Dependency interface)
2223
func (c *customDep) GetName() string {
2324
return c.name
2425
}
2526

27+
// GetConstraint return the version contraints of the dependency (implements the Dependency interface)
2628
func (c *customDep) GetConstraint() Constraint {
2729
return c.cond
2830
}
@@ -34,26 +36,28 @@ func (c *customDep) String() string {
3436
type customRel struct {
3537
name string
3638
vers *Version
37-
deps []Dependency
39+
deps []*customDep
3840
}
3941

42+
// GetName return the name of the release (implements the Release interface)
4043
func (r *customRel) GetName() string {
4144
return r.name
4245
}
4346

47+
// GetVersion return the version of the release (implements the Release interface)
4448
func (r *customRel) GetVersion() *Version {
4549
return r.vers
4650
}
4751

48-
func (r *customRel) GetDependencies() []Dependency {
52+
func (r *customRel) GetDependencies() []*customDep {
4953
return r.deps
5054
}
5155

5256
func (r *customRel) String() string {
5357
return r.name + "@" + r.vers.String()
5458
}
5559

56-
func d(dep string) Dependency {
60+
func d(dep string) *customDep {
5761
name := dep[0:1]
5862
cond, err := ParseConstraint(dep[1:])
5963
if err != nil {
@@ -62,15 +66,15 @@ func d(dep string) Dependency {
6266
return &customDep{name: name, cond: cond}
6367
}
6468

65-
func deps(deps ...string) []Dependency {
66-
res := []Dependency{}
69+
func deps(deps ...string) []*customDep {
70+
var res []*customDep
6771
for _, dep := range deps {
6872
res = append(res, d(dep))
6973
}
7074
return res
7175
}
7276

73-
func rel(name, ver string, deps []Dependency) Release {
77+
func rel(name, ver string, deps []*customDep) *customRel {
7478
return &customRel{name: name, vers: v(ver), deps: deps}
7579
}
7680

@@ -119,18 +123,17 @@ func TestResolver(t *testing.T) {
119123
i160 := rel("I", "1.6.0", deps())
120124
i170 := rel("I", "1.7.0", deps())
121125
i180 := rel("I", "1.8.0", deps())
122-
arch := &Archive{
123-
Releases: map[string]Releases{
124-
"A": {a100, a110, a111, a120, a121},
125-
"B": {b131, b130, b121, b120, b111, b110, b100},
126-
"C": {c200, c120, c111, c110, c102, c101, c100, c021, c020, c010},
127-
"D": {d100, d120},
128-
"E": {e100, e101},
129-
"G": {g130, g140, g150, g160, g170, g180},
130-
"H": {h130, h140, h150, h160, h170, h180},
131-
"I": {i130, i140, i150, i160, i170, i180},
132-
},
133-
}
126+
arch := NewArchive[*customRel, *customDep]()
127+
arch.AddReleases(
128+
a100, a110, a111, a120, a121,
129+
b131, b130, b121, b120, b111, b110, b100,
130+
c200, c120, c111, c110, c102, c101, c100, c021, c020, c010,
131+
d100, d120,
132+
e100, e101,
133+
g130, g140, g150, g160, g170, g180,
134+
h130, h140, h150, h160, h170, h180,
135+
i130, i140, i150, i160, i170, i180,
136+
)
134137

135138
a130 := rel("A", "1.3.0", deps())
136139
r0 := arch.Resolve(a130) // Non-existent in archive
@@ -179,4 +182,7 @@ func TestResolver(t *testing.T) {
179182
case <-time.After(time.Second):
180183
require.FailNow(t, "test didn't complete in the allocated time")
181184
}
185+
186+
r7 := arch.Resolve(e101)
187+
require.Nil(t, r7)
182188
}

0 commit comments

Comments
 (0)