diff --git a/SConstruct b/SConstruct
index fce5514..f65b245 100644
--- a/SConstruct
+++ b/SConstruct
@@ -1,402 +1,429 @@
# -*- mode:python; coding: utf-8 -*-
# vim: set ft=python:
# @file
# @section LICENSE
#
# Copyright (©) 2016-19 EPFL (École Polytechnique Fédérale de Lausanne),
# Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
# ------------------------------------------------------------------------------
# Imports
# ------------------------------------------------------------------------------
from __future__ import print_function
import os
import SCons
from os.path import join, abspath
+# Import below not strictly necessary, but good for pep8
+from SCons.Script import (
+ EnsurePythonVersion,
+ EnsureSConsVersion,
+ Help,
+ Environment,
+ Variables,
+ EnumVariable,
+ PathVariable,
+ BoolVariable,
+ Split,
+ SConscript,
+ Export,
+ Dir
+)
+
from version import write_info_file
-from detect import FindFFTW, FindBoost, FindThrust, FindCuda, FindExpolit
+from detect import (
+ FindFFTW,
+ FindBoost,
+ FindThrust,
+ FindCuda,
+ FindExpolit,
+ FindPybind11
+)
+
# ------------------------------------------------------------------------------
EnsurePythonVersion(2, 7)
EnsureSConsVersion(2, 4)
# ------------------------------------------------------------------------------
tamaas_info = dict(
version="2.1.1",
authors=[
u'Lucas Frérot',
'Guillaume Anciaux',
'Valentine Rey',
'Son Pham-Ba',
u'Jean-François Molinari'
],
maintainer=u'Lucas Frérot',
email='lucas.frerot@epfl.ch',
copyright=u"Copyright (©) 2016-20 EPFL "
+ u"(École Polytechnique Fédérale de Lausanne), "
+ u"Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)"
)
# ------------------------------------------------------------------------------
def detect_dependencies(env):
"""Detect all dependencies"""
FindFFTW(env, ['omp'], precision=env['real_type'])
FindBoost(env, ['boost/preprocessor/seq.hpp'])
FindExpolit(env)
thrust_var = 'THRUST_ROOT'
# Take cuda version of thrust if available
if 'CUDA_ROOT' in env['ENV']:
thrust_var = 'CUDA_ROOT'
FindThrust(env, env['backend'], thrust_var)
# Activate cuda if needed
if env['backend'] == 'cuda':
FindCuda(env)
+ if env['build_python']:
+ FindPybind11(env)
+
# ------------------------------------------------------------------------------
# Main compilation
# ------------------------------------------------------------------------------
# Compilation colors
colors = {
'cyan': '\033[96m',
'purple': '\033[95m',
'blue': '\033[94m',
'green': '\033[92m',
'yellow': '\033[93m',
'red': '\033[91m',
'end': '\033[0m'
}
# Inherit all environment variables (for CXX detection, etc.)
-main_env = Environment(ENV=os.environ)
+main_env = Environment(
+ ENV=os.environ,
+)
# Set tamaas information
for k, v in tamaas_info.items():
main_env[k] = v
main_env['COLOR_DICT'] = colors
# Compiler detection
compiler_default = os.getenv('CXX', 'g++')
# Build variables
vars = Variables('build-setup.conf')
vars.AddVariables(
EnumVariable('build_type', 'Build type', 'release',
allowed_values=('release', 'profiling', 'debug'),
ignorecase=2),
EnumVariable('backend', 'Thrust backend', 'omp',
allowed_values=('omp', 'tbb'),
# allowed_values=('omp', 'cuda'),
ignorecase=2),
EnumVariable('sanitizer', 'Sanitizer type', 'none',
allowed_values=('none', 'memory', 'leaks', 'address'),
ignorecase=2),
PathVariable('prefix',
'Prefix where to install', '/usr/local'),
# Dependencies paths
PathVariable('FFTW_ROOT',
'FFTW custom path', os.getenv('FFTW_ROOT', ''),
PathVariable.PathAccept),
PathVariable('THRUST_ROOT',
'Thrust custom path', os.getenv('THRUST_ROOT', ''),
PathVariable.PathAccept),
PathVariable('BOOST_ROOT',
'Boost custom path', os.getenv('BOOST_ROOT', ''),
PathVariable.PathAccept),
PathVariable('CUDA_ROOT',
'Cuda custom path', os.getenv('CUDA_ROOT', ''),
PathVariable.PathAccept),
# Dependencies provided as submodule get different default
PathVariable('GTEST_ROOT',
'Googletest custom path',
os.getenv('GTEST_ROOT', '#third-party/googletest/googletest'),
PathVariable.PathAccept),
PathVariable('PYBIND11_ROOT',
'Pybind11 custom path',
os.getenv('PYBIND11_ROOT', '#third-party/pybind11/include'),
PathVariable.PathAccept),
PathVariable('EXPOLIT_ROOT',
'Expolit custom path',
os.getenv('EXPOLIT_ROOT', '#third-party/expolit/include'),
PathVariable.PathAccept),
# Executables
('CXX', 'Compiler', compiler_default),
('py_exec', 'Python executable', 'python3'),
# Compiler flags
('CXXFLAGS', 'C++ compiler flags', os.getenv('CXXFLAGS', "")),
# Compile legacy code
BoolVariable('legacy_bem', 'Compile legacy BEM code', False),
# Cosmetic
BoolVariable('verbose', 'Activate verbosity', False),
BoolVariable('color', 'Color the non-verbose compilation output', False),
# Tamaas components
BoolVariable('build_doc', 'Build documentation', False),
BoolVariable('build_tests', 'Build test suite', False),
BoolVariable('build_python', 'Build python wrapper', True),
# Dependencies
BoolVariable('use_googletest', 'Build tests using GTest', False),
# Strip binary of extra info
BoolVariable('strip_info', 'Strip binary of added information', False),
# Type variables
EnumVariable('real_type', 'Type for real precision variables', 'double',
allowed_values=('double', 'long double')),
EnumVariable('integer_type', 'Type for integer variables', 'int',
allowed_values=('int', 'long')),
)
# Set variables of environment
vars.Update(main_env)
help_text = vars.GenerateHelpText(main_env)
help_text += """
Commands:
scons [build] [options]... Compile Tamaas (and additional modules/tests)
scons install [prefix=/your/prefix] [options]... Install Tamaas to prefix
scons dev Install symlink to Tamaas python module (useful to development purposes)
scons test Run tests with pytest
scons doc Compile documentation with Doxygen and Sphinx+Breathe
""" # noqa
Help(help_text)
# Save all options, not just those that differ from default
with open('build-setup.conf', 'w') as setup:
for option in vars.options:
setup.write("# " + option.help + "\n")
setup.write("{} = '{}'\n".format(option.key, main_env[option.key]))
main_env['should_configure'] = \
not main_env.GetOption('clean') and not main_env.GetOption('help')
build_type = main_env['build_type']
build_dir = 'build-' + main_env['build_type']
main_env['build_dir'] = build_dir
if main_env['should_configure']:
print("Building in " + build_dir)
verbose = main_env['verbose']
# Remove colors if not set
if not main_env['color']:
for key in colors:
colors[key] = ''
if not verbose:
- main_env['SHCXXCOMSTR'] = \
+ main_env['CXXCOMSTR'] = main_env['SHCXXCOMSTR'] = \
u'{0}[Compiling ($SHCXX)] {1}$SOURCE'.format(colors['green'],
colors['end'])
- main_env['SHLINKCOMSTR'] = \
+ main_env['LINKCOMSTR'] = main_env['SHLINKCOMSTR'] = \
u'{0}[Linking] {1}$TARGET'.format(colors['purple'],
colors['end'])
+ main_env['PRINT_CMD_LINE_FUNC'] = pretty_cmd_print
# Include paths
main_env.AppendUnique(CPPPATH=['#/src',
'#/src/core',
'#/src/bem',
'#/src/surface',
'#/src/python',
'#/src/percolation',
'#/src/model',
'#/src/model/elasto_plastic',
'#/src/solvers',
'#/src/gpu',
'#/python'])
# Changing the shared object extension
main_env['SHOBJSUFFIX'] = '.o'
# Back to gcc if cuda is activated
if main_env['backend'] == "cuda" and "g++" not in main_env['CXX']:
raise SCons.Errors.StopError('GCC should be used when compiling with CUDA')
# OpenMP flags - compiler dependent
omp_flags = {
"g++": ["-fopenmp"],
"clang++": ["-fopenmp"],
"icpc": ["-qopenmp"]
}
def cxx_alias(cxx):
for k in omp_flags.keys():
if k in cxx:
return k
raise SCons.Errors.StopError('Unsupported compiler: ' + cxx)
cxx = cxx_alias(main_env['CXX'])
main_env['CXXFLAGS'] = Split(main_env['CXXFLAGS'])
main_env.AppendUnique(CXXFLAGS='-std=c++14 -Wall -Wextra -pedantic'.split())
# Append openmp flags regardless of backend for fftw_omp
main_env.AppendUnique(CXXFLAGS=omp_flags[cxx])
main_env.AppendUnique(LINKFLAGS=omp_flags[cxx])
# Correct bug in clang?
if main_env['backend'] == 'omp' and cxx == "clang++":
main_env.AppendUnique(LIBS=["atomic"])
elif main_env['backend'] == 'tbb':
main_env.AppendUnique(LIBS=['tbb'])
# Force deactivate legacy when using intel
# Intel does not like old openmp loops
if cxx == 'icpc' and main_env['legacy_bem']:
print("Using intel compiler => deactivating legacy code")
main_env['legacy_bem'] = False
# Flags and options
if main_env['build_python']:
main_env.AppendUnique(CPPDEFINES=['USE_PYTHON'])
if main_env['strip_info']:
main_env.AppendUnique(CPPDEFINES=['STRIP_INFO'])
if main_env['legacy_bem']:
main_env.AppendUnique(CPPDEFINES=['LEGACY_BEM'])
if main_env['build_type'] == 'debug':
main_env.AppendUnique(CPPDEFINES=['TAMAAS_DEBUG'])
# Define the scalar types
main_env.AppendUnique(CPPDEFINES={'REAL_TYPE': '${real_type}',
'INT_TYPE': '${integer_type}'})
if main_env['real_type'] == 'long double':
main_env.AppendUnique(CPPDEFINES=['LONG_PRECISION'])
# Compilation flags
cxxflags_dict = {
"debug": Split("-g -O0"),
"profiling": Split("-g -O3 -fno-omit-frame-pointer"),
"release": Split("-O3")
}
# Link flags for shared libs
shlinkflags_dict = {
"debug": [],
"profiling": Split("-g -O3 -fno-omit-frame-pointer"),
"release": []
}
if main_env['sanitizer'] != 'none':
if main_env['backend'] == 'cuda':
raise SCons.Errors.StopError(
"Sanitizers with cuda are not yet supported!")
cxxflags_dict[build_type].append('-fsanitize=${sanitizer}')
shlinkflags_dict[build_type].append('-fsanitize=${sanitizer}')
main_env.AppendUnique(CXXFLAGS=cxxflags_dict[build_type])
main_env.AppendUnique(SHLINKFLAGS=shlinkflags_dict[build_type])
main_env.AppendUnique(LINKFLAGS=shlinkflags_dict[build_type])
# Adding
main_env['LIBPATH'] = [abspath(join(build_dir, 'src')),
abspath(join(main_env['prefix'], 'lib'))]
main_env['RPATH'] = "$LIBPATH"
if main_env['should_configure']:
detect_dependencies(main_env)
# Writing information file
write_info_file("src/tamaas_info.cpp", main_env['build_type'])
-# Saving the env file
-main_env['gen_print'] = gen_print
-
env_file_env = main_env.Clone(
- PRINT_CMD_LINE_FUNC=gen_print("Writing", "cyan", main_env),
SUBST_DICT={"@build@": abspath(build_dir)},
tools=['textfile'],
)
# Environment file content
env_content = """
export PYTHONPATH=$$PYTHONPATH:@build@/python
export LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:@build@/src
""".split('\n')
# Writing environment file
-env_file_env.Textfile(join(build_dir, 'tamaas_environment.sh'), env_content)
+env_file = env_file_env.Textfile(join(build_dir, 'tamaas_environment.sh'),
+ env_content)
# Building sub-directories
def subdir(dir):
return SConscript(join(dir, 'SConscript'),
variant_dir=join(build_dir, dir),
duplicate=True)
# Building Tamaas library
Export('main_env')
subdir('src')
-build_targets = ['build-cpp']
+build_targets = ['build-cpp', env_file]
install_targets = ['install-lib']
# Building Tamaas extra components
for dir in ['python', 'tests']:
if main_env['build_{}'.format(dir)] and not main_env.GetOption('help'):
subdir(dir)
build_targets.append('build-{}'.format(dir))
# Building API + Sphinx documentation if requested
if main_env['build_doc']:
doc = main_env.Command('#.phony_doc', '',
'make -C {}'.format(Dir('#/doc')))
main_env.Alias('doc', doc)
else:
dummy_command(main_env, 'doc', 'Command "doc" does not do anything'
' without documentation activated ("build_doc=True")')
# Define dummy dev command when python is deactivated
if not main_env['build_python']:
dummy_command(main_env, 'dev', 'Command "dev" does not do anything'
+ ' without python activated ("build_python=True")')
else:
install_targets.append('install-python')
# Define dummy test command when tests are deactivated
if not main_env['build_tests']:
dummy_command(main_env, 'test', 'Command "test" does not do anything'
+ ' without tests activated ("build_tests=True")')
# Definition of target aliases, a.k.a. sub-commands
main_env.Alias('build', build_targets)
# Define proper install targets
main_env.Alias('install', install_targets)
# Default target is to build stuff
main_env.Default('build')
diff --git a/python/SConscript b/python/SConscript
index 0a6243a..4e8ed36 100644
--- a/python/SConscript
+++ b/python/SConscript
@@ -1,145 +1,143 @@
# -*- mode:python; coding: utf-8 -*-
# vim: set ft=python:
# @file
# @section LICENSE
#
# Copyright (©) 2016-19 EPFL (École Polytechnique Fédérale de Lausanne),
# Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
from __future__ import print_function
from os.path import abspath, join
from SCons.Script import Import, Split, Copy, Dir
Import('main_env')
# Pybind11 wrapper
env_pybind = main_env.Clone(SHLIBPREFIX='')
# Remove pedantic warnings
cxx_flags = env_pybind['CXXFLAGS']
try:
del cxx_flags[cxx_flags.index('-pedantic')]
except ValueError:
pass
env_pybind.Tool(pybind11)
pybind_sources = Split("""
tamaas_module.cpp
wrap/core.cpp
wrap/percolation.cpp
wrap/surface.cpp
wrap/model.cpp
wrap/solvers.cpp
wrap/compute.cpp
wrap/test_features.cpp
""")
# Adding legacy wrap code
if main_env['legacy_bem']:
pybind_sources += ["wrap/bem.cpp"]
# Building the pybind library
-tamaas_wrap = env_pybind.SharedLibrary(
+tamaas_wrap = env_pybind.Pybind11Module(
target='tamaas/_tamaas',
source=pybind_sources,
LIBS=['Tamaas'],
)
# For some reason link happens too early
Import('libTamaas')
env_pybind.Depends(tamaas_wrap, libTamaas)
# Copying the __init__.py file with extra python classes
-copy_env = env_pybind.Clone(
- PRINT_CMD_LINE_FUNC=main_env['gen_print']("Copying", "red", main_env))
+copy_env = env_pybind.Clone()
# Copying additional python files
python_files = """
compute.py
dumpers/__init__.py
dumpers/_helper.py
nonlinear_solvers/__init__.py
""".split()
targets = [tamaas_wrap]
targets += [
copy_env.Command(join('tamaas', f),
join(abspath(str(Dir('#python/tamaas'))), f),
Copy("$TARGET", "$SOURCE"))
for f in python_files
]
targets.append(copy_env.Command('MANIFEST.in', '#python/MANIFEST.in',
Copy("$TARGET", "$SOURCE")))
subst_env = env_pybind.Clone(
- PRINT_CMD_LINE_FUNC=main_env['gen_print']("Generating", "yellow", main_env),
SUBST_DICT={
'@version@': '$version',
'@authors@': str(copy_env['authors']),
'@email@': '$email',
# TODO change when issue with unicode fixed
# '@copyright@': '$copyright,
# '@maintainer@': '$maintainer',
}
)
subst_env.Tool('textfile')
targets.append(subst_env.Substfile('setup.py.in'))
targets.append(subst_env.Substfile('tamaas/__init__.py.in'))
# Defining alias for python builds
main_env.Alias('build-python', targets)
# Checking if we can use pip to install (more convenient for end-user)
conf = Configure(main_env, custom_tests={'CheckPythonModule': CheckPythonModule})
has_pip = conf.CheckPythonModule('pip')
install_env = conf.Finish()
# Current build directory
install_env['PYDIR'] = Dir('.')
# Setting command line for installation
if has_pip:
install_env['PYINSTALLCOM'] = '${py_exec} -m pip install $PYOPTIONS .'
install_env['PYDEVELOPCOM'] = '${py_exec} -m pip install $PYOPTIONS -e .'
else:
install_env['PYINSTALLCOM'] = '${py_exec} setup.py install $PYOPTIONS'
install_env['PYDEVELOPCOM'] = '${py_exec} setup.py develop $PYOPTIONS'
install_env['py_version'] = get_python_version(install_env)
install_env.PrependENVPath(
'PYTHONPATH',
install_env.subst('${prefix}/lib/python${py_version}/site-packages'))
# Specify install target
python_install = install_env.Command(
join('$prefix', 'dummy_target'),
targets, install_env['PYINSTALLCOM'], PYOPTIONS='--prefix ${prefix}',
chdir=install_env['PYDIR'])
python_install_dev = install_env.Command(
join('$prefix', 'dummy_target_local'),
targets, install_env['PYDEVELOPCOM'], PYOPTIONS='--user',
chdir=install_env['PYDIR'])
main_env.Alias('install-python', python_install)
main_env.Alias('dev', python_install_dev)
diff --git a/site_scons/detect.py b/site_scons/detect.py
index 77b04f2..92b8399 100644
--- a/site_scons/detect.py
+++ b/site_scons/detect.py
@@ -1,213 +1,243 @@
# -*- coding: utf-8 -*-
# @file
# @section LICENSE
#
# Copyright (©) 2016-19 EPFL (École Polytechnique Fédérale de Lausanne),
# Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from __future__ import print_function
+
import SCons
-from os.path import join, abspath, exists, isdir, basename
+from os.path import join, abspath, exists, isdir
from SCons.Script import Configure
# ------------------------------------------------------------------------------
def _get_path(env, ext, module_var):
path = ""
if module_var in env and env[module_var] != "":
root = abspath(env[module_var])
if not exists(root) or not isdir(root):
raise RuntimeError("{} is set to a non-existing path '{}'".format(
module_var, root))
path = join(root, ext)
if not exists(path) or not isdir(path):
raise RuntimeError("{} does not contain '{}' directory".format(
module_var, ext))
return [path]
# ------------------------------------------------------------------------------
def FindFFTW(env, components=[], precision='double', module_var='FFTW_ROOT'):
"""Find FFTW3 and set relevant environment variables"""
if not env.get('should_configure', True):
return
fftw_vars = {}
fftw_vars['CPPPATH'] = _get_path(env, 'include', module_var)
fftw_vars['LIBPATH'] = _get_path(env, 'lib', module_var)
# Setting up FFTW
wishes = ['main'] + components
fftw_name = "fftw3{}"
# Components
lib_names = {'main': fftw_name,
'thread': fftw_name + '_threads',
'omp': fftw_name + '_omp'}
inc_names = ['fftw3.h']
# Checking list of wishes
try:
libs = [lib_names[i].format("") for i in wishes]
except KeyError:
raise SCons.Errors.StopError(
'Incompatible FFTW wishlist {0}'.format(wishes))
fftw_vars['LIBS'] = libs
# Add long precision libraries
if precision == 'long double':
fftw_vars['LIBS'] += [lib_names[i].format("l") for i in wishes]
conf_env = env.Clone(**fftw_vars)
conf = Configure(conf_env)
if not conf.CheckLibWithHeader(libs, inc_names, 'c++'):
raise SCons.Errors.StopError(
'Failed to find libraries {0} or '
- 'headers {1}.'.format(str([lib_names[i].format("") for i in wishes]),
- str(inc_names)))
+ 'headers {1}.'.format(
+ str([lib_names[i].format("") for i in wishes]), str(inc_names)))
conf_env = conf.Finish()
# Update modified variables
env.AppendUnique(**fftw_vars)
# ------------------------------------------------------------------------------
def FindBoost(env, headers=['boost/version.hpp'], module_var='BOOST_ROOT'):
"""Find Boost and set relevant environment variables"""
if not env.get('should_configure', True):
return
boost_vars = {}
boost_vars['CPPPATH'] = _get_path(env, 'include', module_var)
conf_env = env.Clone(**boost_vars)
conf = Configure(conf_env)
if not conf.CheckCXXHeader(headers):
raise SCons.Errors.StopError(
'Failed to find Boost headers {}'.format(headers))
conf_env = conf.Finish()
# Update modified variables
env.AppendUnique(**boost_vars)
# ------------------------------------------------------------------------------
def FindThrust(env, backend='omp', module_var='THRUST_ROOT'):
"""Find Thrust and set relevant environment variables"""
if not env.get('should_configure', True):
return
if backend not in ('omp', 'cuda', 'tbb'):
raise SCons.Errors.StopError(
'Unknown thrust backend "{}"'.format(backend))
thrust_vars = {}
thrust_vars['CPPPATH'] = _get_path(env, 'include', module_var)
- if basename(env['CXX']) == "clang++":
+ if "clang++" in env['CXX']:
thrust_vars['CXXFLAGS'] = ["-Wno-unused-local-typedef"]
thrust_vars['CPPDEFINES'] = [
"THRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_{}".format(backend.upper())
]
if backend == 'cuda':
thrust_vars['CPPDEFINES'].append("USE_CUDA")
conf_env = env.Clone(**thrust_vars)
conf = Configure(conf_env)
if not conf.CheckCXXHeader('thrust/version.h'):
raise SCons.Errors.StopError(
'Failed to find Thrust')
conf_env = conf.Finish()
# Update modified variables
env.AppendUnique(**thrust_vars)
+# ------------------------------------------------------------------------------
+def FindPybind11(env, module_var='PYBIND11_ROOT'):
+ """Detech Pybind11 and set appropriate build variables"""
+ if not env.get('should_configure', True) \
+ or env.get("PYBIND11_FOUND", False):
+ return
+
+ pybind11_vars = {}
+
+ clone = env.Clone(CPPPATH=[])
+ clone.ParseConfig('${py_exec}-config --includes')
+ clone.AppendUnique(CPPPATH=['$PYBIND11_ROOT'])
+ pybind11_vars['CPPPATH'] = clone['CPPPATH']
+
+ conf = Configure(clone)
+ if not conf.CheckCXXHeader('pybind11/pybind11.h'):
+ raise SCons.Errors.StopError(
+ 'Failed to find pybind11 header\n' +
+ "Run 'git submodule update --init --recursive " +
+ "third-party/pybind11'")
+ conf.Finish()
+
+ pybind11_vars['PYBIND11_FOUND'] = True
+
+ # Update variables
+ env.AppendUnique(**pybind11_vars)
+
+
# ------------------------------------------------------------------------------
def FindCuda(env, module_var="CUDA_ROOT"):
"""Detect cuda on clusters"""
if not env.get('should_configure', True):
return
if 'CUDA_ROOT' in env:
env['CUDA_TOOLKIT_PATH'] = _get_path(env, '', module_var)
else:
env['CUDA_TOOLKIT_PATH'] = '/opt/cuda'
env['CUDA_COMPONENTS'] = ['cufft']
env['CUDA_ARCH_FLAG'] = '-arch=sm_60'
colors = env['COLOR_DICT']
if not env['verbose']:
env['NVCCCOMSTR'] = env['SHCXXCOMSTR']
env['SHLINKCOMSTR'] = \
u'{0}[Linking (cuda)] {1}$TARGET{2}'.format(colors['purple'],
colors['blue'],
colors['end'])
env.AppendUnique(CXXFLAGS=[
"-expt-extended-lambda", # experimental lambda support
"-expt-relaxed-constexpr", # experimental constexpr
])
if env['build_type'] == 'debug':
env.AppendUnique(CXXFLAGS="-G")
env.Tool('nvcc')
# ------------------------------------------------------------------------------
def FindGTest(env):
"""A tool to configure GoogleTest"""
if not env.get('should_configure', True):
return
conf = Configure(env)
if not conf.CheckCXXHeader('gtest/gtest.h'):
raise SCons.Errors.StopError(
'Failed to find GoogleTest header\n' +
"Run 'git submodule update --init --recursive " +
"third-party/googletest'")
env = conf.Finish()
# ------------------------------------------------------------------------------
def FindExpolit(env):
"""A tool to configure Expolit"""
if not env.get('should_configure', True):
return
expolit_vars = {"CPPPATH": "#/third-party/expolit/include"}
conf_env = env.Clone(**expolit_vars)
conf = Configure(conf_env)
if not conf.CheckCXXHeader('expolit/expolit'):
raise SCons.Errors.StopError(
'Failed to find Expolit header\n' +
"Run 'git submodule update --init " +
"third-party/expolit'")
conf_env = conf.Finish()
env.AppendUnique(**expolit_vars)
diff --git a/site_scons/site_init.py b/site_scons/site_init.py
index d98923f..e7bced8 100644
--- a/site_scons/site_init.py
+++ b/site_scons/site_init.py
@@ -1,131 +1,126 @@
# -*- coding: utf-8 -*-
# @file
# @section LICENSE
#
# Copyright (©) 2016-19 EPFL (École Polytechnique Fédérale de Lausanne),
# Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
import subprocess
-import SCons
-from SCons.Script import Dir, Configure
+from detect import FindPybind11
# ------------------------------------------------------------------------------
def pybind11(env):
"""A tool to configure pybind11"""
- if env.GetOption('clean'):
- return
-
- # Checking if numpy is available
- conf = Configure(env,
- custom_tests={'CheckPythonModule': CheckPythonModule})
- if not conf.CheckPythonModule('numpy'):
- raise SCons.Errors.StopError('Did not find Numpy')
- conf.Finish()
-
- # Run code below to get versions from user preference and not scons
- versions_script = """
-from __future__ import print_function
-import distutils.sysconfig as dsys
-from numpy.distutils.misc_util import get_numpy_include_dirs as numpy_dirs
+ def execute(command):
+ return [line for line in subprocess.check_output(
+ command, universal_newlines=True).split('\n')
+ if line != ""]
-print(dsys.get_python_inc())
-for d in numpy_dirs():
- print(d)"""
+ # Create a clone so we do not modify env
+ clone = env.Clone()
- includes = subprocess.check_output([env['py_exec'], "-c", versions_script],
- universal_newlines=True).split('\n')
-
- includes += [Dir(env['PYBIND11_ROOT'])]
+ # Set variables for clone
+ FindPybind11(clone)
+ includes = clone['CPPPATH']
# Extension of shared library for python
- extension = subprocess.check_output([env['py_exec'] + '-config',
- "--extension-suffix"],
- universal_newlines=True).split('\n')[0]
- env['SHLIBSUFFIX'] = extension
- env.AppendUnique(CPPPATH=includes)
+ try:
+ extension = execute(
+ '{}-config --extension-suffix'.format(env['py_exec']).split())[0]
+ except subprocess.CalledProcessError:
+ extension = ".so"
+
+ def pybind11_builder(env, target, source, **kwargs):
+ """Create a pybind11 module"""
+ clone = env.Clone()
+ clone.AppendUnique(CPPPATH=includes)
+ clone['SHLIBSUFFIX'] = extension
+ clone.AppendUnique(SHLINKFLAGS=['-fvisibility=hidden'])
+ return clone.SharedLibrary(target, source, **kwargs)
- conf = Configure(env)
- if not conf.CheckCXXHeader('pybind11/pybind11.h'):
- raise SCons.Errors.StopError(
- 'Failed to find pybind11 header\n' +
- "Run 'git submodule update --init --recursive " +
- "third-party/pybind11'")
- env = conf.Finish()
+ # Add pseudo-builder to master environment
+ env.AddMethod(pybind11_builder, 'Pybind11Module')
# ------------------------------------------------------------------------------
-def gen_print(action_string, color_string, env):
- """Generic function for creating pretty compile output"""
- if env['verbose']:
- return None
-
- def print_fun(command, target, source, env):
- colors = env['COLOR_DICT']
- print("{}[{}] {}{}".format(colors[color_string],
- action_string,
- colors['end'],
- target[0]))
- return print_fun
+def pretty_cmd_print(command, target, source, env):
+ colors = env['COLOR_DICT']
+ if 'Copy' in command:
+ color = colors['red']
+ action = 'Copying'
+ elif 'Creating' in command:
+ color = colors['yellow']
+ action = 'Generating'
+ else:
+ print(command)
+ return
+
+ print("{color}[{action}] {end}{target}".format(
+ color=color,
+ end=colors['end'],
+ target=target[0],
+ action=action
+ ))
# ------------------------------------------------------------------------------
def CheckPythonModule(context, module):
"""Checks the existence of a python module"""
context.Message('Checking for Python module {}... '.format(module))
env = context.sconf.env
command = [env.subst('${py_exec}'), '-c', 'import {}'.format(module)]
context.Log('Executing external command: {}\n'.format(command))
try:
subprocess.check_output(command, stderr=subprocess.STDOUT)
result = True
except subprocess.CalledProcessError as e:
result = False
context.Log(e.output + '\n')
context.Result(result)
return result
# ------------------------------------------------------------------------------
def dummy_command(env, command, error_msg):
"""Creates a dummy scons command"""
def print_error(*args, **kwargs):
print(error_msg)
def print_cmd(*args, **kwargs):
pass
comm = env.Command('#.phony_{}'.format(command),
'', print_error, PRINT_CMD_LINE_FUNC=print_cmd)
env.Alias(command, comm)
# ------------------------------------------------------------------------------
def get_python_version(env):
versions_script = """
from __future__ import print_function
from sysconfig import get_python_version
print(get_python_version())"""
version = subprocess.check_output([env['py_exec'], "-c", versions_script],
universal_newlines=True).replace('\n',
'')
print(version)
return version
diff --git a/tests/SConscript b/tests/SConscript
index acd6dcf..96f1a6e 100644
--- a/tests/SConscript
+++ b/tests/SConscript
@@ -1,199 +1,201 @@
# -*- mode:python; coding: utf-8 -*-
# vim: set ft=python:
# @file
# @section LICENSE
#
# Copyright (©) 2016-19 EPFL (École Polytechnique Fédérale de Lausanne),
# Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
from __future__ import print_function
from os.path import join
from SCons.Script import Split, Copy, Dir, Import
-from detect import FindGTest
+from detect import FindGTest, FindPybind11
# ------------------------------------------------------------------------------
def copyComStr(env, main):
if 'SHCXXCOMSTR' in main:
env['CXXCOMSTR'] = main['SHCXXCOMSTR']
if 'SHLINKCOMSTR' in main:
env['LINKCOMSTR'] = main['SHLINKCOMSTR']
# ------------------------------------------------------------------------------
def make_python_tests(env):
"""Copy python tests to build directory"""
- test_env = env.Clone(
- PRINT_CMD_LINE_FUNC=main_env['gen_print']("Copying", "red", main_env))
+ test_env = env.Clone()
test_files = Split("""
test_hertz.py
test_westergaard.py
test_patch_westergaard.py
test_patch_plasticity.py
test_surface.py
test_hertz_disp.py
test_hertz_kato.py
test_saturated_pressure.py
test_flood_fill.py
test_integral_operators.py
test_dumper.py
- test_gtest.py
test_tangential.py
test_boussinesq_surface.py
test_voigt.py
test_memory.py
fftfreq.py
conftest.py
""")
if env['legacy_bem']:
test_files += ['test_bem_grid.py', 'test_autocorrelation.py']
src_dir = "#/tests"
- targets = []
-
- for file in test_files:
- source = join(src_dir, file)
- targets.append(test_env.Command(file, source,
- Copy("$TARGET", "$SOURCE")))
+ targets = [
+ test_env.Command(file, join(src_dir, file),
+ Copy("$TARGET", "$SOURCE"))
+ for file in test_files
+ ]
- test_env = env.Clone()
+ test_env = env.Clone(tools=[pybind11])
# Helper module for integral operators
test_env['SHLIBPREFIX'] = ''
- register = test_env.SharedLibrary(
+ register = test_env.Pybind11Module(
target="register_integral_operators",
source=["register_integral_operators.cpp"],
LIBS=['Tamaas'])
Import('libTamaas')
test_env.Depends(register, libTamaas)
targets.append(register)
return targets
# ------------------------------------------------------------------------------
def compile_google_test(env, gtest_path):
gtest_obj = env.Object('gtest.o', [join(gtest_path, "src/gtest-all.cc")])
return env.StaticLibrary('gtest', gtest_obj)
# ------------------------------------------------------------------------------
def make_google_tests(env):
gtest_dir = Dir(env['GTEST_ROOT'])
gtest_env = env.Clone(CPPPATH=[gtest_dir],
CXXFLAGS=['-pthread', '-isystem',
join(str(gtest_dir), "include")])
colors = env['COLOR_DICT']
if not env['verbose']:
gtest_env['ARCOMSTR'] = u'{}[Ar]{} $TARGET'.format(colors['purple'],
colors['end'])
gtest_env['RANLIBCOMSTR'] = \
u'{}[Randlib]{} $TARGET'.format(colors['purple'],
colors['end'])
FindGTest(gtest_env)
libgtest = None
# Hugly hack to detect if we need to compile gtest submodule
if env['GTEST_ROOT'] == '#third-party/googletest/googletest':
gtest_path = str(gtest_dir)
libgtest = compile_google_test(gtest_env, gtest_path)
env.AppendUnique(LIBS=['Tamaas'],
CXXFLAGS=gtest_env['CXXFLAGS'])
google_test_files = Split("""
test_fft.cpp
test_grid.cpp
test_loop.cpp
test_model.cpp
test_static_types.cpp
test_integration.cpp
""")
+ # Some tests require pybind11
gtest_main = env.Object("tamaas_gtest_main.o", 'tamaas_gtest_main.cc')
gtest_all = env.Program('test_gtest_all', google_test_files + [gtest_main],
LIBS=(env['LIBS'] + ['gtest']))
Import('libTamaas')
env.Depends(gtest_all, libTamaas)
env.Depends(gtest_all, libgtest)
return [gtest_all]
# ------------------------------------------------------------------------------
def make_bare_tests(env):
rough = env.Program("test_rough_surface.cpp")
Import('libTamaas')
env.Depends(rough, libTamaas)
return [rough]
# ------------------------------------------------------------------------------
Import('main_env')
# Setup of test environment
test_env = main_env.Clone()
test_env.AppendUnique(LIBS=['Tamaas'], LIBPATH=['.'])
-copyComStr(test_env, main_env)
# Building tests that do not require any third party
targets = make_bare_tests(test_env)
+# Build tests that required python bindings
if test_env['build_python']:
+ print("Checking pybind11")
+ FindPybind11(test_env)
test_env.Tool(pybind11)
test_env.ParseConfig("${py_exec}-config --ldflags")
test_env['CCFLAGS'] = []
targets += make_python_tests(test_env)
# Building google tests
if test_env['use_googletest']:
targets += make_google_tests(test_env)
+ targets.append(test_env.Command('test_gtest.py', '#tests/test_gtest.py',
+ Copy('$TARGET', '$SOURCE')))
# Target alias to build tests
main_env.Alias('build-tests', targets)
# Check if pytest is installed
conf = Configure(main_env,
custom_tests={'CheckPythonModule': CheckPythonModule})
has_pytest = conf.CheckPythonModule('pytest')
conf.Finish()
# Define a command to execute tests
if has_pytest:
pytest_env = main_env.Clone()
pytest_env.PrependENVPath('PYTHONPATH',
pytest_env.subst('${build_dir}/python'))
test_target = pytest_env.Command('#/.phony_test', targets,
'${py_exec} -m pytest ${build_dir}/tests')
main_env.Alias('test', test_target)
else:
# We still define a target here so that `scons test` still works
dummy_command(main_env, 'test',
'Cannot run tests: pytest is not installed')