import sys, os, shutil, re, copy versionString = '1.21.0' def initLogger(verbose = False): import logging.config if verbose: print 'Verbose mode' fullPath = verbose and 'debug_build_cipres_logging.conf' or 'normal_build_cipres_logging.conf' if fullPath and os.path.exists(fullPath): logging.config.fileConfig(fullPath) else: import logging logger = logging.getLogger() logger.setLevel(logging.NOTSET) ch = logging.StreamHandler() ch.setLevel(logging.NOTSET) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) logger.addHandler(ch) checkPythonVersionFlag = 'P' verboseFlag = 'v' noninteractiveFlag = 'n' def processFlags(args): o = { 'verbose' : False, 'checkPythonVersion': True, 'noninteractive': False } for a in args[1:2]: if len(a) > 1 and a.startswith('-') and a[1] != '-': for flag in a[1:]: if flag == checkPythonVersionFlag: o['checkPythonVersion'] = False elif flag == verboseFlag: o['verbose'] = True elif flag == noninteractiveFlag: o['noninteractive'] = True else: sys.exit('The %s option is not recognized' % flag) return o if __name__ == '__main__': global opts opts = processFlags(sys.argv) initLogger(opts['verbose']) from _util import * from target_base import * from target_logic import * targets = {} import logging _log = logging.getLogger('build_cipres.py') _targetNames = [] def serializeTargets(fileObj, targets): global _targetNames fileObj.write('\n') for k in _targetNames: fileObj.write('%s\n' % targets[k]) fileObj.write('\n') def getAntVersion(): stout = os.popen('ant -version', 'r') linesOut = stout.readlines() nLines = len(linesOut) if nLines == 0: raise ValueError, '"ant -version" failed. Make sure that ant is on your PATH!"' if nLines > 1: raise ValueError, '"ant -version" command returned more than one line of output. This was not expected.\nTry the command yourself. If it is working correctly, please update the getAntVersion() function in build_cipres.py ' output = linesOut[0] versionStringRE = re.compile(r'^(Apache\s*)?[Aa]nt\s+version\s*([0-9.]+)\s+.*') matchObj = versionStringRE.match(output) if not matchObj: raise ValueError, 'The output from running "ant -version" command did not match the expected pattern: "Apache Ant version #.#.#"\nTry the command yourself, If it is working correctly, please update the getAntVersion() function in build_cipres.py ' versionNum = matchObj.group(2) # the numbers should be the second match group vNumSplit = versionNum.split('.') while len(vNumSplit) < 3: vNumSplit.append(0) return tuple(map(int, vNumSplit[:3])) def getDevenvVersion(): stout = os.popen('devenv -v', 'r') # @this is NOT the correct command, but I don't see a flag # for "print version and exit". This seems to do the trick thouh # (any unrecognized flag seems to trigger the same usage statement) linesOut = stout.readlines() nLines = len(linesOut) if nLines == 0: raise ValueError, '"devenv -v" failed. Make sure that devenv is on your PATH!"' if nLines < 2: raise ValueError, '"devenv -v" command returned fewer than two lines of output. This was not expected.\nTry the command yourself. If it is working correctly, please update the getDevenvVersion() function in build_cipres.py ' versionStringRE = re.compile(r'^Microsoft\s*\(R\)\s+Development\s+Environment\s+[Vv]ersion\s*([0-9.]+)\s+.*') for output in linesOut: matchObj = versionStringRE.match(output) if matchObj: versionNum = matchObj.group(1) vNumSplit = versionNum.split('.') while len(vNumSplit) < 3: vNumSplit.append(0) return tuple(map(int, vNumSplit[:3])) raise ValueError, 'The output from running "devenv -v" command did not match the expected pattern: "Microsoft (R) Development Environment Version #.#.#"\nTry the command yourself, If it is working correctly, please update the getDevenvVersion() function in build_cipres.py ' def safeSerialize(fileName, allTargets): '''Overwrites a backup file with prefix '.' and then replaces fileName with this. This prevents partial datafiles from being written if there is an exception.''' _log.debug('Serializing to %s...' % fileName) par, file = os.path.split(fileName) workingFile = os.path.join(par, '.' + file) try: dataFileObj = open(workingFile, 'w') serializeTargets(dataFileObj, allTargets) dataFileObj.close() except: _log.error('Error serializing to the working file %s. %s will reflect the previous step!' % (workingFile, fileName)) raise try: shutil.copy2(workingFile, fileName) except: _log.error('Error copying the working file %s to %s. You may need to do this copy manually!' % (fileName, workingFile)) def printHelp(targets): name = sys.argv[0] targetNames = targets.keys() targetNames.sort() print name, 'version', versionString print ''' %s is a tool for building cipres and its dependencies. %s is does some lightweight dependency tracking so that prerequisites are built first (cycles are not detected). The goals of the script are: 1 to automate the builds of the libraries and executables that we use and 2 display the necessary environment settings that a developer will need to have set to continue building (outside of the doing build with the %s script). The ability to add to the environment is the reason this script was written (instead of using ant). Data about targets are stored in cipres_in.xml This file is created from orig_cipres_in.xml if it does not exist. Thus if the cipres_in.xml becomes corrupted, you can simply delete it. Logic about how to build different targets is in target_logic.py (please modify this file if the build process is failing on a new platform, so that others will not struggle too). ''' % (name, name, name) print '''Usage: %s [options] command target [target] Flags: -%s noninteractive mode -%s do not check the version of python -%s Verbose mode ''' % (name, noninteractiveFlag, checkPythonVersionFlag, verboseFlag) print ''' Options: debug If this word appears in the invocation of a command that will cause reconfiguration of targets, then the configure will be run using the --enable-debugging flag. Commands: build Compiles a target (after building its dependencies) stage Builds a target and moves its product to the stage dir (after staging its dependencies) reconfigure Runs touch and then reruns the configure procedure on the target (and any target that depends on it) touch Flags a built or staged target so that it will be rebuilt the next time it is needed (dependencies of the target are also touched). flagbuilt Marks a target as a pseudo target that will not be rebuilt after this point) Target can be any of the following: %s ''' % ' | '.join(targetNames) def do_step(step, requestedTargets, targets, fileName): addedEnv = {} for target in requestedTargets: target.performStep(step, addedEnv) return addedEnv def do_touch(target, addedEnv): _log.info('TOUCH of target %s' % target.name) target.touch() def do_reconfigure(target, addedEnv): do_touch(target, addedEnv) _log.info('RECONFIGURE of target %s' % target.name) target.reconfigure(addedEnv) def flagAsBuilt(dummy, requestedTargets, targets, fileName): for t in requestedTargets: t.is_pseudo_target = True Target.serializer() return {} def performActionOnRequestedAndDependencies(action, requestedTargets, targets, fileName): '''Performs a simple action on requestedTargets and all targets that depend on them.''' newTargets = [] for toActOn in requestedTargets: for tarName, target in targets.iteritems(): if not (target in requestedTargets or target in newTargets): if target._needsTarget(toActOn): newTargets.append(target) if newTargets: return performActionOnRequestedAndDependencies(action, requestedTargets + newTargets, targets, fileName) addedEnv = {} for toActOn in requestedTargets: action(toActOn, addedEnv) return addedEnv def do_edit(bogus, requestedTargets, targets, fileName): sys.exit('Sorry Editing is not supported yet.') def quoteSpaced(a): if len(a.split()) > 1: return '"%s"' % a return a def invocationError(s, targets): _log.error('**Error: %s**\n\n' % s) printHelp(targets) sys.exit(1) def deserializeTargets(dataFileObj): try: from xml.dom.minidom import parse datDOM = parse(dataFileObj) finally: dataFileObj.close() targetNodeList = datDOM.getElementsByTagName('target') targets = {} for t in targetNodeList: classToCreate = eval(t.attributes['type'].value) targets[str(t.attributes['name'].value.upper())] = classToCreate(t) for t in targets.itervalues(): t._resolveDependencies(targets) UtilSingletons.macroExpander = MacroExpander(targets) for t in targets.itervalues(): t._finishInitialization(targets) return targets def displayAddedEnvrionment(addedEnv): addedTuples = addedEnv.items() end = len(addedTuples) if end == 0: return i = 0 while i < end: vals = addedTuples[i][1] if not isinstance(vals, list): vals = [vals] swapWith = 0 for v in vals: if v.startswith('$'): envNamePlus = v[1:] for j in range(i + 1, end): if envNamePlus.startswith(addedTuples[j][0]): swapWith = max(swapWith, j) if swapWith > 0: addedTuples[i], addedTuples[swapWith] = addedTuples[swapWith], addedTuples[i] else: i += 1 print "\nEnvironment tweaks (add these to your shell's log in script if you want to build outside of this script." print 'CSH style:\n' for k,v in addedTuples: if isinstance(v, list): print 'setenv %s %s' % (k, ':'.join(map(quoteSpaced,v))) else: print 'setenv %s %s' % (k, quoteSpaced(v)) print '\nBourne style\n' for k,v in addedTuples: if isinstance(v, list): print 'export %s=(%s)' % (k, ' '.join(map(quoteSpaced,v))) else: print 'export %s=%s' % (k, quoteSpaced(v)) class MacroExpansionError(ValueError): pass class MacroExpander: def __init__(self, targets): self.targets = targets def expandSingleMacro(self, s): global isDarwin, usingSubProcess if s == 'CIPRES_PLATFORM_DIR': cipresLibTarget = self.targets.get('CIPRESLIB') if cipresLibTarget: d = cipresLibTarget.getAbsCurrentPath() if not d: raise MacroExpansionError, 'cipres lib target is not returning a current directory. Cannot expand CIPRES_PLATFORM_DIR.' if cipresLibTarget.currentStep < StepEnum.stage: d = os.path.join(d, 'cipres') d = os.path.join(d, 'cipres_dist', 'bin') #we no longer append a platform-specific directory #if isDarwin: # return os.path.join(d, 'osx-ppc') #return os.path.join(d, 'linux') return d return s def __call__(self, s): sList = s.split('@') if len(sList) > 1: expanded = [] for n, i in enumerate(sList): if n % 2: expanded.append(self.expandSingleMacro(i)) else: expanded.append(i) return ''.join(expanded) return s def replaceCVSRootUserName(dir, oldUserName, newUserName): reObj = re.compile('(^:ext:|^)(%s)' % oldUserName) rep = newUserName for p in os.walk(dir): if os.path.split(p[0])[1] == 'CVS' and 'Root' in p[2]: cvsRootFile = os.path.join(p[0], 'Root') _log.debug('replacing username in %s' % cvsRootFile) origFile = open(cvsRootFile, 'r') contents = origFile.readlines() origFile.close() newFile = open(cvsRootFile, 'w') for line in contents: if reObj.match(line): newLine = reObj.sub(rep, line) _log.debug('Replacing %s with %s in %s' %(line, newLine, cvsRootFile)) newFile.write(newLine) else: _log.debug('not replacing line %s in %s' %(line, cvsRootFile)) newFile.write(line) newFile.close() else: _log.debug('%s is not a CVS directory' % p[0]) def input(prompt, default): global opts if opts['noninteractive']: print prompt, default, '(running in noninteractive mode)' return default return raw_input(prompt) or default def fixCVSDiretoriesForTarget(targets, targetName): reqTarget = targets.get(targetName) if reqTarget is None: sys.exit('Expecting a %s target in cipres_in.xml file.' % targetName) sourceDir = reqTarget.getAbsPath(StepEnum.build, createIfAbsent = False) reqTarget.extract(True) if not reqTarget.__dict__.get('x_cvsFixed'): uname = os.environ.get('USER', 'nobody') print '''This tar archive is a snapshot of the %s cvs tree checked out under another user's name. We need to update the CVS Root files to use your username for this cvs repository. If you are not going to commit changes to cvs from this directory tree, you can safely enter a bogus username.''' % targetName uname = input('Enter your user name (or hit to use %s): ' % uname, uname) unpackedUserName = 'mholder' # tar archives are created from mholder's machine replaceCVSRootUserName(sourceDir, unpackedUserName, uname) reqTarget.x_cvsFixed = 'true' Target.serializer() def main(): global _targetNames, opts, isDarwin, usingSubProcess, isWindows, devenvVersion, devenvMainVersion if isWindows: origDataFile = os.path.abspath('win_orig_cipres_in.xml') else: origDataFile = os.path.abspath('orig_cipres_in.xml') currDataFile = os.path.abspath('cipres_in.xml') if not os.path.exists(currDataFile): _log.warning('%s not found. Creating it from %s.' % (currDataFile, origDataFile)) shutil.copyfile(origDataFile, currDataFile) # create a singleton, that stores the top level paths Target.absPathToTop = TopLevelPath() _log.info('Reading %s...' % currDataFile) targets = deserializeTargets(open(currDataFile)) _log.info('%s successfully read.' % currDataFile) _targetNames.extend(targets.keys()) _targetNames.sort() def serializer(f = currDataFile, t = targets): safeSerialize(f, t) Target.serializer = staticmethod(serializer) cipresLibTarget = targets.get('CIPRESLIB') cipresLibTarget.extract(True) # fixCVSDiretoriesForTarget(targets, 'PHYCAS') if targets.get('PAUP'): fixCVSDiretoriesForTarget(targets, 'PAUP') if cipresLibTarget is None: sys.exit('Expecting a CIPRESLib target in cipres_in.xml file.') # if we change this 'cipres' directory on the end of the path, change it in build_cipres.py too Target.absPathToTop.stage_dir = os.path.join(cipresLibTarget.getAbsPath(StepEnum.build), 'cipres', 'cipres_dist') commandToFunc = { 'BUILD': (do_step, StepEnum.build), 'STAGE': (do_step, StepEnum.stage), 'PACKAGE': (do_step, StepEnum.package), 'EDIT' : (do_edit, None), 'TOUCH' : (performActionOnRequestedAndDependencies, do_touch), 'RECONFIGURE' : (performActionOnRequestedAndDependencies, do_reconfigure), 'FLAGBUILT' : (flagAsBuilt, None) } packageDir = os.path.abspath('package') command = None requestedTargets = [] justHelp = False checkPythonVersion = True _log.debug('Parsing command line arguments') for a in sys.argv[1:]: if len(a) > 1 and a.startswith('-') and a[1] != '-': continue # processFlags above will have dealt with this arg = a.upper() # deprecated stagedir argument, we are now staging in cipres_dist if arg == '--PACKAGEDIR': packageDir = getArgAfterEquals(a, 'DIST') elif arg == '--HELP' or arg == '-H': justHelp = True else: recognized = False if command == None: if arg in ['BUILD', 'STAGE', 'TOUCH', 'RECONFIGURE', 'FLAGBUILT']: recognized = True command = arg else: if arg in _targetNames: recognized = True if arg not in requestedTargets: requestedTargets.append(arg) elif arg == 'ALL': requestedTargets = copy.copy(_targetNames) recognized = True elif arg == 'DEBUG': Target.configureArgs = ['--enable-debugging'] if not recognized: invocationError('Unrecognized argument "%s"' % a, targets) if justHelp or command is None or not requestedTargets: if not justHelp: if not command: invocationError('Command required', targets) else: invocationError('Target(s) required', targets) printHelp(targets) sys.exit(0) func, arg = commandToFunc[command] if command in ['BUILD', 'STAGE']: _log.info('Checking ant version...') antVersionTuple = getAntVersion() requiredAntVersion = (1, 6, 5) if not checkVersionTuple(antVersionTuple, requiredAntVersion): sys.exit('You must have ant version %s (or higher)' % '.'.join(map(str,requiredAntVersion))) _log.info('ant %s should be OK...' % '.'.join(map(str,antVersionTuple))) if isWindows: devenvVersion = getDevenvVersion() minimalDevenvVersion = (7,) if not checkVersionTuple(devenvVersion, minimalDevenvVersion): sys.exit('You must have version %s (or higher) of Microsoft\'s C\\C++ development environment (VisualStudio.NET\'s devenv command line tool is required).' % '.'.join(map(str,minimalDevenvVersion))) _log.info('devenv should be OK') Target.devenvMainVersion = str(devenvVersion[0]) if opts['checkPythonVersion']: needToCheckPython = usingSubProcess if isDarwin and not needToCheckPython: osVersion = my_sw_vers() macWithGoodPython = (10, 4) needToCheckPython = not checkVersionTuple(osVersion, macWithGoodPython) if needToCheckPython: pyVersion = sys.version_info #py version is a tuple of numbers then string of release status for n, i in enumerate(pyVersion): if isinstance(i, str): break if not checkVersionTuple(pyVersion[:n], (2, 4)): if isDarwin: sys.exit(''' The python installation that comes with Mac OS < 10.4, has problems with extensions (such as omniORB). Upgrade to python 2.4 (available from http://undefined.org/python/) and run build_cipres.py again. If you have manually installed a version of python that you think will work with omniORB already, then you can run build_cipres.py by using the %s flag (to suppress checking of the python version)''' % checkPythonVersionFlag) else: sys.exit(''' build_cipres.py uses the subprocess module to launch processes. Upgrade to python 2.4 or install the subprocess module from (http://effbot.org/downloads/#subprocess).''') Target.absPathToTop.package_dir = os.path.abspath(packageDir) _log.info('Executing %s for %s' % (command, ', '.join(requestedTargets))) import time startTime = time.time() addedEnv = func(arg, map(targets.get, requestedTargets), targets, currDataFile) endTime = time.time() displayAddedEnvrionment(addedEnv) secondsTaken = round(endTime - startTime, 1) if secondsTaken < 60.0: _log.info('\nTime required: %.1f seconds' % secondsTaken) else: minutesTaken = int(secondsTaken/60) secondsTaken = int(round(secondsTaken)) % 60 _log.info('\nTime required: %d minutes, %d seconds' % (minutesTaken, secondsTaken)) if __name__ == '__main__': main()