diff options
Diffstat (limited to 'BuildManager')
-rw-r--r-- | BuildManager/__init__.py | 24 | ||||
-rw-r--r-- | BuildManager/build.py | 231 | ||||
-rw-r--r-- | BuildManager/clean.py | 37 | ||||
-rw-r--r-- | BuildManager/fileutil.py | 187 | ||||
-rw-r--r-- | BuildManager/optionparser.py | 34 | ||||
-rw-r--r-- | BuildManager/package.py | 243 | ||||
-rw-r--r-- | BuildManager/rpmver.py | 62 |
7 files changed, 818 insertions, 0 deletions
diff --git a/BuildManager/__init__.py b/BuildManager/__init__.py new file mode 100644 index 0000000..eea669a --- /dev/null +++ b/BuildManager/__init__.py @@ -0,0 +1,24 @@ +import logging +import sys, os + +__all__ = ["Error", "logger"] + +class Error(Exception): pass + +def getlogger(): + class Formatter(logging.Formatter): + def format(self, record): + if record.levelname == "INFO": + record.llevelname = "" + else: + record.llevelname = record.levelname.lower()+": " + return logging.Formatter.format(self, record) + formatter = Formatter("%(llevelname)s%(message)s") + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(formatter) + logger = logging.getLogger("bm") + logger.addHandler(handler) + return logger + +logger = getlogger() + diff --git a/BuildManager/build.py b/BuildManager/build.py new file mode 100644 index 0000000..3bf93c5 --- /dev/null +++ b/BuildManager/build.py @@ -0,0 +1,231 @@ +from BuildManager.fileutil import * +from BuildManager.package import * +from BuildManager import * +import thread +import popen2 +import select +import fcntl +import thread +import sys, os +import time +import shutil + +__all__ = ["PackageBuilder"] + +GLOBAL_PKGLIST_LOCK = thread.allocate_lock() + +STAGE_UNPACK = 0 +STAGE_PREP = 1 +STAGE_COMPILE = 2 +STAGE_INSTALL = 3 +STAGE_SOURCE = 4 +STAGE_BINARY = 5 +STAGE_ALL = 6 + +STAGE_DICT = {"unpack": STAGE_UNPACK, + "prep": STAGE_PREP, + "compile": STAGE_COMPILE, + "install": STAGE_INSTALL, + "source": STAGE_SOURCE, + "binary": STAGE_BINARY, + "all": STAGE_ALL} + +class PackageBuilder: + def __init__(self, opts): + self.opts = opts + self.stage = STAGE_DICT[opts.mode] + + def run(self): + self.pkglist = PackageList() + logger.info("creating package list") + for filename in self.opts.args: + pkg = Package(filename, self.opts.build_log) + for ignore in self.opts.ignore: + if ignore.match(pkg.name): + break + else: + self.pkglist.append(pkg) + for dir in self.opts.filter_renew: + filterpkglist(pkglist, dir, "not_has_ge") + for dir in self.opts.filter_refresh: + filterpkglist(pkglist, dir, "not_has_lt") + self.pkgsleft = len(self.pkglist) + if self.pkgsleft == 0: + logger.info("no packages to process") + return True + elif self.pkgsleft > 1 and self.pkgsleft % 10 != 0: + logger.info("package list has %d packages" % pkgsleft) + self.pkglist_lock = thread.allocate_lock() + self.failures = 0 + if self.opts.parallel != 1: + logger.info("starting threads") + for i in range(self.opts.parallel-1): + thread.start_new_thread(self_processlist, ()) + self._processlist() + while self.pkgsleft > 0: + time.sleep(2) + return not self.failures + + def _processlist(self): + while 1: + self.pkglist_lock.acquire() + if not self.pkglist: + self.pkglist_lock.release() + return 1 + if self.pkgsleft % 10 == 0: + logger.info("package list has %d packages" % self.pkgsleft) + pkg = self.pkglist.pop(0) + logger.info("processing package %s-%s-%s" % + (pkg.name, pkg.version, pkg.release)) + self.pkglist_lock.release() + ret = buildpkg(pkg=pkg, + stage=self.stage, + unpack_dir=self.opts.unpack_dir, + passtrough=" ".join(self.opts.options), + show_log=self.opts.show_log, + dryrun=self.opts.dryrun) + if ret: + if pkg.type == "srpm": + if self.opts.move_succeeded_srpm: + move_file(pkg.file, + self.opts.move_succeeded_srpm, + dryrun=self.opts.dryrun) + elif self.opts.copy_succeeded_srpm: + copy_file(pkg.file, + self.opts.copy_succeeded_srpm, + dryrun=self.opts.dryrun) + elif self.opts.remove_succeeded_srpm: + logger.info("removing %s" % pkg.file) + if not self.opts.dryrun: + os.unlink(pkg.file) + if self.opts.move_srpm: + dir = os.path.join(pkg.builddir, "SRPMS") + for file in os.listdir(dir): + move_file(os.path.join(dir, file), + self.opts.move_srpm, + dryrun=self.opts.dryrun) + if self.opts.move_rpm: + dir = os.path.join(pkg.builddir, "RPMS") + for subdir in os.listdir(dir): + subdir = os.path.join(dir, subdir) + for file in os.listdir(subdir): + move_file(os.path.join(subdir, file), + self.opts.move_rpm, + dryrun=self.opts.dryrun) + if self.opts.move_log: + move_file(pkg.log, + self.opts.move_log, + dryrun=self.dryrun) + if self.opts.clean or self.opts.clean_on_success: + if pkg.builddir != "/": + logger.info("cleaning build directory") + if not self.opts.dryrun: + shutil.rmtree(pkg.builddir) + else: + self.failures += 1 + if pkg.type == "srpm": + if self.opts.move_failed_srpm: + move_file(pkg.file, + self.opts.move_failed_srpm, + dryrun=self.opts.dryrun) + elif self.opts.copy_failed_srpm: + copy_file(pkg.file, + self.opts.copy_failed_srpm, + dryrun=self.opts.dryrun) + elif self.opts.remove_failed_srpm: + logger.info("removing %s" % pkg.file) + if not self.opts.dryrun: + os.unlink(pkg.file) + if self.opts.move_failed_log: + move_file(pkg.log, + self.opts.move_failed_log, + dryrun=self.opts.dryrun) + if self.opts.clean: + if pkg.builddir != "/": + logger.info("cleaning build directory") + if not self.opts.dryrun: + shutil.rmtree(pkg.builddir) + self.pkglist_lock.acquire() + self.pkgsleft -= 1 + self.pkglist_lock.release() + +def filterpkglist(pkglist, directory, rule): + filterlist = PackageList() + logger.info("creating package list filter for "+directory) + for filename in os.listdir(directory): + filename = os.path.join(directory, filename) + if os.path.isfile(filename): + filterlist.append(Package(filename)) + logger.info("filtering") + if rule[:4] == "not_": + filterfunc_tmp = getattr(filterlist, rule[4:]) + filterfunc = lambda x: not filterfunc_tmp(x) + else: + filterfunc = getattr(filterlist, rule) + pkglist[:] = filter(filterfunc, pkglist) + + +def buildpkglist(pkglist, stage, unpack_dir, passtrough="", + show_log=0, dryrun=0): + while 1: + GLOBAL_PKGLIST_LOCK.acquire() + if not pkglist: + GLOBAL_PKGLIST_LOCK.release() + return 1 + pkg = pkglist[0] + del pkglist[0] + GLOBAL_PKGLIST_LOCK.release() + buildpkg(pkg, stage, unpack_dir, passtrough, show_log, dryrun) + +def buildpkg(pkg, stage, unpack_dir, passtrough="", show_log=0, dryrun=0): + stagestr = ["unpacking", + "running prep stage", + "running prep and compile stage", + "running prep, compile, and install stages", + "building source package", + "building binary packages", + "building source and binary packages"][stage] + logger.info(stagestr) + ret = 0 + if pkg.type == "srpm" and not (dryrun or pkg.unpack(unpack_dir)): + logger.error("failed unpacking") + return 0 + else: + status = 0 + if stage != STAGE_UNPACK: + stagechar = ["p","c","i","s","b","a"][stage-1] + if not dryrun and os.path.isdir(pkg.builddir+"/BUILDROOT"): + tmppath = " --define '_tmppath %s/BUILDROOT'" % pkg.builddir + else: + tmppath = "" + cmd = "rpm -b%s --define '_topdir %s'%s %s %s 2>&1" % \ + (stagechar,pkg.builddir,tmppath,passtrough,pkg.spec) + logger.debug("rpm command: "+cmd) + if not dryrun: + log = open(pkg.log, "w") + pop = popen2.Popen3(cmd) + fc = pop.fromchild + flags = fcntl.fcntl (fc.fileno(), fcntl.F_GETFL, 0) + flags = flags | os.O_NONBLOCK + fcntl.fcntl (fc.fileno(), fcntl.F_SETFL, flags) + while 1: + r,w,x = select.select([fc.fileno()], [], [], 2) + if r: + data = fc.read() + if show_log: + sys.stdout.write(data) + log.write(data) + log.flush() + status = pop.poll() + if status != -1: + break + log.close() + if status == 0: + logger.info("succeeded!") + ret = 1 + else: + logger.error("failed!") + ret = 0 + return ret + +# vim:ts=4:sw=4 diff --git a/BuildManager/clean.py b/BuildManager/clean.py new file mode 100644 index 0000000..e0e3f93 --- /dev/null +++ b/BuildManager/clean.py @@ -0,0 +1,37 @@ +from BuildManager.fileutil import * +from BuildManager.package import * +from BuildManager import * +import os + +class PackageCleaner: + def __init__(self, opts): + self.opts = opts + + def run(self): + pkglist = PackageList() + pkglist_check = PackageList() + logger.info("creating package list") + for filename in self.opts.args: + pkglist.append(Package(filename)) + if self.opts.check: + for dir in self.opts.check: + logger.info("creating package check list for "+dir) + for entry in os.listdir(dir): + entrypath = os.path.join(dir, entry) + if os.path.isfile(entrypath): + pkglist_check.append(Package(entrypath)) + logger.info("processing package list") + for pkg in pkglist[:]: + if pkglist.has_gt(pkg) or pkglist_check.has_gt(pkg): + pkglist.remove(pkg) + if self.opts.move: + move_file(pkg.file, self.opts.move, + dryrun=self.opts.dryrun) + elif self.opts.copy: + copy_file(pkg.file, self.opts.copy, + dryrun=self.opts.dryrun) + else: + logger.info("removing "+pkg.file) + if not self.opts.dryrun: + os.unlink(pkg.file) + return True diff --git a/BuildManager/fileutil.py b/BuildManager/fileutil.py new file mode 100644 index 0000000..93dd09d --- /dev/null +++ b/BuildManager/fileutil.py @@ -0,0 +1,187 @@ +# This module was originally part of distutils. +from BuildManager import * +import os + +__all__ = ["copy_file", "move_file"] + +# for generating verbose output in 'copy_file()' +_copy_action = { None: 'copying', + 'hard': 'hard linking', + 'sym': 'symbolically linking' } + + +def _copy_file_contents (src, dst, buffer_size=16*1024): + """Copy the file 'src' to 'dst'; both must be filenames. Any error + opening either file, reading from 'src', or writing to 'dst', raises + BuildManagerFileError. Data is read/written in chunks of 'buffer_size' + bytes (default 16k). No attempt is made to handle anything apart from + regular files. + """ + # Stolen from shutil module in the standard library, but with + # custom error-handling added. + + fsrc = None + fdst = None + try: + try: + fsrc = open(src, 'rb') + except os.error, (errno, errstr): + raise Error, "could not open %s: %s" % (src, errstr) + + try: + fdst = open(dst, 'wb') + except os.error, (errno, errstr): + raise Error, "could not create %s: %s" % (dst, errstr) + + while 1: + try: + buf = fsrc.read(buffer_size) + except os.error, (errno, errstr): + raise Error, "could not read from %s: %s" % (src, errstr) + + if not buf: + break + + try: + fdst.write(buf) + except os.error, (errno, errstr): + raise Error, "could not write to %s: %s" % (dst, errstr) + + finally: + if fdst: + fdst.close() + if fsrc: + fsrc.close() + +def copy_file (src, dst, preserve_mode=1, preserve_times=1, link=None, + dryrun=False): + + """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is + copied there with the same name; otherwise, it must be a filename. (If + the file exists, it will be ruthlessly clobbered.) If 'preserve_mode' + is true (the default), the file's mode (type and permission bits, or + whatever is analogous on the current platform) is copied. If + 'preserve_times' is true (the default), the last-modified and + last-access times are copied as well. + + 'link' allows you to make hard links (os.link) or symbolic links + (os.symlink) instead of copying: set it to "hard" or "sym"; if it is + None (the default), files are copied. Don't set 'link' on systems that + don't support it: 'copy_file()' doesn't check if hard or symbolic + linking is available. + + Under Mac OS, uses the native file copy function in macostools; on + other systems, uses '_copy_file_contents()' to copy file contents. + + Return a tuple (dest_name, copied): 'dest_name' is the actual name of + the output file, and 'copied' is true if the file was copied (or would + have been copied, if 'dryrun' true). + """ + from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE + + if not os.path.isfile(src): + raise Error, "can't copy '%s': " \ + "doesn't exist or not a regular file" % src + + if os.path.isdir(dst): + dir = dst + dst = os.path.join(dst, os.path.basename(src)) + else: + dir = os.path.dirname(dst) + + try: + action = _copy_action[link] + except KeyError: + raise ValueError, "invalid value '%s' for 'link' argument" % link + if os.path.basename(dst) == os.path.basename(src): + logger.info("%s %s to %s" % (action, src, dir)) + else: + logger.info("%s %s to %s" % (action, src, dst)) + + if dryrun: + return (dst, 1) + + # If linking (hard or symbolic), use the appropriate system call + # (Unix only, of course, but that's the caller's responsibility) + if link == 'hard': + if not (os.path.exists(dst) and os.path.samefile(src, dst)): + os.link(src, dst) + elif link == 'sym': + if not (os.path.exists(dst) and os.path.samefile(src, dst)): + os.symlink(src, dst) + + # Otherwise (not linking), copy the file contents and + # (optionally) copy the times and mode. + else: + _copy_file_contents(src, dst) + if preserve_mode or preserve_times: + st = os.stat(src) + if preserve_times: + os.utime(dst, (st[ST_ATIME], st[ST_MTIME])) + if preserve_mode: + os.chmod(dst, S_IMODE(st[ST_MODE])) + + return (dst, 1) + +def move_file (src, dst, dryrun=False): + + """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will + be moved into it with the same name; otherwise, 'src' is just renamed + to 'dst'. Return the new full name of the file. + + Handles cross-device moves on Unix using 'copy_file()'. What about + other systems??? + """ + from os.path import exists, isfile, isdir, basename, dirname + import errno + + logger.info("moving %s to %s" % (src, dst)) + + if dryrun: + return dst + + if not isfile(src): + raise Error, "can't move %s: not a regular file" % src + + if isdir(dst): + dst = os.path.join(dst, basename(src)) + elif exists(dst): + raise Error, "can't move %s: destination %s already exists" % \ + (src, dst) + + if not isdir(dirname(dst)): + raise Error, "can't move %s: destination %s not a valid path" % \ + (src, dst) + + copy_it = 0 + try: + os.rename(src, dst) + except os.error, (num, msg): + if num == errno.EXDEV: + copy_it = 1 + else: + raise Error, "couldn't move %s to %s: %s" % (src, dst, msg) + + if copy_it: + copy_file(src, dst) + try: + os.unlink(src) + except os.error, (num, msg): + try: + os.unlink(dst) + except os.error: + pass + raise Error, "couldn't move %s to %s by copy/delete: " \ + "delete %s failed: %s" % (src, dst, src, msg) + + return dst + +def write_file (filename, contents): + """Create a file with the specified name and write 'contents' (a + sequence of strings without line terminators) to it. + """ + f = open(filename, "w") + for line in contents: + f.write(line + "\n") + f.close() + diff --git a/BuildManager/optionparser.py b/BuildManager/optionparser.py new file mode 100644 index 0000000..ea1ae68 --- /dev/null +++ b/BuildManager/optionparser.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +from BuildManager import Error +import optparse + +__all__ = ["OptionParser"] + +class CapitalizeHelpFormatter(optparse.IndentedHelpFormatter): + + def format_usage(self, usage): + return optparse.IndentedHelpFormatter \ + .format_usage(self, usage).capitalize() + + def format_heading(self, heading): + return optparse.IndentedHelpFormatter \ + .format_heading(self, heading).capitalize() + +class OptionParser(optparse.OptionParser): + + def __init__(self, usage=None, help=None, **kwargs): + if not "formatter" in kwargs: + kwargs["formatter"] = CapitalizeHelpFormatter() + optparse.OptionParser.__init__(self, usage, **kwargs) + self._overload_help = help + + def format_help(self, formatter=None): + if self._overload_help: + return self._overload_help + else: + return optparse.OptionParser.format_help(self, formatter) + + def error(self, msg): + raise Error, msg + +# vim:et:ts=4:sw=4 diff --git a/BuildManager/package.py b/BuildManager/package.py new file mode 100644 index 0000000..ce73333 --- /dev/null +++ b/BuildManager/package.py @@ -0,0 +1,243 @@ +from BuildManager import * +from BuildManager.rpmver import rpmVersionCompare +from UserList import UserList +import commands +import string +import os +import re + +try: + import rpm +except ImportError: + rpm = None + +__all__ = ["Package", "PackageList"] + +def subst(s, vars): + def _subst(match, vars=vars): + name = match.group(1) + return str(vars[name]) + try: + return re.sub(r'\$([a-zA-Z]+)', _subst, s) + except KeyError, var: + raise Error, "variable $%s not declared" % var + +class Package: + def __init__(self, file, log=None): + self._package = None + ext = self._filename_extension(file) + if not ext: + raise Error, "unknown file extension of "+file + if not globals().has_key("_package_"+ext): + raise Error, "unknown package extension of "+file + self._package = globals()["_package_"+ext](file, log) + + def __getattr__(self, name): + return getattr(self._package, name) + + def _filename_extension(self, filename): + try: + dotpos = filename.rindex(".") + except ValueError: + pass + else: + return filename[dotpos+1:] + +class _package: + def __init__(self, file, log): + self.file = file + self.absfile = os.path.abspath(file) + self.type = None + self.name = None + self.version = None + self.release = None + self.epoch = None + self.spec = None + self.builddir = None + self.log = log or "$builddir/SPECS/log.$name" + self._init() + + def __cmp__(self, pkg): + rc = cmp(self.name, pkg.name) + if rc: return rc + return rpmVersionCompare(self.epoch, self.version, self.release, + pkg.epoch, pkg.version, pkg.release) + + def _expand_log(self): + substdict = {"builddir":self.builddir, + "name":self.name, + "version":self.version, + "release":self.release} + self.log = subst(self.log, substdict) + +class _package_spec(_package): + def _rpm_vars(self, str, vars): + end = -1 + ret = [] + while 1: + start = string.find(str, "%{", end+1) + if start == -1: + ret.append(str[end+1:]) + break + ret.append(str[end+1:start]) + end = string.find(str, "}", start) + if end == -1: + ret.append(str[start:]) + break + varname = str[start+2:end] + if vars.has_key(varname): + ret.append(vars[varname]) + else: + ret.append(str[start:end+1]) + return string.join(ret,"") + + def _init(self): + self.spec = self.absfile + self.builddir = os.path.dirname(os.path.dirname(self.absfile)) + ret = os.system("mkdir -p %s/{SOURCES,SPECS,BUILD,SRPMS,RPMS,BUILDROOT}" % self.builddir) + try: + f = open(self.spec,"r") + except IOError, e: + raise BuildManagerFileError, \ + "couldn't open spec file %s" % self.absfile + defines = {} + for line in f.readlines(): + lowerline = string.lower(line) + if not self.name and lowerline[:5] == "name:": + self.name = self._rpm_vars(string.strip(line[5:]), defines) + elif not self.version and lowerline[:8] == "version:": + self.version = self._rpm_vars(string.strip(line[8:]), defines) + elif not self.release and lowerline[:8] == "release:": + self.release = self._rpm_vars(string.strip(line[8:]), defines) + elif lowerline[:7] == "%define": + token = string.split(line[7:]) + if len(token) == 2: + defines[token[0]] = self._rpm_vars(token[1], defines) + if self.name and self.version and self.release: + break + else: + raise Error, "spec file %s doesn't define name, " \ + "version or release" % self.file + self.type = "spec" + self._expand_log() + +class _package_rpm(_package): + def _init(self): + if not rpm: + cmd = "rpm -qp --queryformat='%%{NAME} %%{EPOCH} %%{VERSION} %%{RELEASE} %%{SOURCERPM}' %s"%self.file + status, output = commands.getstatusoutput(cmd) + if status != 0: + raise BuildManagerPackageError, \ + "error querying rpm file %s" % self.file + else: + tokens = string.split(output, " ") + if len(tokens) != 5: + raise Error, \ + "unexpected output querying rpm file %s: %s" % \ + (self.file, output) + else: + self.name, self.epoch, self.version, self.release, srpm = tokens + if self.epoch == "(none)": + self.epoch = None + if srpm != "(none)": + self.type = "rpm" + else: + self.type = "srpm" + else: + # Boost up query if rpm module is available + file = open(self.file) + if hasattr(rpm, "headerFromPackage"): + h = rpm.headerFromPackage(file.fileno())[0] + else: + ts = rpm.TransactionSet() + h = ts.hdrFromFdno(file.fileno()) + file.close() + self.name = h[rpm.RPMTAG_NAME] + self.epoch = h[rpm.RPMTAG_EPOCH] + self.version = h[rpm.RPMTAG_VERSION] + self.release = h[rpm.RPMTAG_RELEASE] + if h[rpm.RPMTAG_SOURCERPM]: + self.type = "rpm" + else: + self.type = "srpm" + + def unpack(self, unpackdir): + if self.type == "srpm": + self.builddir = self._builddir_create(unpackdir) + if self.builddir: + self._expand_log() + return self._install_srpm() + + def _builddir_create(self, unpackdir): + unpackdir = os.path.abspath(unpackdir) + builddir = "%s/%s-%s-%s-topdir" % (unpackdir, self.name, self.version, self.release) + ret = os.system("mkdir -p %s/{SOURCES,SPECS,BUILD,SRPMS,RPMS,BUILDROOT}" % builddir) + if ret != 0: + raise BuildManagerPackageError, \ + "error creating builddir at %s" % builddir + else: + return builddir + + def _install_srpm(self): + cmd = "rpm -i --define '_topdir %s' %s &> %s"%(self.builddir,self.file,self.log) + status, output = commands.getstatusoutput(cmd) + if status != 0: + raise Error, "error installing package "+self.file + else: + spec = self.builddir+"/SPECS/"+self.name+".spec" + if not os.path.isfile(spec): + listdir = os.listdir(self.builddir+"/SPECS") + for file in listdir[:]: + if file[-5:] != ".spec": + listdir.remove(file) + if len(listdir) != 1: + raise Error, "can't guess spec file for "+self.file + else: + self.spec = self.builddir+"/SPECS/"+listdir[0] + return 1 + else: + self.spec = spec + return 1 + +class PackageList(UserList): + def has_lt(self, pkg): + for mypkg in self.data: + if mypkg.name == pkg.name \ + and mypkg.type == pkg.type \ + and mypkg < pkg: + return 1 + return 0 + + def has_le(self, pkg): + for mypkg in self.data: + if mypkg.name == pkg.name \ + and mypkg.type == pkg.type \ + and mypkg <= pkg: + return 1 + return 0 + + def has_eq(self, pkg): + for mypkg in self.data: + if mypkg.name == pkg.name \ + and mypkg.type == pkg.type \ + and mypkg == pkg: + return 1 + return 0 + + def has_ge(self, pkg): + for mypkg in self.data: + if mypkg.name == pkg.name \ + and mypkg.type == pkg.type \ + and mypkg >= pkg: + return 1 + return 0 + + def has_gt(self, pkg): + for mypkg in self.data: + if mypkg.name == pkg.name \ + and mypkg.type == pkg.type \ + and mypkg > pkg: + return 1 + return 0 + +# vim:ts=4:sw=4:et diff --git a/BuildManager/rpmver.py b/BuildManager/rpmver.py new file mode 100644 index 0000000..cb3269c --- /dev/null +++ b/BuildManager/rpmver.py @@ -0,0 +1,62 @@ +# compare alpha and numeric segments of two versions +# return 1: first is newer than second +# 0: first and second are the same version +# -1: second is newer than first +def rpmVersionCompare(e1, v1, r1, e2, v2, r2): + if e1 and not e2: + return 1 + if not e1 and e2: + return -1 + if e1 and e2: + if e1 < e2: + return -1 + if e1 > e2: + return 1 + rc = rpmvercmp(v1, v2) + if rc: return rc + return rpmvercmp(r1, r2) + +# compare alpha and numeric segments of two versions +# return 1: a is newer than b +# 0: a and b are the same version +# -1: b is newer than a +def rpmvercmp(a, b): + if a == b: + return 0 + ai = 0 + bi = 0 + la = len(a) + lb = len(b) + while ai < la and bi < lb: + while ai < la and not a[ai].isalnum(): ai += 1 + while bi < lb and not b[bi].isalnum(): bi += 1 + aj = ai + bj = bi + if a[aj].isdigit(): + while aj < la and a[aj].isdigit(): aj += 1 + while bj < lb and b[bj].isdigit(): bj += 1 + isnum = 1 + else: + while aj < la and a[aj].isalpha(): aj += 1 + while bj < lb and b[bj].isalpha(): bj += 1 + isnum = 0 + if aj == ai or bj == bi: + return -1 + if isnum: + while ai < la and a[ai] == '0': ai += 1 + while bi < lb and b[bi] == '0': bi += 1 + if aj-ai > bj-bi: return 1 + if bj-bi > aj-ai: return -1 + rc = cmp(a[ai:aj], b[bi:bj]) + if rc: + return rc + ai = aj + bi = bj + if ai == la and bi == lb: + return 0 + if ai == la: + return -1 + else: + return 1 + +# vim:ts=4:sw=4 |