From a060d43bf2eb5dee702c75eb7aedf241cc426d14 Mon Sep 17 00:00:00 2001 From: Hajo Kirchhoff Date: Tue, 10 Aug 2021 13:23:18 +0200 Subject: [PATCH 01/66] -fix: boost::python::exec_file completely broken when PY_VERSION_HEX >= 0x03010000. Bug: char* f pointed to a temporary buffer returned by PyBytes_AsString. This buffer was released when Py_DECREF(fb) was called. As a result, f pointed to invalid memory when being passed to Py_RunFile. --- src/exec.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exec.cpp b/src/exec.cpp index dd0c33103..df1971691 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -109,8 +109,8 @@ object BOOST_PYTHON_DECL exec_file(char const *filename, object global, object l PyObject *fo = Py_BuildValue("s", f); PyObject *fb = Py_None; PyUnicode_FSConverter(fo, &fb); - f = PyBytes_AsString(fb); - FILE *fs = fopen(f, "r"); + char *f_as_uft = PyBytes_AsString(fb); + FILE *fs = fopen(f_as_uft, "r"); Py_DECREF(fo); Py_DECREF(fb); #elif PY_VERSION_HEX >= 0x03000000 From f028aa407699d46b677ecc3cd72933c536aec98a Mon Sep 17 00:00:00 2001 From: Hajo Kirchhoff Date: Tue, 10 Aug 2021 13:26:02 +0200 Subject: [PATCH 02/66] -fix: issue #239 exec_file does not close the FILE handle. Note: Using FILE* is a bad choice here because of possible exceptions, but Py_RunFile is a C function. This fix works, because Py_RunFile - as a C function - does not throw exceptions. --- src/exec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/exec.cpp b/src/exec.cpp index df1971691..7488da1f6 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -129,6 +129,7 @@ object BOOST_PYTHON_DECL exec_file(char const *filename, object global, object l f, Py_file_input, global.ptr(), local.ptr()); + fclose(fs); if (!result) throw_error_already_set(); return object(detail::new_reference(result)); } From 41e208ecb598552f6d8c3bf9acacd5611d49a50d Mon Sep 17 00:00:00 2001 From: Denis Arnaud Date: Sun, 15 Aug 2021 17:05:31 +0200 Subject: [PATCH 03/66] Update call_method.hpp Was missing from https://github.com/boostorg/python/pull/320 I've tested it on one of my projects with (that patch on) Boost.Python/Boost 1.76.0 and it works well. Without that patch, there is a deprecation error. --- include/boost/python/call_method.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/python/call_method.hpp b/include/boost/python/call_method.hpp index 424077eab..2f360791d 100644 --- a/include/boost/python/call_method.hpp +++ b/include/boost/python/call_method.hpp @@ -59,7 +59,7 @@ call_method(PyObject* self, char const* name ) { PyObject* const result = - PyEval_CallMethod( + PyObject_CallMethod( self , const_cast(name) , const_cast("(" BOOST_PP_REPEAT_1ST(N, BOOST_PYTHON_FIXED, "O") ")") From a218babc8daee904a83f550fb66e5cb3f1cb3013 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 25 Apr 2022 10:51:46 +0200 Subject: [PATCH 04/66] Fix enum_type_object type on Python 3.11 The enum_type_object type inherits from PyLong_Type which is not tracked by the GC. Instances doesn't have to be tracked by the GC: remove the Py_TPFLAGS_HAVE_GC flag. The Python C API documentation says: "To create a container type, the tp_flags field of the type object must include the Py_TPFLAGS_HAVE_GC and provide an implementation of the tp_traverse handler." https://docs.python.org/dev/c-api/gcsupport.html The new exception was introduced in Python 3.11 by: https://github.com/python/cpython/issues/88429 --- src/object/enum.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/object/enum.cpp b/src/object/enum.cpp index 293e70589..5753b32e0 100644 --- a/src/object/enum.cpp +++ b/src/object/enum.cpp @@ -113,7 +113,6 @@ static PyTypeObject enum_type_object = { #if PY_VERSION_HEX < 0x03000000 | Py_TPFLAGS_CHECKTYPES #endif - | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ From fdd3e8b2c1e06667386c4a23018d5ddb14e6b591 Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Tue, 23 Aug 2022 23:03:03 -0400 Subject: [PATCH 05/66] Remove obsolete Jamfile --- build/Jamfile | 167 -------------------------------------------------- 1 file changed, 167 deletions(-) delete mode 100644 build/Jamfile diff --git a/build/Jamfile b/build/Jamfile deleted file mode 100644 index 03b69a25d..000000000 --- a/build/Jamfile +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright David Abrahams 2001-2006. 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 os ; -import indirect ; -import modules ; -import feature ; -import property ; -import python ; - -if ! [ python.configured ] && ! ( --without-python in [ modules.peek : ARGV ] ) -{ - # Attempt default configuration of python - import toolset : using ; - using python ; -} - -if [ python.configured ] || ( --without-python in [ modules.peek : ARGV ] ) -{ - alias config-warning ; -} -else -{ - message config-warning - : "warning: No python installation configured and autoconfiguration" - : "note: failed. See http://www.boost.org/libs/python/doc/building.html" - : "note: for configuration instructions or pass --without-python to" - : "note: suppress this message and silently skip all Boost.Python targets" - ; -} - -project boost/python - : source-location ../src - ; - -rule cond ( test ? : yes * : no * ) { if $(test) { return $(yes) ; } else { return $(no) ; } } -rule unless ( test ? : yes * : no * ) { if ! $(test) { return $(yes) ; } else { return $(no) ; } } -local rule eq ( a : b ) { if $(a) = $(b) { return 1 ; } } - -lib boost_python - : # sources - list.cpp - long.cpp - dict.cpp - tuple.cpp - str.cpp - slice.cpp - - converter/from_python.cpp - converter/registry.cpp - converter/type_id.cpp - object/enum.cpp - object/class.cpp - object/function.cpp - object/inheritance.cpp - object/life_support.cpp - object/pickle_support.cpp - errors.cpp - module.cpp - converter/builtin_converters.cpp - converter/arg_to_python_base.cpp - object/iterator.cpp - object/stl_iterator.cpp - object_protocol.cpp - object_operators.cpp - wrapper.cpp - import.cpp - exec.cpp - object/function_doc_signature.cpp - : # requirements - static:BOOST_PYTHON_STATIC_LIB - BOOST_PYTHON_SOURCE - - # On Windows, all code using Python has to link to the Python - # import library. - # - # On *nix we never link libboost_python to libpython. When - # extending Python, all Python symbols are provided by the - # Python interpreter executable. When embedding Python, the - # client executable is expected to explicitly link to - # /python//python (the target representing libpython) itself. - # - # python_for_extensions is a target defined by Boost.Build to - # provide the Python include paths, and on Windows, the Python - # import library, as usage requirements. - [ cond [ python.configured ] : /python//python_for_extensions ] - - # we prevent building when there is no python available - # as it's not possible anyway, and to cause dependents to - # fail to build - [ unless [ python.configured ] : no ] - config-warning - on:BOOST_DEBUG_PYTHON - -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag - @python-tag - @python.require-py - - : # default build - shared - : # usage requirements - static:BOOST_PYTHON_STATIC_LIB - on:BOOST_DEBUG_PYTHON - ; - -numpy-include = [ python.numpy-include ] ; -lib boost_numpy - : # sources - numpy/dtype.cpp - numpy/matrix.cpp - numpy/ndarray.cpp - numpy/numpy.cpp - numpy/scalars.cpp - numpy/ufunc.cpp - : # requirements - static:BOOST_NUMPY_STATIC_LIB - BOOST_NUMPY_SOURCE - [ cond [ python.numpy ] : /python//python_for_extensions ] - [ unless [ python.numpy ] : no ] - /python//numpy - boost_python - on:BOOST_DEBUG_PYTHON - -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag - @python-tag - @python.require-py - - : # default build - shared - : # usage requirements - static:BOOST_NUMPY_STATIC_LIB - on:BOOST_DEBUG_PYTHON - ; - -# boost-install creates `stage` and `install` targets -# -# `stage` stages (builds and copies into `stage/lib`) the given libraries -# `boost_python` and `boost_numpy` and their dependencies and is similar -# to issuing `b2 --with-python stage` from top level -# -# `install` installs the two libraries and their dependencies and is similar -# to issuing `b2 --with-python install` from top level - -if [ python.configured ] -{ - if [ python.numpy ] - { - boost-install boost_python boost_numpy ; - } - else - { - boost-install boost_python ; - } -} -else -{ - -# When Python isn't configured, the above `boost-install` is not executed, -# so we create empty `stage` and `install` targets that do nothing but issue -# a warning message unless `--without-python` is given - -alias stage : config-warning ; -explicit stage ; - -alias install : config-warning ; -explicit install ; - -} From 271bcea8bf8fc9a570bb68f7bbd33c538bce1e0e Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Tue, 23 Aug 2022 23:02:24 -0400 Subject: [PATCH 06/66] Don't attempt to deploy documentation from PRs. --- .github/workflows/deploy-documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-documentation.yml b/.github/workflows/deploy-documentation.yml index 83a0f0894..065945137 100644 --- a/.github/workflows/deploy-documentation.yml +++ b/.github/workflows/deploy-documentation.yml @@ -1,6 +1,6 @@ name: deploy documentation -on: [push, pull_request] +on: [push] jobs: deploy: From 508da1d19890a2430a015a9192c94c848c38a8db Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Tue, 23 Aug 2022 09:28:23 -0400 Subject: [PATCH 07/66] Fix windows CI builds. --- .github/workflows/test-windows.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 97761208c..36c4a8e4f 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -15,16 +15,26 @@ jobs: - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - uses: microsoft/setup-msbuild@v1.1 - name: setup boost prerequisites uses: lukka/run-vcpkg@v6 with: - vcpkgGitCommitId: '8a9a97315aefb3f8bc5d81bf66ca0025938b9c91' + 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 - + 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 - name: setup faber - #shell: 'bash' run: | python -m pip install --upgrade pip python -m pip install setuptools faber numpy From 47d5bc76f69e20625214381c930a2fad5765e2b3 Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Mon, 5 Sep 2022 20:54:11 -0400 Subject: [PATCH 08/66] Revert "Remove obsolete Jamfile" --- build/Jamfile | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 build/Jamfile diff --git a/build/Jamfile b/build/Jamfile new file mode 100644 index 000000000..03b69a25d --- /dev/null +++ b/build/Jamfile @@ -0,0 +1,167 @@ +# Copyright David Abrahams 2001-2006. 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 os ; +import indirect ; +import modules ; +import feature ; +import property ; +import python ; + +if ! [ python.configured ] && ! ( --without-python in [ modules.peek : ARGV ] ) +{ + # Attempt default configuration of python + import toolset : using ; + using python ; +} + +if [ python.configured ] || ( --without-python in [ modules.peek : ARGV ] ) +{ + alias config-warning ; +} +else +{ + message config-warning + : "warning: No python installation configured and autoconfiguration" + : "note: failed. See http://www.boost.org/libs/python/doc/building.html" + : "note: for configuration instructions or pass --without-python to" + : "note: suppress this message and silently skip all Boost.Python targets" + ; +} + +project boost/python + : source-location ../src + ; + +rule cond ( test ? : yes * : no * ) { if $(test) { return $(yes) ; } else { return $(no) ; } } +rule unless ( test ? : yes * : no * ) { if ! $(test) { return $(yes) ; } else { return $(no) ; } } +local rule eq ( a : b ) { if $(a) = $(b) { return 1 ; } } + +lib boost_python + : # sources + list.cpp + long.cpp + dict.cpp + tuple.cpp + str.cpp + slice.cpp + + converter/from_python.cpp + converter/registry.cpp + converter/type_id.cpp + object/enum.cpp + object/class.cpp + object/function.cpp + object/inheritance.cpp + object/life_support.cpp + object/pickle_support.cpp + errors.cpp + module.cpp + converter/builtin_converters.cpp + converter/arg_to_python_base.cpp + object/iterator.cpp + object/stl_iterator.cpp + object_protocol.cpp + object_operators.cpp + wrapper.cpp + import.cpp + exec.cpp + object/function_doc_signature.cpp + : # requirements + static:BOOST_PYTHON_STATIC_LIB + BOOST_PYTHON_SOURCE + + # On Windows, all code using Python has to link to the Python + # import library. + # + # On *nix we never link libboost_python to libpython. When + # extending Python, all Python symbols are provided by the + # Python interpreter executable. When embedding Python, the + # client executable is expected to explicitly link to + # /python//python (the target representing libpython) itself. + # + # python_for_extensions is a target defined by Boost.Build to + # provide the Python include paths, and on Windows, the Python + # import library, as usage requirements. + [ cond [ python.configured ] : /python//python_for_extensions ] + + # we prevent building when there is no python available + # as it's not possible anyway, and to cause dependents to + # fail to build + [ unless [ python.configured ] : no ] + config-warning + on:BOOST_DEBUG_PYTHON + -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag + @python-tag + @python.require-py + + : # default build + shared + : # usage requirements + static:BOOST_PYTHON_STATIC_LIB + on:BOOST_DEBUG_PYTHON + ; + +numpy-include = [ python.numpy-include ] ; +lib boost_numpy + : # sources + numpy/dtype.cpp + numpy/matrix.cpp + numpy/ndarray.cpp + numpy/numpy.cpp + numpy/scalars.cpp + numpy/ufunc.cpp + : # requirements + static:BOOST_NUMPY_STATIC_LIB + BOOST_NUMPY_SOURCE + [ cond [ python.numpy ] : /python//python_for_extensions ] + [ unless [ python.numpy ] : no ] + /python//numpy + boost_python + on:BOOST_DEBUG_PYTHON + -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag + @python-tag + @python.require-py + + : # default build + shared + : # usage requirements + static:BOOST_NUMPY_STATIC_LIB + on:BOOST_DEBUG_PYTHON + ; + +# boost-install creates `stage` and `install` targets +# +# `stage` stages (builds and copies into `stage/lib`) the given libraries +# `boost_python` and `boost_numpy` and their dependencies and is similar +# to issuing `b2 --with-python stage` from top level +# +# `install` installs the two libraries and their dependencies and is similar +# to issuing `b2 --with-python install` from top level + +if [ python.configured ] +{ + if [ python.numpy ] + { + boost-install boost_python boost_numpy ; + } + else + { + boost-install boost_python ; + } +} +else +{ + +# When Python isn't configured, the above `boost-install` is not executed, +# so we create empty `stage` and `install` targets that do nothing but issue +# a warning message unless `--without-python` is given + +alias stage : config-warning ; +explicit stage ; + +alias install : config-warning ; +explicit install ; + +} From 6c3f3ecacf66f61d799d80294bbf59ceb84daf8a Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Fri, 22 Dec 2023 05:36:47 +0200 Subject: [PATCH 09/66] Normalize static/dynamic link macros and avoid redefinition warnings --- include/boost/python/detail/config.hpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/include/boost/python/detail/config.hpp b/include/boost/python/detail/config.hpp index 8dce9b742..e2ac82704 100644 --- a/include/boost/python/detail/config.hpp +++ b/include/boost/python/detail/config.hpp @@ -57,9 +57,15 @@ ****************************************************************************/ // backwards compatibility: -#ifdef BOOST_PYTHON_STATIC_LIB -# define BOOST_PYTHON_STATIC_LINK -# elif !defined(BOOST_PYTHON_DYNAMIC_LIB) +#if defined(BOOST_PYTHON_STATIC_LINK) && !defined(BOOST_PYTHON_STATIC_LIB) +# define BOOST_PYTHON_STATIC_LIB +#endif + +#if defined(BOOST_PYTHON_DYNAMIC_LINK) && !defined(BOOST_PYTHON_DYNAMIC_LIB) +# define BOOST_PYTHON_DYNAMIC_LIB +#endif + +#if !defined(BOOST_PYTHON_STATIC_LIB) && !defined(BOOST_PYTHON_DYNAMIC_LIB) # define BOOST_PYTHON_DYNAMIC_LIB #endif From 0474de0f6cc9c6e7230aeb7164af2f7e4ccf74bf Mon Sep 17 00:00:00 2001 From: Alexis DUBURCQ Date: Fri, 15 Mar 2024 14:10:16 +0100 Subject: [PATCH 10/66] Support numpy 2.0.0b1 --- src/numpy/dtype.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/numpy/dtype.cpp b/src/numpy/dtype.cpp index 88a20a27b..da30d1927 100644 --- a/src/numpy/dtype.cpp +++ b/src/numpy/dtype.cpp @@ -98,7 +98,13 @@ python::detail::new_reference dtype::convert(object const & arg, bool align) return python::detail::new_reference(reinterpret_cast(obj)); } -int dtype::get_itemsize() const { return reinterpret_cast(ptr())->elsize;} +int dtype::get_itemsize() const { +#if NPY_ABI_VERSION < 0x02000000 + return reinterpret_cast(ptr())->elsize; +#else + return PyDataType_ELSIZE(reinterpret_cast(ptr())); +#endif +} bool equivalent(dtype const & a, dtype const & b) { // On Windows x64, the behaviour described on From 1fed0824ad95f6f993c931ed4616f7549996e231 Mon Sep 17 00:00:00 2001 From: Konstantin Podsvirov Date: Sat, 3 Dec 2022 19:21:21 +0300 Subject: [PATCH 11/66] Fix typo in numpy tutorial --- doc/numpy/tutorial/dtype.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/numpy/tutorial/dtype.rst b/doc/numpy/tutorial/dtype.rst index 557e72ba2..9bea646a6 100644 --- a/doc/numpy/tutorial/dtype.rst +++ b/doc/numpy/tutorial/dtype.rst @@ -48,7 +48,7 @@ Next, create a list, and add this tuple to the list. Then use the list to create list_for_dtype.append(for_custom_dtype) ; np::dtype custom_dtype = np::dtype(list_for_dtype) ; -We are now ready to create an ndarray with dimensions specified by \*shape\* and of custom dtpye :: +We are now ready to create an ndarray with dimensions specified by \*shape\* and of custom dtype :: np::ndarray new_array = np::zeros(shape,custom_dtype); } From 99a5352b5cf790c559a7b976c1ba99520431d9d1 Mon Sep 17 00:00:00 2001 From: "Billy K. Poon" Date: Mon, 15 Jul 2024 15:48:38 -0700 Subject: [PATCH 12/66] Another fix for numpy 2.0 - Compare pointers directly instead of using PyArray_EquivTypes --- src/numpy/dtype.cpp | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/numpy/dtype.cpp b/src/numpy/dtype.cpp index da30d1927..1ce8c6ec3 100644 --- a/src/numpy/dtype.cpp +++ b/src/numpy/dtype.cpp @@ -107,32 +107,7 @@ int dtype::get_itemsize() const { } bool equivalent(dtype const & a, dtype const & b) { - // On Windows x64, the behaviour described on - // http://docs.scipy.org/doc/numpy/reference/c-api.array.html for - // PyArray_EquivTypes unfortunately does not extend as expected: - // "For example, on 32-bit platforms, NPY_LONG and NPY_INT are equivalent". - // This should also hold for 64-bit platforms (and does on Linux), but not - // on Windows. Implement an alternative: -#ifdef _MSC_VER - if (sizeof(long) == sizeof(int) && - // Manually take care of the type equivalence. - ((a == dtype::get_builtin() || a == dtype::get_builtin()) && - (b == dtype::get_builtin() || b == dtype::get_builtin()) || - (a == dtype::get_builtin() || a == dtype::get_builtin()) && - (b == dtype::get_builtin() || b == dtype::get_builtin()))) { - return true; - } else { - return PyArray_EquivTypes( - reinterpret_cast(a.ptr()), - reinterpret_cast(b.ptr()) - ); - } -#else - return PyArray_EquivTypes( - reinterpret_cast(a.ptr()), - reinterpret_cast(b.ptr()) - ); -#endif + return a == b; } namespace From f6d20e1099fbc6df41a037594a67300b22070812 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Mon, 11 Mar 2024 08:38:16 -0500 Subject: [PATCH 13/66] Make the library modular usable. --- build.jam | 42 ++++++++++++++++++++++++++++++++ build/Jamfile | 61 ++++++++++++++++++++++------------------------- example/README.md | 2 +- test/Jamfile | 8 ++++--- 4 files changed, 76 insertions(+), 37 deletions(-) create mode 100644 build.jam diff --git a/build.jam b/build.jam new file mode 100644 index 000000000..f5bd09974 --- /dev/null +++ b/build.jam @@ -0,0 +1,42 @@ +# Copyright RenĂ© Ferdinand Rivera Morell 2024 +# 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 project ; + +project /boost/python + : common-requirements + /boost/align//boost_align + /boost/bind//boost_bind + /boost/config//boost_config + /boost/conversion//boost_conversion + /boost/core//boost_core + /boost/detail//boost_detail + /boost/foreach//boost_foreach + /boost/function//boost_function + /boost/graph//boost_graph + /boost/integer//boost_integer + /boost/iterator//boost_iterator + /boost/lexical_cast//boost_lexical_cast + /boost/mpl//boost_mpl + /boost/numeric_conversion//boost_numeric_conversion + /boost/preprocessor//boost_preprocessor + /boost/property_map//boost_property_map + /boost/smart_ptr//boost_smart_ptr + /boost/static_assert//boost_static_assert + /boost/tuple//boost_tuple + /boost/type_traits//boost_type_traits + /boost/utility//boost_utility + include + ; + +explicit + [ alias boost_python : build//boost_python ] + [ alias boost_numpy : build//boost_numpy ] + [ alias all : boost_python boost_numpy test ] + ; + +call-if : boost-library python + : install boost_python boost_numpy + ; diff --git a/build/Jamfile b/build/Jamfile index 03b69a25d..d85c165ba 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -30,7 +30,7 @@ else ; } -project boost/python +project : source-location ../src ; @@ -38,6 +38,17 @@ rule cond ( test ? : yes * : no * ) { if $(test) { return $(yes) ; } else { retu rule unless ( test ? : yes * : no * ) { if ! $(test) { return $(yes) ; } else { return $(no) ; } } local rule eq ( a : b ) { if $(a) = $(b) { return 1 ; } } +rule tag ( name : type ? : property-set ) +{ + if python-tag in [ RULENAMES $(__name__) ] + { + return [ $(__name__).python-tag $(name) : $(type) : $(property-set) ] ; + } +} + +if [ python.configured ] +{ + lib boost_python : # sources list.cpp @@ -92,8 +103,8 @@ lib boost_python [ unless [ python.configured ] : no ] config-warning on:BOOST_DEBUG_PYTHON - -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag - @python-tag + -@%boostcpp.tag + @tag @python.require-py : # default build @@ -103,6 +114,17 @@ lib boost_python on:BOOST_DEBUG_PYTHON ; +} +else +{ + +alias boost_python : config-warning ; + +} + +if [ python.configured ] && [ python.numpy ] +{ + numpy-include = [ python.numpy-include ] ; lib boost_numpy : # sources @@ -120,8 +142,8 @@ lib boost_numpy /python//numpy boost_python on:BOOST_DEBUG_PYTHON - -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag - @python-tag + -@%boostcpp.tag + @tag @python.require-py : # default build @@ -131,37 +153,10 @@ lib boost_numpy on:BOOST_DEBUG_PYTHON ; -# boost-install creates `stage` and `install` targets -# -# `stage` stages (builds and copies into `stage/lib`) the given libraries -# `boost_python` and `boost_numpy` and their dependencies and is similar -# to issuing `b2 --with-python stage` from top level -# -# `install` installs the two libraries and their dependencies and is similar -# to issuing `b2 --with-python install` from top level - -if [ python.configured ] -{ - if [ python.numpy ] - { - boost-install boost_python boost_numpy ; - } - else - { - boost-install boost_python ; - } } else { -# When Python isn't configured, the above `boost-install` is not executed, -# so we create empty `stage` and `install` targets that do nothing but issue -# a warning message unless `--without-python` is given - -alias stage : config-warning ; -explicit stage ; - -alias install : config-warning ; -explicit install ; +alias boost_numpy : config-warning ; } diff --git a/example/README.md b/example/README.md index b090cbe1e..af03f20ba 100644 --- a/example/README.md +++ b/example/README.md @@ -3,7 +3,7 @@ # Examples This directory contains various examples using Boost.Python. -You may compile these using the `bjam` command either in this directory +You may compile these using the `b2` command either in this directory or in any of the subdirectories. You may need to adjust the paths in the Jamroot file if Boost.Python is not installed in a default location. diff --git a/test/Jamfile b/test/Jamfile index 07cbd4c7e..48e57487f 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -2,14 +2,16 @@ # Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +require-b2 5.0.1 ; +import-search /boost/config/checks ; + import python ; import os ; -import ../../config/checks/config : requires ; +import config : requires ; lib socket ; -use-project /boost/python : ../build ; -project /boost/python/test +project : requirements gcc:-Wextra qnxnto:socket From d8d9861036eed75918fe50d72cad98a4c7f3160d Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Sun, 17 Mar 2024 18:33:44 -0500 Subject: [PATCH 14/66] Put back removing qualified boostcpp tag. As we need it until the Jamroot removes the qualified tag. --- build/Jamfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/Jamfile b/build/Jamfile index d85c165ba..64843288a 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -104,6 +104,7 @@ lib boost_python config-warning on:BOOST_DEBUG_PYTHON -@%boostcpp.tag + -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag @tag @python.require-py @@ -143,6 +144,7 @@ lib boost_numpy boost_python on:BOOST_DEBUG_PYTHON -@%boostcpp.tag + -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag @tag @python.require-py From 071b0bc964323f2f3eb1cee1624f2a3e00679071 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Fri, 29 Mar 2024 21:15:59 -0500 Subject: [PATCH 15/66] Switch to library requirements instead of source. As source puts extra source in install targets. --- build.jam | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/build.jam b/build.jam index f5bd09974..d03ca41b1 100644 --- a/build.jam +++ b/build.jam @@ -7,27 +7,27 @@ import project ; project /boost/python : common-requirements - /boost/align//boost_align - /boost/bind//boost_bind - /boost/config//boost_config - /boost/conversion//boost_conversion - /boost/core//boost_core - /boost/detail//boost_detail - /boost/foreach//boost_foreach - /boost/function//boost_function - /boost/graph//boost_graph - /boost/integer//boost_integer - /boost/iterator//boost_iterator - /boost/lexical_cast//boost_lexical_cast - /boost/mpl//boost_mpl - /boost/numeric_conversion//boost_numeric_conversion - /boost/preprocessor//boost_preprocessor - /boost/property_map//boost_property_map - /boost/smart_ptr//boost_smart_ptr - /boost/static_assert//boost_static_assert - /boost/tuple//boost_tuple - /boost/type_traits//boost_type_traits - /boost/utility//boost_utility + /boost/align//boost_align + /boost/bind//boost_bind + /boost/config//boost_config + /boost/conversion//boost_conversion + /boost/core//boost_core + /boost/detail//boost_detail + /boost/foreach//boost_foreach + /boost/function//boost_function + /boost/graph//boost_graph + /boost/integer//boost_integer + /boost/iterator//boost_iterator + /boost/lexical_cast//boost_lexical_cast + /boost/mpl//boost_mpl + /boost/numeric_conversion//boost_numeric_conversion + /boost/preprocessor//boost_preprocessor + /boost/property_map//boost_property_map + /boost/smart_ptr//boost_smart_ptr + /boost/static_assert//boost_static_assert + /boost/tuple//boost_tuple + /boost/type_traits//boost_type_traits + /boost/utility//boost_utility include ; From 9ab1742c465ae08aacf79edd922c7e4989437442 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Sun, 28 Apr 2024 20:16:29 -0500 Subject: [PATCH 16/66] Add missing NO_LIB usage requirements. --- build/Jamfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/Jamfile b/build/Jamfile index 64843288a..76042bb12 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -113,6 +113,7 @@ lib boost_python : # usage requirements static:BOOST_PYTHON_STATIC_LIB on:BOOST_DEBUG_PYTHON + BOOST_PYTHON_NO_LIB ; } @@ -153,6 +154,7 @@ lib boost_numpy : # usage requirements static:BOOST_NUMPY_STATIC_LIB on:BOOST_DEBUG_PYTHON + BOOST_NUMPY_NO_LIB ; } From 06fa956fe83a0d44d143c45d92d67bd00c3d91aa Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Sun, 5 May 2024 09:00:01 -0500 Subject: [PATCH 17/66] Add requires-b2 check to top-level build file. --- build.jam | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.jam b/build.jam index d03ca41b1..dbd0f1ffa 100644 --- a/build.jam +++ b/build.jam @@ -3,6 +3,8 @@ # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) +require-b2 5.1 ; + import project ; project /boost/python From 5a07cdb96bba9a310106094a7f1aa262a006700b Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Fri, 14 Jun 2024 11:33:56 -0500 Subject: [PATCH 18/66] Bump B2 require to 5.2 --- build.jam | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.jam b/build.jam index dbd0f1ffa..77aea7b46 100644 --- a/build.jam +++ b/build.jam @@ -3,9 +3,7 @@ # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) -require-b2 5.1 ; - -import project ; +require-b2 5.2 ; project /boost/python : common-requirements From 30bdbf3ae2097e0ae8f3887589b1136373875511 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Tue, 23 Jul 2024 22:34:22 -0500 Subject: [PATCH 19/66] Move inter-lib dependencies to a project variable and into the build targets. --- build.jam | 45 ++++++++++++++++++++++++--------------------- build/Jamfile | 1 + 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/build.jam b/build.jam index 77aea7b46..7adb65de6 100644 --- a/build.jam +++ b/build.jam @@ -5,29 +5,31 @@ require-b2 5.2 ; +constant boost_dependencies : + /boost/align//boost_align + /boost/bind//boost_bind + /boost/config//boost_config + /boost/conversion//boost_conversion + /boost/core//boost_core + /boost/detail//boost_detail + /boost/foreach//boost_foreach + /boost/function//boost_function + /boost/graph//boost_graph + /boost/integer//boost_integer + /boost/iterator//boost_iterator + /boost/lexical_cast//boost_lexical_cast + /boost/mpl//boost_mpl + /boost/numeric_conversion//boost_numeric_conversion + /boost/preprocessor//boost_preprocessor + /boost/property_map//boost_property_map + /boost/smart_ptr//boost_smart_ptr + /boost/static_assert//boost_static_assert + /boost/tuple//boost_tuple + /boost/type_traits//boost_type_traits + /boost/utility//boost_utility ; + project /boost/python : common-requirements - /boost/align//boost_align - /boost/bind//boost_bind - /boost/config//boost_config - /boost/conversion//boost_conversion - /boost/core//boost_core - /boost/detail//boost_detail - /boost/foreach//boost_foreach - /boost/function//boost_function - /boost/graph//boost_graph - /boost/integer//boost_integer - /boost/iterator//boost_iterator - /boost/lexical_cast//boost_lexical_cast - /boost/mpl//boost_mpl - /boost/numeric_conversion//boost_numeric_conversion - /boost/preprocessor//boost_preprocessor - /boost/property_map//boost_property_map - /boost/smart_ptr//boost_smart_ptr - /boost/static_assert//boost_static_assert - /boost/tuple//boost_tuple - /boost/type_traits//boost_type_traits - /boost/utility//boost_utility include ; @@ -40,3 +42,4 @@ explicit call-if : boost-library python : install boost_python boost_numpy ; + diff --git a/build/Jamfile b/build/Jamfile index 76042bb12..dfc1e92d6 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -32,6 +32,7 @@ else project : source-location ../src + : common-requirements $(boost_dependencies) ; rule cond ( test ? : yes * : no * ) { if $(test) { return $(yes) ; } else { return $(no) ; } } From 5a8d09613581f771fe272cead39e0cee7d41fd66 Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Fri, 26 Jul 2024 17:39:24 -0500 Subject: [PATCH 20/66] Split b2 dependencies into public and private. --- build.jam | 4 ---- build/Jamfile | 8 ++++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build.jam b/build.jam index 7adb65de6..e9eb1a11a 100644 --- a/build.jam +++ b/build.jam @@ -14,15 +14,11 @@ constant boost_dependencies : /boost/detail//boost_detail /boost/foreach//boost_foreach /boost/function//boost_function - /boost/graph//boost_graph - /boost/integer//boost_integer /boost/iterator//boost_iterator /boost/lexical_cast//boost_lexical_cast /boost/mpl//boost_mpl /boost/numeric_conversion//boost_numeric_conversion /boost/preprocessor//boost_preprocessor - /boost/property_map//boost_property_map - /boost/smart_ptr//boost_smart_ptr /boost/static_assert//boost_static_assert /boost/tuple//boost_tuple /boost/type_traits//boost_type_traits diff --git a/build/Jamfile b/build/Jamfile index dfc1e92d6..c8f9859c6 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -30,9 +30,17 @@ else ; } +constant boost_dependencies_private : + /boost/graph//boost_graph + /boost/integer//boost_integer + /boost/property_map//boost_property_map + /boost/smart_ptr//boost_smart_ptr + ; + project : source-location ../src : common-requirements $(boost_dependencies) + : requirements $(boost_dependencies_private) ; rule cond ( test ? : yes * : no * ) { if $(test) { return $(yes) ; } else { return $(no) ; } } From 8ca8724ad9eafb5a07a437fb0a676235f1c1cdec Mon Sep 17 00:00:00 2001 From: Rene Rivera Date: Wed, 7 Aug 2024 23:28:26 -0500 Subject: [PATCH 21/66] Update build deps. --- test/Jamfile | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/test/Jamfile b/test/Jamfile index 48e57487f..40115d86c 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -30,7 +30,7 @@ rule py-run ( sources * : input-file ? ) : $(input-file) : #requirements BOOST_PYTHON_SUPPRESS_REGISTRY_INITIALIZATION - + ] ; } @@ -54,6 +54,18 @@ rule require-windows ( properties * ) if [ python.configured ] { +alias base_deps : usage-requirements + /boost/align//boost_align + /boost/assert//boost_assert + /boost/config//boost_config + /boost/core//boost_core + /boost/detail//boost_detail + /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 : @@ -99,8 +111,8 @@ bpl-test crossmod_exception [ bpl-test andreas_beyer ] [ bpl-test wrapper_held_type ] -[ bpl-test polymorphism2_auto_ptr - : polymorphism2_auto_ptr.py polymorphism2.py polymorphism2_auto_ptr.cpp +[ bpl-test polymorphism2_auto_ptr + : polymorphism2_auto_ptr.py polymorphism2.py polymorphism2_auto_ptr.cpp : [ requires auto_ptr ] ] @@ -121,7 +133,7 @@ bpl-test crossmod_exception [ bpl-test try : newtest.py m1.cpp m2.cpp ] [ bpl-test const_argument ] [ bpl-test keywords : keywords.cpp keywords_test.py ] - + [ python-extension builtin_converters_ext : builtin_converters.cpp /boost/python//boost_python ] [ bpl-test builtin_converters : test_builtin_converters.py builtin_converters_ext ] @@ -194,13 +206,13 @@ bpl-test crossmod_opaque # Whenever the cause for the failure of the polymorphism test is found # and fixed, this should be retested. hp_cxx:no ] - + [ python-extension map_indexing_suite_ext : map_indexing_suite.cpp int_map_indexing_suite.cpp a_map_indexing_suite.cpp /boost/python//boost_python ] -[ bpl-test +[ bpl-test map_indexing_suite : map_indexing_suite.py map_indexing_suite_ext ] - + [ run import_.cpp /boost/python//boost_python $(PY) : : import_.py ] # if $(TEST_BIENSTMAN_NON_BUGS) @@ -214,28 +226,29 @@ bpl-test crossmod_opaque # --- unit tests of library components --- -[ compile indirect_traits_test.cpp ] -[ run destroy_test.cpp ] +[ compile indirect_traits_test.cpp : base_deps ] +[ run destroy_test.cpp : : : base_deps ] [ py-run pointer_type_id_test.cpp ] [ py-run bases.cpp ] -[ run if_else.cpp ] +[ run if_else.cpp : : : base_deps ] [ py-run pointee.cpp ] -[ run result.cpp ] +[ run result.cpp : : : base_deps ] -[ compile string_literal.cpp ] +[ compile string_literal.cpp : base_deps ] [ py-compile borrowed.cpp ] [ py-compile object_manager.cpp ] [ py-compile copy_ctor_mutates_rhs.cpp ] [ py-run upcast.cpp ] - + [ py-compile select_holder.cpp ] - -[ run select_from_python_test.cpp ../src/converter/type_id.cpp - : + +[ run select_from_python_test.cpp ../src/converter/type_id.cpp + : : : BOOST_PYTHON_STATIC_LIB $(PY) + base_deps ] From 58b1a010bb49f9709af0835eada5a474841b956e Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Fri, 21 Apr 2023 17:16:41 +0200 Subject: [PATCH 22/66] Set __qualname__ for Python >= 3.3 --- src/object/class.cpp | 13 +++++++++++++ test/nested.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/object/class.cpp b/src/object/class.cpp index 8778b7abc..5b5095151 100644 --- a/src/object/class.cpp +++ b/src/object/class.cpp @@ -502,6 +502,16 @@ namespace objects ); } + str qualname(const char *name) + { +#if PY_VERSION_HEX >= 0x03030000 + if (PyObject_HasAttrString(scope().ptr(), "__qualname__")) { + return str("%s.%s" % make_tuple(scope().attr("__qualname__"), name)); + } +#endif + return str(name); + } + namespace { // Find a registered class object corresponding to id. Return a @@ -564,6 +574,9 @@ namespace objects object m = module_prefix(); if (m) d["__module__"] = m; +#if PY_VERSION_HEX >= 0x03030000 + d["__qualname__"] = qualname(name); +#endif if (doc != 0) d["__doc__"] = doc; diff --git a/test/nested.py b/test/nested.py index 720790173..56f81e526 100644 --- a/test/nested.py +++ b/test/nested.py @@ -14,7 +14,7 @@ 'X' >>> X.Y - + >>> X.Y.__module__ 'nested_ext' From 7a3cc070423a4cba35b5d2585cd680349c84387a Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Mon, 24 Apr 2023 11:14:55 +0200 Subject: [PATCH 23/66] Emit qualfied names in docstrings --- .../boost/python/object/function_doc_signature.hpp | 2 +- src/object/function_doc_signature.cpp | 13 +++++++++---- test/nested.cpp | 7 +++++++ test/nested.py | 3 +++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/include/boost/python/object/function_doc_signature.hpp b/include/boost/python/object/function_doc_signature.hpp index 4f00cb385..5fa2c19dd 100644 --- a/include/boost/python/object/function_doc_signature.hpp +++ b/include/boost/python/object/function_doc_signature.hpp @@ -18,7 +18,7 @@ namespace boost { namespace python { namespace objects { class function_doc_signature_generator{ - static const char * py_type_str(const python::detail::signature_element &s); + static str py_type_str(const python::detail::signature_element &s); static bool arity_cmp( function const *f1, function const *f2 ); static bool are_seq_overloads( function const *f1, function const *f2 , bool check_docs); static std::vector flatten(function const *f); diff --git a/src/object/function_doc_signature.cpp b/src/object/function_doc_signature.cpp index 41695285a..c5a549884 100644 --- a/src/object/function_doc_signature.cpp +++ b/src/object/function_doc_signature.cpp @@ -114,19 +114,24 @@ namespace boost { namespace python { namespace objects { return res; } - const char * function_doc_signature_generator::py_type_str(const python::detail::signature_element &s) + str function_doc_signature_generator::py_type_str(const python::detail::signature_element &s) { if (s.basename==std::string("void")){ static const char * none = "None"; - return none; + return str(none); } PyTypeObject const * py_type = s.pytype_f?s.pytype_f():0; +#if PY_VERSION_HEX < 0x03030000 if ( py_type ) - return py_type->tp_name; + return str(py_type->tp_name); +#else + if ( py_type && (py_type->tp_flags & Py_TPFLAGS_HEAPTYPE) ) + return str(handle<>(borrowed(((PyHeapTypeObject*)(py_type))->ht_qualname))); +#endif else{ static const char * object = "object"; - return object; + return str(object); } } diff --git a/test/nested.cpp b/test/nested.cpp index de656d2b8..b807c8bdb 100644 --- a/test/nested.cpp +++ b/test/nested.cpp @@ -4,6 +4,7 @@ // http://www.boost.org/LICENSE_1_0.txt) #include #include +#include #include #include #include "test_class.hpp" @@ -26,11 +27,13 @@ std::ostream& operator<<(std::ostream& s, Y const& x) return s << x.value(); } +void test_function(const X& x, const Y& y) {} BOOST_PYTHON_MODULE(nested_ext) { using namespace boost::python; + { // Establish X as the current scope. scope x_class = class_("X", init()) @@ -42,6 +45,10 @@ BOOST_PYTHON_MODULE(nested_ext) class_("Y", init()) .def(str(self)) ; + } + + // The generated docstring will use the fully-qualified name of Y + def("test_function", &test_function); } diff --git a/test/nested.py b/test/nested.py index 56f81e526..3d97208b7 100644 --- a/test/nested.py +++ b/test/nested.py @@ -21,6 +21,9 @@ >>> X.Y.__name__ 'Y' + + >>> test_function.__doc__.strip().split('\\n')[0] + 'test_function( (X)arg1, (X.Y)arg2) -> None :' ''' From a498e2458c3ee697176790b6163c6df539f7e62e Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Tue, 25 Apr 2023 15:46:28 +0200 Subject: [PATCH 24/66] Qualify types defined in other modules --- include/boost/python/object/function.hpp | 3 ++ .../python/object/function_doc_signature.hpp | 4 +- src/object/function.cpp | 17 ++++++- src/object/function_doc_signature.cpp | 47 +++++++++++++------ 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/include/boost/python/object/function.hpp b/include/boost/python/object/function.hpp index f29d34482..b1a1676b6 100644 --- a/include/boost/python/object/function.hpp +++ b/include/boost/python/object/function.hpp @@ -42,6 +42,8 @@ struct BOOST_PYTHON_DECL function : PyObject object const& get_namespace() const { return m_namespace; } + object const& get_module() const { return m_module; } + private: // helper functions object signature(bool show_return_type=false) const; object signatures(bool show_return_type=false) const; @@ -53,6 +55,7 @@ struct BOOST_PYTHON_DECL function : PyObject handle m_overloads; object m_name; object m_namespace; + object m_module; object m_doc; object m_arg_names; unsigned m_nkeyword_values; diff --git a/include/boost/python/object/function_doc_signature.hpp b/include/boost/python/object/function_doc_signature.hpp index 5fa2c19dd..91c90895c 100644 --- a/include/boost/python/object/function_doc_signature.hpp +++ b/include/boost/python/object/function_doc_signature.hpp @@ -18,13 +18,13 @@ namespace boost { namespace python { namespace objects { class function_doc_signature_generator{ - static str py_type_str(const python::detail::signature_element &s); + static str py_type_str(const python::detail::signature_element &s, const object& current_module_name); static bool arity_cmp( function const *f1, function const *f2 ); static bool are_seq_overloads( function const *f1, function const *f2 , bool check_docs); static std::vector flatten(function const *f); static std::vector split_seq_overloads( const std::vector &funcs, bool split_on_doc_change); static str raw_function_pretty_signature(function const *f, size_t n_overloads, bool cpp_types = false); - static str parameter_string(py_function const &f, size_t n, object arg_names, bool cpp_types); + static str parameter_string(py_function const &f, size_t n, object arg_names, const object& module_name, bool cpp_types); static str pretty_signature(function const *f, size_t n_overloads, bool cpp_types = false); public: diff --git a/src/object/function.cpp b/src/object/function.cpp index 4adb49453..a220befd5 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -489,11 +489,24 @@ void function::add_to_namespace( assert(!PyErr_Occurred()); handle<> name_space_name( - allow_null(::PyObject_GetAttrString(name_space.ptr(), const_cast("__name__")))); + allow_null(::PyObject_GetAttrString(name_space.ptr(), const_cast( +#if PY_VERSION_HEX < 0x03030000 + "__name__" +#else + "__qualname__" +#endif + )))); PyErr_Clear(); if (name_space_name) new_func->m_namespace = object(name_space_name); + + object module_name( + PyObject_IsInstance(name_space.ptr(), upcast(&PyModule_Type)) + ? object(name_space.attr("__name__")) + : api::getattr(name_space, "__module__", str()) + ); + new_func->m_module = module_name; } if (PyObject_SetAttr(ns, name.ptr(), attribute.ptr()) < 0) @@ -670,7 +683,7 @@ extern "C" static PyObject* function_get_module(PyObject* op, void*) { function* f = downcast(op); - object const& ns = f->get_namespace(); + object const& ns = f->get_module(); if (!ns.is_none()) { return python::incref(ns.ptr()); } diff --git a/src/object/function_doc_signature.cpp b/src/object/function_doc_signature.cpp index c5a549884..18d458698 100644 --- a/src/object/function_doc_signature.cpp +++ b/src/object/function_doc_signature.cpp @@ -114,7 +114,16 @@ namespace boost { namespace python { namespace objects { return res; } - str function_doc_signature_generator::py_type_str(const python::detail::signature_element &s) + static str get_qualname(const PyTypeObject *py_type) + { +# if PY_VERSION_HEX >= 0x03030000 + if ( py_type->tp_flags & Py_TPFLAGS_HEAPTYPE ) + return str(handle<>(borrowed(((PyHeapTypeObject*)(py_type))->ht_qualname))); +# endif + return str(py_type->tp_name); + } + + str function_doc_signature_generator::py_type_str(const python::detail::signature_element &s, const object ¤t_module_name) { if (s.basename==std::string("void")){ static const char * none = "None"; @@ -122,20 +131,30 @@ namespace boost { namespace python { namespace objects { } PyTypeObject const * py_type = s.pytype_f?s.pytype_f():0; -#if PY_VERSION_HEX < 0x03030000 - if ( py_type ) - return str(py_type->tp_name); -#else - if ( py_type && (py_type->tp_flags & Py_TPFLAGS_HEAPTYPE) ) - return str(handle<>(borrowed(((PyHeapTypeObject*)(py_type))->ht_qualname))); -#endif - else{ + if ( py_type ) { + 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__"); + if ( + type_module_name + && PyObject_RichCompareBool( + type_module_name, + current_module_name.ptr(), + Py_NE + ) != 0 + ) { + return str("%s.%s" % make_tuple(handle<>(borrowed(type_module_name)), name)); + } + } + return name; + } else { static const char * object = "object"; return str(object); } } - str function_doc_signature_generator::parameter_string(py_function const &f, size_t n, object arg_names, bool cpp_types) + str function_doc_signature_generator::parameter_string(py_function const &f, size_t n, object arg_names, const object& current_module_name, bool cpp_types) { str param; @@ -161,12 +180,12 @@ namespace boost { namespace python { namespace objects { { object kv; if ( arg_names && (kv = arg_names[n-1]) ) - param = str( " (%s)%s" % make_tuple(py_type_str(s[n]),kv[0]) ); + param = str( " (%s)%s" % make_tuple(py_type_str(s[n], current_module_name),kv[0]) ); else - param = str(" (%s)%s%d" % make_tuple(py_type_str(s[n]),"arg", n) ); + param = str(" (%s)%s%d" % make_tuple(py_type_str(s[n], current_module_name),"arg", n) ); } else //we are processing the return type - param = py_type_str(f.get_return_type()); + param = py_type_str(f.get_return_type(), current_module_name); } //an argument - check for default value and append it @@ -204,7 +223,7 @@ namespace boost { namespace python { namespace objects { str param; formal_params.append( - parameter_string(impl, n, f->m_arg_names, cpp_types) + parameter_string(impl, n, f->m_arg_names, f->get_module(), cpp_types) ); // find all the arguments with default values preceeding the arity-n_overloads From c4e3b13dc2cf510d9824474da9435644ce821c62 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Tue, 25 Apr 2023 16:29:12 +0200 Subject: [PATCH 25/66] Use qualname for enum repr --- src/object/enum.cpp | 14 ++++++++++++-- test/nested.cpp | 10 ++++++++++ test/nested.py | 9 +++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/object/enum.cpp b/src/object/enum.cpp index 5753b32e0..94df8e4ae 100644 --- a/src/object/enum.cpp +++ b/src/object/enum.cpp @@ -49,7 +49,9 @@ extern "C" if (!self->name) { return -#if PY_VERSION_HEX >= 0x03000000 +#if PY_VERSION_HEX >= 0x03030000 + PyUnicode_FromFormat("%S.%S(%ld)", mod, ((PyHeapTypeObject*)(self_->ob_type))->ht_qualname, PyLong_AsLong(self_)); +#elif PY_VERSION_HEX >= 0x03000000 PyUnicode_FromFormat("%S.%s(%ld)", mod, self_->ob_type->tp_name, PyLong_AsLong(self_)); #else PyString_FromFormat("%s.%s(%ld)", PyString_AsString(mod), self_->ob_type->tp_name, PyInt_AS_LONG(self_)); @@ -62,7 +64,9 @@ extern "C" return 0; return -#if PY_VERSION_HEX >= 0x03000000 +#if PY_VERSION_HEX >= 0x03030000 + PyUnicode_FromFormat("%S.%S.%S", mod, ((PyHeapTypeObject*)(self_->ob_type))->ht_qualname, name); +#elif PY_VERSION_HEX >= 0x03000000 PyUnicode_FromFormat("%S.%s.%S", mod, self_->ob_type->tp_name, name); #else PyString_FromFormat("%s.%s.%s", @@ -145,6 +149,7 @@ static PyTypeObject enum_type_object = { }; object module_prefix(); +object qualname(const char *name); namespace { @@ -175,6 +180,11 @@ namespace object module_name = module_prefix(); if (module_name) d["__module__"] = module_name; +#if PY_VERSION_HEX >= 0x03030000 + object q = qualname(name); + if (q) + d["__qualname__"] = q; +#endif if (doc) d["__doc__"] = doc; diff --git a/test/nested.cpp b/test/nested.cpp index b807c8bdb..3a0d05e5e 100644 --- a/test/nested.cpp +++ b/test/nested.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "test_class.hpp" @@ -17,6 +18,8 @@ typedef test_class<> X; typedef test_class<1> Y; +enum color { red = 0, blue = 1, green = 2 }; + std::ostream& operator<<(std::ostream& s, X const& x) { return s << x.value(); @@ -45,6 +48,13 @@ BOOST_PYTHON_MODULE(nested_ext) class_("Y", init()) .def(str(self)) ; + + // so will the enum `color` + enum_("color") + .value("red", red) + .value("green", green) + .value("blue", blue) + ; } // The generated docstring will use the fully-qualified name of Y diff --git a/test/nested.py b/test/nested.py index 3d97208b7..f53e7af50 100644 --- a/test/nested.py +++ b/test/nested.py @@ -22,6 +22,15 @@ >>> X.Y.__name__ 'Y' + >>> X.color.__qualname__ + 'X.color' + + >>> repr(X.color.red) + 'nested_ext.X.color.red' + + >>> repr(X.color(1)) + 'nested_ext.X.color(1)' + >>> test_function.__doc__.strip().split('\\n')[0] 'test_function( (X)arg1, (X.Y)arg2) -> None :' From d1910f3d6567a33b7d55d5421297988cf82c6e4d Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Wed, 26 Apr 2023 21:03:32 +0200 Subject: [PATCH 26/66] Avoid degrading slice to object in generated sig --- src/slice.cpp | 10 ++++++++++ test/slice.py | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/slice.cpp b/src/slice.cpp index ee55f9484..5ff56185d 100644 --- a/src/slice.cpp +++ b/src/slice.cpp @@ -34,4 +34,14 @@ slice_base::step() const ((PySliceObject*)this->ptr())->step)); } +static struct register_slice_pytype_ptr +{ + register_slice_pytype_ptr() + { + const_cast( + converter::registry::lookup(boost::python::type_id()) + ).m_class_object = &PySlice_Type; + } +}register_slice_pytype_ptr_; + } } } // !namespace boost::python::detail diff --git a/test/slice.py b/test/slice.py index c58bbc029..041934cf5 100644 --- a/test/slice.py +++ b/test/slice.py @@ -33,6 +33,8 @@ 0 >>> check_slice_get_indices( slice( -2, -5, -2)) 6 +>>> check_slice_get_indices.__doc__.strip().split('\\n')[0] +'check_slice_get_indices( (slice)arg1) -> int :' """ # Performs an affirmative and negative argument resolution check. From 4c6f40fb8223e1996385ed6a74d56d96b79b4205 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Thu, 27 Apr 2023 09:52:03 +0200 Subject: [PATCH 27/66] Add generated docstrings to property fget/fset --- include/boost/python/class.hpp | 10 ++-- .../boost/python/object/add_to_namespace.hpp | 2 + include/boost/python/object/function.hpp | 2 + src/object/function.cpp | 48 ++++++++++++------- test/properties.cpp | 1 + test/properties.py | 22 +++++++++ 6 files changed, 63 insertions(+), 22 deletions(-) diff --git a/include/boost/python/class.hpp b/include/boost/python/class.hpp index 77f915ba0..0f1c0fdc1 100644 --- a/include/boost/python/class.hpp +++ b/include/boost/python/class.hpp @@ -372,10 +372,11 @@ class class_ : public objects::class_base { typedef typename api::is_object_operators::type is_obj_or_proxy; - return this->make_fn_impl( + return objects::add_doc( + this->make_fn_impl( detail::unwrap_wrapper((W*)0) , f, is_obj_or_proxy(), (char*)0, detail::is_data_member_pointer() - ); + ), NULL); } template @@ -383,10 +384,11 @@ class class_ : public objects::class_base { typedef typename api::is_object_operators::type is_obj_or_proxy; - return this->make_fn_impl( + return objects::add_doc( + this->make_fn_impl( detail::unwrap_wrapper((W*)0) , f, is_obj_or_proxy(), (int*)0, detail::is_data_member_pointer() - ); + ), NULL); } template diff --git a/include/boost/python/object/add_to_namespace.hpp b/include/boost/python/object/add_to_namespace.hpp index 9f4167d6d..e81186790 100644 --- a/include/boost/python/object/add_to_namespace.hpp +++ b/include/boost/python/object/add_to_namespace.hpp @@ -18,6 +18,8 @@ BOOST_PYTHON_DECL void add_to_namespace( BOOST_PYTHON_DECL void add_to_namespace( object const& name_space, char const* name, object const& attribute, char const* doc); +BOOST_PYTHON_DECL object const& add_doc(object const& attribute, char const* doc); + }}} // namespace boost::python::objects #endif // ADD_TO_NAMESPACE_DWA200286_HPP diff --git a/include/boost/python/object/function.hpp b/include/boost/python/object/function.hpp index b1a1676b6..ec1fc3d38 100644 --- a/include/boost/python/object/function.hpp +++ b/include/boost/python/object/function.hpp @@ -35,6 +35,8 @@ struct BOOST_PYTHON_DECL function : PyObject static void add_to_namespace( object const& name_space, char const* name, object const& attribute, char const* doc); + static object const& add_doc(object const& attribute, char const* doc); + object const& doc() const; void doc(object const& x); diff --git a/src/object/function.cpp b/src/object/function.cpp index a220befd5..ec787cf15 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -419,6 +419,30 @@ namespace detail extern char cpp_signature_tag[]; } +object const& function::add_doc(object const& attribute, char const* doc) +{ + str _doc; + + if (docstring_options::show_py_signatures_) + { + _doc += str(const_cast(detail::py_signature_tag)); + } + if (doc != 0 && docstring_options::show_user_defined_) + _doc += doc; + + if (docstring_options::show_cpp_signatures_) + { + _doc += str(const_cast(detail::cpp_signature_tag)); + } + if(_doc) + { + object mutable_attribute(attribute); + mutable_attribute.attr("__doc__")= _doc; + } + + return attribute; +} + void function::add_to_namespace( object const& name_space, char const* name_, object const& attribute, char const* doc) { @@ -545,24 +569,7 @@ void function::add_to_namespace( "C++ signature:", f->signature(true))); } */ - str _doc; - - if (docstring_options::show_py_signatures_) - { - _doc += str(const_cast(detail::py_signature_tag)); - } - if (doc != 0 && docstring_options::show_user_defined_) - _doc += doc; - - if (docstring_options::show_cpp_signatures_) - { - _doc += str(const_cast(detail::cpp_signature_tag)); - } - if(_doc) - { - object mutable_attribute(attribute); - mutable_attribute.attr("__doc__")= _doc; - } + add_doc(attribute, doc); } BOOST_PYTHON_DECL void add_to_namespace( @@ -577,6 +584,11 @@ BOOST_PYTHON_DECL void add_to_namespace( function::add_to_namespace(name_space, name, attribute, doc); } +BOOST_PYTHON_DECL object const& add_doc(object const& attribute, char const* doc) +{ + return function::add_doc(attribute, doc); +} + namespace { diff --git a/test/properties.cpp b/test/properties.cpp index d338beb91..aa1b0a05c 100644 --- a/test/properties.cpp +++ b/test/properties.cpp @@ -64,6 +64,7 @@ BOOST_PYTHON_MODULE(properties_ext) class_("X", init() ) //defining read only property .add_property( "value_r", &X::get_value ) + .add_property( "value_r_f", make_function(&X::get_value) ) .add_property( "value_r_ds", &X::get_value, "value_r_ds is read-only") //defining read \ write property .add_property( "value_rw", &X::get_value, &X::set_value ) diff --git a/test/properties.py b/test/properties.py index 1bc7a624b..88374b410 100644 --- a/test/properties.py +++ b/test/properties.py @@ -20,6 +20,9 @@ >>> x1.value_r 1 +>>> x1.value_r_f +1 + value read - write >>> x1.value_rw 1 @@ -84,8 +87,27 @@ class instance count from object: >>> assert properties.X.value_rw_ds.__doc__ == "value_rw_ds is read-write" +>>> properties.X.value_r_f.fget.__doc__.strip().split("\\n")[0] +'None( (properties_ext.X)arg1) -> int :' + +>>> properties.X.value_rw_ds.fget.__doc__.strip().split("\\n")[0] +'None( (properties_ext.X)arg1) -> int :' + +>>> properties.X.value_rw_ds.fset.__doc__.strip().split("\\n")[0] +'None( (properties_ext.X)arg1, (int)arg2) -> None :' + +>>> properties.X.value_rw_ds.fget.__doc__.strip().split("\\n")[0] +'None( (properties_ext.X)arg1) -> int :' + +>>> properties.X.value_direct.fset.__doc__.strip().split("\\n")[0] +'None( (properties_ext.X)arg1, (int)arg2) -> None :' + +>>> properties.X.value_direct.fget.__doc__.strip().split("\\n")[0] +'None( (properties_ext.X)arg1) -> int :' """ +# FIXME: cases to cover: pointer-to-member, preconstructed function + #import sys; sys.path.append(r'P:\Actimize4.0\smart_const\py_smart_const___Win32_Debug') import properties_ext as properties From 0102b319454feffaaad205c7eb028cc6da8210bf Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Tue, 9 May 2023 11:37:27 +0200 Subject: [PATCH 28/66] Unwrap back_reference in get_pytype() This prevents back_reference parameters from decaying to "object" in py signatures --- include/boost/python/converter/pytype_function.hpp | 8 +++++++- test/map_indexing_suite.py | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/boost/python/converter/pytype_function.hpp b/include/boost/python/converter/pytype_function.hpp index 8e0a4e799..d072b55fb 100644 --- a/include/boost/python/converter/pytype_function.hpp +++ b/include/boost/python/converter/pytype_function.hpp @@ -9,7 +9,7 @@ # include # include # include - +# include namespace boost { namespace python { @@ -46,6 +46,12 @@ inline python::type_info unwind_type_id_(boost::type* = 0, mpl::false_ * =0) return boost::python::detail::unwind_type (); } +template +inline python::type_info unwind_type_id_(boost::type >* = 0, mpl::false_ * =0) +{ + return boost::python::detail::unwind_type (); +} + inline python::type_info unwind_type_id_(boost::type* = 0, mpl::true_* =0) { return type_id(); diff --git a/test/map_indexing_suite.py b/test/map_indexing_suite.py index e772bb53f..6d3e57a10 100644 --- a/test/map_indexing_suite.py +++ b/test/map_indexing_suite.py @@ -214,6 +214,13 @@ ... i.data() 4 +##################################################################### +# Test signature... +##################################################################### + +>>> AMap.__iter__.__doc__.strip().split("\\n")[0] +'__iter__( (AMap)arg1) -> __main__.iterator :' + ##################################################################### # END.... ##################################################################### From c76d67ef3fe81eb4177cfcaa2d6486395e3aed75 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Mon, 11 Mar 2024 12:00:14 +0100 Subject: [PATCH 29/66] Ensure that virtual default implementation has the same kwargs as dispatcher --- include/boost/python/pure_virtual.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/python/pure_virtual.hpp b/include/boost/python/pure_virtual.hpp index 58e9aedef..f3b298de2 100644 --- a/include/boost/python/pure_virtual.hpp +++ b/include/boost/python/pure_virtual.hpp @@ -96,6 +96,7 @@ namespace detail , make_function( detail::nullary_function_adaptor(pure_virtual_called) , default_call_policies() + , options.keywords() , detail::error_signature(detail::get_signature(m_pmf)) ) ); From 301256cf1e3a626694a991d9548dab66daafc5e3 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Wed, 18 Sep 2024 11:48:43 +0200 Subject: [PATCH 30/66] Avoid setting __doc__ on instance methods --- src/object/function.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/object/function.cpp b/src/object/function.cpp index ec787cf15..7b6ab9f08 100644 --- a/src/object/function.cpp +++ b/src/object/function.cpp @@ -586,6 +586,13 @@ BOOST_PYTHON_DECL void add_to_namespace( BOOST_PYTHON_DECL object const& add_doc(object const& attribute, char const* doc) { +#if PY_VERSION_HEX >= 0x03000000 + if (PyInstanceMethod_Check(attribute.ptr())) { +#else + if (PyMethod_Check(attribute.ptr())) { +#endif + return attribute; + } return function::add_doc(attribute, doc); } From 95e53011d816d00d690d91392d81122808fce62c Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Wed, 18 Sep 2024 15:13:43 +0200 Subject: [PATCH 31/66] Conditionalize nested test for py2 __qualname__ didn't exist before python 3.3. Skip checks that depend on it if running in earlier Python versions --- test/nested.py | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/test/nested.py b/test/nested.py index f53e7af50..657d100a7 100644 --- a/test/nested.py +++ b/test/nested.py @@ -13,7 +13,10 @@ >>> X.__name__ 'X' - >>> X.Y + >>> X.Y # doctest: +py2 + + + >>> X.Y # doctest: +py3 >>> X.Y.__module__ @@ -22,16 +25,22 @@ >>> X.Y.__name__ 'Y' - >>> X.color.__qualname__ + >>> getattr(X.color, "__qualname__", None) # doctest: +py3 'X.color' - >>> repr(X.color.red) + >>> repr(X.color.red) # doctest: +py2 + 'nested_ext.color.red' + + >>> repr(X.color.red) # doctest: +py3 'nested_ext.X.color.red' - >>> repr(X.color(1)) + >>> repr(X.color(1)) # doctest: +py2 + 'nested_ext.color(1)' + + >>> repr(X.color(1)) # doctest: +py3 'nested_ext.X.color(1)' - >>> test_function.__doc__.strip().split('\\n')[0] + >>> test_function.__doc__.strip().split('\\n')[0] # doctest: +py3 'test_function( (X)arg1, (X.Y)arg2) -> None :' ''' @@ -42,7 +51,23 @@ def run(args = None): if args is not None: sys.argv = args - return doctest.testmod(sys.modules.get(__name__)) + + py2 = doctest.register_optionflag("py2") + py3 = doctest.register_optionflag("py3") + + class ConditionalChecker(doctest.OutputChecker): + def check_output(self, want, got, optionflags): + if (optionflags & py3) and (sys.version_info[0] < 3): + return True + if (optionflags & py2) and (sys.version_info[0] >= 3): + 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 3ea0cb8501dd64223e32e6db376b259f7fade14b Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Tue, 17 Sep 2024 21:06:48 -0400 Subject: [PATCH 32/66] Upgrade CI platforms. --- .github/workflows/test-osx.yml | 11 +++++++---- .github/workflows/test-ubuntu.yml | 2 +- .github/workflows/test-windows.yml | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-osx.yml b/.github/workflows/test-osx.yml index 19928d875..03b99f8ca 100644 --- a/.github/workflows/test-osx.yml +++ b/.github/workflows/test-osx.yml @@ -9,14 +9,14 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6] + python-version: [3.8.10] cxx: [clang++] - std: [c++98, c++11, c++14] # TODO: c++17 is failing ! + std: [c++11, c++14] # TODO: c++17 is failing ! steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: setup prerequisites @@ -28,9 +28,11 @@ jobs: run: | python --version ${{ matrix.cxx }} --version + brew info boost faber -v sed -e "s/\$PYTHON/python/g" .ci/faber > ~/.faber faber \ + --with-boost-include=/opt/homebrew/Cellar/boost/1.86.0/include \ --builddir=build \ cxx.name=${{ matrix.cxx }} \ cxxflags=-std=${{ matrix.std }} \ @@ -38,6 +40,7 @@ jobs: - name: test run: | faber \ + --with-boost-include=/opt/homebrew/Cellar/boost/1.86.0/include \ --builddir=build\ cxx.name=${{ matrix.cxx }} \ cxxflags=-std=${{ matrix.std }} \ diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index dab98aee4..9910232e8 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@v2 + - uses: actions/checkout@v4 - name: build run: | diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 36c4a8e4f..e7b02927b 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -11,8 +11,8 @@ jobs: python-version: [3.7] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - uses: microsoft/setup-msbuild@v1.1 From b3a28d70339a3da41fb5f0beb54840268a290f70 Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Tue, 17 Sep 2024 20:40:19 -0400 Subject: [PATCH 33/66] Use the expected return type. --- src/object/class.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object/class.cpp b/src/object/class.cpp index 5b5095151..1a198408a 100644 --- a/src/object/class.cpp +++ b/src/object/class.cpp @@ -502,7 +502,7 @@ namespace objects ); } - str qualname(const char *name) + object qualname(const char *name) { #if PY_VERSION_HEX >= 0x03030000 if (PyObject_HasAttrString(scope().ptr(), "__qualname__")) { From b988d702074b3227967f3d440865d0659583f255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20La=C3=BCgt?= Date: Mon, 29 Aug 2022 10:37:00 +0200 Subject: [PATCH 34/66] Alignment fixes --- include/boost/python/make_constructor.hpp | 3 ++- src/object/class.cpp | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/boost/python/make_constructor.hpp b/include/boost/python/make_constructor.hpp index 3ec9ad5f8..3769970ca 100644 --- a/include/boost/python/make_constructor.hpp +++ b/include/boost/python/make_constructor.hpp @@ -61,7 +61,8 @@ namespace detail typedef objects::pointer_holder holder; typedef objects::instance instance_t; - void* memory = holder::allocate(this->m_self, offsetof(instance_t, storage), sizeof(holder)); + void* memory = holder::allocate(this->m_self, offsetof(instance_t, storage), sizeof(holder), + boost::python::detail::alignment_of::value); try { #if defined(BOOST_NO_CXX11_SMART_PTR) (new (memory) holder(x))->install(this->m_self); diff --git a/src/object/class.cpp b/src/object/class.cpp index 1a198408a..8bb462cee 100644 --- a/src/object/class.cpp +++ b/src/object/class.cpp @@ -766,10 +766,9 @@ void* instance_holder::allocate(PyObject* self_, std::size_t holder_offset, std: throw std::bad_alloc(); const uintptr_t x = reinterpret_cast(base_storage) + sizeof(alignment_marker_t); - //this has problems for x -> max(void *) - //const size_t padding = alignment - ((x + sizeof(alignment_marker_t)) % alignment); - //only works for alignments with alignments of powers of 2, but no edge conditions - const uintptr_t padding = alignment == 1 ? 0 : ( alignment - (x & (alignment - 1)) ); + // Padding required to align the start of a data structure is: (alignment - (x % alignment)) % alignment + // Since the alignment is a power of two, the formula can be simplified with bitwise AND operator as follow: + const uintptr_t padding = (alignment - (x & (alignment - 1))) & (alignment - 1); const size_t aligned_offset = sizeof(alignment_marker_t) + padding; void* const aligned_storage = (char *)base_storage + aligned_offset; BOOST_ASSERT((char *) aligned_storage + holder_size <= (char *)base_storage + base_allocation); From ff0ae9b29d7707d63d6f0ad4a64b578021783693 Mon Sep 17 00:00:00 2001 From: Vitaly Buka Date: Fri, 3 Jun 2022 20:49:17 -0700 Subject: [PATCH 35/66] dynamic_cast before destructor Call to the destructor ends lifetime of the object, including vptr used by dynamic_cast. --- src/object/class.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/object/class.cpp b/src/object/class.cpp index 8bb462cee..e03d4e009 100644 --- a/src/object/class.cpp +++ b/src/object/class.cpp @@ -333,8 +333,9 @@ namespace objects for (instance_holder* p = kill_me->objects, *next; p != 0; p = next) { next = p->next(); + void* q = dynamic_cast(p); p->~instance_holder(); - instance_holder::deallocate(inst, dynamic_cast(p)); + instance_holder::deallocate(inst, q); } // Python 2.2.1 won't add weak references automatically when From 4fc3afa3ac1a1edb61a92fccd31d305ba38213f8 Mon Sep 17 00:00:00 2001 From: sdarwin Date: Wed, 5 Oct 2022 20:56:23 +0000 Subject: [PATCH 36/66] Support newer version of Sphinx --- doc/numpy/_static/boost.css | 20 ++++++++++++++++++++ doc/numpy/_templates/layout.html | 3 +++ 2 files changed, 23 insertions(+) diff --git a/doc/numpy/_static/boost.css b/doc/numpy/_static/boost.css index 28f893599..36c1efd08 100644 --- a/doc/numpy/_static/boost.css +++ b/doc/numpy/_static/boost.css @@ -714,3 +714,23 @@ span.purple { color: purple; } span.gold { color: gold; } span.silver { color: silver; } /* lighter gray */ span.gray { color: #808080; } /* light gray */ + +/* 2022 fix */ + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + diff --git a/doc/numpy/_templates/layout.html b/doc/numpy/_templates/layout.html index 1aa68f0ea..d85f07514 100644 --- a/doc/numpy/_templates/layout.html +++ b/doc/numpy/_templates/layout.html @@ -49,6 +49,9 @@ {%- for scriptfile in script_files %} {%- endfor %} + + + {%- if use_opensearch %} Date: Tue, 28 Jan 2025 02:27:49 +0200 Subject: [PATCH 37/66] Replace use of boost/iterator/detail/enable_if.hpp --- include/boost/python/object_operators.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/python/object_operators.hpp b/include/boost/python/object_operators.hpp index d436bb014..45d6d028c 100644 --- a/include/boost/python/object_operators.hpp +++ b/include/boost/python/object_operators.hpp @@ -9,7 +9,7 @@ # include # include -# include +# include # include # include @@ -40,7 +40,7 @@ struct is_object_operators # if !defined(BOOST_NO_SFINAE) && !defined(BOOST_NO_IS_CONVERTIBLE) template struct enable_binary - : boost::iterators::enable_if, T> + : boost::enable_if_::value, T> {}; # define BOOST_PYTHON_BINARY_RETURN(T) typename enable_binary::type # else From b1b43f1e1a535827640833c5214755c28348da5e Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Sat, 1 Feb 2025 13:29:06 -0500 Subject: [PATCH 38/66] Fix homebrew include path. --- .github/workflows/test-osx.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-osx.yml b/.github/workflows/test-osx.yml index 03b99f8ca..bf2008d4d 100644 --- a/.github/workflows/test-osx.yml +++ b/.github/workflows/test-osx.yml @@ -32,7 +32,7 @@ jobs: faber -v sed -e "s/\$PYTHON/python/g" .ci/faber > ~/.faber faber \ - --with-boost-include=/opt/homebrew/Cellar/boost/1.86.0/include \ + --with-boost-include=$(brew --prefix boost)/include \ --builddir=build \ cxx.name=${{ matrix.cxx }} \ cxxflags=-std=${{ matrix.std }} \ @@ -40,7 +40,7 @@ jobs: - name: test run: | faber \ - --with-boost-include=/opt/homebrew/Cellar/boost/1.86.0/include \ + --with-boost-include=$(brew --prefix boost)/include \ --builddir=build\ cxx.name=${{ matrix.cxx }} \ cxxflags=-std=${{ matrix.std }} \ From 4fe3403584a8d029d316abd0af6037ec70c36d9e Mon Sep 17 00:00:00 2001 From: Stefan Seefeld Date: Sat, 1 Feb 2025 22:15:24 -0500 Subject: [PATCH 39/66] Make sure to pass C++ version to preprocessor as well. --- .github/workflows/test-osx.yml | 2 ++ .github/workflows/test-ubuntu.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/test-osx.yml b/.github/workflows/test-osx.yml index bf2008d4d..2521c09a9 100644 --- a/.github/workflows/test-osx.yml +++ b/.github/workflows/test-osx.yml @@ -36,6 +36,7 @@ jobs: --builddir=build \ cxx.name=${{ matrix.cxx }} \ cxxflags=-std=${{ matrix.std }} \ + cppflags=-std=${{ matrix.std }} \ -j`sysctl -n hw.ncpu` - name: test run: | @@ -44,5 +45,6 @@ jobs: --builddir=build\ cxx.name=${{ matrix.cxx }} \ cxxflags=-std=${{ matrix.std }} \ + cppflags=-std=${{ matrix.std }} \ -j`sysctl -n hw.ncpu` \ test.report diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml index 9910232e8..74718b68a 100644 --- a/.github/workflows/test-ubuntu.yml +++ b/.github/workflows/test-ubuntu.yml @@ -39,6 +39,7 @@ jobs: --builddir=build \ cxx.name=${{ matrix.cxx }} \ cxxflags=-std=${{ matrix.std }} \ + cppflags=-std=${{ matrix.std }} \ -j`nproc` - name: test run: | @@ -47,5 +48,6 @@ jobs: --builddir=build \ cxx.name=${{ matrix.cxx }} \ cxxflags=-std=${{ matrix.std }} \ + cppflags=-std=${{ matrix.std }} \ -j`nproc` \ test.report From cbdf1ce2a1f68abbc64729e9026e312fb858e3c6 Mon Sep 17 00:00:00 2001 From: Aditya Pillai Date: Mon, 10 Mar 2025 14:03:41 -0400 Subject: [PATCH 40/66] Use Py_REFCNT instead of ->ob_refcnt Py_REFCNT was stabilized in 3.9, uses this official API instead of the `ob_refcnt` field that doesn't exist in the free-threaded build of 3.13. --- src/converter/from_python.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/converter/from_python.cpp b/src/converter/from_python.cpp index 9678be1cb..589d99eff 100644 --- a/src/converter/from_python.cpp +++ b/src/converter/from_python.cpp @@ -222,7 +222,7 @@ namespace , char const* ref_type) { handle<> holder(source); - if (source->ob_refcnt <= 1) + if (Py_REFCNT(source) <= 1) { handle<> msg( #if PY_VERSION_HEX >= 0x3000000 From 3e7be69e1e405e1d5ddd232c69c024ee441592c5 Mon Sep 17 00:00:00 2001 From: Aditya Pillai Date: Mon, 10 Mar 2025 14:19:34 -0400 Subject: [PATCH 41/66] Conditionally use Py_REFCNT --- src/converter/from_python.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/converter/from_python.cpp b/src/converter/from_python.cpp index 589d99eff..f3989ba77 100644 --- a/src/converter/from_python.cpp +++ b/src/converter/from_python.cpp @@ -222,7 +222,13 @@ namespace , char const* ref_type) { handle<> holder(source); - if (Py_REFCNT(source) <= 1) + if ( +#if PY_VERSION_HEX < 0x03090000 + source->ob_refcnt +#else + Py_REFCNT(source) +#endif + <= 1) { handle<> msg( #if PY_VERSION_HEX >= 0x3000000 From d30c1bb7a8a6d424b06b62cc692cfbf78add397b Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Fri, 28 Mar 2025 07:52:16 +0900 Subject: [PATCH 42/66] refactor: switch to python 3 --- doc/numpy/conf.py | 12 ++++++------ example/numpy/demo_gaussian.py | 13 +++++++------ example/quickstart/script.py | 2 +- example/quickstart/test_extending.py | 2 +- example/tutorial/hello.py | 2 +- test/numpy/dtype.py | 4 ++-- test/numpy/indexing.py | 2 +- test/numpy/ndarray.py | 10 +++++----- test/numpy/shapes.py | 2 +- test/numpy/templates.py | 4 ++-- test/numpy/ufunc.py | 6 +++--- test/test_cltree.py | 2 +- 12 files changed, 31 insertions(+), 30 deletions(-) diff --git a/doc/numpy/conf.py b/doc/numpy/conf.py index 2f5d5e814..23ab678d3 100644 --- a/doc/numpy/conf.py +++ b/doc/numpy/conf.py @@ -40,8 +40,8 @@ master_doc = 'index' # General information about the project. -project = u'Boost.Python NumPy extension' -copyright = u'2011, Stefan Seefeld' +project = 'Boost.Python NumPy extension' +copyright = '2011, Stefan Seefeld' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -181,8 +181,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'BoostPythonNumPy.tex', u'Boost.Python NumPy Documentation', - u'Stefan Seefeld', 'manual'), + ('index', 'BoostPythonNumPy.tex', 'Boost.Python NumPy Documentation', + 'Stefan Seefeld', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -214,6 +214,6 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'boostnumpy', u'Boost.Python NumPy Documentation', - [u'Stefan Seefeld'], 1) + ('index', 'boostnumpy', 'Boost.Python NumPy Documentation', + ['Stefan Seefeld'], 1) ] diff --git a/example/numpy/demo_gaussian.py b/example/numpy/demo_gaussian.py index 0b1c78995..08bb58b82 100644 --- a/example/numpy/demo_gaussian.py +++ b/example/numpy/demo_gaussian.py @@ -3,6 +3,7 @@ # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) +from __future__ import print_function import numpy import gaussian @@ -19,19 +20,19 @@ z = g(x, y) s = z.sum() * (r[1] - r[0])**2 -print "sum (should be ~ 1):", s +print("sum (should be ~ 1):", s) xc = (z * x).sum() / z.sum() -print "x centroid (should be ~ %f): %f" % (mu[0], xc) +print("x centroid (should be ~ %f): %f" % (mu[0], xc)) yc = (z * y).sum() / z.sum() -print "y centroid (should be ~ %f): %f" % (mu[1], yc) +print("y centroid (should be ~ %f): %f" % (mu[1], yc)) xx = (z * (x - xc)**2).sum() / z.sum() -print "xx moment (should be ~ %f): %f" % (sigma[0,0], xx) +print("xx moment (should be ~ %f): %f" % (sigma[0,0], xx)) yy = (z * (y - yc)**2).sum() / z.sum() -print "yy moment (should be ~ %f): %f" % (sigma[1,1], yy) +print("yy moment (should be ~ %f): %f" % (sigma[1,1], yy)) xy = 0.5 * (z * (x - xc) * (y - yc)).sum() / z.sum() -print "xy moment (should be ~ %f): %f" % (sigma[0,1], xy) +print("xy moment (should be ~ %f): %f" % (sigma[0,1], xy)) diff --git a/example/quickstart/script.py b/example/quickstart/script.py index c3e034ba8..f360cef2d 100644 --- a/example/quickstart/script.py +++ b/example/quickstart/script.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#!/usr/bin/env python3 # Copyright Stefan Seefeld 2006. 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) diff --git a/example/quickstart/test_extending.py b/example/quickstart/test_extending.py index 938c7b904..035ca9613 100644 --- a/example/quickstart/test_extending.py +++ b/example/quickstart/test_extending.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#!/usr/bin/env python3 # Copyright Ralf W. Grosse-Kunstleve 2006. 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) diff --git a/example/tutorial/hello.py b/example/tutorial/hello.py index 31f75565d..7888b2e0f 100755 --- a/example/tutorial/hello.py +++ b/example/tutorial/hello.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#!/usr/bin/env python3 # Copyright Joel de Guzman 2002-2007. 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) diff --git a/test/numpy/dtype.py b/test/numpy/dtype.py index a27ee0f55..a2eabb58e 100644 --- a/test/numpy/dtype.py +++ b/test/numpy/dtype.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright Jim Bosch & Ankit Daftery 2010-2012. # Distributed under the Boost Software License, Version 1.0. @@ -15,7 +15,7 @@ class DtypeTestCase(unittest.TestCase): def assertEquivalent(self, a, b): - return self.assert_(dtype_ext.equivalent(a, b), "%r is not equivalent to %r") + return self.assertTrue(dtype_ext.equivalent(a, b), "%r is not equivalent to %r") def testIntegers(self): for bits in (8, 16, 32, 64): diff --git a/test/numpy/indexing.py b/test/numpy/indexing.py index ebd9dcbab..3fb9adb4c 100644 --- a/test/numpy/indexing.py +++ b/test/numpy/indexing.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright Jim Bosch & Ankit Daftery 2010-2012. # Distributed under the Boost Software License, Version 1.0. diff --git a/test/numpy/ndarray.py b/test/numpy/ndarray.py index 2acc384a5..13f3c73e4 100644 --- a/test/numpy/ndarray.py +++ b/test/numpy/ndarray.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright Jim Bosch & Ankit Daftery 2010-2012. # Distributed under the Boost Software License, Version 1.0. @@ -19,7 +19,7 @@ def testNdzeros(self): a1 = ndarray_ext.zeros(shape,dt) a2 = v.reshape(a1.shape) self.assertEqual(shape,a1.shape) - self.assert_((a1 == a2).all()) + self.assertTrue((a1 == a2).all()) def testNdzeros_matrix(self): for dtp in (numpy.int16, numpy.int32, numpy.float32, numpy.complex128): @@ -28,7 +28,7 @@ def testNdzeros_matrix(self): a1 = ndarray_ext.zeros_matrix(shape, dt) a2 = numpy.matrix(numpy.zeros(shape, dtype=dtp)) self.assertEqual(shape,a1.shape) - self.assert_((a1 == a2).all()) + self.assertTrue((a1 == a2).all()) self.assertEqual(type(a1), type(a2)) def testNdarray(self): @@ -38,8 +38,8 @@ def testNdarray(self): dt = numpy.dtype(dtp) a1 = ndarray_ext.array(a) a2 = ndarray_ext.array(a,dt) - self.assert_((a1 == v).all()) - self.assert_((a2 == v).all()) + self.assertTrue((a1 == v).all()) + self.assertTrue((a2 == v).all()) for shape in ((60,),(6,10),(4,3,5),(2,2,3,5)): a1 = a1.reshape(shape) self.assertEqual(shape,a1.shape) diff --git a/test/numpy/shapes.py b/test/numpy/shapes.py index d0a0099ca..28c74b7b1 100644 --- a/test/numpy/shapes.py +++ b/test/numpy/shapes.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright Jim Bosch & Ankit Daftery 2010-2012. # Distributed under the Boost Software License, Version 1.0. diff --git a/test/numpy/templates.py b/test/numpy/templates.py index 8290b13a0..9c2162288 100755 --- a/test/numpy/templates.py +++ b/test/numpy/templates.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright Jim Bosch & Ankit Daftery 2010-2012. # Distributed under the Boost Software License, Version 1.0. @@ -18,7 +18,7 @@ def testTemplates(self): a1 = numpy.zeros(shape, dtype=dtype) a2 = v.reshape(a1.shape) templates_ext.fill(a1) - self.assert_((a1 == a2).all()) + self.assertTrue((a1 == a2).all()) a1 = numpy.zeros((12,), dtype=numpy.float64) self.assertRaises(TypeError, templates_ext.fill, a1) a1 = numpy.zeros((12,2,3), dtype=numpy.float32) diff --git a/test/numpy/ufunc.py b/test/numpy/ufunc.py index e820121ee..380cc5683 100755 --- a/test/numpy/ufunc.py +++ b/test/numpy/ufunc.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright Jim Bosch & Ankit Daftery 2010-2012. # Distributed under the Boost Software License, Version 1.0. @@ -24,7 +24,7 @@ def testArray(self): assert_array_almost_equal(b, a*2.0) c = numpy.zeros(5, dtype=float) d = f(a,output=c) - self.assert_(c is d) + self.assertTrue(c is d) assert_array_almost_equal(d, a*2.0) def testList(self): @@ -47,7 +47,7 @@ def testArray(self): assert_array_almost_equal(f(a,b), (a*2+b*3)) c = numpy.zeros(5, dtype=float) d = f(a,b,output=c) - self.assert_(c is d) + self.assertTrue(c is d) assert_array_almost_equal(d, a*2 + b*3) assert_array_almost_equal(f(a, 2.0), a*2 + 6.0) assert_array_almost_equal(f(1.0, b), 2.0 + b*3) diff --git a/test/test_cltree.py b/test/test_cltree.py index 2127b7cdb..d5df6fc5a 100644 --- a/test/test_cltree.py +++ b/test/test_cltree.py @@ -1,7 +1,7 @@ # Copyright David Abrahams 2004. 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) -#!/usr/bin/env python +#!/usr/bin/env python3 from cltree import basic,symbol,constant,variable From f604eb8d0f99f5c16831cd4ccd86d9edc859c189 Mon Sep 17 00:00:00 2001 From: Eisuke Kawashima Date: Mon, 31 Mar 2025 23:33:50 +0900 Subject: [PATCH 43/66] fix(test.numpy/ufunc): fix import error and value comparison --- test/numpy/ufunc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/numpy/ufunc.py b/test/numpy/ufunc.py index 380cc5683..1fa3090b3 100755 --- a/test/numpy/ufunc.py +++ b/test/numpy/ufunc.py @@ -8,7 +8,10 @@ import ufunc_ext import unittest import numpy -from numpy.testing.utils import assert_array_almost_equal +try: + from numpy.testing import assert_array_almost_equal +except ImportError: + from numpy.testing.utils import assert_array_almost_equal class TestUnary(unittest.TestCase): @@ -24,7 +27,7 @@ def testArray(self): assert_array_almost_equal(b, a*2.0) c = numpy.zeros(5, dtype=float) d = f(a,output=c) - self.assertTrue(c is d) + self.assertTrue((c == d).all()) assert_array_almost_equal(d, a*2.0) def testList(self): @@ -47,7 +50,7 @@ def testArray(self): assert_array_almost_equal(f(a,b), (a*2+b*3)) c = numpy.zeros(5, dtype=float) d = f(a,b,output=c) - self.assertTrue(c is d) + self.assertTrue((c == d).all()) assert_array_almost_equal(d, a*2 + b*3) assert_array_almost_equal(f(a, 2.0), a*2 + 6.0) assert_array_almost_equal(f(1.0, b), 2.0 + b*3) From a40bb656ee0f26f470444a47ea26701a4baca2e5 Mon Sep 17 00:00:00 2001 From: Aditya Pillai Date: Mon, 12 May 2025 15:09:01 -0400 Subject: [PATCH 44/66] 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 eb8b81c0a..d470e32d7 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 45/66] 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 e7b02927b..cc3d98248 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 46/66] 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 065945137..3c5ffafb7 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 2521c09a9..b88b43e5d 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 74718b68a..41185c0dc 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 cc3d98248..b11f14b45 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 47/66] 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 b11f14b45..f9edc3ee8 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 48/66] 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 f9edc3ee8..576e1f441 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 49/66] 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 b8f4efd9b..0df59a4b3 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 be813bbb1..3cf4d7241 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 50/66] 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 88374b410..e95d59bef 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 51/66] 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 7b6ab9f08..fec56768d 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 52/66] 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 255429f16..e00590041 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 53/66] 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 41185c0dc..a85c21ee9 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 54/66] 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 a85c21ee9..aceeb5933 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 8188779fd..5a50615fc 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 3b8198b8d..36affcd21 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 266ed2912..11e47b332 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 41eac495e..be52c4f32 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 d4d7ead83..a002fb2bf 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 73e1e14ba..82db3e82e 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 12f30048d..e62ead16f 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 8650bd06a..77aac6786 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 e99422796..ef494924b 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 55/66] 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 aceeb5933..6f94c2d18 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 56/66] 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 d85f07514..69e1a868c 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 57/66] 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 d250ae7ec..4ef88f78d 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 58/66] 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 4673745e5..9438d79fd 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 59/66] :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 d7c0cfa93..197470013 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 60/66] 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 77d840d45..296bc21e9 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 18d458698..76b620dcb 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 8b1b88476..2b053d831 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 61/66] 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 16480d0d8..074360d41 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 62/66] 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 000000000..2d2e2d626 --- /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 f3989ba77..53a149fa7 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 aa20c3f68..1b23dbef4 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 c6a8bf7a0..fafb13619 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 34ea22f43..7f6b1880d 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 a7b3156e4..44062875a 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 63/66] 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 57675fa2d..707e43394 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 64/66] 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 7fe5a1c8a..390db82cf 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 707e43394..c32f4187b 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 a002fb2bf..7cf22f9c0 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 000000000..331a73cf3 --- /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 000000000..c03543601 --- /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 65/66] 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 000000000..a6c41460d --- /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 000000000..5cb78be6d --- /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 000000000..801664c10 --- /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 6f94c2d18..abb0f2d59 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 66/66] 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 801664c10..000000000 --- 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 abb0f2d59..31637de5e 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