]> git.ipfire.org Git - pakfire.git/commitdiff
Cleanup solver code.
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 7 Mar 2012 10:15:12 +0000 (11:15 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 7 Mar 2012 10:15:12 +0000 (11:15 +0100)
Just unify the solving process. Some more work needs to be done
here.

python/pakfire/api.py
python/pakfire/base.py
python/pakfire/cli.py
python/pakfire/satsolver.py
python/pakfire/transaction.py

index 332a7e752e4a468db6c034658cab61c2b3cbe081..605648886139a0c7cd2f03513e26a9b694c6a609 100644 (file)
@@ -31,10 +31,10 @@ def install(requires, **pakfire_args):
 
        return pakfire.install(requires)
 
-def resolvdep(requires, **pakfire_args):
+def resolvdep(pkgs, **pakfire_args):
        pakfire = Pakfire(**pakfire_args)
 
-       return pakfire.resolvdep(requires)
+       return pakfire.resolvdep(pkgs)
 
 def localinstall(files, **pakfire_args):
        pakfire = Pakfire(**pakfire_args)
index d8b4397eb033b1332306470515e1559d66a50e7d..ee9f6857c28772038484724c8ab9c75a43661d1d 100644 (file)
@@ -100,16 +100,54 @@ class Pakfire(object):
                # Reset logging.
                logger.setup_logging()
 
-       def create_solver(self):
-               return satsolver.Solver(self, self.pool)
+       def expand_requires(self, requires):
+               if requires is None:
+                       return []
 
-       def create_request(self, builder=False):
+               ret = []
+               for req in requires:
+                       if isinstance(req, packages.BinaryPackage):
+                               ret.append(req)
+                               continue
+
+                       if isinstance(req, packages.SolvPackage):
+                               ret.append(req.solvable)
+                               continue
+
+                       assert type(req) == type("a"), req
+
+                       # Expand all groups.
+                       if req.startswith("@"):
+                               reqs = self.grouplist(req[1:])
+                       else:
+                               reqs = [req,]
+
+                       for req in reqs:
+                               req = self.create_relation(req)
+                               ret.append(req)
+
+               return ret
+
+       def create_request(self, builder=False, install=None, remove=None, update=None):
                request = satsolver.Request(self.pool)
 
                # Add multiinstall information.
                for solv in PAKFIRE_MULTIINSTALL:
                        request.noobsoletes(solv)
 
+               # Apply all installs.
+               for req in self.expand_requires(install):
+                       request.install(req)
+
+               # Apply all removes.
+               for req in self.expand_requires(remove):
+                       request.remove(req)
+
+               # Apply all updates.
+               for req in self.expand_requires(update):
+                       request.update(req)
+
+               # Return the request.
                return request
 
        def create_relation(self, s):
@@ -198,53 +236,65 @@ class Pakfire(object):
                # XXX just backwards compatibility
                return self.mode == "builder"
 
-       def resolvdep(self, requires):
+       def resolvdep(self, pkg, logger=None):
+               assert os.path.exists(pkg)
+
+               # Open the package file.
+               pkg = packages.open(self, None, pkg)
+
                # Create a new request.
-               request = self.create_request()
-               for req in requires:
-                       req = self.create_relation(req)
-                       request.install(req)
+               request = self.create_request(install=pkg.requires)
 
-               # Do the solving.
-               solver = self.create_solver()
-               t = solver.solve(request)
+               # Add build dependencies if needed.
+               if isinstance(pkg, packages.Makefile) or isinstance(pkg, packages.SourcePackage):
+                       for req in self.expand_requires(BUILD_PACKAGES):
+                               request.install(req)
 
-               if t:
-                       t.dump()
-               else:
-                       log.info(_("Nothing to do"))
+               # Solv the request.
+               solver = self.solv(request, logger=logger)
+
+               if solver.status:
+                       return solver
+
+               raise DependencyError, solver.get_problem_string()
+
+       def solv(self, request, interactive=False, logger=None, **kwargs):
+               # XXX implement interactive
 
-       def install(self, requires, interactive=True, logger=None, **kwargs):
                if not logger:
                        logger = logging.getLogger("pakfire")
 
-               # Create a new request.
-               request = self.create_request()
+               # Create a solver.
+               solver = satsolver.Solver(self, request, logger=logger)
 
-               # Expand all groups.
-               for req in requires:
-                       if req.startswith("@"):
-                               reqs = self.grouplist(req[1:])
-                       else:
-                               reqs = [req,]
+               # Apply configuration to solver.
+               for key, val in kwargs.items():
+                       solver.set(key, val)
 
-                       for req in reqs:
-                               if not isinstance(req, packages.BinaryPackage):
-                                       req = self.create_relation(req)
+               # Do the solving.
+               solver.solve()
 
-                               request.install(req)
+               # Return the solver so one can do stuff with it...
+               return solver
+
+       def install(self, requires, interactive=True, logger=None, **kwargs):
+               if not logger:
+                       logger = logging.getLogger("pakfire")
 
                # Do the solving.
-               solver = self.create_solver()
-               t = solver.solve(request, logger=logger, **kwargs)
+               request = self.create_request(install=requires)
+               solver  = self.solv(request, logger=logger, interactive=interactive, **kwargs)
 
-               if not t:
+               if not solver.status:
                        if not interactive:
                                raise DependencyError
 
-                       log.info(_("Nothing to do"))
+                       logger.info(_("Nothing to do"))
                        return
 
+               # Create the transaction.
+               t = solver.transaction
+
                if interactive:
                        # Ask if the user acknowledges the transaction.
                        if not t.cli_yesno():
@@ -257,6 +307,9 @@ class Pakfire(object):
                t.run(logger=logger)
 
        def localinstall(self, files, yes=None, allow_uninstall=False, logger=None):
+               if logger is None:
+                       logger = logging.getLogger("pakfire")
+
                repo_name = repo_desc = "localinstall"
 
                # Create a new repository that holds all packages we passed on
@@ -279,18 +332,18 @@ class Pakfire(object):
 
                        # Create a new request that installs all solvables from the
                        # repository.
-                       request = self.create_request()
-                       for solv in [p.solvable for p in repo]:
-                               request.install(solv)
+                       request = self.create_request(install=repo)
 
-                       solver = self.create_solver()
-                       t = solver.solve(request, uninstall=allow_uninstall)
+                       solver = self.solv(request, logger=logger, uninstall=allow_uninstall)
 
                        # If solving was not possible, we exit here.
-                       if not t:
-                               log.info(_("Nothing to do"))
+                       if not solver.status:
+                               logger.info(_("Nothing to do"))
                                return
 
+                       # Create transaction.
+                       t = solver.transaction
+
                        if yes is None:
                                # Ask the user if this is okay.
                                if not t.cli_yesno():
@@ -391,8 +444,10 @@ class Pakfire(object):
                                request.install(new.solvable)
 
                if request:
-                       solver = self.create_solver()
-                       t = solver.solve(request)
+                       solver = self.solv(request)
+                       assert solver.status
+
+                       t = solver.transaction
                else:
                        # Create new transaction.
                        t = transaction.Transaction(self)
@@ -418,31 +473,29 @@ class Pakfire(object):
                        check indicates, if the method should return after calculation
                        of the transaction.
                """
-               request = self.create_request()
+               if logger is None:
+                       logger = logging.getLogger("pakfire")
 
                # If there are given any packets on the command line, we will
                # only update them. Otherwise, we update the whole system.
                if pkgs:
                        update = False
-                       for pkg in pkgs:
-                               pkg = self.create_relation(pkg)
-                               request.update(pkg)
                else:
                        update = True
 
+               request = self.create_request(update=pkgs)
+
                # Exclude packages that should not be updated.
-               if excludes:
-                       for exclude in excludes:
-                               log.info(_("Excluding %s.") % exclude)
+               for exclude in excludes or []:
+                       logger.info(_("Excluding %s.") % exclude)
 
-                               exclude = self.create_relation(exclude)
-                               request.lock(exclude)
+                       exclude = self.create_relation(exclude)
+                       request.lock(exclude)
 
-               solver = self.create_solver()
-               t = solver.solve(request, update=update, logger=logger, **kwargs)
+               solver = self.solv(request, logger=logger, update=update, **kwargs)
 
-               if not t:
-                       log.info(_("Nothing to do"))
+               if not solver.status:
+                       logger.info(_("Nothing to do"))
 
                        # If we are running in check mode, we return a non-zero value to
                        # indicate, that there are no updates.
@@ -451,6 +504,9 @@ class Pakfire(object):
                        else:
                                return
 
+               # Create the transaction.
+               t = solver.transaction
+
                # Just exit here, because we won't do the transaction in this mode.
                if check:
                        t.dump(logger=logger)
@@ -489,10 +545,12 @@ class Pakfire(object):
                                request.install(rel)
 
                # Solve the request.
-               solver = self.create_solver()
-               t = solver.solve(request, allow_downgrade=True,
-                       allow_vendorchange=allow_vendorchange,
+               solver = self.solve(request, allow_downgrade=True, allow_vendorchange=allow_vendorchange,
                        allow_archchange=allow_archchange)
+               assert solver.status is True
+
+               # Create the transaction.
+               t = solver.transaction
 
                if not t:
                        log.info(_("Nothing to do"))
@@ -505,14 +563,14 @@ class Pakfire(object):
 
        def remove(self, pkgs):
                # Create a new request.
-               request = self.create_request()
-               for pkg in pkgs:
-                       pkg = self.create_relation(pkg)
-                       request.remove(pkg)
+               request = self.create_request(remove=pkgs)
 
                # Solve the request.
-               solver = self.create_solver()
-               t = solver.solve(request, uninstall=True)
+               solver = self.solve(request, uninstall=True)
+               assert solver.status is True
+
+               # Create the transaction.
+               t = solver.transaction
 
                if not t:
                        log.info(_("Nothing to do"))
@@ -693,20 +751,16 @@ class Pakfire(object):
                # For that we create an empty request and solver and try to solve
                # something.
                request = self.create_request()
-               solver = self.create_solver()
-
-               # XXX the solver does crash if we call it with fix_system=1,
-               # allow_downgrade=1 and uninstall=1. Need to fix this.
-               allow_downgrade = False
-               uninstall = False
-
-               t = solver.solve(request, fix_system=True, allow_downgrade=downgrade,
+               solver = self.solve(request, fix_system=True, allow_downgrade=downgrade,
                        uninstall=uninstall)
 
-               if not t:
+               if solver.status is False:
                        log.info(_("Everything is fine."))
                        return
 
+               # Create the transaction.
+               t = solver.transaction
+
                # Ask the user if okay.
                if not t.cli_yesno():
                        return
index 7a9490062bb9a1faa56fe1d5c03add2ff02e30dc..c2e2dfb38cc97de5793fa36d1054ca4e8bada0a5 100644 (file)
@@ -278,7 +278,7 @@ class Cli(object):
                # Implement the "resolvdep" command.
                sub_resolvdep = self.sub_commands.add_parser("resolvdep",
                        help=_("Check the dependencies for a particular package."))
-               sub_resolvdep.add_argument("package", nargs="+",
+               sub_resolvdep.add_argument("package", nargs=1,
                        help=_("Give name of at least one package to check."))
                sub_resolvdep.add_argument("action", action="store_const", const="resolvdep")
 
@@ -375,7 +375,12 @@ class Cli(object):
                pakfire.check(**self.pakfire_args)
 
        def handle_resolvdep(self):
-               pakfire.resolvdep(self.args.package, **self.pakfire_args)
+               (pkg,) = self.args.package
+
+               solver = pakfire.resolvdep(pkg, **self.pakfire_args)
+
+               assert solver.status
+               solver.transaction.dump()
 
 
 class CliBuilder(Cli):
index d2ae1ad30a0abb4aaa205b8d5e0c246cad59189b..c1da1717dd5d5d77f08d4db1d12a2e1480bc61c6 100644 (file)
 #                                                                             #
 ###############################################################################
 
+import time
+
 import logging
 log = logging.getLogger("pakfire")
 
 import _pakfire
 from _pakfire import *
+from i18n import _
 
 import transaction
 import util
 
-from i18n import _
-
 class Request(_pakfire.Request):
        def install(self, what):
                if isinstance(what, Solvable):
@@ -108,42 +109,134 @@ class Request(_pakfire.Request):
 
 
 class Solver(object):
-       def __init__(self, pakfire, pool):
+       def __init__(self, pakfire, request, logger=None):
+               if logger is None:
+                       logger = logging.getLogger("pakfire")
+               self.logger = logger
+
                self.pakfire = pakfire
-               self.pool = pool
+               self.pool = self.pakfire.pool
 
-       def solve(self, request, update=False, uninstall=False, allow_downgrade=False,
-                       allow_vendorchange=False, allow_archchange=False, fix_system=False,
-                       interactive=False, logger=None):
+               # Default settings.
+               self.settings = {
+                       # Update all installed packages?
+                       "update" : False,
 
-               # If no logger was provided, we use the root logger.
-               if logger is None:
-                       logger = log
+                       # Allow to uninstall any packages?
+                       "uninstall" : False,
+
+                       # Allow to downgrade any packages?
+                       "allow_downgrade" : False,
+
+                       # Allow packages to change their vendors?
+                       "allow_vendorchange" : False,
+
+                       # Allow packages to change their arch?
+                       "allow_archchange" : False,
+
+                       # Fix system?
+                       "fix_system" : False,
+               }
+
+               self.request = request
+               assert self.request, "Empty request?"
 
                # Create a new solver.
-               solver = _pakfire.Solver(self.pool)
+               self.solver = _pakfire.Solver(self.pool)
+
+               # The status of the solver.
+               #   None when the solving was not done, yet.
+               #   True when the request could be solved.
+               #   False when the request could not be solved.
+               self.status = None
+
+               # Time that was needed to solve the request.
+               self.time = None
+
+               # Cache the transaction and problems.
+               self.__problems = None
+               self.__transaction = None
+
+       def set(self, key, value):
+               assert self.settings.has_key(key), "Unknown configuration setting: %s" % key
+               assert value in (True, False), "Invalid value: %s" % value
+
+               try:
+                       self.settings[key] = value
+               except KeyError:
+                       pass
+
+       def get(self, key):
+               assert self.settings.has_key(key), "Unknown configuration setting: %s" % key
 
-               solver.set_fix_system(fix_system)
-               solver.set_allow_uninstall(uninstall)
-               solver.set_allow_downgrade(allow_downgrade)
+               return self.settings.get(key)
+
+       def solve(self):
+               assert self.status is None, "Solver did already solve something."
+
+               # Apply solver configuration.
+               self.solver.set_fix_system(self.get("fix_system"))
+               self.solver.set_allow_uninstall(self.get("uninstall"))
+               self.solver.set_allow_downgrade(self.get("allow_downgrade"))
 
                # Optionally allow packages to change their vendors.
                # This is not recommended because it may have weird effects.
-               solver.set_allow_vendorchange(allow_vendorchange)
+               self.solver.set_allow_vendorchange(self.get("allow_vendorchange"))
 
                # Optionally allow packages ot change their architecture.
-               solver.set_allow_archchange(allow_archchange)
+               self.solver.set_allow_archchange(self.get("allow_archchange"))
 
                # Configure the solver for an update.
-               if update:
+               if self.get("update"):
                        solver.set_updatesystem(True)
                        solver.set_do_split_provides(True)
 
                # Actually solve the request.
-               res = solver.solve(request)
+               start_time = time.time()
+               self.status = self.solver.solve(self.request)
+
+               # Save the amount of time that was needed to solve the request.
+               self.time = time.time() - start_time
+
+               self.logger.debug("Solver status: %s (%.2f ms)" % (self.status, self.time / 1000))
+
+       @property
+       def transaction(self):
+               if not self.status is True:
+                       return
+
+               if self.__transaction is None:
+                       self.__transaction = \
+                               transaction.Transaction.from_solver(self.pakfire, self)
+
+               return self.__transaction
+
+       @property
+       def problems(self):
+               if self.__problems is None:
+                       self.__problems = self.solver.get_problems(self.request)
+
+               return self.__problems
+
+       def get_problem_string(self):
+               assert self.status is False
+
+               lines = [
+                       _("The solver returned one problem:", "The solver returned %(num)s problems:",
+                               len(self.problems)) % { "num" : len(self.problems) },
+               ]
+
+               i = 0
+               for problem in self.problems:
+                       i += 1
+
+                       # Print information about the problem.
+                       lines.append("  #%d: %s" % (i, problem))
+
+               return "\n".join(lines)
 
-               logger.debug("Solver status: %s" % res)
 
+       def DEADCODE(self):
                # If the solver succeeded, we return the transaction and return.
                if res:
                        # Return a resulting Transaction.
index 5f5076d1d1c4afd0d31298be963addf2542d6305..e8100cb024a20acbf7e7dbcdf6a8ee518f7052c9 100644 (file)
@@ -35,7 +35,8 @@ log = logging.getLogger("pakfire")
 
 from constants import *
 from i18n import _
-from pakfire._pakfire import sync
+from pakfire._pakfire import Transaction, sync
+_Transaction = Transaction
 
 PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-18s %6s "
 
@@ -179,17 +180,18 @@ class Transaction(object):
                return False
 
        @classmethod
-       def from_solver(cls, pakfire, solver, _transaction):
+       def from_solver(cls, pakfire, solver):
                # Create a new instance of our own transaction class.
                transaction = cls(pakfire)
 
+               # Get transaction data from the solver.
+               _transaction = _Transaction(solver.solver)
+
                # Save installsizechange.
                transaction.installsizechange = _transaction.get_installsizechange()
 
                # Get all steps that need to be done from the solver.
                steps = _transaction.steps()
-               if not steps:
-                       return
 
                actions = []
                actions_post = []