repoclosure.py 8.64 KB
Newer Older
1
#!/usr/bin/python -t
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

# 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.
# seth vidal 2005 (c) etc etc


#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
25

26
import logging
27 28 29
import yum
import yum.Errors
from yum.misc import getCacheDir
30 31
from optparse import OptionParser
import rpmUtils.arch
32
from yum.constants import *
33
from yum.packageSack import ListPackageSack
34

35
def parseArgs():
36 37 38 39 40 41 42
    usage = """
    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
    
    %s [-c <config file>] [-a <arch>] [-r <repoid>] [-r <repoid2>]
    """ % sys.argv[0]
43
    parser = OptionParser(usage=usage)
44
    parser.add_option("-c", "--config", default='/etc/yum.conf',
45
        help='config file to use (defaults to /etc/yum.conf)')
46 47 48 49 50
    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("-b", "--builddeps", default=False, action="store_true",
        help='check build dependencies only (needs source repos enabled)')
51 52 53 54 55 56
    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("-q", "--quiet", default=0, action="store_true", 
                      help="quiet (no output to stderr)")
57 58
    parser.add_option("-n", "--newest", default=0, action="store_true",
                      help="check only the newest packages in the repos")
59 60
    parser.add_option("--repofrompath", action="append",
                      help="specify repoid & paths of additional repositories - unique repoid and complete path required, can be specified multiple times. Example. --repofrompath=myrepo,/path/to/repo")
61 62
    (opts, args) = parser.parse_args()
    return (opts, args)
63

64
class RepoClosure(yum.YumBase):
65
    def __init__(self, arch = [], config = "/etc/yum.conf", builddeps = False):
66
        yum.YumBase.__init__(self)
67
        self.logger = logging.getLogger("yum.verbose.repoclosure")
68
        self.arch = arch
69
        self.builddeps = builddeps
70
        self.doConfigSetup(fn = config,init_plugins=False)
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
        if hasattr(self.repos, 'sqlite'):
            self.repos.sqlite = False
            self.repos._selectSackType()
    
    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
    
        val = ''
        if e is not None:
            val = '%s:%s' % (e, v)
    
        if r is not None:
            val = '%s-%s' % (val, r)
    
        return val
    
    def readMetadata(self):
        self.doRepoSetup()
95 96 97 98 99 100 101 102 103
        archs = []
        if not self.arch:
            archs.extend(rpmUtils.arch.getArchList())
        else:
            for arch in self.arch:
                archs.extend(rpmUtils.arch.getArchList(arch))
        if self.builddeps and 'src' not in archs:
            archs.append('src')
        self.doSackSetup(archs)
104
        for repo in self.repos.listEnabled():
105
            self.repos.populateSack(which=[repo.id], mdtype='filelists')
106

107
    def getBrokenDeps(self, newest=False):
108 109
        unresolved = {}
        resolved = {}
110 111 112 113
        if newest:
            pkgs = self.pkgSack.returnNewestByNameArch()
        else:
            pkgs = self.pkgSack
114 115 116

        mypkgSack = ListPackageSack(pkgs)
        pkgtuplist = mypkgSack.simplePkgList()
117
        
118 119 120
        if self.builddeps:
            pkgs = filter(lambda x: x.arch == 'src', pkgs)

121
        for pkg in pkgs:
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
            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 not unresolved.has_key(pkg):
                        unresolved[pkg] = []
                    unresolved[pkg].append((req, flags, ver))
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
                    continue
                    
                if newest:
                    resolved_by_newest = False
                    for po in resolve_sack:# look through and make sure all our answers are newest-only
                        if po.pkgtup in pkgtuplist:
                            resolved_by_newest = True
                            break

                    if resolved_by_newest:                    
                        resolved[(req,flags,ver)] = 1
                    else:
                        if not unresolved.has_key(pkg):
                            unresolved[pkg] = []
                        unresolved[pkg].append((req, flags, ver))                        
                        
153 154
        return unresolved
    
155

156 157
def main():
    (opts, cruft) = parseArgs()
158
    my = RepoClosure(arch = opts.arch, config = opts.config, builddeps = opts.builddeps)
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176

    if opts.repofrompath:
        # setup the fake repos
        for repo in opts.repofrompath:
            repoid,repopath = tuple(repo.split(','))
            if repopath[0] == '/':
                baseurl = 'file://' + repopath
            else:
                baseurl = repopath
                
            repopath = os.path.normpath(repopath)
            newrepo = yum.yumRepo.YumRepository(repoid)
            newrepo.name = repopath
            newrepo.baseurl = baseurl
            newrepo.basecachedir = my.conf.cachedir
            my.repos.add(newrepo)
            my.repos.enableRepo(newrepo.id)
            my.logger.info( "Added %s repo from %s" % (repoid,repopath))
177
    
178
    if opts.repoid:
179
        for repo in my.repos.repos.values():
180
            if repo.id not in opts.repoid:
181 182 183 184
                repo.disable()
            else:
                repo.enable()

185 186 187
    if os.geteuid() != 0 or opts.tempcache:
        cachedir = getCacheDir()
        if cachedir is None:
188
            my.logger.error("Error: Could not make cachedir, exiting")
189 190 191
            sys.exit(50)
            
        my.repos.setCacheDir(cachedir)
192

193
    if not opts.quiet:
194
        my.logger.info('Reading in repository metadata - please wait....')
195 196 197 198

    try:
        my.readMetadata()
    except yum.Errors.RepoError, e:
199
        my.logger.info(e)
200 201
        my.logger.info('Some dependencies may not be complete for this repository')
        my.logger.info('Run as root to get all dependencies or use -t to enable a user temp cache')
202

203
    if not opts.quiet:
204
        my.logger.info('Checking Dependencies')
205

206 207 208 209 210 211
    baddeps = my.getBrokenDeps(opts.newest)
    if opts.newest:
        num = len(my.pkgSack.returnNewestByNameArch())
    else:
        num = len(my.pkgSack)
        
212
    repos = my.repos.listEnabled()
213 214

    if not opts.quiet:
215
        my.logger.info('Repos looked at: %s' % len(repos))
216
        for repo in repos:
217 218
            my.logger.info('   %s' % repo)
        my.logger.info('Num Packages in Repos: %s' % num)
219
    
220
    pkgs = baddeps.keys()
221 222 223 224 225 226
    
    def sortbyname(a,b):
        return cmp(a.__str__(),b.__str__())
    
    pkgs.sort(sortbyname)
    
227
    for pkg in pkgs:
228
        my.logger.info('package: %s from %s\n  unresolved deps: ' % (pkg, pkg.repoid))
229
        for (n, f, v) in baddeps[pkg]:
230 231
            req = '%s' % n
            if f: 
232
                flag = LETTERFLAGS[f]
233 234 235 236
                req = '%s %s'% (req, flag)
            if v:
                req = '%s %s' % (req, v)
            
237
            my.logger.info('     %s' % req)
238

239
if __name__ == "__main__":
240
    main()
241