#!/usr/bin/python -t # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Copyright 2005 Duke University """ Dependency resolution module for yum. """ import os.path import types import logging import rpmUtils.transaction import rpmUtils.miscutils from rpmUtils.arch import archDifference, canCoinstall import misc from misc import unique, version_tuple_to_string from transactioninfo import TransactionMember import rpm from packageSack import ListPackageSack from packages import PackageEVR from constants import * import logginglevels import Errors import warnings warnings.simplefilter("ignore", Errors.YumFutureDeprecationWarning) from weakref import proxy as weakref from yum import _, _rpm_ver_atleast try: assert max(2, 4) == 4 except: # Python-2.4.x doesn't have min/max ... *sigh* def min(x, *args): for y in args: if x > y: x = y return x def max(x, *args): for y in args: if x < y: x = y return x flags = {"GT": rpm.RPMSENSE_GREATER, "GE": rpm.RPMSENSE_EQUAL | rpm.RPMSENSE_GREATER, "LT": rpm.RPMSENSE_LESS, "LE": rpm.RPMSENSE_LESS | rpm.RPMSENSE_EQUAL, "EQ": rpm.RPMSENSE_EQUAL, None: 0 } _rflags = {} for f in flags: _rflags[flags[f]] = f class _wrap_ayum_getPkgSack: """ This is a wrapper for calling YumBase.pkgSack because otherwise we take a real reference through the bound method and that is d00m (this applies to YumBase and TransactionInfo, hence why we have a separate class). """ def __init__(self, ayum): self.ayum = weakref(ayum) def __call__(self): return self.ayum.pkgSack class _wrap_ayum_install: """ This is a wrapper for calling YumBase.install because otherwise we take a real reference through the bound method and that is d00m (this applies to YumBase and TransactionInfo, hence why we have a separate class). """ def __init__(self, ayum): self.ayum = weakref(ayum) def __call__(self, *args, **kwargs): return self.ayum.install(*args, **kwargs) class _wrap_ayum_remove: """ This is a wrapper for calling YumBase.remove because otherwise we take a real reference through the bound method and that is d00m (this applies to YumBase and TransactionInfo, hence why we have a separate class). """ def __init__(self, ayum): self.ayum = weakref(ayum) def __call__(self, *args, **kwargs): return self.ayum.remove(*args, **kwargs) class _wrap_ayum_update: """ This is a wrapper for calling YumBase.update because otherwise we take a real reference through the bound method and that is d00m (this applies to YumBase and TransactionInfo, hence why we have a separate class). """ def __init__(self, ayum): self.ayum = weakref(ayum) def __call__(self, *args, **kwargs): return self.ayum.update(*args, **kwargs) class Depsolve(object): """A class for resolving dependencies.""" def __init__(self): self._ts = None self._tsInfo = None self.dsCallback = None # Callback-style switch, default to legacy (hdr, file) mode self.use_txmbr_in_callback = False self.logger = logging.getLogger("yum.Depsolve") self.verbose_logger = logging.getLogger("yum.verbose.Depsolve") self.path = [] self.loops = [] self.installedFileRequires = None self.installedUnresolvedFileRequires = None self._missing_requires = False def doTsSetup(self): """Sets up the transaction set before it is used.""" warnings.warn(_('doTsSetup() will go away in a future version of Yum.\n'), Errors.YumFutureDeprecationWarning, stacklevel=2) return self._getTs() def _getTs(self, remove_only=False): """setup all the transaction set storage items we'll need This can't happen in __init__ b/c we don't know our installroot yet""" if self._tsInfo != None and self._ts != None: if not remove_only and self._tsInfo.pkgSack is None: pkgSackCtor = _wrap_ayum_getPkgSack(self) self._tsInfo.setDatabases(self.rpmdb, None, pkgSackCtor) return if not self.conf.installroot: raise Errors.YumBaseError, _('Setting up TransactionSets before config class is up') self._getTsInfo(remove_only) self.initActionTs() def _getTsInfo(self, remove_only=False): """ remove_only param. says if we are going to do _only_ remove(s) in the transaction. If so we don't need to setup the remote repos. """ if self._tsInfo is None: self._tsInfo = self._transactionDataFactory() if remove_only: pkgSack = None pkgSackCtor = None else: # Don't instant setup, or things like localinstall are screwed. pkgSack = None pkgSackCtor = _wrap_ayum_getPkgSack(self) self._tsInfo.setDatabases(self.rpmdb, pkgSack, pkgSackCtor) self._tsInfo.installonlypkgs = self.conf.installonlypkgs # this kinda sucks # this REALLY sucks, sadly (needed for group conditionals) self._tsInfo.install_method = _wrap_ayum_install(self) self._tsInfo.update_method = _wrap_ayum_update(self) self._tsInfo.remove_method = _wrap_ayum_remove(self) return self._tsInfo def _setTsInfo(self, value): self._tsInfo = value def _delTsInfo(self): self._tsInfo = None def _getActionTs(self): if not self._ts: self.initActionTs() return self._ts def initActionTs(self): """Set up the transaction set that will be used for all the work.""" # LOL, override rpm transaction macro. # Must be done before rpmtsCreate() if self.conf.override_install_langs: old_install_langs = rpm.expandMacro("%_install_langs") rpm.expandMacro("%define _install_langs " + self.conf.override_install_langs) self._ts = rpmUtils.transaction.TransactionWrapper(self.conf.installroot) ts_flags_to_rpm = { 'noscripts': rpm.RPMTRANS_FLAG_NOSCRIPTS, 'notriggers': rpm.RPMTRANS_FLAG_NOTRIGGERS, 'nodocs': rpm.RPMTRANS_FLAG_NODOCS, 'test': rpm.RPMTRANS_FLAG_TEST, 'justdb': rpm.RPMTRANS_FLAG_JUSTDB, 'repackage': rpm.RPMTRANS_FLAG_REPACKAGE} # This is only in newer rpm.org releases if hasattr(rpm, 'RPMTRANS_FLAG_NOCONTEXTS'): ts_flags_to_rpm['nocontexts'] = rpm.RPMTRANS_FLAG_NOCONTEXTS self._ts.setFlags(0) # reset everything. for flag in self.conf.tsflags: if flag in ts_flags_to_rpm: self._ts.addTsFlag(ts_flags_to_rpm[flag]) else: self.logger.critical(_('Invalid tsflag in config file: %s'), flag) if not self.conf.diskspacecheck: self._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE, rpm.RPMPROB_FILTER_DISKNODES) probfilter = 0 for flag in self.tsInfo.probFilterFlags: probfilter |= flag self._ts.setProbFilter(probfilter) def whatProvides(self, name, flags, version): """Search the packageSacks for what provides the specified feature or file. :param name: a string specifying the file or feature to search for :param flags: flags related to the search :param version: the version to search for :return: a :class:`ListPackagaSack` containing the packages that match the arguments, and may be empty """ self.verbose_logger.log(logginglevels.DEBUG_1, _('Searching pkgSack for dep: %s'), name) defSack = ListPackageSack(self.pkgSack.searchProvides((name, flags, version))) return defSack def allowedMultipleInstalls(self, po): """Return whether the given package object can be installed multiple times with different versions. For example, this would be true of kernels and kernel modules. :param po: the package object that this function will determine whether can be install multiple times :return: a boolean specifying whether *po* can be installed multiple times """ iopkgs = set(self.conf.installonlypkgs) if po.name in iopkgs: return True for prov in po.provides_names: if prov in iopkgs: return True return False def populateTs(self, test=0, keepold=1): """Populate the transaction set. :param test: unused :param keepold: whether to keep old packages """ if self.dsCallback: self.dsCallback.transactionPopulation() ts_elem = {} if self.ts.ts is None: self.initActionTs() if keepold: for te in self.ts: epoch = te.E() if epoch is None: epoch = '0' pkginfo = (te.N(), te.A(), epoch, te.V(), te.R()) if te.Type() == 1: mode = 'i' elif te.Type() == 2: mode = 'e' ts_elem[(pkginfo, mode)] = 1 for txmbr in self.tsInfo.getMembers(): self.verbose_logger.log(logginglevels.DEBUG_3, _('Member: %s'), txmbr) if txmbr.ts_state in ['u', 'i']: if (txmbr.pkgtup, 'i') in ts_elem: continue rpmfile = txmbr.po.localPkg() if os.path.exists(rpmfile): hdr = txmbr.po.returnHeaderFromPackage() else: self.downloadHeader(txmbr.po) hdr = txmbr.po.returnLocalHeader() if txmbr.ts_state == 'u': if self.allowedMultipleInstalls(txmbr.po): self.verbose_logger.log(logginglevels.DEBUG_2, _('%s converted to install'), txmbr.po) txmbr.ts_state = 'i' txmbr.output_state = TS_INSTALL # New-style callback with just txmbr instead of full headers? if self.use_txmbr_in_callback: cbkey = txmbr else: cbkey = (hdr, rpmfile) self.ts.addInstall(hdr, cbkey, txmbr.ts_state) self.verbose_logger.log(logginglevels.DEBUG_1, _('Adding Package %s in mode %s'), txmbr.po, txmbr.ts_state) if self.dsCallback: dscb_ts_state = txmbr.ts_state if dscb_ts_state == 'u' and txmbr.downgrades: dscb_ts_state = 'd' self.dsCallback.pkgAdded(txmbr.pkgtup, dscb_ts_state) elif txmbr.ts_state in ['e']: if (txmbr.pkgtup, txmbr.ts_state) in ts_elem: continue self.ts.addErase(txmbr.po.idx) if self.dsCallback: if txmbr.downgraded_by: continue self.dsCallback.pkgAdded(txmbr.pkgtup, 'e') self.verbose_logger.log(logginglevels.DEBUG_1, _('Removing Package %s'), txmbr.po) def _dscb_procReq(self, po, niceformatneed): """ Call the callback for processing requires, call the nicest one available. """ if not self.dsCallback: return if hasattr(self.dsCallback, 'procReqPo'): self.dsCallback.procReqPo(po, niceformatneed) else: self.dsCallback.procReq(po.name, niceformatneed) def _processReq(self, po, requirement): """processes a Requires dep from the resolveDeps functions, returns a tuple of (CheckDeps, missingdep, conflicts, errors) the last item is an array of error messages""" errormsgs = [] needname, flags, needversion = requirement niceformatneed = rpmUtils.miscutils.formatRequire(needname, needversion, flags) self.verbose_logger.log(logginglevels.DEBUG_1, _('%s requires: %s'), po, niceformatneed) self._dscb_procReq(po, niceformatneed) try: if po.repo.id != "installed": CheckDeps, missingdep = self._requiringFromTransaction(po, requirement, errormsgs) else: CheckDeps, missingdep = self._requiringFromInstalled(po, requirement, errormsgs) # Check packages with problems if missingdep: self.po_with_problems.add((po,self._working_po,errormsgs[-1])) except Errors.DepError,e: # FIXME: This is a hack, it don't solve the problem # of tries to update to a package the have been removed from the # pkgSack because of dep problems. # The real solution is to remove the package from the updates, when # it is remove from the pkgSack self.po_with_problems.add((po,self._working_po,str(e))) CheckDeps = 1 missingdep = 0 return (CheckDeps, missingdep, errormsgs) @staticmethod def _prco_req_nfv2req(rn, rf, rv): return (rn, flags[rf], version_tuple_to_string(rv)) def _prco_req2req(self, req): return self._prco_req_nfv2req(req[0], req[1], req[2]) def _err_missing_requires(self, reqPo, reqTup): self._missing_requires = True if hasattr(self.dsCallback, 'format_missing_requires'): msg = self.dsCallback.format_missing_requires(reqPo, reqTup) if msg is not None: # PK return self.dsCallback.format_missing_requires(reqPo, reqTup) (needname, needflags, needversion) = reqTup ui_req = rpmUtils.miscutils.formatRequire(needname, needversion, needflags) return _('%s requires %s') % (reqPo, ui_req) def _requiringFromInstalled(self, requiringPo, requirement, errorlist): """processes the dependency resolution for a dep where the requiring package is installed""" checkdeps = 0 missingdep = 0 if self.tsInfo.getMembersWithState(requiringPo.pkgtup, TS_REMOVE_STATES): return checkdeps, missingdep name, arch, epoch, ver, rel = requiringPo.pkgtup needname, needflags, needversion = requirement niceformatneed = rpmUtils.miscutils.formatRequire(needname, needversion, needflags) # we must first find out why the requirement is no longer there # we must find out what provides/provided it from the rpmdb (if anything) # then check to see if that thing is being acted upon by the transaction set # if it is then we need to find out what is being done to it and act accordingly needmode = None # mode in the transaction of the needed pkg (if any) needpo = None providers = [] if (needname, needflags, needversion) in self.cheaterlookup: self.verbose_logger.log(logginglevels.DEBUG_2, _('Needed Require has already been looked up, cheating')) cheater_po = self.cheaterlookup[(needname, needflags, needversion)] providers = [cheater_po] elif self.rpmdb.contains(name=needname): txmbrs = self.tsInfo.matchNaevr(name=needname) for txmbr in txmbrs: providers.append(txmbr.po) else: self.verbose_logger.log(logginglevels.DEBUG_2, _('Needed Require is not a package name. Looking up: %s'), niceformatneed) providers = self.rpmdb.getProvides(needname, needflags, needversion) for inst_po in providers: self._working_po = inst_po # store the last provider inst_str = '%s.%s %s:%s-%s' % inst_po.pkgtup (i_n, i_a, i_e, i_v, i_r) = inst_po.pkgtup self.verbose_logger.log(logginglevels.DEBUG_2, _('Potential Provider: %s'), inst_str) thismode = self.tsInfo.getMode(name=i_n, arch=i_a, epoch=i_e, ver=i_v, rel=i_r) if thismode is None and i_n in self.conf.exactarchlist: # check for mode by the same name+arch thismode = self.tsInfo.getMode(name=i_n, arch=i_a) if thismode is None and i_n not in self.conf.exactarchlist: # check for mode by just the name thismode = self.tsInfo.getMode(name=i_n) # if this package is being obsoleted, it's just like if it's # being upgraded as far as checking for other providers if thismode is None: if filter(lambda x: x.obsoleted_by, self.tsInfo.matchNaevr(i_n, i_a, i_e, i_v, i_r)): thismode = 'u' if thismode is not None: needmode = thismode self.cheaterlookup[(needname, needflags, needversion)] = inst_po self.verbose_logger.log(logginglevels.DEBUG_2, _('Mode is %s for provider of %s: %s'), needmode, niceformatneed, inst_str) break self.verbose_logger.log(logginglevels.DEBUG_2, _('Mode for pkg providing %s: %s'), niceformatneed, needmode) if needmode in ['ud']: # the thing it needs is being updated or obsoleted away # try to update the requiring package in hopes that all this problem goes away :( self.verbose_logger.log(logginglevels.DEBUG_2, _('Trying to update %s to resolve dep'), requiringPo) # if the required pkg was updated, not obsoleted, then try to # only update the requiring po origobs = self.conf.obsoletes self.conf.obsoletes = 0 txmbrs = self.update(po=requiringPo, requiringPo=requiringPo) self.conf.obsoletes = origobs def _check_update_worked(txmbrs, obs=False): # Old code assumed that if there was an update, we were good: # if txmbrs: return True # ..however we have a problem when foo-1 and foo-2 both require # bar-1, and bar-2 is being installed. If the req. is identical # then we'll skip checking it in _checkInstall(), so we need to # check it here. for txmbr in txmbrs: if obs or txmbr.name == requiringPo.name: n,f,v = requirement creq = (n, _rflags[f], rpmUtils.miscutils.stringToVersion(v)) # If it's identical ... checkInstall will skip it. if creq not in txmbr.po.requires: return True return False if not _check_update_worked(txmbrs): txmbrs = self.update(po=requiringPo, requiringPo=requiringPo) if not _check_update_worked(txmbrs, obs=True): msg = self._err_missing_requires(requiringPo, requirement) self.verbose_logger.log(logginglevels.DEBUG_2, _('No update paths found for %s. Failure!'), requiringPo) return self._requiringFromTransaction(requiringPo, requirement, errorlist) checkdeps = 1 if needmode in ['od']: # the thing it needs is being updated or obsoleted away # try to update the requiring package in hopes that all this problem goes away :( self.verbose_logger.log(logginglevels.DEBUG_2, _('Trying to update %s to resolve dep'), requiringPo) txmbrs = self.update(po=requiringPo, requiringPo=requiringPo) fixed = False if not txmbrs: msg = self._err_missing_requires(requiringPo, requirement) self.verbose_logger.log(logginglevels.DEBUG_2, _('No update paths found for %s. Failure due to requirement: %s!'), requiringPo, msg) else: req_lookup = (needname, needflags, rpmUtils.miscutils.stringToVersion(needversion)) for txmbr in txmbrs: # This works for upgrades, and for obsoletes, on one side. # Also catches upgrades that don't work on the other. if txmbr.output_state in TS_INSTALL_STATES: if txmbr.po.checkPrco('requires', req_lookup): fixed = False break else: fixed = True if txmbrs and not fixed: msg = self._err_missing_requires(requiringPo, requirement) self.verbose_logger.log(logginglevels.DEBUG_2, _("Update for %s. Doesn't fix requirement: %s!"), requiringPo, msg) if not fixed: return self._requiringFromTransaction(requiringPo, requirement, errorlist) checkdeps = 1 if needmode in ['e']: self.verbose_logger.log(logginglevels.DEBUG_2, _('TSINFO: %s package requiring %s marked as erase'), requiringPo, needname) txmbrs = self.remove(po=requiringPo) for txmbr in txmbrs: txmbr.setAsDep(po=inst_po) checkdeps = 1 if needmode in ['i', 'u']: newupdates = self.update(name=name, epoch=epoch, version=ver, release=rel, requiringPo=requiringPo) txmbrs = self.tsInfo.getMembersWithState(requiringPo.pkgtup, TS_REMOVE_STATES) if newupdates and txmbrs: if txmbrs[0].output_state == TS_OBSOLETED: self.verbose_logger.log(logginglevels.DEBUG_2, _('TSINFO: Obsoleting %s with %s to resolve dep.'), requiringPo, txmbrs[0].obsoleted_by[0]) else: self.verbose_logger.log(logginglevels.DEBUG_2, _('TSINFO: Updating %s to resolve dep.'), requiringPo) # If the requirement is still there, try and solve it again # so we don't lose it for pkg in txmbrs[0].updated_by: if requirement in map(self._prco_req2req, pkg.returnPrco('requires')): return True, missingdep + self._requiringFromTransaction(pkg, requirement, errorlist)[1] checkdeps = True return checkdeps, missingdep self.verbose_logger.log(logginglevels.DEBUG_2, _('Cannot find an update path for dep for: %s'), niceformatneed) return self._requiringFromTransaction(requiringPo, requirement, errorlist) if needmode is None: reqpkg = (name, ver, rel, None) if self.pkgSack is None: return self._requiringFromTransaction(requiringPo, requirement, errorlist) else: msg = self._err_missing_requires(requiringPo, requirement) self.verbose_logger.log(logginglevels.DEBUG_2, msg) checkdeps = 0 missingdep = 1 errorlist.append(msg) return checkdeps, missingdep def _quickWhatProvides(self, name, flags, version): if self._last_req is None: return False if flags == 0: flags = None if type(version) in (types.StringType, types.NoneType, types.UnicodeType): (r_e, r_v, r_r) = rpmUtils.miscutils.stringToVersion(version) elif type(version) in (types.TupleType, types.ListType): # would this ever be a ListType? (r_e, r_v, r_r) = version # Quick lookup, lots of reqs for one pkg: po = self._last_req if po.checkPrco('provides', (name, flags, (r_e, r_v, r_r))): self.verbose_logger.debug(_('Quick matched %s to require for %s'), po, name) return True return False def _requiringFromTransaction(self, requiringPo, requirement, errorlist): """processes the dependency resolution for a dep where requiring package is in the transaction set""" (name, arch, epoch, version, release) = requiringPo.pkgtup (needname, needflags, needversion) = requirement checkdeps = 0 missingdep = 0 upgraded = {} #~ - if it's not available from some repository: #~ - mark as unresolveable. # #~ - if it's available from some repo: #~ - if there is an another version of the package currently installed then # - if the other version is marked in the transaction set # - if it's marked as erase # - mark the dep as unresolveable # - if it's marked as update or install # - check if the version for this requirement: # - if it is higher # - mark this version to be updated/installed # - remove the other version from the transaction set # - tell the transaction set to be rebuilt # - if it is lower # - mark the dep as unresolveable # - if they are the same # - be confused but continue if self._quickWhatProvides(needname, needflags, needversion): return checkdeps, missingdep provSack = self.whatProvides(needname, needflags, needversion) # get rid of things that are already in the rpmdb - b/c it's pointless to use them here for pkg in provSack.returnPackages(): if self.rpmdb.contains(po=pkg): # is it already installed? self.verbose_logger.log(logginglevels.DEBUG_2, _('%s is in providing packages but it is already installed, removing.'), pkg) provSack.delPackage(pkg) continue # we need to check to see, if we have anything similar to it (name-wise) # installed or in the ts, and this isn't a package that allows multiple installs # then if it's newer, fine - continue on, if not, then we're unresolveable # cite it and exit tspkgs = [] if not self.allowedMultipleInstalls(pkg): # from ts tspkgs = self.tsInfo.matchNaevr(name=pkg.name) for tspkg in tspkgs: if not canCoinstall(pkg.arch, tspkg.po.arch): # a comparable arch if tspkg.po.verGT(pkg): msg = _('Potential resolving package %s has newer instance in ts.') % pkg self.verbose_logger.log(logginglevels.DEBUG_2, msg) provSack.delPackage(pkg) continue elif tspkg.po.verLT(pkg): upgraded.setdefault(pkg.pkgtup, []).append(tspkg.pkgtup) # from rpmdb dbpkgs = self.rpmdb.searchNevra(name=pkg.name) for dbpkg in dbpkgs: if dbpkg.verGT(pkg) and not canCoinstall(pkg.arch, dbpkg.arch): msg = _('Potential resolving package %s has newer instance installed.') % pkg self.verbose_logger.log(logginglevels.DEBUG_2, msg) provSack.delPackage(pkg) continue if len(provSack) == 0: # unresolveable missingdep = 1 msg = self._err_missing_requires(requiringPo, requirement) errorlist.append(msg) return checkdeps, missingdep # iterate the provSack briefly, if we find the package is already in the # tsInfo then just skip this run for pkg in provSack.returnPackages(): (n,a,e,v,r) = pkg.pkgtup pkgmode = self.tsInfo.getMode(name=n, arch=a, epoch=e, ver=v, rel=r) if pkgmode in ['i', 'u']: self.verbose_logger.log(logginglevels.DEBUG_2, _('%s already in ts, skipping this one'), pkg) self._last_req = pkg return checkdeps, missingdep # find the best one # try updating the already install pkgs results = [] for pkg in provSack.returnNewestByName(): tresults = self.update(requiringPo=requiringPo, name=pkg.name, epoch=pkg.epoch, version=pkg.version, rel=pkg.rel) # Note that this does "interesting" things with multilib. We can # have say A.i686 and A.x86_64, and if we hit "A.i686" first, # .update() will actually update "A.x86_64" which will then fail # the pkg == txmbr.po test below, but then they'll be nothing to # update when we get around to A.x86_64 ... so this entire loop # fails. # Keeping results through the loop and thus. testing each pkg # against all txmbr's from previous runs "fixes" this. results.extend(tresults) for txmbr in results: if pkg == txmbr.po: checkdeps = True self._last_req = pkg return checkdeps, missingdep pkgs = provSack.returnPackages() if len(pkgs) == 1: # Minor opt. best = pkgs[0] else: # Always do compare providers for multiple pkgs, it deals with # newest etc. ... so no need to do NewestNameArch() ... and it # stops compare_providers from being clever. pkgresults = self._compare_providers(pkgs, requiringPo,req=needname) best = pkgresults[0][0] if self.rpmdb.contains(po=best): # is it already installed? missingdep = 1 checkdeps = 0 msg = self._err_missing_requires(requiringPo, requirement) errorlist.append(msg) return checkdeps, missingdep # FIXME - why can't we look up in the transaction set for the requiringPkg # and know what needs it that way and provide a more sensible dep structure in the txmbr inst = self.rpmdb.searchNevra(name=best.name, arch=best.arch) if len(inst) > 0: self.verbose_logger.debug(_('TSINFO: Marking %s as update for %s') %(best, requiringPo)) # FIXME: we should probably handle updating multiple packages... txmbr = self.tsInfo.addUpdate(best, inst[0]) txmbr.setAsDep(po=requiringPo) if 'reason' in inst[0].yumdb_info: txmbr.reason = inst[0].yumdb_info.reason else: txmbr.reason = 'dep' checkdeps = True self._last_req = best else: self.verbose_logger.debug(_('TSINFO: Marking %s as install for %s'), best, requiringPo) reqtuple = misc.string_to_prco_tuple(needname + str(needflags) + needversion) txmbrs = self.install(best, provides_for=reqtuple) for txmbr in txmbrs: txmbr.setAsDep(po=requiringPo) txmbr.reason = "dep" self._last_req = txmbr.po # if we had other packages with this name.arch that we found # before, they're not going to be installed anymore, so we # should mark them to be re-checked if txmbr.pkgtup in upgraded: map(self.tsInfo.remove, upgraded[txmbr.pkgtup]) if not txmbrs: missingdep = 1 checkdeps = 0 msg = self._err_missing_requires(requiringPo, requirement) errorlist.append(msg) else: checkdeps = 1 return checkdeps, missingdep def _dscb_procConflict(self, po, niceformatneed): """ Call the callback for processing requires, call the nicest one available. """ if not self.dsCallback: return if hasattr(self.dsCallback, 'procConflictPo'): self.dsCallback.procConflictPo(po, niceformatneed) else: self.dsCallback.procConflict(po.name, niceformatneed) def _processConflict(self, po, conflict, conflicting_po): """processes a Conflict dep from the resolveDeps() method""" CheckDeps = True errormsgs = [] needname, flags, needversion = conflict (name, arch, epoch, ver, rel) = po.pkgtup niceformatneed = rpmUtils.miscutils.formatRequire(needname, needversion, flags) self._dscb_procConflict(po, niceformatneed) length = len(self.tsInfo) if flags & rpm.RPMSENSE_LESS: self.update(name=conflicting_po.name) txmbrs = self.tsInfo.getMembersWithState(conflicting_po.pkgtup, TS_REMOVE_STATES) if len(self.tsInfo) != length and txmbrs: return CheckDeps, errormsgs elif flags & rpm.RPMSENSE_GREATER: self.update(name=name) txmbrs = self.tsInfo.getMembersWithState(po.pkgtup, TS_REMOVE_STATES) if len(self.tsInfo) != length and txmbrs: return CheckDeps, errormsgs self.update(name=conflicting_po.name) txmbrs = self.tsInfo.getMembersWithState(conflicting_po.pkgtup, TS_REMOVE_STATES) if len(self.tsInfo) != length and txmbrs: return CheckDeps, errormsgs self.update(name=name) txmbrs = self.tsInfo.getMembersWithState(po.pkgtup, TS_REMOVE_STATES) if len(self.tsInfo) != length and txmbrs: return CheckDeps, errormsgs msg = '%s conflicts with %s' % (name, str(conflicting_po)) errormsgs.append(msg) self.verbose_logger.log(logginglevels.DEBUG_1, msg) CheckDeps = False # report the conflicting po, so skip-broken can remove it self.po_with_problems.add((po,conflicting_po,errormsgs[-1])) return CheckDeps, errormsgs def _undoDepInstalls(self): # clean up after ourselves in the case of failures for txmbr in self.tsInfo: if txmbr.isDep: self.tsInfo.remove(txmbr.pkgtup) def prof_resolveDeps(self): """Call :func:`resolveDeps`, and profile the call using the hotshot profiler. :return: a tuple containing the result code and a list of result strings. This is simply the return value of :func:`resolveDeps` being passed up. """ fn = "anaconda.prof.0" import hotshot, hotshot.stats prof = hotshot.Profile(fn) rc = prof.runcall(self.resolveDeps) prof.close() print "done running depcheck" stats = hotshot.stats.load(fn) stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) return rc def cprof_resolveDeps(self): """Call :func:`resolveDeps`, and profile the call using the cprof profiler. :return: a tuple containing the result code and a list of result strings. This is simply the return value of :func:`resolveDeps` being passed up. """ import cProfile, pstats prof = cProfile.Profile() rc = prof.runcall(self.resolveDeps) prof.dump_stats("yumprof") print "done running depcheck" p = pstats.Stats('yumprof') p.strip_dirs() p.sort_stats('time') p.print_stats(20) return rc def resolveDeps(self, full_check=True, skipping_broken=False): """Resolve dependencies for the packages in the current trasaction set. :param full_check: whether to also check removes and installs, as well as dependencies :param skipping_broken: if this is true, a message saying that dependency solving has finished successfully will not be output. This is useful since if there are broken packages that are being skipped, :func:`resolveDeps` will need to be called multiple times before dependency solving is completely finished """ if not len(self.tsInfo): return (0, [_('Success - empty transaction')]) self.po_with_problems = set() self._working_po = None self._last_req = None self.tsInfo.resetResolved(hard=False) CheckDeps = True CheckRemoves = full_check CheckInstalls = full_check missingdep = 0 errors = [] if self.dsCallback: self.dsCallback.start() depsolve_loop_count = 0 while True: if depsolve_loop_count == (self.conf.depsolve_loop_limit or -1): return (1, [_("Depsolving loop limit reached.")] + unique(errors)) depsolve_loop_count += 1 CheckDeps = True # check Requires while CheckDeps: self.cheaterlookup = {} if self.dsCallback: self.dsCallback.tscheck() CheckDeps, checkinstalls, checkremoves, missing = self._resolveRequires(errors) CheckInstalls |= checkinstalls CheckRemoves |= checkremoves # check global FileRequires self._working_po = None # reset the working po if CheckRemoves: CheckRemoves = False for po, dep in self._checkFileRequires(): (checkdep, missing, errormsgs) = self._processReq(po, dep) CheckDeps |= checkdep errors += errormsgs if CheckDeps: if self.dsCallback: self.dsCallback.restartLoop() self.verbose_logger.log(logginglevels.DEBUG_1, _('Restarting Loop')) continue # check Conflicts self._working_po = None # reset the working po if CheckInstalls: CheckInstalls = False for conflict in self._checkConflicts(): (checkdep, errormsgs) = self._processConflict(*conflict) CheckDeps |= checkdep errors += errormsgs if checkdep: break # The next conflict might be the same pkg if True: # Always have to check obsoletes... if self._checkObsoletes(): CheckDeps = True CheckRemoves = True self._last_req = None if CheckDeps: if self.dsCallback: self.dsCallback.restartLoop() self.verbose_logger.log(logginglevels.DEBUG_1, _('Restarting Loop')) continue break # FIXME: this doesn't belong here at all... for txmbr in self.tsInfo.getMembers(): if self.allowedMultipleInstalls(txmbr.po) and \ txmbr.ts_state == 'u': self.verbose_logger.log(logginglevels.DEBUG_2, _('%s converted to install'), txmbr.po) txmbr.ts_state = 'i' txmbr.output_state = TS_INSTALL if self.dsCallback: if not self.conf.skip_broken: self.dsCallback.end() elif not skipping_broken and not errors: self.dsCallback.end() self.verbose_logger.log(logginglevels.DEBUG_1, _('Dependency Process ending')) self.tsInfo.changed = False if len(errors) > 0: errors = unique(errors) # We immediately display this in cli, so don't show it twice. # Plus skip-broken can get here N times. Might be worth keeping # around for debugging? done = set() # Same as the unique above for po,wpo,err in self.po_with_problems: if (po,err) in done: continue done.add((po, err)) self.verbose_logger.log(logginglevels.DEBUG_4, "SKIPBROKEN: %s from %s has depsolving problems" % (po, po.repoid)) err = err.replace('\n', '\n --> ') self.verbose_logger.log(logginglevels.DEBUG_4,"SKIPBROKEN: --> %s" % err) return (1, errors) if not len(self.tsInfo): return (0, [_('Success - empty transaction')]) return (2, [_('Success - deps resolved')]) def _resolveRequires(self, errors): any_missing = False CheckDeps = False CheckInstalls = False CheckRemoves = False # we need to check the opposite of install and remove for regular # tsInfo members vs removed members for txmbr in self.tsInfo.getUnresolvedMembers(): if self.dsCallback and txmbr.ts_state: dscb_ts_state = txmbr.ts_state if txmbr.downgrades: dscb_ts_state = 'd' if dscb_ts_state == 'u' and txmbr.reinstall: dscb_ts_state = 'r' if dscb_ts_state == 'u': if txmbr.output_state == TS_OBSOLETING: dscb_ts_state = 'o' elif not txmbr.updates: dscb_ts_state = 'i' self.dsCallback.pkgAdded(txmbr.pkgtup, dscb_ts_state) self.verbose_logger.log(logginglevels.DEBUG_2, _("Checking deps for %s") %(txmbr,)) # store the primary po we currently are working on # so we can store it in self.po_with_problems. # it is useful when an update is breaking an require of an installed package # then we want to know who is causing the problem, not just who is having the problem. if not txmbr.updates and txmbr.relatedto: self._working_po = txmbr.relatedto[0][0] else: self._working_po = txmbr.po if (txmbr.output_state in TS_INSTALL_STATES) == (txmbr.po.state != None): thisneeds = self._checkInstall(txmbr) CheckInstalls = True else: thisneeds = self._checkRemove(txmbr) CheckRemoves = True missing_in_pkg = False for po, dep in thisneeds: if txmbr.downgraded_by: # Don't try to chain remove downgrades msg = self._err_missing_requires(po, dep) self.verbose_logger.log(logginglevels.DEBUG_2, msg) errors.append(msg) self.po_with_problems.add((po,self._working_po,errors[-1])) missing_in_pkg = 1 continue (checkdep, missing, errormsgs) = self._processReq(po, dep) CheckDeps |= checkdep errors += errormsgs missing_in_pkg |= missing if not missing_in_pkg: self.tsInfo.markAsResolved(txmbr) any_missing |= missing_in_pkg return CheckDeps, CheckInstalls, CheckRemoves, any_missing @staticmethod def _sort_req_key(pkgtup): """ Get a sort key for a package requires from most "narrow" to least, this tries to ensure that if we have two reqs like "libfoo = 1.2.3-4" and "foo-api" (which is also provided by libxyz-foo) that we'll get just libfoo. There are other similar cases this "handles".""" mapper = {'EQ' : 1, 'LT' : 2, 'LE' : 3, 'GT' : 4, 'GE' : 5, None : 99} flagscore = mapper.get(pkgtup[1], 10) # This is pretty magic, basically we want an explicit: # # Requires: foo # # ...to happen before the implicit: # # Requires: libfoo.so.0() # # ...because sometimes the libfoo.so.0() is provided by multiple # packages. Do we need more magic for other implicit deps. here? namescore = 0 if pkgtup[0].startswith("lib") and \ (pkgtup[0].endswith("()") or pkgtup[0].endswith("()(64bit)")): namescore = 99 # Processes these last return (flagscore, namescore) def _checkInstall(self, txmbr): txmbr_reqs = txmbr.po.returnPrco('requires') # if this is an update, we should check what the old # requires were to make things faster # Note that if the rpmdb is broken, this gets annoying. So we provide # a way to turn it off. oldreqs = [] if not self.conf.recheck_installed_requires: for oldpo in txmbr.updates: oldreqs.extend(oldpo.returnPrco('strong_requires')) oldreqs = set(oldreqs) ret = [] for req in sorted(txmbr_reqs, key=self._sort_req_key): if req[0].startswith('rpmlib('): continue if req in oldreqs: continue self.verbose_logger.log(logginglevels.DEBUG_2, _("looking for %s as a requirement of %s"), req, txmbr) provs = self.tsInfo.getProvides(*req) # The self provides should mostly be caught before here now, but # at least config() crack still turns up, it's not that # expensive to just do it, and we really don't want "false positive" # requires for compare_providers(). if not provs and not txmbr.po.inPrcoRange('provides', req): ret.append( (txmbr.po, self._prco_req2req(req)) ) continue #Add relationship for po in provs: if txmbr.name == po.name: continue for member in self.tsInfo.getMembersWithState( pkgtup=po.pkgtup, output_states=TS_INSTALL_STATES): member.setAsDep(txmbr.po, relonly=True) return ret def _checkRemove(self, txmbr): po = txmbr.po provs = po.returnPrco('provides') # if this is an update, we should check what the new package # provides to make things faster newpoprovs = {} for newpo in txmbr.updated_by + txmbr.obsoleted_by: for p in newpo.provides: newpoprovs[p] = 1 ret = [] # iterate over the provides of the package being removed # and see what's actually going away for prov in provs: if prov[0].startswith('rpmlib('): # ignore rpmlib() provides continue if prov in newpoprovs: continue # FIXME: This is probably the best place to fix the postfix rename # problem long term (post .21) ... see compare_providers. for pkg, hits in self.tsInfo.getRequires(*prov).iteritems(): # See the docs, this is to make remove* "more useful". if (self.conf.repopkgsremove_leaf_only and txmbr.repopkg and txmbr.output_state == TS_ERASE): cb = self.dsCallback if cb and hasattr(cb, 'repoPkgRemoveReq'): cb.repoPkgRemoveReq(txmbr.po, hits) elif cb and hasattr(cb, 'removeReq'): cb.removeReq(txmbr.po, pkg, hits) # We don't undo anything else here ... hopefully that's # fine. self.tsInfo.remove(txmbr.pkgtup) return [] if (self.conf.groupremove_leaf_only and txmbr.groups and txmbr.output_state == TS_ERASE): cb = self.dsCallback if cb and hasattr(cb, 'groupRemoveReq'): cb.groupRemoveReq(pkg, hits) elif cb and hasattr(cb, 'removeReq'): cb.removeReq(txmbr.po, pkg, hits) # We don't undo anything else here ... hopefully that's # fine. self.tsInfo.remove(txmbr.pkgtup) return [] if (self.conf.remove_leaf_only and txmbr.output_state == TS_ERASE): cb = self.dsCallback if cb and hasattr(cb, 'removeReq'): cb.removeReq(txmbr.po, pkg, hits) # We don't undo anything else here ... hopefully that's # fine. self.tsInfo.remove(txmbr.pkgtup) return [] for hit in hits: # See if the update solves the problem... found = False for newpo in txmbr.updated_by: if newpo.checkPrco('provides', hit): found = True break if found: continue for newpo in txmbr.obsoleted_by: if newpo.checkPrco('provides', hit): found = True break if found: continue # It doesn't, so see what else might... rn, rf, rv = hit if not self.tsInfo.getProvides(rn, rf, rv): ret.append( (pkg, self._prco_req_nfv2req(rn, rf, rv)) ) return ret def _checkFileRequires(self): fileRequires = set() nfileRequires = set() # These need to be looked up in the rpmdb. reverselookup = {} ret = [] # generate list of file requirement in rpmdb if self.installedFileRequires is None: self.installedFileRequires, \ self.installedUnresolvedFileRequires, \ self.installedFileProviders = self.rpmdb.fileRequiresData() # get file requirements from packages not deleted todel = [] for pkgtup, files in self.installedFileRequires.iteritems(): if self._tsInfo.getMembersWithState(pkgtup, output_states=TS_REMOVE_STATES): todel.append(pkgtup) else: fileRequires.update(files) for filename in files: reverselookup.setdefault(filename, []).append(pkgtup) for pkgtup in todel: del self.installedFileRequires[pkgtup] fileRequires -= self.installedUnresolvedFileRequires # get file requirements from new packages for txmbr in self._tsInfo.getMembersWithState(output_states=TS_INSTALL_STATES): files = [] for name, flag, evr in txmbr.po.requires: if name.startswith('/'): files.append(name) # check if file requires was already unresolved in update if name in self.installedUnresolvedFileRequires: already_broken = False for oldpo in txmbr.updates: if oldpo.checkPrco('requires', (name, None, (None, None, None))): already_broken = True break if already_broken: continue if name not in fileRequires: nfileRequires.add(name) fileRequires.add(name) reverselookup.setdefault(name, []).append(txmbr.po.pkgtup) self.installedFileRequires[txmbr.po.pkgtup] = files todel = [] for fname in self.installedFileProviders: niFP_fname = [] for pkgtup in self.installedFileProviders[fname]: if self._tsInfo.getMembersWithState(pkgtup, output_states=TS_REMOVE_STATES): continue niFP_fname.append(pkgtup) if not niFP_fname: todel.append(fname) continue self.installedFileProviders[fname] = niFP_fname for fname in todel: del self.installedFileProviders[fname] # check the file requires iFP = self.installedFileProviders for filename in fileRequires: # In theory we need this to be: # # nprov, filename in iFP (or new), oprov # # ...this keeps the cache exactly the same as the non-cached data. # However that also means that we'll always need the filelists, so # we do: # # filename in iFP (if found return), oprov (if found return), # nprov # # ...this means we'll always get the same _result_ (as we only need # to know if _something_ provides), but our cache will be off on # what does/doesn't provide the file. if filename in self.installedFileProviders: continue oprov = self.tsInfo.getOldProvides(filename) if oprov: iFP.setdefault(filename, []).extend([po.pkgtup for po in oprov]) continue nprov = self.tsInfo.getNewProvides(filename) if nprov: iFP.setdefault(filename, []).extend([po.pkgtup for po in nprov]) continue if filename != os.path.realpath(filename): realpath = os.path.realpath(filename) nprov = self.tsInfo.getNewProvides(realpath) if nprov: iFP.setdefault(realpath, []).extend([po.pkgtup for po in nprov]) continue for pkgtup in reverselookup[filename]: po = self.tsInfo.getMembersWithState(pkgtup, TS_INSTALL_STATES) if po: po = po[0].po # Should only have one else: po = self.getInstalledPackageObject(pkgtup) ret.append( (po, (filename, 0, '')) ) self.rpmdb.transactionCacheFileRequires(self.installedFileRequires, self.installedUnresolvedFileRequires, self.installedFileProviders, ret) return ret def _checkConflicts(self): ret = [ ] cpkgs = [] for po in self.rpmdb.returnConflictPackages(): if self.tsInfo.getMembersWithState(po.pkgtup, output_states=TS_REMOVE_STATES): continue conflicts = po.returnPrco('conflicts') if not conflicts: # We broke this due to dbMatch() usage. continue cpkgs.append(po) for conflict in conflicts: (r, f, v) = conflict for conflicting_po in self.tsInfo.getNewProvides(r, f, v): if conflicting_po.pkgtup[0] == po.pkgtup[0] and conflicting_po.pkgtup[2:] == po.pkgtup[2:]: continue ret.append( (po, self._prco_req_nfv2req(r, f, v), conflicting_po) ) for txmbr in self.tsInfo.getMembersWithState(output_states=TS_INSTALL_STATES): po = txmbr.po done = False for conflict in txmbr.po.returnPrco('conflicts'): if not done: cpkgs.append(txmbr.po) done = True (r, f, v) = conflict for conflicting_po in self.tsInfo.getProvides(r, f, v): if conflicting_po.pkgtup[0] == po.pkgtup[0] and conflicting_po.pkgtup[2:] == po.pkgtup[2:]: continue ret.append( (po, self._prco_req_nfv2req(r, f, v), conflicting_po) ) if _rpm_ver_atleast((4, 9, 0)): return ret # Don't need the conflicts cache anymore self.rpmdb.transactionCacheConflictPackages(cpkgs) return ret # This is checking for installed / transaction members which # obsolete each other. Because rpm will now auto. obs. those # anyway (even if we have obsoletes turned off). def _checkObsoletes(self): opkgs = [] ret = False def _do_obs(otxmbr): self.tsInfo.remove(otxmbr.pkgtup) # We need to remove an obsoleted entry that # was maybe used to resolve something ... ? self.pkgSack.delPackage(otxmbr.po) self.up.delPackage(otxmbr.pkgtup) # Remove it from the installed file requires cache (self.installedFileRequires or {}).pop(otxmbr.pkgtup, None) for po in self.rpmdb.returnObsoletePackages(): if self.tsInfo.getMembersWithState(po.pkgtup, output_states=TS_REMOVE_STATES): continue obsoletes = po.returnPrco('obsoletes') if not obsoletes: # We broke this due to dbMatch() usage. continue opkgs.append(po) for obs_name,f,v in obsoletes: for otxmbr in self.tsInfo.matchNaevr(name=obs_name): if not otxmbr.po.obsoletedBy([po]): continue if po.obsoletedBy([otxmbr.po]): # Loops, hope for rpm. continue msg = _('Removing %s due to obsoletes from installed %s') self.verbose_logger.warning(msg, otxmbr, po) _do_obs(otxmbr) ret = True for txmbr in self.tsInfo.getMembersWithState(None, output_states=TS_INSTALL_STATES): done = False for obs_n in txmbr.po.obsoletes_names: if not done: opkgs.append(txmbr.po) done = True for otxmbr in self.tsInfo.matchNaevr(name=obs_n): if otxmbr.output_state not in TS_INSTALL_STATES: continue if otxmbr.po == txmbr.po: # Not sure if we should just ignore this for # us, or for everyone... continue if not otxmbr.po.obsoletedBy([txmbr.po]): continue if txmbr.po.obsoletedBy([otxmbr.po]): # Have to deal with loops! Hope rpm behaves too. continue msg = _('Removing %s due to obsoletes from %s') self.verbose_logger.warning(msg, otxmbr, txmbr) _do_obs(otxmbr) ret = True self.rpmdb.transactionCacheObsoletePackages(opkgs) return ret def isPackageInstalled(self, pkgname): """Return whether the given package in installed. :param pkgname: the name of the package :return: whether the package in installed """ lst = self.tsInfo.matchNaevr(name = pkgname) for txmbr in lst: if txmbr.output_state in TS_INSTALL_STATES: return True if len(lst) > 0: # if we get here then it's in the tsInfo for an erase or obsoleted # --> not going to be installed return False if not self.rpmdb.contains(name=pkgname): return False return True _isPackageInstalled = isPackageInstalled def _compare_providers(self, pkgs, reqpo, req=None): """take the list of pkgs and score them based on the requesting package return a dictionary of po=score""" self.verbose_logger.log(logginglevels.DEBUG_4, _("Running compare_providers() for %s") %(str(pkgs))) def _common_prefix_len(x, y, minlen=2): num = min(len(x), len(y)) for off in range(num): if x[off] != y[off]: return max(off, minlen) return max(num, minlen) def _common_sourcerpm(x, y): if not hasattr(x, 'sourcerpm'): return False if not hasattr(y, 'sourcerpm'): return False return x.sourcerpm == y.sourcerpm def _compare_arch_distance(x, y, req_compare_arch): # take X and Y package objects # determine which has a closer archdistance to compare_arch # if they are equal to compare_arch, compare which is closer to the # running arch # return the package which is closer or None for equal, or equally useless x_dist = archDifference(req_compare_arch, x.arch) if self.arch.multilib: # only go to the next one if we're multilib - if x_dist == 0: # can't really use best's arch anyway... self.verbose_logger.log(logginglevels.DEBUG_4, _("better arch in po %s") %(y)) return y # just try the next one - can't be much worse y_dist = archDifference(req_compare_arch, y.arch) if y_dist > 0 and x_dist > y_dist: self.verbose_logger.log(logginglevels.DEBUG_4, _("better arch in po %s") %(y)) return y if y_dist == x_dist: return None return x def _pkg2prov_version(pkg, provname): ''' This converts a package into a specific version tuple for the required provide. The provide _must_ be '=' and epoch=None and release=None == '0'. If there is 0 or 2+ matches, return None. If the req does not == the provide name, return None. ''' ret = None for prov in pkg.provides: (n, f, (e, v, r)) = prov if n != provname: continue if f != 'EQ': continue if ret is not None: return None ret = (e or '0', v, r or '0') return ret # Actual start of _compare_providers(). # Do a NameArch filtering, based on repo. __cmp__ unique_nevra_pkgs = {} for pkg in pkgs: if (pkg.pkgtup in unique_nevra_pkgs and unique_nevra_pkgs[pkg.pkgtup].repo <= pkg.repo): continue unique_nevra_pkgs[pkg.pkgtup] = pkg pkgs = unique_nevra_pkgs.values() pkgresults = {} for pkg in pkgs: pkgresults[pkg] = 0 # hand this off to our plugins self.plugins.run("compare_providers", providers_dict=pkgresults, reqpo=reqpo) for pkg in pkgresults.keys(): rpmdbpkgs = self.rpmdb.searchNevra(name=pkg.name) if rpmdbpkgs: # We only want to count things as "installed" if they are # older than what we are comparing, because this then an update # so we give preference. If they are newer then obsoletes/etc. # could play a part ... this probably needs a better fix. newest = sorted(rpmdbpkgs)[-1] if newest.verLT(pkg): # give pkgs which are updates just a SLIGHT edge # we should also make sure that any pkg # we are giving an edge to is not obsoleted by # something else in the transaction. :( # there are many ways I hate this - this is but one pkgresults[pkg] += 5 elif newest.verEQ(pkg): # We get here from bestPackagesFromList(), give a giant # bump to stuff that is already installed. pkgresults[pkg] += 1000 elif newest.verGT(pkg): # if the version we're looking at is older than what we have installed # score it down like we would an obsoleted pkg pkgresults[pkg] -= 1024 else: # just b/c they're not installed pkgs doesn't mean they should # be ignored entirely. Just not preferred pass pkgs = pkgresults.keys() # go through each pkg and compare to others # if it is same skip it # if the pkg is obsoleted by any other of the packages # then add -1024 to its score # don't need to look for mutual obsoletes b/c each package # is evaluated against all the others, so mutually obsoleting # packages will have their scores diminished equally # compare the arch vs each other pkg # give each time it returns with a better arch a +5 # look for common source vs the reqpo - give a +10 if it has it # look for common_prefix_len - add the length*2 to the score # add the negative of the length of the name to the score lpos = {} for po in pkgs: for nextpo in pkgs: if po == nextpo: continue # If this package isn't the latest version of said package, # treat it like it's obsoleted. The problem here is X-1 # accidentally provides FOO, so you release X-2 without the # provide, but X-1 is still picked over a real provider. if po.name not in lpos: lpos[po.name] = self.pkgSack.returnNewestByName(po.name)[:1] if not lpos[po.name] or not po.verEQ(lpos[po.name][0]): pkgresults[po] -= 1024 obsoleted = False if po.obsoletedBy([nextpo]): obsoleted = True pkgresults[po] -= 1024 self.verbose_logger.log(logginglevels.DEBUG_4, _("%s obsoletes %s") % (nextpo, po)) if reqpo: arches = (reqpo.arch, self.arch.bestarch) else: arches = (self.arch.bestarch,) for thisarch in arches: res = _compare_arch_distance(po, nextpo, thisarch) if not res: continue self.verbose_logger.log(logginglevels.DEBUG_4, _('archdist compared %s to %s on %s\n Winner: %s' % (po, nextpo, thisarch, res))) if res == po: pkgresults[po] += 5 # End of O(N*N): for nextpo in pkgs: # Respect the repository priority for each provider, the default is 80 pkgresults[po] += (100 - po.repo.compare_providers_priority) * 10 self.verbose_logger.log(logginglevels.DEBUG_4, _('compare_providers_priority for %s is %s' % (po, po.repo.compare_providers_priority))) if _common_sourcerpm(po, reqpo): self.verbose_logger.log(logginglevels.DEBUG_4, _('common sourcerpm %s and %s' % (po, reqpo))) pkgresults[po] += 20 if self.isPackageInstalled(po.base_package_name): self.verbose_logger.log(logginglevels.DEBUG_4, _('base package %s is installed for %s' % (po.base_package_name, po))) pkgresults[po] += 5 # Same as before - - but off of base package name if reqpo: cpl = _common_prefix_len(po.name, reqpo.name) if cpl > 2: self.verbose_logger.log(logginglevels.DEBUG_4, _('common prefix of %s between %s and %s' % (cpl, po, reqpo))) pkgresults[po] += cpl*2 if req is not None: bestnum = max(pkgresults.values()) prov_depsolve = {} for po in pkgs: if pkgresults[po] != bestnum: continue evr = _pkg2prov_version(po, req) if evr is None: prov_depsolve = {} break prov_depsolve[po] = evr if len(prov_depsolve) > 1: self.verbose_logger.log(logginglevels.DEBUG_4, _('provides vercmp: %s') % str(req)) newest = sorted(prov_depsolve, key = lambda x: PackageEVR(*prov_depsolve[x])) for winner in reversed(newest): if prov_depsolve[winner] != prov_depsolve[newest[-1]]: break self.verbose_logger.log(logginglevels.DEBUG_4, _(' Winner: %s') % winner) pkgresults[winner] += 1 # If we have more than one "best", see what would happen if we picked # each package ... ie. what things do they require that _aren't_ already # installed/to-be-installed. In theory this can screw up due to: # pkgA => requires pkgX # pkgB => requires pkgY, requires pkgZ # ...but pkgX requires 666 other things. Going recursive is # "non-trivial" though, python != prolog. This seems to do "better" # from simple testing though. bestnum = max(pkgresults.values()) rec_depsolve = {} for po in pkgs: if pkgresults[po] != bestnum: continue rec_depsolve[po] = 0 if len(rec_depsolve) > 1: for po in rec_depsolve: fake_txmbr = TransactionMember(po) # Note that this is just requirements, so you could also have # 4 requires for a single package. This might be fixable, if # needed, but given the above it's probably better to leave it # like this. reqs = self._checkInstall(fake_txmbr) rec_depsolve[po] = len(reqs) bestnum = min(rec_depsolve.values()) self.verbose_logger.log(logginglevels.DEBUG_4, _('requires minimal: %d') % bestnum) for po in rec_depsolve: if rec_depsolve[po] == bestnum: self.verbose_logger.log(logginglevels.DEBUG_4, _(' Winner: %s') % po) pkgresults[po] += 1 else: num = rec_depsolve[po] self.verbose_logger.log(logginglevels.DEBUG_4, _(' Loser(with %d): %s') % (num, po)) # We don't want to decide to use a "shortest first", if something else # has told us to pick something else. But we want to pick between # multiple "best" packages. So we spike all the best packages (so # only those can win) and then bump them down by package name length. bestnum = max(pkgresults.values()) for po in pkgs: if pkgresults[po] != bestnum: continue pkgresults[po] += 1000 pkgresults[po] += (len(po.name)*-1) bestorder = sorted(pkgresults.items(), key=lambda x: (x[1], x[0]), reverse=True) self.verbose_logger.log(logginglevels.DEBUG_4, _('Best Order: %s' % str(bestorder))) return bestorder class DepCheck(object): """Object that :class:`YumDepsolver` uses to see what things are needed to close the transaction set. The attributes requires and conflicts are lists of requires and conflicts in the current transaction set. Each item in the lists is a :class:`Requires` or :class:`Conflicts` object. """ def __init__(self): self.requires = [] self.conflicts = [] def addRequires(self, po, req_tuple_list): """Create and add a :class:`Requires` object to the list of requires. :param po: the package object involved in the requires :param req_tuple_list: a list of required by *po* that are un-closed in the transaction set """ # fixme - do checking for duplicates or additions in here to zip things along reqobj = Requires(po, req_tuple_list) self.requires.append(reqobj) def addConflicts(self, conflict_po_list, conflict_item): """Create and add a :class:`Conflicts` object to the list of conflicts. :param conflict_po_list: a list of conflicting package objects :param conflict_item: what the conflict between the members of *conflict_po_list is. """ confobj = Conflicts(conflict_po_list, conflict_item) self.conflicts.append(confobj) class Requires(object): """A pure data class for holding a package and the list of things it requires. """ def __init__(self, pkg,requires): self.pkg = pkg # po of requiring pkg self.requires = requires # list of things it requires that are un-closed in the ts class Conflicts(object): """A pure data class for holding a list packages and what the conflict between them is. """ def __init__(self, pkglist, conflict): self.pkglist = pkglist # list of conflicting package objects self.conflict = conflict # what the conflict was between them