#!/usr/bin/python # fedpkg-pull-build-chain: # Build binary RPMS for the named source RPMs from the latest upstream code. Fedora CVS tree # is checked out into the current directory. Build logs go in _build. You must specify # a destination path for RPMS with --resultdir. # # Licensed under the new-BSD license (http://www.opensource.org/licenses/bsd-license.php) # Copyright (C) 2010 Red Hat, Inc. # Written by Colin Walters import getopt import os import sys import subprocess import shutil import dbus, dbus.service, dbus.bus import glib import gobject from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) def check_call_verbose(*args, **kwargs): print "Running: %r %r" % (args[0], kwargs) subprocess.check_call(*args, **kwargs) def popen_verbose(*args, **kwargs): print "Running: %r" % (args[0], ) return subprocess.Popen(*args, **kwargs) STATE_STARTING = 'starting' STATE_VCS = 'vcs' STATE_BUILDING = 'building' class FedpkgPullBuildChainState(dbus.service.Object): def __init__(self, path): dbus.service.Object.__init__(self, dbus.SessionBus(), path) @dbus.service.signal(dbus_interface='org.fedoraproject.FedpkgPullBuildChain', signature='sa{sv}') def StateChanged(self, state, statedata): pass def main(): try: opts, args = getopt.getopt(sys.argv[1:], '', ['release=', 'arch=', 'resultdir=', 'delete-old', 'force']) except getopt.GetoptError, e: print unicode(e) print "Usage: fedpkg-pull-build-chain --release=F-12 --resultdir=/path/to/repo rpm1 rpm2 ..." sys.exit(1) force = False delete_old = False release = None resultdir = None architectures = [] for o, a in opts: if o in ('--force', ): force = True elif o in ('--release', ): release = a elif o in ('--arch', ): architectures.append(a) elif o in ('--resultdir', ): resultdir = a elif o in ('--delete-old', ): delete_old = True if release is None: print "Must specify --release" sys.exit(1) if resultdir is None: print "Must specify --resultdir=/path/to/repository" sys.exit(1) if 'DBUS_SESSION_BUS_ADDRESS' in os.environ: release_bus_name = release.replace('-', '_') bus = dbus.SessionBus() bus_name = dbus.service.BusName('org.fedoraproject.FedpkgPullBuildChain.' + release_bus_name, bus=bus) state_notifier = FedpkgPullBuildChainState('/org/fedoraproject/FedpkgPullBuildChain') state_notifier.StateChanged else: state_notifier = None def notify_state(state, statedata): if not state_notifier: return state_notifier.StateChanged(state, statedata) dbus.SessionBus().flush() if len(architectures) == 0: architectures.append(subprocess.Popen(['uname', '-m'], stdout=subprocess.PIPE).communicate()[0].strip()) if not os.path.exists(resultdir): print "Creating repository in %r" % (resultdir, ) os.mkdir(resultdir) check_call_verbose(['createrepo', '.'], cwd=resultdir) builddir = '_build-' + release try: os.mkdir(builddir) except OSError, e: pass mockreleases = [] for f in ('site-defaults.cfg', 'logging.ini'): shutil.copy2('/etc/mock/' + f, builddir) for architecture in architectures: # FIXME do this better if release.startswith('F-'): mockprefix = 'fedora-' + release[2:].lower() elif release.startswith('RHEL-'): mockprefix = 'rhel-' + release[5:].lower() else: raise ValueError("Can't map release %s to a mock config" % (release, )) mockrelease = '%s-%s' % (mockprefix, architecture) f_in = open(os.path.join('/etc', 'mock', mockrelease + '.cfg')) new_mockrelease_path = os.path.join(builddir, mockrelease + '.cfg') f_out = open(new_mockrelease_path, 'w') for line in f_in: f_out.write(line) f_in.close() f_out.write('config_opts[\'yum.conf\'] += """[buildchain]\nname=buildchain\nbaseurl=file://%s\n"""' % (os.path.abspath(resultdir), )) f_out.close() mockreleases.append(mockrelease) failed = [] for arg in args: if not os.path.isdir(arg): print "Checking out %r from fedora-cvs" % (arg, ) notify_state('fedora-vcs', { 'module': arg }) check_call_verbose(['fedora-cvs', arg], stdout=sys.stdout, stderr=sys.stderr) release_dir = os.path.join(arg, release) for filename in os.listdir(release_dir): fpath = os.path.join(release_dir, filename) if filename.endswith('.src.rpm'): print "Deleting old srpm: " + fpath os.unlink(fpath) mock_resultdir = os.path.join(builddir, arg) try: os.makedirs(mock_resultdir) except OSError, e: # assume EEXIST, for filename in os.listdir(mock_resultdir): if filename == 'lastbuild-status': continue os.unlink(os.path.join(mock_resultdir, filename)) lastbuild_filepath = os.path.join(mock_resultdir, 'lastbuild-status') if os.path.exists(lastbuild_filepath): last_build_succeeded = open(lastbuild_filepath).read() == 'success' else: last_build_succeeded = False print "Running fedpkg-vcs" notify_state('upstream-vcs', { 'module': arg }) args = ['fedpkg-vcs', '--status-file=' + os.path.abspath('pull-status')] if force or not last_build_succeeded: args.append('--force') args.extend(['pull-retarget', 'HEAD']) try: check_call_verbose(args, stdout=sys.stdout, stderr=sys.stderr, cwd=release_dir) except subprocess.CalledProcessError, e: print "Failed: " + unicode(e) failed.append(arg) continue f = open('pull-status') was_updated = f.readline() == 'updated' f.close() if not was_updated and last_build_succeeded: print "No updates and have a previous successful build, nothing to do" continue check_call_verbose(['make', 'srpm'], stdout=sys.stdout, stderr=sys.stderr, cwd=release_dir) srpm = None for filename in os.listdir(release_dir): fpath = os.path.join(release_dir, filename) if filename.endswith('.src.rpm'): srpm = fpath if not srpm: print "Error: Couldn't find .src.rpm!" sys.exit(1) current_failed = False for mockrelease in mockreleases: try: notify_state('build', { 'module': arg, 'srpm': os.path.basename(srpm), 'target': mockrelease }) check_call_verbose(['mock', '--configdir=' + builddir, '-r', mockrelease, '--resultdir=' + mock_resultdir, 'rebuild', srpm], stdout=sys.stdout, stderr=sys.stderr) except subprocess.CalledProcessError, e: print "Failed: " + unicode(e) current_failed = True failed.append(arg) break if current_failed: f = open(lastbuild_filepath, 'w') f.write('failed') f.close() break print "Successfully built %r" % (arg, ) print "Updating repository in %r" % (resultdir, ) linkname = os.path.join(resultdir, os.path.basename(srpm)) if not os.path.exists(linkname): os.link(srpm, linkname) for filename in os.listdir(mock_resultdir): if not filename.endswith('.rpm'): continue src = os.path.join(mock_resultdir, filename) linkname = os.path.join(resultdir, filename) if os.path.exists(linkname): continue os.link(src, linkname) notify_state('createrepo', {}) check_call_verbose(['createrepo', '.'], cwd=resultdir) if delete_old: proc = popen_verbose(['repomanage', '-o', '.'], stdout=subprocess.PIPE, stderr=sys.stderr, cwd=resultdir) output = proc.communicate()[0] for line in output.split('\n'): if line.endswith('.rpm') and os.path.exists(line): os.unlink(line) f = open(lastbuild_filepath, 'w') f.write('success') f.close() if len(failed) > 0: sys.exit(1) else: sys.exit(0) if __name__ == '__main__': main()