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

@@ -104,6 +104,7 @@ To force install a package:
PreserveSystemEssentialData: true,
DownloadOnly: downloadOnly,
Ask: !yes,
Relaxed: relax,
})
inst.Repositories(repos)
@@ -125,6 +126,8 @@ func init() {
installCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
installCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
installCmd.Flags().Bool("nodeps", false, "Don't consider package dependencies (harmful!)")
installCmd.Flags().Bool("relax", false, "Relax installation constraints")
installCmd.Flags().Bool("onlydeps", false, "Consider **only** package dependencies")
installCmd.Flags().Bool("force", false, "Skip errors and keep going (potentially harmful)")
installCmd.Flags().Bool("solver-concurrent", false, "Use concurrent solver (experimental)")

View File

@@ -50,6 +50,7 @@ type LuetInstallerOptions struct {
SolverUpgrade, RemoveUnavailableOnUpgrade, UpgradeNewRevisions bool
Ask bool
DownloadOnly bool
Relaxed bool
}
type LuetInstaller struct {
@@ -513,7 +514,7 @@ func (l *LuetInstaller) Install(cp pkg.Packages, s *System) error {
return err
}
if len(s.Database.World()) > 0 {
if len(s.Database.World()) > 0 && !l.Options.Relaxed {
Info(":thinking: Checking for available upgrades")
if err := l.checkAndUpgrade(syncedRepos, s); err != nil {
return errors.Wrap(err, "while checking upgrades before install")
@@ -684,7 +685,12 @@ func (l *LuetInstaller) computeInstall(o Option, syncedRepos Repositories, cp pk
if !o.NoDeps {
solv := solver.NewResolver(solver.Options{Type: l.Options.SolverOptions.Implementation, Concurrency: l.Options.Concurrency}, s.Database, allRepos, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver())
if l.Options.Relaxed {
solution, err = solv.RelaxedInstall(p)
} else {
solution, err = solv.Install(p)
}
/// TODO: PackageAssertions needs to be a map[fingerprint]pack so lookup is in O(1)
if err != nil && !o.Force {
return toInstall, p, solution, allRepos, errors.Wrap(err, "Failed solving solution for package")

View File

@@ -72,6 +72,7 @@ type Package interface {
SetVersion(string)
RequiresContains(PackageDatabase, Package) (bool, error)
Matches(m Package) bool
AtomMatches(m Package) bool
BumpBuildVersion() error
AddUse(use string)
@@ -536,6 +537,13 @@ func (p *DefaultPackage) Matches(m Package) bool {
return false
}
func (p *DefaultPackage) AtomMatches(m Package) bool {
if p.GetName() == m.GetName() && p.GetCategory() == m.GetCategory() {
return true
}
return false
}
func (p *DefaultPackage) Mark() Package {
marked := p.Clone()
marked.SetName("@@" + marked.GetName())
@@ -795,37 +803,38 @@ func (pack *DefaultPackage) buildFormula(definitiondb PackageDatabase, db Packag
required = requiredDef
} else {
var ALO, priorityConstraints, priorityALO []bf.Formula
var ALO []bf.Formula // , priorityConstraints, priorityALO []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))
priorityALO = append(priorityALO, 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(bf.Not(A), bf.Or(bf.And(C, bf.Or(priorityConstraints...)), bf.And(bf.Not(C), bf.Or(priorityALO...)))))
}
// 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))
// priorityALO = append(priorityALO, 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(bf.Not(A), bf.Or(bf.And(C, bf.Or(priorityConstraints...)), bf.And(bf.Not(C), bf.Or(priorityALO...)))))
// formulas = append(formulas, bf.Or(C, bf.Or(priorityConstraints...)))
// }
// AMO - At most one
// AMO/ALO - At most/least one
for _, o := range packages {
encodedB, err := o.Encode(db)
if err != nil {

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
}
return s.Solve()
assertions, err := s.Solve()
if err != nil {
return nil, err
}
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..
func inPackage(list []pkg.Package, p pkg.Package) bool {
for _, l := range list {
if l.AtomMatches(p) {
return true
}
}
return 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, err := s.DefinitionDatabase.Copy()
if err != nil {
return nil, nil, errors.Wrap(err, "failed creating db copy")
}
universe, _ := defDB.Copy()
installedcopy := pkg.NewInMemoryDatabase(false)
for _, p := range s.InstalledDatabase.World() {
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) {
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
}
}
s2 := NewSolver(Options{Type: SingleCoreSimple}, installedcopy, s.DefinitionDatabase, pkg.NewInMemoryDatabase(false))
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
}
return s.Solve()
assertions, err := s.Solve()
if err != nil {
return nil, err
}
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() {