diff --git a/dnf/base.py b/dnf/base.py index ec41ab0..8c86acb 100644 --- a/dnf/base.py +++ b/dnf/base.py @@ -28,12 +28,12 @@ import argparse import dnf import libdnf.transaction -from copy import deepcopy from dnf.comps import CompsQuery from dnf.i18n import _, P_, ucd from dnf.util import _parse_specs from dnf.db.history import SwdbInterface from dnf.yum import misc +from functools import reduce try: from collections.abc import Sequence except ImportError: @@ -545,7 +545,7 @@ class Base(object): if self.conf.ignorearch: self._rpm_probfilter.add(rpm.RPMPROB_FILTER_IGNOREARCH) - probfilter = functools.reduce(operator.or_, self._rpm_probfilter, 0) + probfilter = reduce(operator.or_, self._rpm_probfilter, 0) self._priv_ts.setProbFilter(probfilter) return self._priv_ts @@ -886,15 +886,6 @@ class Base(object): self._plugins.unload_removed_plugins(self.transaction) self._plugins.run_transaction() - # log post transaction summary - def _pto_callback(action, tsis): - msgs = [] - for tsi in tsis: - msgs.append('{}: {}'.format(action, str(tsi))) - return msgs - for msg in dnf.util._post_transaction_output(self, self.transaction, _pto_callback): - logger.debug(msg) - return tid def _trans_error_summary(self, errstring): @@ -1316,7 +1307,7 @@ class Base(object): if patterns is None or len(patterns) == 0: return list_fn(None) yghs = map(list_fn, patterns) - return functools.reduce(lambda a, b: a.merge_lists(b), yghs) + return reduce(lambda a, b: a.merge_lists(b), yghs) def _list_pattern(self, pkgnarrow, pattern, showdups, ignore_case, reponame=None): @@ -2586,37 +2577,6 @@ class Base(object): """ self._logging._setup_from_dnf_conf(self.conf, file_loggers_only=True) - def _skipped_packages(self, report_problems, transaction): - """returns set of conflicting packages and set of packages with broken dependency that would - be additionally installed when --best and --allowerasing""" - if self._goal.actions & (hawkey.INSTALL | hawkey.UPGRADE | hawkey.UPGRADE_ALL): - best = True - else: - best = False - ng = deepcopy(self._goal) - params = {"allow_uninstall": self._allow_erasing, - "force_best": best, - "ignore_weak": True} - ret = ng.run(**params) - if not ret and report_problems: - msg = dnf.util._format_resolve_problems(ng.problem_rules()) - logger.warning(msg) - problem_conflicts = set(ng.problem_conflicts(available=True)) - problem_dependency = set(ng.problem_broken_dependency(available=True)) - problem_conflicts - - def _nevra(item): - return hawkey.NEVRA(name=item.name, epoch=item.epoch, version=item.version, - release=item.release, arch=item.arch) - - # Sometimes, pkg is not in transaction item, therefore, comparing by nevra - transaction_nevras = [_nevra(tsi) for tsi in transaction] - skipped_conflicts = set( - [pkg for pkg in problem_conflicts if _nevra(pkg) not in transaction_nevras]) - skipped_dependency = set( - [pkg for pkg in problem_dependency if _nevra(pkg) not in transaction_nevras]) - - return skipped_conflicts, skipped_dependency - def _msg_installed(pkg): name = ucd(pkg) diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py index cd720a9..b5f7bca 100644 --- a/dnf/cli/cli.py +++ b/dnf/cli/cli.py @@ -251,12 +251,8 @@ class BaseCli(dnf.Base): trans = None if trans: - # the post transaction summary is already written to log during - # Base.do_transaction() so here only print the messages to the - # user arranged in columns - print() - print('\n'.join(self.output.post_transaction_output(trans))) - print() + msg = self.output.post_transaction_output(trans) + logger.info(msg) for tsi in trans: if tsi.state == libdnf.transaction.TransactionItemState_ERROR: raise dnf.exceptions.Error(_('Transaction failed')) @@ -978,8 +974,6 @@ class Cli(object): self.base.configure_plugins() - self.base.conf._configure_from_options(opts) - self.command.configure() if self.base.conf.destdir: diff --git a/dnf/cli/output.py b/dnf/cli/output.py index 6d729b6..51d6829 100644 --- a/dnf/cli/output.py +++ b/dnf/cli/output.py @@ -21,7 +21,9 @@ from __future__ import absolute_import from __future__ import print_function from __future__ import unicode_literals +from copy import deepcopy import fnmatch +import functools import hawkey import itertools import libdnf.transaction @@ -35,7 +37,7 @@ import time from dnf.cli.format import format_number, format_time from dnf.i18n import _, C_, P_, ucd, fill_exact_width, textwrap_fill, exact_width, select_short_long from dnf.pycomp import xrange, basestring, long, unicode, sys_maxsize -from dnf.yum.rpmtrans import TransactionDisplay +from dnf.yum.rpmtrans import LoggingTransactionDisplay from dnf.db.history import MergedTransactionWrapper import dnf.base import dnf.callback @@ -50,6 +52,50 @@ import dnf.yum.misc logger = logging.getLogger('dnf') +def _make_lists(transaction, goal): + b = dnf.util.Bunch({ + 'downgraded': [], + 'erased': [], + 'erased_clean': [], + 'erased_dep': [], + 'installed': [], + 'installed_group': [], + 'installed_dep': [], + 'installed_weak': [], + 'reinstalled': [], + 'upgraded': [], + 'failed': [], + }) + + for tsi in transaction: + if tsi.state == libdnf.transaction.TransactionItemState_ERROR: + b.failed.append(tsi) + elif tsi.action == libdnf.transaction.TransactionItemAction_DOWNGRADE: + b.downgraded.append(tsi) + elif tsi.action == libdnf.transaction.TransactionItemAction_INSTALL: + if tsi.reason == libdnf.transaction.TransactionItemReason_GROUP: + b.installed_group.append(tsi) + elif tsi.reason == libdnf.transaction.TransactionItemReason_DEPENDENCY: + b.installed_dep.append(tsi) + elif tsi.reason == libdnf.transaction.TransactionItemReason_WEAK_DEPENDENCY: + b.installed_weak.append(tsi) + else: + # TransactionItemReason_USER + b.installed.append(tsi) + elif tsi.action == libdnf.transaction.TransactionItemAction_REINSTALL: + b.reinstalled.append(tsi) + elif tsi.action == libdnf.transaction.TransactionItemAction_REMOVE: + if tsi.reason == libdnf.transaction.TransactionItemReason_CLEAN: + b.erased_clean.append(tsi) + elif tsi.reason == libdnf.transaction.TransactionItemReason_DEPENDENCY: + b.erased_dep.append(tsi) + else: + b.erased.append(tsi) + elif tsi.action == libdnf.transaction.TransactionItemAction_UPGRADE: + b.upgraded.append(tsi) + + return b + def _spread_in_columns(cols_count, label, lst): left = itertools.chain((label,), itertools.repeat('')) @@ -1010,6 +1056,37 @@ class Output(object): out[0:0] = self._banner(col_data, (_('Group'), _('Packages'), '', '')) return '\n'.join(out) + def _skipped_packages(self, report_problems, transaction): + """returns set of conflicting packages and set of packages with broken dependency that would + be additionally installed when --best and --allowerasing""" + if self.base._goal.actions & (hawkey.INSTALL | hawkey.UPGRADE | hawkey.UPGRADE_ALL): + best = True + else: + best = False + ng = deepcopy(self.base._goal) + params = {"allow_uninstall": self.base._allow_erasing, + "force_best": best, + "ignore_weak": True} + ret = ng.run(**params) + if not ret and report_problems: + msg = dnf.util._format_resolve_problems(ng.problem_rules()) + logger.warning(msg) + problem_conflicts = set(ng.problem_conflicts(available=True)) + problem_dependency = set(ng.problem_broken_dependency(available=True)) - problem_conflicts + + def _nevra(item): + return hawkey.NEVRA(name=item.name, epoch=item.epoch, version=item.version, + release=item.release, arch=item.arch) + + # Sometimes, pkg is not in transaction item, therefore, comparing by nevra + transaction_nevras = [_nevra(tsi) for tsi in transaction] + skipped_conflicts = set( + [pkg for pkg in problem_conflicts if _nevra(pkg) not in transaction_nevras]) + skipped_dependency = set( + [pkg for pkg in problem_dependency if _nevra(pkg) not in transaction_nevras]) + + return skipped_conflicts, skipped_dependency + def list_transaction(self, transaction, total_width=None): """Return a string representation of the transaction in an easy-to-read format. @@ -1024,7 +1101,7 @@ class Output(object): # in order to display module changes when RPM transaction is empty transaction = [] - list_bunch = dnf.util._make_lists(transaction) + list_bunch = _make_lists(transaction, self.base._goal) pkglist_lines = [] data = {'n' : {}, 'v' : {}, 'r' : {}} a_wid = 0 # Arch can't get "that big" ... so always use the max. @@ -1193,7 +1270,7 @@ class Output(object): # show skipped conflicting packages if not self.conf.best and self.base._goal.actions & forward_actions: lines = [] - skipped_conflicts, skipped_broken = self.base._skipped_packages( + skipped_conflicts, skipped_broken = self._skipped_packages( report_problems=True, transaction=transaction) skipped_broken = dict((str(pkg), pkg) for pkg in skipped_broken) for pkg in sorted(skipped_conflicts): @@ -1358,8 +1435,13 @@ Transaction Summary max_msg_count, count, msg_pkgs)) return ''.join(out) + def post_transaction_output(self, transaction): + """Returns a human-readable summary of the results of the + transaction. - def _pto_callback(self, action, tsis): + :return: a string containing a human-readable summary of the + results of the transaction + """ # Works a bit like calcColumns, but we never overflow a column we just # have a dynamic number of columns. def _fits_in_cols(msgs, num): @@ -1389,32 +1471,60 @@ Transaction Summary col_lens[col] *= -1 return col_lens - if not tsis: - return '' - out = [] - msgs = [] - out.append('{}:'.format(action)) - for tsi in tsis: - msgs.append(str(tsi)) - for num in (8, 7, 6, 5, 4, 3, 2): - cols = _fits_in_cols(msgs, num) - if cols: - break - if not cols: - cols = [-(self.term.columns - 2)] - while msgs: - current_msgs = msgs[:len(cols)] - out.append(' {}'.format(self.fmtColumns(zip(current_msgs, cols)))) - msgs = msgs[len(cols):] - return out - + def _tsi_or_pkg_nevra_cmp(item1, item2): + """Compares two transaction items or packages by nevra. + Used as a fallback when tsi does not contain package object. + """ + ret = (item1.name > item2.name) - (item1.name < item2.name) + if ret != 0: + return ret + nevra1 = hawkey.NEVRA(name=item1.name, epoch=item1.epoch, version=item1.version, + release=item1.release, arch=item1.arch) + nevra2 = hawkey.NEVRA(name=item2.name, epoch=item2.epoch, version=item2.version, + release=item2.release, arch=item2.arch) + ret = nevra1.evr_cmp(nevra2, self.sack) + if ret != 0: + return ret + return (item1.arch > item2.arch) - (item1.arch < item2.arch) + + out = '' + list_bunch = _make_lists(transaction, self.base._goal) + + skipped_conflicts, skipped_broken = self._skipped_packages( + report_problems=False, transaction=transaction) + skipped = skipped_conflicts.union(skipped_broken) + + for (action, tsis) in [(_('Upgraded'), list_bunch.upgraded), + (_('Downgraded'), list_bunch.downgraded), + (_('Installed'), list_bunch.installed + + list_bunch.installed_group + + list_bunch.installed_weak + + list_bunch.installed_dep), + (_('Reinstalled'), list_bunch.reinstalled), + (_('Skipped'), skipped), + (_('Removed'), list_bunch.erased + + list_bunch.erased_dep + + list_bunch.erased_clean), + (_('Failed'), list_bunch.failed)]: + if not tsis: + continue + msgs = [] + out += '\n%s:\n' % action + for tsi in sorted(tsis, key=functools.cmp_to_key(_tsi_or_pkg_nevra_cmp)): + msgs.append(str(tsi)) + for num in (8, 7, 6, 5, 4, 3, 2): + cols = _fits_in_cols(msgs, num) + if cols: + break + if not cols: + cols = [-(self.term.columns - 2)] + while msgs: + current_msgs = msgs[:len(cols)] + out += ' ' + out += self.fmtColumns(zip(current_msgs, cols), end=u'\n') + msgs = msgs[len(cols):] - def post_transaction_output(self, transaction): - """ - Return a human-readable summary of the transaction. Packages in sections - are arranged to columns. - """ - return dnf.util._post_transaction_output(self.base, transaction, self._pto_callback) + return out def setup_progress_callbacks(self): """Set up the progress callbacks and various @@ -1877,6 +1987,10 @@ Transaction Summary class DepSolveProgressCallBack(dnf.callback.Depsolve): """Provides text output callback functions for Dependency Solver callback.""" + def __init__(self): + """requires yum-cli log and errorlog functions as arguments""" + self.loops = 0 + def pkg_added(self, pkg, mode): """Print information about a package being added to the transaction set. @@ -1923,6 +2037,7 @@ class DepSolveProgressCallBack(dnf.callback.Depsolve): process. """ logger.debug(_('--> Starting dependency resolution')) + self.loops += 1 def end(self): """Output a message stating that dependency resolution has finished.""" @@ -1956,7 +2071,7 @@ class CliKeyImport(dnf.callback.KeyImport): return self.output.userconfirm() -class CliTransactionDisplay(TransactionDisplay): +class CliTransactionDisplay(LoggingTransactionDisplay): """A YUM specific callback class for RPM operations.""" width = property(lambda self: dnf.cli.term._term_width()) @@ -1978,7 +2093,7 @@ class CliTransactionDisplay(TransactionDisplay): :param package: the package involved in the event :param action: the type of action that is taking place. Valid values are given by - :func:`rpmtrans.TransactionDisplay.action.keys()` + :func:`rpmtrans.LoggingTransactionDisplay.action.keys()` :param ti_done: a number representing the amount of work already done in the current transaction :param ti_total: a number representing the total amount of work @@ -2029,6 +2144,20 @@ class CliTransactionDisplay(TransactionDisplay): if ti_done == ti_total: print(" ") + def filelog(self, package, action): + pass + + def error(self, message): + pass + + def scriptout(self, msgs): + """Print messages originating from a package script. + + :param msgs: the messages coming from the script + """ + if msgs: + self.rpm_logger.info(ucd(msgs)) + def _makefmt(self, percent, ts_done, ts_total, progress=True, pkgname=None, wid1=15): l = len(str(ts_total)) diff --git a/dnf/util.py b/dnf/util.py index 0beb044..8cf3627 100644 --- a/dnf/util.py +++ b/dnf/util.py @@ -24,14 +24,13 @@ from __future__ import unicode_literals from .pycomp import PY3, basestring from dnf.i18n import _, ucd +from functools import reduce import argparse import dnf import dnf.callback import dnf.const import dnf.pycomp import errno -import functools -import hawkey import itertools import locale import logging @@ -42,7 +41,6 @@ import sys import tempfile import time import libdnf.repo -import libdnf.transaction logger = logging.getLogger('dnf') @@ -197,7 +195,7 @@ def group_by_filter(fn, iterable): def splitter(acc, item): acc[not bool(fn(item))].append(item) return acc - return functools.reduce(splitter, iterable, ([], [])) + return reduce(splitter, iterable, ([], [])) def insert_if(item, iterable, condition): """Insert an item into an iterable by a condition.""" @@ -506,99 +504,3 @@ class MultiCallList(list): def setter(item): setattr(item, what, val) return list(map(setter, self)) - - -def _make_lists(transaction): - b = Bunch({ - 'downgraded': [], - 'erased': [], - 'erased_clean': [], - 'erased_dep': [], - 'installed': [], - 'installed_group': [], - 'installed_dep': [], - 'installed_weak': [], - 'reinstalled': [], - 'upgraded': [], - 'failed': [], - }) - - for tsi in transaction: - if tsi.state == libdnf.transaction.TransactionItemState_ERROR: - b.failed.append(tsi) - elif tsi.action == libdnf.transaction.TransactionItemAction_DOWNGRADE: - b.downgraded.append(tsi) - elif tsi.action == libdnf.transaction.TransactionItemAction_INSTALL: - if tsi.reason == libdnf.transaction.TransactionItemReason_GROUP: - b.installed_group.append(tsi) - elif tsi.reason == libdnf.transaction.TransactionItemReason_DEPENDENCY: - b.installed_dep.append(tsi) - elif tsi.reason == libdnf.transaction.TransactionItemReason_WEAK_DEPENDENCY: - b.installed_weak.append(tsi) - else: - # TransactionItemReason_USER - b.installed.append(tsi) - elif tsi.action == libdnf.transaction.TransactionItemAction_REINSTALL: - b.reinstalled.append(tsi) - elif tsi.action == libdnf.transaction.TransactionItemAction_REMOVE: - if tsi.reason == libdnf.transaction.TransactionItemReason_CLEAN: - b.erased_clean.append(tsi) - elif tsi.reason == libdnf.transaction.TransactionItemReason_DEPENDENCY: - b.erased_dep.append(tsi) - else: - b.erased.append(tsi) - elif tsi.action == libdnf.transaction.TransactionItemAction_UPGRADE: - b.upgraded.append(tsi) - - return b - - -def _post_transaction_output(base, transaction, action_callback): - """Returns a human-readable summary of the results of the - transaction. - - :param action_callback: function generating output for specific action. It - takes two parameters - action as a string and list of affected packages for - this action - :return: a list of lines containing a human-readable summary of the - results of the transaction - """ - def _tsi_or_pkg_nevra_cmp(item1, item2): - """Compares two transaction items or packages by nevra. - Used as a fallback when tsi does not contain package object. - """ - ret = (item1.name > item2.name) - (item1.name < item2.name) - if ret != 0: - return ret - nevra1 = hawkey.NEVRA(name=item1.name, epoch=item1.epoch, version=item1.version, - release=item1.release, arch=item1.arch) - nevra2 = hawkey.NEVRA(name=item2.name, epoch=item2.epoch, version=item2.version, - release=item2.release, arch=item2.arch) - ret = nevra1.evr_cmp(nevra2, base.sack) - if ret != 0: - return ret - return (item1.arch > item2.arch) - (item1.arch < item2.arch) - - list_bunch = dnf.util._make_lists(transaction) - - skipped_conflicts, skipped_broken = base._skipped_packages( - report_problems=False, transaction=transaction) - skipped = skipped_conflicts.union(skipped_broken) - - out = [] - for (action, tsis) in [(_('Upgraded'), list_bunch.upgraded), - (_('Downgraded'), list_bunch.downgraded), - (_('Installed'), list_bunch.installed + - list_bunch.installed_group + - list_bunch.installed_weak + - list_bunch.installed_dep), - (_('Reinstalled'), list_bunch.reinstalled), - (_('Skipped'), skipped), - (_('Removed'), list_bunch.erased + - list_bunch.erased_dep + - list_bunch.erased_clean), - (_('Failed'), list_bunch.failed)]: - out.extend(action_callback( - action, sorted(tsis, key=functools.cmp_to_key(_tsi_or_pkg_nevra_cmp)))) - - return out diff --git a/dnf/yum/rpmtrans.py b/dnf/yum/rpmtrans.py index 51fa921..447639a 100644 --- a/dnf/yum/rpmtrans.py +++ b/dnf/yum/rpmtrans.py @@ -113,10 +113,7 @@ class TransactionDisplay(object): pass def scriptout(self, msgs): - """Hook for reporting an rpm scriptlet output. - - :param msgs: the scriptlet output - """ + """msgs is the messages that were output (if any).""" pass def error(self, message): @@ -143,7 +140,7 @@ class ErrorTransactionDisplay(TransactionDisplay): dnf.util._terminal_messenger('print', message, sys.stderr) -class LoggingTransactionDisplay(TransactionDisplay): +class LoggingTransactionDisplay(ErrorTransactionDisplay): ''' Base class for a RPMTransaction display callback class ''' @@ -159,10 +156,6 @@ class LoggingTransactionDisplay(TransactionDisplay): msg = '%s: %s' % (action_str, package) self.rpm_logger.log(dnf.logging.SUBDEBUG, msg) - def scriptout(self, msgs): - if msgs: - self.rpm_logger.info(ucd(msgs)) - class RPMTransaction(object): def __init__(self, base, test=False, displays=()): diff --git a/tests/api/test_dnf_base.py b/tests/api/test_dnf_base.py index ca71b75..b1cf49f 100644 --- a/tests/api/test_dnf_base.py +++ b/tests/api/test_dnf_base.py @@ -14,7 +14,6 @@ from .common import TOUR_4_4 class DnfBaseApiTest(TestCase): def setUp(self): self.base = dnf.Base(dnf.conf.Conf()) - self.base.conf.persistdir = "/tmp/tests" def tearDown(self): self.base.close() diff --git a/tests/api/test_dnf_db_group.py b/tests/api/test_dnf_db_group.py index e1828cb..447fd12 100644 --- a/tests/api/test_dnf_db_group.py +++ b/tests/api/test_dnf_db_group.py @@ -12,7 +12,6 @@ from .common import TestCase class DnfRPMTransactionApiTest(TestCase): def setUp(self): self.base = dnf.Base(dnf.conf.Conf()) - self.base.conf.persistdir = "/tmp/tests" self.base.fill_sack(False, False) self.base.resolve() self.rpmTrans = self.base.transaction