diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a007f3ab76..7898fd57b3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -344,7 +344,7 @@ jobs: architecture: x64 - name: Install Python dependencies run: | - python -m pip install -U pip 'setuptools>=47.0.0,<48' + python -m pip install -U pip 'setuptools>=43.0.0' python -m pip install scons pypiwin32 numpy ruamel.yaml cython h5py pandas pytest pytest-github-actions-annotate-failures - name: Restore Boost cache uses: actions/cache@v2 diff --git a/SConstruct b/SConstruct index 46b26d2bd2..3eb0fd07f1 100644 --- a/SConstruct +++ b/SConstruct @@ -88,7 +88,8 @@ if os.name not in ["nt", "posix"]: sys.exit(1) valid_commands = ("build", "clean", "install", "uninstall", - "help", "msi", "samples", "sphinx", "doxygen", "dump") + "help", "msi", "samples", "sphinx", "doxygen", "dump", + "sdist") for command in COMMAND_LINE_TARGETS: if command not in valid_commands and not command.startswith('test'): @@ -106,8 +107,12 @@ if "clean" in COMMAND_LINE_TARGETS: remove_directory("include/cantera/ext") remove_file("interfaces/cython/cantera/_cantera.cpp") remove_file("interfaces/cython/cantera/_cantera.h") - remove_file("interfaces/cython/setup.py") - remove_file("interfaces/python_minimal/setup.py") + remove_file("interfaces/cython/setup.cfg") + remove_file("interfaces/cython/LICENSE.txt") + remove_file("interfaces/cython/README.rst") + remove_file("interfaces/python_minimal/setup.cfg") + remove_file("interfaces/python_minimal/LICENSE.txt") + remove_file("interfaces/python_minimal/README.rst") remove_file("config.log") remove_directory("doc/sphinx/matlab/examples") remove_file("doc/sphinx/matlab/examples.rst") @@ -239,6 +244,10 @@ config_options = [ Cython) are installed. Note: 'y' is a synonym for 'full' and 'n' is a synonym for 'none'.""", "default", ("full", "minimal", "none", "n", "y", "default")), + BoolOption( + "python_sdist", + """Setting this option to True builds the Python sdist.""", + False), PathOption( "python_cmd", """Cantera needs to know where to find the Python interpreter. If @@ -817,6 +826,8 @@ if 'doxygen' in COMMAND_LINE_TARGETS: env['doxygen_docs'] = True if 'sphinx' in COMMAND_LINE_TARGETS: env['sphinx_docs'] = True +if "sdist" in COMMAND_LINE_TARGETS: + env["python_sdist"] = True for arg in ARGUMENTS: if arg not in config: @@ -1349,7 +1360,7 @@ if env['VERBOSE']: env['python_cmd_esc'] = quoted(env['python_cmd']) # Python Package Settings -python_min_version = parse_version('3.5') +python_min_version = parse_version("3.6") # The string is used to set python_requires in setup.py.in env['py_min_ver_str'] = str(python_min_version) # Note: cython_min_version is redefined below if the Python version is 3.8 or higher @@ -1868,6 +1879,10 @@ if env['matlab_toolbox'] == 'y': if env['doxygen_docs'] or env['sphinx_docs']: SConscript('doc/SConscript') +if env["python_sdist"]: + VariantDir("build/python_sdist", "interfaces/python_sdist", duplicate=1) + SConscript("interfaces/python_sdist/SConscript", variant_dir="build/python_sdist") + # Sample programs (also used from test_problems/SConscript) VariantDir('build/samples', 'samples', duplicate=0) sampledir_excludes = ['\\.o$', '^~$', '\\.in', 'SConscript'] @@ -1896,12 +1911,10 @@ def postBuildMessage(target, source, env): print("- To run the test suite, type 'scons test'.") print("- To list available tests, type 'scons test-help'.") if env['googletest'] == 'none': - print(" WARNING: You set the 'googletest' to 'none' and all it's tests will be skipped.") + print(" WARNING: You set the 'googletest' to 'none' and all its tests will be skipped.") + print("- To install, type 'scons install'.") if os.name == 'nt': - print("- To install, type 'scons install'.") print("- To create a Windows MSI installer, type 'scons msi'.") - else: - print("- To install, type 'scons install'.") print("*******************************************************") finish_build = env.Command('finish_build', [], postBuildMessage) diff --git a/ext/SConscript b/ext/SConscript index 309f1159dd..e66f7e3c17 100644 --- a/ext/SConscript +++ b/ext/SConscript @@ -1,11 +1,13 @@ +from pathlib import Path from buildutils import * Import('env', 'build', 'install', 'libraryTargets') localenv = env.Clone() copyenv = localenv.Clone() # no CPPPATH addition, to avoid circular dependencies -license_files = [('Cantera', '#License.txt'), - ('Libexecstream', 'libexecstream/doc/license.txt')] +license_files = {"Cantera": File("#License.txt"), + "Libexecstream": File("#ext/libexecstream/doc/license.txt")} + def prep_default(env): localenv = env.Clone() @@ -42,22 +44,26 @@ for subdir, extensions, prepFunction in libs: objects = localenv.SharedObject(multi_glob(localenv, subdir, *extensions)) libraryTargets.extend(objects) +ext_copies = [] + if not env['system_fmt']: - license_files.append(('fmtlib', 'fmt/LICENSE.rst')) + license_files["fmtlib"] = File("#ext/fmt/LICENSE.rst") localenv = prep_default(env) localenv.Prepend(CPPPATH=Dir('#ext/fmt/include')) libraryTargets.extend( localenv.SharedObject(multi_glob(localenv, 'fmt/src', 'cc'))) for name in ('format.h', 'ostream.h', 'printf.h', 'core.h', 'format-inl.h'): - build(copyenv.Command("#include/cantera/ext/fmt/" + name, - "#ext/fmt/include/fmt/" + name, - Copy('$TARGET', '$SOURCE'))) + ext_copies.extend( + copyenv.Command("#include/cantera/ext/fmt/" + name, + "#ext/fmt/include/fmt/" + name, + Copy("$TARGET", "$SOURCE")) + ) if env['system_sundials'] == 'n': localenv = prep_default(env) localenv.Prepend(CPPPATH=[Dir('#include/cantera/ext'), Dir('#ext/sundials/src/sundials')]) - license_files.append(('Sundials', 'sundials/LICENSE')) + license_files["Sundials"] = File("#ext/sundials/LICENSE") # Generate sundials_config.h sundials_configh = {} @@ -65,17 +71,27 @@ if env['system_sundials'] == 'n': sundials_configh['SUNDIALS_USE_GENERIC_MATH'] = 1 if env['use_lapack']: sundials_configh['SUNDIALS_BLAS_LAPACK'] = 1 - localenv.AlwaysBuild(env.Command('#include/cantera/ext/sundials/sundials_config.h', - 'sundials_config.h.in', - ConfigBuilder(sundials_configh))) + sundials_configh_build = env.Command("#build/ext/sundials_config.h.build", + "sundials_config.h.in", + ConfigBuilder(sundials_configh)) + # This separate copy operation, which SCons will skip if sundials_config.h.build is + # unmodified, prevents unnecessary re-copies of files in the #include directory + localenv.AlwaysBuild(sundials_configh_build) + ext_copies.extend( + localenv.Command("#include/cantera/ext/sundials/sundials_config.h", + "#build/ext/sundials_config.h.build", + Copy("$TARGET", "$SOURCE")) + ) # Copy sundials header files into common include directory for subdir in ('sundials', 'nvector', 'cvodes', 'ida', 'sunmatrix', 'sunlinsol', 'sunnonlinsol'): for header in multi_glob(env, 'sundials/include/'+subdir, 'h'): - build(copyenv.Command('#include/cantera/ext/%s/%s' % (subdir, header.name), - '#ext/sundials/include/%s/%s' % (subdir, header.name), - Copy('$TARGET', '$SOURCE'))) + ext_copies.extend( + copyenv.Command(f"#include/cantera/ext/{subdir}/{header.name}", + f"#ext/sundials/include/{subdir}/{header.name}", + Copy("$TARGET", "$SOURCE")) + ) # Compile Sundials source files. Skip files related to the Sundials Fortran # interface, which start with 'fsun'. @@ -93,14 +109,18 @@ if env['system_sundials'] == 'n': if not env['system_yamlcpp']: localenv = prep_default(env) localenv.Prepend(CPPPATH=Dir('#include/cantera/ext')) - license_files.append(('YAML-CPP', 'yaml-cpp/LICENSE')) + license_files["YAML-CPP"] = File("#ext/yaml-cpp/LICENSE") # Copy header files into common include directory for subdir in ('', 'contrib', 'node', 'node/detail'): for header in multi_glob(env, 'yaml-cpp/include/yaml-cpp/'+subdir, 'h'): - h = build(localenv.Command('#include/cantera/ext/yaml-cpp/{}/{}'.format(subdir, header.name), - '#ext/yaml-cpp/include/yaml-cpp/{}/{}'.format(subdir, header.name), - Copy('$TARGET', '$SOURCE'))) + ext_copies.extend( + localenv.Command( + f"#include/cantera/ext/yaml-cpp/{subdir}/{header.name}", + f"#ext/yaml-cpp/include/yaml-cpp/{subdir}/{header.name}", + Copy("$TARGET", "$SOURCE") + ) + ) # Compile yaml-cpp source files for subdir in ('', 'contrib'): @@ -109,10 +129,11 @@ if not env['system_yamlcpp']: if not env['system_eigen']: - license_files.append(('Eigen', 'eigen/COPYING.MPL2')) + license_files["Eigen"] = File("#ext/eigen/COPYING.MPL2") h = build(copyenv.Command('#include/cantera/ext/Eigen', '#ext/eigen/Eigen', Copy('$TARGET', '$SOURCE'))) copyenv.Depends(copyenv['config_h_target'], h) + ext_copies.extend(h) # Google Test: Used internally for Cantera unit tests. if env['googletest'] == 'submodule': @@ -123,28 +144,35 @@ if env['googletest'] == 'submodule': gmock = build(localenv.Library('../lib/gmock', source=['googletest/googlemock/src/gmock-all.cc'])) -# Create license file containing licenses for Cantera and all included packages +env["ext_include_copies_target"] = build(ext_copies) + + def generate_license(target, source, env): - stars = '*'*50 + '\n' + '*'*50 + '\n' - tpl = stars + 'The following license applies to {}\n' + stars + '\n{}\n' + target = Path(target[0].abspath) + stars = "*" * 50 + "\n" + "*" * 50 + "\n" + tpl = stars + "The following license applies to {}\n" + stars + "\n{}\n" license = [] - for (package,_),filename in zip(license_files, source): - license.append(tpl.format(package, open(filename.path).read().strip())) + for package, license_file in env["license_files"].items(): + license_file = Path(license_file.abspath) + license.append(tpl.format(package, license_file.read_text().strip())) + license = "\n".join(license) + if target.suffix == ".rtf": + license = license.replace("\\", "\\\\").replace("{", "\\{").replace("}", "\\}") + license = license.replace("\n", " \\par\n") + license = (r"{\rtf1\ansi{\fonttbl\f0\fswiss Arial;}\f0\pard\fs16 " + + license + "}") - license = '\n'.join(license) - if target[0].path.endswith('.rtf'): - license = license.replace('\\', '\\\\').replace('{', '\\{').replace('}', '\\}') - license = license.replace('\n', ' \\par\n') - license = r'{\rtf1\ansi{\fonttbl\f0\fswiss Arial;}\f0\pard\fs16 ' + license + '}' + target.write_text(license) - open(target[0].path, 'w').write(license) -license = build(localenv.Command('LICENSE.txt', [x[1] for x in license_files], +localenv["license_files"] = license_files +license = build(localenv.Command("LICENSE.txt", license_files.values(), generate_license)) +env["license_target"] = license install('$inst_docdir', license) if env['OS'] == 'Windows': # RTF version is required for Windows installer - build(localenv.Command('LICENSE.rtf', [x[1] for x in license_files], + build(localenv.Command("LICENSE.rtf", license_files.values(), generate_license)) diff --git a/include/cantera/base/global.h b/include/cantera/base/global.h index fcc76a370f..908efabd5d 100644 --- a/include/cantera/base/global.h +++ b/include/cantera/base/global.h @@ -113,6 +113,7 @@ std::string gitCommit(); * @returns a string containing the name of the base directory where %Cantera is * installed. If the environmental variable CANTERA_ROOT is defined, this * function will return its value, preferentially. + * @deprecated Unused within Cantera. To be removed after Cantera 2.6 * * @ingroup inputfiles */ diff --git a/interfaces/cython/.gitignore b/interfaces/cython/.gitignore index c76ec86cfa..2a5acec62d 100644 --- a/interfaces/cython/.gitignore +++ b/interfaces/cython/.gitignore @@ -2,10 +2,8 @@ cantera/*.cpp cantera/*.c cantera/data cantera/test/data -setup.py -scripts/ctml_writer.py -scripts/ctml_writer -scripts/ck2cti.py -scripts/ck2cti Cantera.egg-info dist +setup.cfg +LICENSE.txt +README.rst diff --git a/interfaces/cython/SConscript b/interfaces/cython/SConscript index c55debb2fb..068abf87d9 100644 --- a/interfaces/cython/SConscript +++ b/interfaces/cython/SConscript @@ -4,6 +4,7 @@ from os.path import join as pjoin from os.path import normpath from pathlib import Path from pkg_resources import parse_version +import json from buildutils import * Import('env', 'build', 'install') @@ -32,24 +33,41 @@ testFiles = localenv.RecursiveInstall('#interfaces/cython/cantera/test/data', build(testFiles) # Get information needed to build the Python module -script = '\n'.join(("from distutils.sysconfig import *", - "import numpy", - "print(get_config_var('EXT_SUFFIX') or get_config_var('SO'))", - "print(get_config_var('INCLUDEPY'))", - "print(get_config_var('LDLIBRARY'))", - "print(get_config_var('prefix'))", - "print(get_python_version())", - "print(numpy.get_include())")) -info = get_command_output(localenv["python_cmd"], "-c", script) -module_ext, inc, pylib, prefix, py_version, numpy_include = info.splitlines()[-6:] +script = """\ +from sysconfig import * +import numpy +import json +vars = get_config_vars() +vars["plat"] = get_platform() +vars["numpy_include"] = numpy.get_include() +print(json.dumps(vars)) +""" +info = json.loads(get_command_output(localenv["python_cmd"], "-c", script)) +module_ext = info["EXT_SUFFIX"] +inc = info["INCLUDEPY"] +pylib = info.get("LDLIBRARY") +prefix = info["prefix"] +py_version_short = parse_version(info["py_version_short"]) +py_version_full = parse_version(info["py_version"]) +numpy_include = info["numpy_include"] localenv.Prepend(CPPPATH=[Dir('#include'), inc, numpy_include]) localenv.Prepend(LIBS=localenv['cantera_libs']) +# Fix the module extension for Windows from the sysconfig library. +# See https://github.com/python/cpython/pull/22088 and +# https://bugs.python.org/issue39825 +if ( + py_version_full < parse_version("3.8.7") + and localenv["OS"] == "Windows" + and module_ext == ".pyd" +): + module_ext = f".cp{info['py_version_nodot']}-{info['plat'].replace('-', '_')}.pyd" + # Don't print deprecation warnings for internal Python changes. # Only applies to Python 3.8. The field that is deprecated in Python 3.8 # and causes the warnings to appear will be removed in Python 3.9 so no # further warnings should be issued. -if localenv['HAS_CLANG'] and parse_version(py_version) == parse_version('3.8'): +if localenv["HAS_CLANG"] and py_version_short == parse_version("3.8"): localenv.Append(CXXFLAGS='-Wno-deprecated-declarations') if localenv['OS'] == 'Darwin': @@ -57,11 +75,13 @@ if localenv['OS'] == 'Darwin': elif localenv['OS'] == 'Windows': localenv.Append(LIBPATH=prefix+'/libs') if localenv['toolchain'] == 'mingw': - localenv.Append(LIBS='python{}'.format(py_version.replace('.',''))) + localenv.Append(LIBS=f"python{info['py_version_nodot']}") if localenv['OS_BITS'] == 64: localenv.Append(CPPDEFINES='MS_WIN64') - # fix for http://bugs.python.org/issue11566 - localenv.Append(CPPDEFINES={'_hypot':'hypot'}) + # Fix for https://bugs.python.org/issue11566. Fixed in 3.7.3 and higher. + # See https://github.com/python/cpython/pull/11283 + if py_version_full < parse_version("3.7.3"): + localenv.Append(CPPDEFINES={"_hypot": "hypot"}) elif localenv['OS'] == 'Cygwin': # extract 'pythonX.Y' from 'libpythonX.Y.dll.a' localenv.Append(LIBS=pylib[3:-6]) @@ -73,16 +93,19 @@ ext = localenv.LoadableModule('#build/python/cantera/_cantera{}'.format(module_e SHLIBPREFIX='', LIBSUFFIXES=[module_ext]) localenv['py_extension'] = ext[0].name -localenv.SubstFile('setup.py', 'setup.py.in') +setup_cfg = localenv.SubstFile("setup.cfg", "setup.cfg.in") +readme = localenv.Command("README.rst", "#README.rst", Copy("$TARGET", "$SOURCE")) +license = localenv.Command("LICENSE.txt", "#build/ext/LICENSE.txt", + Copy("$TARGET", "$SOURCE")) +localenv.Depends(license, localenv["license_target"]) build_cmd = ('cd interfaces/cython &&' ' $python_cmd_esc setup.py build --build-lib=../../build/python') -mod = build(localenv.Command('#build/python/cantera/__init__.py', 'setup.py', +mod = build(localenv.Command("#build/python/cantera/__init__.py", "setup.cfg", build_cmd)) env['python_module'] = mod env['python_extension'] = ext -localenv.Depends(mod, ext) -localenv.Depends(mod, dataFiles + testFiles) +localenv.Depends(mod, [ext, dataFiles, testFiles, setup_cfg, readme, license]) localenv.Depends(ext, localenv['cantera_staticlib']) for f in (multi_glob(localenv, 'cantera', 'py') + diff --git a/interfaces/cython/cantera/test/__init__.py b/interfaces/cython/cantera/test/__init__.py index 4476c62b0f..8fb72ee1bf 100644 --- a/interfaces/cython/cantera/test/__init__.py +++ b/interfaces/cython/cantera/test/__init__.py @@ -15,5 +15,5 @@ from .test_transport import * from .test_utils import * -cantera.add_directory(Path(__file__) / "data") +cantera.add_directory(Path(__file__).parent / "data") cantera.add_directory(Path(__file__).parents[1] / "examples" / "surface_chemistry") diff --git a/interfaces/cython/setup.cfg.in b/interfaces/cython/setup.cfg.in new file mode 100644 index 0000000000..ccf42785c0 --- /dev/null +++ b/interfaces/cython/setup.cfg.in @@ -0,0 +1,69 @@ +[metadata] +name = Cantera +version = @cantera_version@ +description = Cantera is an open-source suite of tools for problems involving chemical kinetics, thermodynamics, and transport processes. +long_description = file: README.rst +long_description_content_type = text/x-rst +license_files = LICENSE.txt +url = https://cantera.org +author = Cantera Developers +author_email = developers@cantera.org +keywords = chemistry physics +license = BSD 3-Clause License +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Education + Intended Audience :: Science/Research + License :: OSI Approved :: BSD License + Operating System :: MacOS :: MacOS X + Operating System :: Microsoft :: Windows + Operating System :: POSIX :: Linux + Programming Language :: C + Programming Language :: C++ + Programming Language :: Cython + Programming Language :: Fortran + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: Implementation :: CPython + Topic :: Scientific/Engineering :: Chemistry + Topic :: Scientific/Engineering :: Physics +project_urls = + Documentation = https://cantera.org/documentation + Funding = https://numfocus.org/donate-to-cantera + Source = https://github.com/Cantera/cantera + Tracker = https://github.com/Cantera/cantera/issues + +[options] +zip_safe = False +include_package_data = True +install_requires = + numpy >= 1.12.0 + ruamel.yaml >= 0.15.34 +python_requires = >=@py_min_ver_str@ +packages = + cantera + cantera.data + cantera.test + cantera.test.data + cantera.examples + +[options.package_data] +cantera.data = *.*, */*.* +cantera.test.data = *.*, */*.* +cantera.examples = */*.* +cantera = *.pxd + +[options.extras_require] +hdf5 = h5py +pandas = pandas + +[options.entry_points] +console_scripts = + ck2cti = cantera.ck2cti:script_entry_point + ctml_writer = cantera.ctml_writer:main + ck2yaml = cantera.ck2yaml:script_entry_point + cti2yaml = cantera.cti2yaml:main + ctml2yaml = cantera.ctml2yaml:main diff --git a/interfaces/cython/setup.py b/interfaces/cython/setup.py new file mode 100644 index 0000000000..667310b861 --- /dev/null +++ b/interfaces/cython/setup.py @@ -0,0 +1,5 @@ +from setuptools import setup, Extension + +extension = Extension("cantera._cantera", sources=[]) + +setup(ext_modules=[extension]) diff --git a/interfaces/cython/setup.py.in b/interfaces/cython/setup.py.in deleted file mode 100644 index 85e9a01f74..0000000000 --- a/interfaces/cython/setup.py.in +++ /dev/null @@ -1,103 +0,0 @@ -import os -from setuptools import setup - -# Monkey patch to prevent bdist_msi from incorrectly overwriting the value of -# build-lib specified on the command line. -# See http://bugs.python.org/issue1109963 - -# This patch works by returning False the first time that the -# 'has_ext_modules' method is called in bdist_msi.run, which is where the -# replacement of build_lib happens. Subsequent calls to 'has_ext_modules' -# should use the correct value so that the resulting installer is specific to -# this Python version. Known to affect Python versions 2.6 through 3.7. If -# this bug is ever fixed, this patch should be made conditional on the Python -# version. -if os.name == 'nt': - from distutils.command.bdist_msi import bdist_msi - bdist_run_orig = bdist_msi.run - def bdist_run_new(self): - has_ext_modules_orig = self.distribution.has_ext_modules - self._first_call = True - def has_ext_modules(): - if self._first_call: - self._first_call = False - return False - else: - return has_ext_modules_orig() - - self.distribution.has_ext_modules = has_ext_modules - return bdist_run_orig(self) - - bdist_msi.run = bdist_run_new - -# Monkey patch to resolve https://bugs.python.org/issue34251 -# (Affects Python 3.7.0) -if os.name == 'nt': - import msilib - msilib.Win64 = msilib.AMD64 - -# Copy the long_description from docs/sphinx/index.rst -long_description = """ -Cantera is a suite of object-oriented software tools for problems involving -chemical kinetics, thermodynamics, and/or transport processes. - -Cantera provides types (or classes) of objects representing phases of -matter, interfaces between these phases, reaction managers, time-dependent -reactor networks, and steady one-dimensional reacting flows. Cantera is -currently used for applications including combustion, detonations, -electrochemical energy conversion and storage, fuel cells, batteries, aqueous -electrolyte solutions, plasmas, and thin film deposition. - -Cantera can be used from Python and Matlab, or in applications written -in C++ and Fortran 90. -""" - -setup( - name="Cantera", - version="@cantera_version@", - description="The Cantera Python Interface", - long_description=long_description, - author="Raymond Speth", - author_email="speth@mit.edu", - url="https://cantera.org", - packages=[ - 'cantera', - 'cantera.data', - 'cantera.test', - 'cantera.test.data', - 'cantera.examples', - ], - entry_points={ - 'console_scripts': [ - 'ck2cti=cantera.ck2cti:script_entry_point', - 'ctml_writer=cantera.ctml_writer:main', - 'ck2yaml=cantera.ck2yaml:script_entry_point', - 'cti2yaml=cantera.cti2yaml:main', - 'ctml2yaml=cantera.ctml2yaml:main', - ], - }, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Education', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: C++', - 'Programming Language :: Fortran', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Topic :: Scientific/Engineering :: Chemistry', - ], - package_data={ - 'cantera.data': ['*.*', '*/*.*'], - 'cantera.test.data': ['*.*', '*/*.*'], - 'cantera.examples': ['*/*.*'], - 'cantera': ["@py_extension@", '*.pxd'], - }, - zip_safe=False, - python_requires=">=@py_min_ver_str@", -) diff --git a/interfaces/python_minimal/.gitignore b/interfaces/python_minimal/.gitignore index 5ca4392ca9..8a8b5c9255 100644 --- a/interfaces/python_minimal/.gitignore +++ b/interfaces/python_minimal/.gitignore @@ -1,5 +1,3 @@ -setup*.py -scripts/* cantera/ck2cti.py cantera/ctml_writer.py cantera/ck2yaml.py @@ -8,3 +6,6 @@ cantera/ctml2yaml.py build dist Cantera_minimal.egg-info +LICENSE.txt +README.rst +setup.cfg diff --git a/interfaces/python_minimal/SConscript b/interfaces/python_minimal/SConscript index 9c3ab3e78c..2047a06ef9 100644 --- a/interfaces/python_minimal/SConscript +++ b/interfaces/python_minimal/SConscript @@ -7,23 +7,30 @@ Import('env', 'build', 'install') localenv = env.Clone() -make_setup = build(localenv.SubstFile('setup.py', 'setup.py.in')) +make_setup = build(localenv.SubstFile("setup.cfg", "setup.cfg.in")) # copy scripts from the full Cython module -for script in ['ctml_writer', 'ck2cti', 'ck2yaml', 'cti2yaml', 'ctml2yaml']: +for script in ["ctml_writer", "ck2cti", "ck2yaml", "cti2yaml", "ctml2yaml"]: # The actual script - s = build(env.Command('cantera/{}.py'.format(script), - '#interfaces/cython/cantera/{}.py'.format(script), - Copy('$TARGET', '$SOURCE'))) + s = build(env.Command(f"cantera/{script}.py", + f"#interfaces/cython/cantera/{script}.py", + Copy("$TARGET", "$SOURCE"))) localenv.Depends(make_setup, s) -build_cmd = ('cd interfaces/python_minimal &&' - ' $python_cmd_esc setup.py build --build-lib=../../build/python') +build_cmd = ("cd interfaces/python_minimal && " + "$python_cmd_esc setup.py build --build-base=../../build/python") -mod = build(localenv.Command('#build/python/cantera/__init__.py', 'setup.py', +mod = build(localenv.Command("#build/python/cantera/__init__.py", "setup.cfg", build_cmd)) env['python_module'] = mod +readme = localenv.Command("README.rst", "#README.rst", Copy("$TARGET", "$SOURCE")) +# The target of this command must match the file listed in setup.cfg.in +license = localenv.Command("LICENSE.txt", "#License.txt", + Copy("$TARGET", "$SOURCE")) +localenv.Depends(license, localenv["license_target"]) +localenv.Depends(mod, [make_setup, readme, license]) + if localenv['PYTHON_INSTALLER'] == 'direct': if localenv['python_prefix'] == 'USER': # Install to the OS-dependent user site-packages directory diff --git a/interfaces/python_minimal/setup.cfg.in b/interfaces/python_minimal/setup.cfg.in new file mode 100644 index 0000000000..d656b87220 --- /dev/null +++ b/interfaces/python_minimal/setup.cfg.in @@ -0,0 +1,49 @@ +[metadata] +name = Cantera_minimal +version = @cantera_version@ +description = A minimal Cantera interface, containing only the input file conversion scripts +long_description = This Cantera interface contains the conversion scripts for input files into the Cantera format. For the full Python interface, please see [Cantera](https://pypi.org/project/cantera). +long_description_content_type = text/markdown +license_files = LICENSE.txt +url = https://cantera.org +author = Cantera Developers +author_email = developers@cantera.org +keywords = chemistry physics +license = BSD 3-Clause License +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Education + Intended Audience :: Science/Research + License :: OSI Approved :: BSD License + Operating System :: MacOS :: MacOS X + Operating System :: Microsoft :: Windows + Operating System :: POSIX :: Linux + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Topic :: Scientific/Engineering :: Chemistry + Topic :: Scientific/Engineering :: Physics +project_urls = + Documentation = https://cantera.org/documentation + Funding = https://numfocus.org/donate-to-cantera + Source = https://github.com/Cantera/cantera + Tracker = https://github.com/Cantera/cantera/issues + +[options] +zip_safe = True +install_requires = + numpy >= 1.12.0 + ruamel.yaml >= 0.15.34 +python_requires = >=@py_min_ver_str@ +packages = + cantera + +[options.entry_points] +console_scripts = + ck2cti = cantera.ck2cti:script_entry_point + ctml_writer = cantera.ctml_writer:main + ck2yaml = cantera.ck2yaml:script_entry_point + cti2yaml = cantera.cti2yaml:main + ctml2yaml = cantera.ctml2yaml:main diff --git a/interfaces/python_minimal/setup.py b/interfaces/python_minimal/setup.py new file mode 100644 index 0000000000..606849326a --- /dev/null +++ b/interfaces/python_minimal/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() diff --git a/interfaces/python_minimal/setup.py.in b/interfaces/python_minimal/setup.py.in deleted file mode 100644 index ead7760440..0000000000 --- a/interfaces/python_minimal/setup.py.in +++ /dev/null @@ -1,20 +0,0 @@ -from setuptools import setup - -setup(name="Cantera_minimal", - version="@cantera_version@", - description="The Minimal Cantera Python Interface", - long_description="", - author="Raymond Speth", - author_email="speth@mit.edu", - url="http://www.cantera.org", - packages = ['cantera'], - entry_points={ - 'console_scripts': [ - 'ck2cti=cantera.ck2cti:script_entry_point', - 'ctml_writer=cantera.ctml_writer:main', - 'ck2yaml=cantera.ck2yaml:script_entry_point', - 'cti2yaml=cantera.cti2yaml:main', - 'ctml2yaml=cantera.ctml2yaml:main', - ], - }, -) diff --git a/interfaces/python_sdist/MANIFEST.in b/interfaces/python_sdist/MANIFEST.in new file mode 100644 index 0000000000..060d90ecf3 --- /dev/null +++ b/interfaces/python_sdist/MANIFEST.in @@ -0,0 +1,10 @@ +include cantera/_cantera.cpp +include cantera/_cantera.h +recursive-include cantera *.pyx +include sundials_config.h.in +graft include +recursive-include ext *.h +recursive-include src *.h +recursive-include ext/libexecstream/posix *.cpp +recursive-include ext/libexecstream/win *.cpp +exclude include/cantera/ext/sundials/sundials_config.h diff --git a/interfaces/python_sdist/SConscript b/interfaces/python_sdist/SConscript new file mode 100644 index 0000000000..79dfb55b61 --- /dev/null +++ b/interfaces/python_sdist/SConscript @@ -0,0 +1,147 @@ +"""SDist of the Python Module""" +from pathlib import Path +import re +import shutil +from textwrap import dedent + +from build import ProjectBuilder +from build.env import IsolatedEnvBuilder + +from buildutils import logger + +Import("env") + +localenv = env.Clone() + +sdist_targets = [] + + +def sdist(targets): + sdist_targets.extend(targets) + return targets + + +def copy_ext_src(target, source, env): + target_ext = Path(target[0].abspath) + source_ext = Path(source[0].abspath) + if target_ext.is_dir(): + shutil.rmtree(target_ext) + + # fmt library + FMT_ROOT = target_ext / "fmt" + FMT_ROOT.mkdir(parents=True) # Needed to be able to use copy2 + for cc_file in (source_ext / "fmt" / "src").glob("*.cc"): + shutil.copy2(cc_file, FMT_ROOT) + shutil.copytree(source_ext / "fmt" / "include" / "fmt", FMT_ROOT / "fmt") + + # libexecstream + EXECSTREAM_ROOT = target_ext / "libexecstream" + EXECSTREAM_ROOT.mkdir(parents=True) # Needed to be able to use copy2 + shutil.copy2(source_ext / "libexecstream" / "exec-stream.cpp", EXECSTREAM_ROOT) + shutil.copy2(source_ext / "libexecstream" / "exec-stream.h", EXECSTREAM_ROOT) + shutil.copytree(source_ext / "libexecstream" / "win", EXECSTREAM_ROOT / "win") + shutil.copytree(source_ext / "libexecstream" / "posix", EXECSTREAM_ROOT / "posix") + + # yaml-cpp library + YAML_ROOT = target_ext / "yaml-cpp" + shutil.copytree(source_ext / "yaml-cpp" / "src", YAML_ROOT) + shutil.copytree(source_ext / "yaml-cpp" / "include" / "yaml-cpp", + YAML_ROOT / "yaml-cpp") + + # SUNDIALS library + SUNDIALS_ROOT = target_ext / "sundials" + subdirs = ["sundials", "nvector/serial", "cvodes", "ida", "sunmatrix/band", + "sunmatrix/dense", "sunmatrix/sparse", "sunlinsol/dense", + "sunlinsol/band", "sunlinsol/spgmr", "sunnonlinsol/newton"] + ignores = shutil.ignore_patterns("fsun*", "CMake*", "fmod", "fcmix") + for subdir in subdirs: + shutil.copytree( + source_ext / "sundials" / "src" / subdir, + SUNDIALS_ROOT / subdir, + ignore=ignores, + ) + + +def replace_git_hash(target, source, env): + # Avoid having to set a C preprocessor define at compile time, since + # the git commit is unknown from the sdist + target = Path(target[0].abspath) + source = Path(source[0].abspath) + git_commit_replaced = re.sub("#ifdef GIT_COMMIT.*?#endif", + f""" return "{env['git_commit']}";""", + source.read_text(), + flags=re.DOTALL) + target.write_text(git_commit_replaced) + + +# Use RecursiveInstall to be able to exclude files and folders. +sdist(localenv.RecursiveInstall( + "src", + "#src", + exclude=["fortran", "matlab", r"global\.cpp", "SCons.*"], +)) + +sdist(localenv.Command("src/base/global.cpp", "#src/base/global.cpp", + replace_git_hash)) + +include_target = sdist(localenv.Command("include", "#include", + Copy("$TARGET", "$SOURCE"))) +localenv.Depends(include_target, env["config_h_target"]) +localenv.Depends(include_target, env["ext_include_copies_target"]) + +sdist(localenv.Command("ext", "#ext", copy_ext_src)) + +# Use RecursiveInstall to make sure that files are not overwritten during the copy. +# A normal Copy Action would fail because of the existing directories. +sdist(localenv.RecursiveInstall("cantera", + "#interfaces/cython/cantera", + exclude=["__pycache__"])) +sdist(localenv.RecursiveInstall("cantera/data", + "#build/data")) +sdist(localenv.RecursiveInstall("cantera/test/data", + "#test/data")) + +# Copy the minimal Sundials configuration template into the sdist so that +# it can be filled in at compile time on the user's machine +sdist(localenv.Command("sundials_config.h.in", "#ext/sundials_config.h.in", + Copy("$TARGET", "$SOURCE"))) + +license = sdist(localenv.Command("LICENSE.txt", "#build/ext/LICENSE.txt", + Copy("$TARGET", "$SOURCE"))) +localenv.Depends(license, localenv["license_target"]) + +sdist(localenv.SubstFile("setup.cfg", "setup.cfg.in")) +sdist(localenv.Command("README.rst", "#README.rst", Copy("$TARGET", "$SOURCE"))) + + +def build_sdist(target, source, env): + build_dir = Path(source[0].abspath).parent + builder = ProjectBuilder(str(build_dir)) + with IsolatedEnvBuilder() as build_env: + builder.python_executable = build_env.executable + builder.scripts_dir = build_env.scripts_dir + # first install the build dependencies + build_env.install(builder.build_system_requires) + # then get the extra required dependencies from the backend + build_env.install(builder.get_requires_for_build("sdist")) + builder.build("sdist", str(build_dir / "dist"), {}) + + +def finish_sdist_message(target, source, env): + sdist = Path(source[0].path).name + message = dedent(f""" + ******************************************************* + Python sdist '{sdist}' created successfully. + The sdist file is in the 'build/python_sdist/dist' + directory. + ******************************************************* + """) + logger.info(message, print_level=False) + + +sdist_target = f"dist/Cantera-{env['cantera_version']}.tar.gz" +sdist_sources = ("setup.py", "pyproject.toml", "MANIFEST.in") +built_sdist = localenv.Command(sdist_target, sdist_sources, build_sdist) +finish_sdist = localenv.Command("finish_sdist", sdist_target, finish_sdist_message) +localenv.Depends(built_sdist, sdist_targets) +env.Alias("sdist", finish_sdist) diff --git a/interfaces/python_sdist/pyproject.toml b/interfaces/python_sdist/pyproject.toml new file mode 100644 index 0000000000..a272bb9561 --- /dev/null +++ b/interfaces/python_sdist/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=43.0.0", "wheel", "oldest-supported-numpy", "Cython>=0.29.12"] +build-backend = "setuptools.build_meta" diff --git a/interfaces/python_sdist/setup.cfg.in b/interfaces/python_sdist/setup.cfg.in new file mode 100644 index 0000000000..ccf42785c0 --- /dev/null +++ b/interfaces/python_sdist/setup.cfg.in @@ -0,0 +1,69 @@ +[metadata] +name = Cantera +version = @cantera_version@ +description = Cantera is an open-source suite of tools for problems involving chemical kinetics, thermodynamics, and transport processes. +long_description = file: README.rst +long_description_content_type = text/x-rst +license_files = LICENSE.txt +url = https://cantera.org +author = Cantera Developers +author_email = developers@cantera.org +keywords = chemistry physics +license = BSD 3-Clause License +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Education + Intended Audience :: Science/Research + License :: OSI Approved :: BSD License + Operating System :: MacOS :: MacOS X + Operating System :: Microsoft :: Windows + Operating System :: POSIX :: Linux + Programming Language :: C + Programming Language :: C++ + Programming Language :: Cython + Programming Language :: Fortran + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: Implementation :: CPython + Topic :: Scientific/Engineering :: Chemistry + Topic :: Scientific/Engineering :: Physics +project_urls = + Documentation = https://cantera.org/documentation + Funding = https://numfocus.org/donate-to-cantera + Source = https://github.com/Cantera/cantera + Tracker = https://github.com/Cantera/cantera/issues + +[options] +zip_safe = False +include_package_data = True +install_requires = + numpy >= 1.12.0 + ruamel.yaml >= 0.15.34 +python_requires = >=@py_min_ver_str@ +packages = + cantera + cantera.data + cantera.test + cantera.test.data + cantera.examples + +[options.package_data] +cantera.data = *.*, */*.* +cantera.test.data = *.*, */*.* +cantera.examples = */*.* +cantera = *.pxd + +[options.extras_require] +hdf5 = h5py +pandas = pandas + +[options.entry_points] +console_scripts = + ck2cti = cantera.ck2cti:script_entry_point + ctml_writer = cantera.ctml_writer:main + ck2yaml = cantera.ck2yaml:script_entry_point + cti2yaml = cantera.cti2yaml:main + ctml2yaml = cantera.ctml2yaml:main diff --git a/interfaces/python_sdist/setup.py b/interfaces/python_sdist/setup.py new file mode 100644 index 0000000000..4bd16ab71e --- /dev/null +++ b/interfaces/python_sdist/setup.py @@ -0,0 +1,148 @@ +import sys +import os +from setuptools import setup, Extension +from setuptools.command.install import install +from setuptools.command.develop import develop +from pathlib import Path +import numpy +import shutil + +HERE = Path(__file__).parent +CT_SRC = HERE / "src" +EXT_SRC = HERE / "ext" +CT_INCLUDE = HERE / "include" +BOOST_INCLUDE = None +FORCE_CYTHON_COMPILE = False + +CYTHON_BUILT_FILES = [HERE / "cantera" / f"_cantera.{ext}" for ext in ("cpp", "h")] + + +class CanteraOptionsMixin: + """Custom options for the install and develop commands. + + Modeled after https://stackoverflow.com/a/53833930 + """ + user_options = [ + ("force-cython-compile", None, "Force compilation of .pyx files via Cython"), + ("boost-include", None, "Location of the Boost header files."), + ] + + def initialize_options(self): + super().initialize_options() + self.force_cython_compile = False + self.boost_include = None + + def finalize_options(self): + if self.boost_include is not None: + if not Path(self.boost_include).is_dir(): + raise TypeError(f"The path {self.boost_include!r} is not a directory.") + super().finalize_options() + + def run(self): + global BOOST_INCLUDE, FORCE_CYTHON_COMPILE + BOOST_INCLUDE = self.boost_include + FORCE_CYTHON_COMPILE = self.force_cython_compile + super().run() + + +class InstallCommand(CanteraOptionsMixin, install): + user_options = (getattr(install, "user_options", []) + + CanteraOptionsMixin.user_options) + + +class DevelopCommand(CanteraOptionsMixin, develop): + user_options = (getattr(develop, "user_options", []) + + CanteraOptionsMixin.user_options) + + +if ( + not all(p.exists() for p in CYTHON_BUILT_FILES) + or "sdist" in sys.argv + or FORCE_CYTHON_COMPILE + or os.environ.get("FORCE_CYTHON_COMPILE", False) +): + from Cython.Build import cythonize + CYTHON_EXT = ".pyx" + for p in CYTHON_BUILT_FILES: + if p.exists(): + p.unlink() +else: + CYTHON_EXT = ".cpp" + + def cythonize(extensions): + """Define a no-op for when we're not using Cython.""" + return extensions + +source_files = ["cantera/_cantera" + CYTHON_EXT] +source_files += list(map(str, CT_SRC.glob("**/*.cpp"))) +sundials_sources = list(map(str, EXT_SRC.glob("sundials/**/*.c"))) +yaml_cpp_sources = list(map(str, EXT_SRC.glob("yaml-cpp/**/*.cpp"))) +fmt_sources = list(map(str, EXT_SRC.glob("fmt/*.cc"))) +libexecstream_sources = [str(EXT_SRC / "libexecstream" / "exec-stream.cpp")] + +include_dirs = [ + str(CT_INCLUDE), + str(CT_INCLUDE / "cantera" / "ext"), + str(CT_SRC), + numpy.get_include() +] + +if "BOOST_INCLUDE" in os.environ: + include_dirs.append(os.environ["BOOST_INCLUDE"]) +elif BOOST_INCLUDE is not None: + include_dirs.append(BOOST_INCLUDE) + +if sys.platform != "win32": + extra_compile_flags = ["-std=c++11"] + sundials_configh = { + "SUNDIALS_USE_GENERIC_MATH": "#define SUNDIALS_USE_GENERIC_MATH 1", + "SUNDIALS_BLAS_LAPACK": "/* #undef SUNDIALS_BLAS_LAPACK */" + } + sundials_cflags = ["-w"] + sundials_macros = [] +else: + extra_compile_flags = [] + sundials_macros = [("_CRT_SECURE_NO_WARNINGS", None)] + sundials_configh = { + "SUNDIALS_USE_GENERIC_MATH": "/* #undef SUNDIALS_USE_GENERIC_MATH */", + "SUNDIALS_BLAS_LAPACK": "/* #undef SUNDIALS_BLAS_LAPACK */" + } + sundials_cflags = [] + +config_h_in = (HERE / "sundials_config.h.in").read_text() +config_h = HERE / "sundials_config.h" +config_h.write_text(config_h_in.format_map(sundials_configh)) +shutil.copy2(config_h, EXT_SRC / "sundials" / "sundials") +shutil.copy2(config_h, CT_INCLUDE / "cantera" / "ext" / "sundials") + +extensions = cythonize([ + Extension( + "cantera._cantera", + source_files, + include_dirs=include_dirs, + extra_compile_args=extra_compile_flags, + language="c++", + ), +]) + + +def lib_def(sources, cflags, include_dirs, macros): + """Convenience factory to create the dictionary for a Setuptools library build.""" + return dict(sources=sources, cflags=cflags, include_dirs=include_dirs, + macros=macros) + + +sundials_inc_dir = include_dirs + [str(EXT_SRC / "sundials" / "sundials")] +libraries = [ + ("sundials", lib_def(sundials_sources, sundials_cflags, sundials_inc_dir, + sundials_macros)), + ("yaml-cpp", lib_def(yaml_cpp_sources, extra_compile_flags, include_dirs, [])), + ("fmtlib", lib_def(fmt_sources, extra_compile_flags, include_dirs, [])), + ("libexecstream", {"sources": libexecstream_sources, "include_dirs": include_dirs}) +] + +setup( + ext_modules=extensions, + libraries=libraries, + cmdclass={"install": InstallCommand, "develop": DevelopCommand}, +) diff --git a/src/base/global.cpp b/src/base/global.cpp index 14f8e2d25a..d0a0377640 100644 --- a/src/base/global.cpp +++ b/src/base/global.cpp @@ -169,6 +169,8 @@ doublereal actEnergyToSI(const std::string& unit) string canteraRoot() { + warn_deprecated("canteraRoot", + "Unused in Cantera. To be removed after Cantera 2.6"); char* ctroot = getenv("CANTERA_ROOT"); if (ctroot != 0) { return string(ctroot);