diff options
-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 | ||||
-rw-r--r-- | LICENSE | 340 | ||||
-rw-r--r-- | MANIFEST.in | 2 | ||||
-rw-r--r-- | PKG-INFO | 15 | ||||
-rwxr-xr-x | bm | 200 | ||||
-rwxr-xr-x | bmclean | 86 | ||||
-rw-r--r-- | setup.cfg | 4 | ||||
-rwxr-xr-x | setup.py | 29 |
14 files changed, 1494 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 @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..82983c7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include BuildManager *.py +include bm bmclean MANIFEST.in LICENSE diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..6abe5db --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,15 @@ +Metadata-Version: 1.0 +Name: bm +Version: 2.1 +Summary: BuildManager - rpm package building helper +Home-page: http://moin.conectiva.com.br/BuildManager +Author: Gustavo Niemeyer +Author-email: niemeyer@conectiva.com +License: GPL +Description: BuildManager, or bm, is a program that wraps and extends rpm while building + packages. Its features allow one to batch process thousand of RPMS at once, + controling logs, rpm and srpm moving, filtering the list of files, ignoring + given packages, completely cleaning the build directories, and many other + features. + +Platform: UNKNOWN @@ -0,0 +1,200 @@ +#!/usr/bin/python +from BuildManager.optionparser import * +from BuildManager.build import * +from BuildManager import * +import fnmatch +import logging +import sys, os +import pwd + +AUTHOR = "Gustavo Niemeyer <niemeyer@conectiva.com>" +VERSION = "2.1" + +def passtrough(option, opt, val, parser): + opts = parser.values + if opt == "--define": + assert option.nargs == 1 + opts.options.insert(0, opt) + opts.options.insert(1, "'%s'" % parser.rargs[0].replace("'", r"\'")) + else: + opts.options.append(opt) + for val in parser.rargs[:option.nargs]: + opts.options.append("'%s'" % val.replace("'", r"\'")) + del parser.rargs[:option.nargs] + +def parse_options(): + parser = OptionParser("%prog [OPTIONS] [<rpm dir>] [<spec file>] [<srpm file>]", + version="%prog "+VERSION) + parser.add_option("-a", dest="mode", action="store_const", const="all", + help="do everything and build source and binary packages" + " (default)", default="all") + parser.add_option("-u", dest="mode", action="store_const", const="unpack", + help="just unpack") + parser.add_option("-p", dest="mode", action="store_const", const="prep", + help="unpack and run %prep stage") + parser.add_option("-c", dest="mode", action="store_const", const="compile", + help="unpack, run %prep, and compile") + parser.add_option("-i", dest="mode", action="store_const", const="install", + help="unpack, run %prep, compile and install"), + parser.add_option("-s", dest="mode", action="store_const", const="source", + help="do everything and build source packages") + parser.add_option("-b", dest="mode", action="store_const", const="binary", + help="do everything and build binary packages") + parser.add_option("-l", dest="show_log", action="store_true", + help="show rpm output, besides copying to the log file") + parser.add_option("-j", dest="parallel", metavar="N", type="int", default=1, + help="specify number of packages to build in parallel") + parser.add_option("-o", dest="options", action="append", + metavar="OPT", default=[], + help="pass given parameters directly to rpm") + parser.add_option("--unpack-dir", metavar="DIR", default="/var/tmp", + help="specify directory where to unpack file(s)") + parser.add_option("--build-log", metavar="FILE", + help="specify where to put the build log for each package") + parser.add_option("--move-srpm", metavar="DIR", + help="move built srpm packages to given directory") + parser.add_option("--move-rpm", metavar="DIR", + help="move built rpm packages to given directory") + parser.add_option("--move-failed-srpm", metavar="DIR", + help="move original srpm packages to given directory, if failed") + parser.add_option("--copy-failed-srpm", metavar="DIR", + help="copy original srpm packages to given directory, if failed") + parser.add_option("--remove-failed-srpm", action="store_true", + help="remove original srpm packages, if failed") + parser.add_option("--move-succeeded-srpm", metavar="DIR", + help="move original srpm packages to given directory, if succeeded") + parser.add_option("--copy-succeeded-srpm", metavar="DIR", + help="copy original srpm packages to given directory, if succeeded") + parser.add_option("--remove-succeeded-srpm", action="store_true", + help="remove original srpm packages, if succeeded") + parser.add_option("--move-log", metavar="DIR", + help="move log files to given directory") + parser.add_option("--move-failed-log", metavar="DIR", + help="move log files to given directory, if failed") + parser.add_option("--filter-renew", metavar="DIR", action="append", default=[], + help="don't build packages if a newer version exists in given directory") + parser.add_option("--filter-refresh", metavar="DIR", action="append", default=[], + help="only build packages if an older version exists in given directory") + parser.add_option("--clean", action="store_true", + help="recursively remove directory used as topdir after the build process") + parser.add_option("--clean-on-success", action="store_true", + help="same as --clean, but only remove if build has succeeded") + parser.add_option("--ignore", metavar="PKGNAME", action="append", default=[], + help="ignore given package names (shell globbing allowed)") + + # Passtrough options + parser.add_option("--sign", action="callback", nargs=0, callback=passtrough, + help="pass this option to rpm") + parser.add_option("--nodeps", action="callback", nargs=0, callback=passtrough, + help="pass this option to rpm") + parser.add_option("--debug", action="callback", nargs=0, callback=passtrough, + help="pass this option to rpm") + parser.add_option("--short-circuit", action="callback", nargs=0, callback=passtrough, + help="pass this option to rpm") + parser.add_option("--with", action="callback", nargs=1, callback=passtrough, + help="pass this option to rpm") + parser.add_option("--without", action="callback", nargs=1, callback=passtrough, + help="pass this option to rpm") + parser.add_option("--define", action="callback", nargs=1, callback=passtrough, + help="pass this option to rpm") + parser.add_option("--target", action="callback", nargs=1, callback=passtrough, + help="pass this option to rpm") + + parser.add_option("--dryrun", dest="dryrun", action="store_true", + help="do not commit changes to the system") + parser.add_option("--log", dest="loglevel", metavar="LEVEL", + help="set logging level to LEVEL (debug, info, " + "warning, error)", default="info") + opts, args = parser.parse_args() + opts.args = args + + if not opts.args: + # Let's try to find a spec file by ourselves + dir = os.getcwd() + if dir[-6:] == "/SPECS": + dir = "./" + elif os.path.isdir("./SPECS"): + dir = "./SPECS" + else: + while dir != "/": + dir = os.path.dirname(dir) + tmpdir = dir+"/SPECS" + if os.path.isdir(tmpdir): + dir = tmpdir + break + else: + raise Error, "couldn't guess SPECS directory" + filelist = os.listdir(dir) + for file in filelist[:]: + if file[-5:] != ".spec": + filelist.remove(file) + if len(filelist) != 1: + raise Error, "couldn't guess spec file in "+dir + opts.args = [os.path.join(dir, filelist[0])] + else: + # Detect directories with a SPECS/ directory inside it + for i in range(len(opts.args)): + specsdir = os.path.join(opts.args[i]+"/SPECS") + if os.path.isdir(specsdir): + filelist = os.listdir(specsdir) + for file in filelist[:]: + if file[-5:] != ".spec": + filelist.remove(file) + if len(filelist) != 1: + raise Error, "couldn't guess spec file in "+specsdir + opts.args[i] = os.path.join(specsdir, filelist[0]) + + opts.filter_renew = [y for x in opts.filter_renew + for y in x.split()] + opts.filter_refresh = [y for x in opts.filter_refresh + for y in x.split()] + + for attr in ["unpack_dir", "move_srpm", "move_rpm", + "move_failed_srpm", "copy_failed_srpm", + "move_succeeded_srpm", "copy_succeeded_srpm", + "move_log", "move_failed_log", + "filter_renew", "filter_refresh"]: + attrval = getattr(opts, attr) + error = False + if type(attrval) is list: + for attrval in attrval: + if attrval and not os.path.isdir(attrval): + error = True + break + else: + if attrval and not os.path.isdir(attrval): + error = True + if error: + raise Error, "value of --%s must be a directory" \ + % attr.replace("_", "-") + + old_ignore = opts.ignore + opts.ignore = [] + for ignore in old_ignore: + for item in ignore.split(): + opts.ignore.append(re.compile(fnmatch.translate(item))) + + return opts + +def main(): + # Get the right $HOME, even when using sudo. + if os.getuid() == 0: + os.environ["HOME"] = pwd.getpwuid(0)[5] + try: + opts = parse_options() + logger.setLevel(logging.getLevelName(opts.loglevel.upper())) + logger.debug("starting bm") + builder = PackageBuilder(opts) + status = builder.run() + except Error, e: + logger.error(str(e)) + logger.debug("finishing bm with error") + sys.exit(1) + else: + logger.debug("finishing bm") + sys.exit(int(not status)) + +if __name__ == "__main__": + main() + +# vim:et:ts=4:sw=4 @@ -0,0 +1,86 @@ +#!/usr/bin/python +from BuildManager.optionparser import * +from BuildManager.clean import * +from BuildManager import * +import fnmatch +import logging +import sys, os +import pwd + +AUTHOR = "Gustavo Niemeyer <niemeyer@conectiva.com>" +VERSION = "2.1" + +def parse_options(): + parser = OptionParser("%prog [OPTIONS] <srpm files>", + version="%prog "+VERSION) + parser.add_option("--remove", action="store_true", + help="remove old packages (default)") + parser.add_option("--move", metavar="DIR", + help="move old packages to given directory") + parser.add_option("--copy", metavar="DIR", + help="copy old packages to given directory") + parser.add_option("--check", metavar="DIR", action="append", default=[], + help="check given directory for newer pkgs, " + "but don't remove files there") + + parser.add_option("--dryrun", dest="dryrun", action="store_true", + help="do not commit changes to the system") + parser.add_option("--log", dest="loglevel", metavar="LEVEL", + help="set logging level to LEVEL (debug, info, " + "warning, error)", default="info") + opts, args = parser.parse_args() + opts.args = args + + if not opts.args: + raise Error, "you must specify one or more files to process" + + actions = 0 + if opts.remove: + actions += 1 + if opts.copy: + actions += 1 + if opts.move: + actions += 1 + + if actions > 1: + raise Error, "you can't specify more than one action to perform" + + for attr in ["move", "copy", "check"]: + attrval = getattr(opts, attr) + error = False + if type(attrval) is list: + for attrval in attrval: + if attrval and not os.path.isdir(attrval): + error = True + break + else: + if attrval and not os.path.isdir(attrval): + error = True + if error: + raise Error, "value of --%s must be a directory" \ + % attr.replace("_", "-") + + return opts + +def main(): + # Get the right $HOME, even when using sudo. + if os.getuid() == 0: + os.environ["HOME"] = pwd.getpwuid(0)[5] + try: + opts = parse_options() + logger.setLevel(logging.getLevelName(opts.loglevel.upper())) + logger.debug("starting bmclean") + cleaner = PackageCleaner(opts) + status = cleaner.run() + except Error, e: + logger.error(str(e)) + logger.debug("finishing bmclean with error") + sys.exit(1) + else: + logger.debug("finishing bmclean") + sys.exit(int(not status)) + +if __name__ == "__main__": + main() + +# vim:et:ts=4:sw=4 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8e38abd --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[bdist_rpm] +doc_files = LICENSE +release = 1cl +use_bzip2 = 1 diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..9882c14 --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +#!/usr/bin/python +from distutils.core import setup +import re + +verpat = re.compile("VERSION *= *\"(.*)\"") +data = open("bm").read() +m = verpat.search(data) +if not m: + sys.exit("error: can't find VERSION") +VERSION = m.group(1) + +setup(name="bm", + version = VERSION, + description = "BuildManager - rpm package building helper", + author = "Gustavo Niemeyer", + author_email = "niemeyer@conectiva.com", + url = "http://moin.conectiva.com.br/BuildManager", + license = "GPL", + long_description = +"""\ +BuildManager, or bm, is a program that wraps and extends rpm while building +packages. Its features allow one to batch process thousand of RPMS at once, +controling logs, rpm and srpm moving, filtering the list of files, ignoring +given packages, completely cleaning the build directories, and many other +features. +""", + packages = ["BuildManager"], + scripts = ["bm", "bmclean"], + ) |