Make sure we do compute the best fit

While install calls upgrade which in turns calls a relaxed install on
its results, this doesn't make sure that the new results are at the best
available version. We do iterate here over the results to compute the
best set.

It also expands computeUpgrade with the possibility to selectively
choose which packages to upgrade and which not.
This commit is contained in:
Ettore Di Giacinto
2021-10-10 19:04:55 +02:00
parent 780b7aa610
commit 6ba028f0ea
3 changed files with 188 additions and 138 deletions

View File

@@ -146,6 +146,17 @@ func (assertions PackagesAssertions) Search(f string) *PackageAssert {
return nil return nil
} }
func (assertions PackagesAssertions) ToDB() pkg.PackageDatabase {
db := pkg.NewInMemoryDatabase(false)
for _, a := range assertions {
if a.Value {
db.CreatePackage(a.Package)
}
}
return db
}
func (assertions PackagesAssertions) Order(definitiondb pkg.PackageDatabase, fingerprint string) (PackagesAssertions, error) { func (assertions PackagesAssertions) Order(definitiondb pkg.PackageDatabase, fingerprint string) (PackagesAssertions, error) {
orderedAssertions := PackagesAssertions{} orderedAssertions := PackagesAssertions{}

View File

@@ -503,36 +503,41 @@ func inPackage(list []pkg.Package, p pkg.Package) bool {
} }
// Compute upgrade between packages if specified, or all if none is specified // Compute upgrade between packages if specified, or all if none is specified
func (s *Solver) computeUpgrade(pps ...pkg.Package) func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase) { func (s *Solver) computeUpgrade(ppsToUpgrade, ppsToNotUpgrade []pkg.Package) func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase, []pkg.Package) {
return func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase) { return func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase, []pkg.Package) {
toUninstall := pkg.Packages{} toUninstall := pkg.Packages{}
toInstall := pkg.Packages{} toInstall := pkg.Packages{}
// we do this in memory so we take into account of provides, and its faster // we do this in memory so we take into account of provides, and its faster
universe, _ := defDB.Copy() universe, _ := defDB.Copy()
installedcopy := pkg.NewInMemoryDatabase(false) installedcopy := pkg.NewInMemoryDatabase(false)
for _, p := range installDB.World() { for _, p := range installDB.World() {
installedcopy.CreatePackage(p) installedcopy.CreatePackage(p)
packages, err := universe.FindPackageVersions(p) packages, err := universe.FindPackageVersions(p)
if err == nil && len(packages) != 0 { if err == nil && len(packages) != 0 {
best := packages.Best(nil) best := packages.Best(nil)
if !best.Matches(p) && len(pps) == 0 ||
len(pps) != 0 && inPackage(pps, p) { // This make sure that we don't try to upgrade something that was specified
// specifically to not be marked for upgrade
// At the same time, makes sure that if we mark a package to look for upgrades
// it doesn't have to be in the blacklist (the packages to NOT upgrade)
if !best.Matches(p) &&
((len(ppsToUpgrade) == 0 && len(ppsToNotUpgrade) == 0) ||
(inPackage(ppsToUpgrade, p) && !inPackage(ppsToNotUpgrade, p)) ||
(len(ppsToUpgrade) == 0 && !inPackage(ppsToNotUpgrade, p))) {
toUninstall = append(toUninstall, p) toUninstall = append(toUninstall, p)
toInstall = append(toInstall, best) toInstall = append(toInstall, best)
} }
} }
} }
return toUninstall, toInstall, installedcopy return toUninstall, toInstall, installedcopy, ppsToUpgrade
} }
} }
func (s *Solver) upgrade(fn func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase), defDB pkg.PackageDatabase, installDB pkg.PackageDatabase, checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) { func (s *Solver) upgrade(psToUpgrade, psToNotUpgrade pkg.Packages, fn func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase, []pkg.Package), defDB pkg.PackageDatabase, installDB pkg.PackageDatabase, checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
toUninstall, toInstall, installedcopy := fn(defDB, installDB)
toUninstall, toInstall, installedcopy, packsToUpgrade := fn(defDB, installDB)
s2 := NewSolver(Options{Type: SingleCoreSimple}, installedcopy, defDB, pkg.NewInMemoryDatabase(false)) s2 := NewSolver(Options{Type: SingleCoreSimple}, installedcopy, defDB, pkg.NewInMemoryDatabase(false))
s2.SetResolver(s.Resolver) s2.SetResolver(s.Resolver)
if !full { if !full {
@@ -560,14 +565,42 @@ func (s *Solver) upgrade(fn func(defDB pkg.PackageDatabase, installDB pkg.Packag
} }
return toUninstall, ass, nil return toUninstall, ass, nil
} }
assertions, err := s2.RelaxedInstall(toInstall.Unique()) assertions, err := s2.RelaxedInstall(toInstall.Unique())
wantedSystem := assertions.ToDB()
fn = s.computeUpgrade(pkg.Packages{}, pkg.Packages{})
if len(packsToUpgrade) > 0 {
// If we have packages in input,
// compute what we are looking to upgrade.
// those are assertions minus packsToUpgrade
var selectedPackages []pkg.Package
for _, p := range assertions {
if p.Value && !inPackage(psToUpgrade, p.Package) {
selectedPackages = append(selectedPackages, p.Package)
}
}
fn = s.computeUpgrade(selectedPackages, psToNotUpgrade)
}
_, toInstall, _, _ = fn(defDB, wantedSystem)
if len(toInstall) > 0 {
_, toInstall, ass := s.upgrade(psToUpgrade, psToNotUpgrade, fn, defDB, wantedSystem, checkconflicts, full)
return toUninstall, toInstall, ass
}
return toUninstall, assertions, err return toUninstall, assertions, err
} }
func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) { func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
return s.upgrade(s.computeUpgrade(), s.DefinitionDatabase, s.InstalledDatabase, checkconflicts, full)
installedcopy := pkg.NewInMemoryDatabase(false)
err := s.InstalledDatabase.Clone(installedcopy)
if err != nil {
return nil, nil, err
}
return s.upgrade(pkg.Packages{}, pkg.Packages{}, s.computeUpgrade(pkg.Packages{}, pkg.Packages{}), s.DefinitionDatabase, installedcopy, checkconflicts, full)
} }
// Uninstall takes a candidate package and return a list of packages that would be removed // Uninstall takes a candidate package and return a list of packages that would be removed
@@ -787,16 +820,18 @@ func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
systemAfterInstall := pkg.NewInMemoryDatabase(false) systemAfterInstall := pkg.NewInMemoryDatabase(false)
toUpgrade := pkg.Packages{} toUpgrade := pkg.Packages{}
toNotUpgrade := pkg.Packages{}
for _, p := range c { for _, p := range c {
if p.GetVersion() == ">=0" || p.GetVersion() == ">0" { if p.GetVersion() == ">=0" || p.GetVersion() == ">0" {
toUpgrade = append(toUpgrade, p) toUpgrade = append(toUpgrade, p)
} else {
toNotUpgrade = append(toNotUpgrade, p)
} }
} }
for _, p := range assertions { for _, p := range assertions {
if p.Value { if p.Value {
systemAfterInstall.CreatePackage(p.Package) systemAfterInstall.CreatePackage(p.Package)
if !inPackage(c, p.Package) { if !inPackage(c, p.Package) && !inPackage(toUpgrade, p.Package) && !inPackage(toNotUpgrade, p.Package) {
toUpgrade = append(toUpgrade, p.Package) toUpgrade = append(toUpgrade, p.Package)
} }
} }
@@ -805,10 +840,13 @@ func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
if len(toUpgrade) == 0 { if len(toUpgrade) == 0 {
return assertions, nil return assertions, nil
} }
toUninstall, _, _, _ := s.computeUpgrade(toUpgrade, toNotUpgrade)(s.DefinitionDatabase, systemAfterInstall)
if len(toUninstall) > 0 {
// do partial upgrade based on input. // do partial upgrade based on input.
// IF there is no version specified in the input, or >=0 is specified, // IF there is no version specified in the input, or >=0 is specified,
// then compute upgrade for those // then compute upgrade for those
_, newassertions, err := s.upgrade(s.computeUpgrade(toUpgrade...), s.DefinitionDatabase, systemAfterInstall, false, false) _, newassertions, err := s.upgrade(toUpgrade, toNotUpgrade, s.computeUpgrade(toUpgrade, toNotUpgrade), s.DefinitionDatabase, systemAfterInstall, false, false)
if err != nil { if err != nil {
// TODO: Emit warning. // TODO: Emit warning.
// We were not able to compute upgrades (maybe for some pinned packages, or a conflict) // We were not able to compute upgrades (maybe for some pinned packages, or a conflict)
@@ -820,6 +858,8 @@ func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
if len(newassertions) == 0 && len(assertions) > 0 { if len(newassertions) == 0 && len(assertions) > 0 {
return assertions, nil return assertions, nil
} }
return newassertions, nil return newassertions, nil
} }
return assertions, nil
}

View File

@@ -16,8 +16,6 @@
package solver_test package solver_test
import ( import (
"fmt"
pkg "github.com/mudler/luet/pkg/package" pkg "github.com/mudler/luet/pkg/package"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@@ -40,6 +38,7 @@ var _ = Describe("Solver", func() {
}) })
Context("Select of best available package", func() { Context("Select of best available package", func() {
for i := 0; i < 200; i++ {
It("picks the best versions available for each package, excluding the ones manually specified while installing", func() { It("picks the best versions available for each package, excluding the ones manually specified while installing", func() {
@@ -73,7 +72,7 @@ var _ = Describe("Solver", func() {
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db) s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
solution, err := s.(*Solver).Install([]pkg.Package{D}) solution, err := s.(*Solver).Install([]pkg.Package{D})
fmt.Println(solution)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(8)) Expect(len(solution)).To(Equal(8))
@@ -91,7 +90,6 @@ var _ = Describe("Solver", func() {
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db) s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
solution, err = s.(*Solver).Install([]pkg.Package{D, B2}) solution, err = s.(*Solver).Install([]pkg.Package{D, B2})
fmt.Println(solution)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(8)) Expect(len(solution)).To(Equal(8))
@@ -140,7 +138,6 @@ var _ = Describe("Solver", func() {
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db) s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
solution, err := s.(*Solver).Install([]pkg.Package{pkg.NewPackage("D", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})}) solution, err := s.(*Solver).Install([]pkg.Package{pkg.NewPackage("D", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})})
fmt.Println(solution)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(8)) Expect(len(solution)).To(Equal(8))
@@ -158,7 +155,6 @@ var _ = Describe("Solver", func() {
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db) s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
solution, err = s.(*Solver).Install([]pkg.Package{pkg.NewPackage("D", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}), B2}) solution, err = s.(*Solver).Install([]pkg.Package{pkg.NewPackage("D", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}), B2})
fmt.Println(solution)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(8)) Expect(len(solution)).To(Equal(8))
@@ -174,7 +170,10 @@ var _ = Describe("Solver", func() {
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false})) Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
}) })
}
}) })
Context("Simple set", func() { Context("Simple set", func() {
It("Solves correctly if the selected package has no requirements or conflicts and we have nothing installed yet", func() { It("Solves correctly if the selected package has no requirements or conflicts and we have nothing installed yet", func() {