From a40bb656ee0f26f470444a47ea26701a4baca2e5 Mon Sep 17 00:00:00 2001 From: Aditya Pillai Date: Mon, 12 May 2025 15:09:01 -0400 Subject: [PATCH 01/53] Use Py_REFCNT instead of ob_refcnt on Python 3.9 and above --- .../suite/indexing/detail/indexing_suite_detail.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/boost/python/suite/indexing/detail/indexing_suite_detail.hpp b/include/boost/python/suite/indexing/detail/indexing_suite_detail.hpp index eb8b81c0a3..d470e32d77 100644 --- a/include/boost/python/suite/indexing/detail/indexing_suite_detail.hpp +++ b/include/boost/python/suite/indexing/detail/indexing_suite_detail.hpp @@ -216,7 +216,13 @@ namespace boost { namespace python { namespace detail { { for (const_iterator i = proxies.begin(); i != proxies.end(); ++i) { - if ((*i)->ob_refcnt <= 0) + if ( +#if PY_VERSION_HEX < 0x03090000 + (*i)->ob_refcnt +#else + Py_REFCNT(*i) +#endif + <= 0) { PyErr_SetString(PyExc_RuntimeError, "Invariant: Proxy vector in an inconsistent state"); From 867f0dddfe89f066420382dc250d4406f720850e Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Thu, 23 Oct 2025 20:25:07 -0400 Subject: [PATCH 02/53] Fix windows header path. --- .github/workflows/test-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e7b02927b2..cc3d982484 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -42,8 +42,8 @@ jobs: - name: build shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include=${{ runner.workspace }}/vcpkg/installed/x64-windows/include -j4 + faber --builddir=build cxx.name=msvc --with-boost-include=${{ runner.workspace }}\vcpkg\installed\x64-windows\include -j4 - name: test shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include=${{ runner.workspace }}/vcpkg/installed/x64-windows/include -j4 test.report + faber --builddir=build cxx.name=msvc --with-boost-include=${{ runner.workspace }}\vcpkg\installed\x64-windows\include -j4 test.report From b4fb28e99a1ba5ed23d597d38e70e72e261a03eb Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Fri, 24 Oct 2025 06:40:33 +0900 Subject: [PATCH 03/53] ci: update GitHub Actions --- .github/workflows/deploy-documentation.yml | 8 ++++---- .github/workflows/test-osx.yml | 4 ++-- .github/workflows/test-ubuntu.yml | 2 +- .github/workflows/test-windows.yml | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy-documentation.yml b/.github/workflows/deploy-documentation.yml index 065945137b..3c5ffafb70 100644 --- a/.github/workflows/deploy-documentation.yml +++ b/.github/workflows/deploy-documentation.yml @@ -4,14 +4,14 @@ on: [push] jobs: deploy: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: setup run: | sudo apt-get update sudo apt-get install \ - libboost1.71-tools-dev \ + libboost-tools-dev \ python3 \ python3-numpy \ python3-sphinx \ @@ -29,7 +29,7 @@ jobs: echo "destination_dir=doc/develop/html" >> $GITHUB_ENV fi - name: deploy - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: build/doc/html diff --git a/.github/workflows/test-osx.yml b/.github/workflows/test-osx.yml index 2521c09a9b..b88b43e5dd 100644 --- a/.github/workflows/test-osx.yml +++ b/.github/workflows/test-osx.yml @@ -14,9 +14,9 @@ jobs: std: [c++11, c++14] # TODO: c++17 is failing ! steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: setup prerequisites diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index 74718b68a5..41185c0dc0 100644 --- a/.github/workflows/test-ubuntu.yml +++ b/.github/workflows/test-ubuntu.yml @@ -26,7 +26,7 @@ jobs: image: ${{ matrix.docker-img }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: build run: | diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index cc3d982484..b11f14b453 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -11,13 +11,13 @@ jobs: python-version: [3.7] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - - uses: microsoft/setup-msbuild@v1.1 + - uses: microsoft/setup-msbuild@v2 - name: setup boost prerequisites - uses: lukka/run-vcpkg@v6 + uses: lukka/run-vcpkg@v11 with: vcpkgGitCommitId: '88b1071e39f13b632644d9d953738d345a4ac055' vcpkgDirectory: '${{ runner.workspace }}/vcpkg' From cb95b611bbf708f9c92a3fe97a95b848dadb578f Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Fri, 24 Oct 2025 08:45:04 -0400 Subject: [PATCH 04/53] Downgrade run-vcpkg dependency to avoid regression. --- .github/workflows/test-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index b11f14b453..f9edc3ee86 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -17,7 +17,7 @@ jobs: python-version: ${{ matrix.python-version }} - uses: microsoft/setup-msbuild@v2 - name: setup boost prerequisites - uses: lukka/run-vcpkg@v11 + uses: lukka/run-vcpkg@v6 with: vcpkgGitCommitId: '88b1071e39f13b632644d9d953738d345a4ac055' vcpkgDirectory: '${{ runner.workspace }}/vcpkg' From 303299e6775aed26e9f6b29d5e4d382c558eb97e Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Fri, 24 Oct 2025 09:41:39 -0400 Subject: [PATCH 05/53] log commands --- .github/workflows/test-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f9edc3ee86..576e1f4415 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -42,7 +42,7 @@ jobs: - name: build shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include=${{ runner.workspace }}\vcpkg\installed\x64-windows\include -j4 + faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include=${{ runner.workspace }}\vcpkg\installed\x64-windows\include -j4 - name: test shell: cmd run: | From 16627261f10b7507a380b5487c25c145f0fb5576 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Mon, 31 Mar 2025 19:49:28 +0900 Subject: [PATCH 06/53] fix(test.pickle): fix for change in the return value of object.__reduce__() https://docs.python.org/3.11/library/pickle.html#object.__reduce__ fix #461 --- test/pickle1.py | 26 ++++++++++++++++++++++++-- test/pickle4.py | 26 ++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/test/pickle1.py b/test/pickle1.py index b8f4efd9b0..0df59a4b3a 100644 --- a/test/pickle1.py +++ b/test/pickle1.py @@ -9,8 +9,10 @@ 1 >>> pickle1_ext.world.__name__ 'world' - >>> pickle1_ext.world('Hello').__reduce__() + >>> pickle1_ext.world('Hello').__reduce__() # doctest: +PY310 (, ('Hello',)) + >>> pickle1_ext.world('Hello').__reduce__() # doctest: +PY311 + (, ('Hello',), None) >>> wd = pickle1_ext.world('California') >>> pstr = pickle.dumps(wd) >>> wl = pickle.loads(pstr) @@ -31,7 +33,27 @@ def run(args = None): if args is not None: sys.argv = args - return doctest.testmod(sys.modules.get(__name__)) + + # > https://docs.python.org/3.11/library/pickle.html#object.__reduce__ + # object.__reduce__() returns + # - python 3.10 or prior: a 2-element tuple + # - python 3.11 or later: a 3-element tuple (object's state added) + PY310 = doctest.register_optionflag("PY310") + PY311 = doctest.register_optionflag("PY311") + + class ConditionalChecker(doctest.OutputChecker): + def check_output(self, want, got, optionflags): + if (optionflags & PY311) and (sys.version_info[:2] < (3, 11)): + return True + if (optionflags & PY310) and (sys.version_info[:2] >= (3, 11)): + return True + return doctest.OutputChecker.check_output(self, want, got, optionflags) + + runner = doctest.DocTestRunner(ConditionalChecker()) + for test in doctest.DocTestFinder().find(sys.modules.get(__name__)): + runner.run(test) + + return doctest.TestResults(runner.failures, runner.tries) if __name__ == '__main__': print("running...") diff --git a/test/pickle4.py b/test/pickle4.py index be813bbb13..3cf4d7241f 100644 --- a/test/pickle4.py +++ b/test/pickle4.py @@ -12,8 +12,10 @@ 1 >>> pickle4_ext.world.__name__ 'world' - >>> pickle4_ext.world('Hello').__reduce__() + >>> pickle4_ext.world('Hello').__reduce__() # doctest: +PY310 (, ('Hello',)) + >>> pickle4_ext.world('Hello').__reduce__() # doctest: +PY311 + (, ('Hello',), None) >>> wd = pickle4_ext.world('California') >>> pstr = pickle.dumps(wd) >>> wl = pickle.loads(pstr) @@ -29,7 +31,27 @@ def run(args = None): if args is not None: sys.argv = args - return doctest.testmod(sys.modules.get(__name__)) + + # > https://docs.python.org/3.11/library/pickle.html#object.__reduce__ + # object.__reduce__() returns + # - python 3.10 or prior: a 2-element tuple + # - python 3.11 or later: a 3-element tuple (object's state added) + PY310 = doctest.register_optionflag("PY310") + PY311 = doctest.register_optionflag("PY311") + + class ConditionalChecker(doctest.OutputChecker): + def check_output(self, want, got, optionflags): + if (optionflags & PY311) and (sys.version_info[:2] < (3, 11)): + return True + if (optionflags & PY310) and (sys.version_info[:2] >= (3, 11)): + return True + return doctest.OutputChecker.check_output(self, want, got, optionflags) + + runner = doctest.DocTestRunner(ConditionalChecker()) + for test in doctest.DocTestFinder().find(sys.modules.get(__name__)): + runner.run(test) + + return doctest.TestResults(runner.failures, runner.tries) if __name__ == '__main__': print("running...") From aa458d2ca959bc93ff78e0d39eae9eb17e05cbf6 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Mon, 31 Mar 2025 19:54:55 +0900 Subject: [PATCH 07/53] fix(test.properties): use doctest.ELLIPSIS for traceback Since python 3.11 (PEP 657) traceback info is changed fix #460 --- test/properties.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/properties.py b/test/properties.py index 88374b4107..e95d59bef2 100644 --- a/test/properties.py +++ b/test/properties.py @@ -56,11 +56,10 @@ class instance count from object: 1 as expected you can't assign new value to read only property ->>> x1.value_r = 2 +>>> x1.value_r = 2 # doctest: +ELLIPSIS Traceback (most recent call last): - File "properties.py", line 49, in ? - x1.value_r = 2 -AttributeError: can't set attribute + ... +AttributeError: ... setting value_rw to 2. value_direct: >>> x1.value_rw = 2 From 2b6f667e987c81e91e7cf805e15ec19863f83ed3 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Wed, 22 Jan 2025 11:25:51 +0900 Subject: [PATCH 08/53] chore: remove meaningless comparison --- src/object/function.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/object/function.cpp b/src/object/function.cpp index 7b6ab9f08b..fec56768da 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -161,7 +161,6 @@ PyObject* function::call(PyObject* args, PyObject* keywords) const else { // build a new arg tuple, will adjust its size later - assert(max_arity <= static_cast(ssize_t_max)); inner_args = handle<>( PyTuple_New(static_cast(max_arity))); From 7fd39323acb89378882f6188ca2d3f63de199c08 Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Sat, 1 Nov 2025 22:30:52 -0400 Subject: [PATCH 09/53] Don't rely on Py_REFCNT to test upcast. --- test/upcast.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/upcast.cpp b/test/upcast.cpp index 255429f168..e005900410 100644 --- a/test/upcast.cpp +++ b/test/upcast.cpp @@ -13,7 +13,7 @@ int main() { PyTypeObject o; Y y; - BOOST_TEST(&Py_REFCNT(boost::python::upcast(&o)) == &Py_REFCNT(&o)); - BOOST_TEST(&Py_REFCNT(boost::python::upcast(&y)) == &Py_REFCNT(&y)); + BOOST_TEST(boost::python::upcast(&o) == reinterpret_cast(&o)); + BOOST_TEST(boost::python::upcast(&y) == &y); return boost::report_errors(); } From 608ec27c4d59800fec21c8d19b381a815f9c8e75 Mon Sep 17 00:00:00 2001 From: Tom Kent Date: Tue, 28 Oct 2025 19:01:46 -0500 Subject: [PATCH 10/53] Updated to recent compilers/boost --- .github/workflows/test-ubuntu.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index 41185c0dc0..a85c21ee96 100644 --- a/.github/workflows/test-ubuntu.yml +++ b/.github/workflows/test-ubuntu.yml @@ -18,9 +18,9 @@ jobs: # pre-reqs installed, see: # https://github.com/teeks99/boost-python-test-docker - cxx: clang++ - docker-img: teeks99/boost-python-test:clang-12_1.76.0 + docker-img: teeks99/boost-python-test:clang-21_1.89.0 - cxx: g++ - docker-img: teeks99/boost-python-test:gcc-10_1.76.0 + docker-img: teeks99/boost-python-test:gcc-15_1.89.0 container: image: ${{ matrix.docker-img }} From 5d7b9a064811392d9fe3f19ebbaef9cd32fdbaa7 Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Sun, 2 Nov 2025 09:33:34 -0500 Subject: [PATCH 11/53] Stop testing c++98 support --- .github/workflows/test-ubuntu.yml | 2 +- fabscript | 1 + include/boost/python/detail/is_auto_ptr.hpp | 2 ++ test/back_reference.cpp | 2 +- test/copy_ctor_mutates_rhs.cpp | 3 +-- test/fabscript | 8 ++++---- test/injected.cpp | 2 +- test/operators_wrapper.cpp | 4 ++-- test/select_holder.cpp | 12 ++++++------ test/wrapper_held_type.cpp | 8 ++++---- 10 files changed, 23 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index a85c21ee96..aceeb59334 100644 --- a/.github/workflows/test-ubuntu.yml +++ b/.github/workflows/test-ubuntu.yml @@ -11,7 +11,7 @@ jobs: matrix: python: [python, python3] cxx: [g++, clang++] - std: [c++98, c++11, c++14, c++17] + std: [c++11, c++14, c++17] include: # Add the appropriate docker image for each compiler. # The images from teeks99/boost-python-test already have boost::python diff --git a/fabscript b/fabscript index 8188779fd3..5a50615fc8 100644 --- a/fabscript +++ b/fabscript @@ -16,6 +16,7 @@ from faber.config.try_run import try_run features += include('include') features += define('BOOST_ALL_NO_LIB') # disable auto-linking +features += define('BOOST_NO_AUTO_PTR') boost_include = options.get_with('boost-include') if boost_include: features += include(boost_include) diff --git a/include/boost/python/detail/is_auto_ptr.hpp b/include/boost/python/detail/is_auto_ptr.hpp index 3b8198b8dd..36affcd215 100644 --- a/include/boost/python/detail/is_auto_ptr.hpp +++ b/include/boost/python/detail/is_auto_ptr.hpp @@ -8,6 +8,8 @@ # ifndef BOOST_NO_AUTO_PTR # include # include +# else +# include # endif namespace boost { namespace python { namespace detail { diff --git a/test/back_reference.cpp b/test/back_reference.cpp index 266ed29125..11e47b3321 100644 --- a/test/back_reference.cpp +++ b/test/back_reference.cpp @@ -99,7 +99,7 @@ BOOST_PYTHON_MODULE(back_reference_ext) .def("set", &Y::set) ; - class_ >("Z", init()) + class_ >("Z", init()) .def("value", &Z::value) .def("set", &Z::set) ; diff --git a/test/copy_ctor_mutates_rhs.cpp b/test/copy_ctor_mutates_rhs.cpp index 41eac495e4..be52c4f327 100644 --- a/test/copy_ctor_mutates_rhs.cpp +++ b/test/copy_ctor_mutates_rhs.cpp @@ -9,14 +9,13 @@ struct foo { - operator std::auto_ptr&() const; + operator std::shared_ptr&() const; }; int main() { using namespace boost::python::detail; BOOST_STATIC_ASSERT(!copy_ctor_mutates_rhs::value); - BOOST_STATIC_ASSERT(copy_ctor_mutates_rhs >::value); BOOST_STATIC_ASSERT(!copy_ctor_mutates_rhs::value); BOOST_STATIC_ASSERT(!copy_ctor_mutates_rhs::value); return 0; diff --git a/test/fabscript b/test/fabscript index d4d7ead83b..a002fb2bf0 100644 --- a/test/fabscript +++ b/test/fabscript @@ -118,10 +118,10 @@ for t in [('injected',), tests.append(extension_test('shared_ptr', condition=set.define.contains('HAS_CXX11'))) -tests.append(extension_test('polymorphism2_auto_ptr', - condition=set.define.contains('HAS_CXX11').not_())) -tests.append(extension_test('auto_ptr', - condition=set.define.contains('HAS_CXX11'))) +#tests.append(extension_test('polymorphism2_auto_ptr', +# condition=set.define.contains('HAS_CXX11').not_())) +#tests.append(extension_test('auto_ptr', +# condition=set.define.contains('HAS_CXX11'))) import_ = binary('import_', ['import_.cpp', src.bpl], features=features|python_libs) if platform.os == 'Windows': diff --git a/test/injected.cpp b/test/injected.cpp index 73e1e14baa..82db3e82e6 100644 --- a/test/injected.cpp +++ b/test/injected.cpp @@ -17,7 +17,7 @@ typedef test_class<> X; X* empty() { return new X(1000); } -std::auto_ptr sum(int a, int b) { return std::auto_ptr(new X(a+b)); } +std::shared_ptr sum(int a, int b) { return std::shared_ptr(new X(a+b)); } boost::shared_ptr product(int a, int b, int c) { diff --git a/test/operators_wrapper.cpp b/test/operators_wrapper.cpp index 12f30048d0..e62ead16f8 100644 --- a/test/operators_wrapper.cpp +++ b/test/operators_wrapper.cpp @@ -36,7 +36,7 @@ BOOST_PYTHON_MODULE( operators_wrapper_ext ) ; scope().attr("v") = vector(); - std::auto_ptr dp(new dvector); - register_ptr_to_python< std::auto_ptr >(); + std::shared_ptr dp(new dvector); + register_ptr_to_python< std::shared_ptr >(); scope().attr("d") = dp; } diff --git a/test/select_holder.cpp b/test/select_holder.cpp index 8650bd06a0..77aac67868 100644 --- a/test/select_holder.cpp +++ b/test/select_holder.cpp @@ -62,14 +62,14 @@ int test_main(int, char * []) assert_holder >(); - assert_holder - ,pointer_holder,Base> >(); + assert_holder + ,pointer_holder,Base> >(); - assert_holder - ,pointer_holder_back_reference,Base> >(); + assert_holder + ,pointer_holder_back_reference,Base> >(); - assert_holder - ,pointer_holder_back_reference,BR> > (); + assert_holder + ,pointer_holder_back_reference,BR> > (); return 0; } diff --git a/test/wrapper_held_type.cpp b/test/wrapper_held_type.cpp index e99422796e..ef494924b9 100644 --- a/test/wrapper_held_type.cpp +++ b/test/wrapper_held_type.cpp @@ -20,12 +20,12 @@ struct data } }; -std::auto_ptr create_data() +std::shared_ptr create_data() { - return std::auto_ptr( new data ); + return std::shared_ptr( new data ); } -void do_nothing( std::auto_ptr& ){} +void do_nothing( std::shared_ptr& ){} namespace bp = boost::python; @@ -59,7 +59,7 @@ struct data_wrapper : data, bp::wrapper< data > BOOST_PYTHON_MODULE(wrapper_held_type_ext) { - bp::class_< data_wrapper, std::auto_ptr< data > >( "data" ) + bp::class_< data_wrapper, std::shared_ptr< data > >( "data" ) .def( "id", &data::id, &::data_wrapper::default_id ); bp::def( "do_nothing", &do_nothing ); From 20de46cd0cf0038fa97574cde6a102433cd9f4f5 Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Sun, 2 Nov 2025 14:06:12 -0500 Subject: [PATCH 12/53] Update faber --- .github/workflows/test-ubuntu.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index aceeb59334..6f94c2d186 100644 --- a/.github/workflows/test-ubuntu.yml +++ b/.github/workflows/test-ubuntu.yml @@ -28,6 +28,10 @@ jobs: steps: - uses: actions/checkout@v5 + - name: setup prerequisites + run: | + # Warning: this is not necessarily the same Python version as the one configured above ! + python3 -m pip install -U faber --break-system-packages - name: build run: | ${{ matrix.python }} --version From cc873d968278445e3524eabecff8880569154add Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Sun, 2 Nov 2025 15:35:47 -0500 Subject: [PATCH 13/53] Fix documentation build error. --- doc/numpy/_templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/numpy/_templates/layout.html b/doc/numpy/_templates/layout.html index d85f075141..69e1a868c0 100644 --- a/doc/numpy/_templates/layout.html +++ b/doc/numpy/_templates/layout.html @@ -90,7 +90,7 @@

C++ Boost

+ alt="C++ Boost" src="{{ pathto('_static/bpl.png', 1) }}" border="0"> From 5f5f38fa8a510e43fc747422cfe7eab0c3daf403 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sat, 25 Oct 2025 03:58:46 +0900 Subject: [PATCH 14/53] fix: fix quotation --- test/shared_ptr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/shared_ptr.py b/test/shared_ptr.py index d250ae7eca..4ef88f78d8 100644 --- a/test/shared_ptr.py +++ b/test/shared_ptr.py @@ -38,7 +38,7 @@ 12 >>> try: modify(p) ... except TypeError: pass -... else: 'print(expected a TypeError)' +... else: print('expected a TypeError') >>> look(None) -1 >>> store(p) @@ -61,7 +61,7 @@ 13 >>> try: modify(z) ... except TypeError: pass -... else: 'print(expected a TypeError)' +... else: print('expected a TypeError') >>> Z.get() # should be None >>> store(z) @@ -84,7 +84,7 @@ 17 >>> try: modify(x) ... except TypeError: pass -... else: 'print(expected a TypeError)' +... else: print('expected a TypeError') >>> look(None) -1 >>> store(x) From 668bc7c106bf32aa64e05ab4f26d13a708971baf Mon Sep 17 00:00:00 2001 From: Anton Gladky Date: Tue, 28 Oct 2025 20:56:28 +0100 Subject: [PATCH 15/53] Include missing header boost/type_traits/is_unsigned.hpp During the Debian Packaging of new version it was found that this header is missing during the rebuild with GCC-15. --- include/boost/python/numpy/dtype.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/python/numpy/dtype.hpp b/include/boost/python/numpy/dtype.hpp index 4673745e57..9438d79fdc 100644 --- a/include/boost/python/numpy/dtype.hpp +++ b/include/boost/python/numpy/dtype.hpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace boost { namespace python { namespace numpy { From 97402f7925648ef433d0f1f979b9b422936dac31 Mon Sep 17 00:00:00 2001 From: Abhay Kumar Date: Fri, 6 Jun 2025 11:27:53 +0530 Subject: [PATCH 16/53] :bug: Fix broken link to Jamroot in example docs --- doc/tutorial.qbk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorial.qbk b/doc/tutorial.qbk index d7c0cfa93e..197470013e 100644 --- a/doc/tutorial.qbk +++ b/doc/tutorial.qbk @@ -117,7 +117,7 @@ platforms. The complete list of Bjam executables can be found [h2 Let's Jam!] __jam__ -[@../../../../example/tutorial/Jamroot Here] is our minimalist Jamroot +[@../example/Jamroot Here] is our minimalist Jamroot file. Simply copy the file and tweak [^use-project boost] to where your boost root directory is and you're OK. From cabb466057c53d1ff065384ff86c8a98719f3bc2 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 13 Oct 2025 20:59:57 -0700 Subject: [PATCH 17/53] Use strong reference APIs. For the free-threaded build, it is not safe use borrowed references. Another thread could deallocate the object and cause the reference to become invalid. Replace API calls that borrow references with strong reference APIs. --- src/dict.cpp | 8 ++++++++ src/object/function_doc_signature.cpp | 15 +++++++++++++-- src/wrapper.cpp | 20 ++++++++++++++------ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/dict.cpp b/src/dict.cpp index 77d840d455..296bc21e9c 100644 --- a/src/dict.cpp +++ b/src/dict.cpp @@ -68,8 +68,16 @@ object dict_base::get(object_cref k) const { if (check_exact(this)) { +#ifdef Py_GIL_DISABLED + PyObject* result; + if (PyDict_GetItemRef(this->ptr(),k.ptr(),&result) < 0) { + throw_error_already_set(); + } + return object(detail::new_reference(result ? result : Py_None)); +#else PyObject* result = PyDict_GetItem(this->ptr(),k.ptr()); return object(detail::borrowed_reference(result ? result : Py_None)); +#endif } else { diff --git a/src/object/function_doc_signature.cpp b/src/object/function_doc_signature.cpp index 18d458698d..76b620dcb9 100644 --- a/src/object/function_doc_signature.cpp +++ b/src/object/function_doc_signature.cpp @@ -135,7 +135,15 @@ namespace boost { namespace python { namespace objects { str name(get_qualname(py_type)); if ( py_type->tp_flags & Py_TPFLAGS_HEAPTYPE ) { // Qualify the type name if it is defined in a different module. - PyObject *type_module_name = PyDict_GetItemString(py_type->tp_dict, "__module__"); + PyObject *type_module_name; +#if PY_VERSION_HEX >= 0x030D0000 + if (PyDict_GetItemStringRef(py_type->tp_dict, "__module__", &type_module_name) < 0) { + throw_error_already_set(); + } +#else + type_module_name = PyDict_GetItemString(py_type->tp_dict, "__module__"); + Py_XINCREF(type_module_name); +#endif if ( type_module_name && PyObject_RichCompareBool( @@ -144,8 +152,11 @@ namespace boost { namespace python { namespace objects { Py_NE ) != 0 ) { - return str("%s.%s" % make_tuple(handle<>(borrowed(type_module_name)), name)); + str result = str("%s.%s" % make_tuple(handle<>(type_module_name), name)); + return result; } + // Clean up the strong reference if we didn't use it + Py_XDECREF(type_module_name); } return name; } else { diff --git a/src/wrapper.cpp b/src/wrapper.cpp index 8b1b884769..2b053d8311 100644 --- a/src/wrapper.cpp +++ b/src/wrapper.cpp @@ -21,20 +21,28 @@ namespace detail this->m_self, const_cast(name)))) ) { - PyObject* borrowed_f = 0; - + PyObject* class_f = 0; + if ( PyMethod_Check(m.get()) && PyMethod_GET_SELF(m.get()) == this->m_self && class_object->tp_dict != 0 ) { - borrowed_f = ::PyDict_GetItemString( +#if PY_VERSION_HEX >= 0x030D0000 + if (::PyDict_GetItemStringRef( + class_object->tp_dict, const_cast(name), &class_f) < 0) { + throw_error_already_set(); + } +#else + class_f = ::PyDict_GetItemString( class_object->tp_dict, const_cast(name)); - - + Py_XINCREF(class_f); +#endif } - if (borrowed_f != PyMethod_GET_FUNCTION(m.get())) + bool is_override = (class_f != PyMethod_GET_FUNCTION(m.get())); + Py_XDECREF(class_f); + if (is_override) return override(m); } } From 6f5f3b66074b27b44ddc5f1198003d0b01d31a52 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Thu, 30 Oct 2025 14:23:37 -0700 Subject: [PATCH 18/53] Add work-around to crash in ~object_base(). For the free-threaded build (and possibly the debug build), it is not safe to call Py_DECREF() if there is no valid Python thread-state. --- include/boost/python/object_core.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/boost/python/object_core.hpp b/include/boost/python/object_core.hpp index 16480d0d89..074360d415 100644 --- a/include/boost/python/object_core.hpp +++ b/include/boost/python/object_core.hpp @@ -419,6 +419,16 @@ inline api::object_base& api::object_base::operator=(api::object_base const& rhs inline api::object_base::~object_base() { +#ifdef Py_GIL_DISABLED + // This is a not very elegant fix for a problem that occurs with the + // free-threaded build of Python. If this is called when the interpreter + // has already been finalized, the thread-state can be null. Unlike the + // GIL-enabled build, Py_DECREF() requires a valid thread-state. This + // causes a memory leak, rather than crash, which seems preferable. + if (PyThreadState_GetUnchecked() == NULL) { + return; + } +#endif assert( Py_REFCNT(m_ptr) > 0 ); Py_DECREF(m_ptr); } From cfbefe893ca6254fd2377c4f6fcdacfd3b851680 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 13 Oct 2025 22:42:42 -0700 Subject: [PATCH 19/53] Use re-entrant mutex to protect global state. Add pymutex.hpp which implements a re-entrant mutex on top of Python's PyMutex. Add BOOST_PYTHON_LOCK_STATE() macro that uses RAII to lock mutable global state as required. --- include/boost/python/detail/pymutex.hpp | 103 ++++++++++++++++++++++++ src/converter/from_python.cpp | 7 +- src/converter/registry.cpp | 9 ++- src/converter/type_id.cpp | 6 +- src/errors.cpp | 22 ++++- src/object/inheritance.cpp | 6 ++ 6 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 include/boost/python/detail/pymutex.hpp diff --git a/include/boost/python/detail/pymutex.hpp b/include/boost/python/detail/pymutex.hpp new file mode 100644 index 0000000000..2d2e2d6266 --- /dev/null +++ b/include/boost/python/detail/pymutex.hpp @@ -0,0 +1,103 @@ +// Copyright 2025 Boost.Python Contributors +// 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) + +#ifndef BOOST_PYTHON_DETAIL_PYMUTEX_HPP +#define BOOST_PYTHON_DETAIL_PYMUTEX_HPP + +#include +#ifdef Py_GIL_DISABLED +// needed for pymutex wrapper +#include +#include +#endif + +namespace boost { namespace python { namespace detail { + +#ifdef Py_GIL_DISABLED + +// Re-entrant wrapper around PyMutex for free-threaded Python +// Similar to _PyRecursiveMutex or threading.RLock +class pymutex { + PyMutex m_mutex; + std::atomic m_owner; + std::size_t m_level; + +public: + pymutex() : m_mutex({}), m_owner(0), m_level(0) {} + + // Non-copyable, non-movable + pymutex(const pymutex&) = delete; + pymutex& operator=(const pymutex&) = delete; + + void lock() { + unsigned long thread = PyThread_get_thread_ident(); + if (m_owner.load(std::memory_order_relaxed) == thread) { + m_level++; + return; + } + PyMutex_Lock(&m_mutex); + m_owner.store(thread, std::memory_order_relaxed); + // m_level should be 0 when we acquire the lock + } + + void unlock() { + unsigned long thread = PyThread_get_thread_ident(); + // Verify current thread owns the lock + if (m_owner.load(std::memory_order_relaxed) != thread) { + // This should never happen - programming error + return; + } + if (m_level > 0) { + m_level--; + return; + } + m_owner.store(0, std::memory_order_relaxed); + PyMutex_Unlock(&m_mutex); + } + + bool is_locked_by_current_thread() const { + unsigned long thread = PyThread_get_thread_ident(); + return m_owner.load(std::memory_order_relaxed) == thread; + } +}; + + +// RAII lock guard for pymutex +class pymutex_guard { + pymutex& m_mutex; + +public: + explicit pymutex_guard(pymutex& mutex) : m_mutex(mutex) { + m_mutex.lock(); + } + + ~pymutex_guard() { + m_mutex.unlock(); + } + + // Non-copyable, non-movable + pymutex_guard(const pymutex_guard&) = delete; + pymutex_guard& operator=(const pymutex_guard&) = delete; +}; + +// Global mutex for protecting all Boost.Python internal state +// Similar to pybind11's internals.mutex +BOOST_PYTHON_DECL pymutex& get_global_mutex(); + +// Macro for acquiring the global lock +// Similar to pybind11's PYBIND11_LOCK_INTERNALS +#define BOOST_PYTHON_LOCK_STATE() \ + ::boost::python::detail::pymutex_guard lock(::boost::python::detail::get_global_mutex()) + +#else + +// No-op macro when not in free-threaded mode +#define BOOST_PYTHON_LOCK_STATE() + +#endif // Py_GIL_DISABLED + +}}} // namespace boost::python::detail + +#endif // BOOST_PYTHON_DETAIL_PYMUTEX_HPP diff --git a/src/converter/from_python.cpp b/src/converter/from_python.cpp index f3989ba77f..53a149fa72 100644 --- a/src/converter/from_python.cpp +++ b/src/converter/from_python.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -145,6 +146,8 @@ namespace inline bool visit(rvalue_from_python_chain const* chain) { + BOOST_PYTHON_LOCK_STATE(); + visited_t::iterator const p = std::lower_bound(visited.begin(), visited.end(), chain); if (p != visited.end() && *p == chain) return false; @@ -157,9 +160,11 @@ namespace { unvisit(rvalue_from_python_chain const* chain) : chain(chain) {} - + ~unvisit() { + BOOST_PYTHON_LOCK_STATE(); + visited_t::iterator const p = std::lower_bound(visited.begin(), visited.end(), chain); assert(p != visited.end()); visited.erase(p); diff --git a/src/converter/registry.cpp b/src/converter/registry.cpp index aa20c3f685..1b23dbef48 100644 --- a/src/converter/registry.cpp +++ b/src/converter/registry.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -112,9 +113,9 @@ registration::~registration() namespace // { typedef registration entry; - + typedef std::set registry_t; - + #ifndef BOOST_PYTHON_CONVERTER_REGISTRY_APPLE_MACH_WORKAROUND registry_t& entries() { @@ -181,6 +182,8 @@ namespace // entry* get(type_info type, bool is_shared_ptr = false) { + BOOST_PYTHON_LOCK_STATE(); + # ifdef BOOST_PYTHON_TRACE_REGISTRY registry_t::iterator p = entries().find(entry(type)); @@ -293,6 +296,8 @@ namespace registry registration const* query(type_info type) { + BOOST_PYTHON_LOCK_STATE(); + registry_t::iterator p = entries().find(entry(type)); # ifdef BOOST_PYTHON_TRACE_REGISTRY std::cout << "querying " << type diff --git a/src/converter/type_id.cpp b/src/converter/type_id.cpp index c6a8bf7a04..fafb13619c 100644 --- a/src/converter/type_id.cpp +++ b/src/converter/type_id.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -81,7 +82,7 @@ namespace { free_mem(char*p) : p(p) {} - + ~free_mem() { std::free(p); @@ -92,6 +93,7 @@ namespace bool cxxabi_cxa_demangle_is_broken() { + BOOST_PYTHON_LOCK_STATE(); static bool was_tested = false; static bool is_broken = false; if (!was_tested) { @@ -109,6 +111,8 @@ namespace detail { BOOST_PYTHON_DECL char const* gcc_demangle(char const* mangled) { + BOOST_PYTHON_LOCK_STATE(); + typedef std::vector< std::pair > mangling_map; diff --git a/src/errors.cpp b/src/errors.cpp index 34ea22f43e..7f6b1880d5 100644 --- a/src/errors.cpp +++ b/src/errors.cpp @@ -10,9 +10,21 @@ #include #include #include +#include namespace boost { namespace python { +#ifdef Py_GIL_DISABLED +namespace detail { + // Global mutex for protecting all Boost.Python internal state + pymutex& get_global_mutex() + { + static pymutex mutex; + return mutex; + } +} +#endif + error_already_set::~error_already_set() {} // IMPORTANT: this function may only be called from within a catch block! @@ -20,8 +32,13 @@ BOOST_PYTHON_DECL bool handle_exception_impl(function0 f) { try { - if (detail::exception_handler::chain) - return detail::exception_handler::chain->handle(f); + detail::exception_handler* handler_chain = nullptr; + { + BOOST_PYTHON_LOCK_STATE(); + handler_chain = detail::exception_handler::chain; + } + if (handler_chain) + return handler_chain->handle(f); f(); return false; } @@ -80,6 +97,7 @@ exception_handler::exception_handler(handler_function const& impl) : m_impl(impl) , m_next(0) { + BOOST_PYTHON_LOCK_STATE(); if (chain != 0) tail->m_next = this; else diff --git a/src/object/inheritance.cpp b/src/object/inheritance.cpp index a7b3156e41..44062875a4 100644 --- a/src/object/inheritance.cpp +++ b/src/object/inheritance.cpp @@ -4,6 +4,7 @@ // http://www.boost.org/LICENSE_1_0.txt) #include #include +#include #include #if _MSC_FULL_VER >= 13102171 && _MSC_FULL_VER <= 13102179 # include @@ -390,6 +391,8 @@ namespace inline void* convert_type(void* const p, class_id src_t, class_id dst_t, bool polymorphic) { + BOOST_PYTHON_LOCK_STATE(); + // Quickly rule out unregistered types index_entry* src_p = seek_type(src_t); if (src_p == 0) @@ -452,6 +455,8 @@ BOOST_PYTHON_DECL void* find_static_type(void* p, class_id src_t, class_id dst_t BOOST_PYTHON_DECL void add_cast( class_id src_t, class_id dst_t, cast_function cast, bool is_downcast) { + BOOST_PYTHON_LOCK_STATE(); + // adding an edge will invalidate any record of unreachability in // the cache. static std::size_t expected_cache_len = 0; @@ -490,6 +495,7 @@ BOOST_PYTHON_DECL void add_cast( BOOST_PYTHON_DECL void register_dynamic_id_aux( class_id static_id, dynamic_id_function get_dynamic_id) { + BOOST_PYTHON_LOCK_STATE(); tuples::get(*demand_type(static_id)) = get_dynamic_id; } From fc68878e02bb2cb9a0e107a24daf890b5ffc4477 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 13 Oct 2025 20:59:37 -0700 Subject: [PATCH 20/53] Set the Py_MOD_GIL_NOT_USED flag on modules. This indicates that the free-threaded build of Python can keep the GIL disabled when the module is loaded. Without this module flag, importing the module will cause the GIL to be re-enabled. A warning is emitted if this happens. --- src/module.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/module.cpp b/src/module.cpp index 57675fa2df..707e433941 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -40,8 +40,14 @@ BOOST_PYTHON_DECL void scope_setattr_doc(char const* name, object const& x, char BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, void(*init_function)()) { + PyObject *mod = PyModule_Create(&moduledef); +#ifdef Py_GIL_DISABLED + if (mod != NULL) { + PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); + } +#endif return init_module_in_scope( - PyModule_Create(&moduledef), + mod, init_function); } From 501356431631b3bcb10dd0a9336a5a05d2171889 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Thu, 6 Nov 2025 15:45:22 -0800 Subject: [PATCH 21/53] Add "nogil" option for BOOST_PYTHON_MODULE_INIT. Implement optional arguments for BOOST_PYTHON_MODULE_INIT and allow the boost::python::mod_gil_not_used() option. This sets the Py_MOD_GIL_NOT_USED flag for the extension module. To define a module that supports free-threaded Python, define it like this: BOOST_PYTHON_MODULE(my_module, boost::python::mod_gil_not_used()) { ... } --- include/boost/python/module_init.hpp | 75 ++++++++++++++++++++++++++-- src/module.cpp | 5 +- test/fabscript | 1 + test/module_nogil.cpp | 25 ++++++++++ test/module_nogil.py | 29 +++++++++++ 5 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 test/module_nogil.cpp create mode 100644 test/module_nogil.py diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 7fe5a1c8a2..390db82cf4 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -11,11 +11,41 @@ # ifndef BOOST_PYTHON_MODULE_INIT -namespace boost { namespace python { namespace detail { +namespace boost { namespace python { + +#ifdef HAS_CXX11 +// Use to activate the Py_MOD_GIL_NOT_USED flag. +class mod_gil_not_used { +public: + explicit mod_gil_not_used(bool flag = true) : flag_(flag) {} + bool flag() const { return flag_; } + +private: + bool flag_; +}; + +namespace detail { + +inline bool gil_not_used_option() { return false; } +template +bool gil_not_used_option(F &&, O &&...o); +template +inline bool gil_not_used_option(mod_gil_not_used f, O &&...o) { + return f.flag() || gil_not_used_option(o...); +} +template +inline bool gil_not_used_option(F &&, O &&...o) { + return gil_not_used_option(o...); +} + +} +#endif // HAS_CXX11 + +namespace detail { # if PY_VERSION_HEX >= 0x03000000 -BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)()); +BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_used = false); #else @@ -27,7 +57,37 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); # if PY_VERSION_HEX >= 0x03000000 -# define _BOOST_PYTHON_MODULE_INIT(name) \ +# ifdef HAS_CXX11 +# define _BOOST_PYTHON_MODULE_INIT(name, ...) \ + 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 struct PyModuleDef moduledef = { \ + initial_m_base, \ + BOOST_PP_STRINGIZE(name), \ + 0, /* m_doc */ \ + -1, /* m_size */ \ + initial_methods, \ + 0, /* m_reload */ \ + 0, /* m_traverse */ \ + 0, /* m_clear */ \ + 0, /* m_free */ \ + }; \ + \ + return boost::python::detail::init_module( \ + moduledef, BOOST_PP_CAT(init_module_, name), \ + boost::python::detail::gil_not_used_option(__VA_ARGS__) ); \ + } \ + void BOOST_PP_CAT(init_module_, name)() + +# else // !HAS_CXX11 +# define _BOOST_PYTHON_MODULE_INIT(name) \ PyObject* BOOST_PP_CAT(PyInit_, name)() \ { \ static PyModuleDef_Base initial_m_base = { \ @@ -53,6 +113,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); moduledef, BOOST_PP_CAT(init_module_, name) ); \ } \ void BOOST_PP_CAT(init_module_, name)() +# endif // HAS_CXX11 # else @@ -66,9 +127,15 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); # endif -# define BOOST_PYTHON_MODULE_INIT(name) \ +# if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x03000000) +# define BOOST_PYTHON_MODULE_INIT(name, ...) \ + void BOOST_PP_CAT(init_module_,name)(); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name, __VA_ARGS__) +# else +# define BOOST_PYTHON_MODULE_INIT(name) \ void BOOST_PP_CAT(init_module_,name)(); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) +# endif // HAS_CXX11 && Python 3 # endif diff --git a/src/module.cpp b/src/module.cpp index 707e433941..c32f4187bc 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -38,11 +38,12 @@ BOOST_PYTHON_DECL void scope_setattr_doc(char const* name, object const& x, char #if PY_VERSION_HEX >= 0x03000000 -BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, void(*init_function)()) +BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, + void(*init_function)(), bool gil_not_used) { PyObject *mod = PyModule_Create(&moduledef); #ifdef Py_GIL_DISABLED - if (mod != NULL) { + if (mod != NULL && gil_not_used) { PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); } #endif diff --git a/test/fabscript b/test/fabscript index a002fb2bf0..7cf22f9c09 100644 --- a/test/fabscript +++ b/test/fabscript @@ -68,6 +68,7 @@ for t in [('injected',), ('raw_ctor',), ('exception_translator',), ('module_init_exception',), + ('module_nogil',), ('test_enum', ['enum_ext']), ('test_cltree', ['cltree']), ('newtest', ['m1', 'm2']), diff --git a/test/module_nogil.cpp b/test/module_nogil.cpp new file mode 100644 index 0000000000..331a73cf31 --- /dev/null +++ b/test/module_nogil.cpp @@ -0,0 +1,25 @@ +// Test for BOOST_PYTHON_MODULE with optional mod_gil_not_used argument + +#include +#include + +// Simple function to export +int get_value() { + return 1234; +} + +#if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x03000000) +// C++11 build with Python 3: test with mod_gil_not_used option +BOOST_PYTHON_MODULE(module_nogil_ext, boost::python::mod_gil_not_used()) +{ + using namespace boost::python; + def("get_value", get_value); +} +#else +// C++98 build or Python 2: test without optional arguments +BOOST_PYTHON_MODULE(module_nogil_ext) +{ + using namespace boost::python; + def("get_value", get_value); +} +#endif diff --git a/test/module_nogil.py b/test/module_nogil.py new file mode 100644 index 0000000000..c035436014 --- /dev/null +++ b/test/module_nogil.py @@ -0,0 +1,29 @@ +""" +>>> from module_nogil_ext import * +>>> get_value() +1234 +>>> import sys, sysconfig +>>> Py_GIL_DISABLED = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) +>>> if Py_GIL_DISABLED and sys._is_gil_enabled(): +... print('GIL is enabled and should not be') +... else: +... print('okay') +okay +""" + +from __future__ import print_function + +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) From e89f86b74f7cdd5682c1ea4e45a809471a11d7f0 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Tue, 11 Nov 2025 10:04:47 -0800 Subject: [PATCH 22/53] Update Linux CI scripts, more Python versions. Update scripts to use actions/setup-python to install different Python versions. Add run-faber.sh and get-py-env.py scripts. Add test-ubuntu-py-ver.yml CI script to test with different Python versions. --- .github/get-py-env.py | 65 +++++++++++++++++++++++++++ .github/run-faber.sh | 46 +++++++++++++++++++ .github/workflows/test-ubuntu-py2.yml | 58 ++++++++++++++++++++++++ .github/workflows/test-ubuntu.yml | 58 +++++++++++------------- 4 files changed, 194 insertions(+), 33 deletions(-) create mode 100755 .github/get-py-env.py create mode 100755 .github/run-faber.sh create mode 100644 .github/workflows/test-ubuntu-py2.yml diff --git a/.github/get-py-env.py b/.github/get-py-env.py new file mode 100755 index 0000000000..a6c41460d8 --- /dev/null +++ b/.github/get-py-env.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# Determine info about the Python install and write shell code to stdout, to +# set env variables. This will set the variables PY_LDFLAGS, PY_CFLAGS and +# PY_INC_PATH. +# +# The python3-config tool is used as the source of this info. In theory we +# could use sysconfig as well but the setup-python action from github appears +# to patch python3-config but not patch the sysconfig info. +# +# Usage: +# eval $(python3 get-py-env.py) + +import os +import re +import subprocess + + +def get_output(cmd): + rv = subprocess.run( + cmd, + capture_output=True, # Capture stdout and stderr + text=True, # Decode output as text (UTF-8) + check=True, # Raise an error if the command fails + ) + return rv.stdout + + +def extract_flags(cmd, prefix): + flags = [] + for part in get_output(cmd).split(): + part = part.strip() + if part.startswith(prefix): + flags.append(part) + return ' '.join(flags) + + +def find_python_h(): + """Find the include path that has Python.h contained inside. + We could use INCLUDEPY from sysconfig but github patches + python3-config but not the sysconfig info (after moving the + install). + """ + c_flags = extract_flags(['python3-config', '--cflags'], '-I') + for part in c_flags.split(): + m = re.search(r'-I(\S+)', part) + if not m: + continue + inc_path = m.group(1) + if os.path.exists(os.path.join(inc_path, 'Python.h')): + return inc_path + raise SystemExit('cannot find Python.h') + + +def main(): + ld_flags = extract_flags(['python3-config', '--ldflags'], '-L') + c_flags = extract_flags(['python3-config', '--cflags'], '-I') + include_path = find_python_h() + print(f'PY_LDFLAGS="{ld_flags}"') + print(f'PY_CFLAGS="{c_flags}"') + print(f'PY_INC_PATH="{include_path}"') + + +if __name__ == '__main__': + main() diff --git a/.github/run-faber.sh b/.github/run-faber.sh new file mode 100755 index 0000000000..5cb78be6dd --- /dev/null +++ b/.github/run-faber.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +set -eu + +echo "cxx version: $CXX $($CXX --version)" +echo "cxx std: $CXX_STD" +echo "python3 path: $(which python3)" +echo "python3 version: $(python3 --version)" + +if ! which faber > /dev/null; then + echo "Installing faber..." + python3 -m pip install --upgrade pip + python3 -m pip install -U faber +fi +echo "faber version: $(faber -v)" + +# find and set PY_LDFLAGS and PY_INC_PATH +eval $(python3 .github/get-py-env.py) + +echo "PY_INC_PATH=$PY_INC_PATH" +echo "PY_LDFLAGS=$PY_LDFLAGS" + +case $(python3-config --abiflags) in + *t*) + # When running with free-threaded, we always want to disable the GIL + # even for extensions without the mod_gil_not_used() flag + export PYTHON_GIL=0 + ;; +esac + +# this could be set by LD_LIBRARY_PATH but faber overrides it +prefix=$(python3-config --prefix) +echo "${prefix}/lib" > /etc/ld.so.conf.d/boost-ci.conf && ldconfig + +sed -e "s/\$PYTHON/python3/g" .ci/faber > $HOME/.faber + +faber \ + --with-boost-include=${BOOST_PY_DEPS} \ + --builddir=build \ + cxx.name="${CXX}" \ + cxxflags="-std=${CXX_STD}" \ + cppflags="-std=${CXX_STD}" \ + include="${PY_INC_PATH}" \ + ldflags="${PY_LDFLAGS}" \ + -j`nproc` \ + "$@" diff --git a/.github/workflows/test-ubuntu-py2.yml b/.github/workflows/test-ubuntu-py2.yml new file mode 100644 index 0000000000..801664c105 --- /dev/null +++ b/.github/workflows/test-ubuntu-py2.yml @@ -0,0 +1,58 @@ +name: Test Ubuntu, Python 2.x + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python: [python] + cxx: [g++] + std: [c++11] + include: + # Add the appropriate docker image for each compiler. + # The images from teeks99/boost-python-test already have boost::python + # pre-reqs installed, see: + # https://github.com/teeks99/boost-python-test-docker + - cxx: g++ + docker-img: teeks99/boost-python-test:gcc-15_1.89.0 + + container: + image: ${{ matrix.docker-img }} + + steps: + - uses: actions/checkout@v5 + + - name: setup prerequisites + run: | + # Warning: this is not necessarily the same Python version as the one configured above ! + python3 -m pip install -U faber --break-system-packages + - name: build + run: | + ${{ matrix.python }} --version + ${{ matrix.cxx }} --version + faber -v + sed -e "s/\$PYTHON/${{ matrix.python }}/g" .ci/faber > ~/.faber + faber \ + --with-boost-include=${BOOST_PY_DEPS} \ + --builddir=build \ + cxx.name=${{ matrix.cxx }} \ + cxxflags=-std=${{ matrix.std }} \ + cppflags=-std=${{ matrix.std }} \ + -j`nproc` + - name: test + run: | + faber \ + --with-boost-include=${BOOST_PY_DEPS} \ + --builddir=build \ + cxx.name=${{ matrix.cxx }} \ + cxxflags=-std=${{ matrix.std }} \ + cppflags=-std=${{ matrix.std }} \ + -j`nproc` \ + test.report diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index 6f94c2d186..abb0f2d59a 100644 --- a/.github/workflows/test-ubuntu.yml +++ b/.github/workflows/test-ubuntu.yml @@ -1,6 +1,10 @@ +# Test on Ubuntu with various compiler and language standard versions. name: Test Ubuntu -on: [push, pull_request] +on: + push: + pull_request: + workflow_dispatch: jobs: build: @@ -9,49 +13,37 @@ jobs: strategy: fail-fast: false matrix: - python: [python, python3] + python-version: ['3.14'] cxx: [g++, clang++] std: [c++11, c++14, c++17] include: - # Add the appropriate docker image for each compiler. - # The images from teeks99/boost-python-test already have boost::python - # pre-reqs installed, see: - # https://github.com/teeks99/boost-python-test-docker - - cxx: clang++ - docker-img: teeks99/boost-python-test:clang-21_1.89.0 - - cxx: g++ - docker-img: teeks99/boost-python-test:gcc-15_1.89.0 + # Also test with free-threaded build of Python + - python-version: '3.14t' + cxx: clang++ + std: c++17 container: - image: ${{ matrix.docker-img }} + # Add the appropriate docker image for the compiler. + # The images from teeks99/boost-python-test already have boost::python + # pre-reqs installed, see: + # https://github.com/teeks99/boost-python-test-docker + image: ${{ matrix.cxx == 'g++' && + 'teeks99/boost-python-test:gcc-15_1.89.0' || + 'teeks99/boost-python-test:clang-21_1.89.0' }} steps: - uses: actions/checkout@v5 - + - name: setup python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} - name: setup prerequisites run: | - # Warning: this is not necessarily the same Python version as the one configured above ! - python3 -m pip install -U faber --break-system-packages + echo "CXX=${{ matrix.cxx }}" >> "$GITHUB_ENV" + echo "CXX_STD=${{ matrix.std }}" >> "$GITHUB_ENV" - name: build run: | - ${{ matrix.python }} --version - ${{ matrix.cxx }} --version - faber -v - sed -e "s/\$PYTHON/${{ matrix.python }}/g" .ci/faber > ~/.faber - faber \ - --with-boost-include=${BOOST_PY_DEPS} \ - --builddir=build \ - cxx.name=${{ matrix.cxx }} \ - cxxflags=-std=${{ matrix.std }} \ - cppflags=-std=${{ matrix.std }} \ - -j`nproc` + .github/run-faber.sh - name: test run: | - faber \ - --with-boost-include=${BOOST_PY_DEPS} \ - --builddir=build \ - cxx.name=${{ matrix.cxx }} \ - cxxflags=-std=${{ matrix.std }} \ - cppflags=-std=${{ matrix.std }} \ - -j`nproc` \ - test.report + .github/run-faber.sh test.report From 32da86df269fb8f7ef777ddd8c3424d854e94455 Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Tue, 2 Dec 2025 08:38:34 -0500 Subject: [PATCH 23/53] Improve test coverage. --- .github/workflows/test-ubuntu-py2.yml | 58 --------------------------- .github/workflows/test-ubuntu.yml | 49 +++++++++++++++++++++- 2 files changed, 47 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/test-ubuntu-py2.yml diff --git a/.github/workflows/test-ubuntu-py2.yml b/.github/workflows/test-ubuntu-py2.yml deleted file mode 100644 index 801664c105..0000000000 --- a/.github/workflows/test-ubuntu-py2.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Test Ubuntu, Python 2.x - -on: - push: - pull_request: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - python: [python] - cxx: [g++] - std: [c++11] - include: - # Add the appropriate docker image for each compiler. - # The images from teeks99/boost-python-test already have boost::python - # pre-reqs installed, see: - # https://github.com/teeks99/boost-python-test-docker - - cxx: g++ - docker-img: teeks99/boost-python-test:gcc-15_1.89.0 - - container: - image: ${{ matrix.docker-img }} - - steps: - - uses: actions/checkout@v5 - - - name: setup prerequisites - run: | - # Warning: this is not necessarily the same Python version as the one configured above ! - python3 -m pip install -U faber --break-system-packages - - name: build - run: | - ${{ matrix.python }} --version - ${{ matrix.cxx }} --version - faber -v - sed -e "s/\$PYTHON/${{ matrix.python }}/g" .ci/faber > ~/.faber - faber \ - --with-boost-include=${BOOST_PY_DEPS} \ - --builddir=build \ - cxx.name=${{ matrix.cxx }} \ - cxxflags=-std=${{ matrix.std }} \ - cppflags=-std=${{ matrix.std }} \ - -j`nproc` - - name: test - run: | - faber \ - --with-boost-include=${BOOST_PY_DEPS} \ - --builddir=build \ - cxx.name=${{ matrix.cxx }} \ - cxxflags=-std=${{ matrix.std }} \ - cppflags=-std=${{ matrix.std }} \ - -j`nproc` \ - test.report diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index abb0f2d59a..31637de5ef 100644 --- a/.github/workflows/test-ubuntu.yml +++ b/.github/workflows/test-ubuntu.yml @@ -17,6 +17,21 @@ jobs: cxx: [g++, clang++] std: [c++11, c++14, c++17] include: + - python-version: '2.7' + cxx: g++ + std: c++11 + - python-version: '3.10' + cxx: g++ + std: c++17 + - python-version: '3.11' + cxx: g++ + std: c++17 + - python-version: '3.12' + cxx: g++ + std: c++17 + - python-version: '3.13' + cxx: g++ + std: c++17 # Also test with free-threaded build of Python - python-version: '3.14t' cxx: clang++ @@ -34,16 +49,46 @@ jobs: steps: - uses: actions/checkout@v5 - name: setup python + if: "${{ matrix.python-version != '2.7' }}" uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: setup prerequisites run: | + # Warning: this is not necessarily the same Python version as the one configured above ! + python3 -m pip install -U faber --break-system-packages echo "CXX=${{ matrix.cxx }}" >> "$GITHUB_ENV" echo "CXX_STD=${{ matrix.std }}" >> "$GITHUB_ENV" - - name: build + - name: build-py2 + if: "${{ matrix.python-version == '2.7' }}" + run: | + python --version + ${{ matrix.cxx }} --version + faber -v + sed -e "s/\$PYTHON/python/g" .ci/faber > ~/.faber + faber \ + --with-boost-include=${BOOST_PY_DEPS} \ + --builddir=build \ + cxx.name=${{ matrix.cxx }} \ + cxxflags=-std=${{ matrix.std }} \ + cppflags=-std=${{ matrix.std }} \ + -j`nproc` + - name: build-py3 + if: "${{ matrix.python-version != '2.7' }}" run: | .github/run-faber.sh - - name: test + - name: test-py2 + if: "${{ matrix.python-version == '2.7' }}" + run: | + faber \ + --with-boost-include=${BOOST_PY_DEPS} \ + --builddir=build \ + cxx.name=${{ matrix.cxx }} \ + cxxflags=-std=${{ matrix.std }} \ + cppflags=-std=${{ matrix.std }} \ + -j`nproc` \ + test.report + - name: test-py3 + if: "${{ matrix.python-version != '2.7' }}" run: | .github/run-faber.sh test.report From 9c887a1331973db4c92852903b65216c09f01fe6 Mon Sep 17 00:00:00 2001 From: Andrey Semashev Date: Thu, 22 Jan 2026 22:59:58 +0300 Subject: [PATCH 24/53] Remove dependencies on Boost.StaticAssert. Boost.StaticAssert has been merged into Boost.Config, so remove the dependency. --- CMakeLists.txt | 1 - build.jam | 1 - test/Jamfile | 1 - 3 files changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 299ef84e9c..71bad15520 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,6 @@ target_link_libraries(${_boost_python} Boost::numeric_conversion Boost::preprocessor Boost::smart_ptr - Boost::static_assert Boost::tuple Boost::type_traits Boost::utility diff --git a/build.jam b/build.jam index e9eb1a11a2..55bed73ba7 100644 --- a/build.jam +++ b/build.jam @@ -19,7 +19,6 @@ constant boost_dependencies : /boost/mpl//boost_mpl /boost/numeric_conversion//boost_numeric_conversion /boost/preprocessor//boost_preprocessor - /boost/static_assert//boost_static_assert /boost/tuple//boost_tuple /boost/type_traits//boost_type_traits /boost/utility//boost_utility ; diff --git a/test/Jamfile b/test/Jamfile index 40115d86cb..2656588923 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -63,7 +63,6 @@ alias base_deps : usage-requirements /boost/function//boost_function /boost/mpl//boost_mpl /boost/preprocessor//boost_preprocessor - /boost/static_assert//boost_static_assert /boost/type_traits//boost_type_traits ; test-suite python From e6917d9c58d86f1ac5f06cd6da7f033b70db7395 Mon Sep 17 00:00:00 2001 From: sdarwin Date: Fri, 13 Mar 2026 07:01:23 +0100 Subject: [PATCH 25/53] Docs: adjust template to support Sphinx 9 --- doc/numpy/_templates/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/numpy/_templates/layout.html b/doc/numpy/_templates/layout.html index 69e1a868c0..89f1ca0b39 100644 --- a/doc/numpy/_templates/layout.html +++ b/doc/numpy/_templates/layout.html @@ -47,7 +47,7 @@ }; {%- for scriptfile in script_files %} - + {%- endfor %} From b774e5948f4ccc31c1ba7f63d225968582399f13 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Thu, 4 Jun 2026 00:15:55 +0000 Subject: [PATCH 26/53] Create vcpkg.json --- vcpkg.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 vcpkg.json diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000000..1d2d001f7f --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", + "name": "boost-python", + "version-string": "1.91.0", + "builtin-baseline": "d015e31e90838a4c9dfa3eed45979bc70d9357fc", + "dependencies": [ + "boost-config", + "boost-core", + "boost-function", + "boost-graph", + "boost-iterator", + "boost-lexical-cast", + "boost-mpl", + "boost-preprocessor", + "boost-smart-ptr", + "boost-static-assert", + "boost-align", + "python3" + ] +} From a5b21ee931a0428c0a5333adedf58bf6bf6dbf11 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Thu, 4 Jun 2026 00:28:44 +0000 Subject: [PATCH 27/53] Update test-windows.yml --- .github/workflows/test-windows.yml | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 576e1f4415..1f95821cb8 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -10,6 +10,10 @@ jobs: matrix: python-version: [3.7] + env: + VCPKG_DEFAULT_TRIPLET: 'x64-windows' + VCPKG_DEFAULT_HOST_TRIPLET: 'x64-windows' + steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 @@ -17,23 +21,10 @@ jobs: python-version: ${{ matrix.python-version }} - uses: microsoft/setup-msbuild@v2 - name: setup boost prerequisites - uses: lukka/run-vcpkg@v6 + uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11.6 with: - vcpkgGitCommitId: '88b1071e39f13b632644d9d953738d345a4ac055' - vcpkgDirectory: '${{ runner.workspace }}/vcpkg' - vcpkgTriplet: x64-windows - vcpkgArguments: > - boost-config - boost-core - boost-function - boost-graph - boost-iterator - boost-lexical-cast - boost-mpl - boost-preprocessor - boost-smart-ptr - boost-static-assert - boost-align + vcpkgDirectory: '${{ github.workspace }}/vcpkg' + runVcpkgInstall: true - name: setup faber run: | python -m pip install --upgrade pip From d68c52f3c3ae3fbe8c8d3f4d4d38cef2aeef968e Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Wed, 17 Jun 2026 19:05:48 +0000 Subject: [PATCH 28/53] Update test-windows.yml: Fix the `--with-boost-include` directory passed to the `faber` commands --- .github/workflows/test-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 1f95821cb8..639fe65d59 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -33,8 +33,8 @@ jobs: - name: build shell: cmd run: | - faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include=${{ runner.workspace }}\vcpkg\installed\x64-windows\include -j4 + faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include=${{ github.workspace }}\vcpkg\installed\x64-windows\include -j4 - name: test shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include=${{ runner.workspace }}\vcpkg\installed\x64-windows\include -j4 test.report + faber --builddir=build cxx.name=msvc --with-boost-include=${{ github.workspace }}\vcpkg\installed\x64-windows\include -j4 test.report From 8b3a45d79ba1ac3fbed80206a2dead6c745bf4c4 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Wed, 17 Jun 2026 15:26:35 -0700 Subject: [PATCH 29/53] List vcpkg directory contents, looking for config.hpp --- .github/workflows/test-windows.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 639fe65d59..35bfe79d6c 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -25,6 +25,9 @@ jobs: with: vcpkgDirectory: '${{ github.workspace }}/vcpkg' runVcpkgInstall: true + - name: List directory contents + run: Get-ChildItem "${{ github.workspace }}/vcpkg" -Recurse -Force -File -Filter 'config.hpp' + shell: pwsh - name: setup faber run: | python -m pip install --upgrade pip From 874b21a680eae016d55546581d8aa9bb174dab71 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Thu, 18 Jun 2026 10:01:12 -0700 Subject: [PATCH 30/53] vcpkg.json: Go back to the same commit for builtin-baseline as was used for `vcpkgGitCommitId` prior to this PR --- vcpkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index 1d2d001f7f..2c5bb02df3 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,7 +2,7 @@ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", "name": "boost-python", "version-string": "1.91.0", - "builtin-baseline": "d015e31e90838a4c9dfa3eed45979bc70d9357fc", + "builtin-baseline": "88b1071e39f13b632644d9d953738d345a4ac055", "dependencies": [ "boost-config", "boost-core", From d5ab28a8cc64f3cf3591f63bf270dad1caa4edb1 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Thu, 18 Jun 2026 13:53:19 -0700 Subject: [PATCH 31/53] test-windows.yml: Set VCPKG_ROOT environment variable, etc. --- .github/workflows/test-windows.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 35bfe79d6c..f905e4c36e 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -11,6 +11,7 @@ jobs: python-version: [3.7] env: + VCPKG_ROOT: ${{ github.workspace }}\vcpkg VCPKG_DEFAULT_TRIPLET: 'x64-windows' VCPKG_DEFAULT_HOST_TRIPLET: 'x64-windows' @@ -26,7 +27,7 @@ jobs: vcpkgDirectory: '${{ github.workspace }}/vcpkg' runVcpkgInstall: true - name: List directory contents - run: Get-ChildItem "${{ github.workspace }}/vcpkg" -Recurse -Force -File -Filter 'config.hpp' + run: Get-ChildItem "${{ github.workspace }}\vcpkg" -Recurse -Force -File -Filter 'config.hpp' shell: pwsh - name: setup faber run: | @@ -36,8 +37,8 @@ jobs: - name: build shell: cmd run: | - faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include=${{ github.workspace }}\vcpkg\installed\x64-windows\include -j4 + faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}\vcpkg\installed\x64-windows\include" -j4 - name: test shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include=${{ github.workspace }}\vcpkg\installed\x64-windows\include -j4 test.report + faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}\vcpkg\installed\x64-windows\include" -j4 test.report From 52ac3bdcafa91fb35b4e9838370c66aebb0b0035 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Thu, 18 Jun 2026 15:18:53 -0700 Subject: [PATCH 32/53] test-windows.yml: Make workflow much more like my other, working projects that use vcpkg --- .github/workflows/test-windows.yml | 51 ++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f905e4c36e..0258b8aa95 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -11,34 +11,81 @@ jobs: python-version: [3.7] env: - VCPKG_ROOT: ${{ github.workspace }}\vcpkg + VCPKG_ROOT: '${{ github.workspace }}/vcpkg' + VCPKG_ASSET_SOURCES: "${{ github.workspace }}/vcpkgAssets" + X_VCPKG_ASSET_SOURCES: clear;x-azurl,file:///${{ github.workspace }}/vcpkgAssets,,readwrite + VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkgBin,readwrite + VCPKG_BUILD_TREES: "${{ github.workspace }}/vcpkgBuild" + VCPKG_INSTALL_OPTIONS: "--x-buildtrees-root=${{ github.workspace }}/vcpkgBuild" VCPKG_DEFAULT_TRIPLET: 'x64-windows' VCPKG_DEFAULT_HOST_TRIPLET: 'x64-windows' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + + - name: Restore vcpkg cache + id: cache-vcpkg-restore + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + "${{ github.workspace }}/vcpkgAssets" + "${{ github.workspace }}/vcpkgBCache" + "${{ github.workspace }}/vcpkgBin" + "${{ github.workspace }}/vcpkgBuild" + key: vcpkgCache-${{ github.repository_owner }}-${{ matrix.os }}-${{ matrix.triplet }}-${{ hashFiles('vcpkg.json') }} + + - name: Create directories on cache miss + run: | + mkdir -p "${{ github.workspace }}/vcpkgAssets" + mkdir -p "${{ github.workspace }}/vcpkgBCache" + mkdir -p "${{ github.workspace }}/vcpkgBin" + mkdir -p "${{ github.workspace }}/vcpkgBuild" + if: steps.cache-vcpkg-restore.outputs.cache-hit != 'true' + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + - uses: microsoft/setup-msbuild@v2 + + - name: Install CMake + uses: lukka/get-cmake@591817e96fcad43505fb4eae36172462abb3a42e # v4.3.3 + - name: setup boost prerequisites uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11.6 with: vcpkgDirectory: '${{ github.workspace }}/vcpkg' + binaryCachePath: "${{ github.workspace }}/vcpkgBCache" runVcpkgInstall: true + - name: List directory contents run: Get-ChildItem "${{ github.workspace }}\vcpkg" -Recurse -Force -File -Filter 'config.hpp' shell: pwsh + - name: setup faber run: | python -m pip install --upgrade pip python -m pip install setuptools faber numpy faber --info=tools cxx + - name: build shell: cmd run: | faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}\vcpkg\installed\x64-windows\include" -j4 + - name: test shell: cmd run: | faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}\vcpkg\installed\x64-windows\include" -j4 test.report + + - name: Save vcpkg cache + id: cache-vcpkg-save + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + "${{ github.workspace }}/vcpkgAssets" + "${{ github.workspace }}/vcpkgBCache" + "${{ github.workspace }}/vcpkgBin" + "${{ github.workspace }}/vcpkgBuild" + key: ${{ steps.cache-vcpkg-restore.outputs.cache-primary-key }} + if: steps.cache-vcpkg-restore.outputs.cache-hit != 'true' From c7ce567e1722d4c353d1cfdee372f25ad800b4ee Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Sat, 20 Jun 2026 07:58:36 -0700 Subject: [PATCH 33/53] test-windows.yml: Search the entire github.workspace directory and subdirectories for config.hpp --- .github/workflows/test-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 0258b8aa95..243cae93a8 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -59,7 +59,7 @@ jobs: runVcpkgInstall: true - name: List directory contents - run: Get-ChildItem "${{ github.workspace }}\vcpkg" -Recurse -Force -File -Filter 'config.hpp' + run: Get-ChildItem "${{ github.workspace }}" -Recurse -Force -File -Filter 'config.hpp' shell: pwsh - name: setup faber From 112ec2ab011bbdfb29f3001eb3f7b2b957718fd2 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Sat, 20 Jun 2026 08:03:10 -0700 Subject: [PATCH 34/53] test-windows.yml: Update `microsoft/setup-msbuild` to latest version, v3.0.0 --- .github/workflows/test-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 243cae93a8..ce1f704505 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -46,7 +46,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - - uses: microsoft/setup-msbuild@v2 + - uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 - name: Install CMake uses: lukka/get-cmake@591817e96fcad43505fb4eae36172462abb3a42e # v4.3.3 From b0bac91a8174b082a44d0849bcddbfe1905c4f15 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Sat, 20 Jun 2026 09:41:14 -0700 Subject: [PATCH 35/53] vcpkg.json: Go back to latest vcpkg release for builtin-baseline --- vcpkg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index 2c5bb02df3..5a9e71d23c 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,7 +2,7 @@ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", "name": "boost-python", "version-string": "1.91.0", - "builtin-baseline": "88b1071e39f13b632644d9d953738d345a4ac055", + "builtin-baseline": "f3e10653cc27d62a37a3763cd84b38bca07c6075", "dependencies": [ "boost-config", "boost-core", From 83d828d53d04f82e8038e819b4ffcb862622c4c0 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Sat, 20 Jun 2026 11:00:01 -0700 Subject: [PATCH 36/53] test-windows.yml: Try again to set the correct with-boost-include directory --- .github/workflows/test-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index ce1f704505..7066904705 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -71,12 +71,12 @@ jobs: - name: build shell: cmd run: | - faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}\vcpkg\installed\x64-windows\include" -j4 + faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}\\969f6665-88a2-4c98-938e-ca9259871fec\\vcpkg_installed\\x64-windows\\include" -j4 - name: test shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}\vcpkg\installed\x64-windows\include" -j4 test.report + faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}\\969f6665-88a2-4c98-938e-ca9259871fec\\vcpkg_installed\\x64-windows\\include" -j4 test.report - name: Save vcpkg cache id: cache-vcpkg-save From 7f1f7a8c9eac35d1db7f793744471bb11a9daece Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Sat, 20 Jun 2026 17:59:12 -0700 Subject: [PATCH 37/53] test-windows.yml: Set VCPKG_INSTALLED_DIR env var, and use its value in `--with-boost-include=` faber parameters --- .github/workflows/test-windows.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 7066904705..2515f527db 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -16,6 +16,7 @@ jobs: X_VCPKG_ASSET_SOURCES: clear;x-azurl,file:///${{ github.workspace }}/vcpkgAssets,,readwrite VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkgBin,readwrite VCPKG_BUILD_TREES: "${{ github.workspace }}/vcpkgBuild" + VCPKG_INSTALLED_DIR: "${{ github.workspace }}/vcpkgInstalled" VCPKG_INSTALL_OPTIONS: "--x-buildtrees-root=${{ github.workspace }}/vcpkgBuild" VCPKG_DEFAULT_TRIPLET: 'x64-windows' VCPKG_DEFAULT_HOST_TRIPLET: 'x64-windows' @@ -71,12 +72,12 @@ jobs: - name: build shell: cmd run: | - faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}\\969f6665-88a2-4c98-938e-ca9259871fec\\vcpkg_installed\\x64-windows\\include" -j4 + faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}/vcpkgInstalled" -j4 - name: test shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}\\969f6665-88a2-4c98-938e-ca9259871fec\\vcpkg_installed\\x64-windows\\include" -j4 test.report + faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}/vcpkgInstalled" -j4 test.report - name: Save vcpkg cache id: cache-vcpkg-save From a017f0877027a814f8b21b8bd50fba11a05f0da1 Mon Sep 17 00:00:00 2001 From: Stephen G Tuggy Date: Sat, 20 Jun 2026 23:15:32 -0700 Subject: [PATCH 38/53] test-windows.yml: Forgot to include `/x64-windows/include` in `--with-boost-include=` faber parameters --- .github/workflows/test-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 2515f527db..feb827d23f 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -72,12 +72,12 @@ jobs: - name: build shell: cmd run: | - faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}/vcpkgInstalled" -j4 + faber --builddir=build cxx.name=msvc --log=commands --log=output --with-boost-include="${{ github.workspace }}/vcpkgInstalled/x64-windows/include" -j4 - name: test shell: cmd run: | - faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}/vcpkgInstalled" -j4 test.report + faber --builddir=build cxx.name=msvc --with-boost-include="${{ github.workspace }}/vcpkgInstalled/x64-windows/include" -j4 test.report - name: Save vcpkg cache id: cache-vcpkg-save From 521a86817da54cb28cac71287704b7ae39d1e0f6 Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Sun, 21 Jun 2026 14:13:22 -0400 Subject: [PATCH 39/53] Windows build cleanup. --- .github/workflows/test-windows.yml | 8 +------- vcpkg.json => .github/workflows/vcpkg.json | 0 2 files changed, 1 insertion(+), 7 deletions(-) rename vcpkg.json => .github/workflows/vcpkg.json (100%) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index feb827d23f..d13b6bf86c 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -49,19 +49,13 @@ jobs: - uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 - - name: Install CMake - uses: lukka/get-cmake@591817e96fcad43505fb4eae36172462abb3a42e # v4.3.3 - - name: setup boost prerequisites uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11.6 with: vcpkgDirectory: '${{ github.workspace }}/vcpkg' binaryCachePath: "${{ github.workspace }}/vcpkgBCache" runVcpkgInstall: true - - - name: List directory contents - run: Get-ChildItem "${{ github.workspace }}" -Recurse -Force -File -Filter 'config.hpp' - shell: pwsh + vcpkgJsonGlob: .github/workflows/vcpkg.json - name: setup faber run: | diff --git a/vcpkg.json b/.github/workflows/vcpkg.json similarity index 100% rename from vcpkg.json rename to .github/workflows/vcpkg.json From 14f1bbe6b884d9363378f42898d198a99a584e48 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 3 May 2026 15:55:57 +0900 Subject: [PATCH 40/53] refactor: use PyUnicode_AsUTF8 with Python 3.14+ _PyUnicode_AsString is deprecated since Python 3.14 https://docs.python.org/3.14/deprecations/index.html#id2 fix #512 --- src/converter/builtin_converters.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/converter/builtin_converters.cpp b/src/converter/builtin_converters.cpp index ee2d5b4794..227fe2f805 100644 --- a/src/converter/builtin_converters.cpp +++ b/src/converter/builtin_converters.cpp @@ -40,22 +40,18 @@ namespace // An lvalue conversion function which extracts a char const* from a // Python String. -#if PY_VERSION_HEX < 0x03000000 void* convert_to_cstring(PyObject* obj) { +#if PY_VERSION_HEX < 0x03000000 return PyString_Check(obj) ? PyString_AsString(obj) : 0; - } #elif PY_VERSION_HEX < 0x03070000 - void* convert_to_cstring(PyObject* obj) - { return PyUnicode_Check(obj) ? _PyUnicode_AsString(obj) : 0; - } -#else - void* convert_to_cstring(PyObject* obj) - { +#elif PY_VERSION_HEX < 0x030E0000 return PyUnicode_Check(obj) ? const_cast(reinterpret_cast(_PyUnicode_AsString(obj))) : 0; - } +#else + return PyUnicode_Check(obj) ? const_cast(reinterpret_cast(PyUnicode_AsUTF8(obj))) : 0; #endif + } // Given a target type and a SlotPolicy describing how to perform a // given conversion, registers from_python converters which use the From 9e2f5191e53f12248175a434f7f7496454e09fa4 Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sun, 20 Apr 2025 09:24:16 +0400 Subject: [PATCH 41/53] 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 390db82cf4..f12550b221 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -47,6 +47,12 @@ namespace detail { BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_used = false); +# 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(*)()); @@ -115,6 +121,46 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); void BOOST_PP_CAT(init_module_, name)() # endif // HAS_CXX11 +# 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) \ @@ -137,6 +183,14 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name, __VA_ARGS__) extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) # endif // HAS_CXX11 && Python 3 +# 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 c32f4187bc..ef83d31807 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -52,6 +52,18 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, 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 7cf22f9c09..362ff82d18 100644 --- a/test/fabscript +++ b/test/fabscript @@ -174,4 +174,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) From 0796140961369513bdf230596861eec781867885 Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:34:27 +0400 Subject: [PATCH 42/53] Fix indent and add more remarks to make it more manageable --- include/boost/python/module_init.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index f12550b221..ba62259bff 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -13,7 +13,7 @@ namespace boost { namespace python { -#ifdef HAS_CXX11 +# ifdef HAS_CXX11 // Use to activate the Py_MOD_GIL_NOT_USED flag. class mod_gil_not_used { public: @@ -39,7 +39,7 @@ inline bool gil_not_used_option(F &&, O &&...o) { } } -#endif // HAS_CXX11 +# endif // HAS_CXX11 namespace detail { @@ -51,13 +51,13 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_us BOOST_PYTHON_DECL int exec_module(PyObject*, void(*)()); -# endif +# endif // PY_VERSION_HEX >= 0x03050000 -#else +# else // PY_VERSION_HEX >= 0x03000000 BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); -#endif +# endif // PY_VERSION_HEX >= 0x03000000 }}} @@ -159,9 +159,9 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); } \ void BOOST_PP_CAT(init_module_, name)() -# endif +# endif // PY_VERSION_HEX >= 0x03050000 -# else +# else // ! PY_VERSION_HEX >= 0x03000000 # define _BOOST_PYTHON_MODULE_INIT(name) \ void BOOST_PP_CAT(init,name)() \ @@ -171,7 +171,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); } \ void BOOST_PP_CAT(init_module_,name)() -# endif +# endif // PY_VERSION_HEX >= 0x03000000 # if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x03000000) # define BOOST_PYTHON_MODULE_INIT(name, ...) \ @@ -189,8 +189,8 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) void BOOST_PP_CAT(init_module_,name)(); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) -# endif +# endif // PY_VERSION_HEX >= 0x03050000 -# endif +# endif // BOOST_PYTHON_MODULE_INIT #endif // MODULE_INIT_DWA20020722_HPP From b8b00933c6cecbb47e871855ebef431524fc9869 Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:51:30 +0400 Subject: [PATCH 43/53] Add mod_gil_not_used option to BOOST_PYTHON_MODULE_MULTI_PHASE_INIT --- include/boost/python/module_init.hpp | 51 +++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index ba62259bff..05ead85c71 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -123,7 +123,8 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); # if PY_VERSION_HEX >= 0x03050000 -# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ +# if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) +# define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, ...) \ int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ { \ return boost::python::detail::exec_module( \ @@ -140,6 +141,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); \ static PyModuleDef_Slot slots[] = { \ {Py_mod_exec, reinterpret_cast(reinterpret_cast(BOOST_PP_CAT(exec_module_, name)))}, \ + {Py_mod_gil, boost::python::detail::gil_not_used_option(__VA_ARGS__) ? Py_MOD_GIL_NOT_USED : Py_MOD_GIL_USED}, \ {0, NULL} \ }; \ \ @@ -158,6 +160,43 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); return PyModuleDef_Init(&moduledef); \ } \ void BOOST_PP_CAT(init_module_, name)() +# else // ! HAS_CXX11 && Python 3.13+ +# 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 // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 @@ -184,11 +223,15 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) # endif // HAS_CXX11 && Python 3 # if PY_VERSION_HEX >= 0x03050000 - -# define BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ +# if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) +# 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, __VA_ARGS__) +# else +# 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 // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 # endif // BOOST_PYTHON_MODULE_INIT From b34c8953a6543c50ad2ce5dd9e1cb01d3dea6c0c Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:53:54 +0400 Subject: [PATCH 44/53] Rename module to mod --- include/boost/python/module_init.hpp | 8 ++++---- src/module.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 05ead85c71..0d826be654 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -125,10 +125,10 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); # if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) # define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, ...) \ - int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ { \ return boost::python::detail::exec_module( \ - module, BOOST_PP_CAT(init_module_, name) ); \ + mod, BOOST_PP_CAT(init_module_, name) ); \ } \ extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ { \ @@ -162,10 +162,10 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); void BOOST_PP_CAT(init_module_, name)() # else // ! HAS_CXX11 && Python 3.13+ # define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ - int BOOST_PP_CAT(exec_module_,name)(PyObject* module) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ { \ return boost::python::detail::exec_module( \ - module, BOOST_PP_CAT(init_module_, name) ); \ + mod, BOOST_PP_CAT(init_module_, name) ); \ } \ extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ { \ diff --git a/src/module.cpp b/src/module.cpp index ef83d31807..22c4c5868a 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -54,10 +54,10 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef& moduledef, # if PY_VERSION_HEX >= 0x03050000 -BOOST_PYTHON_DECL int exec_module(PyObject* module, void(*init_function)()) +BOOST_PYTHON_DECL int exec_module(PyObject* mod, void(*init_function)()) { PyObject* retval = init_module_in_scope( - module, + mod, init_function); return retval ? 0 : -1; } From 97a623d3ed1b62605d3b8d57d3b5e6bde237f0f1 Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:12:47 +0400 Subject: [PATCH 45/53] Add tests --- test/fabscript | 2 ++ test/module_multi_phase_nogil.cpp | 25 +++++++++++++++++++++++++ test/module_multi_phase_nogil.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 test/module_multi_phase_nogil.cpp create mode 100644 test/module_multi_phase_nogil.py diff --git a/test/fabscript b/test/fabscript index 362ff82d18..f418ed3efb 100644 --- a/test/fabscript +++ b/test/fabscript @@ -178,5 +178,7 @@ python_version_major, python_version_minor = map(int, python.instance().version. tests.append(extension_test("module_multi_phase", condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) +tests.append(extension_test("module_multi_phase_nogil", + 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_nogil.cpp b/test/module_multi_phase_nogil.cpp new file mode 100644 index 0000000000..6de74c29d7 --- /dev/null +++ b/test/module_multi_phase_nogil.cpp @@ -0,0 +1,25 @@ +// Test for BOOST_PYTHON_MODULE_MULTI_PHASE with optional mod_gil_not_used argument + +#include +#include + +// Simple function to export +int get_value() { + return 1234; +} + +#if defined(HAS_CXX11) && (PY_VERSION_HEX >= 0x030D0000) +// C++11 build with Python 3.13+: test with mod_gil_not_used option +BOOST_PYTHON_MODULE_MULTI_PHASE(module_multi_phase_nogil_ext, boost::python::mod_gil_not_used()) +{ + using namespace boost::python; + def("get_value", get_value); +} +#else +// C++98 build or Python 3.12-: test without optional arguments +BOOST_PYTHON_MODULE_MULTI_PHASE(module_multi_phase_nogil_ext) +{ + using namespace boost::python; + def("get_value", get_value); +} +#endif diff --git a/test/module_multi_phase_nogil.py b/test/module_multi_phase_nogil.py new file mode 100644 index 0000000000..30c6bcb984 --- /dev/null +++ b/test/module_multi_phase_nogil.py @@ -0,0 +1,29 @@ +""" +>>> from module_multi_phase_nogil_ext import * +>>> get_value() +1234 +>>> import sys, sysconfig +>>> Py_GIL_DISABLED = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) +>>> if Py_GIL_DISABLED and sys._is_gil_enabled(): +... print('GIL is enabled and should not be') +... else: +... print('okay') +okay +""" + +from __future__ import print_function + +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) From d018bb17d4083565a23b14ad254d1b92353b804c Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Fri, 26 Jun 2026 08:12:02 +0400 Subject: [PATCH 46/53] Add POD state for multi-phase modules --- include/boost/python/module.hpp | 1 + include/boost/python/module_init.hpp | 86 ++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/include/boost/python/module.hpp b/include/boost/python/module.hpp index f6f9fa871f..4ec34868b6 100644 --- a/include/boost/python/module.hpp +++ b/include/boost/python/module.hpp @@ -11,6 +11,7 @@ # 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 +# define BOOST_PYTHON_MODULE_WITH_STATE BOOST_PYTHON_MODULE_WITH_STATE_INIT # endif #endif // MODULE_DWA20011221_HPP diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 0d826be654..910421d4ec 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -9,6 +9,11 @@ # include # include +# if PY_VERSION_HEX >= 0x03050000 +# include +# include +# endif + # ifndef BOOST_PYTHON_MODULE_INIT namespace boost { namespace python { @@ -160,6 +165,44 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); return PyModuleDef_Init(&moduledef); \ } \ void BOOST_PP_CAT(init_module_, name)() +# define _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, ...) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ + { \ + return boost::python::detail::exec_module( \ + mod, BOOST_PP_CAT(init_module_, name) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + BOOST_STATIC_ASSERT_MSG(boost::is_pod::value, \ + "Module State MUST be a plain POD structure!"); \ + 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)))}, \ + {Py_mod_gil, boost::python::detail::gil_not_used_option(__VA_ARGS__) ? Py_MOD_GIL_NOT_USED : Py_MOD_GIL_USED}, \ + {0, NULL} \ + }; \ + \ + static struct PyModuleDef moduledef = { \ + initial_m_base, \ + BOOST_PP_STRINGIZE(name), \ + 0, /* m_doc */ \ + sizeof(StateType), /* 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)() # else // ! HAS_CXX11 && Python 3.13+ # define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ @@ -196,6 +239,43 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); return PyModuleDef_Init(&moduledef); \ } \ void BOOST_PP_CAT(init_module_, name)() +# define _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) \ + int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ + { \ + return boost::python::detail::exec_module( \ + mod, BOOST_PP_CAT(init_module_, name) ); \ + } \ + extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ + { \ + BOOST_STATIC_ASSERT_MSG(boost::is_pod::value, \ + "Module State MUST be a plain POD structure!"); \ + 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 */ \ + sizeof(StateType), /* 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 // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 @@ -227,10 +307,16 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) # 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, __VA_ARGS__) +# define BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, ...) \ + void BOOST_PP_CAT(init_module_,name)(); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, __VA_ARGS__) # else # 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) +# define BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) \ + void BOOST_PP_CAT(init_module_,name)(); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name) # endif // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 From afd27c63d5b7e261cfe0ef2bc45414d65b2ad81d Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Fri, 26 Jun 2026 09:09:01 +0400 Subject: [PATCH 47/53] Provide 'state' accessor in module definition --- include/boost/python/module_init.hpp | 16 +++++++------ src/module.cpp | 36 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 910421d4ec..0478cd9c48 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -56,6 +56,8 @@ BOOST_PYTHON_DECL PyObject* init_module(PyModuleDef&, void(*)(), bool gil_not_us BOOST_PYTHON_DECL int exec_module(PyObject*, void(*)()); +BOOST_PYTHON_DECL int exec_module_with_state(PyObject*, void(*)(void*)); + # endif // PY_VERSION_HEX >= 0x03050000 # else // PY_VERSION_HEX >= 0x03000000 @@ -169,7 +171,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ { \ return boost::python::detail::exec_module( \ - mod, BOOST_PP_CAT(init_module_, name) ); \ + mod, reinterpret_cast(BOOST_PP_CAT(init_module_, name)) ); \ } \ extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ { \ @@ -202,7 +204,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); \ return PyModuleDef_Init(&moduledef); \ } \ - void BOOST_PP_CAT(init_module_, name)() + void BOOST_PP_CAT(init_module_, name)(StateType* state) # else // ! HAS_CXX11 && Python 3.13+ # define _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name) \ int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ @@ -243,7 +245,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ { \ return boost::python::detail::exec_module( \ - mod, BOOST_PP_CAT(init_module_, name) ); \ + mod, reinterpret_cast(BOOST_PP_CAT(init_module_, name)) ); \ } \ extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ { \ @@ -275,7 +277,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); \ return PyModuleDef_Init(&moduledef); \ } \ - void BOOST_PP_CAT(init_module_, name)() + void BOOST_PP_CAT(init_module_, name)(StateType* state) # endif // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 @@ -308,15 +310,15 @@ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_INIT(name) void BOOST_PP_CAT(init_module_,name)(); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_MULTI_PHASE_INIT(name, __VA_ARGS__) # define BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, ...) \ - void BOOST_PP_CAT(init_module_,name)(); \ + void BOOST_PP_CAT(init_module_,name)(StateType* state); \ extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, __VA_ARGS__) # else # 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) # define BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) \ - void BOOST_PP_CAT(init_module_,name)(); \ -extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name) + void BOOST_PP_CAT(init_module_,name)(StateType* state); \ +extern "C" BOOST_SYMBOL_EXPORT _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) # endif // HAS_CXX11 && Python 3.13+ # endif // PY_VERSION_HEX >= 0x03050000 diff --git a/src/module.cpp b/src/module.cpp index 22c4c5868a..200b0bb787 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -26,6 +26,34 @@ namespace return m; } +# if PY_VERSION_HEX >= 0x03050000 + class init_function_with_state { + public: + init_function_with_state(void(*init_function)(void*), void* state) + : init_function_(init_function), state_(state) {} + + void operator()() const { init_function_(state_); } + private: + void(*const init_function_)(void*); + void* const state_; + }; + + PyObject* init_module_in_scope_with_state(PyObject* m, void(*init_function)(void*)) + { + if (m != 0) + { + // Create the current module scope + object m_obj(((borrowed_reference_t*)m)); + scope current_module(m_obj); + + void* state = PyModule_GetState(m); + + if (handle_exception(init_function_with_state(init_function, state))) return NULL; + } + + return m; + } +# endif } BOOST_PYTHON_DECL void scope_setattr_doc(char const* name, object const& x, char const* doc) @@ -62,6 +90,14 @@ BOOST_PYTHON_DECL int exec_module(PyObject* mod, void(*init_function)()) return retval ? 0 : -1; } +BOOST_PYTHON_DECL int exec_module_with_state(PyObject* mod, void(*init_function)(void*)) +{ + PyObject* retval = init_module_in_scope_with_state( + mod, + init_function); + return retval ? 0 : -1; +} + # endif #else From c86ea207d1a7eb84c34f7b606e62a7a22b43538d Mon Sep 17 00:00:00 2001 From: O01eg <397177+o01eg@users.noreply.github.com> Date: Sat, 27 Jun 2026 00:16:21 +0400 Subject: [PATCH 48/53] Fix and add tests for module state --- include/boost/python/module_init.hpp | 4 ++-- test/fabscript | 2 ++ test/module_multi_phase_state.cpp | 30 ++++++++++++++++++++++++++++ test/module_multi_phase_state.py | 23 +++++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 test/module_multi_phase_state.cpp create mode 100644 test/module_multi_phase_state.py diff --git a/include/boost/python/module_init.hpp b/include/boost/python/module_init.hpp index 0478cd9c48..abf1ce2524 100644 --- a/include/boost/python/module_init.hpp +++ b/include/boost/python/module_init.hpp @@ -170,7 +170,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); # define _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType, ...) \ int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ { \ - return boost::python::detail::exec_module( \ + return boost::python::detail::exec_module_with_state( \ mod, reinterpret_cast(BOOST_PP_CAT(init_module_, name)) ); \ } \ extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ @@ -244,7 +244,7 @@ BOOST_PYTHON_DECL PyObject* init_module(char const* name, void(*)()); # define _BOOST_PYTHON_MODULE_WITH_STATE_INIT(name, StateType) \ int BOOST_PP_CAT(exec_module_,name)(PyObject* mod) \ { \ - return boost::python::detail::exec_module( \ + return boost::python::detail::exec_module_with_state( \ mod, reinterpret_cast(BOOST_PP_CAT(init_module_, name)) ); \ } \ extern "C" BOOST_SYMBOL_EXPORT PyObject* BOOST_PP_CAT(PyInit_, name)() \ diff --git a/test/fabscript b/test/fabscript index f418ed3efb..c6d834aa0d 100644 --- a/test/fabscript +++ b/test/fabscript @@ -180,5 +180,7 @@ tests.append(extension_test("module_multi_phase", condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) tests.append(extension_test("module_multi_phase_nogil", condition=python_version_major > 3 or (python_version_major == 3 and python_version_minor >= 5))) +tests.append(extension_test("module_multi_phase_state", + 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_state.cpp b/test/module_multi_phase_state.cpp new file mode 100644 index 0000000000..0e497fc9cf --- /dev/null +++ b/test/module_multi_phase_state.cpp @@ -0,0 +1,30 @@ +// 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 +#include + +using namespace boost::python; + +struct TestState +{ + int x; +}; + +int get_state_x() { + TestState* state = reinterpret_cast(PyModule_GetState(import("module_multi_phase_state_ext").ptr())); + if (state != nullptr) { + return state->x; + } + return -1; +} + +BOOST_PYTHON_MODULE_WITH_STATE(module_multi_phase_state_ext, TestState) +{ + state->x = 42; + def("x", get_state_x); +} + +#include "module_tail.cpp" diff --git a/test/module_multi_phase_state.py b/test/module_multi_phase_state.py new file mode 100644 index 0000000000..1df4b96235 --- /dev/null +++ b/test/module_multi_phase_state.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_state_ext +>>> module_multi_phase_state_ext.x() +42 +""" + +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) From e08584a4ed0f87b3f2d6b590c3198a5df66f07c3 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 17:36:56 +0900 Subject: [PATCH 49/53] feat(vector_indexing_suite): implement __iadd__ --- .../boost/python/suite/indexing/vector_indexing_suite.hpp | 8 ++++++++ test/vector_indexing_suite.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index 34c29ecc6f..6ca31896ae 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -61,6 +61,7 @@ namespace boost { namespace python { extension_def(Class& cl) { cl + .def("__iadd__", &base_iadd) .def("append", &base_append) .def("extend", &base_extend) ; @@ -235,6 +236,13 @@ namespace boost { namespace python { container_utils::extend_container(temp, v); DerivedPolicies::extend(container, temp.begin(), temp.end()); } + + static object + base_iadd(Container& container, object v) + { + base_extend(container, v); + return object(container); + } }; }} // namespace boost::python diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index 478cd01516..5e5e8f7a3d 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -320,6 +320,9 @@ >>> v.extend(['f','g','h','i','j']) >>> print_xvec(v) [ a b c d e f g h i j ] +>>> v += ['k','l','m'] +>>> print_xvec(v) +[ a b c d e f g h i j k l m ] ##################################################################### # extend using a generator expression From d8668c5963fc085076828b77bfda75a52eb72289 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 16:20:09 +0900 Subject: [PATCH 50/53] feat(indexing_suite): implement clear --- include/boost/python/suite/indexing/indexing_suite.hpp | 7 +++++++ include/boost/python/suite/indexing/map_indexing_suite.hpp | 6 ++++++ .../boost/python/suite/indexing/vector_indexing_suite.hpp | 6 ++++++ test/map_indexing_suite.py | 4 ++++ test/vector_indexing_suite.py | 4 ++++ 5 files changed, 27 insertions(+) diff --git a/include/boost/python/suite/indexing/indexing_suite.hpp b/include/boost/python/suite/indexing/indexing_suite.hpp index 3469a2a40f..140e3a665c 100644 --- a/include/boost/python/suite/indexing/indexing_suite.hpp +++ b/include/boost/python/suite/indexing/indexing_suite.hpp @@ -184,6 +184,7 @@ namespace boost { namespace python { .def("__getitem__", &base_get_item) .def("__contains__", &base_contains) .def("__iter__", def_iterator()) + .def("clear", &base_clear) ; DerivedPolicies::extension_def(cl); @@ -288,6 +289,12 @@ namespace boost { namespace python { return false; } } + + static void + base_clear(Container& container) + { + return DerivedPolicies::clear(container); + } }; }} // namespace boost::python diff --git a/include/boost/python/suite/indexing/map_indexing_suite.hpp b/include/boost/python/suite/indexing/map_indexing_suite.hpp index 7fbad4cace..567abe4e74 100644 --- a/include/boost/python/suite/indexing/map_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/map_indexing_suite.hpp @@ -137,6 +137,12 @@ namespace boost { namespace python { container.erase(i); } + static void + clear(Container& container) + { + container.clear(); + } + static size_t size(Container& container) { diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index 6ca31896ae..264eac0133 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -135,6 +135,12 @@ namespace boost { namespace python { container.erase(container.begin()+from, container.begin()+to); } + static void + clear(Container& container) + { + container.clear(); + } + static size_t size(Container& container) { diff --git a/test/map_indexing_suite.py b/test/map_indexing_suite.py index 6d3e57a102..939d984c7b 100644 --- a/test/map_indexing_suite.py +++ b/test/map_indexing_suite.py @@ -202,6 +202,10 @@ ... dom = el.data() joel kimpo +>>> tm.clear() +>>> print_xmap(tm) +[ ] + ##################################################################### # Test custom converter... ##################################################################### diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index 5e5e8f7a3d..24d1caba42 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -337,6 +337,10 @@ >>> print_xvec(v) [ a b c d e f h i j ] +>>> v.clear() +>>> print_xvec(v) +[ ] + ##################################################################### # vector of strings ##################################################################### From 27ff6f6f37775792e98dcfd5a87258fc106e536c Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 16:13:45 +0900 Subject: [PATCH 51/53] feat(vector_indexing_suite): implement count --- .../suite/indexing/vector_indexing_suite.hpp | 16 ++++++++++++++++ test/vector_indexing_suite.py | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index 264eac0133..7cbdd2c6e2 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -63,6 +63,7 @@ namespace boost { namespace python { cl .def("__iadd__", &base_iadd) .def("append", &base_append) + .def("count", &base_count) .def("extend", &base_extend) ; } @@ -208,6 +209,21 @@ namespace boost { namespace python { } private: + + static size_t + base_count(Container& container, object v) + { + extract elem(v); + if (elem.check()) { + return std::count(container.begin(), container.end(), elem()); + } else { + extract elem(v); + if (!elem.check()) { + return 0; + } + return std::count(container.begin(), container.end(), elem()); + } + } static void base_append(Container& container, object v) diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index 24d1caba42..6abca6140a 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -297,6 +297,13 @@ >>> assert not 12345 in v ##################################################################### +# Count +##################################################################### +>>> v.count('a') +1 +>>> v.count(12345) +0 + # Show that iteration allows mutable access to the elements ##################################################################### >>> v[:] = ['a','b','c','d','e'] # reset again From 660e8944ffcfd02890e3cfdff4c168219f65a495 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 17:50:35 +0900 Subject: [PATCH 52/53] feat(vector_indexing_suite): implement remove --- .../suite/indexing/vector_indexing_suite.hpp | 37 +++++++++++++++++++ test/vector_indexing_suite.py | 8 ++++ 2 files changed, 45 insertions(+) diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index 7cbdd2c6e2..fd5d86c406 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -65,6 +65,7 @@ namespace boost { namespace python { .def("append", &base_append) .def("count", &base_count) .def("extend", &base_extend) + .def("remove", &base_remove) ; } @@ -265,6 +266,42 @@ namespace boost { namespace python { base_extend(container, v); return object(container); } + + static void + base_remove(Container& container, object v) + { + extract key(v); + if (key.check()) + { + auto i = std::find(container.begin(), container.end(), key()); + if (i == container.end()) + { + PyErr_SetString(PyExc_ValueError, "remove(x): x not in vector_indexing_suite"); + throw_error_already_set(); + } + container.erase(i); + } + else + { + extract key(v); + if (key.check()) + { + auto i = std::find(container.begin(), container.end(), key()); + if (i == container.end()) + { + PyErr_SetString(PyExc_ValueError, "remove(x): x not in vector_indexing_suite"); + throw_error_already_set(); + } + container.erase(i); + } + else + { + PyErr_SetString(PyExc_TypeError, + "Attempting to remove an invalid type"); + throw_error_already_set(); + } + } + } }; }} // namespace boost::python diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index 6abca6140a..c0b72c6ab9 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -78,6 +78,14 @@ >>> print_xvec(v) [ yaba c d e ] +>>> v2 = XVec() +>>> v2[:] = [X('b'), X('a'), X('c'), X('b'), X('a')] +>>> try: v2.remove("z") +... except ValueError: pass +>>> v2.remove("a") +>>> print_xvec(v2) +[ b c b a ] + ##################################################################### # Calling a mutating function of a container element ##################################################################### From fe087f9c2ff586d9b105c3c4507e2d913238a106 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Sun, 5 Apr 2026 16:29:32 +0900 Subject: [PATCH 53/53] feat(vector_indexing_suite): implement reverse --- .../python/suite/indexing/vector_indexing_suite.hpp | 10 ++++++++++ test/vector_indexing_suite.py | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/include/boost/python/suite/indexing/vector_indexing_suite.hpp b/include/boost/python/suite/indexing/vector_indexing_suite.hpp index fd5d86c406..f1b27c7381 100644 --- a/include/boost/python/suite/indexing/vector_indexing_suite.hpp +++ b/include/boost/python/suite/indexing/vector_indexing_suite.hpp @@ -66,6 +66,7 @@ namespace boost { namespace python { .def("count", &base_count) .def("extend", &base_extend) .def("remove", &base_remove) + .def("reverse", &base_reverse) ; } @@ -302,6 +303,15 @@ namespace boost { namespace python { } } } + + static void + base_reverse(Container& container) + { + using std::swap; + const unsigned n = size(container); + for (unsigned i = 0; i < n / 2; i++) + swap(container[i], container[n - i - 1]); + } }; }} // namespace boost::python diff --git a/test/vector_indexing_suite.py b/test/vector_indexing_suite.py index c0b72c6ab9..fb2b92a1ee 100644 --- a/test/vector_indexing_suite.py +++ b/test/vector_indexing_suite.py @@ -312,6 +312,14 @@ >>> v.count(12345) 0 +##################################################################### +# Reverse +##################################################################### +>>> v.reverse() +>>> print_xvec(v) +[ e d c b a ] + +##################################################################### # Show that iteration allows mutable access to the elements ##################################################################### >>> v[:] = ['a','b','c','d','e'] # reset again