#!/usr/bin/python -t # -*- mode: Python; indent-tabs-mode: nil; coding: utf-8 -*- # # Copyright (c) 2005-2012 Fedora Project # # 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 re import subprocess import sys import textwrap import time from optparse import OptionParser __version__ = "1.0.11" class BumpSpecError(Exception): pass class SpecFile: def __init__(self, filename, verbose=False, string=None): self.verbose = verbose self.string = string self.filename = filename f = None try: f = open(filename,"r") self.lines = f.readlines() finally: f and f.close() _bump_patterns = ( re.compile(r"^Release\s*:\s*(\d+.*)", re.I), re.compile(r"^%(?:define|global)\s+(?i)release\s+(\d+.*)"), re.compile(r"^Release\s*:\s+%release_func\s+(\d+.*)", re.I), re.compile(r"^%(?:define|global)\s+(?i)baserelease\s+(\d+.*)"), ) _skip_patterns = ( re.compile(r"\$Revision:"), ) def bumpRelease(self): bumped = False for i in range(len(self.lines)): skipped = False for pattern in SpecFile._skip_patterns: if pattern.search(self.lines[i]): skipped = True break if skipped: continue for pattern in SpecFile._bump_patterns: (self.lines[i], n) = \ pattern.subn(self.increase, self.lines[i], 1) bumped = bumped or n if bumped: return # Here, no line matched at all. # Happens with macro-overloaded spec files e.g. # Bump ^Release: ... line least-significant. for i in range(len(self.lines)): if self.lines[i].startswith('Release:'): old = self.lines[i][len('Release:'):].rstrip() new = self.increaseFallback(old) if self.verbose: self.debugdiff(old, new) if old != new: self.lines[i] = self.lines[i].replace(old, new) bumped = True if bumped: return if self.verbose: sys.stderr.write('ERROR: No release value matched: %s\n' % self.filename) sys.exit(1) _changelog_pattern = re.compile(r"^%changelog(\s|$)") def addChangelogEntry(self, evr, entry, email): for i in range(len(self.lines)): if SpecFile._changelog_pattern.match(self.lines[i]): if len(evr): evrstring = ' - %s' % evr else: evrstring = '' date = time.strftime("%a %b %d %Y", time.gmtime()) newchangelogentry = "* %s %s%s\n%s\n\n" % \ (date, email, evrstring, entry) self.lines[i] += newchangelogentry return _main_pre_pattern = re.compile(r'^0\.(?P\d+)(?P.*)') _main_pattern = re.compile(r'^(?P\d+)(?P.*)') def increaseMain(self, release): if release.startswith('0.'): relre = SpecFile._main_pre_pattern pre = True else: relre = SpecFile._main_pattern pre = False relmatch = relre.search(release) if not relmatch: # pattern match failed raise BumpSpecError value = str(int(relmatch.group('rel')) + 1) post = relmatch.group('post') new = value + post if not pre: if post.find('rc')>=0: sys.stderr.write( 'WARNING: Bad pre-release versioning scheme: %s\n' % self.filename) raise BumpSpecError else: new = '0.' + new return new _jpp_pattern = \ re.compile(r'(?P.*)(?P\d+)(?Pjpp\.)(?P.*)') def increaseJPP(self, release): """Fedora jpackage release versioning scheme""" relmatch = SpecFile._jpp_pattern.search(release) if not relmatch: # pattern match failed raise BumpSpecError prefix = relmatch.group('prefix') value = int(relmatch.group('rel')) jpp = relmatch.group('jpp') post = relmatch.group('post') newpost = self.increaseMain(post) new = prefix+str(value)+jpp+newpost return new def increaseFallback(self, release): """bump trailing . or add .1""" string = self.string if string is None: string = "" relre = re.compile(r'(?P.+\.)' + re.escape(string) + r'(?P\d+$)') relmatch = relre.search(release) if relmatch: prefix = relmatch.group('prefix') post = relmatch.group('post') new = prefix + string + self.increaseMain(post) else: new = release.rstrip() + '.' + string + '1' return new def increase(self, match): old = match.group(1) # only the release value try: if self.string is not None: new = self.increaseFallback(old) elif old.find('jpp')>0: new = self.increaseJPP(old) else: new = self.increaseMain(old) except BumpSpecError: new = self.increaseFallback(old) if self.verbose: self.debugdiff(old, new) # group 0 is the full line that defines the release return match.group(0).replace(old, new) def writeFile(self, filename): f = open(filename, "w") f.writelines(self.lines) f.close() def debugdiff(self, old, new): print ('%s\n-%s\n+%s\n' % (self.filename, old, new)) if __name__ == "__main__": usage = '''Usage: %prog [OPTION]... SPECFILE... rpmdev-bumpspec bumps release tags in specfiles.''' version = '''rpmdev-bumpspec version %s Copyright (c) 2005-2010 Fedora Project 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.''' % __version__ userstring = subprocess.Popen("rpmdev-packager 2>/dev/null", shell = True, stdout = subprocess.PIPE).communicate()[0] userstring = userstring.strip() or None parser = OptionParser(usage=usage) parser.add_option("-c", "--comment", default='- rebuilt', help="changelog comment (default: \"- rebuilt\")") parser.add_option("-u", "--userstring", default=userstring, help="user name+email string (default: output from "+ "rpmdev-packager(1))") parser.add_option("-r", "--rightmost", default=False, action='store_true', help="bump trailing . component if found, " "append .1 if not; no-op if -s is specified") parser.add_option("-s", "--string", default=None, help="bump trailing .STRING component if found, " "append .STRING1 if not; trumps -r") parser.add_option("-V", "--verbose", default=False, action='store_true', help="more output") parser.add_option("-v", "--version", default=False, action='store_true', help="output version number and exit") (opts, args) = parser.parse_args() if opts.version: print (version) sys.exit(0) if not args: parser.error('No specfiles specified') if not opts.userstring: parser.error('Userstring required, see option -u') # Grab bullet, insert one if not found. bullet_re = re.compile(r'^([^\s\w])\s', re.UNICODE) bullet = "-" match = bullet_re.search(opts.comment) if match: bullet = match.group(1) else: opts.comment = bullet + " " + opts.comment # Format comment. if opts.comment.find("\n") == -1: wrapopts = { "subsequent_indent": (len(bullet)+1) * " ", "break_long_words": False } if sys.version_info[:2] > (2, 5): wrapopts["break_on_hyphens"] = False opts.comment = textwrap.fill(opts.comment, 80, **wrapopts) # Prepare release component string. string = opts.string if string is None and opts.rightmost: string = "" for aspec in args: try: s = SpecFile(aspec, opts.verbose, string) except: # Not actually a parser error, but... meh. parser.error(sys.exc_info()[1]) s.bumpRelease() s.writeFile(aspec) # Get EVR for changelog entry. cmd = ("rpm", "-q", "--specfile", "--define", "dist %{nil}", "--qf=%|epoch?{%{epoch}:}:{}|%{version}-%{release}\n", aspec) popen = subprocess.Popen(cmd, stdout = subprocess.PIPE) evr = str(popen.communicate()[0]).split("\n")[0] s.addChangelogEntry(evr, opts.comment, opts.userstring) s.writeFile(aspec) sys.exit(0)