import os from config import application # version of our application from config import version # contains the versions of our dependencies. from config import dependencies # order in which we want to overwrite spec # files should that be an issue from config import order # distribution that contains the spec files. from config import distribution from config import additional #### for bookkeeping # maps the application name to the paths it apears # in. E.g. if 2 subpackages have a dependency in common # this dependency package will have two entries in # its packagePaths mapping. These entries represent # the path from the root (our application) via dependencies # to this application and uniquely defines that application # as nodes in a tree. packagePaths = {} # package tree. A dictionary with key the path until this # node and value the path + the node. This uniquely defines # the relations between the nodes in the tree. Note we are # talking about a tree not acyclic graph. We unfold the acyclic # graph in this algorithm. packageTree = {} # patches list. Some spec files have associated patched # files. These need to be checked out too for creating # a tag. It is a dictionary mapping the patches to their # versions per direct subpackage. patches = {} # list of files with their version (tag) that where # attempted to check out, but did not check out. This # can be the case if a subpackage is tagged but some # files are missed. missedFiles = {} # the tree produces a lot of duplicates potentially # under the same tag. we do not want to check out the # same package of the same version (tag) twice # checkedOut contains a list of string (package:tag) # that have been checked out. checkedOut ={} def getFiles(filePrefix, filePostFix, path): """ Auxixilary method to get some files from a dir. """ result = [] for file in os.listdir(path): if file.startswith(filePrefix) and file.endswith(filePostFix): result.append(file[:file.find(filePostFix)]) return result def checkoutTag(basedir, tag = 'head'): """ Checks out the spec files filed under a certain task. """ targetDir = os.path.join(basedir, tag) # if the target dir exists we do not # have to do anything as the code # is already checkedout. try: os.makedirs(targetDir) os.chdir(targetDir) stdin = stdout = stderr = None if tag == 'head': stdin, stdout, stderr = os.popen3('cvs co '+distribution) else: stdin, stdout, stderr = os.popen3('cvs co -r ' + tag +' '+distribution) line = stdout.readline() while line: print line line = stdout.readline() except Exception,ex: #print('Targetdir perhaps already there? '+str(ex)) pass return os.path.join(basedir, tag +'/'+distribution) def createTree(useTagVersions = False): """ Creates a dependency tree based on dependencies specified in the spec file (does this in memory). By default it uses the spec files that are in the cmsdistDir, however if useTagVersions is set to true it will check out the tagged versions and use this as a base. """ global packages # start with the root package. queue = [ {'package' : application, 'path':''}] while len(queue) != 0: # get next item in the queue. parentPackage = queue[0]['package'] parentPath = queue[0]['path'] packageTree[parentPath+':'+parentPackage] = [] del queue[0] # tag represents the version the spec file is associated # to. We also need this for patches. tag = '' # check out the head or a tagged version. if not useTagVersions : cmsdistPath = checkoutTag(os.getenv('CMSDIST_DIR')) else: # differentiate between application # as it has its own version variable # and the other packages. if parentPath == '': tag = version else: # find out to which version in the dependency # list in the config file this package is # associated to. path = parentPath+':'+parentPackage dependency = path.split(':')[2] tag = dependencies[dependency] cmsdistPath = checkoutTag(os.getenv('CMSDIST_DIR'), tag) fileName = os.path.join(cmsdistPath, parentPackage+'.spec') file = open(fileName,'r') line = file.readline() while line: if line.startswith('Requires:'): # parse this line # remove unnessesary head and tail. line = line[:line.rfind('\n')] line = line.split(':')[1] lineparts = line.split(' ') # simple filter for part in lineparts: if part != '': path = parentPath+':'+parentPackage item = {'package' : part, 'path' : path} if not packagePaths.has_key(part): packagePaths[part] = [] # track where packages are located (for duplicates) packagePaths[part].append(path) # track where package are located in the tree packageTree[parentPath+':'+parentPackage].append(path+":"+part) queue.append(item) if line.startswith('Patch'): line = line[:line.rfind('\n')] line = line.split(':')[1] lineparts = line.split(' ') for part in lineparts: if part != '': if part.find('%') > -1: # to get rid of wildcards we cast a wide net. files = getFiles(part[:part.find('%')], '.patch', cmsdistPath) else: files =[part] # associate patch with right dependency. path = parentPath+':'+parentPackage dependency = path.split(':')[2] if not patches.has_key(dependency): patches[dependency] = [] for fileName in files: patches[dependency].append({'patch' : fileName, 'tag' : tag}) line = file.readline() def printTree(parent, level): """ Prints the tree in a more visual appealing format. """ indent = 3*level space = " " * indent package = parent.split(':')[-1] # get its real path path = parent[0:parent.rfind(package)-1] # check if this path is a subset of another # path for this package. If so it can be removed. remove = " " if packagePaths.has_key(package): for package_path in packagePaths[package]: if package_path.find(path) > -1 and package_path != path: remove = " **" print space, package+remove for child in packageTree[parent]: printTree(child,level+1) def checkAppDependencies(application): msg = """ Packages that can be removed from your application spec file as dependend applications also depend on these packages. """ print(msg) for child in packageTree[application]: childPackage = child.split(':')[-1] childPath = child[0:child.rfind(childPackage)-1] # here we check if a parent package has a dependency # on a package in common with one of its subpackages. # if so we can remove that dependency. if packagePaths.has_key(childPackage): for package_path in packagePaths[childPackage]: if package_path.find(childPath) > -1 and package_path != childPath: print('-'+childPackage) break def preparePatches(targetDir): os.chdir(targetDir) # we want to checkout the patches in the order # defined in the config file. for package in order: if patches.has_key(package): for patch in patches[package]: if not checkedOut.has_key(patch['patch']+':'+patch['tag']): checkedOut[patch['patch']+':'+patch['tag']] = 'done' command = 'cvs update -r '+patch['tag']+ ' '+patch['patch']+'.patch' print(command) stdin, stdout, stderr = os.popen3(command) line = stdout.readline() while line: print line line = stdout.readline() # check if file is checked out. If not register it so we can fileLocation = os.path.join(targetDir, patch['patch']+'.patch') if not os.path.exists(fileLocation): missedFiles[patch['patch']+'.patch'] = patch['tag'] else: # delete the entry if we overwrite it with another tag # version of another dependency. try: del missedFiles[patch['patch']+'.patch'] except: pass def additionalFiles(targetDir): os.chdir(targetDir) for file in additional.keys(): command = 'cvs update -r '+additional[file]+' '+file print(command) stdin, stdout, stderr = os.popen3(command) line = stdout.readline() while line: print line line = stdout.readline() # check if file is checked out. If not register it so we can fileLocation = os.path.join(targetDir, file) if not os.path.exists(fileLocation): print('test1 '+fileLocation) missedFiles[file] = additional[file] else: # see if we can remove the entry # as other tags might have overwritten it. try: del missedFiles[file] except: pass def prepareTag(parent, level): """ Prints the tree in a more visual appealing format. """ # prepare some stuff on level 0 (e.g. creating the # directory. if level == 0: targetDir = '' try: targetDir = os.path.join(os.getenv('CMSDIST_DIR'), 'to_b_tagged') os.makedirs(targetDir) os.chdir(targetDir) # seed the directory so we can use the update command. stdin, stdout, stderr = os.popen3('cvs co -r '+version+' '+distribution+'/'+application+'.spec') line = stdout.readline() while line: print line line = stdout.readline() except Exception,ex: print('Targetdir perhaps already there? '+str(ex)) preparePatches(os.path.join(targetDir,distribution)) targetDir = os.path.join(os.getenv('CMSDIST_DIR'), 'to_b_tagged/'+distribution) os.chdir(targetDir) package = parent.split(':')[-1] # get its version from the config file based # on to what dependency it is associated to # we check that by looking at the path. path = parent[0:parent.rfind(package)-1] if parent != ':' + application: dependency = parent.split(':')[2] tag = dependencies[dependency] print('package: '+package+' version: '+tag) # found the package and its version. Now # dump it in the to_b_tagged dir if not checkedOut.has_key(package+':'+tag): checkedOut[package+':'+tag] = 'done' command = 'cvs update -r '+tag+' '+package+'.spec' print(command) stdin, stdout, stderr = os.popen3(command) line = stdout.readline() while line: print line line = stdout.readline() # check if file is checked out. If not register it so we can fileLocation = os.path.join(targetDir, package+'.spec') if not os.path.exists(fileLocation): missedFiles[package] = tag else: # see if we can remove the entry # as other tags might have overwritten it. try: del missedFiles[package] except: pass # for the direct dependencies we want to handle # them in a certain order because if they have common # packages we want to overwrite them in a certain order. if level == 0: for package in order: # construct the correct child path child = parent+':'+package prepareTag(child,level+1) additionalFiles(os.path.join(targetDir, '')) else: for child in packageTree[parent]: prepareTag(child,level+1) createTree(useTagVersions = True) msg = """ Printing dependency tree for your application based on the spec file dependencies. Packages marked with ** can be removed from their parent spec file as applications the parent depends on also depend on these packages. """ print msg printTree(':'+application,0) checkAppDependencies(':'+application) raw_input('Preparing for tagging specs for release. Press any key to continue.') prepareTag(':'+application,0) msg = """ The following files where not checked out properly Perhaps because subprojects did not tag them properly in their respective tag/versions? Or perhaps these files are unnecessary? Or the filters in the algorithm need to be sharpened? """ print(msg) print('missed files: '+str(missedFiles)) print msg = """ After you satisfied with the checedkout files, you can start the build package script. """ print msg