#!/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 import errno, os, sys import glob, re import rpm, rpmUtils.transaction, rpmUtils.miscutils import Utils, RepoSupport import yum, yum.Errors yumver = yum.__version__ from yum.packages import YumLocalPackage # Note: older Yum doesn't offer yum.__version_info__ for tuple comparison if cmp(yumver,'2.9')<0: # yum < 2.9 from repomd.packageSack import ListPackageSack else: from yum.packageSack import ListPackageSack # The following dict can be generated with rpmUtils.arch functions. # target arch -> compatible archs compatarchdict = { 'x86_64' : ['x86_64', 'athlon', 'i686', 'i586', 'i486', 'i386', 'noarch'], 'ppc' : ['ppc64', 'ppc', 'noarch'], } biarchdict = { 'x86_64' : 'i386', 'ppc' : 'ppc64' } # map: target arch -> 'debug','rpms' -> list of new multicompat pkgs # to be installed/linked/copied and resolved later in a second pass missingdict = {} def isRejectedName(cfg,dist,targetarch,name): """Check a single package name against black-list. Returns: True or False""" if cfg.multiblacklists.has_key(dist) and cfg.multiblacklists[dist].has_key(targetarch): for r in cfg.multiblacklists[dist][targetarch]: if re.compile('^'+r+'$').search(name): return True return False # accept by default def isWantedName(cfg,dist,targetarch,namelist): """Check a single package name against black-list and white-list. Since a package can have virtual names, this takes a list of names as input. Black-list overrides white-list. Returns: True or False""" for n in namelist: if isRejectedName(cfg,dist,targetarch,n): return False if cfg.multiwhitelists.has_key(dist) and cfg.multiwhitelists[dist].has_key(targetarch): for r in cfg.multiwhitelists[dist][targetarch]: if re.compile('^'+r+'$').search(n): return True return False # reject by default def pkgtuple(pkg): if cmp(yumver,'2.9.6')<0: # < 2.9.6 (n,a,e,v,r) = pkg.returnPackageTuple() else: (n,a,e,v,r) = pkg.pkgtup return (n,a,e,v,r) def evalPackage(cfg,ts,fname,dist, srpmlocdict): # Examine the package, see whether we want it for any of the # enabled multilib repositories. Then store the package names # in a map, which is used in pass 2 (install+resolve deps). for targetarch in cfg.archdict[dist]: # list of repo archs if not compatarchdict.has_key(targetarch): continue # don't multilib this repo (targetarch) pkg = YumLocalPackage(ts=ts,filename=fname) srcrpm = pkg.tagByName('sourcerpm') (n,a,e,v,r) = pkgtuple(pkg) (sn,sv,sr,se,sa) = rpmUtils.miscutils.splitFilename(srcrpm) debugrpm = '%s-debuginfo-%s-%s.%s.rpm' % (sn,sv,sr,a) if a == targetarch or a == 'noarch': # installed by main push script continue if a not in compatarchdict[targetarch]: # incompatible pkg arch continue names = [n] if not n.endswith('-devel'): # packageObject in yum-2.6.0-1 is broken: no such attr 'prco' #for (prov, flag, (prove, provv, provr)) in pkg.returnPrco('provides'): hdr = rpmUtils.miscutils.hdrFromPackage(ts,fname) for prov in hdr['provides']: if prov.endswith('-devel'): names.append(prov) if not isWantedName(cfg,dist,targetarch,names): continue srpmloc = os.path.join( Utils.srpm_repodir(cfg,dist), srcrpm ) #if DEBUG and not os.path.isfile(srpmloc): # srpmloc = srpmlocdict[srcrpm] try: hdr = rpmUtils.miscutils.hdrFromPackage(ts,srpmloc) excludearch = hdr['excludearch'] except rpmUtils.RpmUtilsError, e: # source rpm not there or bad print 'ERROR: %s' % ''.join(e) print srpmloc print 'referenced by: %s' % fname # TODO: We don't want such builds! Do something about it. # This is normal for multi-lib, where a test update pulls in # deps from stable as long as it stays in updates-testing. srcrpm = None excludearch = [] if not srcrpm: continue pkgbasefn = os.path.basename(fname) if targetarch in excludearch: print ' -', pkgbasefn, ': Multi-compat, but ExcludeArch %s' % excludearch continue # Save base file-names here. missingdict.setdefault(targetarch,{}) pkglist = missingdict[targetarch].setdefault('rpms',[]) if pkgbasefn not in pkglist: pkglist.append(pkgbasefn) missingdict[targetarch]['rpms'] = pkglist # print ' +', pkgbasefn pkglist = missingdict[targetarch].setdefault('debug',[]) # Don't multilib debuginfo rpms (not done in base Fedora either). # if debugrpm not in pkglist: # pkglist.append(debugrpm) # missingdict[targetarch]['debug'] = pkglist return def evalFromTree(cfg,dist): if dist not in cfg.multiwhitelists.keys(): return # MultiLib.py not enabled for this dist ts = rpmUtils.transaction.initReadOnlyTransaction() global missingdict missingdict = {} for targetarch in biarchdict.keys(): if targetarch not in cfg.multiwhitelists[dist].keys(): continue # don't multilib this repo arch srcarch = biarchdict[targetarch] srcdir = Utils.rpm_repodir(cfg,dist,srcarch) print 'Scanning for multi-compat packages:', srcdir files = glob.glob('%s/*.rpm' % srcdir) for f in files: evalPackage(cfg,ts,f,dist,{}) return # -------------------------------------------------------------------- class Resolver(yum.YumBase): def __init__(self, arch, config, pushcfg, dist, targetarch): yum.YumBase.__init__(self) print arch, '=>', targetarch self.arch = arch self.doConfigSetup(fn = config) if hasattr(self.repos, 'sqlite'): self.repos.sqlite = False self.repos._selectSackType() self.targetarch = targetarch if targetarch=='ppc': self.compatbasearch = 'ppc64' else: self.compatbasearch = targetarch self.pushcfg = pushcfg self.dist = dist self.resolved = {} self.seenbefore = [] self.needed = {} def readMetadata(self): self.doRepoSetup() self.doSackSetup( self.arch ) for repo in self.repos.listEnabled(): if cmp(yumver,'3.1.1')>0: # > 3.1.1 self.repos.populateSack(which=[repo.id], mdtype='filelists') # since 3.1.1 else: # named 'with' arg is reserved keyword in Python 2.6 self.repos.populateSack([repo.id], 'filelists') def addNeededPkg(self,pkg): self.needed.setdefault(pkg.repoid,{}) self.needed[pkg.repoid].setdefault('rpms',[]) self.needed[pkg.repoid].setdefault('debug',[]) (n,a,e,v,r) = pkgtuple(pkg) file = '%s-%s-%s.%s.rpm' % (n,v,r,a) if file not in self.needed[pkg.repoid]['rpms']: self.needed[pkg.repoid]['rpms'].append(file) print ' +', file # Don't multilib debuginfo rpms (not done in base Fedora either). # binarch = a # srcrpm = pkg.returnSimple('sourcerpm') # (n,v,r,e,a) = rpmUtils.miscutils.splitFilename(srcrpm) # debugrpm = '%s-debuginfo-%s-%s.%s.rpm' % (n,v,r,binarch) # if debugrpm not in self.needed[pkg.repoid]['debug']: # self.needed[pkg.repoid]['debug'].append(debugrpm) # print ' +', debugrpm def resolveRequires(self,pkg): if pkg.__str__() in self.seenbefore: #print " ! SEEN BEFORE" return self.seenbefore.append( pkg.__str__() ) # yum-2.6.0-1 # for (req,flags,(reqe,reqv,reqr)) in pkg.returnPrco('requires'): # File "packageObject.py", line 224, in returnPrco #AttributeError: YumLocalPackage instance has no attribute 'prco' #for (req,flags,evr) in getRequires( ... ): for dep in pkg.requiresList(): if dep.startswith('rpmlib'): continue # ignore rpmlib deps if self.resolved.has_key(dep): continue # resolved before results = [] try: results = self.returnPackagesByDep(dep) results = self.bestPackagesFromList(results,self.compatbasearch) except: pass if len(results) < 1: # unresolved, most likely in Core #print 'UNRESOLVED:', dep continue self.resolved[dep] = True # Prefer self-requires (pkg requires something provided by pkg). insamepkg = False for p in results: if pkgtuple(p) == pkgtuple(pkg): insamepkg = True self.addNeededPkg(pkg) self.resolveRequires(pkg) break # got a provider if insamepkg: continue for p in results: if not isRejectedName(self.pushcfg,self.dist,self.targetarch,p.returnSimple('name')): self.addNeededPkg(p) self.resolveRequires(p) break # got a provider def log(self, value, msg): pass def resolveMissing(cfg,dist,targetarch): if not missingdict.has_key(targetarch): return False missingdict[targetarch].setdefault('rpms',[]) missingdict[targetarch].setdefault('debug',[]) dotesting = dist.startswith('testing/') testdist = dist if dotesting: dist = dist.replace('testing/','') rs = RepoSupport.RepoSupport(cfg) conf = rs.GenerateConfig([dist]) repoids = [] srcarch = biarchdict[targetarch] repolist = rs.ReleaseRepoList(dist) if dotesting: repolist += rs.TestRepoList(dist) for r in repolist: if r.find(cfg.project) < 0: # only look at our project's repos continue repoids.append(rs.RepoId(r,dist,srcarch)) repoids.append(rs.RepoId(r,dist,targetarch)) print repoids archlist = compatarchdict[targetarch] my = Resolver(arch = archlist, config = conf, pushcfg=cfg, dist=testdist, targetarch=targetarch) os.remove(conf) for repo in my.repos.repos.values(): if repo.id not in repoids: repo.disable() else: repo.enable() usercachedir = hasattr(cfg.opts,'usercachedir') and cfg.opts.usercachedir if usercachedir: cachedir = '%s-uid-%i' % (cfg.cachedir, os.geteuid()) else: cachedir = '%s-%s-%s-%s' % (cfg.cachedir, cfg.distro, dist, srcarch) my.repos.setCacheDir(cachedir) if not usercachedir: Utils.fix_mdcache_access(cfg.rundir,cachedir) try: print 'Reading metadata...' my.readMetadata() except yum.Errors.RepoError, e: print 'Yum error: %s' % e # TODO: This is only bad if it's something a re-run doesn't fix automatically. except: if not usercachedir: Utils.fix_mdcache_access(cfg.rundir,cachedir) raise print 'done.' # Skip this 2nd run for now. # It leaves the mdcache dirs with mode 0755, though. # if not usercachedir: # Utils.fix_mdcache_access(cfg.rundir,cachedir) srcrepodir = Utils.rpm_repodir(cfg, testdist, srcarch) srcdebugrepodir = Utils.debug_repodir(cfg, testdist, srcarch) destrepodir = Utils.rpm_repodir(cfg, testdist, targetarch) destdebugrepodir = Utils.debug_repodir(cfg, testdist, targetarch) extdestrepodir = Utils.rpm_repodir(cfg, dist, targetarch) extdestdebugrepodir = Utils.debug_repodir(cfg, dist, targetarch) ts = rpmUtils.transaction.initReadOnlyTransaction() for f in missingdict[targetarch]['rpms']: print f fpath = os.path.join(srcrepodir,f) pkg = YumLocalPackage(ts=ts,filename=fpath) my.resolveRequires(pkg) changed = False print 'Installing needed packages for %s:' % targetarch def addrpm(f,src,dest): srcfile = os.path.join(src,f) destfile = os.path.join(dest,f) if not os.path.exists(destfile): # silent extra-check print ' ', os.path.basename(destfile) if not os.path.exists(srcfile): if srcfile.find('-debuginfo-')<0: # Don't warn about missing debuginfo pkgs. print 'WARNING: missing', srcfile return False Utils.install_link_or_copy(srcfile,destfile) return True else: return False for f in missingdict[targetarch]['rpms']: if addrpm(f,srcrepodir,destrepodir): changed = True for repoid in my.needed.keys(): if repoid.find('testing')<0: # Make available new multi-lib deps from non-testing repo. srcrepodir = Utils.rpm_repodir(cfg,dist,srcarch) addfromext = True else: addfromext = False for f in my.needed[repoid]['rpms']: if addfromext: if os.path.exists(os.path.join(extdestrepodir,f)): continue if addrpm(f,srcrepodir,destrepodir): changed = True for f in my.needed[repoid]['debug']: if addfromext: if os.path.exists(os.path.join(extdestdebugrepodir,f)): continue if addrpm(f,srcdebugrepodir,destdebugrepodir): changed = True print 'done.' return changed # -------------------------------------------------------------------- def main(cfg,dists): if hasattr(cfg,'multilibdists'): print 'WARNING: Config value "multilibdists" is obsolete.' for dist in dists: evalFromTree(cfg,dist) for arch in cfg.archdict[dist]: # list of repo archs changedagain = resolveMissing(cfg,dist,arch) if changedagain: Utils.create_repository(cfg, dist, Utils.rpm_repodir(cfg,dist,arch), arch=arch) Utils.create_repository(cfg, dist, Utils.debug_repodir(cfg,dist,arch), debuginfo=True, nodeltas=True) if __name__ == '__main__': if len(sys.argv) < 3: print 'Usage: %s [release]...\n' % os.path.basename(sys.argv[0]) sys.exit(errno.EINVAL) cfg = Utils.load_config_module(sys.argv[1]) Utils.signer_gid_check(cfg.signersgid) os.umask(cfg.signersumask) main(cfg,sys.argv[2:]) sys.exit(0)