Force the solver to look at the best match first

Add support clauses to force the solver to look after the best match
first.

Closes #29
This commit is contained in:
Ettore Di Giacinto 2019-12-16 17:56:52 +01:00 committed by Ettore Di Giacinto
parent 0ccaf47f45
commit d583fa8bf5
2 changed files with 66 additions and 20 deletions

View File

@ -386,7 +386,6 @@ func Best(set []Package) Package {
}
func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db PackageDatabase) ([]bf.Formula, error) {
// TODO: Expansion needs to go here - and so we ditch Resolvedeps()
p, err := definitiondb.FindPackage(pack)
if err != nil {
p = pack // Relax failures and trust the def
@ -400,11 +399,6 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
var formulas []bf.Formula
for _, requiredDef := range p.GetRequires() {
// TODO: Stabilize this. We allow any of those version to be selected,
// at the price that they can't be selected alltogether.
// This have the downside that we cannot specify a preference (e.g. The best matching)
// unless we have a user-defined version. It means that the solver could
// give different output between calls, but they are all legit as they respect the constraints.
required, err := definitiondb.FindPackage(requiredDef)
if err != nil {
packages, err := definitiondb.FindPackages(requiredDef)
@ -414,19 +408,43 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
if len(packages) == 1 {
required = packages[0]
} else {
var bb []bf.Formula
var ALO, priorityConstraints []bf.Formula
// Try to prio best match
// Force the solver to consider first our candidate (if does exists).
// Then builds ALO and AMO for the requires.
c, candidateErr := definitiondb.FindPackageCandidate(requiredDef)
var C bf.Formula
if candidateErr == nil {
// We have a desired candidate, try to look a solution with that included first
for _, o := range packages {
encodedB, err := o.Encode(db)
if err != nil {
return nil, err
}
B := bf.Var(encodedB)
if !o.Matches(c) {
priorityConstraints = append(priorityConstraints, bf.Not(B))
}
}
encodedC, err := c.Encode(db)
if err != nil {
return nil, err
}
C = bf.Var(encodedC)
// Or the Candidate is true, or all the others might be not true
// This forces the CDCL sat implementation to look first at a solution with C=true
formulas = append(formulas, bf.Or(C, bf.Or(priorityConstraints...)))
}
// AMO - At most one
for _, o := range packages {
encodedB, err := o.Encode(db)
if err != nil {
return nil, err
}
B := bf.Var(encodedB)
bb = append(bb, B)
// f, err := o.BuildFormula(definitiondb, db)
// if err != nil {
// return nil, err
// }
// formulas = append(formulas, f...)
ALO = append(ALO, B)
for _, i := range packages {
encodedI, err := i.Encode(db)
if err != nil {
@ -434,16 +452,11 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
}
I := bf.Var(encodedI)
if !o.Matches(i) {
// formulas = append(formulas, bf.Or(I, B))
formulas = append(formulas, bf.Or(bf.Not(I), bf.Not(B)))
}
}
}
formulas = append(formulas, bf.Or(bb...))
formulas = append(formulas, bf.Or(ALO...)) // ALO - At least one
continue
}
}
@ -489,7 +502,6 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
}
}
// return nil, errors.Wrap(err, "Couldn't find required package in db definition")
encodedB, err := required.Encode(db)
if err != nil {
return nil, err

View File

@ -359,6 +359,40 @@ var _ = Describe("Solver", func() {
Expect(len(solution)).To(Equal(5))
Expect(err).ToNot(HaveOccurred())
})
It("Selects best version", func() {
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
D2 := pkg.NewPackage("D", "1.9", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("D", "1.8", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
D1 := pkg.NewPackage("D", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0"}}, []*pkg.DefaultPackage{})
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "D", Version: ">=1.0"}}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{A, B, C, D, D1, D2, E} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
s = NewSolver(dbInstalled, dbDefinitions, db)
solution, err := s.Install([]pkg.Package{A, B})
Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
Expect(solution).ToNot(ContainElement(PackageAssert{Package: D1, Value: true}))
Expect(solution).ToNot(ContainElement(PackageAssert{Package: D, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: D2, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: D1, Value: false}))
Expect(len(solution)).To(Equal(5))
Expect(err).ToNot(HaveOccurred())
})
It("Uninstalls simple package correctly", func() {
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})