From a1d8ef1422b35953807af94521dc1393033127cd Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Sun, 3 May 2020 12:16:45 +0200 Subject: [PATCH] Allow to partially uninstall a package graph, make uninstall --full optional --- cmd/uninstall.go | 16 +++++++++++----- pkg/installer/installer.go | 17 ++++++++++------- pkg/solver/solver.go | 28 ++++++++++++++++++++++++---- pkg/solver/solver_test.go | 18 +++++++++--------- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/cmd/uninstall.go b/cmd/uninstall.go index 3574ae4e..4d3bc63f 100644 --- a/cmd/uninstall.go +++ b/cmd/uninstall.go @@ -57,6 +57,8 @@ var uninstallCmd = &cobra.Command{ attempts := LuetCfg.Viper.GetInt("solver.max_attempts") force := LuetCfg.Viper.GetBool("force") nodeps := LuetCfg.Viper.GetBool("nodeps") + full, _ := cmd.Flags().GetBool("full") + checkconflicts, _ := cmd.Flags().GetBool("conflictscheck") LuetCfg.GetSolverOptions().Type = stype LuetCfg.GetSolverOptions().LearnRate = float32(rate) @@ -66,10 +68,12 @@ var uninstallCmd = &cobra.Command{ Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{ - Concurrency: LuetCfg.GetGeneral().Concurrency, - SolverOptions: *LuetCfg.GetSolverOptions(), - NoDeps: nodeps, - Force: force, + Concurrency: LuetCfg.GetGeneral().Concurrency, + SolverOptions: *LuetCfg.GetSolverOptions(), + NoDeps: nodeps, + Force: force, + FullUninstall: full, + CheckConflicts: checkconflicts, }) 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-discount", 1.0, "Solver discount rate") 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("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) } diff --git a/pkg/installer/installer.go b/pkg/installer/installer.go index 7927e809..47ecb9cd 100644 --- a/pkg/installer/installer.go +++ b/pkg/installer/installer.go @@ -41,6 +41,8 @@ type LuetInstallerOptions struct { OnlyDeps bool Force bool PreserveSystemEssentialData bool + FullUninstall bool + CheckConflicts bool } 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 // now the solver enforces the constraints and explictly denies two packages // of the same version installed. - forced := false - if l.Options.Force { - forced = true - } + forced := l.Options.Force + l.Options.Force = true 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 // Get installed definition - checkConflicts := true - if l.Options.Force == true { + checkConflicts := l.Options.CheckConflicts + full := l.Options.FullUninstall + if l.Options.Force == true { // IF forced, we want to remove the package and all its requires checkConflicts = false + full = false } + // Create a temporary DB with the installed packages // so the solver is much faster finding the deptree installedtmp := pkg.NewInMemoryDatabase(false) @@ -589,7 +592,7 @@ func (l *LuetInstaller) Uninstall(p pkg.Package, s *System) error { if !l.Options.NoDeps { 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 { 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") } diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index a64f6955..a82a5c81 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -18,6 +18,8 @@ package solver import ( //. "github.com/mudler/luet/pkg/logger" + "fmt" + "github.com/pkg/errors" "github.com/crillab/gophersat/bf" @@ -28,7 +30,7 @@ import ( type PackageSolver interface { SetDefinitionDatabase(pkg.PackageDatabase) 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) ConflictsWith(p pkg.Package, ls 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{}) 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 @@ -271,7 +281,7 @@ func (s *Solver) Upgrade(checkconflicts bool) (pkg.Packages, PackagesAssertions, s2.SetResolver(s.Resolver) // Then try to uninstall the versions in the system, and store that tree for _, p := range toUninstall { - r, err := s.Uninstall(p, checkconflicts) + r, err := s.Uninstall(p, checkconflicts, false) if err != nil { 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 // 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 candidate, err := s.InstalledDatabase.FindPackage(c) 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 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 for _, i := range s.Installed() { if !i.Matches(candidate) { diff --git a/pkg/solver/solver_test.go b/pkg/solver/solver_test.go index 3f88b925..e09130fb 100644 --- a/pkg/solver/solver_test.go +++ b/pkg/solver/solver_test.go @@ -547,7 +547,7 @@ var _ = Describe("Solver", func() { } s = NewSolver(dbInstalled, dbDefinitions, db) - solution, err := s.Uninstall(A, true) + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) Expect(solution).To(ContainElement(A.IsFlagged(false))) @@ -573,7 +573,7 @@ var _ = Describe("Solver", func() { } 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(solution).To(ContainElement(A.IsFlagged(false))) @@ -692,7 +692,7 @@ var _ = Describe("Solver", func() { } val, err := s.Conflicts(A, dbInstalled.World()) - Expect(err).ToNot(HaveOccurred()) + Expect(err.Error()).To(Equal("\n/B-")) Expect(val).To(BeTrue()) }) @@ -716,7 +716,7 @@ var _ = Describe("Solver", func() { } val, err := s.Conflicts(D, dbInstalled.World()) - Expect(err).ToNot(HaveOccurred()) + Expect(err.Error()).To(Equal("\n/A-\n/B-")) Expect(val).To(BeTrue()) }) @@ -781,7 +781,7 @@ var _ = Describe("Solver", func() { _, err := dbInstalled.CreatePackage(p) Expect(err).ToNot(HaveOccurred()) } - solution, err := s.Uninstall(A, true) + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) Expect(solution).To(ContainElement(A.IsFlagged(false))) @@ -805,7 +805,7 @@ var _ = Describe("Solver", func() { _, err := dbInstalled.CreatePackage(p) Expect(err).ToNot(HaveOccurred()) } - solution, err := s.Uninstall(A, true) + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) Expect(solution).To(ContainElement(A.IsFlagged(false))) @@ -828,7 +828,7 @@ var _ = Describe("Solver", func() { _, err := dbInstalled.CreatePackage(p) Expect(err).ToNot(HaveOccurred()) } - solution, err := s.Uninstall(A, true) + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) Expect(solution).To(ContainElement(A.IsFlagged(false))) @@ -853,7 +853,7 @@ var _ = Describe("Solver", func() { Expect(err).ToNot(HaveOccurred()) } - solution, err := s.Uninstall(A, true) + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) Expect(solution).To(ContainElement(A.IsFlagged(false))) @@ -879,7 +879,7 @@ var _ = Describe("Solver", func() { Expect(err).ToNot(HaveOccurred()) } - solution, err := s.Uninstall(A, true) + solution, err := s.Uninstall(A, true, true) Expect(err).ToNot(HaveOccurred()) Expect(solution).To(ContainElement(A.IsFlagged(false)))