2018-09-21 21:29:50 +00:00
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package solver
import (
2019-11-29 18:01:41 +00:00
//. "github.com/mudler/luet/pkg/logger"
2020-05-03 10:16:45 +00:00
"fmt"
2019-11-29 18:01:41 +00:00
"github.com/pkg/errors"
2020-02-11 13:58:17 +00:00
"github.com/crillab/gophersat/bf"
2019-06-05 17:13:09 +00:00
pkg "github.com/mudler/luet/pkg/package"
2018-09-21 21:29:50 +00:00
)
2020-10-25 17:43:35 +00:00
type SolverType int
const (
SingleCoreSimple = 0
)
2019-06-11 16:03:50 +00:00
// PackageSolver is an interface to a generic package solving algorithm
2018-09-21 21:29:50 +00:00
type PackageSolver interface {
2019-11-29 18:01:41 +00:00
SetDefinitionDatabase ( pkg . PackageDatabase )
2020-04-04 12:29:08 +00:00
Install ( p pkg . Packages ) ( PackagesAssertions , error )
2021-10-09 15:36:13 +00:00
RelaxedInstall ( p pkg . Packages ) ( PackagesAssertions , error )
2020-11-19 15:25:51 +00:00
Uninstall ( checkconflicts , full bool , candidate ... pkg . Package ) ( pkg . Packages , error )
2019-06-11 16:03:50 +00:00
ConflictsWithInstalled ( p pkg . Package ) ( bool , error )
2020-04-04 12:29:08 +00:00
ConflictsWith ( p pkg . Package , ls pkg . Packages ) ( bool , error )
2020-05-02 15:05:26 +00:00
Conflicts ( pack pkg . Package , lsp pkg . Packages ) ( bool , error )
2020-04-04 12:29:08 +00:00
World ( ) pkg . Packages
2020-05-20 21:24:32 +00:00
Upgrade ( checkconflicts , full bool ) ( pkg . Packages , PackagesAssertions , error )
2020-02-10 08:41:09 +00:00
2020-05-22 18:45:28 +00:00
UpgradeUniverse ( dropremoved bool ) ( pkg . Packages , PackagesAssertions , error )
UninstallUniverse ( toremove pkg . Packages ) ( pkg . Packages , error )
2020-02-10 08:41:09 +00:00
SetResolver ( PackageResolver )
2020-02-10 16:16:35 +00:00
Solve ( ) ( PackagesAssertions , error )
2021-10-09 15:36:13 +00:00
// BestInstall(c pkg.Packages) (PackagesAssertions, error)
2018-09-21 21:29:50 +00:00
}
2019-06-11 16:03:50 +00:00
// Solver is the default solver for luet
2018-09-21 21:29:50 +00:00
type Solver struct {
2019-11-29 18:01:41 +00:00
DefinitionDatabase pkg . PackageDatabase
SolverDatabase pkg . PackageDatabase
2020-04-04 12:29:08 +00:00
Wanted pkg . Packages
2019-11-29 18:01:41 +00:00
InstalledDatabase pkg . PackageDatabase
2020-02-10 08:41:09 +00:00
Resolver PackageResolver
2018-09-21 21:29:50 +00:00
}
2020-10-25 17:43:35 +00:00
type Options struct {
2021-08-11 14:26:34 +00:00
Type SolverType ` yaml:"type,omitempty" `
Concurrency int ` yaml:"concurrency,omitempty" `
2020-10-25 17:43:35 +00:00
}
2019-06-11 16:03:50 +00:00
// NewSolver accepts as argument two lists of packages, the first is the initial set,
// the second represent all the known packages.
2020-10-25 17:43:35 +00:00
func NewSolver ( t Options , installed pkg . PackageDatabase , definitiondb pkg . PackageDatabase , solverdb pkg . PackageDatabase ) PackageSolver {
2021-08-07 09:11:42 +00:00
return NewResolver ( t , installed , definitiondb , solverdb , & Explainer { } )
2020-02-12 10:22:23 +00:00
}
2020-10-25 17:43:35 +00:00
// NewResolver accepts as argument two lists of packages, the first is the initial set,
2020-02-12 10:22:23 +00:00
// the second represent all the known packages.
2020-10-25 17:43:35 +00:00
// Using constructors as in the future we foresee warmups for hot-restore solver cache
func NewResolver ( t Options , installed pkg . PackageDatabase , definitiondb pkg . PackageDatabase , solverdb pkg . PackageDatabase , re PackageResolver ) PackageSolver {
var s PackageSolver
switch t . Type {
2021-10-09 22:26:12 +00:00
default :
2020-10-25 17:43:35 +00:00
s = & Solver { InstalledDatabase : installed , DefinitionDatabase : definitiondb , SolverDatabase : solverdb , Resolver : re }
}
return s
2018-09-21 21:29:50 +00:00
}
2020-02-10 08:41:09 +00:00
// SetDefinitionDatabase is a setter for the definition Database
2019-06-11 16:03:50 +00:00
2019-11-29 18:01:41 +00:00
func ( s * Solver ) SetDefinitionDatabase ( db pkg . PackageDatabase ) {
s . DefinitionDatabase = db
}
2020-02-10 08:41:09 +00:00
// SetResolver is a setter for the unsat resolver backend
func ( s * Solver ) SetResolver ( r PackageResolver ) {
s . Resolver = r
}
2020-04-04 12:29:08 +00:00
func ( s * Solver ) World ( ) pkg . Packages {
2019-11-29 18:01:51 +00:00
return s . DefinitionDatabase . World ( )
2019-11-29 18:01:41 +00:00
}
2020-04-04 12:29:08 +00:00
func ( s * Solver ) Installed ( ) pkg . Packages {
2019-11-29 18:01:51 +00:00
return s . InstalledDatabase . World ( )
2019-06-04 19:25:17 +00:00
}
2019-06-05 15:51:24 +00:00
func ( s * Solver ) noRulesWorld ( ) bool {
2019-11-29 18:01:41 +00:00
for _ , p := range s . World ( ) {
2019-06-05 15:51:24 +00:00
if len ( p . GetConflicts ( ) ) != 0 || len ( p . GetRequires ( ) ) != 0 {
return false
}
}
return true
}
2020-05-22 18:45:28 +00:00
func ( s * Solver ) noRulesInstalled ( ) bool {
for _ , p := range s . Installed ( ) {
if len ( p . GetConflicts ( ) ) != 0 || len ( p . GetRequires ( ) ) != 0 {
return false
}
}
return true
}
2019-06-11 16:03:50 +00:00
func ( s * Solver ) BuildInstalled ( ) ( bf . Formula , error ) {
var formulas [ ] bf . Formula
2020-10-30 18:12:12 +00:00
var packages pkg . Packages
2019-11-29 18:01:41 +00:00
for _ , p := range s . Installed ( ) {
2020-10-30 18:12:12 +00:00
packages = append ( packages , p )
2020-12-08 09:58:08 +00:00
for _ , dep := range p . Related ( s . InstalledDatabase ) {
2020-10-30 18:12:12 +00:00
packages = append ( packages , dep )
}
}
for _ , p := range packages {
2020-12-08 09:58:08 +00:00
solvable , err := p . BuildFormula ( s . InstalledDatabase , s . SolverDatabase )
2019-06-11 16:03:50 +00:00
if err != nil {
return nil , err
}
//f = bf.And(f, solvable)
formulas = append ( formulas , solvable ... )
}
return bf . And ( formulas ... ) , nil
}
// BuildWorld builds the formula which olds the requirements from the package definitions
// which are available (global state)
func ( s * Solver ) BuildWorld ( includeInstalled bool ) ( bf . Formula , error ) {
2018-09-21 21:29:50 +00:00
var formulas [ ] bf . Formula
2019-06-11 16:03:50 +00:00
// NOTE: This block should be enabled in case of very old systems with outdated world sets
if includeInstalled {
solvable , err := s . BuildInstalled ( )
if err != nil {
return nil , err
}
//f = bf.And(f, solvable)
formulas = append ( formulas , solvable )
}
2019-11-29 18:01:41 +00:00
for _ , p := range s . World ( ) {
solvable , err := p . BuildFormula ( s . DefinitionDatabase , s . SolverDatabase )
2018-09-21 21:29:50 +00:00
if err != nil {
return nil , err
}
2019-06-04 19:25:17 +00:00
formulas = append ( formulas , solvable ... )
2018-09-21 21:29:50 +00:00
}
2019-06-04 19:25:17 +00:00
return bf . And ( formulas ... ) , nil
}
2018-09-21 21:29:50 +00:00
2020-10-30 18:12:12 +00:00
// BuildWorld builds the formula which olds the requirements from the package definitions
// which are available (global state)
func ( s * Solver ) BuildPartialWorld ( includeInstalled bool ) ( bf . Formula , error ) {
var formulas [ ] bf . Formula
// NOTE: This block shouldf be enabled in case of very old systems with outdated world sets
if includeInstalled {
solvable , err := s . BuildInstalled ( )
if err != nil {
return nil , err
}
//f = bf.And(f, solvable)
formulas = append ( formulas , solvable )
}
var packages pkg . Packages
for _ , p := range s . Wanted {
// packages = append(packages, p)
for _ , dep := range p . Related ( s . DefinitionDatabase ) {
packages = append ( packages , dep )
}
}
for _ , p := range packages {
solvable , err := p . BuildFormula ( s . DefinitionDatabase , s . SolverDatabase )
if err != nil {
return nil , err
}
formulas = append ( formulas , solvable ... )
}
if len ( formulas ) != 0 {
return bf . And ( formulas ... ) , nil
}
2020-11-20 16:23:21 +00:00
return bf . True , nil
2020-10-30 18:12:12 +00:00
}
2020-04-04 12:29:08 +00:00
func ( s * Solver ) getList ( db pkg . PackageDatabase , lsp pkg . Packages ) ( pkg . Packages , error ) {
var ls pkg . Packages
2019-11-29 18:01:55 +00:00
2019-11-29 18:01:41 +00:00
for _ , pp := range lsp {
cp , err := db . FindPackage ( pp )
if err != nil {
2019-12-06 15:28:42 +00:00
packages , err := pp . Expand ( db )
2019-11-29 18:01:55 +00:00
// Expand, and relax search - if not found pick the same one
if err != nil || len ( packages ) == 0 {
cp = pp
} else {
2020-04-04 13:33:14 +00:00
cp = packages . Best ( nil )
2019-11-29 18:01:55 +00:00
}
2019-11-29 18:01:41 +00:00
}
ls = append ( ls , cp )
}
return ls , nil
}
2020-05-02 15:05:26 +00:00
// Conflicts acts like ConflictsWith, but uses package's reverse dependencies to
// determine if it conflicts with the given set
func ( s * Solver ) Conflicts ( pack pkg . Package , lsp pkg . Packages ) ( bool , error ) {
p , err := s . DefinitionDatabase . FindPackage ( pack )
if err != nil {
p = pack
}
ls , err := s . getList ( s . DefinitionDatabase , lsp )
if err != nil {
return false , errors . Wrap ( err , "Package not found in definition db" )
}
if s . noRulesWorld ( ) {
return false , nil
}
temporarySet := pkg . NewInMemoryDatabase ( false )
for _ , p := range ls {
temporarySet . CreatePackage ( p )
}
2020-11-20 16:23:21 +00:00
revdeps , err := temporarySet . GetRevdeps ( p )
if err != nil {
return false , errors . Wrap ( err , "error scanning revdeps" )
}
2020-05-02 15:05:26 +00:00
2020-05-03 10:16:45 +00:00
var revdepsErr error
for _ , r := range revdeps {
if revdepsErr == nil {
revdepsErr = errors . New ( "" )
}
2020-12-29 21:13:26 +00:00
revdepsErr = fmt . Errorf ( "%s\n%s" , revdepsErr . Error ( ) , r . HumanReadableString ( ) )
2020-05-03 10:16:45 +00:00
}
return len ( revdeps ) != 0 , revdepsErr
2020-05-02 15:05:26 +00:00
}
// ConflictsWith return true if a package is part of the requirement set of a list of package
// return false otherwise (and thus it is NOT relevant to the given list)
2020-04-04 12:29:08 +00:00
func ( s * Solver ) ConflictsWith ( pack pkg . Package , lsp pkg . Packages ) ( bool , error ) {
2019-11-29 18:01:41 +00:00
p , err := s . DefinitionDatabase . FindPackage ( pack )
if err != nil {
p = pack //Relax search, otherwise we cannot compute solutions for packages not in definitions
// return false, errors.Wrap(err, "Package not found in definition db")
}
ls , err := s . getList ( s . DefinitionDatabase , lsp )
if err != nil {
return false , errors . Wrap ( err , "Package not found in definition db" )
}
2019-06-11 16:03:50 +00:00
var formulas [ ] bf . Formula
if s . noRulesWorld ( ) {
return false , nil
}
2019-11-29 18:01:41 +00:00
encodedP , err := p . Encode ( s . SolverDatabase )
2019-06-11 16:03:50 +00:00
if err != nil {
return false , err
}
P := bf . Var ( encodedP )
r , err := s . BuildWorld ( false )
if err != nil {
return false , err
}
formulas = append ( formulas , bf . And ( bf . Not ( P ) , r ) )
for _ , i := range ls {
2019-11-15 17:04:46 +00:00
if i . Matches ( p ) {
2019-06-11 16:03:50 +00:00
continue
}
// XXX: Skip check on any of its requires ? ( Drop to avoid removing system packages when selecting an uninstall)
// if i.RequiresContains(p) {
// fmt.Println("Requires found")
// continue
// }
2019-11-29 18:01:12 +00:00
encodedI , err := i . Encode ( s . SolverDatabase )
2019-06-11 16:03:50 +00:00
if err != nil {
return false , err
}
I := bf . Var ( encodedI )
formulas = append ( formulas , bf . And ( I , r ) )
}
model := bf . Solve ( bf . And ( formulas ... ) )
if model == nil {
return true , nil
}
return false , nil
}
func ( s * Solver ) ConflictsWithInstalled ( p pkg . Package ) ( bool , error ) {
2019-11-29 18:01:41 +00:00
return s . ConflictsWith ( p , s . Installed ( ) )
2019-06-11 16:03:50 +00:00
}
2020-05-22 18:45:28 +00:00
// UninstallUniverse takes a list of candidate package and return a list of packages that would be removed
// in order to purge the candidate. Uses the solver to check constraints and nothing else
//
// It can be compared to the counterpart Uninstall as this method acts like a uninstall --full
// it removes all the packages and its deps. taking also in consideration other packages that might have
// revdeps
func ( s * Solver ) UninstallUniverse ( toremove pkg . Packages ) ( pkg . Packages , error ) {
if s . noRulesInstalled ( ) {
return s . getList ( s . InstalledDatabase , toremove )
}
// resolve to packages from the db
toRemove , err := s . getList ( s . InstalledDatabase , toremove )
if err != nil {
return nil , errors . Wrap ( err , "Package not found in definition db" )
}
var formulas [ ] bf . Formula
r , err := s . BuildInstalled ( )
if err != nil {
return nil , errors . Wrap ( err , "Package not found in definition db" )
}
// SAT encode the clauses against the world
for _ , p := range toRemove . Unique ( ) {
encodedP , err := p . Encode ( s . InstalledDatabase )
if err != nil {
return nil , errors . Wrap ( err , "Package not found in definition db" )
}
P := bf . Var ( encodedP )
formulas = append ( formulas , bf . And ( bf . Not ( P ) , r ) )
}
markedForRemoval := pkg . Packages { }
model := bf . Solve ( bf . And ( formulas ... ) )
if model == nil {
return nil , errors . New ( "Failed finding a solution" )
}
assertion , err := DecodeModel ( model , s . InstalledDatabase )
if err != nil {
return nil , errors . Wrap ( err , "while decoding model from solution" )
}
for _ , a := range assertion {
if ! a . Value {
if p , err := s . InstalledDatabase . FindPackage ( a . Package ) ; err == nil {
markedForRemoval = append ( markedForRemoval , p )
}
}
}
return markedForRemoval , nil
}
// UpgradeUniverse mark packages for removal and returns a solution. It considers
// the Universe db as authoritative
// See also on the subject: https://arxiv.org/pdf/1007.1021.pdf
func ( s * Solver ) UpgradeUniverse ( dropremoved bool ) ( pkg . Packages , PackagesAssertions , error ) {
// we first figure out which aren't up-to-date
// which has to be removed
// and which needs to be upgraded
notUptodate := pkg . Packages { }
removed := pkg . Packages { }
toUpgrade := pkg . Packages { }
2020-12-08 09:58:08 +00:00
replacements := map [ pkg . Package ] pkg . Package { }
2020-05-22 18:45:28 +00:00
// TODO: this is memory expensive, we need to optimize this
2020-12-19 16:45:50 +00:00
universe , err := s . DefinitionDatabase . Copy ( )
if err != nil {
return nil , nil , errors . Wrap ( err , "Failed copying db" )
2020-05-22 18:45:28 +00:00
}
2020-12-19 16:45:50 +00:00
2020-05-22 18:45:28 +00:00
for _ , p := range s . Installed ( ) {
universe . CreatePackage ( p )
}
// Grab all the installed ones, see if they are eligible for update
for _ , p := range s . Installed ( ) {
2020-12-08 11:07:28 +00:00
available , err := s . DefinitionDatabase . FindPackageVersions ( p )
2020-12-08 11:28:20 +00:00
if len ( available ) == 0 || err != nil {
2020-05-22 18:45:28 +00:00
removed = append ( removed , p )
continue
}
bestmatch := available . Best ( nil )
// Found a better version available
if ! bestmatch . Matches ( p ) {
notUptodate = append ( notUptodate , p )
toUpgrade = append ( toUpgrade , bestmatch )
2020-12-08 09:58:08 +00:00
replacements [ p ] = bestmatch
2020-05-22 18:45:28 +00:00
}
}
var formulas [ ] bf . Formula
// Build constraints for the whole defdb
r , err := s . BuildWorld ( true )
if err != nil {
return nil , nil , errors . Wrap ( err , "couldn't build world constraints" )
}
// Treat removed packages from universe as marked for deletion
if dropremoved {
2020-12-08 09:58:08 +00:00
// SAT encode the clauses against the world
for _ , p := range removed . Unique ( ) {
encodedP , err := p . Encode ( universe )
if err != nil {
return nil , nil , errors . Wrap ( err , "couldn't encode package" )
}
P := bf . Var ( encodedP )
formulas = append ( formulas , bf . And ( bf . Not ( P ) , r ) )
}
2020-05-22 18:45:28 +00:00
}
2020-12-08 09:58:08 +00:00
for old , new := range replacements {
oldP , err := old . Encode ( universe )
2020-05-22 18:45:28 +00:00
if err != nil {
return nil , nil , errors . Wrap ( err , "couldn't encode package" )
}
2020-12-08 09:58:08 +00:00
oldencodedP := bf . Var ( oldP )
newP , err := new . Encode ( universe )
2020-05-22 18:45:28 +00:00
if err != nil {
return nil , nil , errors . Wrap ( err , "couldn't encode package" )
}
2020-12-08 09:58:08 +00:00
newEncodedP := bf . Var ( newP )
//solvable, err := old.BuildFormula(s.DefinitionDatabase, s.SolverDatabase)
solvablenew , err := new . BuildFormula ( s . DefinitionDatabase , s . SolverDatabase )
formulas = append ( formulas , bf . And ( bf . Not ( oldencodedP ) , bf . And ( append ( solvablenew , newEncodedP ) ... ) ) )
2020-05-22 18:45:28 +00:00
}
2020-12-08 09:58:08 +00:00
//formulas = append(formulas, r)
2020-05-22 18:45:28 +00:00
markedForRemoval := pkg . Packages { }
2020-10-30 18:12:12 +00:00
if len ( formulas ) == 0 {
return pkg . Packages { } , PackagesAssertions { } , nil
}
2020-05-22 18:45:28 +00:00
model := bf . Solve ( bf . And ( formulas ... ) )
if model == nil {
return nil , nil , errors . New ( "Failed finding a solution" )
}
assertion , err := DecodeModel ( model , universe )
if err != nil {
return nil , nil , errors . Wrap ( err , "while decoding model from solution" )
}
for _ , a := range assertion {
if ! a . Value {
if p , err := s . InstalledDatabase . FindPackage ( a . Package ) ; err == nil {
markedForRemoval = append ( markedForRemoval , p )
}
}
}
return markedForRemoval , assertion , nil
}
2021-10-09 15:36:13 +00:00
func inPackage ( list [ ] pkg . Package , p pkg . Package ) bool {
for _ , l := range list {
if l . AtomMatches ( p ) {
return true
}
2020-11-05 19:52:02 +00:00
}
2021-10-09 15:36:13 +00:00
return false
}
2020-11-05 19:52:02 +00:00
2021-10-09 15:36:13 +00:00
// Compute upgrade between packages if specified, or all if none is specified
2021-10-10 17:04:55 +00:00
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 , [ ] pkg . Package ) {
2021-10-09 15:36:13 +00:00
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 )
2021-10-10 17:04:55 +00:00
2021-10-09 15:36:13 +00:00
if err == nil && len ( packages ) != 0 {
best := packages . Best ( nil )
2021-10-10 17:04:55 +00:00
// 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 ) ) ) {
2021-10-09 15:36:13 +00:00
toUninstall = append ( toUninstall , p )
toInstall = append ( toInstall , best )
}
2019-11-29 18:01:56 +00:00
}
}
2021-10-10 17:04:55 +00:00
return toUninstall , toInstall , installedcopy , ppsToUpgrade
2019-11-29 18:01:56 +00:00
}
2021-10-09 15:36:13 +00:00
}
2020-05-20 20:46:47 +00:00
2021-10-10 17:04:55 +00:00
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 ) {
2021-10-09 15:36:13 +00:00
2021-10-10 17:04:55 +00:00
toUninstall , toInstall , installedcopy , packsToUpgrade := fn ( defDB , installDB )
2021-10-09 15:36:13 +00:00
s2 := NewSolver ( Options { Type : SingleCoreSimple } , installedcopy , defDB , pkg . NewInMemoryDatabase ( false ) )
2020-02-10 16:16:35 +00:00
s2 . SetResolver ( s . Resolver )
2020-05-20 21:24:32 +00:00
if ! full {
ass := PackagesAssertions { }
for _ , i := range toInstall {
ass = append ( ass , PackageAssert { Package : i . ( * pkg . DefaultPackage ) , Value : true } )
}
}
2019-11-29 18:01:56 +00:00
// Then try to uninstall the versions in the system, and store that tree
2020-11-20 16:23:21 +00:00
r , err := s . Uninstall ( checkconflicts , false , toUninstall . Unique ( ) ... )
2020-11-19 15:25:51 +00:00
if err != nil {
return nil , nil , errors . Wrap ( err , "Could not compute upgrade - couldn't uninstall candidates " )
}
for _ , z := range r {
err = installedcopy . RemovePackage ( z )
2019-11-29 18:01:56 +00:00
if err != nil {
2020-11-19 15:25:51 +00:00
return nil , nil , errors . Wrap ( err , "Could not compute upgrade - couldn't remove copy of package targetted for removal" )
2019-11-29 18:01:56 +00:00
}
}
2020-11-19 15:25:51 +00:00
2020-10-30 18:12:12 +00:00
if len ( toInstall ) == 0 {
2021-10-09 15:36:13 +00:00
ass := PackagesAssertions { }
for _ , i := range installDB . World ( ) {
ass = append ( ass , PackageAssert { Package : i . ( * pkg . DefaultPackage ) , Value : true } )
}
return toUninstall , ass , nil
2020-10-30 18:12:12 +00:00
}
2021-10-09 15:36:13 +00:00
assertions , err := s2 . RelaxedInstall ( toInstall . Unique ( ) )
2020-11-20 16:23:21 +00:00
2021-10-10 17:04:55 +00:00
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
}
2020-11-19 15:25:51 +00:00
return toUninstall , assertions , err
2021-10-09 15:36:13 +00:00
}
2019-11-29 18:01:56 +00:00
2021-10-09 15:36:13 +00:00
func ( s * Solver ) Upgrade ( checkconflicts , full bool ) ( pkg . Packages , PackagesAssertions , error ) {
2021-10-10 17:04:55 +00:00
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 )
2019-11-29 18:01:56 +00:00
}
2019-06-11 16:03:50 +00:00
// 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.
2020-11-19 15:25:51 +00:00
func ( s * Solver ) Uninstall ( checkconflicts , full bool , packs ... pkg . Package ) ( pkg . Packages , error ) {
if len ( packs ) == 0 {
return pkg . Packages { } , nil
}
2020-04-04 12:29:08 +00:00
var res pkg . Packages
2019-11-29 18:01:55 +00:00
2020-11-19 15:25:51 +00:00
toRemove := pkg . Packages { }
for _ , c := range packs {
candidate , err := s . InstalledDatabase . FindPackage ( c )
if err != nil {
// return nil, errors.Wrap(err, "Couldn't find required package in db definition")
packages , err := c . Expand ( s . InstalledDatabase )
// Info("Expanded", packages, err)
if err != nil || len ( packages ) == 0 {
candidate = c
} else {
candidate = packages . Best ( nil )
}
//Relax search, otherwise we cannot compute solutions for packages not in definitions
// return nil, errors.Wrap(err, "Package not found between installed")
2019-11-29 18:01:55 +00:00
}
2020-11-19 15:25:51 +00:00
toRemove = append ( toRemove , candidate )
2019-11-29 18:01:41 +00:00
}
2020-11-19 15:25:51 +00:00
2019-06-11 16:03:50 +00:00
// Build a fake "Installed" - Candidate and its requires tree
2020-04-04 12:29:08 +00:00
var InstalledMinusCandidate pkg . Packages
2019-11-29 18:01:41 +00:00
2020-05-03 10:16:45 +00:00
// 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 {
2020-11-19 15:25:51 +00:00
for _ , candidate := range toRemove {
2020-12-30 00:15:44 +00:00
if conflicts , err := s . Conflicts ( candidate , s . Installed ( ) ) ; conflicts {
return nil , errors . Wrap ( err , "while searching for " + candidate . HumanReadableString ( ) + " conflicts" )
2020-11-19 15:25:51 +00:00
}
2020-05-03 10:16:45 +00:00
}
2020-11-19 15:25:51 +00:00
return toRemove , nil
2020-05-03 10:16:45 +00:00
}
2019-11-29 18:01:41 +00:00
// TODO: Can be optimized
for _ , i := range s . Installed ( ) {
2020-11-19 15:25:51 +00:00
matched := false
for _ , candidate := range toRemove {
if ! i . Matches ( candidate ) {
contains , err := candidate . RequiresContains ( s . SolverDatabase , i )
if err != nil {
return nil , errors . Wrap ( err , "Failed getting installed list" )
}
if ! contains {
matched = true
}
2019-11-29 18:01:41 +00:00
}
2019-06-11 16:03:50 +00:00
}
2020-11-19 15:25:51 +00:00
if matched {
InstalledMinusCandidate = append ( InstalledMinusCandidate , i )
}
2019-06-11 16:03:50 +00:00
}
2020-12-08 01:04:02 +00:00
s2 := NewSolver ( Options { Type : SingleCoreSimple } , pkg . NewInMemoryDatabase ( false ) , s . InstalledDatabase , pkg . NewInMemoryDatabase ( false ) )
2020-02-27 17:26:48 +00:00
s2 . SetResolver ( s . Resolver )
2020-11-20 16:23:21 +00:00
2019-06-11 16:03:50 +00:00
// Get the requirements to install the candidate
2021-10-09 15:36:13 +00:00
asserts , err := s2 . RelaxedInstall ( toRemove )
2019-06-05 15:51:24 +00:00
if err != nil {
return nil , err
}
for _ , a := range asserts {
2019-11-29 18:01:41 +00:00
if a . Value {
2020-02-27 17:26:48 +00:00
if ! checkconflicts {
2020-08-02 09:31:23 +00:00
res = append ( res , a . Package )
2020-02-27 17:26:48 +00:00
continue
}
2019-06-11 16:03:50 +00:00
c , err := s . ConflictsWithInstalled ( a . Package )
if err != nil {
return nil , err
}
2019-06-11 16:47:07 +00:00
// If doesn't conflict with installed we just consider it for removal and look for the next one
if ! c {
2020-08-02 09:31:23 +00:00
res = append ( res , a . Package )
2019-06-11 16:47:07 +00:00
continue
}
// If does conflicts, give it another chance by checking conflicts if in case we didn't installed our candidate and all the required packages in the system
c , err = s . ConflictsWith ( a . Package , InstalledMinusCandidate )
if err != nil {
return nil , err
}
if ! c {
2020-08-02 09:31:23 +00:00
res = append ( res , a . Package )
2019-06-11 16:03:50 +00:00
}
2019-06-05 15:51:24 +00:00
}
}
return res , nil
}
2019-06-11 16:03:50 +00:00
// BuildFormula builds the main solving formula that is evaluated by the sat solver.
2019-06-04 19:25:17 +00:00
func ( s * Solver ) BuildFormula ( ) ( bf . Formula , error ) {
var formulas [ ] bf . Formula
2021-01-11 19:10:32 +00:00
r , err := s . BuildWorld ( false )
2019-06-04 19:25:17 +00:00
if err != nil {
return nil , err
}
2020-10-30 18:12:12 +00:00
2019-06-04 19:25:17 +00:00
for _ , wanted := range s . Wanted {
2020-11-20 16:23:21 +00:00
2019-11-29 18:01:12 +00:00
encodedW , err := wanted . Encode ( s . SolverDatabase )
2018-09-21 21:29:50 +00:00
if err != nil {
return nil , err
}
2019-06-04 19:25:17 +00:00
W := bf . Var ( encodedW )
2020-10-30 18:12:12 +00:00
// allW = append(allW, W)
2019-11-29 18:01:41 +00:00
installedWorld := s . Installed ( )
//TODO:Optimize
if len ( installedWorld ) == 0 {
2019-06-05 15:51:24 +00:00
formulas = append ( formulas , W ) //bf.And(bf.True, W))
2019-06-04 20:05:52 +00:00
continue
}
2019-06-05 15:51:24 +00:00
2019-11-29 18:01:41 +00:00
for _ , installed := range installedWorld {
2019-11-29 18:01:12 +00:00
encodedI , err := installed . Encode ( s . SolverDatabase )
2019-06-04 19:25:17 +00:00
if err != nil {
return nil , err
}
I := bf . Var ( encodedI )
formulas = append ( formulas , bf . And ( W , I ) )
}
2018-09-21 21:29:50 +00:00
}
2019-06-05 15:51:24 +00:00
2020-10-30 18:12:12 +00:00
formulas = append ( formulas , r )
2018-09-21 21:29:50 +00:00
return bf . And ( formulas ... ) , nil
}
func ( s * Solver ) solve ( f bf . Formula ) ( map [ string ] bool , bf . Formula , error ) {
model := bf . Solve ( f )
if model == nil {
return model , f , errors . New ( "Unsolvable" )
}
2019-06-04 19:25:17 +00:00
2018-09-21 21:29:50 +00:00
return model , f , nil
}
2019-06-11 16:03:50 +00:00
// Solve builds the formula given the current state and returns package assertions
2019-11-11 10:02:32 +00:00
func ( s * Solver ) Solve ( ) ( PackagesAssertions , error ) {
2020-02-10 08:41:09 +00:00
var model map [ string ] bool
var err error
2018-09-21 21:29:50 +00:00
f , err := s . BuildFormula ( )
2019-06-04 20:22:45 +00:00
2018-09-21 21:29:50 +00:00
if err != nil {
2019-06-04 20:22:45 +00:00
return nil , err
2018-09-21 21:29:50 +00:00
}
2020-02-10 08:41:09 +00:00
model , _ , err = s . solve ( f )
if err != nil && s . Resolver != nil {
2020-02-10 16:16:35 +00:00
return s . Resolver . Solve ( f , s )
2020-02-10 08:41:09 +00:00
}
2018-09-21 21:29:50 +00:00
if err != nil {
2019-06-04 19:25:17 +00:00
return nil , err
2018-09-21 21:29:50 +00:00
}
2019-06-04 19:25:17 +00:00
2019-11-29 18:01:12 +00:00
return DecodeModel ( model , s . SolverDatabase )
2018-09-21 21:29:50 +00:00
}
2019-06-04 20:22:45 +00:00
2019-06-11 16:03:50 +00:00
// 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
2021-10-09 15:36:13 +00:00
func ( s * Solver ) RelaxedInstall ( c pkg . Packages ) ( PackagesAssertions , error ) {
2019-11-29 18:01:41 +00:00
coll , err := s . getList ( s . DefinitionDatabase , c )
if err != nil {
return nil , errors . Wrap ( err , "Packages not found in definition db" )
2019-06-04 20:22:45 +00:00
}
2019-11-29 18:01:41 +00:00
2019-06-04 20:22:45 +00:00
s . Wanted = coll
2019-06-05 15:51:24 +00:00
if s . noRulesWorld ( ) {
2019-11-11 10:02:32 +00:00
var ass PackagesAssertions
2019-11-29 18:01:41 +00:00
for _ , p := range s . Installed ( ) {
ass = append ( ass , PackageAssert { Package : p . ( * pkg . DefaultPackage ) , Value : true } )
2019-06-05 15:51:24 +00:00
}
for _ , p := range s . Wanted {
2019-11-29 18:01:41 +00:00
ass = append ( ass , PackageAssert { Package : p . ( * pkg . DefaultPackage ) , Value : true } )
2019-06-05 15:51:24 +00:00
}
return ass , nil
}
2021-10-09 15:36:13 +00:00
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 { }
2021-10-10 17:04:55 +00:00
toNotUpgrade := pkg . Packages { }
2021-10-09 15:36:13 +00:00
for _ , p := range c {
if p . GetVersion ( ) == ">=0" || p . GetVersion ( ) == ">0" {
toUpgrade = append ( toUpgrade , p )
2021-10-10 17:04:55 +00:00
} else {
toNotUpgrade = append ( toNotUpgrade , p )
2021-10-09 15:36:13 +00:00
}
}
for _ , p := range assertions {
if p . Value {
systemAfterInstall . CreatePackage ( p . Package )
2021-10-10 17:04:55 +00:00
if ! inPackage ( c , p . Package ) && ! inPackage ( toUpgrade , p . Package ) && ! inPackage ( toNotUpgrade , p . Package ) {
2021-10-09 15:36:13 +00:00
toUpgrade = append ( toUpgrade , p . Package )
}
}
}
if len ( toUpgrade ) == 0 {
return assertions , nil
}
2021-10-10 17:04:55 +00:00
toUninstall , _ , _ , _ := s . computeUpgrade ( toUpgrade , toNotUpgrade ) ( s . DefinitionDatabase , systemAfterInstall )
if len ( toUninstall ) > 0 {
// 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 ( toUpgrade , toNotUpgrade , s . computeUpgrade ( toUpgrade , toNotUpgrade ) , 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
2021-10-09 15:36:13 +00:00
}
2019-06-05 15:51:24 +00:00
2021-10-10 17:04:55 +00:00
return assertions , nil
2019-06-04 20:22:45 +00:00
}