Allow to partially uninstall a package graph, make uninstall --full optional

This commit is contained in:
Ettore Di Giacinto
2020-05-03 12:16:45 +02:00
parent 7b6e4a2176
commit a1d8ef1422
4 changed files with 54 additions and 25 deletions

View File

@@ -57,6 +57,8 @@ var uninstallCmd = &cobra.Command{
attempts := LuetCfg.Viper.GetInt("solver.max_attempts") attempts := LuetCfg.Viper.GetInt("solver.max_attempts")
force := LuetCfg.Viper.GetBool("force") force := LuetCfg.Viper.GetBool("force")
nodeps := LuetCfg.Viper.GetBool("nodeps") nodeps := LuetCfg.Viper.GetBool("nodeps")
full, _ := cmd.Flags().GetBool("full")
checkconflicts, _ := cmd.Flags().GetBool("conflictscheck")
LuetCfg.GetSolverOptions().Type = stype LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate) LuetCfg.GetSolverOptions().LearnRate = float32(rate)
@@ -66,10 +68,12 @@ var uninstallCmd = &cobra.Command{
Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{ inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency, Concurrency: LuetCfg.GetGeneral().Concurrency,
SolverOptions: *LuetCfg.GetSolverOptions(), SolverOptions: *LuetCfg.GetSolverOptions(),
NoDeps: nodeps, NoDeps: nodeps,
Force: force, Force: force,
FullUninstall: full,
CheckConflicts: checkconflicts,
}) })
if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { if LuetCfg.GetSystem().DatabaseEngine == "boltdb" {
@@ -98,8 +102,10 @@ func init() {
uninstallCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate") uninstallCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
uninstallCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate") uninstallCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
uninstallCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts") uninstallCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
uninstallCmd.Flags().Bool("nodeps", false, "Don't consider package dependencies (harmful!)") uninstallCmd.Flags().Bool("nodeps", false, "Don't consider package dependencies (harmful! overrides checkconflicts and full!)")
uninstallCmd.Flags().Bool("force", false, "Force uninstall") uninstallCmd.Flags().Bool("force", false, "Force uninstall")
uninstallCmd.Flags().Bool("full", false, "Attempts to remove as much packages as possible which aren't required (slow)")
uninstallCmd.Flags().Bool("conflictscheck", true, "Check if the package marked for deletion is required by other packages")
RootCmd.AddCommand(uninstallCmd) RootCmd.AddCommand(uninstallCmd)
} }

View File

@@ -41,6 +41,8 @@ type LuetInstallerOptions struct {
OnlyDeps bool OnlyDeps bool
Force bool Force bool
PreserveSystemEssentialData bool PreserveSystemEssentialData bool
FullUninstall bool
CheckConflicts bool
} }
type LuetInstaller struct { type LuetInstaller struct {
@@ -132,10 +134,8 @@ func (l *LuetInstaller) swap(syncedRepos Repositories, toRemove pkg.Packages, to
// if the old A results installed in the system. This is due to the fact that // if the old A results installed in the system. This is due to the fact that
// now the solver enforces the constraints and explictly denies two packages // now the solver enforces the constraints and explictly denies two packages
// of the same version installed. // of the same version installed.
forced := false forced := l.Options.Force
if l.Options.Force {
forced = true
}
l.Options.Force = true l.Options.Force = true
for _, u := range toRemove { for _, u := range toRemove {
@@ -572,10 +572,13 @@ func (l *LuetInstaller) Uninstall(p pkg.Package, s *System) error {
// compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) TODO - mark the uninstallation in db // compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) TODO - mark the uninstallation in db
// Get installed definition // Get installed definition
checkConflicts := true checkConflicts := l.Options.CheckConflicts
if l.Options.Force == true { full := l.Options.FullUninstall
if l.Options.Force == true { // IF forced, we want to remove the package and all its requires
checkConflicts = false checkConflicts = false
full = false
} }
// Create a temporary DB with the installed packages // Create a temporary DB with the installed packages
// so the solver is much faster finding the deptree // so the solver is much faster finding the deptree
installedtmp := pkg.NewInMemoryDatabase(false) installedtmp := pkg.NewInMemoryDatabase(false)
@@ -589,7 +592,7 @@ func (l *LuetInstaller) Uninstall(p pkg.Package, s *System) error {
if !l.Options.NoDeps { if !l.Options.NoDeps {
solv := solver.NewResolver(installedtmp, installedtmp, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver()) solv := solver.NewResolver(installedtmp, installedtmp, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver())
solution, err := solv.Uninstall(p, checkConflicts) solution, err := solv.Uninstall(p, checkConflicts, full)
if err != nil && !l.Options.Force { if err != nil && !l.Options.Force {
return errors.Wrap(err, "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps") return errors.Wrap(err, "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps")
} }

View File

@@ -18,6 +18,8 @@ package solver
import ( import (
//. "github.com/mudler/luet/pkg/logger" //. "github.com/mudler/luet/pkg/logger"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/crillab/gophersat/bf" "github.com/crillab/gophersat/bf"
@@ -28,7 +30,7 @@ import (
type PackageSolver interface { type PackageSolver interface {
SetDefinitionDatabase(pkg.PackageDatabase) SetDefinitionDatabase(pkg.PackageDatabase)
Install(p pkg.Packages) (PackagesAssertions, error) Install(p pkg.Packages) (PackagesAssertions, error)
Uninstall(candidate pkg.Package, checkconflicts bool) (pkg.Packages, error) Uninstall(candidate pkg.Package, checkconflicts, full bool) (pkg.Packages, error)
ConflictsWithInstalled(p pkg.Package) (bool, error) ConflictsWithInstalled(p pkg.Package) (bool, error)
ConflictsWith(p pkg.Package, ls pkg.Packages) (bool, error) ConflictsWith(p pkg.Package, ls pkg.Packages) (bool, error)
Conflicts(pack pkg.Package, lsp pkg.Packages) (bool, error) Conflicts(pack pkg.Package, lsp pkg.Packages) (bool, error)
@@ -175,7 +177,15 @@ func (s *Solver) Conflicts(pack pkg.Package, lsp pkg.Packages) (bool, error) {
visited := make(map[string]interface{}) visited := make(map[string]interface{})
revdeps := p.ExpandedRevdeps(temporarySet, visited) revdeps := p.ExpandedRevdeps(temporarySet, visited)
return len(revdeps) != 0, nil var revdepsErr error
for _, r := range revdeps {
if revdepsErr == nil {
revdepsErr = errors.New("")
}
revdepsErr = errors.New(fmt.Sprintf("%s\n%s", revdepsErr.Error(), r.HumanReadableString()))
}
return len(revdeps) != 0, revdepsErr
} }
// ConflictsWith return true if a package is part of the requirement set of a list of package // ConflictsWith return true if a package is part of the requirement set of a list of package
@@ -271,7 +281,7 @@ func (s *Solver) Upgrade(checkconflicts bool) (pkg.Packages, PackagesAssertions,
s2.SetResolver(s.Resolver) s2.SetResolver(s.Resolver)
// Then try to uninstall the versions in the system, and store that tree // Then try to uninstall the versions in the system, and store that tree
for _, p := range toUninstall { for _, p := range toUninstall {
r, err := s.Uninstall(p, checkconflicts) r, err := s.Uninstall(p, checkconflicts, false)
if err != nil { if err != nil {
return nil, nil, errors.Wrap(err, "Could not compute upgrade - couldn't uninstall selected candidate "+p.GetFingerPrint()) return nil, nil, errors.Wrap(err, "Could not compute upgrade - couldn't uninstall selected candidate "+p.GetFingerPrint())
} }
@@ -292,7 +302,7 @@ func (s *Solver) Upgrade(checkconflicts bool) (pkg.Packages, PackagesAssertions,
// 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
// in order to purge the candidate. Returns error if unsat. // in order to purge the candidate. Returns error if unsat.
func (s *Solver) Uninstall(c pkg.Package, checkconflicts bool) (pkg.Packages, error) { func (s *Solver) Uninstall(c pkg.Package, checkconflicts, full bool) (pkg.Packages, error) {
var res pkg.Packages var res pkg.Packages
candidate, err := s.InstalledDatabase.FindPackage(c) candidate, err := s.InstalledDatabase.FindPackage(c)
if err != nil { if err != nil {
@@ -311,6 +321,16 @@ func (s *Solver) Uninstall(c pkg.Package, checkconflicts bool) (pkg.Packages, er
// Build a fake "Installed" - Candidate and its requires tree // Build a fake "Installed" - Candidate and its requires tree
var InstalledMinusCandidate pkg.Packages var InstalledMinusCandidate pkg.Packages
// We are asked to not perform a full uninstall (checking all the possible requires that could
// be removed). Let's only check if we can remove the selected package
if !full && checkconflicts {
if conflicts, err := s.Conflicts(candidate, s.Installed()); conflicts {
return nil, err
} else {
return pkg.Packages{candidate}, nil
}
}
// TODO: Can be optimized // TODO: Can be optimized
for _, i := range s.Installed() { for _, i := range s.Installed() {
if !i.Matches(candidate) { if !i.Matches(candidate) {

View File

@@ -547,7 +547,7 @@ var _ = Describe("Solver", func() {
} }
s = NewSolver(dbInstalled, dbDefinitions, db) s = NewSolver(dbInstalled, dbDefinitions, db)
solution, err := s.Uninstall(A, true) solution, err := s.Uninstall(A, true, true)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(A.IsFlagged(false))) Expect(solution).To(ContainElement(A.IsFlagged(false)))
@@ -573,7 +573,7 @@ var _ = Describe("Solver", func() {
} }
s = NewSolver(dbInstalled, dbDefinitions, db) s = NewSolver(dbInstalled, dbDefinitions, db)
solution, err := s.Uninstall(&pkg.DefaultPackage{Name: "A", Version: ">1.0"}, true) solution, err := s.Uninstall(&pkg.DefaultPackage{Name: "A", Version: ">1.0"}, true, true)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(A.IsFlagged(false))) Expect(solution).To(ContainElement(A.IsFlagged(false)))
@@ -692,7 +692,7 @@ var _ = Describe("Solver", func() {
} }
val, err := s.Conflicts(A, dbInstalled.World()) val, err := s.Conflicts(A, dbInstalled.World())
Expect(err).ToNot(HaveOccurred()) Expect(err.Error()).To(Equal("\n/B-"))
Expect(val).To(BeTrue()) Expect(val).To(BeTrue())
}) })
@@ -716,7 +716,7 @@ var _ = Describe("Solver", func() {
} }
val, err := s.Conflicts(D, dbInstalled.World()) val, err := s.Conflicts(D, dbInstalled.World())
Expect(err).ToNot(HaveOccurred()) Expect(err.Error()).To(Equal("\n/A-\n/B-"))
Expect(val).To(BeTrue()) Expect(val).To(BeTrue())
}) })
@@ -781,7 +781,7 @@ var _ = Describe("Solver", func() {
_, err := dbInstalled.CreatePackage(p) _, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
solution, err := s.Uninstall(A, true) solution, err := s.Uninstall(A, true, true)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(A.IsFlagged(false))) Expect(solution).To(ContainElement(A.IsFlagged(false)))
@@ -805,7 +805,7 @@ var _ = Describe("Solver", func() {
_, err := dbInstalled.CreatePackage(p) _, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
solution, err := s.Uninstall(A, true) solution, err := s.Uninstall(A, true, true)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(A.IsFlagged(false))) Expect(solution).To(ContainElement(A.IsFlagged(false)))
@@ -828,7 +828,7 @@ var _ = Describe("Solver", func() {
_, err := dbInstalled.CreatePackage(p) _, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
solution, err := s.Uninstall(A, true) solution, err := s.Uninstall(A, true, true)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(A.IsFlagged(false))) Expect(solution).To(ContainElement(A.IsFlagged(false)))
@@ -853,7 +853,7 @@ var _ = Describe("Solver", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
solution, err := s.Uninstall(A, true) solution, err := s.Uninstall(A, true, true)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(A.IsFlagged(false))) Expect(solution).To(ContainElement(A.IsFlagged(false)))
@@ -879,7 +879,7 @@ var _ = Describe("Solver", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
solution, err := s.Uninstall(A, true) solution, err := s.Uninstall(A, true, true)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(A.IsFlagged(false))) Expect(solution).To(ContainElement(A.IsFlagged(false)))