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"
"github.com/pkg/errors"
2018-09-21 21:29:50 +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
)
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 )
2019-11-11 10:02:32 +00:00
Install ( p [ ] pkg . Package ) ( PackagesAssertions , error )
2019-06-05 15:51:24 +00:00
Uninstall ( candidate pkg . Package ) ( [ ] pkg . Package , error )
2019-06-11 16:03:50 +00:00
ConflictsWithInstalled ( p pkg . Package ) ( bool , error )
ConflictsWith ( p pkg . Package , ls [ ] pkg . Package ) ( bool , error )
2019-11-29 18:01:41 +00:00
World ( ) [ ] pkg . Package
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
Wanted [ ] pkg . Package
InstalledDatabase pkg . PackageDatabase
2018-09-21 21:29:50 +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.
2019-11-29 18:01:41 +00:00
func NewSolver ( installed pkg . PackageDatabase , definitiondb pkg . PackageDatabase , solverdb pkg . PackageDatabase ) PackageSolver {
return & Solver { InstalledDatabase : installed , DefinitionDatabase : definitiondb , SolverDatabase : solverdb }
2018-09-21 21:29:50 +00:00
}
2019-06-11 16:03:50 +00:00
// SetWorld is a setter for the list of all known packages to the solver
2019-11-29 18:01:41 +00:00
func ( s * Solver ) SetDefinitionDatabase ( db pkg . PackageDatabase ) {
s . DefinitionDatabase = db
}
func ( s * Solver ) World ( ) [ ] pkg . Package {
2019-11-29 18:01:51 +00:00
return s . DefinitionDatabase . World ( )
2019-11-29 18:01:41 +00:00
}
func ( s * Solver ) Installed ( ) [ ] pkg . Package {
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
}
2019-06-11 16:03:50 +00:00
func ( s * Solver ) BuildInstalled ( ) ( bf . Formula , error ) {
var formulas [ ] bf . Formula
2019-11-29 18:01:41 +00:00
for _ , p := range s . Installed ( ) {
solvable , err := p . BuildFormula ( s . DefinitionDatabase , 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
2019-11-29 18:01:41 +00:00
func ( s * Solver ) getList ( db pkg . PackageDatabase , lsp [ ] pkg . Package ) ( [ ] pkg . Package , error ) {
var ls [ ] pkg . Package
for _ , pp := range lsp {
cp , err := db . FindPackage ( pp )
if err != nil {
cp = pp //Relax search, otherwise we cannot compute solutions for packages not in definitions
//return nil, errors.Wrap(err, "Package not found in db")
}
ls = append ( ls , cp )
}
return ls , nil
}
func ( s * Solver ) ConflictsWith ( pack pkg . Package , lsp [ ] pkg . Package ) ( bool , error ) {
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
}
// 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.
2019-11-29 18:01:41 +00:00
func ( s * Solver ) Uninstall ( c pkg . Package ) ( [ ] pkg . Package , error ) {
2019-06-05 15:51:24 +00:00
var res [ ] pkg . Package
2019-11-29 18:01:41 +00:00
candidate , err := s . InstalledDatabase . FindPackage ( c )
if err != nil {
candidate = c //Relax search, otherwise we cannot compute solutions for packages not in definitions
// return nil, errors.Wrap(err, "Package not found between installed")
}
2019-06-11 16:03:50 +00:00
// Build a fake "Installed" - Candidate and its requires tree
var InstalledMinusCandidate [ ] pkg . Package
2019-11-29 18:01:41 +00:00
// TODO: Can be optimized
for _ , i := range s . Installed ( ) {
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 {
InstalledMinusCandidate = append ( InstalledMinusCandidate , i )
}
2019-06-11 16:03:50 +00:00
}
}
// Get the requirements to install the candidate
2019-11-29 18:01:41 +00:00
saved := s . InstalledDatabase
s . InstalledDatabase = pkg . NewInMemoryDatabase ( false )
2019-06-05 15:51:24 +00:00
asserts , err := s . Install ( [ ] pkg . Package { candidate } )
if err != nil {
return nil , err
}
2019-11-29 18:01:41 +00:00
s . InstalledDatabase = saved
2019-06-05 15:51:24 +00:00
for _ , a := range asserts {
2019-11-29 18:01:41 +00:00
if a . Value {
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 {
res = append ( res , a . Package . IsFlagged ( false ) )
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 {
2019-06-11 16:03:50 +00:00
res = append ( res , a . Package . IsFlagged ( false ) )
}
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
2019-06-11 16:03:50 +00:00
r , err := s . BuildWorld ( false )
2019-06-04 19:25:17 +00:00
if err != nil {
return nil , err
}
for _ , wanted := range s . Wanted {
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 )
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
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 ) {
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
}
2019-06-04 20:22:45 +00:00
model , _ , err := s . solve ( f )
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
2019-11-29 18:01:41 +00:00
func ( s * Solver ) Install ( c [ ] pkg . Package ) ( PackagesAssertions , error ) {
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
}
2019-06-04 20:22:45 +00:00
return s . Solve ( )
}