Skip to content

Commit 8151b13

Browse files
authored
Merge pull request #15 from bugst/speedup
Speedup dependency resolver by analizying fruitless branches first
2 parents df298c3 + 7cce8cf commit 8151b13

File tree

2 files changed

+76
-11
lines changed

2 files changed

+76
-11
lines changed

resolver.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,31 @@ type Archive struct {
5555
// Resolve will try to depp-resolve dependencies from the Release passed as
5656
// arguent using a backtracking algorithm.
5757
func (ar *Archive) Resolve(release Release) []Release {
58-
solution := map[string]Release{release.GetName(): release}
59-
depsToProcess := release.GetDependencies()
60-
return ar.resolve(solution, depsToProcess)
58+
mainDep := &bareDependency{
59+
name: release.GetName(),
60+
version: release.GetVersion(),
61+
}
62+
return ar.resolve(map[string]Release{}, []Dependency{mainDep}, map[Dependency]int{})
63+
}
64+
65+
type bareDependency struct {
66+
name string
67+
version *Version
68+
}
69+
70+
func (b *bareDependency) GetName() string {
71+
return b.name
6172
}
6273

63-
func (ar *Archive) resolve(solution map[string]Release, depsToProcess []Dependency) []Release {
74+
func (b *bareDependency) GetConstraint() Constraint {
75+
return &Equals{Version: b.version}
76+
}
77+
78+
func (b *bareDependency) String() string {
79+
return b.GetName() + b.GetConstraint().String()
80+
}
81+
82+
func (ar *Archive) resolve(solution map[string]Release, depsToProcess []Dependency, problematicDeps map[Dependency]int) []Release {
6483
debug("deps to process: %s", depsToProcess)
6584
if len(depsToProcess) == 0 {
6685
debug("All dependencies have been resolved.")
@@ -80,7 +99,7 @@ func (ar *Archive) resolve(solution map[string]Release, depsToProcess []Dependen
8099
if existingRelease, has := solution[depName]; has {
81100
if match(existingRelease, dep) {
82101
debug("%s already in solution and matching", existingRelease)
83-
return ar.resolve(solution, depsToProcess[1:])
102+
return ar.resolve(solution, depsToProcess[1:], problematicDeps)
84103
}
85104
debug("%s already in solution do not match... rollingback", existingRelease)
86105
return nil
@@ -112,12 +131,18 @@ func (ar *Archive) resolve(solution map[string]Release, depsToProcess []Dependen
112131
}
113132

114133
solution[depName] = release
115-
res := ar.resolve(solution, append(depsToProcess[1:], deps...))
116-
if res != nil {
134+
newDepsToProcess := append(depsToProcess[1:], deps...)
135+
// 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]]
138+
})
139+
if res := ar.resolve(solution, newDepsToProcess, problematicDeps); res != nil {
117140
return res
118141
}
119142
debug("%s did not work...", release)
120143
delete(solution, depName)
121144
}
145+
146+
problematicDeps[dep]++
122147
return nil
123148
}

resolver_test.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package semver
99
import (
1010
"fmt"
1111
"testing"
12+
"time"
1213

1314
"github.com/stretchr/testify/require"
1415
)
@@ -74,6 +75,11 @@ func rel(name, ver string, deps []Dependency) Release {
7475
}
7576

7677
func TestResolver(t *testing.T) {
78+
a100 := rel("A", "1.0.0", deps("B>=1.2.0", "C>=2.0.0"))
79+
a110 := rel("A", "1.1.0", deps("B=1.2.0", "C>=2.0.0"))
80+
a111 := rel("A", "1.1.1", deps("B", "C=1.1.1"))
81+
a120 := rel("A", "1.2.0", deps("B=1.2.0", "C>2.0.0"))
82+
a121 := rel("A", "1.2.1", deps("B", "C", "G", "H", "I", "E=1.0.1"))
7783
b131 := rel("B", "1.3.1", deps("C<2.0.0"))
7884
b130 := rel("B", "1.3.0", deps())
7985
b121 := rel("B", "1.2.1", deps())
@@ -95,19 +101,40 @@ func TestResolver(t *testing.T) {
95101
d120 := rel("D", "1.2.0", deps("E"))
96102
e100 := rel("E", "1.0.0", deps())
97103
e101 := rel("E", "1.0.1", deps("F")) // INVALID
104+
g130 := rel("G", "1.3.0", deps())
105+
g140 := rel("G", "1.4.0", deps())
106+
g150 := rel("G", "1.5.0", deps())
107+
g160 := rel("G", "1.6.0", deps())
108+
g170 := rel("G", "1.7.0", deps())
109+
g180 := rel("G", "1.8.0", deps())
110+
h130 := rel("H", "1.3.0", deps())
111+
h140 := rel("H", "1.4.0", deps())
112+
h150 := rel("H", "1.5.0", deps())
113+
h160 := rel("H", "1.6.0", deps())
114+
h170 := rel("H", "1.7.0", deps())
115+
h180 := rel("H", "1.8.0", deps())
116+
i130 := rel("I", "1.3.0", deps())
117+
i140 := rel("I", "1.4.0", deps())
118+
i150 := rel("I", "1.5.0", deps())
119+
i160 := rel("I", "1.6.0", deps())
120+
i170 := rel("I", "1.7.0", deps())
121+
i180 := rel("I", "1.8.0", deps())
98122
arch := &Archive{
99123
Releases: map[string]Releases{
124+
"A": {a100, a110, a111, a120, a121},
100125
"B": {b131, b130, b121, b120, b111, b110, b100},
101126
"C": {c200, c120, c111, c110, c102, c101, c100, c021, c020, c010},
102127
"D": {d100, d120},
103128
"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},
104132
},
105133
}
106134

107-
a100 := rel("A", "1.0.0", deps("B>=1.2.0", "C>=2.0.0"))
108-
a110 := rel("A", "1.1.0", deps("B=1.2.0", "C>=2.0.0"))
109-
a111 := rel("A", "1.1.1", deps("B", "C=1.1.1"))
110-
a120 := rel("A", "1.2.0", deps("B=1.2.0", "C>2.0.0"))
135+
a130 := rel("A", "1.3.0", deps())
136+
r0 := arch.Resolve(a130) // Non-existent in archive
137+
require.Nil(t, r0)
111138

112139
r1 := arch.Resolve(a100)
113140
require.Len(t, r1, 3)
@@ -139,4 +166,17 @@ func TestResolver(t *testing.T) {
139166
require.Contains(t, r5, d120)
140167
require.Contains(t, r5, e100)
141168
fmt.Println(r5)
169+
170+
done := make(chan bool)
171+
go func() {
172+
r6 := arch.Resolve(a121)
173+
require.Nil(t, r6)
174+
fmt.Println(r6)
175+
close(done)
176+
}()
177+
select {
178+
case <-done:
179+
case <-time.After(time.Second):
180+
require.FailNow(t, "test didn't complete in the allocated time")
181+
}
142182
}

0 commit comments

Comments
 (0)