From 508967a1a332df01269738c1323186243b0b415e Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Tue, 31 Jan 2023 21:07:22 -0500 Subject: [PATCH] Check for match between shared lib and Python module When the Python module is linked to the Cantera shared library, it is possible that a user has multiple incompatible versions of the library installed. This checks that the Cantera version and Git commit are the same when importing the Python module, to avoid crashes or erroneous behavior due to ABI mismatches. --- include/cantera/base/global.h | 4 ++++ include/cantera/cython/utils_utils.h | 15 +++++++++++++-- interfaces/cython/SConscript | 15 +++++++++++---- interfaces/cython/cantera/_cantera.pyx | 2 +- interfaces/cython/cantera/_utils.pxd | 4 +++- interfaces/cython/cantera/_utils.pyx | 12 +++++++++++- src/base/global.cpp | 5 +++++ 7 files changed, 48 insertions(+), 9 deletions(-) diff --git a/include/cantera/base/global.h b/include/cantera/base/global.h index 407681c1065..fe8f183c154 100644 --- a/include/cantera/base/global.h +++ b/include/cantera/base/global.h @@ -101,6 +101,10 @@ void appdelete(); //! @copydoc Application::thread_complete void thread_complete(); +//! Returns the Cantera version. This function when needing to access the version from a +//! library, rather than the `CANTERA_VERSION` macro that is available at compile time. +string version(); + //! Returns the hash of the git commit from which Cantera was compiled, if known std::string gitCommit(); diff --git a/include/cantera/cython/utils_utils.h b/include/cantera/cython/utils_utils.h index b3d16aabf4c..1619cf1d0c9 100644 --- a/include/cantera/cython/utils_utils.h +++ b/include/cantera/cython/utils_utils.h @@ -25,12 +25,23 @@ static std::map mapped_PyWarnings = { {"User", PyExc_UserWarning} }; -// Wrappers for preprocessor defines -inline std::string get_cantera_version() +// Cantera version for Python module compilation +inline std::string get_cantera_version_py() { return std::string(CANTERA_VERSION); } +// Git commit for Python module compilation +inline std::string get_cantera_git_commit_py() +{ +#ifdef GIT_COMMIT + return GIT_COMMIT; +#else + return "unknown"; +#endif +} + +// Wrappers for preprocessor defines inline int get_sundials_version() { return CT_SUNDIALS_VERSION; diff --git a/interfaces/cython/SConscript b/interfaces/cython/SConscript index a541b344914..f71cff15543 100644 --- a/interfaces/cython/SConscript +++ b/interfaces/cython/SConscript @@ -33,14 +33,21 @@ if env["coverage"]: # Build the Python module cython_obj = [] for pyxfile in multi_glob(localenv, "cantera", "pyx"): - cythonized = localenv.Command( + if pyxfile.name == "_utils.pyx": + # Define GIT_COMMIT only in _utils.pyx to avoid unnecessary recompilations + cython_env = localenv.Clone() + cython_env.Append(CPPDEFINES={'GIT_COMMIT': '\\"{0}\\"'.format(env['git_commit'])}) + else: + cython_env = localenv + + cythonized = cython_env.Command( f"cantera/{pyxfile.name.replace('.pyx', '.cpp')}", pyxfile, f'''${{python_cmd}} -c "import Cython.Build; Cython.Build.cythonize(r'${{SOURCE}}', compiler_directives={directives!r})"''' ) - for pxd in multi_glob(localenv, "cantera", "pxd"): - localenv.Depends(cythonized, pxd) + for pxd in multi_glob(cython_env, "cantera", "pxd"): + cython_env.Depends(cythonized, pxd) - obj = localenv.SharedObject( + obj = cython_env.SharedObject( f"#build/temp-py/{pyxfile.name.split('.')[0]}", cythonized) cython_obj.append(obj) cython_obj.extend(env['python_ext_objects']) diff --git a/interfaces/cython/cantera/_cantera.pyx b/interfaces/cython/cantera/_cantera.pyx index 4503566d6c7..93ea5c8bb34 100644 --- a/interfaces/cython/cantera/_cantera.pyx +++ b/interfaces/cython/cantera/_cantera.pyx @@ -30,8 +30,8 @@ def bootstrap_cython_submodules(): bootstrap_cython_submodules() # Import the contents of the individual .pyx files -from ._onedim import * from ._utils import * +from ._onedim import * from .solutionbase import * from .delegator import * from .func1 import * diff --git a/interfaces/cython/cantera/_utils.pxd b/interfaces/cython/cantera/_utils.pxd index 453d1c4ca9d..81d3e1e9251 100644 --- a/interfaces/cython/cantera/_utils.pxd +++ b/interfaces/cython/cantera/_utils.pxd @@ -72,12 +72,14 @@ cdef extern from "cantera/base/global.h" namespace "Cantera": cdef void Cxx_suppress_thermo_warnings "Cantera::suppress_thermo_warnings" (cbool) cdef void Cxx_use_legacy_rate_constants "Cantera::use_legacy_rate_constants" (cbool) cdef string CxxGitCommit "Cantera::gitCommit" () + cdef string CxxVersion "Cantera::version" () cdef cbool CxxUsesHDF5 "Cantera::usesHDF5" () cdef cbool CxxDebugModeEnabled "Cantera::debugModeEnabled" () cdef extern from "cantera/cython/utils_utils.h": - cdef string get_cantera_version() + cdef string get_cantera_version_py() + cdef string get_cantera_git_commit_py() cdef int get_sundials_version() cdef cppclass CxxPythonLogger "PythonLogger": pass diff --git a/interfaces/cython/cantera/_utils.pyx b/interfaces/cython/cantera/_utils.pyx index 2ca1b293319..e1cdd259553 100644 --- a/interfaces/cython/cantera/_utils.pyx +++ b/interfaces/cython/cantera/_utils.pyx @@ -49,10 +49,20 @@ def get_data_directories(): __sundials_version__ = '.'.join(str(get_sundials_version())) -__version__ = pystr(get_cantera_version()) +__version__ = pystr(CxxVersion()) + +if __version__ != pystr(get_cantera_version_py()): + raise ImportError("Mismatch betweeen Cantera Python module version " + f"({pystr(get_cantera_version_py())}) and Cantera shared library " + f"version ({__version__})") __git_commit__ = pystr(CxxGitCommit()) +if __git_commit__ != pystr(get_cantera_git_commit_py()): + raise ImportError("Mismatch betweeen Cantera Python module Git commit " + f"({pystr(get_cantera_git_commit_py())}) and Cantera shared library " + f"git commit ({__git_commit__})") + _USE_SPARSE = False def debug_mode_enabled(): diff --git a/src/base/global.cpp b/src/base/global.cpp index 563326cd77c..6aac18468e9 100644 --- a/src/base/global.cpp +++ b/src/base/global.cpp @@ -122,6 +122,11 @@ void thread_complete() app()->thread_complete(); } +string version() +{ + return CANTERA_VERSION; +} + std::string gitCommit() { #ifdef GIT_COMMIT