#!/usr/bin/env python # # A simple utility to integrate Mock with SCMs # # Copyright (C) 2010 Marko Myllynen # # 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, see . import subprocess import tempfile import optparse import shutil import shlex import glob import sys import rpm import os # Script version VERSION = "0.1" # Configuration OUTPUT_DIR = "/tmp/mock-results" SCM_GET_CVS = "cvs -d :ext:cvs.example.com:/srv/cvs co SCM_BRN SCM_PKG" SCM_GET_GIT = "git clone SCM_BRN git://git.example.com/git/SCM_PKG.git SCM_PKG" SCM_GET_SVN = "svn co http://svn.example.com/srv/svn/SCM_PKG/SCM_BRN SCM_PKG" EXT_SRC_DIR = "/mnt/src" RPMBUILD = 'rpmbuild --nodeps COMPAT --define "_sourcedir DIR" --define "_specdir DIR" --define "_builddir DIR/BUILD" --define "_srcrpmdir DIR/SRPMS" DIST -bs SPEC' MOCK = "/usr/bin/mock CHROOT --resultdir=DIR DIST SRC_RPM" # Command line parser def parse_cmd_line(): """Parse command line arguments""" usage = "%prog [options] " parser = optparse.OptionParser(usage) parser.disable_interspersed_args() parser.description = "A simple utility to integrate Mock with SCMs" parser.add_option("-V", "--version", dest="version", action="store_const", const=1, default=0, help="show program's version number and exit") parser.add_option("-v", "--verbose", dest="verbose", action="store_const", const=1, default=0, help="verbose execution") parser.add_option("-s", "--scm-method", dest="scm", action="store", type="string", default="git", help="SCM to use, one of: cvs, git (default), svn") parser.add_option("-g", "--scm-get", dest="get", help="SCM checkout command, " + "CVS default: " + SCM_GET_CVS + ", " + "Git default: " + SCM_GET_GIT + ", " + "SVN default: " + SCM_GET_SVN) parser.add_option("-b", "--scm-branch", dest="tag", help="SCM branch (or tag with CVS/SVN)") parser.add_option("-d", "--rpm-dist", dest="dist", help="RPM dist tag") parser.add_option("-o", "--output-dir", dest="output", action="store", type="string", default=OUTPUT_DIR, help="output directory for RPMs, default: " + OUTPUT_DIR) parser.add_option("-t", "--temp-dir", dest="temp", help="Dir to use instead of a generated one, not removed") parser.add_option("-r", "--mock-chroot", dest="chroot", help="Mock chroot (-r) configuration") parser.add_option("-c", "--rpm-compat", dest="compat", action="store_const", const=1, default=0, help="run rpmbuild with compat flags for older RPM versions") parser.add_option("-w", "--write-tar", dest="tar", action="store_const", const=1, default=0, help="write tarball on the fly from the checked out sources") return parser.parse_args() # Main def main(): """A simple utility to integrate Mock with SCMs""" (options, args) = parse_cmd_line() if options.version: print os.path.basename(sys.argv[0]) + " " + VERSION sys.exit(0) if len(args) != 1: print os.path.basename(sys.argv[0]) + ": incorrect number of arguments" sys.exit(1) pkg = args[0] # Generate temporary build directory and RPM output directory if options.temp: build_dir = options.temp else: build_dir = tempfile.mkdtemp(".mock-scm." + pkg) print "Using temporary directory: " + build_dir output_dir = OUTPUT_DIR if options.output: output_dir = options.output if not os.path.exists(output_dir): os.makedirs(output_dir) src_dir = build_dir + "/" + pkg # Generate SCM command to get files if options.verbose: print "START: Sources from SCM" cmd = SCM_GET_CVS if options.scm == "cvs": cmd = SCM_GET_CVS elif options.scm == "git": cmd = SCM_GET_GIT elif options.scm == "svn": cmd = SCM_GET_SVN else: raise Exception("SCM method %s not supported" % options.scm) if options.get: cmd = options.get if options.tag: if options.scm == "cvs": cmd = cmd.replace("SCM_BRN", "-r " + options.tag) elif options.scm == "git": cmd = cmd.replace("SCM_BRN", "-b " + options.tag) elif options.scm == "svn": cmd = cmd.replace("SCM_BRN", options.tag) else: raise Exception("SCM method %s not supported" % options.scm) elif options.scm == "svn": cmd = cmd.replace("SCM_BRN", "trunk") cmd = cmd.replace("SCM_BRN", "") cmd = cmd.replace("SCM_PKG", pkg) if options.verbose: print "Using command: " + cmd # External process output handling if options.verbose: output = None else: output = subprocess.PIPE # Get files from SCM args = shlex.split(cmd) r = subprocess.call(args, cwd=build_dir, stdout=output, stderr=subprocess.STDOUT) if r != 0: raise Exception("SCM command %s failed" % cmd) if options.verbose: print "DONE: Sources from SCM" # Check some helper files if os.path.exists(src_dir + "/.write_tar"): if options.verbose: print "INFO: .write_tar detected, will write tarball on the fly" options.tar = 1 # Figure out the spec file spec = src_dir + "/" + pkg + ".spec" if not os.path.exists(spec): pkg_lc = pkg.lower() spec = src_dir + "/" + pkg_lc + ".spec" if not os.path.exists(spec): raise IOError("Can't find spec file %s" % spec) # Dig out some basic information from the spec file sources = [] name = version = None ts = rpm.ts() rpm_spec = ts.parseSpec(spec) name = rpm.expandMacro("%{name}") version = rpm.expandMacro("%{version}") for (filename, num, flags) in rpm_spec.sources: sources.append(filename.split("/")[-1]) if options.verbose: print "Sources: %s" % sources # Generate a tarball from the checked out sources if needed if options.tar: tardir = name + "-" + version tarball = tardir + ".tar.gz" if options.verbose: print "Writing " + src_dir + "/" + tarball + "..." if not os.path.exists(src_dir + "/" + tarball): open(src_dir + "/" + tarball, 'w').close() cmd = "tar czf " + src_dir + "/" + tarball + \ " --exclude " + src_dir + "/" + tarball + \ " --exclude " + src_dir + "/BUILD" + \ " --exclude " + src_dir + "/SRPMS" + \ " --xform='s,^" + pkg + "," + tardir + ",' " + pkg args = shlex.split(cmd) r = subprocess.call(args, cwd=build_dir, stdout=output, stderr=subprocess.STDOUT) if r != 0: raise Exception("SCM command %s failed" % cmd) # Get possible external sources from EXT_SRC_DIR if options.verbose: print "START: Fetch external sources" for f in sources: if options.verbose: print "Processing: %s" % f if not os.path.exists(src_dir + "/" + f) and \ os.path.exists(EXT_SRC_DIR + "/" + f): if options.verbose: print "Copying " + EXT_SRC_DIR + "/" + f + " to " + src_dir + "/" + f + "..." shutil.copy2(EXT_SRC_DIR + "/" + f, src_dir + "/" + f) if options.verbose: print "DONE: Fetch external sources" # Build src.rpm if options.verbose: print "START: Build src.rpm" if not os.path.exists(src_dir + "/" + "BUILD"): os.mkdir(src_dir + "/" + "BUILD") if not os.path.exists(src_dir + "/" + "SRPMS"): os.mkdir(src_dir + "/" + "SRPMS") rpmbuild = RPMBUILD.replace("DIR", src_dir) if options.compat: rpmbuild = rpmbuild.replace("COMPAT", '--define "_source_filedigest_algorithm 1" --define "_source_payload w9.gzdio"') rpmbuild = rpmbuild.replace("COMPAT", "") if options.dist: rpmbuild = rpmbuild.replace("DIST", '--define "dist .' + options.dist + '"') rpmbuild = rpmbuild.replace("DIST", "") rpmbuild = rpmbuild.replace("SPEC", spec) args = shlex.split(rpmbuild) r = subprocess.call(args, stdout=output, stderr=subprocess.STDOUT) if r != 0: raise Exception("rpmbuild command %s failed", rpmbuild) if options.verbose: print "DONE: Build src.rpm" # Build from src.rpm if options.verbose: print "START: Mock build" mock = MOCK if options.chroot: mock = mock.replace("CHROOT", "-r " + options.chroot) mock = mock.replace("CHROOT", "") if options.dist: mock = mock.replace("DIST", '-D "dist .' + options.dist + '"') mock = mock.replace("DIST", "") mock = mock.replace("DIR", build_dir) src_rpm = glob.glob(src_dir + "/" + "SRPMS" + "/" + pkg + "-*.src.rpm")[0] mock = mock.replace("SRC_RPM", src_rpm) args = shlex.split(mock) r = subprocess.call(args, stdout=output, stderr=subprocess.STDOUT) if r != 0: raise Exception("mock command %s failed", mock) if options.verbose: print "DONE: Mock build" # Copy build results to destination directory if options.verbose: print "Copying build results to " + output_dir + ":" for f in glob.glob(build_dir + "/" + "*.rpm"): if options.verbose: print f shutil.copy2(f, output_dir) print "Build results are in: " + output_dir # Clean up if not options.temp: if options.verbose: print "All done, cleaning up." shutil.rmtree(build_dir) if __name__ == "__main__": main()