cantera/buildutils.py
Ray Speth d7a384e25d Extended SCons testing to be able to handle multiple output files
Extra files to be compared are passed as a list of tuples of
(blessed_output, test_output) using the 'comparisons' keyword
argument.
2011-12-14 05:55:24 +00:00

310 lines
8.6 KiB
Python

from glob import glob
import os
import shutil
import sys
from os.path import join as pjoin
import textwrap
import re
import subprocess
import difflib
import time
class DefineDict(object):
def __init__(self, data):
self.data = data
self.undefined = set()
def __getitem__(self, key):
if key not in self.data:
self.undefined.add(key)
return '/* #undef %s */' % key
elif self.data[key] is None:
return '/* #undef %s */' % key
else:
return '#define %s %s' % (key, self.data[key])
class ConfigBuilder(object):
def __init__(self, defines):
self.defines = DefineDict(defines)
def __call__(self, source, target, env):
for s, t in zip(source, target):
config_h_in = file(str(s), "r")
config_h = file(str(t), "w")
config_h.write(config_h_in.read() % self.defines)
config_h_in.close()
config_h.close()
self.print_config(str(t))
def print_config(self, filename):
print 'Generating %s with the following settings:' % filename
for key, val in sorted(self.defines.data.iteritems()):
if val is not None:
print " %-35s %s" % (key, val)
for key in sorted(self.defines.undefined):
print " %-35s %s" % (key, '*undefined*')
def regression_test(target, source, env):
# unpack:
program = source[0]
blessedFile = source[1]
if len(source) > 2:
clargs = [s.name for s in source[2:]]
else:
clargs = []
# Name to use for the output file
if 'blessed' in blessedFile.name:
output = blessedFile.abspath.replace('blessed', 'output')
else:
output = pjoin(blessedFile.dir.abspath, 'test_output.txt')
dir = str(target[0].dir.abspath)
with open(output, 'w') as outfile:
code = subprocess.call([program.abspath] + clargs,
stdout=outfile, stderr=outfile,
cwd=dir)
diff = compareFiles(blessedFile.abspath, output)
# Compare other output files
for blessed,output in env['test_comparisons']:
print blessed, output, diff,
print pjoin(dir, blessed), ',', pjoin(dir, output)
diff |= compareFiles(pjoin(dir, blessed), pjoin(dir, output))
print diff
if diff or code:
print 'FAILED'
if diff:
print 'Difference between blessed output and current output:'
print '>>>'
print '\n'.join(diff)
print '<<<'
if os.path.exists(target[0].abspath):
os.path.unlink(target[0].abspath)
return -1
else:
print 'PASSED'
open(target[0].path, 'w').write(time.asctime()+'\n')
def compareFiles(file1, file2):
text1 = [line.rstrip() for line in open(file1).readlines()]
text2 = [line.rstrip() for line in open(file2).readlines()]
diff = list(difflib.unified_diff(text1, text2))
if diff:
'Found differences between %s and %s:' % (file1, file2)
print '>>>'
print '\n'.join(diff)
print '<<<'
return 1
return 0
def regression_test_message(target, source, env):
print """* Running test '%s'...""" % source[0].name,
def add_RegressionTest(env):
env['BUILDERS']['RegressionTest'] = env.Builder(
action=env.Action(regression_test, regression_test_message))
class CopyNoPrefix(object):
"""
Copy a file, ignoring leading directories that are part
of 'prefix' (e.g. the variant directory)
"""
def __init__(self, prefix):
self.prefix = prefix
def __call__(self, source, target, env):
sourcepath = psplit(str(source[0]))
targetpath = psplit(str(target[0]))
depth = 0
for a,b in zip(targetpath, psplit(self.prefix)):
if a == b:
depth += 1
else:
break
print str(source[0]), pjoin(*targetpath[depth:])
shutil.copyfile(str(source[0]), pjoin(*targetpath[depth:]))
class BuildOpts(object):
def __init__(self, subdir, name, exts=('cpp',), **kwargs):
self.subdir = subdir
self.name = name
self.extensions = exts
self.linklibs = kwargs.get('libs', [])
def quoted(s):
return '"%s"' % s
def mglob(env, subdir, *args):
"""
Each arg in args is assumed to be file extension,
unless the arg starts with a '^', in which case the remainder
of the arg is taken to be a complete pattern.
"""
matches = []
for ext in args:
if ext.startswith('^'):
matches += env.Glob(pjoin(subdir, ext[1:]))
else:
matches += env.Glob(pjoin(subdir, '*.%s' % ext))
return matches
def psplit(s):
head, tail = os.path.split(s)
path = [tail]
while head:
head, tail = os.path.split(head)
path.append(tail)
path.reverse()
return path
def which(program):
""" Replicates the functionality of the 'which' shell command """
import os
def is_exe(fpath):
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
optionWrapper = textwrap.TextWrapper(initial_indent=' ',
subsequent_indent=' ',
width=72)
def formatOption(env, opt):
"""
Print a nicely formatted description of a SCons configuration
option, it's permitted values, default value, and current value
if different from the default.
"""
# Extract the help description from the permitted values. Original format
# is in the format: "Help text ( value1|value2 )" or "Help text"
if opt.help.endswith(')'):
parts = opt.help.split('(')
help = '('.join(parts[:-1])
values = parts[-1][:-1].strip().replace('|', ' | ')
if values == '':
values = 'string'
else:
help = opt.help
values = 'string'
# Fix the representation of boolean options, which are stored as
# Python bools, but need to be passed by the user as strings
default = opt.default
if default is True:
default = 'yes'
elif default is False:
default = 'no'
# First line: "* option-name: [ choice1 | choice2 ]"
lines = ['* %s: [ %s ]' % (opt.key, values)]
# Help text, wrapped and idented 4 spaces
lines.extend(optionWrapper.wrap(re.sub(r'\s+', ' ',help)))
# Default value
lines.append(' - default: %r' % default)
# Get the actual value in the current environment
if opt.key in env:
actual = env.subst('${%s}' % opt.key)
else:
actual = None
# Fix the representation of boolean options
if actual == 'True':
actual = 'yes'
elif actual == 'False':
actual = 'no'
# Print the value if it differs from the default
if actual != default:
lines.append(' - actual: %r' % actual)
lines.append('')
return lines
# This tool adds the builder:
#
# env.RecursiveInstall(target, path)
#
# This is useful for doing:
#
# k = env.RecursiveInstall(dir_target, dir_source)
#
# and if any thing in dir_source is updated the install is rerun
#
# It behaves similar to the env.Install builtin. However it expects
# two directories and correctly sets up the dependencies between each
# sub file instead of just between the two directories.
#
# Note in also traverses the in memory node tree for the source
# directory and can detect things that are not built yet. Internally
# we use the env.Glob function for this support.
#
# You can see the effect of this function by doing:
#
# scons --tree=all,prune
#
# and see the one to one correspondence between source and target
# files within each directory.
def RecursiveInstall(env, target, dir):
nodes = _recursive_install(env, dir)
dir = env.Dir(dir).abspath
target = env.Dir(target).abspath
l = len(dir) + 1
relnodes = [n.abspath[l:] for n in nodes]
out = []
for n in relnodes:
t = os.path.join(target, n)
s = os.path.join(dir, n)
out.extend(env.InstallAs(env.File(t), env.File(s)))
return out
def _recursive_install(env, path):
nodes = env.Glob(os.path.join(path, '*'), strings=False)
nodes.extend(env.Glob(os.path.join(path, '*.*'), strings=False))
out = []
for n in nodes:
if n.isdir():
out.extend(_recursive_install(env, n.abspath))
else:
out.append(n)
return out