diff --git a/SConstruct b/SConstruct index a8d197d..8e168ca 100644 --- a/SConstruct +++ b/SConstruct @@ -1,463 +1,464 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # @file # @section LICENSE # # Copyright (©) 2016-2021 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 from subprocess import check_output -from os.path import join, abspath, basename # 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 SCons.Errors import StopError from SCons import __version__ as scons_version from version import get_git_subst from detect import ( FindFFTW, FindBoost, FindThrust, FindCuda, FindExpolit, FindPybind11 ) from INFOS import TAMAAS_INFOS # ------------------------------------------------------------------------------ EnsurePythonVersion(2, 7) EnsureSConsVersion(2, 4) # ------------------------------------------------------------------------------ def detect_dependencies(env): "Detect all dependencies" fftw_comp = { 'omp': ['omp'], 'threads': ['threads'], 'none': [], } fftw_components = fftw_comp[env['fftw_threads']] if main_env['use_mpi']: fftw_components.append('mpi') FindFFTW(env, fftw_components, 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) -def subdir(dir): +def subdir(env, dir): "Building a sub-directory" - return SConscript(join(dir, 'SConscript'), - variant_dir=join(build_dir, dir), - duplicate=True) + return env.SConscript(env.File('SConscript', dir), + variant_dir=env.Dir(dir, env['build_dir']), + duplicate=True) # ------------------------------------------------------------------------------ # Main compilation # ------------------------------------------------------------------------------ # Compilation colors colors = { 'cyan': '\033[96m', 'purple': '\033[95m', 'blue': '\033[94m', 'green': '\033[92m', 'yellow': '\033[93m', 'gray': '\033[38;5;8m', 'orange': '\033[38;5;208m', 'red': '\033[91m', 'end': '\033[0m' } # Inherit all environment variables (for CXX detection, etc.) main_env = Environment( ENV=os.environ, ) # Set tamaas information for k, v in TAMAAS_INFOS.items(): main_env[k] = v main_env['COLOR_DICT'] = colors +main_env.AddMethod(subdir, 'SubDirectory') # 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=('cpp', 'omp', 'tbb'), ignorecase=2), EnumVariable('fftw_threads', 'Threads FFTW library preference', 'omp', allowed_values=('omp', 'threads', 'none'), 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', os.getenv('CXX', 'g++')), ('MPICXX', 'MPI Compiler wrapper', os.getenv('MPICXX', 'mpicxx')), ('py_exec', 'Python executable', 'python3'), # Compiler flags ('CXXFLAGS', 'C++ compiler flags', os.getenv('CXXFLAGS', "")), # 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), BoolVariable('use_mpi', 'Builds multi-process parallelism', False), # Distribution options BoolVariable('strip_info', 'Strip binary of added information', False), BoolVariable('build_static_lib', "Build a static libTamaas", 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 scons archive Create a gzipped archive from source """ # 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 +build_dir = 'build-${build_type}' +main_env['build_dir'] = main_env.Dir(build_dir) # Printing some build infos if main_env['should_configure']: print('-- SCons {}'.format(scons_version)) print(main_env.subst("-- Build type: ${build_type}\n" "-- Thrust backend: ${backend}\n" "-- FFTW threads: ${fftw_threads}\n" - "-- MPI: ${use_mpi}")) - print("-- Build directory: " + build_dir) + "-- MPI: ${use_mpi}\n" + "-- Build directory: ${build_dir}")) # Setting up the python name with version if main_env['build_python']: args = (main_env.subst("${py_exec} -c").split() + ["from distutils.sysconfig import get_python_version;" "print(get_python_version())"]) main_env['py_version'] = bytes(check_output(args)).decode() print(main_env.subst('-- Python version: $py_version')) 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['CXXCOMSTR'] = main_env['SHCXXCOMSTR'] = \ u'{0}[Compiling ($SHCXX)] {1}$SOURCE'.format(colors['green'], colors['end']) main_env['LINKCOMSTR'] = main_env['SHLINKCOMSTR'] = \ u'{0}[Linking] {1}$TARGET'.format(colors['purple'], colors['end']) main_env['ARCOMSTR'] = u'{}[Ar]{} $TARGET'.format(colors['purple'], colors['end']) main_env['RANLIBCOMSTR'] = \ u'{}[Randlib]{} $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/mpi', '#/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 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 StopError('Unsupported compiler: ' + cxx) cxx = cxx_alias(main_env['CXX']) # Setting main compilation flags main_env['CXXFLAGS'] = Split(main_env['CXXFLAGS']) main_env['LINKFLAGS'] = main_env['CXXFLAGS'] main_env.AppendUnique( CXXFLAGS=Split('-std=c++14 -Wall -Wextra -pedantic'), CPPDEFINES=['TAMAAS_BACKEND=TAMAAS_BACKEND_{}'.format( main_env['backend'].upper() )], ) # Adding OpenMP flags if main_env['backend'] == 'omp': main_env.AppendUnique(CXXFLAGS=omp_flags[cxx]) main_env.AppendUnique(LINKFLAGS=omp_flags[cxx]) else: main_env.AppendUnique(CXXFLAGS=['-Wno-unknown-pragmas']) # 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']) # Manage MPI compiler if main_env['use_mpi']: main_env['CXX'] = '$MPICXX' main_env.AppendUnique(CPPDEFINES=['TAMAAS_USE_MPI']) main_env.AppendUnique(CXXFLAGS=['-Wno-cast-function-type']) # Flags and options if main_env['build_type'] == 'debug': main_env.AppendUnique(CPPDEFINES=['TAMAAS_DEBUG']) # Define the scalar types main_env.AppendUnique(CPPDEFINES={'TAMAAS_REAL_TYPE': '${real_type}', 'TAMAAS_INT_TYPE': '${integer_type}'}) # Compilation flags cxxflags_dict = { "debug": Split("-g -O0"), "profiling": Split("-g -O3 -fno-omit-frame-pointer"), "release": Split("-O3") } if main_env['sanitizer'] != 'none': if main_env['backend'] == 'cuda': raise StopError( "Sanitizers with cuda are not yet supported!") cxxflags_dict[build_type].append('-fsanitize=${sanitizer}') main_env.AppendUnique(CXXFLAGS=cxxflags_dict[build_type]) main_env.AppendUnique(SHLINKFLAGS=cxxflags_dict[build_type]) main_env.AppendUnique(LINKFLAGS=cxxflags_dict[build_type]) if main_env['should_configure']: basic_checks(main_env) detect_dependencies(main_env) # Writing information file main_env.Tool('textfile') main_env['SUBST_DICT'] = get_git_subst() # Empty values if requested if main_env['strip_info']: for k in main_env['SUBST_DICT']: main_env['SUBST_DICT'][k] = "" # Substitution of environment file main_env['SUBST_DICT'].update({ '@build_type@': '$build_type', - '@build_dir@': abspath(build_dir), + '@build_dir@': '${build_dir.abspath}', '@build_version@': '$version', }) # Environment file content env_content = """export PYTHONPATH=@build_dir@/python:$$PYTHONPATH export LD_LIBRARY_PATH=@build_dir@/src:$$LD_LIBRARY_PATH """ # Writing environment file -env_file = main_env.Textfile(join(build_dir, 'tamaas_environment.sh'), - env_content) +env_file = main_env.Textfile( + main_env.File('tamaas_environment.sh', main_env['build_dir']), + env_content) # Default targets build_targets = ['build-cpp', env_file] install_targets = ['install-lib'] if main_env._get_major_minor_revision(scons_version)[0] >= 4: main_env.Tool('compilation_db') main_env.CompilationDatabase(PRINT_CMD_LINE_FUNC=pretty_cmd_print) # Building Tamaas library Export('main_env') -subdir('src') +main_env.SubDirectory('src') # Building Tamaas extra components for dir in ['python', 'tests']: if main_env['build_{}'.format(dir)] and not main_env.GetOption('help'): - subdir(dir) + main_env.SubDirectory(dir) build_targets.append('build-{}'.format(dir)) # Building API + Sphinx documentation if requested if main_env['build_doc']: - subdir('doc') + main_env.SubDirectory('doc') main_env.Alias('doc', 'build-doc') install_targets.append('install-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') # Building a tar archive archive = main_env.Command( 'tamaas-{}.tar.gz'.format(main_env['version']), '', ('tar --exclude-vcs --exclude-vcs-ignores ' '--exclude=third-party/googletest ' '--exclude=third-party/pybind11 ' '--exclude=joss ' '--exclude=".*" ' - '-czf $TARGET {}'.format(basename(os.getcwd()))), + '-czf $TARGET {}'.format(Dir('.').name)), chdir='..', ) main_env.Alias('archive', archive) diff --git a/doc/SConscript b/doc/SConscript index a3ee390..5ad7226 100644 --- a/doc/SConscript +++ b/doc/SConscript @@ -1,79 +1,80 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # @file # @section LICENSE # # Copyright (©) 2016-2021 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 os - -from os.path import join from SCons.Script import Import, Glob, Dir, File +def add_sources(source_list, dirname, fnames): + source_list += Dir(dirname).glob('*.hh') + source_list += Dir(dirname).glob('*.cpp') + + Import('main_env') Import('libTamaas') doc_env = main_env.Clone() # Generating Doxyfile doxyfile_target = doc_env.Substfile('doxygen/Doxyfile', 'doxygen/Doxyfile.in', SUBST_DICT={ '@version@': '$version', '@build_dir@': Dir('doxygen').path, '@logo@': File('icon.svg').path, '@src_dir@': Dir('#src').abspath, }) # Generate Doxygen API documentation doc_env.Tool('doxygen') doxygen_target = doc_env.Doxygen('doxygen/xml/index.xml', [doxyfile_target, 'icon.svg']) # Adding all source files as dependencies sources = [] -for root, _, files in os.walk(Dir('#src').abspath): - sources += Glob(join(root, '*.hh')) - sources += Glob(join(root, '*.cpp')) +Dir('#src').walk(add_sources, sources) doc_env.Depends(doxygen_target, sources) # Generate Sphinx User documentation doc_env.Tool('sphinx') sphinx_sources = [Glob('sphinx/source/*'), Glob('sphinx/source/figures/*')] sphinx_targets = [] sphinx_targets.append(doc_env.Sphinx('sphinx/html/index.html', sphinx_sources)) sphinx_targets.append(doc_env.Sphinx('sphinx/tamaas.1', sphinx_sources)) doc_env.Depends(sphinx_targets, doxygen_target) # Alias for both docs doc_targets = [doxygen_target] + sphinx_targets main_env.Alias('build-doc', doc_targets) # Install target for documentation -doc_prefix = join(doc_env['prefix'], 'share', 'doc', 'tamaas') -man_prefix = join(doc_env['prefix'], 'share', 'man', 'man1') +share = Dir('share', doc_env['prefix']) +doc_prefix = share.Dir('doc').Dir('tamaas') +man_prefix = share.Dir('man').Dir('man1') doc_install = [] doc_install.append(doc_env.Install(target=doc_prefix, source=sphinx_targets[0][0].dir)) doc_install.append(doc_env.Install(target=man_prefix, source=sphinx_targets[1])) main_env.Alias('install-doc', doc_install) diff --git a/python/SConscript b/python/SConscript index f2d4f7b..1ba61c3 100644 --- a/python/SConscript +++ b/python/SConscript @@ -1,160 +1,159 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # @file # @section LICENSE # # Copyright (©) 2016-2021 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/mpi.cpp wrap/test_features.cpp """) # Setting paths to find libTamaas -env_pybind.AppendUnique(LIBPATH=[Dir(join('#${build_dir}', 'src'))]) +env_pybind.AppendUnique(LIBPATH=['../src']) # Link against a static libTamaas if env_pybind['build_static_lib']: env_pybind.PrependUnique(LIBS=['Tamaas']) # keep other libs for link env_pybind['RPATH'] = "" # no need for rpath w/ static lib # Link against a dynamic libTamaas else: env_pybind.AppendUnique(RPATH=[ "'$$$$ORIGIN/../../src'", # path to lib in build_dir "'$$$$ORIGIN/../../..'", # path to lib in install prefix ]) env_pybind['LIBS'] = ['Tamaas'] # discard other libs for link # Building the pybind library tamaas_wrap = env_pybind.Pybind11Module( target='tamaas/_tamaas', source=pybind_sources, ) # 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() # 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_env.Command(copy_env.File(f, 'tamaas'), + copy_env.File(f, '#python/tamaas'), 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( 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) install_env = main_env.Clone() conf = Configure(install_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 -U $PYOPTIONS .' install_env['PYDEVELOPCOM'] = \ '${py_exec} -m pip install $PYOPTIONS -e .[all]' 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'), + '.python_install_phony', targets, install_env['PYINSTALLCOM'], PYOPTIONS='--prefix ${prefix}', chdir=install_env['PYDIR']) python_install_dev = install_env.Command( - join('$prefix', 'dummy_target_local'), + '.python_install_local_phony', targets, install_env['PYDEVELOPCOM'], PYOPTIONS='--user', chdir=install_env['PYDIR']) # Defining aliases 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 86fb5b8..c41a831 100644 --- a/site_scons/detect.py +++ b/site_scons/detect.py @@ -1,264 +1,262 @@ # -*- coding: utf-8 -*- # @file # @section LICENSE # # Copyright (©) 2016-2021 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 -from SCons.Script import Configure +from SCons.Script import Configure, Dir +from SCons.Errors import StopError # ------------------------------------------------------------------------------ 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): + root = Dir(env[module_var]) + if not root.exists() or not root.isdir(): 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): + path = Dir(ext, root) + if not path.exists() or not path.isdir(): raise RuntimeError("{} does not contain '{}' directory".format( module_var, ext)) return [path] # ------------------------------------------------------------------------------ def FindFFTW(env, components=None, precision='double', module_var='FFTW_ROOT'): """Find FFTW3 and set relevant environment variables""" if not env.get('should_configure', True): return if components is None: components = [] fftw_vars = {} fftw_vars['CPPPATH'] = _get_path(env, 'include', module_var) fftw_vars['LIBPATH'] = _get_path(env, 'lib', module_var) try: fftw_vars['LIBPATH'] += _get_path(env, 'lib64', module_var) except RuntimeError: pass fftw_vars['RPATH'] = fftw_vars['LIBPATH'] if 'threads' in components: fftw_vars['LIBS'] = ['pthread'] # Setting up FFTW wishes = ['main'] + components fftw_name = "fftw3{}" # Components lib_names = {'main': fftw_name, 'threads': fftw_name + '_threads', 'omp': fftw_name + '_omp', 'mpi': fftw_name + '_mpi'} # Checking list of wishes try: libs = [lib_names[i].format("") for i in wishes] except KeyError: - raise SCons.Errors.StopError( + raise StopError( 'Incompatible FFTW wishlist {0}'.format(wishes)) # Add long precision libraries if precision == 'long double': libs += [lib_names[i].format("l") for i in wishes] conf_env = env.Clone(**fftw_vars) conf = Configure(conf_env) for lib in libs: inc_names = ['fftw3.h'] if 'mpi' in lib: inc_names.append('fftw3-mpi.h') if not conf.CheckLibWithHeader(lib, inc_names, 'c++'): - raise SCons.Errors.StopError( + raise StopError( ('Failed to find library {0} or ' 'headers {1}. Check the build options "fftw_threads"' 'and "FFTW_ROOT".').format(lib, str(inc_names))) conf_env = conf.Finish() fftw_vars['LIBS'] = fftw_vars.get('LIBS', []) + libs # 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( + raise 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 ('cpp', 'omp', 'cuda', 'tbb'): - raise SCons.Errors.StopError( + raise StopError( 'Unknown thrust backend "{}"'.format(backend)) thrust_vars = {} try: thrust_vars['CPPPATH'] = _get_path(env, 'include', module_var) except RuntimeError: thrust_vars['CPPPATH'] = _get_path(env, '', module_var) 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("TAMAAS_USE_CUDA") conf_env = env.Clone(**thrust_vars) conf = Configure(conf_env) if not conf.CheckCXXHeader('thrust/version.h'): - raise SCons.Errors.StopError( + raise 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( + raise 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( + raise 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( + raise 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_tools/nvcc.py b/site_scons/site_tools/nvcc.py index e9316fd..e3b02b7 100644 --- a/site_scons/site_tools/nvcc.py +++ b/site_scons/site_tools/nvcc.py @@ -1,223 +1,223 @@ # -*- coding: utf-8 -*- # @file # @section LICENSE # # Copyright (©) 2016-2021 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 . """ SCons tool to setup compilation environment for NVidia's NVCC Based on version https://github.com/thrust/thrust/blob/master/site_scons/site_tools/nvcc.py """ from __future__ import print_function +import platform import SCons from SCons.Errors import StopError +from SCons.Script import Dir -import platform -from os.path import join known_components = { 'cufft': 'cufft.h', 'cufftw': 'cufftw.h' } def _find_cuda_paths(env): """Finding cuda paths""" if 'CUDA_TOOLKIT_PATH' not in env: raise StopError("CUDA_TOOLKIT_PATH variable not found in environment") cuda_path = env['CUDA_TOOLKIT_PATH'] - inc_path = join(cuda_path, 'include') - lib_path = join(cuda_path, 'lib') - bin_path = join(cuda_path, 'bin') + inc_path = Dir('include', cuda_path) + lib_path = Dir('lib', cuda_path) + bin_path = Dir('bin', cuda_path) if platform.machine()[-2:] == '64': lib_path += '64' # add nvcc's location to PATH env.PrependENVPath('PATH', bin_path) # add the location of the cudart shared library to LD_LIBRARY_PATH as well # this allows us to execute CUDA programs during the build env.PrependENVPath('LD_LIBRARY_PATH', lib_path) # add to include paths env.AppendUnique(CPPPATH=[inc_path]) # add to library paths env.AppendUnique(LIBPATH=[lib_path]) def _configure_cuda_components(env): """Finding required components in cuda""" if env.GetOption('clean'): return if 'CUDA_COMPONENTS' in env: for component in env['CUDA_COMPONENTS']: if component not in known_components: raise StopError("Unknown cuda component '{}'".format(component)) conf = SCons.Script.Configure(env) if not conf.CheckLibWithHeader(component, known_components[component], 'c++'): raise StopError("Failed to find library {} or header {}".format( component, known_components[component])) env = conf.Finish() def _define_compile_cuda(env): """Define the compile string for files that need nvcc""" env['NVCC'] = env.Detect('nvcc') # Setting compilation command env['NVCCCOM'] = \ '$NVCC $CUDA_ARCH_FLAG -o $TARGET -c $NVCC_CXXFLAGS $NVCC_CCFLAGS ' \ + '$_CCCOMCOM -x cu $SOURCES' env['SHNVCCCOM'] = \ '$NVCC $CUDA_ARCH_FLAG -o $TARGET -dc -Xcompiler -fPIC ' \ + '$NVCC_CXXFLAGS $NVCC_CCFLAGS $_CCCOMCOM -x cu $SOURCES' # Constructing proper cc flags env['_NVCC_BARE_CCFLAGS'] = \ '${_concat("", CCFLAGS, "", __env__, _NVCC_BARE_FILTER)}' env['_NVCC_PASSED_CCFLAGS'] = \ '${_concat("-Xcompiler ", CCFLAGS, "", __env__, ' \ + '_NVCC_COMPILER_PASSED_FILTER)}' # Constructing proper cxx flags env['_NVCC_BARE_CXXFLAGS'] = \ '${_concat("", CXXFLAGS, "", __env__, _NVCC_BARE_FILTER)}' env['_NVCC_PASSED_CXXFLAGS'] = \ '${_concat("-Xcompiler ", CXXFLAGS, "", __env__, ' \ + '_NVCC_COMPILER_PASSED_FILTER)}' # Putting all together env['NVCC_CCFLAGS'] = '$_NVCC_BARE_CCFLAGS $_NVCC_PASSED_CCFLAGS' env['NVCC_CXXFLAGS'] = '$_NVCC_BARE_CXXFLAGS $_NVCC_PASSED_CXXFLAGS' def _define_link_cuda(env): """Define the link string""" # Fixing rpaths env['RPATHPREFIX'] = '-rpath=' env['__RPATH'] = '${_concat("-Xlinker ", _RPATH, "", __env__)}' env['LINK'] = env['NVCC'] env['SHLINK'] = env['NVCC'] # Replacing old link command strings env['LINKCOM'] = \ '$LINK $CUDA_ARCH_FLAG -o $TARGET $NVCC_LINKFLAGS $__RPATH ' \ + '$SOURCES $_LIBDIRFLAGS $_LIBFLAGS' env['SHLINKCOM'] = \ '$SHLINK -shared $CUDA_ARCH_FLAG -o $TARGET $NVCC_SHLINKFLAGS ' \ + '$__SHLIBVERSIONFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' # Constructing proper static linker flags env['_NVCC_BARE_LINKFLAGS'] = \ '${_concat("", LINKFLAGS, "", __env__, _NVCC_BARE_FILTER)}' env['_NVCC_COMPILER_LINKFLAGS'] = \ '${_concat("-Xcompiler ", LINKFLAGS, "", __env__, ' \ + '_NVCC_COMPILER_FILTER)}' env['_NVCC_PASSED_LINKFLAGS'] = \ '${_concat("-Xlinker ", LINKFLAGS, "", __env__, ' \ + '_NVCC_LINK_PASSED_FILTER)}' # Constructing proper shared linker flags env['_NVCC_BARE_SHLINKFLAGS'] = \ '${_concat("", SHLINKFLAGS, "", __env__, _NVCC_BARE_FILTER)}' env['_NVCC_COMPILER_SHLINKFLAGS'] = \ '${_concat("-Xcompiler ", LINKFLAGS, "", __env__, ' \ + '_NVCC_COMPILER_FILTER)}' env['_NVCC_PASSED_SHLINKFLAGS'] = \ '${_concat("-Xlinker ", SHLINKFLAGS, "", __env__, ' \ + '_NVCC_LINK_PASSED_FILTER)}' # Putting all together env['NVCC_LINKFLAGS'] = \ '$_NVCC_BARE_LINKFLAGS $_NVCC_COMPILER_LINKFLAGS ' \ + '$_NVCC_PASSED_LINKFLAGS' env['NVCC_SHLINKFLAGS'] = env['NVCC_LINKFLAGS'] env['NVCC_SHLINKFLAGS'] += \ ' $_NVCC_BARE_SHLINKFLAGS $_NVCC_COMPILER_SHLINKFLAGS ' \ + '$_NVCC_PASSED_SHLINKFLAGS' def _define_commands(env): """Defining the command strings""" # Flags allowed by nvcc bare_flags = """-std=c++11 -O0 -O1 -O2 -O3 -g -pg -G -w""".split() # Experimental flags bare_flags += """-expt-extended-lambda -expt-relaxed-constexpr""".split() # Flags that must be passed to compiler at link time compiler_flags = """-fopenmp -qopenmp""".split() # Pass flags bare to nvcc env['_NVCC_BARE_FILTER'] = lambda flags: list(filter( lambda flag: flag in bare_flags, flags)) # Must prepend -Xcompiler, even at link time env['_NVCC_COMPILER_FILTER'] = lambda flags: list(filter( lambda flag: flag in compiler_flags, flags)) # Prepend -Xcompiler env['_NVCC_COMPILER_PASSED_FILTER'] = lambda flags: list(filter( lambda flag: flag not in set(bare_flags), flags)) # Prepend -Xlinker env['_NVCC_LINKED_PASSED_FILTER'] = lambda flags: list(filter( lambda flag: flag not in set(bare_flags) | set(compiler_flags), flags)) _define_compile_cuda(env) _define_link_cuda(env) def _add_actions_cuda(env): """ Adding actions to .cu files to compile with nvcc. Other files are not affected. """ nvcc_action = SCons.Action.Action('$NVCCCOM', '$NVCCCOMSTR') shnvcc_action = SCons.Action.Action('$SHNVCCCOM', '$NVCCCOMSTR') static, shared = SCons.Tool.createObjBuilders(env) # Compiling with nvcc action added to detected .cu files static.add_action('.cu', nvcc_action) shared.add_action('.cu', shnvcc_action) # Emitter to qualify correctly object code type static.add_emitter('.cu', SCons.Defaults.StaticObjectEmitter) shared.add_emitter('.cu', SCons.Defaults.SharedObjectEmitter) env['CXXCOM'] = '$NVCCCOM' env['SHCXXCOM'] = '$SHNVCCCOM' # Scanner for dependency calculations SCons.Tool.SourceFileScanner.add_scanner('.cu', SCons.Scanner.C.CScanner()) def generate(env): """Setup environment for compiling .cu files with nvcc""" _find_cuda_paths(env) _add_actions_cuda(env) _define_commands(env) _configure_cuda_components(env) def exists(env): return env.Detect('nvcc') diff --git a/site_scons/site_tools/tamaas.py b/site_scons/site_tools/tamaas.py index 822a716..2ba3ab3 100644 --- a/site_scons/site_tools/tamaas.py +++ b/site_scons/site_tools/tamaas.py @@ -1,63 +1,61 @@ # -*- coding: utf-8 -*- # @file # @section LICENSE # # Copyright (©) 2016-2021 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 os from SCons.Errors import StopError -from SCons.Script import Configure +from SCons.Script import Configure, Dir def generate(env): """Sets correct environement variables for Tamaas. - 'TAMAAS_PATH' is the path to the build directory of Tamaas""" if env.GetOption("clean"): return if 'TAMAAS_PATH' not in env: raise StopError("Please set the 'TAMAAS_PATH' variable in your " + "scons environment") - tm_path = env['TAMAAS_PATH'] - include_dir = tm_path + '/src' + tm_path = Dir(env['TAMAAS_PATH']) + include_dir = Dir('src', tm_path) subdirs = "core model solvers bem surface percolation".split(" ") - include_dirs = [os.path.abspath(os.path.join(include_dir, x)) - for x in subdirs] + include_dirs = [Dir(x, include_dir).abspath for x in subdirs] lib_dir = include_dir env.AppendUnique(CPPPATH=include_dirs) - env.AppendUnique(LIBPATH=[os.path.abspath(lib_dir)]) - env.AppendUnique(RPATH=[os.path.abspath(lib_dir)]) + env.AppendUnique(LIBPATH=[lib_dir.abspath]) + env.AppendUnique(RPATH=[lib_dir.abspath]) env.AppendUnique(CXXFLAGS=["-std=c++14"]) env.AppendUnique( CPPDEFINES=["THRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_OMP"]) env.AppendUnique(LIBS=['fftw3', 'fftw3_omp']) conf = Configure(env) if not conf.CheckLibWithHeader("Tamaas", "tamaas.hh", language="CXX"): raise StopError( - "Tamaas not found in {}".format(os.path.abspath(tm_path))) - env['TAMAAS_PATH'] = os.path.abspath(tm_path) + "Tamaas not found in {}".format(tm_path.abspath)) + env['TAMAAS_PATH'] = tm_path.abspath env = conf.Finish() def exists(env): if 'TAMAAS_PATH' in env: return True return False diff --git a/src/SConscript b/src/SConscript index fc34057..0488e9d 100644 --- a/src/SConscript +++ b/src/SConscript @@ -1,158 +1,158 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # @file # @section LICENSE # # Copyright (©) 2016-2021 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 os from SCons.Script import Import, Export -def prepend(path, list): - return [os.path.join(path, x) for x in list] +def prepend(env, path, list): + return [env.File(x, path) for x in list] Import('main_env') env = main_env.Clone() +env.AddMethod(prepend, 'PrependDir') # Core core_list = """ fft_engine.cpp fftw_engine.cpp grid.cpp grid_hermitian.cpp statistics.cpp tamaas.cpp loop.cpp computes.cpp logger.cpp """.split() -core_list = prepend('core', core_list) +core_list = env.PrependDir('core', core_list) info_file = env.Substfile('tamaas_info.cpp', 'tamaas_info.cpp.in') core_list.append(info_file) # Lib roughcontact generator_list = """ surface_generator.cpp surface_generator_filter.cpp surface_generator_random_phase.cpp isopowerlaw.cpp regularized_powerlaw.cpp """.split() -generator_list = prepend('surface', generator_list) +generator_list = env.PrependDir('surface', generator_list) # Lib PERCOLATION percolation_list = """ flood_fill.cpp """.split() -percolation_list = prepend('percolation', percolation_list) +percolation_list = env.PrependDir('percolation', percolation_list) # Model model_list = """ model.cpp model_factory.cpp model_type.cpp model_template.cpp integral_operator.cpp be_engine.cpp westergaard.cpp elastic_functional.cpp meta_functional.cpp adhesion_functional.cpp volume_potential.cpp kelvin.cpp mindlin.cpp boussinesq.cpp hooke.cpp elasto_plastic/isotropic_hardening.cpp elasto_plastic/residual.cpp integration/element.cpp """.split() -model_list = prepend('model', model_list) +model_list = env.PrependDir('model', model_list) # Solvers solvers_list = """ contact_solver.cpp polonsky_keer_rey.cpp kato_saturated.cpp kato.cpp beck_teboulle.cpp condat.cpp polonsky_keer_tan.cpp ep_solver.cpp dfsane_solver.cpp epic.cpp """.split() -solvers_list = prepend('solvers', solvers_list) +solvers_list = env.PrependDir('solvers', solvers_list) # GPU API gpu_list = """ fftransform_cufft.cpp """.split() -gpu_list = prepend('gpu', gpu_list) +gpu_list = env.PrependDir('gpu', gpu_list) # MPI API mpi_list = """ fftw_mpi_engine.cpp mpi_interface.cpp """.split() -mpi_list = prepend('mpi', mpi_list) +mpi_list = env.PrependDir('mpi', mpi_list) # Assembling total list rough_contact_list = \ core_list + generator_list + percolation_list + model_list + solvers_list # Adding GPU if needed if env['backend'] == 'cuda': rough_contact_list += gpu_list # Adding MPI if needed if env['use_mpi']: rough_contact_list += mpi_list # Adding extra warnings for Tamaas base lib env.AppendUnique(CXXFLAGS=['-Wextra']) # Allowing libTamaas.so to find libs in the same directory env.AppendUnique(RPATH=["'$$$$ORIGIN'"]) # Build static library for packaging if env['build_static_lib']: env.AppendUnique(CXXFLAGS='-fPIC') libTamaas = env.StaticLibrary('Tamaas', rough_contact_list) # Build shared library (default) else: libTamaas = env.SharedLibrary('Tamaas', rough_contact_list, SHLIBVERSION=env['version']) # Specify install target to install lib -lib_prefix = os.path.join(env['prefix'], 'lib') +lib_prefix = env.Dir('lib', env['prefix']) lib_install = env.InstallVersionedLib(target=lib_prefix, source=libTamaas) # Defining alias targets main_env.Alias('build-cpp', libTamaas) main_env.Alias('install-lib', lib_install) # Export target for use in python builds Export('libTamaas') diff --git a/tests/SConscript b/tests/SConscript index 5fe3156..d567952 100644 --- a/tests/SConscript +++ b/tests/SConscript @@ -1,215 +1,214 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # @file # @section LICENSE # # Copyright (©) 2016-2021 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, abspath - from SCons.Script import Split, Copy, Dir, Import 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() test_files = Split(""" test_autocorrelation.py 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_tangential.py test_boussinesq_surface.py test_voigt.py test_memory.py test_epic.py fftfreq.py conftest.py pytest.ini """) if env['use_mpi']: test_files += ['test_mpi_routines.py', 'mpi_routines.py'] src_dir = "#/tests" targets = [ - test_env.Command(file, join(src_dir, file), + test_env.Command(file, test_env.File(file, src_dir), Copy("$TARGET", "$SOURCE")) for file in test_files ] test_env = env.Clone(tools=[pybind11]) # Helper module for integral operators test_env['SHLIBPREFIX'] = '' test_env.PrependUnique(LIBS=['Tamaas']) register = test_env.Pybind11Module( target="register_integral_operators", source=["register_integral_operators.cpp"]) 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")]) + gtest_obj = env.Object('gtest.o', + [env.File("src/gtest-all.cc", gtest_path)]) 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")]) + env.Dir('include', gtest_dir).path]) 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(CXXFLAGS=gtest_env['CXXFLAGS']) env.PrependUnique(LIBS=['Tamaas', env.subst('python${py_version}')]) google_test_files = Split(""" test_fft.cpp test_grid.cpp test_loop.cpp test_model.cpp test_static_types.cpp test_integration.cpp """) # Necessary for the tests that use pybind11 calls to python uses = [] if env['build_python']: google_test_files.append('test_fftfreq.cpp') uses = ['TAMAAS_USE_PYTHON'] if env['use_mpi']: google_test_files.append('test_mpi.cpp') defines = env['CPPDEFINES'] if type(defines) is not list: defines = [defines] gtest_main = env.Object("tamaas_gtest_main.o", 'tamaas_gtest_main.cc', CPPDEFINES=defines + uses) 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( - LIBPATH=['.', Dir(join('#${build_dir}', 'src'))], + LIBPATH=['.', '../src'], RPATH=["'$$$$ORIGIN/../src'"] ) test_env.PrependUnique(LIBS=['Tamaas']) # 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']: 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(test_env, custom_tests={'CheckPythonModule': CheckPythonModule}) has_pytest = conf.CheckPythonModule('pytest') conf.Finish() # Define a command to execute tests if has_pytest: pytest_env = test_env.Clone() - pytest_env.PrependENVPath('PYTHONPATH', - abspath(pytest_env.subst('${build_dir}/python'))) - pytest_env.PrependENVPath('LD_LIBRARY_PATH', - abspath(pytest_env.subst('${build_dir}/src'))) + test_env['pythonpath'] = '${build_dir}/python' + test_env['ld_library_path'] = '${build_dir}/src' + pytest_env.PrependENVPath('PYTHONPATH', '${pythonpath.abspath}') + pytest_env.PrependENVPath('LD_LIBRARY_PATH', '${ld_library_path.abspath}') # Setting a moderate thread number pytest_env['ENV']['OMP_NUM_THREADS'] = "1" - test_target = pytest_env.Command('#/.phony_test', targets, + 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')