#!/usr/bin/python -t # -*- mode: Python; indent-tabs-mode: nil; -*- # 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 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. # seth vidal 2005 (c) etc etc # mschwendt: modified for Fedora Extras buildsys #Read in the metadata of a series of repositories and check all the # dependencies in all packages for resolution. Print out the list of # packages with unresolved dependencies import sys import os import re import string import errno # For patched "yum" and "rpmUtils" (post 2.6.1 checkForObsolete support). # Comment this to use system yum. #sys.path.insert(0,'/srv/extras-push/work/buildsys-utils/pushscript') import yum import yum.Errors from yum.misc import getCacheDir from optparse import OptionParser import rpmUtils.arch from yum.constants import * if yum.__version__ < '3.0': # TODO: check from repomd.packageSack import ListPackageSack else: from yum.packageSack import ListPackageSack def parseArgs(): usage = "usage: %s [-c ] [-a ] [-r ] [-r ]" % sys.argv[0] parser = OptionParser(usage=usage) parser.add_option("-c", "--config", default='/etc/yum.conf', help='config file to use (defaults to /etc/yum.conf)') parser.add_option("-a", "--arch", default=[], action='append', help='check packages of the given archs, can be specified multiple ' + 'times (default: current arch)') parser.add_option("-r", "--repoid", default=[], action='append', help="specify repo ids to query, can be specified multiple times (default is all enabled)") parser.add_option("-t", "--tempcache", default=False, action="store_true", help="use a temp dir for storing/accessing yum-cache") parser.add_option("-d", "--cachedir", default='', help="specify a custom directory for storing/accessing yum-cache") parser.add_option("-q", "--quiet", default=0, action="store_true", help="quiet (no output to stderr)") parser.add_option("-n", "--newest", default=0, action="store_true", help="check only the newest packages in the repos") parser.add_option("", "--nomultilibhack", default=False, action="store_true", help="disable multi-lib hack") parser.add_option("", "--rawhidehack", default=False, action="store_true", help="Fedora 11 does i386->i586 updates") (opts, args) = parser.parse_args() return (opts, args) class RepoClosure(yum.YumBase): def __init__(self, arch = [], config = "/etc/yum.conf"): yum.YumBase.__init__(self) if yum.__version__ < '3.2.24': if arch: self.arch = arch[0] else: self.arch = None else: self._rc_arches = arch if yum.__version__ < '3.0': # TODO: check self.doConfigSetup(fn = config) else: self.doConfigSetup(fn = config, init_plugins = False) if hasattr(self.repos, 'sqlite'): self.repos.sqlite = False self.repos._selectSackType() self.guessMultiLibProbs = True self.rawhidehack = False def evrTupletoVer(self,tuple): """convert and evr tuple to a version string, return None if nothing to convert""" e, v,r = tuple if v is None: return None if e is not None: val = '%s:%s' % (e, v) else: val = '%s' % v if r is not None: val = '%s-%s' % (val, r) return val def readMetadata(self): self.doRepoSetup() if yum.__version__ < '3.2.24': self.doSackSetup(rpmUtils.arch.getArchList(self.arch)) else: archs = [] if not self._rc_arches: archs.extend(self.arch.archlist) else: for arch in self._rc_arches: archs.extend(self.arch.get_arch_list(arch)) self.doSackSetup(archs) for repo in self.repos.listEnabled(): try: # TODO: when exactly did this change from "with=" to "mdtype="? self.repos.populateSack(which=[repo.id], mdtype='filelists') except TypeError: # Retry without named arguments for compatibility with older # Yum. Can't used "with=" here because finally it's a fatal # error in Python >= 2.6 self.repos.populateSack([repo.id], 'filelists') def isnewest(self, pkg): newest = pkg.pkgtup in self.newestpkgtuplist if not self.guessMultiLibProbs: return newest # Multi-lib hack: # # This is supposed to catch corner-cases, such as: # Base-arch pkg was updated, but a corresponding compat-arch pkg # is not included in the repo, because e.g. it was repackaged # and no longer is pulled in by the multi-lib resolver. # Assume, that if it the old compat-arch pkg is in the repo, # there is no upgrade path from biarch installs to single-arch # (the one pkg upgrades two installed pkgs with different arch) (n,a,e,v,r) = pkg.pkgtup if newest or a=='noarch': return newest # the trivial case for provpkg in self.pkgSack.returnNewestByName(n): prov_a = provpkg.pkgtup[1] if prov_a=='noarch' or prov_a==a: (prov_e, prov_v, prov_r) = provpkg.pkgtup[2:] vercmp = rpmUtils.miscutils.compareEVR( (prov_e,prov_v,prov_r), (e,v,r) ) if vercmp>0: # provpkg is newer return False # No noarch/same-arch pkg is newer, but a basearch pkg may be newer # and therefore be the only one in newestpkgtuplist. return True def hasupdate(self, pkg): # Ugly hack for the i386->i586 switch in F11 development. # Ugly hack for the i586->i686 switch in F12 development. (n,a,e,v,r) = pkg.pkgtup for provpkg in self.pkgSack.returnNewestByName(n): prov_a = provpkg.pkgtup[1] if prov_a=='i586' or prov_a=='i686': (prov_e, prov_v, prov_r) = provpkg.pkgtup[2:] vercmp = rpmUtils.miscutils.compareEVR( (prov_e,prov_v,prov_r), (e,v,r) ) if vercmp>0: # provpkg is newer, i586/i686 updates i386/i586 return True return False # there is no update for the i386/i586 pkg def getBrokenDeps(self, newest=False): unresolved = {} resolved = {} self.newestpkgtuplist = [] if newest: if yum.__version__ >= '2.9': # TODO: check pkgs = self.pkgSack.returnNewestByName() else: pkgs = [] for l in self.pkgSack.returnNewestByName(): pkgs.extend(l) self.newestpkgtuplist = ListPackageSack(pkgs).simplePkgList() pkgs = self.pkgSack.returnNewestByNameArch() else: pkgs = self.pkgSack self.numpkgs = len(pkgs) mypkgSack = ListPackageSack(pkgs) pkgtuplist = mypkgSack.simplePkgList() # Support new checkForObsolete code in Yum (#190116) # _if available_ # so we don't examine old _obsolete_ sub-packages. import rpmUtils.updates self.up = rpmUtils.updates.Updates([],pkgtuplist) self.up.rawobsoletes = mypkgSack.returnObsoletes() haveCheckForObsolete = hasattr(rpmUtils.updates.Updates,'checkForObsolete') if not haveCheckForObsolete: print 'WARNING: rpmUtils.updates.checkForObsolete missing!' for pkg in pkgs: if self.rawhidehack and (pkg.pkgtup[1]=='i386' or pkg.pkgtup[1]=='i586') and self.hasupdate(pkg): print 'WARNING: Skipping', pkg continue thispkgobsdict = {} if haveCheckForObsolete: try: thispkgobsdict = self.up.checkForObsolete([pkg.pkgtup]) if thispkgobsdict.has_key(pkg.pkgtup): # print "OBSOLETE:", pkg.pkgtup continue except AttributeError: pass for (req, flags, (reqe, reqv, reqr)) in pkg.returnPrco('requires'): if req.startswith('rpmlib'): continue # ignore rpmlib deps ver = self.evrTupletoVer((reqe, reqv, reqr)) if resolved.has_key((req,flags,ver)): continue try: resolve_sack = self.whatProvides(req, flags, ver) except yum.Errors.RepoError, e: pass if len(resolve_sack) < 1: if newest and not self.isnewest(pkg): break if not unresolved.has_key(pkg): unresolved[pkg] = [] unresolved[pkg].append((req, flags, ver)) continue if newest: resolved_by_newest = False for po in resolve_sack:# look through and make sure any of our answers are newest-only # 2nd stage handling of obsoletes. If provider is # obsolete, check that a pkg which obsoletes it, # is newest. thispkgobsdict = {} if haveCheckForObsolete: try: thispkgobsdict = self.up.checkForObsolete([po.pkgtup]) if thispkgobsdict.has_key(po.pkgtup): for pkgtup in thispkgobsdict[po.pkgtup]: if pkgtup in pkgtuplist and pkgtup in resolve_sack.simplePkgList(): resolved_by_newest = True break # print "OBSOLETE PROVIDER:", po.pkgtup continue # Obsoletes without Provides except AttributeError: pass if resolved_by_newest: break if po.pkgtup in pkgtuplist: if not self.rawhidehack: resolved_by_newest = True break # In F11 devel i386 pkgs are updated by i586. # In F12 devel i586 pkgs are updated by i686. elif (po.pkgtup[1]!='i386' or po.pkgtup[1]!='i586') or not self.hasupdate(po): resolved_by_newest = True break if resolved_by_newest: resolved[(req,flags,ver)] = 1 else: if newest and not self.isnewest(pkg): break if not unresolved.has_key(pkg): unresolved[pkg] = [] unresolved[pkg].append((req, flags, ver)) return unresolved def log(self, value, msg): pass # originally taken from mash's spam-o-matic # but enhanced def libmunge_fwd(match): if match.groups()[1].isdigit(): return "%s%d" % (match.groups()[0],int(match.groups()[1])+1) else: return "%s%s" % (match.groups()[0],match.groups()[1]) def libmunge_bwd(match): if match.groups()[1].isdigit(): return "%s%d" % (match.groups()[0],int(match.groups()[1])-1) else: return "%s%s" % (match.groups()[0],match.groups()[1]) # taken from mash's spam-o-matic def getSrcPkg(pkg): if pkg.arch == 'src': return pkg.name srpm = pkg.returnSimple('sourcerpm') if not srpm: return None srcpkg = string.join(srpm.split('-')[:-2],'-') return srcpkg # adapted from mash's spam-o-matic def getRelated(resolver, dep): # Given a dep, find potential responsible parties list = [] def __addpackages(sack): for package in sack.returnPackages(): p = getSrcPkg(package) if p not in list: list.append(p) # Something that provides the dep __addpackages(resolver.whatProvides(dep, None, None)) # Libraries: check for variant in soname if re.match("lib.*\.so\.[0-9]+",dep): new = re.sub("(lib.*\.so\.)([0-9]+)",libmunge_fwd,dep) __addpackages(resolver.whatProvides(new, None, None)) new = re.sub("(lib.*\.so\.)([0-9]+)",libmunge_bwd,dep) __addpackages(resolver.whatProvides(new, None, None)) libname = dep.split('.')[0] __addpackages(resolver.whatProvides(libname, None, None)) return list def main(): (opts, cruft) = parseArgs() if yum.__version__ < '3.2.24': if len(opts.arch)>1: print 'ERROR: can handle only a single arch with Yum < 3.2.24' sys.exit(errno.EINVAL) my = RepoClosure(arch = opts.arch, config = opts.config) my.guessMultiLibProbs = not opts.nomultilibhack my.rawhidehack = opts.rawhidehack if opts.repoid: for repo in my.repos.repos.values(): if repo.id not in opts.repoid: repo.disable() else: repo.enable() if os.geteuid() != 0 or opts.tempcache or opts.cachedir != '': if opts.cachedir != '': cachedir = opts.cachedir else: cachedir = getCacheDir() if cachedir is None: print "Error: Could not make cachedir, exiting" sys.exit(50) my.repos.setCacheDir(cachedir) if not opts.quiet: print 'Reading in repository metadata - please wait....' try: my.readMetadata() except yum.Errors.RepoError, e: print e sys.exit(1) if not opts.quiet: print 'Checking Dependencies' baddeps = my.getBrokenDeps(opts.newest) num = my.numpkgs repos = my.repos.listEnabled() if not opts.quiet: print 'Repos looked at: %s' % len(repos) for repo in repos: print ' %s' % repo print 'Num Packages in Repos: %s' % num pkgs = baddeps.keys() def sortbyname(a,b): return cmp(a.__str__(),b.__str__()) pkgs.sort(sortbyname) for pkg in pkgs: srcrpm = pkg.returnSimple('sourcerpm') print 'source rpm: %s' % srcrpm related = [] print 'package: %s from %s' % (pkg, pkg.repoid) print ' unresolved deps:' for (n, f, v) in baddeps[pkg]: for relsrcname in getRelated(my,n): if relsrcname not in related: related.append(relsrcname) req = '%s' % n if f: flag = LETTERFLAGS[f] req = '%s %s'% (req, flag) if v: req = '%s %s' % (req, v) print ' %s' % req if len(related): print 'related pkgs:' for i in related: print ' %s' % i print if __name__ == "__main__": main()