#!/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 datetime, fnmatch, pickle import rpmUtils.transaction, rpmUtils.miscutils import Utils # Map of maps: dist -> src.rpm %name -> list of changelog 3-tuples names = {} # Map of maps: dist -> pkgid -> list of formatted clog lines clogdiffs = {} # The 'names' dict is used to maintain a pickle db of known # srcr.rpm %name values and their newest %changelog entries. # The 'clogdiffs' dict is filled when new packages are published # and is cleared after sending a build report. def stripold(rpmfiles): ts = rpmUtils.transaction.initReadOnlyTransaction() newestrpms = {} for f in rpmfiles: try: hdr = rpmUtils.miscutils.hdrFromPackage(ts,f) except: print "Unable to open %s" % f else: n = hdr['name'] v = hdr['version'] r = hdr['release'] e = hdr['epoch'] if e is None: e = 0 newestrpms.setdefault(n,[]) newestrpms[n].append((f,n,e,v,r)) for l in newestrpms.values(): x = len(l) if x > 1: def sortByEVR(fnevr1, fnevr2): (f1,n1,e1,v1,r1) = fnevr1 (f2,n2,e2,v2,r2) = fnevr2 rc = rpmUtils.miscutils.compareEVR((e1,v1,r1),(e2,v2,r2)) if rc == 0: return 0 if rc > 0: return -1 if rc < 0: return 1 l.sort(sortByEVR) # highest first in list oldies = [] if len(l) > 1: oldies = l[1:] for (f,n,e,v,r) in oldies: #print ' Ignoring', os.path.basename(f) rpmfiles.remove(f) def rebuild(dist,srcdirs): """initialise the pickle files""" ts = rpmUtils.transaction.initReadOnlyTransaction() rpmfiles = [] for srcdir in srcdirs: for root, dirs, files in os.walk(srcdir): srpms = [] srpms.extend(fnmatch.filter(files,'*.src.rpm')) srpms.extend(fnmatch.filter(files,'*.nosrc.rpm')) for f in srpms: srcfile = os.path.join(root,f) rpmfiles.append(srcfile) stripold(rpmfiles) for srcfile in rpmfiles: hdr = rpmUtils.miscutils.hdrFromPackage(ts,srcfile) put(dist,srcfile,ts) def putname(dist,name): """mark the name of this source rpm as seen before""" names.setdefault(dist,{}) names[dist].setdefault(name,[]) def queryname(dist,name): """query whether source rpm name is known, returns True or False""" instable = names.has_key(dist) and names[dist].has_key(name) if dist.startswith('testing/'): dist = dist.replace('testing/', '') intesting = names.has_key(dist) and names[dist].has_key(name) return (instable or intesting) else: return instable def put(dist,srcrpm,ts): """mark the name of this source rpm as seen before and collect most recent changelog data""" hdr = rpmUtils.miscutils.hdrFromPackage(ts,srcrpm) name = hdr['name'] putclog(dist,name,readclog(srcrpm,ts)) def putclog(dist,name,clogtuples): """update a package's changelog data input: zipped RPM changelog tuples""" tosave = [] # changelog tuples to store if len(clogtuples): reftime = clogtuples[0][1] # only save entries with highest timestamp for (clogname, clogtime, clogtext) in clogtuples: if clogtime >= reftime: tosave.append( (clogname,clogtime,clogtext) ) else: break putname(dist,name) names[dist][name] = tosave def getclog(dist,name): """get the list of most recent changelog 3-tuples for this package, if any have been stored in the WhatsNew db before""" def _getclog(dist,name): if not names.has_key(dist): return [] return names[dist].get(name,[]) if dist.startswith('testing/'): testingclog = _getclog(dist,name) if len(testingclog): return testingclog # inherit changelogs from stable repo dist = dist.replace('testing/', '') return _getclog(dist,name) def formatclog(clogtuples): """format a list of changelog 3-tuples to a printable string""" clog = [] if len(clogtuples): for (clogname, clogtime, clogtext) in clogtuples: datestr = datetime.date.fromtimestamp(clogtime).strftime("%a %b %d %Y") line = '* %s %s\n%s\n\n' % (datestr,clogname,clogtext) clog.append(line) return ''.join(clog) def readclog(srcrpm,ts): """return a list of low-level changelog 3-tuples""" if not srcrpm: return [] hdr = rpmUtils.miscutils.hdrFromPackage(ts,srcrpm) if not len(hdr['changelogname']): return [] ctimes = hdr['changelogtime'] if isinstance(ctimes,int): # pitfall in RPM ctimes = [ctimes] return zip(hdr['changelogname'],ctimes,hdr['changelogtext']) def getclogdiff(dist,name,rpm,ts): """compares the changelog entries in a given rpm with what is stored in the WhatsNew db and returns new entries in printable form""" oldclog = getclog(dist,name) newclog = readclog(rpm,ts) if len(oldclog): reftime = datetime.date.fromtimestamp(oldclog[0][1]) # only get entries at least as new as this haveold = True else: reftime = datetime.date.today()-datetime.timedelta( days = 30 ) haveold = False # TODO: limit max.number of entries? tosave = [] if len(newclog): for clogtup in newclog: if haveold and (clogtup == oldclog[0]): break (clogname, clogtime, clogtext) = clogtup if datetime.date.fromtimestamp(clogtime) >= reftime: tosave.append( (clogname,clogtime,clogtext) ) else: break if not len(tosave) and len(newclog): # changelog entries too old, get most recent one tosave.append( newclog[0] ) return formatclog(tosave) def putclogdiff(dist,pkgid,clogdiff): """store a pair of package id and printable changelog entries""" clogdiffs.setdefault(dist,{}) clogdiffs[dist].setdefault(pkgid,clogdiff) def getallclogdiffs(dist): rv = '' if not clogdiffs.has_key(dist): return rv pkgids = clogdiffs[dist].keys() def cmplc(a,b): # case-insensitive cmp for Python < 2.4 sort() return cmp(a.lower(),b.lower()) pkgids.sort(cmplc) for pkgid in pkgids: clog = clogdiffs[dist][pkgid] if not len(clog): clog = '\n' rv += '%s\n' % pkgid +'-'*len(pkgid)+'\n'+clog return rv def clearclogdiffs(workdir,dist): load(workdir) clogdiffs.setdefault(dist,{}) clogdiffs[dist] = {} save(workdir) def save(workdir): """save WhatNew run-time data to disk""" f = file(os.path.join(workdir,'whatsnewdict.pickle'),'w') pickle.dump(names,f) pickle.dump(clogdiffs,f) f.close() def load(workdir): """load WhatsNew run-time data from disk""" global names, clogdiffs names = {} clogdiffs = {} try: f = file(os.path.join(workdir,'whatsnewdict.pickle'),'r') except: return names = pickle.load(f) clogdiffs = pickle.load(f) f.close() def main(cfg): global names, clogdiffs names = {} clogdiffs = {} for dist in cfg.alldists: srcdirs = [Utils.srpm_repodir( cfg, dist )] # Consider the oldpackagedirs map to inherit src.rpms from # any external release repos. # It is assumed that the 'source/SRPMS' folder is found at # the same directory level than the $arch folders. if hasattr(cfg,'oldpackagedirs'): dirs = cfg.oldpackagedirs.get(dist,None) if not dirs or not isinstance(dirs,list): dirs = [] for dir in dirs: archpos = dir.rfind('$arch') if archpos<0: continue srcdir = dir[:archpos]+'source/SRPMS' if os.path.exists( srcdir ): srcdirs.append( srcdir ) else: print 'WARNING:', srcdir, 'does not exist!' print '[WhatsNew] Rebuilding from', srcdirs rebuild(dist,srcdirs) save(cfg.rundir) if __name__ == '__main__': if len(sys.argv) < 2: print 'Usage: %s \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.exit(0)