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()