1""" 2SCons build script for Cantera 3 4Basic usage: 5 'scons help' - print a description of user-specifiable options. 6 7 'scons build' - Compile Cantera and the language interfaces using 8 default options. 9 10 'scons clean' - Delete files created while building Cantera. 11 12 'scons install' - Install Cantera. 13 14 'scons uninstall' - Uninstall Cantera. 15 16 'scons test' - Run all tests which did not previously pass or for which the 17 results may have changed. 18 19 'scons test-reset' - Reset the passing status of all tests. 20 21 'scons test-clean' - Delete files created while running the tests. 22 23 'scons test-help' - List available tests. 24 25 'scons test-NAME' - Run the test named "NAME". 26 27 'scons <command> dump' - Dump the state of the SCons environment to the 28 screen instead of doing <command>, e.g. 29 'scons build dump'. For debugging purposes. 30 31 'scons samples' - Compile the C++ and Fortran samples. 32 33 'scons msi' - Build a Windows installer (.msi) for Cantera. 34 35 'scons sphinx' - Build the Sphinx documentation 36 37 'scons doxygen' - Build the Doxygen documentation 38""" 39 40# This f-string is deliberately here to trigger a SyntaxError when 41# SConstruct is parsed by Python 2. This seems to be the most robust 42# and simplest option that will reliably trigger an error in Python 2 43# and provide actionable feedback for users. 44f""" 45Cantera must be built using Python 3.6 or higher. You can invoke SCons by executing 46 python3 `which scons` 47followed by any desired options. 48""" 49 50from pathlib import Path 51import sys 52import os 53import platform 54import subprocess 55import re 56import textwrap 57from os.path import join as pjoin 58from pkg_resources import parse_version 59import SCons 60from buildutils import * 61 62if not COMMAND_LINE_TARGETS: 63 # Print usage help 64 logger.info(__doc__, print_level=False) 65 sys.exit(0) 66 67valid_commands = ("build", "clean", "install", "uninstall", 68 "help", "msi", "samples", "sphinx", "doxygen", "dump") 69 70for command in COMMAND_LINE_TARGETS: 71 if command not in valid_commands and not command.startswith('test'): 72 logger.error("Unrecognized command line target: {!r}", command) 73 sys.exit(1) 74 75if "clean" in COMMAND_LINE_TARGETS: 76 remove_directory("build") 77 remove_directory("stage") 78 remove_directory(".sconf_temp") 79 remove_directory("test/work") 80 remove_file(".sconsign.dblite") 81 remove_file("include/cantera/base/config.h") 82 remove_file("src/pch/system.h.gch") 83 remove_directory("include/cantera/ext") 84 remove_file("interfaces/cython/cantera/_cantera.cpp") 85 remove_file("interfaces/cython/cantera/_cantera.h") 86 remove_file("interfaces/cython/setup.py") 87 remove_file("interfaces/python_minimal/setup.py") 88 remove_file("config.log") 89 remove_directory("doc/sphinx/matlab/examples") 90 remove_file("doc/sphinx/matlab/examples.rst") 91 for name in Path("doc/sphinx/matlab/").glob("**/*.rst"): 92 if name.name != "index.rst": 93 remove_file(name) 94 remove_directory("doc/sphinx/cython/examples") 95 remove_file("doc/sphinx/cython/examples.rst") 96 remove_directory("interfaces/cython/Cantera.egg-info") 97 remove_directory("interfaces/python_minimal/Cantera_minimal.egg-info") 98 for name in Path("interfaces/cython/cantera/data/").iterdir(): 99 if name.is_dir(): 100 remove_directory(name) 101 elif name.name != "__init__.py": 102 remove_file(name) 103 remove_directory("interfaces/cython/cantera/test/data/test_subdir") 104 for name in Path("interfaces/cython/cantera/test/data/").iterdir(): 105 if name.name != "__init__.py": 106 remove_file(name) 107 for name in Path(".").glob("*.msi"): 108 remove_file(name) 109 for name in Path("site_scons").glob("**/*.pyc"): 110 remove_file(name) 111 for name in Path("interfaces/python_minimal/cantera").iterdir(): 112 if name.name != "__init__.py": 113 remove_file(name) 114 remove_file("interfaces/matlab/toolbox/cantera_shared.dll") 115 remove_file("interfaces/matlab/Contents.m") 116 remove_file("interfaces/matlab/ctpath.m") 117 for name in Path("interfaces/matlab/toolbox").glob("ctmethods.*"): 118 remove_file(name) 119 120 print("Done removing output files.") 121 122 if COMMAND_LINE_TARGETS == ["clean"]: 123 # Just exit if there's nothing else to do 124 sys.exit(0) 125 else: 126 Alias("clean", []) 127 128if "test-clean" in COMMAND_LINE_TARGETS: 129 remove_directory("build/test") 130 remove_directory("test/work") 131 remove_directory("build/python_local") 132 133# ****************************************************** 134# *** Set system-dependent defaults for some options *** 135# ****************************************************** 136 137logger.info("SCons is using the following Python interpreter: {}", sys.executable) 138 139opts = Variables('cantera.conf') 140 141windows_compiler_options = [] 142extraEnvArgs = {} 143 144if os.name == 'nt': 145 # On Windows, target the same architecture as the current copy of Python, 146 # unless the user specified another option. 147 if '64 bit' in sys.version: 148 target_arch = 'amd64' 149 else: 150 target_arch = 'x86' 151 152 # Make an educated guess about the right default compiler 153 if which('g++') and not which('cl.exe'): 154 defaultToolchain = 'mingw' 155 else: 156 defaultToolchain = 'msvc' 157 158 windows_compiler_options.extend([ 159 ('msvc_version', 160 """Version of Visual Studio to use. The default is the newest 161 installed version. Specify '12.0' for Visual Studio 2013 or '14.0' 162 for Visual Studio 2015.""", 163 ''), 164 EnumVariable( 165 'target_arch', 166 """Target architecture. The default is the same architecture as the 167 installed version of Python.""", 168 target_arch, ('amd64', 'x86')) 169 ]) 170 opts.AddVariables(*windows_compiler_options) 171 172 pickCompilerEnv = Environment() 173 opts.Update(pickCompilerEnv) 174 175 if pickCompilerEnv['msvc_version']: 176 defaultToolchain = 'msvc' 177 178 windows_compiler_options.append(EnumVariable( 179 'toolchain', 180 """The preferred compiler toolchain.""", 181 defaultToolchain, ('msvc', 'mingw', 'intel'))) 182 opts.AddVariables(windows_compiler_options[-1]) 183 opts.Update(pickCompilerEnv) 184 185 if pickCompilerEnv['toolchain'] == 'msvc': 186 toolchain = ['default'] 187 if pickCompilerEnv['msvc_version']: 188 extraEnvArgs['MSVC_VERSION'] = pickCompilerEnv['msvc_version'] 189 print('INFO: Compiling with MSVC', (pickCompilerEnv['msvc_version'] or 190 pickCompilerEnv['MSVC_VERSION'])) 191 192 elif pickCompilerEnv['toolchain'] == 'mingw': 193 toolchain = ['mingw', 'f90'] 194 extraEnvArgs['F77'] = None 195 # Next line fixes http://scons.tigris.org/issues/show_bug.cgi?id=2683 196 extraEnvArgs['WINDOWS_INSERT_DEF'] = 1 197 198 elif pickCompilerEnv['toolchain'] == 'intel': 199 toolchain = ['intelc'] # note: untested 200 201 extraEnvArgs['TARGET_ARCH'] = pickCompilerEnv['target_arch'] 202 print('INFO: Compiling for architecture:', pickCompilerEnv['target_arch']) 203 print('INFO: Compiling using the following toolchain(s):', repr(toolchain)) 204 205else: 206 toolchain = ['default'] 207 208env = Environment(tools=toolchain+['textfile', 'subst', 'recursiveInstall', 'wix', 'gch'], 209 ENV={'PATH': os.environ['PATH']}, 210 toolchain=toolchain, 211 **extraEnvArgs) 212 213env['OS'] = platform.system() 214env['OS_BITS'] = int(platform.architecture()[0][:2]) 215if 'cygwin' in env['OS'].lower(): 216 env['OS'] = 'Cygwin' # remove Windows version suffix 217 218# Fixes a linker error in Windows 219if os.name == 'nt' and 'TMP' in os.environ: 220 env['ENV']['TMP'] = os.environ['TMP'] 221 222# Fixes issues with Python subprocesses. See http://bugs.python.org/issue13524 223if os.name == 'nt': 224 env['ENV']['SystemRoot'] = os.environ['SystemRoot'] 225 226# Needed for Matlab to source ~/.matlab7rc.sh 227if 'HOME' in os.environ: 228 env['ENV']['HOME'] = os.environ['HOME'] 229 230# Fix an issue with Unicode sneaking into the environment on Windows 231if os.name == 'nt': 232 for key,val in env['ENV'].items(): 233 env['ENV'][key] = str(val) 234 235if 'FRAMEWORKS' not in env: 236 env['FRAMEWORKS'] = [] 237 238add_RegressionTest(env) 239 240class defaults: pass 241 242if os.name == 'posix': 243 defaults.prefix = '/usr/local' 244 defaults.boostIncDir = '' 245 env['INSTALL_MANPAGES'] = True 246elif os.name == 'nt': 247 defaults.prefix = pjoin(os.environ['ProgramFiles'], 'Cantera') 248 defaults.boostIncDir = '' 249 env['INSTALL_MANPAGES'] = False 250else: 251 print("Error: Unrecognized operating system '%s'" % os.name) 252 sys.exit(1) 253 254compiler_options = [ 255 ('CXX', 256 """The C++ compiler to use.""", 257 env['CXX']), 258 ('CC', 259 """The C compiler to use. This is only used to compile CVODE.""", 260 env['CC'])] 261opts.AddVariables(*compiler_options) 262opts.Update(env) 263 264defaults.cxxFlags = '' 265defaults.ccFlags = '' 266defaults.noOptimizeCcFlags = '-O0' 267defaults.optimizeCcFlags = '-O3' 268defaults.debugCcFlags = '-g' 269defaults.noDebugCcFlags = '' 270defaults.debugLinkFlags = '' 271defaults.noDebugLinkFlags = '' 272defaults.warningFlags = '-Wall' 273defaults.buildPch = False 274defaults.sphinx_options = '-W --keep-going' 275env['pch_flags'] = [] 276env['openmp_flag'] = ['-fopenmp'] # used to generate sample build scripts 277 278env['using_apple_clang'] = False 279# Check if this is actually Apple's clang on macOS 280if env['OS'] == 'Darwin': 281 result = subprocess.check_output([env.subst('$CC'), '--version']).decode('utf-8') 282 if 'clang' in result.lower() and ('Xcode' in result or 'Apple' in result): 283 env['using_apple_clang'] = True 284 env['openmp_flag'].insert(0, '-Xpreprocessor') 285 286if 'gcc' in env.subst('$CC') or 'gnu-cc' in env.subst('$CC'): 287 defaults.optimizeCcFlags += ' -Wno-inline' 288 if env['OS'] == 'Cygwin': 289 # See http://stackoverflow.com/questions/18784112 290 defaults.cxxFlags = '-std=gnu++0x' 291 else: 292 defaults.cxxFlags = '-std=c++0x' 293 defaults.buildPch = True 294 env['pch_flags'] = ['-include', 'src/pch/system.h'] 295 296elif env['CC'] == 'cl': # Visual Studio 297 defaults.cxxFlags = ['/EHsc'] 298 defaults.ccFlags = ['/MD', '/nologo', 299 '/D_SCL_SECURE_NO_WARNINGS', '/D_CRT_SECURE_NO_WARNINGS'] 300 defaults.debugCcFlags = '/Zi /Fd${TARGET}.pdb' 301 defaults.noOptimizeCcFlags = '/Od /Ob0' 302 defaults.optimizeCcFlags = '/O2' 303 defaults.debugLinkFlags = '/DEBUG' 304 defaults.warningFlags = '/W3' 305 defaults.buildPch = True 306 env['pch_flags'] = ['/FIpch/system.h'] 307 env['openmp_flag'] = ['/openmp'] 308 309elif 'icc' in env.subst('$CC'): 310 defaults.cxxFlags = '-std=c++0x' 311 defaults.ccFlags = '-vec-report0 -diag-disable 1478' 312 defaults.warningFlags = '-Wcheck' 313 env['openmp_flag'] = ['-openmp'] 314 315elif 'clang' in env.subst('$CC') or 'cc' in env.subst('$CC'): 316 defaults.ccFlags = '-fcolor-diagnostics' 317 defaults.cxxFlags = '-std=c++11' 318 defaults.buildPch = True 319 env['pch_flags'] = ['-include-pch', 'src/pch/system.h.gch'] 320 321else: 322 print("WARNING: Unrecognized C compiler '%s'" % env['CC']) 323 324if env['OS'] in ('Windows', 'Darwin'): 325 defaults.threadFlags = '' 326else: 327 defaults.threadFlags = '-pthread' 328 329# InstallVersionedLib only fully functional in SCons >= 2.4.0 330# SHLIBVERSION fails with MinGW: http://scons.tigris.org/issues/show_bug.cgi?id=3035 331if (env['toolchain'] == 'mingw' 332 or parse_version(SCons.__version__) < parse_version('2.4.0')): 333 defaults.versionedSharedLibrary = False 334else: 335 defaults.versionedSharedLibrary = True 336 337defaults.fsLayout = 'compact' if env['OS'] == 'Windows' else 'standard' 338defaults.env_vars = 'PATH,LD_LIBRARY_PATH,PYTHONPATH' 339 340defaults.python_prefix = '$prefix' if env['OS'] != 'Windows' else '' 341 342# Transform lists into strings to keep cantera.conf clean 343for key,value in defaults.__dict__.items(): 344 if isinstance(value, (list, tuple)): 345 setattr(defaults, key, ' '.join(value)) 346 347# ************************************** 348# *** Read user-configurable options *** 349# ************************************** 350 351config_options = [ 352 PathVariable( 353 'prefix', 354 'Set this to the directory where Cantera should be installed.', 355 defaults.prefix, PathVariable.PathAccept), 356 PathVariable( 357 'libdirname', 358 """Set this to the directory where Cantera libraries should be installed. 359 Some distributions (for example, Fedora/RHEL) use 'lib64' instead of 'lib' on 64-bit systems 360 or could use some other library directory name instead of 'lib' depends 361 on architecture and profile (for example, Gentoo 'libx32' on x32 profile). 362 If user didn't set 'libdirname' configuration variable set it to default value 'lib'""", 363 'lib', PathVariable.PathAccept), 364 EnumVariable( 365 'python_package', 366 """If you plan to work in Python, then you need the ``full`` Cantera Python 367 package. If, on the other hand, you will only use Cantera from some 368 other language (for example, MATLAB or Fortran 90/95) and only need Python 369 to process CTI files, then you only need a ``minimal`` subset of the 370 package and Cython and NumPy are not necessary. The ``none`` option 371 doesn't install any components of the Python interface. The default 372 behavior is to build the full Python module for whichever version of 373 Python is running SCons if the required prerequisites (NumPy and 374 Cython) are installed. Note: ``y`` is a synonym for ``full`` and ``n`` 375 is a synonym for ``none``.""", 376 'default', ('full', 'minimal', 'none', 'n', 'y', 'default')), 377 PathVariable( 378 'python_cmd', 379 """Cantera needs to know where to find the Python interpreter. If 380 PYTHON_CMD is not set, then the configuration process will use the 381 same Python interpreter being used by SCons.""", 382 sys.executable, PathVariable.PathAccept), 383 PathVariable( 384 'python_prefix', 385 """Use this option if you want to install the Cantera Python package to 386 an alternate location. On Unix-like systems, the default is the same 387 as the 'prefix' option. If the 'python_prefix' option is set to 388 the empty string or the 'prefix' option is not set, then the package 389 will be installed to the system default 'site-packages' directory. 390 To install to the current user's 'site-packages' directory, use 391 'python_prefix=USER'.""", 392 defaults.python_prefix, PathVariable.PathAccept), 393 EnumVariable( 394 'matlab_toolbox', 395 """This variable controls whether the MATLAB toolbox will be built. If 396 set to 'y', you will also need to set the value of the 'matlab_path' 397 variable. If set to 'default', the MATLAB toolbox will be built if 398 'matlab_path' is set.""", 399 'default', ('y', 'n', 'default')), 400 PathVariable( 401 'matlab_path', 402 """Path to the MATLAB install directory. This should be the directory 403 containing the 'extern', 'bin', etc. subdirectories. Typical values 404 are: "C:/Program Files/MATLAB/R2011a" on Windows, 405 "/Applications/MATLAB_R2011a.app" on OS X, or 406 "/opt/MATLAB/R2011a" on Linux.""", 407 '', PathVariable.PathAccept), 408 EnumVariable( 409 'f90_interface', 410 """This variable controls whether the Fortran 90/95 interface will be 411 built. If set to 'default', the builder will look for a compatible 412 Fortran compiler in the 'PATH' environment variable, and compile 413 the Fortran 90 interface if one is found.""", 414 'default', ('y', 'n', 'default')), 415 PathVariable( 416 'FORTRAN', 417 """The Fortran (90) compiler. If unspecified, the builder will look for 418 a compatible compiler (pgfortran, gfortran, ifort, g95) in the 'PATH' environment 419 variable. Used only for compiling the Fortran 90 interface.""", 420 '', PathVariable.PathAccept), 421 ('FORTRANFLAGS', 422 'Compilation options for the Fortran (90) compiler.', 423 '-O3'), 424 BoolVariable( 425 'coverage', 426 """Enable collection of code coverage information with gcov. 427 Available only when compiling with gcc.""", 428 False), 429 BoolVariable( 430 'doxygen_docs', 431 """Build HTML documentation for the C++ interface using Doxygen.""", 432 False), 433 BoolVariable( 434 'sphinx_docs', 435 """Build HTML documentation for Cantera using Sphinx.""", 436 False), 437 PathVariable( 438 'sphinx_cmd', 439 """Command to use for building the Sphinx documentation.""", 440 'sphinx-build', PathVariable.PathAccept), 441 ( 442 "sphinx_options", 443 """Options passed to the 'sphinx_cmd' command line. Separate multiple 444 options with spaces, for example, "-W --keep-going".""", 445 defaults.sphinx_options, 446 ), 447 EnumVariable( 448 'system_eigen', 449 """Select whether to use Eigen from a system installation ('y'), from a 450 Git submodule ('n'), or to decide automatically ('default'). If Eigen 451 is not installed directly into a system include directory, for example, it is 452 installed in '/opt/include/eigen3/Eigen', then you will need to add 453 '/opt/include/eigen3' to 'extra_inc_dirs'. 454 """, 455 'default', ('default', 'y', 'n')), 456 EnumVariable( 457 'system_fmt', 458 """Select whether to use the fmt library from a system installation 459 ('y'), from a Git submodule ('n'), or to decide automatically 460 ('default'). If you do not want to use the Git submodule and fmt 461 is not installed directly into system include and library 462 directories, then you will need to add those directories to 463 'extra_inc_dirs' and 'extra_lib_dirs'. This installation of fmt 464 must include the shared version of the library, for example, 465 'libfmt.so'.""", 466 'default', ('default', 'y', 'n')), 467 EnumVariable( 468 'system_yamlcpp', 469 """Select whether to use the yaml-cpp library from a system installation 470 ('y'), from a Git submodule ('n'), or to decide automatically 471 ('default'). If yaml-cpp is not installed directly into system 472 include and library directories, then you will need to add those 473 directories to 'extra_inc_dirs' and 'extra_lib_dirs'.""", 474 'default', ('default', 'y', 'n')), 475 EnumVariable( 476 'system_sundials', 477 """Select whether to use SUNDIALS from a system installation ('y'), from 478 a Git submodule ('n'), or to decide automatically ('default'). 479 Specifying 'sundials_include' or 'sundials_libdir' changes the 480 default to 'y'.""", 481 'default', ('default', 'y', 'n')), 482 PathVariable( 483 'sundials_include', 484 """The directory where the SUNDIALS header files are installed. This 485 should be the directory that contains the "cvodes", "nvector", etc. 486 subdirectories. Not needed if the headers are installed in a 487 standard location, for example, '/usr/include'.""", 488 '', PathVariable.PathAccept), 489 PathVariable( 490 'sundials_libdir', 491 """The directory where the SUNDIALS static libraries are installed. 492 Not needed if the libraries are installed in a standard location, 493 for example, '/usr/lib'.""", 494 '', PathVariable.PathAccept), 495 ( 496 'blas_lapack_libs', 497 """Cantera can use BLAS and LAPACK libraries available on your system if 498 you have optimized versions available (for example, Intel MKL). Otherwise, 499 Cantera will use Eigen for linear algebra support. To use BLAS 500 and LAPACK, set 'blas_lapack_libs' to the the list of libraries 501 that should be passed to the linker, separated by commas, for example, 502 "lapack,blas" or "lapack,f77blas,cblas,atlas". Eigen is required 503 whether or not BLAS/LAPACK are used.""", 504 ''), 505 PathVariable( 506 'blas_lapack_dir', 507 """Directory containing the libraries specified by 'blas_lapack_libs'. Not 508 needed if the libraries are installed in a standard location, for example, 509 ``/usr/lib``.""", 510 '', PathVariable.PathAccept), 511 EnumVariable( 512 'lapack_names', 513 """Set depending on whether the procedure names in the specified 514 libraries are lowercase or uppercase. If you don't know, run 'nm' on 515 the library file (for example, 'nm libblas.a').""", 516 'lower', ('lower','upper')), 517 BoolVariable( 518 'lapack_ftn_trailing_underscore', 519 """Controls whether the LAPACK functions have a trailing underscore 520 in the Fortran libraries.""", 521 True), 522 BoolVariable( 523 'lapack_ftn_string_len_at_end', 524 """Controls whether the LAPACK functions have the string length 525 argument at the end of the argument list ('yes') or after 526 each argument ('no') in the Fortran libraries.""", 527 True), 528 EnumVariable( 529 'googletest', 530 """Select whether to use gtest/gmock from system 531 installation ('system'), from a Git submodule ('submodule'), to decide 532 automatically ('default') or don't look for gtest/gmock ('none') 533 and don't run tests that depend on gtest/gmock.""", 534 'default', ('default', 'system', 'submodule', 'none')), 535 ( 536 'env_vars', 537 """Environment variables to propagate through to SCons. Either the 538 string "all" or a comma separated list of variable names, for example, 539 'LD_LIBRARY_PATH,HOME'.""", 540 defaults.env_vars), 541 BoolVariable( 542 'use_pch', 543 """Use a precompiled-header to speed up compilation""", 544 defaults.buildPch), 545 ( 546 'cxx_flags', 547 """Compiler flags passed to the C++ compiler only. Separate multiple 548 options with spaces, for example, "cxx_flags='-g -Wextra -O3 --std=c++11'" 549 """, 550 defaults.cxxFlags), 551 ( 552 'cc_flags', 553 """Compiler flags passed to both the C and C++ compilers, regardless of optimization level.""", 554 defaults.ccFlags), 555 ( 556 'thread_flags', 557 """Compiler and linker flags for POSIX multithreading support.""", 558 defaults.threadFlags), 559 BoolVariable( 560 'optimize', 561 """Enable extra compiler optimizations specified by the 562 'optimize_flags' variable, instead of the flags specified by the 563 'no_optimize_flags' variable.""", 564 True), 565 ( 566 'optimize_flags', 567 """Additional compiler flags passed to the C/C++ compiler when 'optimize=yes'.""", 568 defaults.optimizeCcFlags), 569 ( 570 'no_optimize_flags', 571 """Additional compiler flags passed to the C/C++ compiler when 'optimize=no'.""", 572 defaults.noOptimizeCcFlags), 573 BoolVariable( 574 'debug', 575 """Enable compiler debugging symbols.""", 576 True), 577 ( 578 'debug_flags', 579 """Additional compiler flags passed to the C/C++ compiler when 'debug=yes'.""", 580 defaults.debugCcFlags), 581 ( 582 'no_debug_flags', 583 """Additional compiler flags passed to the C/C++ compiler when 'debug=no'.""", 584 defaults.noDebugCcFlags), 585 ( 586 'debug_linker_flags', 587 """Additional options passed to the linker when 'debug=yes'.""", 588 defaults.debugLinkFlags), 589 ( 590 'no_debug_linker_flags', 591 """Additional options passed to the linker when 'debug=no'.""", 592 defaults.noDebugLinkFlags), 593 ( 594 'warning_flags', 595 """Additional compiler flags passed to the C/C++ compiler to enable 596 extra warnings. Used only when compiling source code that is part 597 of Cantera (for example, excluding code in the 'ext' directory).""", 598 defaults.warningFlags), 599 ( 600 'extra_inc_dirs', 601 """Additional directories to search for header files, with multiple 602 directories separated by colons (*nix, macOS) or semicolons (Windows)""", 603 ''), 604 ( 605 'extra_lib_dirs', 606 """Additional directories to search for libraries, with multiple 607 directories separated by colons (*nix, macOS) or semicolons (Windows)""", 608 ''), 609 PathVariable( 610 'boost_inc_dir', 611 """Location of the Boost header files. Not needed if the headers are 612 installed in a standard location, for example, '/usr/include'.""", 613 defaults.boostIncDir, PathVariable.PathAccept), 614 PathVariable( 615 'stage_dir', 616 """Directory relative to the Cantera source directory to be 617 used as a staging area for building for example, a Debian 618 package. If specified, 'scons install' will install files 619 to 'stage_dir/prefix/...'.""", 620 '', 621 PathVariable.PathAccept), 622 BoolVariable( 623 'VERBOSE', 624 """Create verbose output about what SCons is doing.""", 625 False), 626 ( 627 'gtest_flags', 628 """Additional options passed to each GTest test suite, for example, 629 '--gtest_filter=*pattern*'. Separate multiple options with spaces.""", 630 ''), 631 BoolVariable( 632 'renamed_shared_libraries', 633 """If this option is turned on, the shared libraries that are created 634 will be renamed to have a '_shared' extension added to their base name. 635 If not, the base names will be the same as the static libraries. 636 In some cases this simplifies subsequent linking environments with 637 static libraries and avoids a bug with using valgrind with 638 the '-static' linking flag.""", 639 True), 640 BoolVariable( 641 'versioned_shared_library', 642 """If enabled, create a versioned shared library, with symlinks to the 643 more generic library name, for example, 'libcantera_shared.so.2.5.0' as the 644 actual library and 'libcantera_shared.so' and 'libcantera_shared.so.2' 645 as symlinks. 646 """, 647 defaults.versionedSharedLibrary), 648 BoolVariable( 649 'use_rpath_linkage', 650 """If enabled, link to all shared libraries using 'rpath', i.e., a fixed 651 run-time search path for dynamic library loading.""", 652 True), 653 EnumVariable( 654 'layout', 655 """The layout of the directory structure. 'standard' installs files to 656 several subdirectories under 'prefix', for example, 'prefix/bin', 657 'prefix/include/cantera', 'prefix/lib' etc. This layout is best used in 658 conjunction with "prefix'='/usr/local'". 'compact' puts all installed 659 files in the subdirectory defined by 'prefix'. This layout is best 660 with a prefix like '/opt/cantera'. 'debian' installs to the stage 661 directory in a layout used for generating Debian packages.""", 662 defaults.fsLayout, ('standard','compact','debian')), 663 BoolVariable( 664 "fast_fail_tests", 665 """If enabled, tests will exit at the first failure.""", 666 False), 667 BoolVariable( 668 "skip_slow_tests", 669 """If enabled, skip a subset of tests that are known to have long runtimes. 670 Skipping these may be desirable when running with options that cause tests 671 to run slowly, like disabling optimization or activating code profiling.""", 672 False), 673 BoolVariable( 674 "show_long_tests", 675 """If enabled, duration of slowest tests will be shown.""", 676 False), 677 BoolVariable( 678 "verbose_tests", 679 """If enabled, verbose test output will be shown.""", 680 False), 681 BoolVariable( 682 "legacy_rate_constants", 683 """If enabled, rate constant calculations include third-body concentrations 684 for three-body reactions, which corresponds to the legacy implementation. 685 For Cantera 2.6, the option remains enabled (no change compared to past 686 behavior). After Cantera 2.6, the default will be to disable this option, 687 and rate constant calculations will be consistent with conventional 688 definitions (see Eq. 9.75 in Kee, Coltrin and Glarborg, 'Chemically Reacting 689 Flow', Wiley Interscience, 2003).""", 690 True), 691] 692 693opts.AddVariables(*config_options) 694opts.Update(env) 695opts.Save('cantera.conf', env) 696 697# Expand ~/ and environment variables used in cantera.conf (variables used on 698# the command line will be expanded by the shell) 699for option in opts.keys(): 700 original = env[option] 701 if isinstance(original, str): 702 modified = os.path.expandvars(os.path.expanduser(env[option])) 703 if original != modified: 704 print('INFO: Expanding {!r} to {!r}'.format(original, modified)) 705 env[option] = modified 706 707if "help" in COMMAND_LINE_TARGETS: 708 help(env, opts) 709 sys.exit(0) 710 711if 'doxygen' in COMMAND_LINE_TARGETS: 712 env['doxygen_docs'] = True 713if 'sphinx' in COMMAND_LINE_TARGETS: 714 env['sphinx_docs'] = True 715 716valid_arguments = (set(opt[0] for opt in windows_compiler_options) | 717 set(opt[0] for opt in compiler_options) | 718 set(opt[0] for opt in config_options)) 719for arg in ARGUMENTS: 720 if arg not in valid_arguments: 721 print('Encountered unexpected command line argument: %r' % arg) 722 #sys.exit(1) 723 724env["cantera_version"] = "2.6.0a3" 725# For use where pre-release tags are not permitted (MSI, sonames) 726env['cantera_pure_version'] = re.match(r'(\d+\.\d+\.\d+)', env['cantera_version']).group(0) 727env['cantera_short_version'] = re.match(r'(\d+\.\d+)', env['cantera_version']).group(0) 728 729#try: 730# env["git_commit"] = get_command_output("git", "rev-parse", "--short", "HEAD") 731#except subprocess.CalledProcessError: 732# env["git_commit"] = "unknown" 733env["git_commit"] = "unknown" 734 735# Print values of all build options: 736print("Configuration variables read from 'cantera.conf' and command line:") 737for line in open('cantera.conf'): 738 print(' ', line.strip()) 739print() 740 741# ******************************************** 742# *** Configure system-specific properties *** 743# ******************************************** 744 745# Copy in external environment variables 746if env['env_vars'] == 'all': 747 env['ENV'].update(os.environ) 748 if 'PYTHONHOME' in env['ENV']: 749 del env['ENV']['PYTHONHOME'] 750elif env['env_vars']: 751 for name in env['env_vars'].split(','): 752 if name in os.environ: 753 if name == 'PATH': 754 env.AppendENVPath('PATH', os.environ['PATH']) 755 else: 756 env['ENV'][name] = os.environ[name] 757 if env['VERBOSE']: 758 print('Propagating environment variable {0}={1}'.format(name, env['ENV'][name])) 759 elif name not in defaults.env_vars.split(','): 760 print('WARNING: failed to propagate environment variable', repr(name)) 761 print(' Edit cantera.conf or the build command line to fix this.') 762 763# @todo: Remove this Warning after Cantera 2.5 764if os.pathsep == ';': 765 for dirs in (env['extra_inc_dirs'], env['extra_lib_dirs']): 766 if re.search(r':\w:', dirs): 767 print('ERROR: Multiple entries in "extra_inc_dirs" and "extra_lib_dirs" ' 768 'should be separated by semicolons (;) on Windows. Use of OS-specific ' 769 'path separator introduced in Cantera 2.5.') 770 sys.exit(1) 771 772env['extra_inc_dirs'] = [d for d in env['extra_inc_dirs'].split(os.pathsep) if d] 773env['extra_lib_dirs'] = [d for d in env['extra_lib_dirs'].split(os.pathsep) if d] 774 775env.Append(CPPPATH=env['extra_inc_dirs'], 776 LIBPATH=env['extra_lib_dirs']) 777 778if env['use_rpath_linkage']: 779 env.Append(RPATH=env['extra_lib_dirs']) 780 781if env['CC'] == 'cl': 782 # embed manifest file 783 env['LINKCOM'] = [env['LINKCOM'], 784 'if exist ${TARGET}.manifest mt.exe -nologo -manifest ${TARGET}.manifest -outputresource:$TARGET;1'] 785 env['SHLINKCOM'] = [env['SHLINKCOM'], 786 'if exist ${TARGET}.manifest mt.exe -nologo -manifest ${TARGET}.manifest -outputresource:$TARGET;2'] 787 env['FORTRAN_LINK'] = 'link' 788else: 789 env['FORTRAN_LINK'] = '$FORTRAN' 790 791if env['boost_inc_dir']: 792 env.Append(CPPPATH=env['boost_inc_dir']) 793 794if env['blas_lapack_dir']: 795 env.Append(LIBPATH=[env['blas_lapack_dir']]) 796 if env['use_rpath_linkage']: 797 env.Append(RPATH=env['blas_lapack_dir']) 798 799if env['system_sundials'] in ('y','default'): 800 if env['sundials_include']: 801 env.Append(CPPPATH=[env['sundials_include']]) 802 env['system_sundials'] = 'y' 803 if env['sundials_libdir']: 804 env.Append(LIBPATH=[env['sundials_libdir']]) 805 env['system_sundials'] = 'y' 806 if env['use_rpath_linkage']: 807 env.Append(RPATH=env['sundials_libdir']) 808 809# BLAS / LAPACK configuration 810if env['blas_lapack_libs'] != '': 811 env['blas_lapack_libs'] = env['blas_lapack_libs'].split(',') 812 env['use_lapack'] = True 813elif env['OS'] == 'Darwin': 814 env['blas_lapack_libs'] = [] 815 env['use_lapack'] = True 816 env.Append(FRAMEWORKS=['Accelerate']) 817else: 818 env['blas_lapack_libs'] = [] 819 env['use_lapack'] = False 820 821# ************************************ 822# *** Compiler Configuration Tests *** 823# ************************************ 824 825def CheckStatement(context, function, includes=""): 826 context.Message('Checking for %s... ' % function) 827 src = """ 828%(include)s 829int main(int argc, char** argv) { 830 %(func)s; 831 return 0; 832} 833""" % {'func':function, 'include':includes} 834 result = context.TryCompile(src, '.cpp') 835 context.Result(result) 836 return result 837 838conf = Configure(env, custom_tests={'CheckStatement': CheckStatement}) 839 840# Set up compiler options before running configuration tests 841env['CXXFLAGS'] = listify(env['cxx_flags']) 842env['CCFLAGS'] = listify(env['cc_flags']) + listify(env['thread_flags']) 843env['LINKFLAGS'] += listify(env['thread_flags']) 844env['CPPDEFINES'] = {} 845 846env['warning_flags'] = listify(env['warning_flags']) 847 848if env['optimize']: 849 env['CCFLAGS'] += listify(env['optimize_flags']) 850 env.Append(CPPDEFINES=['NDEBUG']) 851else: 852 env['CCFLAGS'] += listify(env['no_optimize_flags']) 853 854if env['debug']: 855 env['CCFLAGS'] += listify(env['debug_flags']) 856 env['LINKFLAGS'] += listify(env['debug_linker_flags']) 857else: 858 env['CCFLAGS'] += listify(env['no_debug_flags']) 859 env['LINKFLAGS'] += listify(env['no_debug_linker_flags']) 860 861if env['coverage']: 862 if 'gcc' in env.subst('$CC') or 'clang' in env.subst('$CC'): 863 env.Append(CCFLAGS=['-fprofile-arcs', '-ftest-coverage']) 864 env.Append(LINKFLAGS=['-fprofile-arcs', '-ftest-coverage']) 865 866 else: 867 print('Error: coverage testing is only available with GCC.') 868 exit(0) 869 870if env['toolchain'] == 'mingw': 871 env.Append(LINKFLAGS=['-static-libgcc', '-static-libstdc++']) 872 873def config_error(message): 874 print('ERROR:', message) 875 if env['VERBOSE']: 876 print('*' * 25, 'Contents of config.log:', '*' * 25) 877 print(open('config.log').read()) 878 print('*' * 28, 'End of config.log', '*' * 28) 879 else: 880 print("See 'config.log' for details.") 881 sys.exit(1) 882 883# First, a sanity check: 884if not conf.CheckCXXHeader('cmath', '<>'): 885 config_error('The C++ compiler is not correctly configured.') 886 887 888def get_expression_value(includes, expression, defines=()): 889 s = ['#define ' + d for d in defines] 890 s.extend('#include ' + i for i in includes) 891 s.extend(('#define Q(x) #x', 892 '#define QUOTE(x) Q(x)', 893 '#include <iostream>', 894 '#ifndef SUNDIALS_PACKAGE_VERSION', # name change in Sundials >= 3.0 895 '#define SUNDIALS_PACKAGE_VERSION SUNDIALS_VERSION', 896 '#endif', 897 'int main(int argc, char** argv) {', 898 ' std::cout << %s << std::endl;' % expression, 899 ' return 0;', 900 '}\n')) 901 return '\n'.join(s) 902 903# Check for fmt library and checkout submodule if needed 904# Test for 'ostream.h' to ensure that version >= 3.0.0 is available 905if env['system_fmt'] in ('y', 'default'): 906 if conf.CheckCXXHeader('fmt/ostream.h', '""'): 907 env['system_fmt'] = True 908 print("""INFO: Using system installation of fmt library.""") 909 910 elif env['system_fmt'] == 'y': 911 config_error('Expected system installation of fmt library, but it ' 912 'could not be found.') 913 914if env['system_fmt'] in ('n', 'default'): 915 env['system_fmt'] = False 916 print("""INFO: Using private installation of fmt library.""") 917 if not os.path.exists('ext/fmt/include/fmt/ostream.h'): 918 if not os.path.exists('.git'): 919 config_error('fmt is missing. Install source in ext/fmt.') 920 921 try: 922 code = subprocess.call(['git','submodule','update','--init', 923 '--recursive','ext/fmt']) 924 except Exception: 925 code = -1 926 if code: 927 config_error('fmt submodule checkout failed.\n' 928 'Try manually checking out the submodule with:\n\n' 929 ' git submodule update --init --recursive ext/fmt\n') 930 931fmt_include = '<fmt/format.h>' if env['system_fmt'] else '"../ext/fmt/include/fmt/format.h"' 932fmt_version_source = get_expression_value([fmt_include], 'FMT_VERSION', ['FMT_HEADER_ONLY']) 933retcode, fmt_lib_version = conf.TryRun(fmt_version_source, '.cpp') 934try: 935 fmt_lib_version = divmod(float(fmt_lib_version.strip()), 10000) 936 (fmt_maj, (fmt_min, fmt_pat)) = fmt_lib_version[0], divmod(fmt_lib_version[1], 100) 937 env['FMT_VERSION'] = '{major:.0f}.{minor:.0f}.{patch:.0f}'.format(major=fmt_maj, minor=fmt_min, patch=fmt_pat) 938 print('INFO: Found fmt version {}'.format(env['FMT_VERSION'])) 939except ValueError: 940 env['FMT_VERSION'] = '0.0.0' 941 print('INFO: Could not find version of fmt') 942 943# Check for yaml-cpp library and checkout submodule if needed 944if env['system_yamlcpp'] in ('y', 'default'): 945 if conf.CheckCXXHeader('yaml-cpp/yaml.h', '""'): 946 env['system_yamlcpp'] = True 947 print("""INFO: Using system installation of yaml-cpp library.""") 948 949 elif env['system_yamlcpp'] == 'y': 950 config_error('Expected system installation of yaml-cpp library, but it ' 951 'could not be found.') 952 953if env['system_yamlcpp'] in ('n', 'default'): 954 env['system_yamlcpp'] = False 955 print("""INFO: Using private installation of yaml-cpp library.""") 956 if not os.path.exists('ext/yaml-cpp/include/yaml-cpp/yaml.h'): 957 if not os.path.exists('.git'): 958 config_error('yaml-cpp is missing. Install source in ext/yaml-cpp.') 959 960 try: 961 code = subprocess.call(['git', 'submodule', 'update', '--init', 962 '--recursive', 'ext/yaml-cpp']) 963 except Exception: 964 code = -1 965 if code: 966 config_error('yaml-cpp submodule checkout failed.\n' 967 'Try manually checking out the submodule with:\n\n' 968 ' git submodule update --init --recursive ext/yaml-cpp\n') 969 970# Check for googletest and checkout submodule if needed 971if env['googletest'] in ('system', 'default'): 972 has_gtest = conf.CheckCXXHeader('gtest/gtest.h', '""') 973 has_gmock = conf.CheckCXXHeader('gmock/gmock.h', '""') 974 if has_gtest and has_gmock: 975 env['googletest'] = 'system' 976 print("""INFO: Using system installation of Googletest""") 977 elif env['googletest'] == 'system': 978 config_error('Expected system installation of Googletest-1.8.0, but it ' 979 'could not be found.') 980 981if env['googletest'] in ('submodule', 'default'): 982 env['googletest'] = 'submodule' 983 has_gtest = os.path.exists('ext/googletest/googletest/include/gtest/gtest.h') 984 has_gmock = os.path.exists('ext/googletest/googlemock/include/gmock/gmock.h') 985 if not (has_gtest and has_gmock): 986 if not os.path.exists('.git'): 987 config_error('Googletest is missing. Install source in ext/googletest.') 988 989 try: 990 code = subprocess.call(['git','submodule','update','--init', 991 '--recursive','ext/googletest']) 992 except Exception: 993 code = -1 994 if code: 995 config_error('Googletest not found and submodule checkout failed.\n' 996 'Try manually checking out the submodule with:\n\n' 997 ' git submodule update --init --recursive ext/googletest\n') 998 print("""INFO: Using Googletest from Git submodule""") 999 1000if env['googletest'] == 'none': 1001 print("""INFO: Not using Googletest -- unable to run complete test suite""") 1002 1003# Check for Eigen and checkout submodule if needed 1004if env['system_eigen'] in ('y', 'default'): 1005 if conf.CheckCXXHeader('Eigen/Dense', '<>'): 1006 env['system_eigen'] = True 1007 print("""INFO: Using system installation of Eigen.""") 1008 elif env['system_eigen'] == 'y': 1009 config_error('Expected system installation of Eigen, but it ' 1010 'could not be found.') 1011 1012if env['system_eigen'] in ('n', 'default'): 1013 env['system_eigen'] = False 1014 print("""INFO: Using private installation of Eigen.""") 1015 if not os.path.exists('ext/eigen/Eigen/Dense'): 1016 if not os.path.exists('.git'): 1017 config_error('Eigen is missing. Install Eigen in ext/eigen.') 1018 1019 try: 1020 code = subprocess.call(['git','submodule','update','--init', 1021 '--recursive','ext/eigen']) 1022 except Exception: 1023 code = -1 1024 if code: 1025 config_error('Eigen not found and submodule checkout failed.\n' 1026 'Try manually checking out the submodule with:\n\n' 1027 ' git submodule update --init --recursive ext/eigen\n') 1028 1029eigen_include = '<Eigen/Core>' if env['system_eigen'] else '"../ext/eigen/Eigen/Core"' 1030eigen_versions = 'QUOTE(EIGEN_WORLD_VERSION) "." QUOTE(EIGEN_MAJOR_VERSION) "." QUOTE(EIGEN_MINOR_VERSION)' 1031eigen_version_source = get_expression_value([eigen_include], eigen_versions) 1032retcode, eigen_lib_version = conf.TryRun(eigen_version_source, '.cpp') 1033env['EIGEN_LIB_VERSION'] = eigen_lib_version.strip() 1034print('INFO: Found Eigen version {}'.format(env['EIGEN_LIB_VERSION'])) 1035 1036# Determine which standard library to link to when using Fortran to 1037# compile code that links to Cantera 1038if conf.CheckDeclaration('__GLIBCXX__', '#include <iostream>', 'C++'): 1039 env['cxx_stdlib'] = ['stdc++'] 1040elif conf.CheckDeclaration('_LIBCPP_VERSION', '#include <iostream>', 'C++'): 1041 env['cxx_stdlib'] = ['c++'] 1042else: 1043 env['cxx_stdlib'] = [] 1044 1045env['HAS_CLANG'] = conf.CheckDeclaration('__clang__', '', 'C++') 1046env['HAS_OPENMP'] = conf.CheckLibWithHeader("omp", "omp.h", language="C++") 1047 1048boost_version_source = get_expression_value(['<boost/version.hpp>'], 'BOOST_LIB_VERSION') 1049retcode, boost_lib_version = conf.TryRun(boost_version_source, '.cpp') 1050env['BOOST_LIB_VERSION'] = '.'.join(boost_lib_version.strip().split('_')) 1051if not env['BOOST_LIB_VERSION']: 1052 config_error("Boost could not be found. Install Boost headers or set" 1053 " 'boost_inc_dir' to point to the boost headers.") 1054else: 1055 print('INFO: Found Boost version {0}'.format(env['BOOST_LIB_VERSION'])) 1056# demangle is availble in Boost 1.55 or newer 1057env['has_demangle'] = conf.CheckDeclaration("boost::core::demangle", 1058 '#include <boost/core/demangle.hpp>', 'C++') 1059 1060import SCons.Conftest, SCons.SConf 1061context = SCons.SConf.CheckContext(conf) 1062 1063# Check initially for Sundials<=3.2 and then for Sundials>=4.0 1064for cvode_call in ['CVodeCreate(CV_BDF, CV_NEWTON);','CVodeCreate(CV_BDF);']: 1065 ret = SCons.Conftest.CheckLib(context, 1066 ['sundials_cvodes'], 1067 header='#include "cvodes/cvodes.h"', 1068 language='C++', 1069 call=cvode_call, 1070 autoadd=False, 1071 extra_libs=env['blas_lapack_libs']) 1072 # CheckLib returns False to indicate success 1073 if not ret: 1074 if env['system_sundials'] == 'default': 1075 env['system_sundials'] = 'y' 1076 break 1077 1078# Execute if the cycle ends without 'break' 1079else: 1080 if env['system_sundials'] == 'default': 1081 env['system_sundials'] = 'n' 1082 elif env['system_sundials'] == 'y': 1083 config_error('Expected system installation of Sundials, but it could ' 1084 'not be found.') 1085 1086# Checkout Sundials submodule if needed 1087if (env['system_sundials'] == 'n' and 1088 not os.path.exists('ext/sundials/include/cvodes/cvodes.h')): 1089 if not os.path.exists('.git'): 1090 config_error('Sundials is missing. Install source in ext/sundials.') 1091 1092 try: 1093 code = subprocess.call(['git','submodule','update','--init', 1094 '--recursive','ext/sundials']) 1095 except Exception: 1096 code = -1 1097 if code: 1098 config_error('Sundials not found and submodule checkout failed.\n' 1099 'Try manually checking out the submodule with:\n\n' 1100 ' git submodule update --init --recursive ext/sundials\n') 1101 1102 1103env['NEED_LIBM'] = not conf.CheckLibWithHeader(None, 'math.h', 'C', 1104 'double x; log(x);', False) 1105env['LIBM'] = ['m'] if env['NEED_LIBM'] else [] 1106 1107if env['system_sundials'] == 'y': 1108 for subdir in ('sundials', 'nvector', 'cvodes', 'ida', 'sunlinsol', 'sunmatrix'): 1109 remove_directory('include/cantera/ext/' + subdir) 1110 1111 # Determine Sundials version 1112 sundials_version_source = get_expression_value(['"sundials/sundials_config.h"'], 1113 'QUOTE(SUNDIALS_PACKAGE_VERSION)') 1114 retcode, sundials_version = conf.TryRun(sundials_version_source, '.cpp') 1115 if retcode == 0: 1116 config_error("Failed to determine Sundials version.") 1117 sundials_version = sundials_version.strip(' "\n') 1118 1119 # Ignore the minor version, e.g. 2.4.x -> 2.4 1120 env['sundials_version'] = '.'.join(sundials_version.split('.')[:2]) 1121 sundials_ver = parse_version(env['sundials_version']) 1122 if sundials_ver < parse_version('2.4') or sundials_ver >= parse_version('6.0'): 1123 print("""ERROR: Sundials version %r is not supported.""" % env['sundials_version']) 1124 sys.exit(1) 1125 elif sundials_ver > parse_version('5.7'): 1126 print("WARNING: Sundials version %r has not been tested." % env['sundials_version']) 1127 1128 print("""INFO: Using system installation of Sundials version %s.""" % sundials_version) 1129 1130 # Determine whether or not Sundials was built with BLAS/LAPACK 1131 if sundials_ver < parse_version('2.6'): 1132 # In Sundials 2.4 / 2.5, SUNDIALS_BLAS_LAPACK is either 0 or 1 1133 sundials_blas_lapack = get_expression_value(['"sundials/sundials_config.h"'], 1134 'SUNDIALS_BLAS_LAPACK') 1135 retcode, has_sundials_lapack = conf.TryRun(sundials_blas_lapack, '.cpp') 1136 if retcode == 0: 1137 config_error("Failed to determine Sundials BLAS/LAPACK.") 1138 env['has_sundials_lapack'] = int(has_sundials_lapack.strip()) 1139 elif sundials_ver < parse_version('5.5'): 1140 # In Sundials 2.6-5.5, SUNDIALS_BLAS_LAPACK is either defined or undefined 1141 env['has_sundials_lapack'] = conf.CheckDeclaration('SUNDIALS_BLAS_LAPACK', 1142 '#include "sundials/sundials_config.h"', 'C++') 1143 else: 1144 # In Sundials 5.5 and higher, two defines are included specific to the 1145 # SUNLINSOL packages indicating whether SUNDIALS has been built with LAPACK 1146 lapackband = conf.CheckDeclaration( 1147 "SUNDIALS_SUNLINSOL_LAPACKBAND", 1148 '#include "sundials/sundials_config.h"', 1149 "C++", 1150 ) 1151 lapackdense = conf.CheckDeclaration( 1152 "SUNDIALS_SUNLINSOL_LAPACKDENSE", 1153 '#include "sundials/sundials_config.h"', 1154 "C++", 1155 ) 1156 env["has_sundials_lapack"] = lapackband and lapackdense 1157 1158 # In the case where a user is trying to link Cantera to an external BLAS/LAPACK 1159 # library, but Sundials was configured without this support, print a Warning. 1160 if not env['has_sundials_lapack'] and env['use_lapack']: 1161 print('WARNING: External BLAS/LAPACK has been specified for Cantera ' 1162 'but Sundials was built without this support.') 1163else: # env['system_sundials'] == 'n' 1164 print("""INFO: Using private installation of Sundials version 5.3.""") 1165 env['sundials_version'] = '5.3' 1166 env['has_sundials_lapack'] = int(env['use_lapack']) 1167 1168def set_fortran(pattern, value): 1169 # Set compiler / flags for all Fortran versions to be the same 1170 for version in ("FORTRAN", "F77", "F90", "F95", "F03", "F08"): 1171 env[pattern.format(version)] = value 1172 1173# Try to find a working Fortran compiler: 1174def check_fortran(compiler, expected=False): 1175 hello_world = ''' 1176program main 1177 write(*,'(a)') 'Hello, world!' 1178end program main 1179 ''' 1180 if which(compiler): 1181 set_fortran("{}", compiler) 1182 success, output = conf.TryRun(hello_world, '.f90') 1183 if success and 'Hello, world!' in output: 1184 return True 1185 else: 1186 print("WARNING: Unable to use '%s' to compile the Fortran " 1187 "interface. See config.log for details." % compiler) 1188 return False 1189 elif expected: 1190 print("ERROR: Couldn't find specified Fortran compiler: '%s'" % compiler) 1191 sys.exit(1) 1192 1193 return False 1194 1195set_fortran("{}FLAGS", env["FORTRANFLAGS"]) 1196 1197if env['f90_interface'] in ('y','default'): 1198 foundF90 = False 1199 if env['FORTRAN']: 1200 foundF90 = check_fortran(env['FORTRAN'], True) 1201 1202 for compiler in ('pgfortran', 'gfortran', 'ifort', 'g95'): 1203 if foundF90: 1204 break 1205 foundF90 = check_fortran(compiler) 1206 1207 if foundF90: 1208 print("INFO: Using '%s' to build the Fortran 90 interface" % env['FORTRAN']) 1209 env['f90_interface'] = 'y' 1210 else: 1211 if env['f90_interface'] == 'y': 1212 print("ERROR: Couldn't find a suitable Fortran compiler to build the Fortran 90 interface.") 1213 sys.exit(1) 1214 else: 1215 env['f90_interface'] = 'n' 1216 env['FORTRAN'] = '' 1217 print("INFO: Skipping compilation of the Fortran 90 interface.") 1218 1219if 'pgfortran' in env['FORTRAN']: 1220 env['FORTRANMODDIRPREFIX'] = '-module ' 1221elif 'gfortran' in env['FORTRAN']: 1222 env['FORTRANMODDIRPREFIX'] = '-J' 1223elif 'g95' in env['FORTRAN']: 1224 env['FORTRANMODDIRPREFIX'] = '-fmod=' 1225elif 'ifort' in env['FORTRAN']: 1226 env['FORTRANMODDIRPREFIX'] = '-module ' 1227 1228set_fortran("{}", env["FORTRAN"]) 1229set_fortran("SH{}", env["FORTRAN"]) 1230env['FORTRANMODDIR'] = '${TARGET.dir}' 1231 1232env = conf.Finish() 1233 1234if env['VERBOSE']: 1235 print('-------------------- begin config.log --------------------') 1236 print(open('config.log').read()) 1237 print('--------------------- end config.log ---------------------') 1238 1239env['python_cmd_esc'] = quoted(env['python_cmd']) 1240 1241# Python Package Settings 1242python_min_version = parse_version('3.5') 1243# The string is used to set python_requires in setup.py.in 1244env['py_min_ver_str'] = str(python_min_version) 1245# Note: cython_min_version is redefined below if the Python version is 3.8 or higher 1246cython_min_version = parse_version('0.23') 1247numpy_min_version = parse_version('1.12.0') 1248 1249# We choose ruamel.yaml 0.15.34 as the minimum version 1250# since it is the highest version available in the Ubuntu 1251# 18.04 repositories and seems to work. Older versions such as 1252# 0.13.14 on CentOS7 and 0.10.23 on Ubuntu 16.04 raise an exception 1253# that they are missing the RoundTripRepresenter 1254ruamel_min_version = parse_version('0.15.34') 1255 1256# Check for the minimum ruamel.yaml version, 0.15.34, at install and test 1257# time. The check happens at install and test time because ruamel.yaml is 1258# only required to run the Python interface, not to build it. 1259check_for_ruamel_yaml = any( 1260 target in COMMAND_LINE_TARGETS 1261 for target in ["install", "test", "test-python-convert"] 1262) 1263 1264if env['python_package'] == 'y': 1265 env['python_package'] = 'full' # Allow 'y' as a synonym for 'full' 1266elif env['python_package'] == 'n': 1267 env['python_package'] = 'none' # Allow 'n' as a synonym for 'none' 1268 1269env['install_python_action'] = '' 1270env['python_module_loc'] = '' 1271 1272if env['python_package'] != 'none': 1273 # Test to see if we can import numpy and Cython 1274 script = textwrap.dedent("""\ 1275 import sys 1276 print('{v.major}.{v.minor}'.format(v=sys.version_info)) 1277 err = '' 1278 try: 1279 import numpy 1280 print(numpy.__version__) 1281 except ImportError as np_err: 1282 print('0.0.0') 1283 err += str(np_err) + '\\n' 1284 try: 1285 import Cython 1286 print(Cython.__version__) 1287 except ImportError as cython_err: 1288 print('0.0.0') 1289 err += str(cython_err) + '\\n' 1290 if err: 1291 print(err) 1292 """) 1293 expected_output_lines = 3 1294 if check_for_ruamel_yaml: 1295 ru_script = textwrap.dedent("""\ 1296 try: 1297 import ruamel_yaml as yaml 1298 print(yaml.__version__) 1299 except ImportError as ru_err: 1300 try: 1301 from ruamel import yaml 1302 print(yaml.__version__) 1303 except ImportError as ru_err_2: 1304 print('0.0.0') 1305 err += str(ru_err) + '\\n' 1306 err += str(ru_err_2) + '\\n' 1307 """).splitlines() 1308 s = script.splitlines() 1309 s[-2:-2] = ru_script 1310 script = "\n".join(s) 1311 expected_output_lines = 4 1312 1313 try: 1314 info = get_command_output(env["python_cmd"], "-c", script).splitlines() 1315 except OSError as err: 1316 if env['VERBOSE']: 1317 print('Error checking for Python:') 1318 print(err) 1319 warn_no_python = True 1320 except subprocess.CalledProcessError as err: 1321 if env['VERBOSE']: 1322 print('Error checking for Python:') 1323 print(err, err.output) 1324 warn_no_python = True 1325 else: 1326 warn_no_python = False 1327 python_version = parse_version(info[0]) 1328 numpy_version = parse_version(info[1]) 1329 cython_version = parse_version(info[2]) 1330 if check_for_ruamel_yaml: 1331 ruamel_yaml_version = parse_version(info[3]) 1332 if ruamel_yaml_version == parse_version("0.0.0"): 1333 print("ERROR: ruamel.yaml was not found. {} or newer is " 1334 "required".format(ruamel_min_version)) 1335 sys.exit(1) 1336 elif ruamel_yaml_version < ruamel_min_version: 1337 print("ERROR: ruamel.yaml is an incompatible version: Found " 1338 "{}, but {} or newer is required.".format( 1339 ruamel_yaml_version, ruamel_min_version)) 1340 sys.exit(1) 1341 1342 if warn_no_python: 1343 if env['python_package'] == 'default': 1344 print('WARNING: Not building the Python package because the Python ' 1345 'interpreter {!r} could not be found'.format(env['python_cmd'])) 1346 env['python_package'] = 'none' 1347 else: 1348 print('ERROR: Could not execute the Python interpreter {!r}'.format( 1349 env['python_cmd'])) 1350 sys.exit(1) 1351 elif python_version < python_min_version: 1352 msg = ("{}: Python version is incompatible. Found {} but {} " 1353 "or newer is required") 1354 if env["python_package"] in ("minimal", "full"): 1355 print(msg.format("ERROR", python_version, python_min_version)) 1356 sys.exit(1) 1357 elif env["python_package"] == "default": 1358 print(msg.format("WARNING", python_version, python_min_version)) 1359 env["python_package"] = "none" 1360 elif env['python_package'] == 'minimal': 1361 # If the minimal package was specified, no further checking 1362 # needs to be done 1363 print('INFO: Building the minimal Python package for Python {}'.format(python_version)) 1364 else: 1365 1366 if len(info) > expected_output_lines: 1367 print("WARNING: Unexpected output while checking Python " 1368 "dependency versions:") 1369 print('| ' + '\n| '.join(info[expected_output_lines:])) 1370 1371 warn_no_full_package = False 1372 if python_version >= parse_version("3.8"): 1373 # Reset the minimum Cython version if the Python version is 3.8 or higher 1374 # Due to internal changes in the CPython API, more recent versions of 1375 # Cython are necessary to build for Python 3.8. There is nothing Cantera 1376 # can do about this, the changes in CPython are handled by Cython. This 1377 # version bump is used to produce a more useful/actionable error message 1378 # for users than the compilation errors that result from using 1379 # Cython < 0.29.12. 1380 cython_min_version = parse_version("0.29.12") 1381 1382 if numpy_version == parse_version('0.0.0'): 1383 print("NumPy not found.") 1384 warn_no_full_package = True 1385 elif numpy_version < numpy_min_version: 1386 print("WARNING: NumPy is an incompatible version: " 1387 "Found {0} but {1} or newer is required".format( 1388 numpy_version, numpy_min_version)) 1389 warn_no_full_package = True 1390 else: 1391 print('INFO: Using NumPy version {0}.'.format(numpy_version)) 1392 1393 if cython_version == parse_version('0.0.0'): 1394 print("Cython not found.") 1395 warn_no_full_package = True 1396 elif cython_version < cython_min_version: 1397 print("WARNING: Cython is an incompatible version: " 1398 "Found {0} but {1} or newer is required.".format( 1399 cython_version, cython_min_version)) 1400 warn_no_full_package = True 1401 else: 1402 print('INFO: Using Cython version {0}.'.format(cython_version)) 1403 1404 if warn_no_full_package: 1405 msg = ('{}: Unable to build the full Python package because compatible ' 1406 'versions of Python, Numpy, and Cython could not be found.') 1407 if env['python_package'] == 'default': 1408 print(msg.format("WARNING")) 1409 print('INFO: Building the minimal Python package for Python {}'.format(python_version)) 1410 env['python_package'] = 'minimal' 1411 else: 1412 print(msg.format("ERROR")) 1413 sys.exit(1) 1414 else: 1415 print('INFO: Building the full Python package for Python {0}'.format(python_version)) 1416 env['python_package'] = 'full' 1417 1418# Matlab Toolbox settings 1419if env['matlab_path'] != '' and env['matlab_toolbox'] == 'default': 1420 env['matlab_toolbox'] = 'y' 1421 1422if env['matlab_toolbox'] == 'y': 1423 matPath = env['matlab_path'] 1424 if matPath == '': 1425 print("ERROR: Unable to build the Matlab toolbox because 'matlab_path' has not been set.") 1426 sys.exit(1) 1427 1428 if env['blas_lapack_libs']: 1429 print('ERROR: The Matlab toolbox is incompatible with external BLAS ' 1430 'and LAPACK libraries. Unset blas_lapack_libs (e.g. "scons ' 1431 'build blas_lapack_libs=") in order to build the Matlab ' 1432 'toolbox, or set matlab_toolbox=n to use the specified BLAS/' 1433 'LAPACK libraries and skip building the Matlab toolbox.') 1434 sys.exit(1) 1435 1436 if env['system_sundials'] == 'y': 1437 print('ERROR: The Matlab toolbox is incompatible with external ' 1438 'SUNDIALS libraries. Set system_sundials to no (e.g., "scons build ' 1439 'system_sundials=n") in order to build the Matlab ' 1440 'toolbox, or set matlab_toolbox=n to use the specified ' 1441 'SUNDIALS libraries and skip building the Matlab toolbox.') 1442 sys.exit(1) 1443 1444 if not (os.path.isdir(matPath)): 1445 print("""ERROR: Path set for 'matlab_path' is not correct.""") 1446 print("""ERROR: Path was: '%s'""" % matPath) 1447 sys.exit(1) 1448 1449 1450# ********************************************** 1451# *** Set additional configuration variables *** 1452# ********************************************** 1453 1454# On Debian-based systems, need to special-case installation to 1455# /usr/local because of dist-packages vs site-packages 1456env['debian'] = any(name.endswith('dist-packages') for name in sys.path) 1457 1458# Directories where things will be after actually being installed. These 1459# variables are the ones that are used to populate header files, scripts, etc. 1460env['prefix'] = os.path.normpath(env['prefix']) 1461env['ct_installroot'] = env['prefix'] 1462env['ct_libdir'] = pjoin(env['prefix'], env['libdirname']) 1463env['ct_bindir'] = pjoin(env['prefix'], 'bin') 1464env['ct_incdir'] = pjoin(env['prefix'], 'include', 'cantera') 1465env['ct_incroot'] = pjoin(env['prefix'], 'include') 1466 1467if env['layout'] == 'compact': 1468 env['ct_datadir'] = pjoin(env['prefix'], 'data') 1469 env['ct_sampledir'] = pjoin(env['prefix'], 'samples') 1470 env['ct_mandir'] = pjoin(env['prefix'], 'man1') 1471 env['ct_matlab_dir'] = pjoin(env['prefix'], 'matlab', 'toolbox') 1472else: 1473 env['ct_datadir'] = pjoin(env['prefix'], 'share', 'cantera', 'data') 1474 env['ct_sampledir'] = pjoin(env['prefix'], 'share', 'cantera', 'samples') 1475 env['ct_mandir'] = pjoin(env['prefix'], 'share', 'man', 'man1') 1476 env['ct_matlab_dir'] = pjoin(env['prefix'], env['libdirname'], 1477 'cantera', 'matlab', 'toolbox') 1478 1479# Always set the stage directory before building an MSI installer 1480if 'msi' in COMMAND_LINE_TARGETS: 1481 COMMAND_LINE_TARGETS.append('install') 1482 env['stage_dir'] = 'stage' 1483 env['prefix'] = '.' 1484 env['PYTHON_INSTALLER'] = 'binary' 1485elif env['layout'] == 'debian': 1486 COMMAND_LINE_TARGETS.append('install') 1487 env['stage_dir'] = 'stage/cantera' 1488 env['PYTHON_INSTALLER'] = 'debian' 1489 env['INSTALL_MANPAGES'] = False 1490else: 1491 env['PYTHON_INSTALLER'] = 'direct' 1492 1493 1494addInstallActions = ('install' in COMMAND_LINE_TARGETS or 1495 'uninstall' in COMMAND_LINE_TARGETS) 1496 1497# Directories where things will be staged for package creation. These 1498# variables should always be used by the Install(...) targets 1499if env["stage_dir"]: 1500 stage_prefix = Path(env["prefix"]) 1501 # Strip the root off the prefix if it's absolute 1502 if stage_prefix.is_absolute(): 1503 stage_prefix = Path(*stage_prefix.parts[1:]) 1504 1505 instRoot = Path.cwd().joinpath(env["stage_dir"], stage_prefix) 1506else: 1507 instRoot = env["prefix"] 1508 1509# Prevent setting Cantera installation path to source directory 1510if os.path.abspath(instRoot) == Dir('.').abspath: 1511 print('ERROR: cannot install Cantera into source directory.') 1512 exit(1) 1513 1514if env['layout'] == 'debian': 1515 base = pjoin(os.getcwd(), 'debian') 1516 1517 env['inst_libdir'] = pjoin(base, 'cantera-dev', 'usr', env['libdirname']) 1518 env['inst_incdir'] = pjoin(base, 'cantera-dev', 'usr', 'include', 'cantera') 1519 env['inst_incroot'] = pjoin(base, 'cantera-dev', 'usr' 'include') 1520 1521 env['inst_bindir'] = pjoin(base, 'cantera-common', 'usr', 'bin') 1522 env['inst_datadir'] = pjoin(base, 'cantera-common', 'usr', 'share', 'cantera', 'data') 1523 env['inst_docdir'] = pjoin(base, 'cantera-common', 'usr', 'share', 'cantera', 'doc') 1524 env['inst_sampledir'] = pjoin(base, 'cantera-common', 'usr', 'share', 'cantera', 'samples') 1525 env['inst_mandir'] = pjoin(base, 'cantera-common', 'usr', 'share', 'man', 'man1') 1526 1527 env['inst_matlab_dir'] = pjoin(base, 'cantera-matlab', 'usr', 1528 env['libdirname'], 'cantera', 'matlab', 'toolbox') 1529 1530 env['inst_python_bindir'] = pjoin(base, 'cantera-python', 'usr', 'bin') 1531 env['python_prefix'] = pjoin(base, 'cantera-python3') 1532else: 1533 env['inst_libdir'] = pjoin(instRoot, env['libdirname']) 1534 env['inst_bindir'] = pjoin(instRoot, 'bin') 1535 env['inst_python_bindir'] = pjoin(instRoot, 'bin') 1536 env['inst_incdir'] = pjoin(instRoot, 'include', 'cantera') 1537 env['inst_incroot'] = pjoin(instRoot, 'include') 1538 1539 if env['layout'] == 'compact': 1540 env['inst_matlab_dir'] = pjoin(instRoot, 'matlab', 'toolbox') 1541 env['inst_datadir'] = pjoin(instRoot, 'data') 1542 env['inst_sampledir'] = pjoin(instRoot, 'samples') 1543 env['inst_docdir'] = pjoin(instRoot, 'doc') 1544 env['inst_mandir'] = pjoin(instRoot, 'man1') 1545 else: # env['layout'] == 'standard' 1546 env['inst_matlab_dir'] = pjoin(instRoot, env['libdirname'], 'cantera', 1547 'matlab', 'toolbox') 1548 env['inst_datadir'] = pjoin(instRoot, 'share', 'cantera', 'data') 1549 env['inst_sampledir'] = pjoin(instRoot, 'share', 'cantera', 'samples') 1550 env['inst_docdir'] = pjoin(instRoot, 'share', 'cantera', 'doc') 1551 env['inst_mandir'] = pjoin(instRoot, 'share', 'man', 'man1') 1552 1553# ************************************** 1554# *** Set options needed in config.h *** 1555# ************************************** 1556 1557configh = {} 1558 1559configh['CANTERA_VERSION'] = quoted(env['cantera_version']) 1560configh['CANTERA_SHORT_VERSION'] = quoted(env['cantera_short_version']) 1561 1562# Conditional defines 1563def cdefine(definevar, configvar, comp=True, value=1): 1564 if env.get(configvar) == comp: 1565 configh[definevar] = value 1566 else: 1567 configh[definevar] = None 1568 1569# Need to test all of these to see what platform.system() returns 1570configh['SOLARIS'] = 1 if env['OS'] == 'Solaris' else None 1571configh['DARWIN'] = 1 if env['OS'] == 'Darwin' else None 1572 1573if env['OS'] == 'Solaris' or env['HAS_CLANG']: 1574 configh['NEEDS_GENERIC_TEMPL_STATIC_DECL'] = 1 1575 1576configh['CT_SUNDIALS_VERSION'] = env['sundials_version'].replace('.','') 1577 1578if env.get('has_sundials_lapack') and env['use_lapack']: 1579 configh['CT_SUNDIALS_USE_LAPACK'] = 1 1580else: 1581 configh['CT_SUNDIALS_USE_LAPACK'] = 0 1582 1583if env['legacy_rate_constants']: 1584 configh['CT_LEGACY_RATE_CONSTANTS'] = 1 1585else: 1586 configh['CT_LEGACY_RATE_CONSTANTS'] = 0 1587 1588cdefine('LAPACK_FTN_STRING_LEN_AT_END', 'lapack_ftn_string_len_at_end') 1589cdefine('LAPACK_FTN_TRAILING_UNDERSCORE', 'lapack_ftn_trailing_underscore') 1590cdefine('FTN_TRAILING_UNDERSCORE', 'lapack_ftn_trailing_underscore') 1591cdefine('LAPACK_NAMES_LOWERCASE', 'lapack_names', 'lower') 1592cdefine('CT_USE_LAPACK', 'use_lapack') 1593cdefine('CT_USE_SYSTEM_EIGEN', 'system_eigen') 1594cdefine('CT_USE_SYSTEM_FMT', 'system_fmt') 1595cdefine('CT_USE_SYSTEM_YAMLCPP', 'system_yamlcpp') 1596cdefine('CT_USE_DEMANGLE', 'has_demangle') 1597 1598config_h_build = env.Command('build/src/config.h.build', 1599 'include/cantera/base/config.h.in', 1600 ConfigBuilder(configh)) 1601# This separate copy operation, which SCons will skip if config.h.build is 1602# unmodified, prevents unnecessary rebuilds of the precompiled header 1603config_h = env.Command('include/cantera/base/config.h', 1604 'build/src/config.h.build', 1605 Copy('$TARGET', '$SOURCE')) 1606env.AlwaysBuild(config_h_build) 1607env['config_h_target'] = config_h 1608 1609# ********************* 1610# *** Build Cantera *** 1611# ********************* 1612 1613# Some options to speed up SCons 1614env.SetOption('max_drift', 2) 1615env.SetOption('implicit_cache', True) 1616 1617buildTargets = [] 1618env['build_targets'] = buildTargets 1619libraryTargets = [] # objects that go in the Cantera library 1620installTargets = [] 1621sampleTargets = [] 1622 1623def build(targets): 1624 """ Wrapper to add target to list of build targets """ 1625 buildTargets.extend(targets) 1626 return targets 1627 1628def buildSample(*args, **kwargs): 1629 """ Wrapper to add target to list of samples """ 1630 targets = args[0](*args[1:], **kwargs) 1631 sampleTargets.extend(targets) 1632 return targets 1633 1634def install(*args, **kwargs): 1635 """ Wrapper to add target to list of install targets """ 1636 if not addInstallActions: 1637 return 1638 if len(args) == 2: 1639 inst = env.Install(*args, **kwargs) 1640 else: 1641 inst = args[0](*args[1:], **kwargs) 1642 1643 installTargets.extend(inst) 1644 return inst 1645 1646 1647env.SConsignFile() 1648 1649env.Prepend(CPPPATH=[], 1650 LIBPATH=[Dir('build/lib')]) 1651 1652# preprocess input files (cti -> xml) 1653convertedInputFiles = set() 1654for cti in multi_glob(env, 'data/inputs', 'cti'): 1655 build(env.Command('build/data/%s' % cti.name, cti.path, 1656 Copy('$TARGET', '$SOURCE'))) 1657 outName = os.path.splitext(cti.name)[0] + '.xml' 1658 convertedInputFiles.add(outName) 1659 build(env.Command('build/data/%s' % outName, cti.path, 1660 '$python_cmd_esc interfaces/cython/cantera/ctml_writer.py $SOURCE $TARGET')) 1661 1662# Copy XML input files which are not present as cti: 1663for xml in multi_glob(env, 'data/inputs', 'xml'): 1664 dest = pjoin('build','data',xml.name) 1665 if xml.name not in convertedInputFiles: 1666 build(env.Command(dest, xml.path, Copy('$TARGET', '$SOURCE'))) 1667 1668for yaml in multi_glob(env, "data", "yaml"): 1669 dest = pjoin("build", "data", yaml.name) 1670 build(env.Command(dest, yaml.path, Copy("$TARGET", "$SOURCE"))) 1671for subdir in os.listdir('data'): 1672 if os.path.isdir(pjoin('data', subdir)): 1673 for yaml in multi_glob(env, pjoin("data", subdir), "yaml"): 1674 dest = pjoin("build", "data", subdir, yaml.name) 1675 if not os.path.exists(pjoin("build", "data", subdir)): 1676 os.makedirs(pjoin("build", "data", subdir)) 1677 build(env.Command(dest, yaml.path, Copy("$TARGET", "$SOURCE"))) 1678 1679 1680if addInstallActions: 1681 # Put headers in place 1682 headerBase = 'include/cantera' 1683 install(env.RecursiveInstall, '$inst_incdir', 'include/cantera') 1684 1685 # Data files 1686 install(env.RecursiveInstall, '$inst_datadir', 'build/data') 1687 1688 1689### List of libraries needed to link to Cantera ### 1690linkLibs = ['cantera'] 1691 1692### List of shared libraries needed to link applications to Cantera 1693linkSharedLibs = ['cantera_shared'] 1694 1695if env['system_sundials'] == 'y': 1696 env['sundials_libs'] = ['sundials_cvodes', 'sundials_ida', 'sundials_nvecserial'] 1697 if env['use_lapack'] and sundials_ver >= parse_version('3.0'): 1698 if env.get('has_sundials_lapack'): 1699 env['sundials_libs'].extend(('sundials_sunlinsollapackdense', 1700 'sundials_sunlinsollapackband')) 1701 else: 1702 env['sundials_libs'].extend(('sundials_sunlinsoldense', 1703 'sundials_sunlinsolband')) 1704else: 1705 env['sundials_libs'] = [] 1706 1707linkLibs.extend(env['sundials_libs']) 1708linkSharedLibs.extend(env['sundials_libs']) 1709 1710if env['system_fmt']: 1711 linkLibs.append('fmt') 1712 linkSharedLibs.append('fmt') 1713 1714if env['system_yamlcpp']: 1715 linkLibs.append('yaml-cpp') 1716 linkSharedLibs.append('yaml-cpp') 1717 1718# Add LAPACK and BLAS to the link line 1719if env['blas_lapack_libs']: 1720 linkLibs.extend(env['blas_lapack_libs']) 1721 linkSharedLibs.extend(env['blas_lapack_libs']) 1722 1723# Store the list of needed static link libraries in the environment 1724env['cantera_libs'] = linkLibs 1725env['cantera_shared_libs'] = linkSharedLibs 1726if not env['renamed_shared_libraries']: 1727 env['cantera_shared_libs'] = linkLibs 1728 1729# Add targets from the SConscript files in the various subdirectories 1730Export('env', 'build', 'libraryTargets', 'install', 'buildSample') 1731 1732# ext needs to come before src so that libraryTargets is fully populated 1733VariantDir('build/ext', 'ext', duplicate=0) 1734SConscript('build/ext/SConscript') 1735 1736# Fortran needs to come before src so that libraryTargets is fully populated 1737if env['f90_interface'] == 'y': 1738 VariantDir('build/src/fortran/', 'src/fortran', duplicate=1) 1739 SConscript('build/src/fortran/SConscript') 1740 1741VariantDir('build/src', 'src', duplicate=0) 1742SConscript('build/src/SConscript') 1743 1744if env['python_package'] == 'full': 1745 SConscript('interfaces/cython/SConscript') 1746elif env['python_package'] == 'minimal': 1747 SConscript('interfaces/python_minimal/SConscript') 1748 1749if env['CC'] != 'cl': 1750 VariantDir('build/platform', 'platform/posix', duplicate=0) 1751 SConscript('build/platform/SConscript') 1752 1753if env['matlab_toolbox'] == 'y': 1754 SConscript('build/src/matlab/SConscript') 1755 1756if env['doxygen_docs'] or env['sphinx_docs']: 1757 SConscript('doc/SConscript') 1758 1759# Sample programs (also used from test_problems/SConscript) 1760VariantDir('build/samples', 'samples', duplicate=0) 1761sampledir_excludes = ['\\.o$', '^~$', '\\.in', 'SConscript'] 1762SConscript('build/samples/cxx/SConscript') 1763 1764# Install C++ samples 1765install(env.RecursiveInstall, '$inst_sampledir/cxx', 1766 'samples/cxx', exclude=sampledir_excludes) 1767 1768if env['f90_interface'] == 'y': 1769 SConscript('build/samples/f77/SConscript') 1770 SConscript('build/samples/f90/SConscript') 1771 1772 # install F90 / F77 samples 1773 install(env.RecursiveInstall, '$inst_sampledir/f77', 1774 'samples/f77', sampledir_excludes) 1775 install(env.RecursiveInstall, '$inst_sampledir/f90', 1776 'samples/f90', sampledir_excludes) 1777 1778### Meta-targets ### 1779build_samples = Alias('samples', sampleTargets) 1780 1781def postBuildMessage(target, source, env): 1782 print("*******************************************************") 1783 print("Compilation completed successfully.\n") 1784 print("- To run the test suite, type 'scons test'.") 1785 print("- To list available tests, type 'scons test-help'.") 1786 if env['googletest'] == 'none': 1787 print(" WARNING: You set the 'googletest' to 'none' and all it's tests will be skipped.") 1788 if os.name == 'nt': 1789 print("- To install, type 'scons install'.") 1790 print("- To create a Windows MSI installer, type 'scons msi'.") 1791 else: 1792 print("- To install, type 'scons install'.") 1793 print("*******************************************************") 1794 1795finish_build = env.Command('finish_build', [], postBuildMessage) 1796env.Depends(finish_build, buildTargets) 1797build_cantera = Alias('build', finish_build) 1798 1799Default('build') 1800 1801def postInstallMessage(target, source, env): 1802 # Only needed because Python 2 doesn't support textwrap.indent 1803 def indent(inp_str, indent): 1804 return '\n'.join([indent + spl for spl in inp_str.splitlines()]) 1805 1806 env_dict = env.Dictionary() 1807 install_message = textwrap.dedent(""" 1808 Cantera has been successfully installed. 1809 1810 File locations: 1811 1812 applications {ct_bindir!s} 1813 library files {ct_libdir!s} 1814 C++ headers {ct_incroot!s} 1815 samples {ct_sampledir!s} 1816 data files {ct_datadir!s}""".format(**env_dict)) 1817 1818 if env['sphinx_docs'] or env['doxygen_docs']: 1819 install_message += indent(textwrap.dedent(""" 1820 HTML documentation {inst_docdir!s}""".format(**env_dict)), ' ') 1821 1822 if env['python_package'] == 'full': 1823 env['python_example_loc'] = pjoin(env['python_module_loc'], 'cantera', 'examples') 1824 install_message += indent(textwrap.dedent(""" 1825 Python package (cantera) {python_module_loc!s} 1826 Python samples {python_example_loc!s}""".format(**env_dict)), ' ') 1827 elif env['python_package'] == 'minimal': 1828 install_message += indent(textwrap.dedent(""" 1829 minimal Python module {python_module_loc!s}""".format(**env_dict)), ' ') 1830 1831 if env['matlab_toolbox'] == 'y': 1832 env['matlab_sample_loc'] = pjoin(env['ct_sampledir'], 'matlab') 1833 env['matlab_ctpath_loc'] = pjoin(env['ct_matlab_dir'], 'ctpath.m') 1834 install_message += textwrap.dedent(""" 1835 Matlab toolbox {ct_matlab_dir!s} 1836 Matlab samples {matlab_sample_loc!s} 1837 1838 An m-file to set the correct matlab path for Cantera is at: 1839 1840 {matlab_ctpath_loc!s} 1841 """.format(**env_dict)) 1842 1843 if os.name != 'nt': 1844 env['setup_cantera'] = pjoin(env['ct_bindir'], 'setup_cantera') 1845 env['setup_cantera_csh'] = pjoin(env['ct_bindir'], 'setup_cantera.csh') 1846 install_message += textwrap.dedent(""" 1847 1848 Setup scripts to configure the environment for Cantera are at: 1849 1850 setup script (bash) {setup_cantera!s} 1851 setup script (csh/tcsh) {setup_cantera_csh!s} 1852 1853 It is recommended that you run the script for your shell by typing: 1854 1855 source {setup_cantera!s} 1856 1857 before using Cantera, or else include its contents in your shell login script. 1858 """.format(**env_dict)) 1859 1860 print(install_message) 1861 1862finish_install = env.Command('finish_install', [], postInstallMessage) 1863env.Depends(finish_install, installTargets) 1864install_cantera = Alias('install', finish_install) 1865 1866### Uninstallation 1867def getParentDirs(path, top=True): 1868 head,tail = os.path.split(path) 1869 if head == os.path.abspath(env['prefix']): 1870 return [path] 1871 elif not tail: 1872 if head.endswith(os.sep): 1873 return [] 1874 else: 1875 return [head] 1876 elif top: 1877 return getParentDirs(head, False) 1878 else: 1879 return getParentDirs(head, False) + [path] 1880 1881# Files installed by SCons 1882allfiles = FindInstalledFiles() 1883 1884# Files installed by the Python installer 1885if os.path.exists('build/python-installed-files.txt'): 1886 with open('build/python-installed-files.txt', 'r') as f: 1887 file_list = f.readlines() 1888 1889 install_base = os.path.dirname(file_list[0].strip()) 1890 if os.path.exists(install_base): 1891 not_listed_files = [s for s in os.listdir(install_base) if not any(s in j for j in file_list)] 1892 for f in not_listed_files: 1893 f = pjoin(install_base, f) 1894 if not os.path.isdir(f) and os.path.exists(f): 1895 allfiles.append(File(f)) 1896 for f in file_list: 1897 f = f.strip() 1898 if not os.path.isdir(f) and os.path.exists(f): 1899 allfiles.append(File(f)) 1900 1901# After removing files (which SCons keeps track of), 1902# remove any empty directories (which SCons doesn't track) 1903def removeDirectories(target, source, env): 1904 # Get all directories where files are installed 1905 alldirs = set() 1906 for f in allfiles: 1907 alldirs.update(getParentDirs(f.path)) 1908 if env['layout'] == 'compact': 1909 alldirs.add(os.path.abspath(env['prefix'])) 1910 # Sort in order of decreasing directory length so that empty subdirectories 1911 # will be removed before their parents are checked. 1912 alldirs = sorted(alldirs, key=lambda x: -len(x)) 1913 1914 # Don't remove directories that probably existed before installation, 1915 # even if they are empty 1916 keepDirs = ['local/share', 'local/lib', 'local/include', 'local/bin', 1917 'man/man1', 'dist-packages', 'site-packages'] 1918 for d in alldirs: 1919 if any(d.endswith(k) for k in keepDirs): 1920 continue 1921 if os.path.isdir(d) and not os.listdir(d): 1922 os.rmdir(d) 1923 1924uninstall = env.Command("uninstall", None, Delete(allfiles)) 1925env.AddPostAction(uninstall, Action(removeDirectories)) 1926 1927### Windows MSI Installer ### 1928if 'msi' in COMMAND_LINE_TARGETS: 1929 def build_wxs(target, source, env): 1930 import wxsgen 1931 wxs = wxsgen.WxsGenerator(env['stage_dir'], 1932 short_version=env['cantera_short_version'], 1933 full_version=env['cantera_pure_version'], 1934 x64=env['TARGET_ARCH']=='amd64', 1935 includeMatlab=env['matlab_toolbox']=='y') 1936 wxs.make_wxs(str(target[0])) 1937 1938 wxs_target = env.Command('build/wix/cantera.wxs', [], build_wxs) 1939 env.AlwaysBuild(wxs_target) 1940 1941 env.Append(WIXLIGHTFLAGS=['-ext', 'WixUIExtension']) 1942 msi_target = env.WiX('cantera.msi', ['build/wix/cantera.wxs']) 1943 env.Depends(wxs_target, installTargets) 1944 env.Depends(msi_target, wxs_target) 1945 build_msi = Alias('msi', msi_target) 1946 1947### Tests ### 1948if any(target.startswith('test') for target in COMMAND_LINE_TARGETS): 1949 env['testNames'] = [] 1950 env['test_results'] = env.Command('test_results', [], test_results.print_report) 1951 1952 if env['python_package'] == 'none': 1953 # copy scripts from the full Cython module 1954 test_py_int = env.Command('#build/python_local/cantera/__init__.py', 1955 '#interfaces/python_minimal/cantera/__init__.py', 1956 Copy('$TARGET', '$SOURCE')) 1957 for script in ['ctml_writer', 'ck2cti', 'ck2yaml', 'ctml2yaml']: 1958 s = env.Command('#build/python_local/cantera/{}.py'.format(script), 1959 '#interfaces/cython/cantera/{}.py'.format(script), 1960 Copy('$TARGET', '$SOURCE')) 1961 env.Depends(test_py_int, s) 1962 1963 env.Depends(env['test_results'], test_py_int) 1964 1965 env['python_cmd'] = sys.executable 1966 env.PrependENVPath('PYTHONPATH', Dir('build/python_local').abspath) 1967 else: 1968 env.PrependENVPath('PYTHONPATH', Dir('build/python').abspath) 1969 1970 env['ENV']['PYTHON_CMD'] = env.subst('$python_cmd') 1971 1972 # Tests written using the gtest framework, the Python unittest module, 1973 # or the Matlab xunit package. 1974 VariantDir('build/test', 'test', duplicate=0) 1975 SConscript('build/test/SConscript') 1976 1977 # Regression tests 1978 SConscript('test_problems/SConscript') 1979 1980 if 'test-help' in COMMAND_LINE_TARGETS: 1981 print('\n*** Available tests ***\n') 1982 for name in env['testNames']: 1983 print('test-%s' % name) 1984 sys.exit(0) 1985 1986 Alias('test', env['test_results']) 1987 1988### Dump (debugging SCons) 1989if 'dump' in COMMAND_LINE_TARGETS: 1990 import pprint 1991 # Typical usage: 'scons build dump' 1992 print('os.environ:\n', pprint.pprint(dict(os.environ))) 1993 print('env.Dump():\n', env.Dump()) 1994 sys.exit(0) 1995