#!/usr/bin/python -tt # skvidal at fedoraproject.org & pknirsch at redhat.com # take a set of srpms # resolve their buildreqs (pkg name only and virtual build provides) # sort them # break them into groups for parallelizing the build # Very simple implementation of a topology sort using filtering mechanism # based on # of requirements and leaf nodes import sys import tempfile import rpm import subprocess import os import glob import yum import shutil def return_binary_pkgs_from_srpm(srpmfn): 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: shutil.rmtree(mydir) return binary_pkgs rpm.reloadConfig() try: spkg = rpm.spec(specs[0]) for p in spkg.packages: binary_pkgs.append(p.header['name']) binary_pkgs.extend(p.header['provides']) shutil.rmtree(mydir) return binary_pkgs except: shutil.rmtree(mydir) return [] def get_buildreqs(srpms): 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 srpm_short = os.path.basename(i) build_bin[srpm_short] = 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[srpm_short]: providers = my.pkgSack.searchNevra(name=name) if providers: p_names.extend(providers[0].provides_names) build_bin[srpm_short].extend(p_names) 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(filename=i) srpm_short = os.path.basename(i) # setup the build_reqs build_reqs[srpm_short] = set([]) srpms_to_pkgs[srpm_short] = lp for r in lp.requires_names: for srpm in build_bin: if r in build_bin[srpm]: build_reqs[srpm_short].add(srpm) return build_reqs def main(): if len(sys.argv) < 2: print 'usage: buildorder.py srpm1 srpm2 srpm3' sys.exit(1) srpms = sys.argv[1:] print 'Sorting %s srpms' % len(srpms) print 'Getting build reqs' build_reqs = get_buildreqs(srpms) print 'Breaking loops, brutally' # need to break any loops up before we pass it to the tsort # we warn and nuke the loop - tough noogies - you shouldn't have them anyway broken_loop_unsorted = {} for (node,reqs) in build_reqs.items(): broken_loop_unsorted[node] = set() for p in reqs: if node in build_reqs[p]: print 'WARNING: loop: %s and %s' % (node, p) else: broken_loop_unsorted[node].add(p) build_reqs = broken_loop_unsorted # new_reqs = {} # new_reqs['bzip2-1.0.3-6.el5_5.src.rpm'] = build_reqs['bzip2-1.0.3-6.el5_5.src.rpm'] # modified = True # while modified: # modified = False # for (node,reqs) in new_reqs.items(): # for r in reqs: # if r in build_reqs.keys() and r not in new_reqs.keys(): # modified = True # new_reqs[r] = build_reqs[r] # # build_reqs = new_reqs # very simple topology sort algorithm: # - Calculate # of requirements of each component # - List all components that have 0 requires all at once, # otherwise pick a suitable (is required by one of the low requires components) candidate # - Remove all components and their respective requires in all other components group = 0 while len(build_reqs.keys()) > 0: lenreqs = {} for (node,reqs) in build_reqs.items(): lenreqs.setdefault(len(reqs), []).append(node) group += 1 minreqs = min(lenreqs.keys()) # print "Group %d (%d reqs)" % (group, minreqs) if minreqs == 0: nodelist = lenreqs[minreqs] else: nodes = [] for r1 in sorted(lenreqs.keys()): reverse_req = {} max = [0, None] nodes.extend(lenreqs[r1]) for n1 in nodes: for req in build_reqs[n1]: reverse_req.setdefault(req, 0) reverse_req[req] += 1 if reverse_req[req] > max[0]: max = [reverse_req[req], req] if max[0] > 0: nodelist = [max[1],] break for node in nodelist: print node del build_reqs[node] for (tnode,treqs) in build_reqs.items(): treqs.discard(node) # print if __name__ == '__main__': main()