Introduce install --relaxed

It introduces a relaxed way to install packages with loose deps. Default
installation now will by default prefer up-to-date packages during
selection.

Also:
- Upgrade now it's used in install so it have to return the full system view also when there is nothing to upgrade
- Avoid checking upgrade upfront if relaxed is on
This commit is contained in:
Ettore Di Giacinto
2021-10-09 17:36:13 +02:00
parent 77b4c9a972
commit e64f68d36b
7 changed files with 329 additions and 64 deletions

View File

@@ -415,6 +415,7 @@ var _ = Describe("Decoder", func() {
orderW, err := solution.Order(dbDefinitions, W.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
Expect(len(orderW) > 0).To(BeTrue())
Expect(orderW[0].Package.GetName()).To(Equal("X"))
Expect(orderW[1].Package.GetName()).To(Equal("Y"))
Expect(orderW[2].Package.GetName()).To(Equal("Z"))

View File

@@ -550,6 +550,13 @@ func (s *Parallel) UpgradeUniverse(dropremoved bool) (pkg.Packages, PackagesAsse
// Upgrade compute upgrades of the package against the world definition.
// It accepts two boolean indicating if it has to check for conflicts or try to attempt a full upgrade
func (s *Parallel) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
return s.upgrade(s.DefinitionDatabase, s.InstalledDatabase, checkconflicts, full)
}
// Upgrade compute upgrades of the package against the world definition.
// It accepts two boolean indicating if it has to check for conflicts or try to attempt a full upgrade
func (s *Parallel) upgrade(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase, checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
// First get candidates that needs to be upgraded..
@@ -557,7 +564,7 @@ func (s *Parallel) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAss
toInstall := pkg.Packages{}
// we do this in memory so we take into account of provides
universe, err := s.DefinitionDatabase.Copy()
universe, err := defDB.Copy()
if err != nil {
return nil, nil, errors.Wrap(err, "Could not copy def db")
}
@@ -595,7 +602,7 @@ func (s *Parallel) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAss
}
}()
for _, p := range s.InstalledDatabase.World() {
for _, p := range installDB.World() {
all <- p
}
@@ -604,7 +611,7 @@ func (s *Parallel) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAss
close(results)
wg2.Wait()
s2 := &Parallel{Concurrency: s.Concurrency, InstalledDatabase: installedcopy, DefinitionDatabase: s.DefinitionDatabase, ParallelDatabase: pkg.NewInMemoryDatabase(false)}
s2 := &Parallel{Concurrency: s.Concurrency, InstalledDatabase: installedcopy, DefinitionDatabase: defDB, ParallelDatabase: pkg.NewInMemoryDatabase(false)}
s2.SetResolver(s.Resolver)
if !full {
ass := PackagesAssertions{}
@@ -855,6 +862,32 @@ func (s *Parallel) Install(c pkg.Packages) (PackagesAssertions, error) {
}
return ass, nil
}
assertions, err := s.Solve()
if err != nil {
return nil, err
}
return s.Solve()
return s.upgradeAssertions(assertions)
}
func (s *Parallel) upgradeAssertions(assertions PackagesAssertions) (PackagesAssertions, error) {
systemAfterInstall := pkg.NewInMemoryDatabase(false)
for _, p := range assertions {
if p.Value {
systemAfterInstall.CreatePackage(p.Package)
}
}
_, assertions, err := s.upgrade(s.DefinitionDatabase, systemAfterInstall, false, false)
if err != nil {
return nil, err
}
// for _, u := range toUninstall {
// systemAfterInstall.RemovePackage()
// }
return assertions, nil
}

View File

@@ -37,6 +37,8 @@ const (
type PackageSolver interface {
SetDefinitionDatabase(pkg.PackageDatabase)
Install(p pkg.Packages) (PackagesAssertions, error)
RelaxedInstall(p pkg.Packages) (PackagesAssertions, error)
Uninstall(checkconflicts, full bool, candidate ...pkg.Package) (pkg.Packages, error)
ConflictsWithInstalled(p pkg.Package) (bool, error)
ConflictsWith(p pkg.Package, ls pkg.Packages) (bool, error)
@@ -51,6 +53,7 @@ type PackageSolver interface {
SetResolver(PackageResolver)
Solve() (PackagesAssertions, error)
// BestInstall(c pkg.Packages) (PackagesAssertions, error)
}
// Solver is the default solver for luet
@@ -493,34 +496,47 @@ func (s *Solver) UpgradeUniverse(dropremoved bool) (pkg.Packages, PackagesAssert
return markedForRemoval, assertion, nil
}
func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
// First get candidates that needs to be upgraded..
toUninstall := pkg.Packages{}
toInstall := pkg.Packages{}
// we do this in memory so we take into account of provides, and its faster
universe, err := s.DefinitionDatabase.Copy()
if err != nil {
return nil, nil, errors.Wrap(err, "failed creating db copy")
}
installedcopy := pkg.NewInMemoryDatabase(false)
for _, p := range s.InstalledDatabase.World() {
installedcopy.CreatePackage(p)
packages, err := universe.FindPackageVersions(p)
if err == nil && len(packages) != 0 {
best := packages.Best(nil)
if !best.Matches(p) {
toUninstall = append(toUninstall, p)
toInstall = append(toInstall, best)
}
func inPackage(list []pkg.Package, p pkg.Package) bool {
for _, l := range list {
if l.AtomMatches(p) {
return true
}
}
return false
}
s2 := NewSolver(Options{Type: SingleCoreSimple}, installedcopy, s.DefinitionDatabase, pkg.NewInMemoryDatabase(false))
// 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) {
return func(defDB pkg.PackageDatabase, installDB pkg.PackageDatabase) (pkg.Packages, pkg.Packages, pkg.PackageDatabase) {
toUninstall := pkg.Packages{}
toInstall := pkg.Packages{}
// we do this in memory so we take into account of provides, and its faster
universe, _ := defDB.Copy()
installedcopy := pkg.NewInMemoryDatabase(false)
for _, p := range installDB.World() {
installedcopy.CreatePackage(p)
packages, err := universe.FindPackageVersions(p)
if err == nil && len(packages) != 0 {
best := packages.Best(nil)
if !best.Matches(p) && len(pps) == 0 ||
len(pps) != 0 && inPackage(pps, p) {
toUninstall = append(toUninstall, p)
toInstall = append(toInstall, best)
}
}
}
return toUninstall, toInstall, installedcopy
}
}
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) {
toUninstall, toInstall, installedcopy := fn(defDB, installDB)
s2 := NewSolver(Options{Type: SingleCoreSimple}, installedcopy, defDB, pkg.NewInMemoryDatabase(false))
s2.SetResolver(s.Resolver)
if !full {
ass := PackagesAssertions{}
@@ -541,15 +557,20 @@ func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAsser
}
if len(toInstall) == 0 {
return toUninstall, PackagesAssertions{}, nil
ass := PackagesAssertions{}
for _, i := range installDB.World() {
ass = append(ass, PackageAssert{Package: i.(*pkg.DefaultPackage), Value: true})
}
return toUninstall, ass, nil
}
assertions, err := s2.Install(toInstall.Unique())
assertions, err := s2.RelaxedInstall(toInstall.Unique())
return toUninstall, assertions, err
// To that tree, ask to install the versions that should be upgraded, and try to solve
// Return the solution
}
func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
return s.upgrade(s.computeUpgrade(), s.DefinitionDatabase, s.InstalledDatabase, checkconflicts, full)
}
// Uninstall takes a candidate package and return a list of packages that would be removed
@@ -619,7 +640,7 @@ func (s *Solver) Uninstall(checkconflicts, full bool, packs ...pkg.Package) (pkg
s2.SetResolver(s.Resolver)
// Get the requirements to install the candidate
asserts, err := s2.Install(toRemove)
asserts, err := s2.RelaxedInstall(toRemove)
if err != nil {
return nil, err
}
@@ -729,7 +750,7 @@ func (s *Solver) Solve() (PackagesAssertions, error) {
// Install given a list of packages, returns package assertions to indicate the packages that must be installed in the system in order
// to statisfy all the constraints
func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
func (s *Solver) RelaxedInstall(c pkg.Packages) (PackagesAssertions, error) {
coll, err := s.getList(s.DefinitionDatabase, c)
if err != nil {
@@ -749,6 +770,59 @@ func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
}
return ass, nil
}
assertions, err := s.Solve()
if err != nil {
return nil, err
}
return s.Solve()
return assertions, nil
}
// Install returns the assertions necessary in order to install the packages in
// a system.
// It calculates the best result possible, trying to maximize new packages.
func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
assertions, err := s.RelaxedInstall(c)
if err != nil {
return nil, err
}
systemAfterInstall := pkg.NewInMemoryDatabase(false)
toUpgrade := pkg.Packages{}
for _, p := range c {
if p.GetVersion() == ">=0" || p.GetVersion() == ">0" {
toUpgrade = append(toUpgrade, p)
}
}
for _, p := range assertions {
if p.Value {
systemAfterInstall.CreatePackage(p.Package)
if !inPackage(c, p.Package) {
toUpgrade = append(toUpgrade, p.Package)
}
}
}
if len(toUpgrade) == 0 {
return assertions, nil
}
// do partial upgrade based on input.
// IF there is no version specified in the input, or >=0 is specified,
// then compute upgrade for those
_, newassertions, err := s.upgrade(s.computeUpgrade(toUpgrade...), s.DefinitionDatabase, systemAfterInstall, false, false)
if err != nil {
// TODO: Emit warning.
// We were not able to compute upgrades (maybe for some pinned packages, or a conflict)
// so we return the relaxed result
return assertions, nil
}
// Protect if we return no assertion at all
if len(newassertions) == 0 && len(assertions) > 0 {
return assertions, nil
}
return newassertions, nil
}

View File

@@ -16,6 +16,8 @@
package solver_test
import (
"fmt"
pkg "github.com/mudler/luet/pkg/package"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -36,6 +38,143 @@ var _ = Describe("Solver", func() {
dbDefinitions = pkg.NewInMemoryDatabase(false)
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
})
Context("Select of best available package", func() {
It("picks the best versions available for each package, excluding the ones manually specified while installing", func() {
B1 := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B2 := pkg.NewPackage("B", "1.2", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B3 := pkg.NewPackage("B", "1.3", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B4 := pkg.NewPackage("B", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
A1 := pkg.NewPackage("A", "1.1", []*pkg.DefaultPackage{
pkg.NewPackage("B", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
}, []*pkg.DefaultPackage{})
A2 := pkg.NewPackage("A", "1.2", []*pkg.DefaultPackage{
pkg.NewPackage("B", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("D", "1.0", []*pkg.DefaultPackage{
pkg.NewPackage("A", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
}, []*pkg.DefaultPackage{})
C := pkg.NewPackage("C", "1", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{A1, A2, B1, B2, B3, B4, C, D} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{C} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
solution, err := s.(*Solver).Install([]pkg.Package{D})
fmt.Println(solution)
Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(8))
// Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: A2, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: B4, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B1, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B2, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
solution, err = s.(*Solver).Install([]pkg.Package{D, B2})
fmt.Println(solution)
Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(8))
// Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: A2, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: B2, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B1, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B4, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
})
It("picks the best available excluding those manually input. In this case we the input is a selector >=0", func() {
B1 := pkg.NewPackage("B", "1.1", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B2 := pkg.NewPackage("B", "1.2", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B3 := pkg.NewPackage("B", "1.3", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B4 := pkg.NewPackage("B", "1.4", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
A1 := pkg.NewPackage("A", "1.1", []*pkg.DefaultPackage{
pkg.NewPackage("B", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
}, []*pkg.DefaultPackage{})
A2 := pkg.NewPackage("A", "1.2", []*pkg.DefaultPackage{
pkg.NewPackage("B", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("D", "1.0", []*pkg.DefaultPackage{
pkg.NewPackage("A", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}),
}, []*pkg.DefaultPackage{})
C := pkg.NewPackage("C", "1", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{A1, A2, B1, B2, B3, B4, C, D} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{C} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
solution, err := s.(*Solver).Install([]pkg.Package{pkg.NewPackage("D", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})})
fmt.Println(solution)
Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(8))
// Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: A2, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: B4, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B1, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B2, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
s = NewSolver(Options{Type: SingleCoreSimple}, dbInstalled, dbDefinitions, db)
solution, err = s.(*Solver).Install([]pkg.Package{pkg.NewPackage("D", ">=0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}), B2})
fmt.Println(solution)
Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(8))
// Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: A2, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: B2, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B1, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B4, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B3, Value: false}))
})
})
Context("Simple set", func() {
It("Solves correctly if the selected package has no requirements or conflicts and we have nothing installed yet", func() {