From ae20b05fa3eab42a73a1cc4ee522ae9c90701750 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 29 Apr 2011 21:20:03 +0200 Subject: [PATCH] Initial import of the sat solver code. --- pakfire/base.py | 77 ++----- pakfire/builder.py | 36 ++- pakfire/depsolve.py | 270 ----------------------- pakfire/packages/base.py | 108 +-------- pakfire/packages/binary.py | 16 -- pakfire/packages/installed.py | 42 ++-- pakfire/packages/source.py | 5 - pakfire/repository/__init__.py | 53 +++-- pakfire/repository/actions.py | 0 pakfire/repository/base.py | 19 ++ pakfire/repository/index.py | 25 +++ pakfire/repository/solver.py | 187 ++++++++++++++++ pakfire/repository/transaction.py | 341 +++++++++++++++++++++++++++++ pakfire/transaction.py | 351 ------------------------------ po/POTFILES.in | 59 ++--- po/pakfire.pot | 300 ++++++++++++------------- 16 files changed, 855 insertions(+), 1034 deletions(-) delete mode 100644 pakfire/depsolve.py create mode 100644 pakfire/repository/actions.py create mode 100644 pakfire/repository/solver.py create mode 100644 pakfire/repository/transaction.py delete mode 100644 pakfire/transaction.py diff --git a/pakfire/base.py b/pakfire/base.py index 6412a8428..cc8d66c1f 100644 --- a/pakfire/base.py +++ b/pakfire/base.py @@ -7,20 +7,18 @@ import string import builder import config -import depsolve import distro import logger -import packages import repository -import transaction +import packages import util from constants import * from i18n import _ class Pakfire(object): - def __init__(self, builder=False, configs=[], disable_repos=None, - distro_config=None): + def __init__(self, builder=False, configs=[], enable_repos=None, + disable_repos=None, distro_config=None): # Check if we are operating as the root user. self.check_root_user() @@ -46,16 +44,11 @@ class Pakfire(object): # Get more information about the distribution we are running # or building self.distro = distro.Distribution(self, distro_config) - self.repos = repository.Repositories(self) + self.repos = repository.Repositories(self, + enable_repos=enable_repos, disable_repos=disable_repos) - # Disable repositories if passed on command line - if disable_repos: - for repo in disable_repos: - self.repos.disable_repo(repo) - - # Update all indexes of the repositories (not force) so that we will - # always work with valid data. - self.repos.update() + # Create a short reference to the solver of this pakfire instance. + self.solver = self.repos.solver def destroy(self): if not self.path == "/": @@ -92,54 +85,22 @@ class Pakfire(object): raise BuildError, arch def install(self, requires): - ds = depsolve.DependencySet(pakfire=self) - + # Create a new request. + request = self.solver.create_request() for req in requires: - if isinstance(req, packages.BinaryPackage): - ds.add_package(req) - else: - ds.add_requires(req) + request.install(req) - ds.resolve() - ds.dump() + # Do the solving. + t = self.solver.solve(request) - ret = cli.ask_user(_("Is this okay?")) - if not ret: + if not t: return - ts = transaction.Transaction(self, ds) - ts.run() + t.run() def update(self, pkgs): - ds = depsolve.DependencySet(pakfire=self) - - for pkg in ds.packages: - # Skip unwanted packages (passed on command line) - if pkgs and not pkg.name in pkgs: - continue - - updates = self.repos.get_by_name(pkg.name) - updates = packages.PackageListing(updates) - - latest = updates.get_most_recent() - - # If the current package is already the latest - # we skip it. - if latest == pkg: - continue - - # Otherwise we want to update the package. - ds.add_package(latest) - - ds.resolve() - ds.dump() - - ret = cli.ask_user(_("Is this okay?")) - if not ret: - return - - ts = transaction.Transaction(self, ds) - ts.run() + # XXX needs to be done + pass def info(self, patterns): pkgs = [] @@ -251,8 +212,7 @@ class Pakfire(object): def provides(self, patterns): pkgs = [] for pattern in patterns: - requires = depsolve.Requires(None, pattern) - pkgs += self.repos.get_by_provides(requires) + pkgs += self.repos.get_by_provides(pattern) pkgs = packages.PackageListing(pkgs) #pkgs.unique() @@ -262,8 +222,7 @@ class Pakfire(object): def requires(self, patterns): pkgs = [] for pattern in patterns: - requires = depsolve.Requires(None, pattern) - pkgs += self.repos.get_by_requires(requires) + pkgs += self.repos.get_by_requires(pattern) pkgs = packages.PackageListing(pkgs) #pkgs.unique() diff --git a/pakfire/builder.py b/pakfire/builder.py index 124c7c795..db648088a 100644 --- a/pakfire/builder.py +++ b/pakfire/builder.py @@ -14,11 +14,9 @@ import uuid import base import chroot -import depsolve import logger import packages import repository -import transaction import util from constants import * @@ -135,6 +133,10 @@ class Builder(object): """ return self.distro.arch + @property + def solver(self): + return self.pakfire.solver + @property def info(self): return { @@ -269,19 +271,29 @@ class Builder(object): if not requires: return - ds = depsolve.DependencySet(self.pakfire) - for r in requires: - if isinstance(r, packages.BinaryPackage): - ds.add_package(r) - else: - ds.add_requires(r) - ds.resolve() - ds.dump(logger=self.log) + # Create a request and fill it with what we need. + request = self.solver.create_request() + + for req in requires: + if isinstance(req, packages.BinaryPackage): + req = req.friendly_name + + req = self.solver.create_relation(req) - ts = transaction.Transaction(self.pakfire, ds) - ts.run() + request.install(req) + + # Do the solving. + transaction = self.solver.solve(request) + + # Show the user what is going to be done. + transaction.dump(logger=self.log) + + # Run the transaction. + transaction.run() def install_test(self): + return # XXX currently disabled + pkgs = [] # Connect packages to the FS repository. diff --git a/pakfire/depsolve.py b/pakfire/depsolve.py deleted file mode 100644 index 3ccec24e9..000000000 --- a/pakfire/depsolve.py +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/python - -import logging -import re - -import packages -import repository -import transaction -import util - -from errors import * - -from i18n import _ - -PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-19s %5s " - -class Requires(object): - def __init__(self, pkg, requires, dep=False): - self.pkg = pkg - self.requires = requires - self.dep = dep - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.requires) - - def __str__(self): - return self.requires - - def __cmp__(self, other): - return cmp(self.requires, other.requires) - - def __hash__(self): - return hash(self.requires) - - @property - def type(self): - if self.requires.startswith("/"): - return "file" - - elif "(" in self.requires: - return "virtual" - - elif ">" in self.requires or "<" in self.requires or "=" in self.requires: - return "expr" - - elif not re.match("^lib.*\.so.*", self.requires): - return "lib" - - return "generic" - - -class Conflicts(object): - def __init__(self, pkg, conflicts): - self.pkg = pkg - self.conflicts = conflicts - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.conflicts) - - def __str__(self): - return self.conflicts - - def __hash__(self): - return hash(self.conflicts) - - -class Obsoletes(object): - def __init__(self, pkg, obsoletes): - self.pkg = pkg - self.obsoletes = obsoletes - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.obsoletes) - - def __str__(self): - return self.obsoletes - - def __hash__(self): - return hash(self.obsoletes) - - -class DependencySet(object): - def __init__(self, pakfire): - # Reference all repositories - self.repos = pakfire.repos #repository.Repositories() - - # List of packages in this set - self.__packages = set() - - # Helper lists - self.__conflicts = set() - self.__requires = set() - self.__obsoletes = set() - - # Create a new transaction set. - self.ts = transaction.TransactionSet() - - # Read-in all packages from the database that have - # been installed previously and need to be taken into - # account when resolving dependencies. - for pkg in self.repos.local.packages: - self.add_package(pkg, transaction=False) - - def add_requires(self, requires, pkg=None, dep=False): - requires = Requires(pkg, requires, dep) - - if requires in self.__requires: - return - - for pkg in self.__packages: - if pkg.does_provide(requires): - logging.debug("Skipping requires '%s' which is already provided by %s" % (requires.requires, pkg)) - return - - #logging.debug("Adding requires: %s" % requires) - self.__requires.add(requires) - - def add_obsoletes(self, obsoletes, pkg=None): - obsoletes = Obsoletes(pkg, obsoletes) - - self.__obsoletes.add(obsoletes) - - def add_package(self, pkg, dep=False, transaction=True): - #print pkg, sorted(self.__packages) - #assert not pkg in self.__packages - if pkg in self.__packages: - logging.debug("Trying to add package which is already in the dependency set: %s" % pkg) - return - - if transaction: - transaction_mode = "install" - for p in self.__packages: - if pkg.name == p.name: - transaction_mode = "update" - - # Set pointer to package that is updated. - pkg.old_package = p - - # Remove old package from list of packages. - self.__packages.remove(pkg.old_package) - break - - # Add package to transaction set - func = getattr(self.ts, transaction_mode) - func(pkg, dep=dep) - - #if not isinstance(pkg, packages.DatabasePackage): - # logging.info(" --> Adding package to dependency set: %s" % pkg.friendly_name) - self.__packages.add(pkg) - - # Add the requirements of the newly added package. - for req in pkg.requires: - self.add_requires(req, pkg, dep=True) - - # Remove all requires that are fulfilled by this package. - # For that we copy the matching requires to _requires and remove them - # afterwards, because changing self.__requires in a "for" loop is not - # a good idea. - _requires = [] - for req in self.__requires: - if pkg.does_provide(req): - _requires.append(req) - - for req in _requires: - self.__requires.remove(req) - - @property - def packages(self): - if not self.__requires: - return self.__packages[:] - - def resolve(self): - unresolveable_reqs = [] - - while self.__requires: - requires = self.__requires.pop() - logging.debug("Resolving requirement \"%s\"" % requires) - - # Fetch all candidates from the repositories and save the - # best one - if requires.type == "file": - candidates = self.repos.get_by_file(requires.requires) - else: - candidates = self.repos.get_by_provides(requires) - - # Turn the candidates into a package listing. - candidates = packages.PackageListing(candidates) - - if not candidates: - logging.debug(" Got no candidates for that") - unresolveable_reqs.append(requires) - continue - - logging.debug(" Got candidates for that:") - for candidate in candidates: - logging.debug(" --> %s" % candidate) - - best = candidates.get_most_recent() - if best: - self.add_package(best, dep=requires.dep) - - if unresolveable_reqs: - raise DependencyError, "Cannot resolve %s" % \ - " ".join([r.requires for r in unresolveable_reqs]) - - def dump_pkg(self, format, pkg): - return format % ( - pkg.name, - pkg.arch, - pkg.friendly_version, - pkg.repo.name, - util.format_size(pkg.size), - ) - - def dump_pkgs(self, caption, pkgs): - if not pkgs: - return [] - - s = [caption,] - for pkg in sorted(pkgs): - s.append(self.dump_pkg(PKG_DUMP_FORMAT, pkg)) - s.append("") - return s - - def dump(self, logger=None): - # If no logger was given, we use the root logger. - if not logger: - logger = logging.getLogger() - - width = 80 - line = "=" * width - - s = [] - s.append(line) - s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"), _("Repository"), _("Size"))) - s.append(line) - - s += self.dump_pkgs(_("Installing:"), self.ts.installs) - s += self.dump_pkgs(_("Installing for dependencies:"), self.ts.install_deps) - s += self.dump_pkgs(_("Updating:"), self.ts.updates) - s += self.dump_pkgs(_("Updating for dependencies:"), self.ts.update_deps) - s += self.dump_pkgs(_("Removing:"), self.ts.removes) - s += self.dump_pkgs(_("Removing for dependencies:"), self.ts.remove_deps) - - s.append(_("Transaction Summary")) - s.append(line) - - format = "%-20s %-4d %s" - - if self.ts.installs or self.ts.install_deps: - s.append(format % (_("Install"), - len(self.ts.installs + self.ts.install_deps), _("Package(s)"))) - - if self.ts.updates or self.ts.update_deps: - s.append(format % (_("Updates"), - len(self.ts.updates + self.ts.update_deps), _("Package(s)"))) - - if self.ts.removes or self.ts.remove_deps: - s.append(format % (_("Remove"), - len(self.ts.removes + self.ts.remove_deps), _("Package(s)"))) - - # Calculate the size of all files that need to be downloaded this this - # transaction. - download_size = sum([p.size for p in self.ts.downloads]) - if download_size: - s.append(_("Total download size: %s") % util.format_size(download_size)) - s.append("") - - for line in s: - logger.info(line) diff --git a/pakfire/packages/base.py b/pakfire/packages/base.py index d7d1f48c4..bd90428b9 100644 --- a/pakfire/packages/base.py +++ b/pakfire/packages/base.py @@ -1,13 +1,9 @@ #!/usr/bin/python - -import fnmatch import logging -import re import util -import pakfire.depsolve from pakfire.i18n import _ class Package(object): @@ -291,6 +287,10 @@ class Package(object): def supported_arches(self): return self.metadata.get("PKG_SUPPORTED_ARCHES", "all") + @property + def vendor(self): + return self.metadata.get("PKG_VENDOR", "") + @property def requires(self): ret = "" @@ -310,101 +310,17 @@ class Package(object): return set(ret.split()) @property - def _provides(self): - # Make package identifyable by its name and version/release tuples. - provides = [ - self.name, - "%s=%s-%s" % (self.name, self.version, self.release), - "%s=%s:%s-%s" % (self.name, self.epoch, self.version, self.release), - ] + def provides(self): + provides = self.metadata.get("PKG_PROVIDES", "").split() return set(provides) - ### methods ### - - def _does_provide_file(self, requires): - for file in self.filelist: - if fnmatch.fnmatch(file, requires.requires): - return True - - return False - - def does_provide(self, requires): - if not isinstance(requires, pakfire.depsolve.Requires): - requires = pakfire.depsolve.Requires(self, requires) - - # Get all provide strings from the package data - # and return true if requires is matched. - if requires.requires in self.provides: - return True - - if requires.type == "file": - return self._does_provide_file(requires) - - elif requires.type == "expr": - # Handle all expressions like "gcc>=4.0.0-1" - (e_expr, e_name, e_epoch, e_version, e_release) = \ - util.parse_pkg_expr(requires.requires) - - # If the package names do not match, we do not provide this: - if not self.name == e_name: - return False - - ret = util.version_compare(self.version_tuple, (e_epoch, e_version, e_release)) - - # If we equal the version, we provide this - if "=" in e_expr and ret == 0: - return True - - elif ">" in e_expr and ret > 0: - return True - - elif "<" in e_expr and ret < 0: - return True - - return False - - elif requires.type == "virtual": - (r_type, r_expr, r_name, r_version) = \ - util.parse_virtual_expr(requires.requires) - - # If we get an invalid expression with no name, we - # do not provide this. - if not r_name: - return False - - for provides in self.provides: - (p_type, p_expr, p_name, p_version) = \ - util.parse_virtual_expr(provides) - - # If name does not match, we have no match at all. - if not p_type == r_type or not p_name == r_name: - continue - - # Check if the expression is fulfilled. - if r_expr == "=": - return p_version == r_version - - elif r_expr == ">=": - return p_version >= r_version - - elif r_expr == ">": - return p_version > r_version - - elif r_expr == "<": - return p_version < r_version - - elif r_expr == "<=": - return p_version <= r_version - - elif not r_expr: - # If we get here, the name matches and there was no version - # required. - return True + @property + def obsoletes(self): + obsoletes = self.metadata.get("PKG_OBSOLETES", "").split() - # No match was found at all - return False + return set(obsoletes) - def extract(self, path): - raise NotImplementedError + def extract(self, path, prefix=None): + raise NotImplementedError, "%s" % type(self) diff --git a/pakfire/packages/binary.py b/pakfire/packages/binary.py index 06c6bbf84..150056624 100644 --- a/pakfire/packages/binary.py +++ b/pakfire/packages/binary.py @@ -1,7 +1,5 @@ #!/usr/bin/python -import sys - from file import FilePackage class BinaryPackage(FilePackage): @@ -9,20 +7,6 @@ class BinaryPackage(FilePackage): def arch(self): return self.metadata.get("PKG_ARCH") - @property - def provides(self): - if not hasattr(self, "__provides"): - # Get automatic provides - provides = self._provides - - # Add other provides - for prov in self.metadata.get("PKG_PROVIDES", "").split(): - provides.add(prov) - - self.__provides = provides - - return self.__provides - @property def conflicts(self): conflicts = self.metadata.get("PKG_CONFLICTS", "").split() diff --git a/pakfire/packages/installed.py b/pakfire/packages/installed.py index 5b6943ed8..9d66dec4b 100644 --- a/pakfire/packages/installed.py +++ b/pakfire/packages/installed.py @@ -110,35 +110,27 @@ class DatabasePackage(Package): @property def provides(self): - if not hasattr(self, "__provides"): - # Get automatic provides - provides = self._provides + provides = self.metadata.get("provides", "").split() - # Add other provides - for prov in self.metadata.get("provides", "").split(): - provides.add(prov) - - self.__provides = provides - - return self.__provides + return set(provides) @property def requires(self): - requires = self.metadata.get("requires") - - if requires: - return requires.split() + requires = self.metadata.get("requires", "").split() - return [] + return set(requires) @property def conflicts(self): - conflicts = self.metadata.get("conflicts") + conflicts = self.metadata.get("conflicts", "").split() - if conflicts: - return conflicts.split() + return set(conflicts) + + @property + def obsoletes(self): + obsoletes = self.metadata.get("obsoletes", "").split() - return [] + return set(obsoletes) @property def hash1(self): @@ -154,18 +146,14 @@ class DatabasePackage(Package): @property def filelist(self): - if not hasattr(self, "__filelist"): - c = self.db.cursor() + c = self.db.cursor() + try: c.execute("SELECT name FROM files WHERE pkg = ?", (self.id,)) - self.__filelist = [] - for f in c: - self.__filelist.append(f["name"]) - + return [f["name"] for f in c] + finally: c.close() - return self.__filelist - def _does_provide_file(self, requires): """ A faster version to find a file in the database. diff --git a/pakfire/packages/source.py b/pakfire/packages/source.py index e606f4748..67a0b48e7 100644 --- a/pakfire/packages/source.py +++ b/pakfire/packages/source.py @@ -13,8 +13,3 @@ class SourcePackage(FilePackage): Return the requirements for the build. """ return self.metadata.get("PKG_REQUIRES", "").split() - - @property - def provides(self): - # XXX just a dummy - return [] diff --git a/pakfire/repository/__init__.py b/pakfire/repository/__init__.py index f0aebf33f..21557e5bd 100644 --- a/pakfire/repository/__init__.py +++ b/pakfire/repository/__init__.py @@ -2,6 +2,8 @@ import logging +import solver + from installed import InstalledRepository from local import LocalRepository, LocalBuildRepository, LocalSourceRepository from oddments import DummyRepository, FileSystemRepository @@ -14,7 +16,7 @@ class Repositories(object): This is the place where repositories can be activated or deactivated. """ - def __init__(self, pakfire): + def __init__(self, pakfire, enable_repos=None, disable_repos=None): self.pakfire = pakfire self.config = pakfire.config @@ -38,6 +40,15 @@ class Repositories(object): for repo_name, repo_args in self.config.get_repos(): self._parse(repo_name, repo_args) + # XXX need to process enable_repos and disable_repos here + + # Update all indexes of the repositories (not force) so that we will + # always work with valid data. + self.update() + + # Initialize the solver. + self.solver = solver.Solver(self.pakfire, self) + def __len__(self): """ Return the count of enabled repositories. @@ -93,40 +104,48 @@ class Repositories(object): for repo in self.enabled: repo.update(force=force) + def get_repo_by_name(self, name): + for repo in self.enabled: + if repo.name == name: + return repo + def get_all(self): for repo in self.enabled: for pkg in repo.get_all(): yield pkg def get_by_name(self, name): - for repo in self.enabled: - for pkg in repo.get_by_name(name): - yield pkg + #for repo in self.enabled: + # for pkg in repo.get_by_name(name): + # yield pkg + return self.solver.get_by_name(name) def get_by_glob(self, pattern): for repo in self.enabled: for pkg in repo.get_by_glob(pattern): yield pkg - def get_by_provides(self, requires): - if requires.type == "file": - for pkg in self.get_by_file(requires.requires): - yield pkg - - else: - for repo in self.enabled: - for pkg in repo.get_by_provides(requires): - yield pkg + #def get_by_provides(self, requires): + # if requires.type == "file": + # for pkg in self.get_by_file(requires.requires): + # yield pkg + # + # else: + # for repo in self.enabled: + # for pkg in repo.get_by_provides(requires): + # yield pkg + get_by_provides = get_by_name def get_by_requires(self, requires): for repo in self.enabled: for pkg in repo.get_by_requires(requires): yield pkg - def get_by_file(self, filename): - for repo in self.enabled: - for pkg in repo.get_by_file(filename): - yield pkg + #def get_by_file(self, filename): + # for repo in self.enabled: + # for pkg in repo.get_by_file(filename): + # yield pkg + get_by_file = get_by_name def get_by_group(self, group): for repo in self.enabled: diff --git a/pakfire/repository/actions.py b/pakfire/repository/actions.py new file mode 100644 index 000000000..e69de29bb diff --git a/pakfire/repository/base.py b/pakfire/repository/base.py index cdf65779f..d80448cbb 100644 --- a/pakfire/repository/base.py +++ b/pakfire/repository/base.py @@ -105,6 +105,11 @@ class RepositoryFactory(object): if group in pkg.groups: yield pkg + def get_by_friendly_name(self, name): + for pkg in self.packages: + if pkg.friendly_name == name: + return pkg + def search(self, pattern): """ Returns a list of packages, that match the given pattern, @@ -123,3 +128,17 @@ class RepositoryFactory(object): Returns all packages. """ return self.index.packages + + @property + def size(self): + """ + Return the number of packages. + """ + return self.index.size + + @property + def filelist(self): + if hasattr(self.index, "filelist"): + return self.index.filelist + + return {} diff --git a/pakfire/repository/index.py b/pakfire/repository/index.py index 2b2991eae..854e0a3a9 100644 --- a/pakfire/repository/index.py +++ b/pakfire/repository/index.py @@ -64,6 +64,14 @@ class Index(object): for pkg in self._packages: yield pkg + @property + def size(self): + i = 0 + for pkg in self.packages: + i += 1 + + return i + def update(self, force=False): pass @@ -180,6 +188,23 @@ class DatabaseIndexFactory(Index): c.close() + @property + def filelist(self): + c = self.db.cursor() + c.execute("SELECT pkg, name FROM files") + + files = {} + + for entry in c: + file = entry["name"] + try: + files[pkg_id].append(file) + except KeyError: + files[pkg_id] = [file,] + + c.close() + + return files class InstalledIndex(DatabaseIndexFactory): def open_database(self): diff --git a/pakfire/repository/solver.py b/pakfire/repository/solver.py new file mode 100644 index 000000000..b9f5772a0 --- /dev/null +++ b/pakfire/repository/solver.py @@ -0,0 +1,187 @@ +#!/usr/bin/python + +import logging +import satsolver +import time + +from transaction import Transaction + +import pakfire.util as util + +from pakfire.constants import * +from pakfire.i18n import _ + +class Solver(object): + RELATIONS = ( + (">=", satsolver.REL_GE,), + ("<=", satsolver.REL_LE,), + ("=" , satsolver.REL_EQ,), + ("<" , satsolver.REL_LT,), + (">" , satsolver.REL_GT,), + ) + + def __init__(self, pakfire, repos, arch=None): + self.pakfire = pakfire + self.repos = repos + + if not arch: + arch = self.pakfire.distro.arch + + # Mapping from solver ID to a package. + self.id2pkg = {} + + # Initialize the pool and set the architecture. + self.pool = satsolver.Pool() + self.pool.set_arch(arch) + + # Initialize all repositories. + self.repos = self.init_repos() + + self.pool.prepare() + + def create_relation(self, s): + s = str(s) + + if s.startswith("/"): + return satsolver.Relation(self.pool, s) + + for pattern, type in self.RELATIONS: + if not pattern in s: + continue + + name, version = s.split(pattern, 1) + + return satsolver.Relation(self.pool, name, type, version) + + return satsolver.Relation(self.pool, s) + + def init_repos(self): + repos = [] + + for repo in self.repos.enabled: + solvrepo = self.pool.create_repo(repo.name) + if repo.name == "installed": + self.pool.set_installed(solvrepo) + + pb = util.make_progress(_("Loading %s") % repo.name, repo.size) + i = 0 + + for pkg in repo.get_all(): + if pb: + i += 1 + pb.update(i) + + self.add_package(pkg) + + logging.debug("Initialized new repo '%s' with %s packages." % \ + (solvrepo.name(), solvrepo.size())) + + if pb: + pb.finish() + + repos.append(solvrepo) + + return repos + + def get_repo(self, name): + for repo in self.pool.repos(): + if not repo.name() == name: + continue + + return repo + + def add_package(self, pkg, repo_name=None): + if not repo_name: + repo_name = pkg.repo.name + + solvrepo = self.get_repo(repo_name) + assert solvrepo + + solvable = satsolver.Solvable(solvrepo, str(pkg.name), + str(pkg.friendly_version), str(pkg.arch)) + + # Store the solver's ID. + self.id2pkg[solvable.id()] = pkg + + # Set vendor. + solvable.set_vendor(pkg.vendor) + + # Import all requires. + for req in pkg.requires: + rel = self.create_relation(req) + solvable.requires().add(rel) + + # Import all provides. + for prov in pkg.provides: + rel = self.create_relation(prov) + solvable.provides().add(rel) + + # Import all conflicts. + for conf in pkg.conflicts: + rel = self.create_relation(conf) + solvable.conflicts().add(rel) + + # Import all obsoletes. + for obso in pkg.obsoletes: + rel = self.create_relation(obso) + solvable.obsoletes().add(rel) + + # Import all files that are in the package. + rel = self.create_relation("solvable:filemarker") + solvable.provides().add(rel) + for file in pkg.filelist: + rel = self.create_relation(file) + solvable.provides().add(rel) + + def create_request(self): + return self.pool.create_request() + + def solve(self, request): + solver = self.pool.create_solver() + solver.set_allow_uninstall(True) + + while True: + # Save start time. + time_start = time.time() + + # Acutally run the solver. + res = solver.solve(request) + + # Log time and status of the solver. + logging.debug("Solving took %s" % (time.time() - time_start)) + logging.debug("Solver status: %s" % res) + + # If the solver succeeded, we return the transaction and return. + if res: + # Return a resulting Transaction. + return Transaction.from_solver(self.pakfire, self, solver) + + # Solver had an error and we now see what we can do: + logging.info("The solver returned %s problems." % solver.problems_count()) + + for p in solver.problems(request): + logging.info("Problem: %s" % p) + for s in p.solutions(): + s = "%s" % s + logging.info(s.strip()) + + break + + def solvables2packages(self, solvables): + pkgs = [] + + for solv in solvables: + pkg = self.id2pkg[solv.id()] + pkgs.append(pkg) + + return pkgs + + def get_by_provides(self, provides): + print provides + provides = self.create_relation(provides) + + pkgs = self.solvables2packages(self.pool.providers(provides)) + + return pkgs + + get_by_name = get_by_provides diff --git a/pakfire/repository/transaction.py b/pakfire/repository/transaction.py new file mode 100644 index 000000000..f1ce127ed --- /dev/null +++ b/pakfire/repository/transaction.py @@ -0,0 +1,341 @@ +#!/usr/bin/python + +import logging +import os +import progressbar +import sys + +import pakfire.packages as packages +import pakfire.util as util + +from pakfire.i18n import _ + +PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-19s %5s " + +class ActionError(Exception): + pass + + +class Action(object): + def __init__(self, pakfire, pkg, deps=None): + self.pakfire = pakfire + self.pkg = pkg + self.deps = deps or [] + + def __cmp__(self, other): + # XXX ugly + return cmp(self.__repr__(), other.__repr__()) + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, self.pkg.friendly_name) + + def remove_dep(self, dep): + if not self.deps: + return + + while dep in self.deps: + logging.debug("Removing dep %s from %s" % (dep, self)) + self.deps.remove(dep) + + def run(self): + raise NotImplementedError + + @property + def local(self): + """ + Reference to local repository (database). + """ + return self.pakfire.repos.local + + +class ActionCleanup(Action): + def gen_files(self): + """ + Return a list of all files that are not in the package anymore + and so to be removed. + """ + files = [] + + # Compare the filelist of the old and the new package and save the + # difference. + + for f in self.pkg.old_package.filelist: + if f in self.pkg.filelist: + continue + + # Save absolute path. + f = os.path.join(self.pakfire.path, f) + files.append(f) + + return files + + def remove_files(self, message, files): + if not files: + return + + pb = util.make_progress(message, len(files)) + i = 0 + + for f in self.gen_files(): + # Update progress if any. + i += 1 + if pb: + pb.update(i) + + # Skip non-existant files (mabye the user removed it already?) + if not os.path.exists(f): + continue + + logging.debug("Going to remove file: %s" % f) + + try: + os.unlink(f) + except: + logging.critical("Could not remove file: %s. Do it manually." % f) + + # XXX remove file from database + + if pb: + pb.finish() + + def run(self): + files = self.gen_files() + + if not files: + return + + self.remove_files(_("Cleanup: %s") % self.pkg.name, files) + + +class ActionScript(Action): + def run(self): + pass # XXX TBD + + +class ActionScriptPreIn(ActionScript): + pass + + +class ActionScriptPostIn(ActionScript): + pass + + +class ActionScriptPreUn(ActionScript): + pass + + +class ActionScriptPostUn(ActionScript): + pass + + +class ActionInstall(Action): + type = "install" + + def extract(self, message, prefix=None): + logging.debug("Extracting package %s" % self.pkg.friendly_name) + + if prefix is None: + prefix = self.pakfire.path + + self.pkg.extract(message, prefix=prefix) + + # Create package in the database + self.local.index.add_package(self.pkg) + + def run(self): + self.extract(_("Installing: %s") % self.pkg.name) + + self.pakfire.solver.add_package(self.pkg, "installed") + + +class ActionUpdate(ActionInstall): + type = "update" + + def run(self): + self.extract(_("Updating: %s") % self.pkg.name) + + +class ActionRemove(ActionCleanup): + type = "remove" + + def run(self): + files = self.pkg.filelist + + if not files: + return + + self.remove_files(_("Removing: %s") % self.pkg.name, files) + + +class Transaction(object): + action_classes = [ + ActionInstall, + ActionUpdate, + ActionRemove, + ] + + def __init__(self, pakfire): + self.pakfire = pakfire + self.actions = [] + + self.downloads = [] + + @classmethod + def from_solver(cls, pakfire, solver1, solver2): + # Grab the original transaction object from the solver. + _transaction = solver2.transaction() + + # Order the objects in the transaction in that way we will run the + # installation. + _transaction.order() + + # Create a new instance of our own transaction class. + transaction = cls(pakfire) + + for step in _transaction.steps(): + action = step.type_s() + pkg = solver1.id2pkg[step.solvable().id()] + + if not isinstance(pkg, packages.BinaryPackage): + transaction.downloads.append(pkg) + + for action_cls in cls.action_classes: + if action_cls.type == action: + action = action_cls(pakfire, pkg) + + if not isinstance(action, Action): + raise Exception, "Unknown action required: %s" % action + + transaction.add_action(action) + + return transaction + + def download(self): + if not self.downloads: + return + + i = 0 + for pkg in self.downloads: + i += 1 + + # Actually download the package. + pkg_bin = pkg.download(text="(%2d/%02d): " % (i, len(self.downloads))) + + # Replace the package in all actions where it matches. + actions = [a for a in self.actions if a.pkg == pkg] + + for action in actions: + action.pkg = pkg_bin + + # Reset packages to be downloaded. + self.downloads = [] + print + + @property + def installs(self): + return [a for a in self.actions if a.type == "install"] + + @property + def removes(self): + return [a for a in self.actions if a.type == "remove"] + + @property + def updates(self): + return [a for a in self.actions if a.type == "update"] + + def dump_pkg(self, pkg): + ret = [] + + name = pkg.name + if len(name) > 21: + ret.append(" %s" % name) + name = "" + + ret.append(PKG_DUMP_FORMAT % (name, pkg.arch, pkg.friendly_version, + pkg.repo.name, util.format_size(pkg.size))) + + return ret + + def dump_pkgs(self, caption, pkgs): + if not pkgs: + return [] + + s = [caption,] + for pkg in sorted(pkgs): + s += self.dump_pkg(pkg) + s.append("") + return s + + def dump(self, logger=None): + if not logger: + logger = logging.getLogger() + + width = 80 + line = "=" * width + + s = [] + s.append(line) + s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"), _("Repository"), _("Size"))) + s.append(line) + + s += self.dump_pkgs(_("Installing:"), [a.pkg for a in self.installs]) + s += self.dump_pkgs(_("Updating:"), [a.pkg for a in self.updates]) + s += self.dump_pkgs(_("Removing:"), [a.pkg for a in self.removes]) + + s.append(_("Transaction Summary")) + s.append(line) + + format = "%-20s %-4d %s" + + if self.installs: + s.append(format % (_("Install"), len(self.installs), _("Package(s)"))) + + if self.updates: + s.append(format % (_("Updates"), len(self.updates), _("Package(s)"))) + + if self.removes: + s.append(format % (_("Remove"), len(self.removes), _("Package(s)"))) + + # Calculate the size of all files that need to be downloaded this this + # transaction. + download_size = sum([p.size for p in self.downloads]) + if download_size: + s.append(_("Total download size: %s") % util.format_size(download_size)) + s.append("") + + for line in s: + logger.info(line) + + def run_action(self, action): + try: + action.run() + except ActionError, e: + logging.error("Action finished with an error: %s - %s" % (action, e)) + + def add_action(self, action): + logging.debug("New action added: %s" % action) + + self.actions.append(action) + + def remove_action(self, action): + logging.debug("Removing action: %s" % action) + + self.actions.remove(action) + for action in self.actions: + action.remove_dep(action) + + def run(self): + # Download all packages. + self.download() + + while True: + if not [a for a in self.actions]: + break + + for action in self.actions: + if action.deps: + #logging.debug("Skipping %s which cannot be run now." % action) + continue + + self.run_action(action) + self.remove_action(action) diff --git a/pakfire/transaction.py b/pakfire/transaction.py deleted file mode 100644 index d2dc90bec..000000000 --- a/pakfire/transaction.py +++ /dev/null @@ -1,351 +0,0 @@ -#!/usr/bin/python - -import logging -import os -import progressbar -import sys -import tarfile - -import depsolve -import packages -import util - -from i18n import _ - -class ActionError(Exception): - pass - - -class Action(object): - def __init__(self, pakfire, pkg, deps=None): - self.pakfire = pakfire - self.pkg = pkg - self.deps = deps or [] - - def __cmp__(self, other): - # XXX ugly - return cmp(self.__repr__(), other.__repr__()) - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.pkg.friendly_name) - - def remove_dep(self, dep): - if not self.deps: - return - - while dep in self.deps: - logging.debug("Removing dep %s from %s" % (dep, self)) - self.deps.remove(dep) - - def run(self): - raise NotImplementedError - - @property - def local(self): - """ - Reference to local repository (database). - """ - return self.pakfire.repos.local - - -class ActionCleanup(Action): - def gen_files(self): - """ - Return a list of all files that are not in the package anymore - and so to be removed. - """ - files = [] - - # Compare the filelist of the old and the new package and save the - # difference. - - for f in self.pkg.old_package.filelist: - if f in self.pkg.filelist: - continue - - # Save absolute path. - f = os.path.join(self.pakfire.path, f) - files.append(f) - - return files - - def remove_files(self, message, files): - if not files: - return - - pb = util.make_progress(message, len(files)) - i = 0 - - for f in self.gen_files(): - # Update progress if any. - i += 1 - if pb: - pb.update(i) - - # Skip non-existant files (mabye the user removed it already?) - if not os.path.exists(f): - continue - - logging.debug("Going to remove file: %s" % f) - - try: - os.unlink(f) - except: - logging.critical("Could not remove file: %s. Do it manually." % f) - - # XXX remove file from database - - if pb: - pb.finish() - - def run(self): - files = self.gen_files() - - if not files: - return - - self.remove_files(_("Cleanup: %s") % self.pkg.name, files) - - -class ActionScript(Action): - def run(self): - pass # XXX TBD - - -class ActionScriptPreIn(ActionScript): - pass - - -class ActionScriptPostIn(ActionScript): - pass - - -class ActionScriptPreUn(ActionScript): - pass - - -class ActionScriptPostUn(ActionScript): - pass - - -class ActionInstall(Action): - def extract(self, message, prefix=None): - logging.debug("Extracting package %s" % self.pkg.friendly_name) - - if prefix is None: - prefix = self.pakfire.path - - self.pkg.extract(message, prefix=prefix) - - # Create package in the database - self.local.index.add_package(self.pkg) - - def run(self): - self.extract(_("Installing: %s") % self.pkg.name) - - -class ActionUpdate(ActionInstall): - def run(self): - self.extract(_("Updating: %s") % self.pkg.name) - - -class ActionRemove(ActionCleanup): - def run(self): - files = self.pkg.filelist - - if not files: - return - - self.remove_files(_("Removing: %s") % self.pkg.name, files) - - -class TransactionSet(object): - def __init__(self): - self.installs = [] - self.install_deps = [] - - self.updates = [] - self.update_deps = [] - - self.removes = [] - self.remove_deps = [] - - @property - def download_lists(self): - # All elements in these lists must be local. - return (self.installs, self.install_deps, self.updates, self.update_deps) - - @property - def downloads(self): - """ - Return a list containing all packages that need to be downloaded. - """ - pkgs = [] - for dl_list in self.download_lists: - pkgs += dl_list - - pkgs.sort() - - for pkg in pkgs: - # Skip all packages that are already local. - if pkg.local: - continue - - yield pkg - - def install(self, pkg, dep=False): - logging.info(" --> Marking package for install: %s" % pkg.friendly_name) - - if dep: - self.install_deps.append(pkg) - else: - self.installs.append(pkg) - - def remove(self, pkg, dep=False): - logging.info(" --> Marking package for remove: %s" % pkg.friendly_name) - - if dep: - self.remove_deps.append(pkg) - else: - self.removes.append(pkg) - - def update(self, pkg, dep=False): - assert pkg.old_package - - logging.info(" --> Marking package for update: %s (was %s)" % \ - (pkg.friendly_name, pkg.old_package.friendly_version)) - - if dep: - self.update_deps.append(pkg) - else: - self.updates.append(pkg) - - def download(self): - """ - Convert all packages to BinaryPackage. - """ - pkgs = [] - for pkg in self.downloads: - pkgs.append(pkg) - - # If there are no packages to download skip the rest. - if not pkgs: - return - - logging.info("Downloading packages:") - i = 0 - for download in pkgs: - i += 1 - pkg = download.download(text="(%2d/%02d): " % (i, len(pkgs))) - - for download_list in self.download_lists: - if download in download_list: - download_list.remove(download) - download_list.append(pkg) - break - - # Just an empty line to seperate the downloads from the extractions. - logging.info("") - - -class Transaction(object): - def __init__(self, pakfire, ds): - self.pakfire = pakfire - self.ds = ds - - self._actions = [] - - def _install_pkg(self, pkg): - assert isinstance(pkg, packages.BinaryPackage) - - # XXX add dependencies for running the script here - action_prein = ActionScriptPreIn(self.pakfire, pkg) - - action_install = ActionInstall(self.pakfire, pkg, deps=[action_prein]) - - # XXX add dependencies for running the script here - action_postin = ActionScriptPostIn(self.pakfire, pkg, deps=[action_install]) - - for action in (action_prein, action_install, action_postin): - self.add_action(action) - - def _update_pkg(self, pkg): - assert isinstance(pkg, packages.BinaryPackage) - - action_update = ActionUpdate(self.pakfire, pkg) - - action_cleanup = ActionCleanup(self.pakfire, pkg, deps=[action_update]) - - for action in (action_update, action_cleanup): - self.add_action(action) - - def _remove_pkg(self, pkg): - # XXX add scripts - action_remove = ActionRemove(self.pakfire, pkg) - - for action in (action_remove): - self.add_action(action) - - def populate(self): - # Determine which packages we have to add - # and which we have to remove. - - # Add all packages that need to be installed. - for pkg in self.ds.ts.installs + self.ds.ts.install_deps: - self._install_pkg(pkg) - - # Add all packages that need to be updated. - for pkg in self.ds.ts.updates + self.ds.ts.update_deps: - self._update_pkg(pkg) - - # Add all packages that need to be removed. - for pkg in self.ds.ts.removes + self.ds.ts.remove_deps: - self._remove_pkg(pkg) - - def add_action(self, action): - logging.debug("New action added: %s" % action) - - self._actions.append(action) - - def remove_action(self, action): - logging.debug("Removing action: %s" % action) - - self._actions.remove(action) - for _action in self.actions: - _action.remove_dep(action) - - @property - def actions(self): - for action in self._actions: - yield action - - @property - def packages(self): - for action in self._actions: - yield action.pkg - - def run_action(self, action): - try: - action.run() - except ActionError, e: - logging.error("Action finished with an error: %s - %s" % (action, e)) - - def run(self): - # Download all packages. - self.ds.ts.download() - - # Create all the actions that need to be done. - self.populate() - - while True: - if not [a for a in self.actions]: - break - - for action in self.actions: - if action.deps: - #logging.debug("Skipping %s which cannot be run now." % action) - continue - - self.run_action(action) - self.remove_action(action) - diff --git a/po/POTFILES.in b/po/POTFILES.in index 1a1127a0c..48d38bb5d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,36 +1,43 @@ -pakfire/base.py -pakfire/builder.py -pakfire/cli.py -pakfire/config.py -pakfire/constants.py -pakfire/depsolve.py +pakfire/util.py +pakfire/api.py pakfire/distro.py -pakfire/downloader.py -pakfire/errors.py -pakfire/i18n.py -pakfire/__init__.py -pakfire/logger.py +pakfire/packages/util.py pakfire/packages/base.py -pakfire/packages/binary.py -pakfire/packages/file.py -pakfire/packages/__init__.py -pakfire/packages/installed.py -pakfire/packages/listing.py pakfire/packages/make.py -pakfire/packages/packager.py +pakfire/packages/__init__.py pakfire/packages/source.py -pakfire/packages/util.py +pakfire/packages/packager.py +pakfire/packages/file.py +pakfire/packages/installed.py pakfire/packages/virtual.py +pakfire/packages/listing.py +pakfire/packages/binary.py +pakfire/chroot.py +pakfire/downloader.py +pakfire/compress.py +pakfire/base.py +pakfire/i18n.py +pakfire/__init__.py +pakfire/server/base.py +pakfire/server/slave.py +pakfire/server/__init__.py +pakfire/server/master.py pakfire/repository/base.py -pakfire/repository/cache.py pakfire/repository/database.py -pakfire/repository/index.py pakfire/repository/__init__.py -pakfire/repository/installed.py +pakfire/repository/solver.py +pakfire/repository/transaction.py pakfire/repository/local.py -pakfire/repository/metadata.py -pakfire/repository/oddments.py pakfire/repository/remote.py -pakfire/transaction.py -pakfire/util.py -scripts/pakfire +pakfire/repository/oddments.py +pakfire/repository/metadata.py +pakfire/repository/installed.py +pakfire/repository/actions.py +pakfire/repository/cache.py +pakfire/repository/index.py +pakfire/config.py +pakfire/logger.py +pakfire/builder.py +pakfire/cli.py +pakfire/errors.py +pakfire/constants.py diff --git a/po/pakfire.pot b/po/pakfire.pot index b16a365c5..a2bb874fe 100644 --- a/po/pakfire.pot +++ b/po/pakfire.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-04-23 12:44+0200\n" +"POT-Creation-Date: 2011-04-29 21:17+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,11 +17,152 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: ../pakfire/base.py:106 ../pakfire/base.py:137 -msgid "Is this okay?" +#: ../pakfire/packages/base.py:67 +msgid "Name" +msgstr "" + +#: ../pakfire/packages/base.py:68 ../pakfire/repository/transaction.py:278 +msgid "Arch" msgstr "" -#: ../pakfire/builder.py:208 +#: ../pakfire/packages/base.py:69 ../pakfire/repository/transaction.py:278 +msgid "Version" +msgstr "" + +#: ../pakfire/packages/base.py:70 +msgid "Release" +msgstr "" + +#: ../pakfire/packages/base.py:71 ../pakfire/repository/transaction.py:278 +msgid "Size" +msgstr "" + +#: ../pakfire/packages/base.py:72 +msgid "Repo" +msgstr "" + +#: ../pakfire/packages/base.py:73 +msgid "Summary" +msgstr "" + +#: ../pakfire/packages/base.py:74 +msgid "Groups" +msgstr "" + +#: ../pakfire/packages/base.py:75 +msgid "URL" +msgstr "" + +#: ../pakfire/packages/base.py:76 +msgid "License" +msgstr "" + +#: ../pakfire/packages/base.py:79 +msgid "Description" +msgstr "" + +#: ../pakfire/packages/base.py:85 +msgid "UUID" +msgstr "" + +#: ../pakfire/packages/base.py:86 +msgid "Build ID" +msgstr "" + +#: ../pakfire/packages/base.py:87 +msgid "Build date" +msgstr "" + +#: ../pakfire/packages/base.py:88 +msgid "Build host" +msgstr "" + +#: ../pakfire/packages/base.py:90 +msgid "Provides" +msgstr "" + +#: ../pakfire/packages/base.py:95 +msgid "Requires" +msgstr "" + +#: ../pakfire/repository/solver.py:66 +#, python-format +msgid "Loading %s" +msgstr "" + +#: ../pakfire/repository/transaction.py:107 +#, python-format +msgid "Cleanup: %s" +msgstr "" + +#: ../pakfire/repository/transaction.py:146 +#, python-format +msgid "Installing: %s" +msgstr "" + +#: ../pakfire/repository/transaction.py:155 +#, python-format +msgid "Updating: %s" +msgstr "" + +#: ../pakfire/repository/transaction.py:167 +#, python-format +msgid "Removing: %s" +msgstr "" + +#: ../pakfire/repository/transaction.py:278 +msgid "Package" +msgstr "" + +#: ../pakfire/repository/transaction.py:278 ../pakfire/cli.py:237 +msgid "Repository" +msgstr "" + +#: ../pakfire/repository/transaction.py:281 +msgid "Installing:" +msgstr "" + +#: ../pakfire/repository/transaction.py:282 +msgid "Updating:" +msgstr "" + +#: ../pakfire/repository/transaction.py:283 +msgid "Removing:" +msgstr "" + +#: ../pakfire/repository/transaction.py:285 +msgid "Transaction Summary" +msgstr "" + +#: ../pakfire/repository/transaction.py:291 +msgid "Install" +msgstr "" + +#: ../pakfire/repository/transaction.py:291 +#: ../pakfire/repository/transaction.py:294 +#: ../pakfire/repository/transaction.py:297 +msgid "Package(s)" +msgstr "" + +#: ../pakfire/repository/transaction.py:294 +msgid "Updates" +msgstr "" + +#: ../pakfire/repository/transaction.py:297 +msgid "Remove" +msgstr "" + +#: ../pakfire/repository/transaction.py:303 +#, python-format +msgid "Total download size: %s" +msgstr "" + +#: ../pakfire/repository/index.py:335 +#, python-format +msgid "%s: package database" +msgstr "" + +#: ../pakfire/builder.py:254 #, python-format msgid "Extracting: %s (source)" msgstr "" @@ -123,10 +264,6 @@ msgstr "" msgid "List all currently enabled repositories." msgstr "" -#: ../pakfire/cli.py:237 ../pakfire/depsolve.py:229 -msgid "Repository" -msgstr "" - #: ../pakfire/cli.py:237 msgid "Enabled" msgstr "" @@ -218,150 +355,3 @@ msgstr "" #: ../pakfire/cli.py:498 msgid "Send a keepalive to the server." msgstr "" - -#: ../pakfire/depsolve.py:229 -msgid "Package" -msgstr "" - -#: ../pakfire/depsolve.py:229 ../pakfire/packages/base.py:72 -msgid "Arch" -msgstr "" - -#: ../pakfire/depsolve.py:229 ../pakfire/packages/base.py:73 -msgid "Version" -msgstr "" - -#: ../pakfire/depsolve.py:229 ../pakfire/packages/base.py:75 -msgid "Size" -msgstr "" - -#: ../pakfire/depsolve.py:232 -msgid "Installing:" -msgstr "" - -#: ../pakfire/depsolve.py:233 -msgid "Installing for dependencies:" -msgstr "" - -#: ../pakfire/depsolve.py:234 -msgid "Updating:" -msgstr "" - -#: ../pakfire/depsolve.py:235 -msgid "Updating for dependencies:" -msgstr "" - -#: ../pakfire/depsolve.py:236 -msgid "Removing:" -msgstr "" - -#: ../pakfire/depsolve.py:237 -msgid "Removing for dependencies:" -msgstr "" - -#: ../pakfire/depsolve.py:239 -msgid "Transaction Summary" -msgstr "" - -#: ../pakfire/depsolve.py:245 -msgid "Install" -msgstr "" - -#: ../pakfire/depsolve.py:246 ../pakfire/depsolve.py:250 -#: ../pakfire/depsolve.py:254 -msgid "Package(s)" -msgstr "" - -#: ../pakfire/depsolve.py:249 -msgid "Updates" -msgstr "" - -#: ../pakfire/depsolve.py:253 -msgid "Remove" -msgstr "" - -#: ../pakfire/depsolve.py:260 -#, python-format -msgid "Total download size: %s" -msgstr "" - -#: ../pakfire/packages/base.py:71 -msgid "Name" -msgstr "" - -#: ../pakfire/packages/base.py:74 -msgid "Release" -msgstr "" - -#: ../pakfire/packages/base.py:76 -msgid "Repo" -msgstr "" - -#: ../pakfire/packages/base.py:77 -msgid "Summary" -msgstr "" - -#: ../pakfire/packages/base.py:78 -msgid "Groups" -msgstr "" - -#: ../pakfire/packages/base.py:79 -msgid "URL" -msgstr "" - -#: ../pakfire/packages/base.py:80 -msgid "License" -msgstr "" - -#: ../pakfire/packages/base.py:83 -msgid "Description" -msgstr "" - -#: ../pakfire/packages/base.py:89 -msgid "UUID" -msgstr "" - -#: ../pakfire/packages/base.py:90 -msgid "Build ID" -msgstr "" - -#: ../pakfire/packages/base.py:91 -msgid "Build date" -msgstr "" - -#: ../pakfire/packages/base.py:92 -msgid "Build host" -msgstr "" - -#: ../pakfire/packages/base.py:94 -msgid "Provides" -msgstr "" - -#: ../pakfire/packages/base.py:99 -msgid "Requires" -msgstr "" - -#: ../pakfire/repository/index.py:310 -#, python-format -msgid "%s: package database" -msgstr "" - -#: ../pakfire/transaction.py:107 -#, python-format -msgid "Cleanup: %s" -msgstr "" - -#: ../pakfire/transaction.py:144 -#, python-format -msgid "Installing: %s" -msgstr "" - -#: ../pakfire/transaction.py:149 -#, python-format -msgid "Updating: %s" -msgstr "" - -#: ../pakfire/transaction.py:159 -#, python-format -msgid "Removing: %s" -msgstr "" -- 2.39.5