From c901e1f8f251888b06a60f38dbab1ffa543951b6 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Wed, 7 Mar 2012 11:15:12 +0100 Subject: [PATCH] Cleanup solver code. Just unify the solving process. Some more work needs to be done here. --- python/pakfire/api.py | 4 +- python/pakfire/base.py | 198 +++++++++++++++++++++------------- python/pakfire/cli.py | 9 +- python/pakfire/satsolver.py | 131 ++++++++++++++++++---- python/pakfire/transaction.py | 10 +- 5 files changed, 253 insertions(+), 99 deletions(-) diff --git a/python/pakfire/api.py b/python/pakfire/api.py index 332a7e752..605648886 100644 --- a/python/pakfire/api.py +++ b/python/pakfire/api.py @@ -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) diff --git a/python/pakfire/base.py b/python/pakfire/base.py index d8b4397eb..ee9f6857c 100644 --- a/python/pakfire/base.py +++ b/python/pakfire/base.py @@ -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 diff --git a/python/pakfire/cli.py b/python/pakfire/cli.py index 7a9490062..c2e2dfb38 100644 --- a/python/pakfire/cli.py +++ b/python/pakfire/cli.py @@ -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): diff --git a/python/pakfire/satsolver.py b/python/pakfire/satsolver.py index d2ae1ad30..c1da1717d 100644 --- a/python/pakfire/satsolver.py +++ b/python/pakfire/satsolver.py @@ -19,17 +19,18 @@ # # ############################################################################### +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. diff --git a/python/pakfire/transaction.py b/python/pakfire/transaction.py index 5f5076d1d..e8100cb02 100644 --- a/python/pakfire/transaction.py +++ b/python/pakfire/transaction.py @@ -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 = [] -- 2.39.5