summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBogdano Arendartchuk <bogdano@mandriva.org>2007-05-07 21:51:40 +0000
committerBogdano Arendartchuk <bogdano@mandriva.org>2007-05-07 21:51:40 +0000
commit11e2e30360a24bcc22ca4231fca495e5d846abb5 (patch)
tree9bf91715b22e91a7b739c72a08f6a3f4395c1c25
downloadbm-2.1.tar
bm-2.1.tar.gz
bm-2.1.tar.bz2
bm-2.1.tar.xz
bm-2.1.zip
Imported bm-2.1 from2.1
svn+ssh://svn.mandriva.com/svn/packages/cooker/bm/current/SOURCES/bm-2.1.tar.bz2 at r24959.
-rw-r--r--BuildManager/__init__.py24
-rw-r--r--BuildManager/build.py231
-rw-r--r--BuildManager/clean.py37
-rw-r--r--BuildManager/fileutil.py187
-rw-r--r--BuildManager/optionparser.py34
-rw-r--r--BuildManager/package.py243
-rw-r--r--BuildManager/rpmver.py62
-rw-r--r--LICENSE340
-rw-r--r--MANIFEST.in2
-rw-r--r--PKG-INFO15
-rwxr-xr-xbm200
-rwxr-xr-xbmclean86
-rw-r--r--setup.cfg4
-rwxr-xr-xsetup.py29
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/bm b/bm
new file mode 100755
index 0000000..f8d330e
--- /dev/null
+++ b/bm
@@ -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
diff --git a/bmclean b/bmclean
new file mode 100755
index 0000000..bbc1495
--- /dev/null
+++ b/bmclean
@@ -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"],
+ )