From 957157e1e8ba932508f225c6a0d177fc35e7a49a Mon Sep 17 00:00:00 2001 From: O01eg Date: Sun, 20 Apr 2025 09:24:16 +0400 Subject: [PATCH] Add support for multi-phase module initialization --- include/boost/python/module.hpp | 3 ++ include/boost/python/module_init.hpp | 54 ++++++++++++++++++++++++++++ src/module.cpp | 12 +++++++ test/fabscript | 5 +++ test/module_multi_phase.cpp | 15 ++++++++ test/module_multi_phase.py | 23 ++++++++++++ 6 files changed, 112 insertions(+) create mode 100644 test/module_multi_phase.cpp create mode 100644 test/module_multi_phase.py diff --git a/include/boost/python/module.hpp b/include/boost/python/module.hpp index 8ad69f5a34..f6f9fa871f 100644 --- a/include/boost/python/module.hpp +++ b/include/boost/python/module.hpp @@ -9,5 +9,8 @@ # include # define BOOST_PYTHON_MODULE BOOST_PYTHON_MODULE_INIT +# if PY_VERSION_HEX >= 0x03050000 +# define BOOST_PYTHON_MODULE_MULTI_PHASE BOOST_PYTHON_MODULE_MULTI_PHASE_INIT +# endif #endif // MODULE_DWA20011221_HPP diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 7fe5a1c8a2..9cc1190fc9 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -17,6 +17,12 @@ namespace boost { namespace python { namespace detail { BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)()); +# if PY_VERSION_HEX >= 0x03050000 + +BOOST_PYTHON_DECL int exec_module(PyObject*, void(*)()); + +# endif + #else BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); @@ -54,6 +60,46 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); } \ void BOOST_PP_CAT(init_module_, name)() +# if PY_VERSION_HEX >= 0x03050000 + +# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ + { \ + return boost::python::detail::exec_module( \ + module, BOOST_PP_CAT(init_module_, name) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + static PyModuleDef_Base initial_m_base = { \ + PyObject_HEAD_INIT(NULL) \ + 0, /* m_init */ \ + 0, /* m_index */ \ + 0 /* m_copy */ }; \ + static PyMethodDef initial_methods[] = { { 0, 0, 0, 0 } }; \ + \ + static PyModuleDef_Slot slots[] = { \ + {Py_mod_exec, reinterpret_cast(reinterpret_cast(BOOST_PP_CAT(exec_module_, name)))}, \ + {0, NULL} \ + }; \ + \ + static struct PyModuleDef moduledef = { \ + initial_m_base, \ + BOOST_PP_STRINGIZE(name), \ + 0, /* m_doc */ \ + 0, /* m_size */ \ + initial_methods, \ + slots, /* m_slots */ \ + 0, /* m_traverse */ \ + 0, /* m_clear */ \ + 0, /* m_free */ \ + }; \ + \ + return PyModuleDef_Init(&moduledef); \ + } \ + void BOOST_PP_CAT(init_module_, name)() + +# endif + # else # define _BOOST_PYTHON_MODULE_INIT(name) \ @@ -70,6 +116,14 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); void BOOST_PP_CAT(init_module_,name)(); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) +# if PY_VERSION_HEX >= 0x03050000 + +# define BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ + void BOOST_PP_CAT(init_module_,name)(); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) + +# endif + # endif #endif // MODULE_INIT_DWA20020722_HPP diff --git a/src/module.cpp b/src/module.cpp index 57675fa2df..e695a143de 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -45,6 +45,18 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, void(*init_funct init_function); } +# if PY_VERSION_HEX >= 0x03050000 + +BOOST_PYTHON_DECL int exec_module(PyObject* module, void(*init_function)()) +{ + PyObject* retval = init_module_in_scope( + module, + init_function); + return retval ? 0 : -1; +} + +# endif + #else namespace diff --git a/test/fabscript b/test/fabscript index d4d7ead83b..eb8f923029 100644 --- a/test/fabscript +++ b/test/fabscript @@ -173,4 +173,9 @@ for t in ['numpy/dtype', tests.append(extension_test(t, numpy=True, condition=set.define.contains('HAS_NUMPY'))) +python_version_major, python_version_minor = map(int, python.instance().version.split('.')[:2]) + +tests.append(extension_test("module_multi_phase", + condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) + default = report('report', tests, fail_on_failures=True) diff --git a/test/module_multi_phase.cpp b/test/module_multi_phase.cpp new file mode 100644 index 0000000000..dcbbe74305 --- /dev/null +++ b/test/module_multi_phase.cpp @@ -0,0 +1,15 @@ +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::python; + +BOOST_PYTHON_MODULE_MULTI_PHASE(module_multi_phase_ext) +{ + scope().attr("x") = "x"; +} + +#include "module_tail.cpp" diff --git a/test/module_multi_phase.py b/test/module_multi_phase.py new file mode 100644 index 0000000000..138f5d9820 --- /dev/null +++ b/test/module_multi_phase.py @@ -0,0 +1,23 @@ +# Distributed under the Boost +# Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +""" +>>> import module_multi_phase_ext +>>> module_multi_phase_ext.x +'x' +""" + +def run(args = None): + import sys + import doctest + + if args is not None: + sys.argv = args + return doctest.testmod(sys.modules.get(__name__)) + +if __name__ == '__main__': + print("running...") + import sys + status = run()[0] + if (status == 0): print("Done.") + sys.exit(status)