Files
cantera/buildutils.py

348 lines
9.8 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]
blessedName = source[1].name
if len(source) > 2:
clargs = [s.name for s in source[2:]]
else:
clargs = []
# Name to use for the output file
if 'blessed' in blessedName:
outputName = blessedName.replace('blessed', 'output')
else:
outputName = 'test_output.txt'
# command line options
clopts = env['test_command_options'].split()
# Run the test program
dir = str(target[0].dir.abspath)
with open(pjoin(dir,outputName), 'w') as outfile:
code = subprocess.call([program.abspath] + clopts + clargs,
stdout=outfile, stderr=outfile,
cwd=dir)
diff = 0
# Compare output files
for blessed,output in [(blessedName,outputName)] + env['test_comparisons']:
print """Comparing '%s' with '%s'""" % (blessed, output)
diff |= compareFiles(env, pjoin(dir, blessed), pjoin(dir, output))
if diff or code:
print 'FAILED'
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(env, file1, file2):
if file1.endswith('csv') and file2.endswith('csv'):
return compareCsvFiles(env, file1, file2)
else:
return compareTextFiles(env, file1, file2)
def compareTextFiles(env, 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 compareCsvFiles(env, file1, file2):
try:
import numpy as np
except ImportError:
print 'WARNING: skipping .csv diff because numpy is not installed'
return 0
# decide how many header lines to skip
f = open(file1)
headerRows = 0
goodChars = set('0123456789.+-eE, ')
for line in f:
if not set(line[:-1]).issubset(goodChars):
headerRows += 1
else:
break
try:
data1 = np.genfromtxt(file1, skiprows=headerRows, delimiter=',')
data2 = np.genfromtxt(file2, skiprows=headerRows, delimiter=',')
except IOError as e:
print e
return 1
relerror = np.abs(data2-data1) / (np.maximum(np.abs(data2), np.abs(data1)) + env['test_csv_threshold'])
maxerror = np.nanmax(relerror.flat)
tol = env['test_csv_tolerance']
if maxerror > tol: # Threshold based on printing 6 digits in the CSV file
print "Files differ. %i / %i elements above specified tolerance" % (np.sum(relerror > tol), relerror.size)
return 1
else:
return 0
def regression_test_message(target, source, env):
return """* 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