#!/usr/bin/python # mock-many-srpms: # Build binary RPMS for the named source RPMs. # # 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. # # Written by Colin Walters # --auto-sort option derived from code by Seth Vidal import getopt import os import sys import subprocess import tempfile import shutil from StringIO import StringIO 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) def list_source_names_in_dir(dirpath): """Return all source RPM names from directory (unsorted, may include duplicates).""" files = os.listdir(dirpath) sources = [] for f in files: if not f.endswith('.src.rpm'): continue name = f.rsplit('-', 1)[0] sources.append(name) return sources def delete_old_rpms_in_dir(dirpath): """Ensure there's only one version of each binary RPM in a directory.""" proc = popen_verbose(['repomanage', '-o', '.'], stdout=subprocess.PIPE, stderr=sys.stderr, cwd=dirpath) output = proc.communicate()[0] for line in output.split('\n'): if line.endswith('.rpm') and os.path.exists(line): os.unlink(line) def return_binary_pkgs_from_srpm(srpmfn): import glob import rpm mydir = tempfile.mkdtemp() binary_pkgs = [] rc = subprocess.Popen(['rpm2cpio', srpmfn],stdout=subprocess.PIPE) cs = subprocess.Popen(['cpio', '--quiet', '-i', '*.spec'], cwd=mydir, stdin=rc.stdout, stdout=subprocess.PIPE, stderr=open('/dev/null', 'w')) output = cs.communicate()[0] specs = glob.glob(mydir + '/*.spec') if not specs: return binary_pkgs spkg = rpm.spec(specs[0]) for p in spkg.packages: binary_pkgs.append(p.header['name']) return binary_pkgs def sort_srpms_by_build_order(srpms): """Input: list of file paths to source RPMs. Output: Sorted list.""" import yum my = yum.YumBase() my.preconf.init_plugins=False my.setCacheDir() build_reqs = {} build_bin = {} srpms_to_pkgs = {} for i in srpms: # generate the list of binpkgs the srpms create build_bin[i] = return_binary_pkgs_from_srpm(i) # generate the list of provides in the repos we know about from those binpkgs (if any) p_names = [] for name in build_bin[i]: providers = my.pkgSack.searchNevra(name=name) if providers: p_names.extend(providers[0].provides_names) build_bin[i].extend(p_names) # setup the build_reqs build_reqs[i] = [] for i in srpms: # go through each srpm and take its buildrequires and resolve them out to one of other # srpms, if possible using the build_bin list we just generated # toss out any pkg which doesn't map back - this only does requires NAMES - not versions # so don't go getting picky about versioning here. lp = yum.packages.YumLocalPackage(ts=my.ts, filename=i) srpms_to_pkgs[i] = lp for r in lp.requires_names: for srpm in build_bin: if r in build_bin[srpm]: build_reqs[i].append(srpm) # output the results in a format tsort(1) can cope with (tmpfd, temppath) = tempfile.mkstemp() tmpf = os.fdopen(tmpfd, 'w') for (pkg,reqlist) in build_reqs.items(): for req in reqlist: tmpf.write('%s %s' % (pkg, req)) tmpf.write('\n') tmpf.close() tsort_proc = subprocess.Popen(['tsort', temppath], stdout=subprocess.PIPE, stderr=sys.stderr) output_str = tsort_proc.communicate()[0] output = StringIO(output_str) output_str = None tsort_proc.wait() os.unlink(temppath) result = [] # Reverse output order for line in output: result.insert(0, line.strip()) return result def main(): try: opts, args = getopt.getopt(sys.argv[1:], '', ['root=', 'resultdir=', 'logdir=', 'delete-old', 'continue-on-fail', 'skip-have-build', 'save-temps', 'auto-sort']) except getopt.GetoptError, e: print unicode(e) print "Usage: mock-many-srpms --root=fedora-13-x86-64 --root=fedora-13-i386 --logdir=/path/to/logdir --resultdir=/path/to/repo rpm1 rpm2 ..." sys.exit(1) auto_sort = False save_temps = False continue_on_fail = False skip_have_build = False delete_old = False resultdir = None logdir = None roots = [] for o, a in opts: if o in ('--root', ): roots.append(a) elif o in ('--resultdir', ): resultdir = a elif o in ('--logdir', ): logdir = a elif o in ('--delete-old', ): delete_old = True elif o in ('--continue-on-fail'): continue_on_fail = True elif o in ('--skip-have-build'): skip_have_build = True elif o in ('--save-temps'): save_temps = True elif o in ('--auto-sort'): auto_sort = True if len(roots) == 0: print "Must specify at least one --root" sys.exit(1) if logdir is None: print "Must specify --logdir=/path/to/logs" sys.exit(1) if resultdir is None: print "Must specify --resultdir=/path/to/repository" sys.exit(1) if len(args) == 0: print "No source RPMS specified." sys.exit(1) for arg in args: if not os.path.isfile(arg): print "Couldn't find source RPM '%r'" % (arg, ) if auto_sort: print "Auto-sorting by build order" args = sort_srpms_by_build_order(args) print "Determined build order: %r" % (args, ) if not os.path.exists(resultdir): print "Creating initial empty repository in %r" % (resultdir, ) os.makedirs(resultdir) check_call_verbose(['createrepo', '.'], cwd=resultdir) if not os.path.isdir(logdir): os.mkdir(logdir) tmpdir = tempfile.mkdtemp() tmp_mock_dir = os.path.join(tmpdir, 'mock') os.mkdir(tmp_mock_dir) for f in ('site-defaults.cfg', 'logging.ini'): path = os.path.join('/etc', 'mock', f) new_path = os.path.join(tmp_mock_dir, f) shutil.copy2(path, new_path) orig_stat = os.stat(path) os.utime(new_path, (orig_stat.st_atime, orig_stat.st_mtime)) for root in roots: orig_config_path = os.path.join('/etc', 'mock', root + '.cfg') f_in = open(orig_config_path) new_mockroot_path = os.path.join(tmp_mock_dir, root + '.cfg') f_out = open(new_mockroot_path, 'w') for line in f_in: f_out.write(line) f_in.close() orig_stat = os.stat(orig_config_path) os.utime(new_mockroot_path, (orig_stat.st_atime, orig_stat.st_mtime)) f_out.write('config_opts[\'yum.conf\'] += """[buildchain]\nname=buildchain\nbaseurl=file://%s\n"""' % (os.path.abspath(resultdir), )) f_out.close() if skip_have_build: previous_successful_builds = list_source_names_in_dir(resultdir) else: previous_successful_builds = [] succeeded = [] skipped = [] failed = [] for srpm in args: srpm_name = os.path.basename(srpm) src_name = srpm_name.rsplit('-', 1)[0] if src_name in previous_successful_builds: print "Skipping %r due to previous successful build" % (srpm_name, ) skipped.append(srpm_name) continue mock_resultdir = os.path.join(logdir, srpm_name) if not os.path.isdir(mock_resultdir): os.makedirs(mock_resultdir) current_failed = False for root in roots: try: check_call_verbose(['mock', '--configdir=' + tmp_mock_dir, '-r', root, '--resultdir=' + mock_resultdir, 'rebuild', srpm], stdout=sys.stdout, stderr=sys.stderr) except subprocess.CalledProcessError, e: current_failed = True if current_failed: print "FAILED: %r" % (srpm_name, ) if continue_on_fail: failed.append(srpm_name) continue else: break succeeded.append(srpm_name) print "Successfully built %r" % (srpm_name, ) print "Updating repository in %r" % (resultdir, ) linkname = os.path.join(resultdir, srpm_name) 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) check_call_verbose(['createrepo', '.'], cwd=resultdir) if delete_old: delete_old_rpms_in_dir(resultdir) if save_temps: print "Temporary files saved in %r" % (tmp_mock_dir, ) else: shutil.rmtree(tmp_mock_dir) if len(skipped) > 0: print "The following builds were skipped due to a previous successful build:" for v in skipped: print " %r" % (v, ) if len(succeeded) > 0: print "The following builds were successful:" for v in succeeded: print " %r" % (v, ) if len(failed) > 0: print "The following builds failed:" for v in failed: print " %r" % (v, ) if len(failed) == 0: sys.exit(0) else: sys.exit(1) if __name__ == '__main__': main()