From 898794488a23c6d51b3474814939a60e5f68a096 Mon Sep 17 00:00:00 2001 From: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com> Date: Sat, 10 Aug 2024 04:28:12 +0800 Subject: [PATCH 01/26] Add `type_caster_std_function_specializations` feature. (#4597) * Allow specializations based on callback function return values. * clang-tidy auto fix * Add a test case for function specialization. * Add test for callback function that raises Python exception. * Fix test failures. * style: pre-commit fixes * Add `#define PYBIND11_HAS_TYPE_CASTER_STD_FUNCTION_SPECIALIZATIONS` --------- Co-authored-by: Ralf W. Grosse-Kunstleve Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- include/pybind11/functional.h | 79 +++++++++++-------- tests/CMakeLists.txt | 1 + ...pe_caster_std_function_specializations.cpp | 46 +++++++++++ ...ype_caster_std_function_specializations.py | 15 ++++ 4 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 tests/test_type_caster_std_function_specializations.cpp create mode 100644 tests/test_type_caster_std_function_specializations.py diff --git a/include/pybind11/functional.h b/include/pybind11/functional.h index 6856119cde..4b3610117c 100644 --- a/include/pybind11/functional.h +++ b/include/pybind11/functional.h @@ -9,12 +9,55 @@ #pragma once +#define PYBIND11_HAS_TYPE_CASTER_STD_FUNCTION_SPECIALIZATIONS + #include "pybind11.h" #include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) +PYBIND11_NAMESPACE_BEGIN(type_caster_std_function_specializations) + +// ensure GIL is held during functor destruction +struct func_handle { + function f; +#if !(defined(_MSC_VER) && _MSC_VER == 1916 && defined(PYBIND11_CPP17)) + // This triggers a syntax error under very special conditions (very weird indeed). + explicit +#endif + func_handle(function &&f_) noexcept + : f(std::move(f_)) { + } + func_handle(const func_handle &f_) { operator=(f_); } + func_handle &operator=(const func_handle &f_) { + gil_scoped_acquire acq; + f = f_.f; + return *this; + } + ~func_handle() { + gil_scoped_acquire acq; + function kill_f(std::move(f)); + } +}; + +// to emulate 'move initialization capture' in C++11 +struct func_wrapper_base { + func_handle hfunc; + explicit func_wrapper_base(func_handle &&hf) noexcept : hfunc(hf) {} +}; + +template +struct func_wrapper : func_wrapper_base { + using func_wrapper_base::func_wrapper_base; + Return operator()(Args... args) const { + gil_scoped_acquire acq; + // casts the returned object as a rvalue to the return type + return hfunc.f(std::forward(args)...).template cast(); + } +}; + +PYBIND11_NAMESPACE_END(type_caster_std_function_specializations) template struct type_caster> { @@ -77,40 +120,8 @@ struct type_caster> { // See PR #1413 for full details } - // ensure GIL is held during functor destruction - struct func_handle { - function f; -#if !(defined(_MSC_VER) && _MSC_VER == 1916 && defined(PYBIND11_CPP17)) - // This triggers a syntax error under very special conditions (very weird indeed). - explicit -#endif - func_handle(function &&f_) noexcept - : f(std::move(f_)) { - } - func_handle(const func_handle &f_) { operator=(f_); } - func_handle &operator=(const func_handle &f_) { - gil_scoped_acquire acq; - f = f_.f; - return *this; - } - ~func_handle() { - gil_scoped_acquire acq; - function kill_f(std::move(f)); - } - }; - - // to emulate 'move initialization capture' in C++11 - struct func_wrapper { - func_handle hfunc; - explicit func_wrapper(func_handle &&hf) noexcept : hfunc(std::move(hf)) {} - Return operator()(Args... args) const { - gil_scoped_acquire acq; - // casts the returned object as a rvalue to the return type - return hfunc.f(std::forward(args)...).template cast(); - } - }; - - value = func_wrapper(func_handle(std::move(func))); + value = type_caster_std_function_specializations::func_wrapper( + type_caster_std_function_specializations::func_handle(std::move(func))); return true; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aae9be720b..5b7e3f801d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -158,6 +158,7 @@ set(PYBIND11_TEST_FILES test_tagbased_polymorphic test_thread test_type_caster_pyobject_ptr + test_type_caster_std_function_specializations test_union test_unnamed_namespace_a test_unnamed_namespace_b diff --git a/tests/test_type_caster_std_function_specializations.cpp b/tests/test_type_caster_std_function_specializations.cpp new file mode 100644 index 0000000000..89213ddb19 --- /dev/null +++ b/tests/test_type_caster_std_function_specializations.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include "pybind11_tests.h" + +namespace py = pybind11; + +namespace { + +struct SpecialReturn { + int value = 99; +}; + +} // namespace + +namespace pybind11 { +namespace detail { +namespace type_caster_std_function_specializations { + +template +struct func_wrapper : func_wrapper_base { + using func_wrapper_base::func_wrapper_base; + SpecialReturn operator()(Args... args) const { + gil_scoped_acquire acq; + SpecialReturn result; + try { + result = hfunc.f(std::forward(args)...).template cast(); + } catch (error_already_set &) { + result.value += 1; + } + result.value += 100; + return result; + } +}; + +} // namespace type_caster_std_function_specializations +} // namespace detail +} // namespace pybind11 + +TEST_SUBMODULE(type_caster_std_function_specializations, m) { + py::class_(m, "SpecialReturn") + .def(py::init<>()) + .def_readwrite("value", &SpecialReturn::value); + m.def("call_callback_with_special_return", + [](const std::function &func) { return func(); }); +} diff --git a/tests/test_type_caster_std_function_specializations.py b/tests/test_type_caster_std_function_specializations.py new file mode 100644 index 0000000000..9e45d4f59e --- /dev/null +++ b/tests/test_type_caster_std_function_specializations.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from pybind11_tests import type_caster_std_function_specializations as m + + +def test_callback_with_special_return(): + def return_special(): + return m.SpecialReturn() + + def raise_exception(): + raise ValueError("called raise_exception.") + + assert return_special().value == 99 + assert m.call_callback_with_special_return(return_special).value == 199 + assert m.call_callback_with_special_return(raise_exception).value == 200 From fc59f4e6e508f28c0e2896937660b9eae0f5ea51 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 12 Aug 2024 16:51:48 -0400 Subject: [PATCH 02/26] fix(cmake): add required emscripten flags (#5298) * fix(cmake): add required emscripten flags Signed-off-by: Henry Schreiner * Update emscripten.yaml * fix(cmake): add required emscripten flags to headers target Signed-off-by: Henry Schreiner * fix(cmake): incorrect detection of Emscripten Signed-off-by: Henry Schreiner * fix(cmake): allow pybind11::headers to be modified Signed-off-by: Henry Schreiner * fix(cmake): hide a warning when building the tests standalone Signed-off-by: Henry Schreiner * fix(cmake): use explicit variable for is config Signed-off-by: Henry Schreiner * fix(cmake): go back to ALIAS target Signed-off-by: Henry Schreiner * chore: reduce overall diff Signed-off-by: Henry Schreiner * chore: reduce overall diff Signed-off-by: Henry Schreiner * chore: shorten code a bit Signed-off-by: Henry Schreiner --------- Signed-off-by: Henry Schreiner --- .github/workflows/emscripten.yaml | 4 ++-- tests/pyproject.toml | 4 ++++ tools/pybind11Common.cmake | 30 ++++++++++++++++++++++++++++-- tools/pybind11Config.cmake.in | 2 +- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/emscripten.yaml b/.github/workflows/emscripten.yaml index cbd7f5d541..14b2b9dc7d 100644 --- a/.github/workflows/emscripten.yaml +++ b/.github/workflows/emscripten.yaml @@ -5,6 +5,8 @@ on: pull_request: branches: - master + - stable + - v* concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -23,8 +25,6 @@ jobs: - uses: pypa/cibuildwheel@v2.20 env: PYODIDE_BUILD_EXPORTS: whole_archive - CFLAGS: -fexceptions - LDFLAGS: -fexceptions with: package-dir: tests only: cp312-pyodide_wasm32 diff --git a/tests/pyproject.toml b/tests/pyproject.toml index 469c145dfd..044bf15c05 100644 --- a/tests/pyproject.toml +++ b/tests/pyproject.toml @@ -10,6 +10,10 @@ name = "pybind11_tests" version = "0.0.1" dependencies = ["pytest", "pytest-timeout", "numpy", "scipy"] +[tool.scikit-build] +# Hide a warning while we also support CMake < 3.15 +cmake.version = ">=3.15" + [tool.scikit-build.cmake.define] PYBIND11_FINDPYTHON = true diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index 8467b45d2c..585ed3d213 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -2,7 +2,7 @@ Adds the following targets:: - pybind11::pybind11 - link to headers and pybind11 + pybind11::pybind11 - link to Python headers and pybind11::headers pybind11::module - Adds module links pybind11::embed - Adds embed links pybind11::lto - Link time optimizations (only if CMAKE_INTERPROCEDURAL_OPTIMIZATION is not set) @@ -75,6 +75,32 @@ set_property( APPEND PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11) +# -------------- emscripten requires exceptions enabled ------------- +# _pybind11_no_exceptions is a private mechanism to disable this addition. +# Please open an issue if you need to use it; it will be removed if no one +# needs it. +if(CMAKE_SYSTEM_NAME MATCHES Emscripten AND NOT _pybind11_no_exceptions) + if(CMAKE_VERSION VERSION_LESS 3.13) + message(WARNING "CMake 3.13+ is required to build for Emscripten. Some flags will be missing") + else() + if(_is_config) + set(_tmp_config_target pybind11::pybind11_headers) + else() + set(_tmp_config_target pybind11_headers) + endif() + + set_property( + TARGET ${_tmp_config_target} + APPEND + PROPERTY INTERFACE_LINK_OPTIONS -fexceptions) + set_property( + TARGET ${_tmp_config_target} + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS -fexceptions) + unset(_tmp_config_target) + endif() +endif() + # --------------------------- link helper --------------------------- add_library(pybind11::python_link_helper IMPORTED INTERFACE ${optional_global}) @@ -329,7 +355,7 @@ function(_pybind11_generate_lto target prefer_thin_lto) if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le" OR CMAKE_SYSTEM_PROCESSOR MATCHES "mips64") # Do nothing - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES emscripten) + elseif(CMAKE_SYSTEM_NAME MATCHES Emscripten) # This compile is very costly when cross-compiling, so set this without checking set(PYBIND11_LTO_CXX_FLAGS "-flto${thin}${cxx_append}") set(PYBIND11_LTO_LINKER_FLAGS "-flto${thin}${linker_append}") diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index 304f1d9077..2d9fa94f67 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -84,7 +84,7 @@ you can either use the basic targets, or use the FindPython tools: # Python method: Python_add_library(MyModule2 src2.cpp) - target_link_libraries(MyModule2 pybind11::headers) + target_link_libraries(MyModule2 PUBLIC pybind11::headers) set_target_properties(MyModule2 PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON CXX_VISIBILITY_PRESET ON From 8d90b83b19de61009779edaa363679cfb9babbb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:49:56 -0400 Subject: [PATCH 03/26] chore(deps): bump actions/attest-build-provenance in the actions group (#5297) Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/210c1913531870065f03ce1f9440dd87bc0938cd...310b0a4a3b0b78ef57ecda988ee04b132db73ef8) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index 75074ff7f5..3edfa612d6 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -102,7 +102,7 @@ jobs: - uses: actions/download-artifact@v4 - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 + uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1 with: subject-path: "*/pybind11*" From 40f2c7863b68f7d7568910c316291f4f0c51222b Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 13 Aug 2024 09:04:03 -0400 Subject: [PATCH 04/26] docs: prepare for 2.13.2 (#5299) * docs: prepare for 2.13.2 Signed-off-by: Henry Schreiner * Update changelog.rst * Update changelog.rst --------- Signed-off-by: Henry Schreiner --- docs/changelog.rst | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index ab6c713c16..22b12e409f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,6 +15,64 @@ IN DEVELOPMENT Changes will be summarized here periodically. +New Features: + +* Support for Python 3.7 was removed. (Official end-of-life: 2023-06-27). + `#5191 `_ + +Support for CMake older than 3.15 and some older compilers will also be removed. + +Version 2.13.2 (August 13, 2024) +-------------------------------- + +New Features: + +* A ``pybind11::detail::type_caster_std_function_specializations`` feature was added, to support specializations for + ``std::function``'s with return types that require custom to-Python conversion behavior (to primary use case is to catch and + convert exceptions). + `#4597 `_ + + +Changes: + + +* Use ``PyMutex`` instead of ``std::mutex`` for internal locking in the free-threaded build. + `#5219 `_ + +* Add a special type annotation for C++ empty tuple. + `#5214 `_ + +* When compiling for WebAssembly, add the required exception flags (CMake 3.13+). + `#5298 `_ + +Bug fixes: + +* Make ``gil_safe_call_once_and_store`` thread-safe in free-threaded CPython. + `#5246 `_ + +* A missing ``#include `` in pybind11/typing.h was added to fix build errors (in case user code does not already depend + on that include). + `#5208 `_ + +* Fix regression introduced in #5201 for GCC<10.3 in C++20 mode. + `#5205 `_ + + +.. fix(cmake) + +* Remove extra = when assigning flto value in the case for Clang in CMake. + `#5207 `_ + + +Tests: + +* Adding WASM testing to our CI (Pyodide / Emscripten via scikit-build-core). + `#4745 `_ + +* clang-tidy (in GitHub Actions) was updated from clang 15 to clang 18. + `#5272 `_ + + Version 2.13.1 (June 26, 2024) ------------------------------ From 1fe92c7b35f9b80c7e952bb1a91841c4f362a3b1 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 13 Aug 2024 12:32:32 -0400 Subject: [PATCH 05/26] fix: emscripten cmake issue (#5301) Signed-off-by: Henry Schreiner --- tools/pybind11Common.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index 585ed3d213..7d8d94b11d 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -83,7 +83,7 @@ if(CMAKE_SYSTEM_NAME MATCHES Emscripten AND NOT _pybind11_no_exceptions) if(CMAKE_VERSION VERSION_LESS 3.13) message(WARNING "CMake 3.13+ is required to build for Emscripten. Some flags will be missing") else() - if(_is_config) + if(is_config) set(_tmp_config_target pybind11::pybind11_headers) else() set(_tmp_config_target pybind11_headers) From 8d9f4d50cc7baa461365e977a0645c6c0d7b1b12 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 13 Aug 2024 13:02:15 -0400 Subject: [PATCH 06/26] fix: quote paths from pybind11-config (#5302) Signed-off-by: Henry Schreiner --- pybind11/__main__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pybind11/__main__.py b/pybind11/__main__.py index b656ce6fee..dadf24f3cb 100644 --- a/pybind11/__main__.py +++ b/pybind11/__main__.py @@ -2,6 +2,7 @@ from __future__ import annotations import argparse +import shlex import sys import sysconfig @@ -22,7 +23,7 @@ def print_includes() -> None: if d and d not in unique_dirs: unique_dirs.append(d) - print(" ".join("-I" + d for d in unique_dirs)) + print(" ".join(shlex.quote(f"-I{d}") for d in unique_dirs)) def main() -> None: @@ -54,9 +55,9 @@ def main() -> None: if args.includes: print_includes() if args.cmakedir: - print(get_cmake_dir()) + print(shlex.quote(get_cmake_dir())) if args.pkgconfigdir: - print(get_pkgconfig_dir()) + print(shlex.quote(get_pkgconfig_dir())) if __name__ == "__main__": From 4a06eca59173cdc8c9bcb28a186e2c512cfc9f97 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 13 Aug 2024 13:25:23 -0400 Subject: [PATCH 07/26] docs: prepare for 2.13.3 Signed-off-by: Henry Schreiner --- docs/changelog.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 22b12e409f..d19a901297 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,19 @@ New Features: Support for CMake older than 3.15 and some older compilers will also be removed. +Version 2.13.3 (August 13, 2024) +-------------------------------- + +Bug fixes: + +* Quote paths from pybind11-config + `#5302 `_ + + +* Fix typo in Emscripten support when in config mode (CMake) + `#5301 `_ + + Version 2.13.2 (August 13, 2024) -------------------------------- From 0d44d720cb1b911de2d96849860d0c5beb368f95 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Wed, 14 Aug 2024 01:42:51 +0700 Subject: [PATCH 08/26] Make stl.h `list|set|map_caster` more user friendly. (#4686) * Add `test_pass_std_vector_int()`, `test_pass_std_set_int()` in test_stl * Change `list_caster` to also accept generator objects (`PyGen_Check(src.ptr()`). Note for completeness: This is a more conservative change than https://github.com/google/pywrapcc/pull/30042 * Drop in (currently unpublished) PyCLIF code, use in `list_caster`, adjust tests. * Use `PyObjectTypeIsConvertibleToStdSet()` in `set_caster`, adjust tests. * Use `PyObjectTypeIsConvertibleToStdMap()` in `map_caster`, add tests. * Simplify `list_caster` `load()` implementation, push str/bytes check into `PyObjectTypeIsConvertibleToStdVector()`. * clang-tidy cleanup with a few extra `(... != 0)` to be more consistent. * Also use `PyObjectTypeIsConvertibleToStdVector()` in `array_caster`. * Update comment pointing to clif/python/runtime.cc (code is unchanged). * Comprehensive test coverage, enhanced set_caster load implementation. * Resolve clang-tidy eror. * Add a long C++ comment explaining what led to the `PyObjectTypeIsConvertibleTo*()` implementations. * Minor function name change in test. * strcmp -> std::strcmp (thanks @Skylion007 for catching this) * Add `PyCallable_Check(items)` in `PyObjectTypeIsConvertibleToStdMap()` * Resolve clang-tidy error * Use `PyMapping_Items()` instead of `src.attr("items")()`, to be internally consistent with `PyMapping_Check()` * Update link to PyCLIF sources. * Fix typo (thanks @wangxf123456 for catching this) * Add `test_pass_std_vector_int()`, `test_pass_std_set_int()` in test_stl * Change `list_caster` to also accept generator objects (`PyGen_Check(src.ptr()`). Note for completeness: This is a more conservative change than https://github.com/google/pywrapcc/pull/30042 * Drop in (currently unpublished) PyCLIF code, use in `list_caster`, adjust tests. * Use `PyObjectTypeIsConvertibleToStdSet()` in `set_caster`, adjust tests. * Use `PyObjectTypeIsConvertibleToStdMap()` in `map_caster`, add tests. * Simplify `list_caster` `load()` implementation, push str/bytes check into `PyObjectTypeIsConvertibleToStdVector()`. * clang-tidy cleanup with a few extra `(... != 0)` to be more consistent. * Also use `PyObjectTypeIsConvertibleToStdVector()` in `array_caster`. * Update comment pointing to clif/python/runtime.cc (code is unchanged). * Comprehensive test coverage, enhanced set_caster load implementation. * Resolve clang-tidy eror. * Add a long C++ comment explaining what led to the `PyObjectTypeIsConvertibleTo*()` implementations. * Minor function name change in test. * strcmp -> std::strcmp (thanks @Skylion007 for catching this) * Add `PyCallable_Check(items)` in `PyObjectTypeIsConvertibleToStdMap()` * Resolve clang-tidy error * Use `PyMapping_Items()` instead of `src.attr("items")()`, to be internally consistent with `PyMapping_Check()` * Update link to PyCLIF sources. * Fix typo (thanks @wangxf123456 for catching this) * Fix typo discovered by new version of codespell. --- include/pybind11/stl.h | 210 ++++++++++++++++++++++++++++++++++------- tests/test_stl.cpp | 34 +++++++ tests/test_stl.py | 126 +++++++++++++++++++++++++ 3 files changed, 337 insertions(+), 33 deletions(-) diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 71bc5902ef..096301bc72 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -13,6 +13,7 @@ #include "detail/common.h" #include +#include #include #include #include @@ -35,6 +36,89 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) +// +// Begin: Equivalent of +// https://github.com/google/clif/blob/ae4eee1de07cdf115c0c9bf9fec9ff28efce6f6c/clif/python/runtime.cc#L388-L438 +/* +The three `PyObjectTypeIsConvertibleTo*()` functions below are +the result of converging the behaviors of pybind11 and PyCLIF +(http://github.com/google/clif). + +Originally PyCLIF was extremely far on the permissive side of the spectrum, +while pybind11 was very far on the strict side. Originally PyCLIF accepted any +Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as +the elements were convertible. The obvious (in hindsight) problem was that +any empty Python iterable could be passed to any of these C++ types, e.g. `{}` +was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments. + +The functions below strike a practical permissive-vs-strict compromise, +informed by tens of thousands of use cases in the wild. A main objective is +to prevent accidents and improve readability: + +- Python literals must match the C++ types. + +- For C++ `set`: The potentially reducing conversion from a Python sequence + (e.g. Python `list` or `tuple`) to a C++ `set` must be explicit, by going + through a Python `set`. + +- However, a Python `set` can still be passed to a C++ `vector`. The rationale + is that this conversion is not reducing. Implicit conversions of this kind + are also fairly commonly used, therefore enforcing explicit conversions + would have an unfavorable cost : benefit ratio; more sloppily speaking, + such an enforcement would be more annoying than helpful. +*/ + +inline bool PyObjectIsInstanceWithOneOfTpNames(PyObject *obj, + std::initializer_list tp_names) { + if (PyType_Check(obj)) { + return false; + } + const char *obj_tp_name = Py_TYPE(obj)->tp_name; + for (const auto *tp_name : tp_names) { + if (std::strcmp(obj_tp_name, tp_name) == 0) { + return true; + } + } + return false; +} + +inline bool PyObjectTypeIsConvertibleToStdVector(PyObject *obj) { + if (PySequence_Check(obj) != 0) { + return !PyUnicode_Check(obj) && !PyBytes_Check(obj); + } + return (PyGen_Check(obj) != 0) || (PyAnySet_Check(obj) != 0) + || PyObjectIsInstanceWithOneOfTpNames( + obj, {"dict_keys", "dict_values", "dict_items", "map", "zip"}); +} + +inline bool PyObjectTypeIsConvertibleToStdSet(PyObject *obj) { + return (PyAnySet_Check(obj) != 0) || PyObjectIsInstanceWithOneOfTpNames(obj, {"dict_keys"}); +} + +inline bool PyObjectTypeIsConvertibleToStdMap(PyObject *obj) { + if (PyDict_Check(obj)) { + return true; + } + // Implicit requirement in the conditions below: + // A type with `.__getitem__()` & `.items()` methods must implement these + // to be compatible with https://docs.python.org/3/c-api/mapping.html + if (PyMapping_Check(obj) == 0) { + return false; + } + PyObject *items = PyObject_GetAttrString(obj, "items"); + if (items == nullptr) { + PyErr_Clear(); + return false; + } + bool is_convertible = (PyCallable_Check(items) != 0); + Py_DECREF(items); + return is_convertible; +} + +// +// End: Equivalent of clif/python/runtime.cc +// + /// Extracts an const lvalue reference or rvalue reference for U based on the type of T (e.g. for /// forwarding a container element). Typically used indirect via forwarded_type(), below. template @@ -66,17 +150,10 @@ struct set_caster { } void reserve_maybe(const anyset &, void *) {} -public: - bool load(handle src, bool convert) { - if (!isinstance(src)) { - return false; - } - auto s = reinterpret_borrow(src); - value.clear(); - reserve_maybe(s, &value); - for (auto entry : s) { + bool convert_iterable(const iterable &itbl, bool convert) { + for (const auto &it : itbl) { key_conv conv; - if (!conv.load(entry, convert)) { + if (!conv.load(it, convert)) { return false; } value.insert(cast_op(std::move(conv))); @@ -84,6 +161,29 @@ struct set_caster { return true; } + bool convert_anyset(anyset s, bool convert) { + value.clear(); + reserve_maybe(s, &value); + return convert_iterable(s, convert); + } + +public: + bool load(handle src, bool convert) { + if (!PyObjectTypeIsConvertibleToStdSet(src.ptr())) { + return false; + } + if (isinstance(src)) { + value.clear(); + return convert_anyset(reinterpret_borrow(src), convert); + } + if (!convert) { + return false; + } + assert(isinstance(src)); + value.clear(); + return convert_iterable(reinterpret_borrow(src), convert); + } + template static handle cast(T &&src, return_value_policy policy, handle parent) { if (!std::is_lvalue_reference::value) { @@ -115,15 +215,10 @@ struct map_caster { } void reserve_maybe(const dict &, void *) {} -public: - bool load(handle src, bool convert) { - if (!isinstance(src)) { - return false; - } - auto d = reinterpret_borrow(src); + bool convert_elements(const dict &d, bool convert) { value.clear(); reserve_maybe(d, &value); - for (auto it : d) { + for (const auto &it : d) { key_conv kconv; value_conv vconv; if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) { @@ -134,6 +229,25 @@ struct map_caster { return true; } +public: + bool load(handle src, bool convert) { + if (!PyObjectTypeIsConvertibleToStdMap(src.ptr())) { + return false; + } + if (isinstance(src)) { + return convert_elements(reinterpret_borrow(src), convert); + } + if (!convert) { + return false; + } + auto items = reinterpret_steal(PyMapping_Items(src.ptr())); + if (!items) { + throw error_already_set(); + } + assert(isinstance(items)); + return convert_elements(dict(reinterpret_borrow(items)), convert); + } + template static handle cast(T &&src, return_value_policy policy, handle parent) { dict d; @@ -166,13 +280,35 @@ struct list_caster { using value_conv = make_caster; bool load(handle src, bool convert) { - if (!isinstance(src) || isinstance(src) || isinstance(src)) { + if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) { + return false; + } + if (isinstance(src)) { + return convert_elements(src, convert); + } + if (!convert) { return false; } - auto s = reinterpret_borrow(src); + // Designed to be behavior-equivalent to passing tuple(src) from Python: + // The conversion to a tuple will first exhaust the generator object, to ensure that + // the generator is not left in an unpredictable (to the caller) partially-consumed + // state. + assert(isinstance(src)); + return convert_elements(tuple(reinterpret_borrow(src)), convert); + } + +private: + template ::value, int> = 0> + void reserve_maybe(const sequence &s, Type *) { + value.reserve(s.size()); + } + void reserve_maybe(const sequence &, void *) {} + + bool convert_elements(handle seq, bool convert) { + auto s = reinterpret_borrow(seq); value.clear(); reserve_maybe(s, &value); - for (const auto &it : s) { + for (const auto &it : seq) { value_conv conv; if (!conv.load(it, convert)) { return false; @@ -182,13 +318,6 @@ struct list_caster { return true; } -private: - template ::value, int> = 0> - void reserve_maybe(const sequence &s, Type *) { - value.reserve(s.size()); - } - void reserve_maybe(const sequence &, void *) {} - public: template static handle cast(T &&src, return_value_policy policy, handle parent) { @@ -237,12 +366,8 @@ struct array_caster { return size == Size; } -public: - bool load(handle src, bool convert) { - if (!isinstance(src)) { - return false; - } - auto l = reinterpret_borrow(src); + bool convert_elements(handle seq, bool convert) { + auto l = reinterpret_borrow(seq); if (!require_size(l.size())) { return false; } @@ -257,6 +382,25 @@ struct array_caster { return true; } +public: + bool load(handle src, bool convert) { + if (!PyObjectTypeIsConvertibleToStdVector(src.ptr())) { + return false; + } + if (isinstance(src)) { + return convert_elements(src, convert); + } + if (!convert) { + return false; + } + // Designed to be behavior-equivalent to passing tuple(src) from Python: + // The conversion to a tuple will first exhaust the generator object, to ensure that + // the generator is not left in an unpredictable (to the caller) partially-consumed + // state. + assert(isinstance(src)); + return convert_elements(tuple(reinterpret_borrow(src)), convert); + } + template static handle cast(T &&src, return_value_policy policy, handle parent) { list l(src.size()); diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index 48c907ff3d..c35f0d4cad 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -167,6 +167,14 @@ struct type_caster> } // namespace detail } // namespace PYBIND11_NAMESPACE +int pass_std_vector_int(const std::vector &v) { + int zum = 100; + for (const int i : v) { + zum += 2 * i; + } + return zum; +} + TEST_SUBMODULE(stl, m) { // test_vector m.def("cast_vector", []() { return std::vector{1}; }); @@ -546,4 +554,30 @@ TEST_SUBMODULE(stl, m) { []() { return new std::vector(4513); }, // Without explicitly specifying `take_ownership`, this function leaks. py::return_value_policy::take_ownership); + + m.def("pass_std_vector_int", pass_std_vector_int); + m.def("pass_std_vector_pair_int", [](const std::vector> &v) { + int zum = 0; + for (const auto &ij : v) { + zum += ij.first * 100 + ij.second; + } + return zum; + }); + m.def("pass_std_array_int_2", [](const std::array &a) { + return pass_std_vector_int(std::vector(a.begin(), a.end())) + 1; + }); + m.def("pass_std_set_int", [](const std::set &s) { + int zum = 200; + for (const int i : s) { + zum += 3 * i; + } + return zum; + }); + m.def("pass_std_map_int", [](const std::map &m) { + int zum = 500; + for (const auto &p : m) { + zum += p.first * 1000 + p.second; + } + return zum; + }); } diff --git a/tests/test_stl.py b/tests/test_stl.py index 65fda54cc3..1896c32217 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -381,3 +381,129 @@ def test_return_vector_bool_raw_ptr(): v = m.return_vector_bool_raw_ptr() assert isinstance(v, list) assert len(v) == 4513 + + +@pytest.mark.parametrize( + ("fn", "offset"), [(m.pass_std_vector_int, 0), (m.pass_std_array_int_2, 1)] +) +def test_pass_std_vector_int(fn, offset): + assert fn([7, 13]) == 140 + offset + assert fn({6, 2}) == 116 + offset + assert fn({"x": 8, "y": 11}.values()) == 138 + offset + assert fn({3: None, 9: None}.keys()) == 124 + offset + assert fn(i for i in [4, 17]) == 142 + offset + assert fn(map(lambda i: i * 3, [8, 7])) == 190 + offset # noqa: C417 + with pytest.raises(TypeError): + fn({"x": 0, "y": 1}) + with pytest.raises(TypeError): + fn({}) + + +def test_pass_std_vector_pair_int(): + fn = m.pass_std_vector_pair_int + assert fn({1: 2, 3: 4}.items()) == 406 + assert fn(zip([5, 17], [13, 9])) == 2222 + + +def test_list_caster_fully_consumes_generator_object(): + def gen_invalid(): + yield from [1, 2.0, 3] + + gen_obj = gen_invalid() + with pytest.raises(TypeError): + m.pass_std_vector_int(gen_obj) + assert not tuple(gen_obj) + + +def test_pass_std_set_int(): + fn = m.pass_std_set_int + assert fn({3, 15}) == 254 + assert fn({5: None, 12: None}.keys()) == 251 + with pytest.raises(TypeError): + fn([]) + with pytest.raises(TypeError): + fn({}) + with pytest.raises(TypeError): + fn({}.values()) + with pytest.raises(TypeError): + fn(i for i in []) + + +def test_set_caster_dict_keys_failure(): + dict_keys = {1: None, 2.0: None, 3: None}.keys() + # The asserts does not really exercise anything in pybind11, but if one of + # them fails in some future version of Python, the set_caster load + # implementation may need to be revisited. + assert tuple(dict_keys) == (1, 2.0, 3) + assert tuple(dict_keys) == (1, 2.0, 3) + with pytest.raises(TypeError): + m.pass_std_set_int(dict_keys) + assert tuple(dict_keys) == (1, 2.0, 3) + + +class FakePyMappingMissingItems: + def __getitem__(self, _): + raise RuntimeError("Not expected to be called.") + + +class FakePyMappingWithItems(FakePyMappingMissingItems): + def items(self): + return ((1, 3), (2, 4)) + + +class FakePyMappingBadItems(FakePyMappingMissingItems): + def items(self): + return ((1, 2), (3, "x")) + + +class FakePyMappingItemsNotCallable(FakePyMappingMissingItems): + @property + def items(self): + return ((1, 2), (3, 4)) + + +class FakePyMappingItemsWithArg(FakePyMappingMissingItems): + def items(self, _): + return ((1, 2), (3, 4)) + + +class FakePyMappingGenObj(FakePyMappingMissingItems): + def __init__(self, gen_obj): + super().__init__() + self.gen_obj = gen_obj + + def items(self): + yield from self.gen_obj + + +def test_pass_std_map_int(): + fn = m.pass_std_map_int + assert fn({1: 2, 3: 4}) == 4506 + with pytest.raises(TypeError): + fn([]) + assert fn(FakePyMappingWithItems()) == 3507 + with pytest.raises(TypeError): + fn(FakePyMappingMissingItems()) + with pytest.raises(TypeError): + fn(FakePyMappingBadItems()) + with pytest.raises(TypeError): + fn(FakePyMappingItemsNotCallable()) + with pytest.raises(TypeError): + fn(FakePyMappingItemsWithArg()) + + +@pytest.mark.parametrize( + ("items", "expected_exception"), + [ + (((1, 2), (3, "x"), (4, 5)), TypeError), + (((1, 2), (3, 4, 5), (6, 7)), ValueError), + ], +) +def test_map_caster_fully_consumes_generator_object(items, expected_exception): + def gen_invalid(): + yield from items + + gen_obj = gen_invalid() + with pytest.raises(expected_exception): + m.pass_std_map_int(FakePyMappingGenObj(gen_obj)) + assert not tuple(gen_obj) From 2885b8d9b51120f456f1ff9b8b674733cbccda33 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Wed, 14 Aug 2024 10:08:09 +0700 Subject: [PATCH 09/26] Remove test_class_sh_module_local (#5306) --- tests/CMakeLists.txt | 3 --- tests/class_sh_module_local_0.cpp | 34 ------------------------ tests/class_sh_module_local_1.cpp | 40 ----------------------------- tests/class_sh_module_local_2.cpp | 40 ----------------------------- tests/test_class_sh_module_local.py | 30 ---------------------- 5 files changed, 147 deletions(-) delete mode 100644 tests/class_sh_module_local_0.cpp delete mode 100644 tests/class_sh_module_local_1.cpp delete mode 100644 tests/class_sh_module_local_2.cpp delete mode 100644 tests/test_class_sh_module_local.py diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 64d4f8dd09..8765a518c8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -130,7 +130,6 @@ set(PYBIND11_TEST_FILES test_class_sh_factory_constructors test_class_sh_inheritance test_class_sh_mi_thunks - test_class_sh_module_local.py test_class_sh_property test_class_sh_property_non_owning test_class_sh_shared_ptr_copy_move @@ -245,8 +244,6 @@ tests_extra_targets("test_exceptions.py;test_local_bindings.py;test_stl.py;test_ # And add additional targets for other tests. tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already_set") tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils") -tests_extra_targets("test_class_sh_module_local.py" - "class_sh_module_local_0;class_sh_module_local_1;class_sh_module_local_2") set(PYBIND11_EIGEN_REPO "https://gitlab.com/libeigen/eigen.git" diff --git a/tests/class_sh_module_local_0.cpp b/tests/class_sh_module_local_0.cpp deleted file mode 100644 index 4b570624a6..0000000000 --- a/tests/class_sh_module_local_0.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include - -namespace pybind11_tests { -namespace class_sh_module_local { - -struct atyp { // Short for "any type". - std::string mtxt; -}; - -std::string get_mtxt(const atyp &obj) { return obj.mtxt; } - -atyp rtrn_valu_atyp() { return atyp(); } - -} // namespace class_sh_module_local -} // namespace pybind11_tests - -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp) - -PYBIND11_MODULE(class_sh_module_local_0, m) { - m.attr("defined_PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = -#ifndef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT - false; -#else - true; - - using namespace pybind11_tests::class_sh_module_local; - - m.def("get_mtxt", get_mtxt); - - m.def("rtrn_valu_atyp", rtrn_valu_atyp); -#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT -} diff --git a/tests/class_sh_module_local_1.cpp b/tests/class_sh_module_local_1.cpp deleted file mode 100644 index 5197d25f9d..0000000000 --- a/tests/class_sh_module_local_1.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Identical to class_sh_module_local_2.cpp, except 2 replaced with 1. -#include - -#include - -namespace pybind11_tests { -namespace class_sh_module_local { - -struct atyp { // Short for "any type". - std::string mtxt; -}; - -std::string get_mtxt(const atyp &obj) { return obj.mtxt; } - -} // namespace class_sh_module_local -} // namespace pybind11_tests - -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp) - -PYBIND11_MODULE(class_sh_module_local_1, m) { - m.attr("defined_PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = -#ifndef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT - false; -#else - true; - - namespace py = pybind11; - using namespace pybind11_tests::class_sh_module_local; - - py::classh(m, "atyp", py::module_local()) - .def(py::init([](const std::string &mtxt) { - atyp obj; - obj.mtxt = mtxt; - return obj; - })) - .def("tag", [](const atyp &) { return 1; }); - - m.def("get_mtxt", get_mtxt); -#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT -} diff --git a/tests/class_sh_module_local_2.cpp b/tests/class_sh_module_local_2.cpp deleted file mode 100644 index 0e3a39ba38..0000000000 --- a/tests/class_sh_module_local_2.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Identical to class_sh_module_local_1.cpp, except 1 replaced with 2. -#include - -#include - -namespace pybind11_tests { -namespace class_sh_module_local { - -struct atyp { // Short for "any type". - std::string mtxt; -}; - -std::string get_mtxt(const atyp &obj) { return obj.mtxt; } - -} // namespace class_sh_module_local -} // namespace pybind11_tests - -PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp) - -PYBIND11_MODULE(class_sh_module_local_2, m) { - m.attr("defined_PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT") = -#ifndef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT - false; -#else - true; - - namespace py = pybind11; - using namespace pybind11_tests::class_sh_module_local; - - py::classh(m, "atyp", py::module_local()) - .def(py::init([](const std::string &mtxt) { - atyp obj; - obj.mtxt = mtxt; - return obj; - })) - .def("tag", [](const atyp &) { return 2; }); - - m.def("get_mtxt", get_mtxt); -#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT -} diff --git a/tests/test_class_sh_module_local.py b/tests/test_class_sh_module_local.py deleted file mode 100644 index 9f42300525..0000000000 --- a/tests/test_class_sh_module_local.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -import class_sh_module_local_0 as m0 -import class_sh_module_local_1 as m1 -import class_sh_module_local_2 as m2 -import pytest - -if not m0.defined_PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT: - pytest.skip("smart_holder not available.", allow_module_level=True) - - -def test_cross_module_get_mtxt(): - obj1 = m1.atyp("A") - assert obj1.tag() == 1 - obj2 = m2.atyp("B") - assert obj2.tag() == 2 - assert m1.get_mtxt(obj1) == "A" - assert m2.get_mtxt(obj2) == "B" - assert m1.get_mtxt(obj2) == "B" - assert m2.get_mtxt(obj1) == "A" - assert m0.get_mtxt(obj1) == "A" - assert m0.get_mtxt(obj2) == "B" - - -def test_m0_rtrn_valu_atyp(): - with pytest.raises(TypeError) as exc_info: - m0.rtrn_valu_atyp() - assert str(exc_info.value).startswith( - "Unable to convert function return value to a Python type!" - ) From 01169061891d8f5609d8cdf10f6dc396ef4ccd44 Mon Sep 17 00:00:00 2001 From: pwdcd Date: Wed, 14 Aug 2024 13:04:10 +0800 Subject: [PATCH 10/26] chore: remove repetitive words (#5308) Signed-off-by: pwdcd --- docs/advanced/cast/eigen.rst | 2 +- docs/compiling.rst | 4 ++-- include/pybind11/detail/common.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/advanced/cast/eigen.rst b/docs/advanced/cast/eigen.rst index a5c11a3f14..894ce97f3c 100644 --- a/docs/advanced/cast/eigen.rst +++ b/docs/advanced/cast/eigen.rst @@ -259,7 +259,7 @@ copying to take place: "small"_a // <- This one can be copied if needed ); -With the above binding code, attempting to call the the ``some_method(m)`` +With the above binding code, attempting to call the ``some_method(m)`` method on a ``MyClass`` object, or attempting to call ``some_function(m, m2)`` will raise a ``RuntimeError`` rather than making a temporary copy of the array. It will, however, allow the ``m2`` argument to be copied into a temporary if diff --git a/docs/compiling.rst b/docs/compiling.rst index 0c788335de..65db93c810 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -388,7 +388,7 @@ that will be respected instead of the built-in flag search. The ``OPT_SIZE`` flag enables size-based optimization equivalent to the standard ``/Os`` or ``-Os`` compiler flags and the ``MinSizeRel`` build type, -which avoid optimizations that that can substantially increase the size of the +which avoid optimizations that can substantially increase the size of the resulting binary. This flag is particularly useful in projects that are split into performance-critical parts and associated bindings. In this case, we can compile the project in release mode (and hence, optimize performance globally), @@ -719,7 +719,7 @@ customizable pybind11-based wrappers by parsing C++ header files. [litgen]_ is an automatic python bindings generator with a focus on generating documented and discoverable bindings: bindings will nicely reproduce the documentation -found in headers. It is is based on srcML (srcml.org), a highly scalable, multi-language +found in headers. It is based on srcML (srcml.org), a highly scalable, multi-language parsing tool with a developer centric approach. The API that you want to expose to python must be C++14 compatible (but your implementation can use more modern constructs). diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 71a98f064a..643fd33d7e 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -554,7 +554,7 @@ enum class return_value_policy : uint8_t { object without taking ownership similar to the above return_value_policy::reference policy. In contrast to that policy, the function or property's implicit this argument (called the parent) is - considered to be the the owner of the return value (the child). + considered to be the owner of the return value (the child). pybind11 then couples the lifetime of the parent to the child via a reference relationship that ensures that the parent cannot be garbage collected while Python is still using the child. More advanced From 0a2efdfb94e2ebf6ffaf0b41b9d2812632b64617 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Wed, 14 Aug 2024 13:59:12 +0700 Subject: [PATCH 11/26] [smart_holder] Configure only one job per OS kind to build and run tests with `-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT` (#5307) * Configure only one job per OS kind to build and run tests with `-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT` Remove ci_sh_def.yml Piggy-backed: Add `smart_holder` to `branches` in emscripten.yaml * Fix misunderstanding. * Add `/GR /EHsc` for Windows. * #error "Intentional Breakage" (to verify: PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT) * Revert "#error "Intentional Breakage" (to verify: PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT)" This reverts commit 4f6f5cb6e907ab2352733c4756bf68662e9c3f01. --- .github/workflows/ci.yml | 13 +- .github/workflows/ci_sh_def.yml | 1262 ------------------------- .github/workflows/ci_sh_def.yml.patch | 223 ----- .github/workflows/emscripten.yaml | 1 + 4 files changed, 13 insertions(+), 1486 deletions(-) delete mode 100644 .github/workflows/ci_sh_def.yml delete mode 100644 .github/workflows/ci_sh_def.yml.patch diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b4851951c..d77071aba3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,18 @@ jobs: # Extra ubuntu latest job - runs-on: ubuntu-latest python: '3.11' - + - runs-on: ubuntu-latest + python: '3.12' + args: > + -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" + - runs-on: macos-13 + python: '3.12' + args: > + -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" + - runs-on: windows-2022 + python: '3.12' + args: > + -DCMAKE_CXX_FLAGS="/DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT /GR /EHsc" name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} diff --git a/.github/workflows/ci_sh_def.yml b/.github/workflows/ci_sh_def.yml deleted file mode 100644 index 68a7398502..0000000000 --- a/.github/workflows/ci_sh_def.yml +++ /dev/null @@ -1,1262 +0,0 @@ -# PLEASE KEEP THIS GROUP OF FILES IN SYNC AT ALL TIMES: -# ci.yml -# ci_sh_def.yml -# ci_sh_def.yml.patch -# To import changes made to ci.yml *** ESPECIALLY AFTER `git merge master` ***: -# patch -i ci_sh_def.yml.patch -o ci_sh_def.yml -# To update the patch file after making changes to ci_sh.yml: -# diff -u ci.yml ci_sh_def.yml > ci_sh_def.yml.patch -# git commit -a -m 'Tracking ci.yml changes from master.' -# -# Thanks a lot to @rhaschke for PR #2930! - -name: "CI-SH-DEF" - -on: - workflow_dispatch: - pull_request: - push: - branches: - - master - - stable - - smart_holder - - v* - -permissions: read-all - -concurrency: - group: test-sh-def-${{ github.ref }} - cancel-in-progress: true - -env: - PIP_BREAK_SYSTEM_PACKAGES: 1 - PIP_ONLY_BINARY: numpy - FORCE_COLOR: 3 - PYTEST_TIMEOUT: 300 - # For cmake: - VERBOSE: 1 - -jobs: - # This is the "main" test suite, which tests a large number of different - # versions of default compilers and Python versions in GitHub Actions. - standard: - strategy: - fail-fast: false - matrix: - runs-on: [ubuntu-20.04, windows-2022, macos-13] - python: - - '3.8' - - '3.9' - - '3.12' - - '3.13' - - 'pypy-3.8' - - 'pypy-3.9' - - 'pypy-3.10' - - # Items in here will either be added to the build matrix (if not - # present), or add new keys to an existing matrix element if all the - # existing keys match. - # - # We support an optional key: args, for cmake args - include: - # Just add a key - - runs-on: ubuntu-20.04 - python: '3.8' - args: > - -DPYBIND11_FINDPYTHON=ON - -DCMAKE_CXX_FLAGS="-D_=1" - exercise_D_: 1 - - runs-on: ubuntu-20.04 - python: 'pypy-3.8' - args: > - -DPYBIND11_FINDPYTHON=ON - - runs-on: windows-2019 - python: '3.8' - args: > - -DPYBIND11_FINDPYTHON=ON - # Inject a couple Windows 2019 runs - - runs-on: windows-2019 - python: '3.9' - # Extra ubuntu latest job - - runs-on: ubuntu-latest - python: '3.11' - - - name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" - runs-on: ${{ matrix.runs-on }} - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - allow-prereleases: true - - - name: Setup Boost (Linux) - # Can't use boost + define _ - if: runner.os == 'Linux' && matrix.exercise_D_ != 1 - run: sudo apt-get install libboost-dev - - - name: Setup Boost (macOS) - if: runner.os == 'macOS' - run: brew install boost - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.0 - - - name: Cache wheels - if: runner.os == 'macOS' - uses: actions/cache@v4 - with: - # This path is specific to macOS - we really only need it for PyPy NumPy wheels - # See https://github.com/actions/cache/blob/master/examples.md#python---pip - # for ways to do this more generally - path: ~/Library/Caches/pip - # Look to see if there is a cache hit for the corresponding requirements file - key: ${{ runner.os }}-pip-${{ matrix.python }}-x64-${{ hashFiles('tests/requirements.txt') }} - - - name: Prepare env - run: | - python -m pip install -r tests/requirements.txt - - - name: Setup annotations on Linux - if: runner.os == 'Linux' - run: python -m pip install pytest-github-actions-annotate-failures - - # First build - C++11 mode and inplace - # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here - # (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime). - - name: Configure C++11 ${{ matrix.args }} - run: > - cmake -S . -B . - -DPYBIND11_WERROR=ON - -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON - -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON - -DPYBIND11_NUMPY_1_ONLY=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=11 - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT ${{runner.os == 'Windows' && '/GR /EHsc' || ''}}" - ${{ matrix.args }} - - - name: Build C++11 - run: cmake --build . -j 2 - - - name: Python tests C++11 - run: cmake --build . --target pytest -j 2 - - - name: C++11 tests - run: cmake --build . --target cpptest -j 2 - - - name: Interface test C++11 - run: cmake --build . --target test_cmake_build - - - name: Clean directory - run: git clean -fdx - - # Second build - C++17 mode and in a build directory - # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here. - # (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime). - - name: Configure C++17 - run: > - cmake -S . -B build2 - -DPYBIND11_WERROR=ON - -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF - -DPYBIND11_NUMPY_1_ONLY=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=17 - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT ${{runner.os == 'Windows' && '/GR /EHsc' || ''}}" - ${{ matrix.args }} - - - name: Build - run: cmake --build build2 -j 2 - - - name: Python tests - run: cmake --build build2 --target pytest - - - name: C++ tests - run: cmake --build build2 --target cpptest - - # Third build - C++17 mode with unstable ABI - - name: Configure (unstable ABI) - run: > - cmake -S . -B build3 - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=17 - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT ${{runner.os == 'Windows' && '/GR /EHsc' || ''}}" - -DPYBIND11_INTERNALS_VERSION=10000000 - ${{ matrix.args }} - - - name: Build (unstable ABI) - run: cmake --build build3 -j 2 - - - name: Python tests (unstable ABI) - run: cmake --build build3 --target pytest - - - name: Interface test - run: cmake --build build2 --target test_cmake_build - - # This makes sure the setup_helpers module can build packages using - # setuptools - - name: Setuptools helpers test - run: | - pip install setuptools - pytest tests/extra_setuptools - if: "!(matrix.runs-on == 'windows-2022')" - - manylinux: - name: Manylinux on 🐍 3.13t • GIL - runs-on: ubuntu-latest - timeout-minutes: 40 - container: quay.io/pypa/musllinux_1_2_x86_64:latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Prepare venv - run: python3.13t -m venv .venv - - - name: Install Python deps - run: .venv/bin/pip install -r tests/requirements.txt - - - name: Configure C++11 - run: > - cmake -S. -Bbuild - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DPython_ROOT_DIR=.venv - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build C++11 - run: cmake --build build -j2 - - - name: Python tests C++11 - run: cmake --build build --target pytest -j2 - - deadsnakes: - strategy: - fail-fast: false - matrix: - include: - # TODO: Fails on 3.10, investigate - # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4889 - # - python-version: "3.9" - # python-debug: true - # valgrind: true - - python-version: "3.11" - python-debug: false - - name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64" - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python ${{ matrix.python-version }} (deadsnakes) - uses: deadsnakes/action@v3.1.0 - with: - python-version: ${{ matrix.python-version }} - debug: ${{ matrix.python-debug }} - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.0 - - - name: Valgrind cache - if: matrix.valgrind - uses: actions/cache@v4 - id: cache-valgrind - with: - path: valgrind - key: 3.16.1 # Valgrind version - - - name: Compile Valgrind - if: matrix.valgrind && steps.cache-valgrind.outputs.cache-hit != 'true' - run: | - VALGRIND_VERSION=3.16.1 - curl https://sourceware.org/pub/valgrind/valgrind-$VALGRIND_VERSION.tar.bz2 -o - | tar xj - mv valgrind-$VALGRIND_VERSION valgrind - cd valgrind - ./configure - make -j 2 > /dev/null - - - name: Install Valgrind - if: matrix.valgrind - working-directory: valgrind - run: | - sudo make install - sudo apt-get update - sudo apt-get install libc6-dbg # Needed by Valgrind - - - name: Prepare env - run: | - python -m pip install -r tests/requirements.txt - - - name: Configure - run: > - cmake -S . -B build - -DCMAKE_BUILD_TYPE=Debug - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=17 - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build - run: cmake --build build -j 2 - - - name: Python tests - run: cmake --build build --target pytest - - - name: C++ tests - run: cmake --build build --target cpptest - - - name: Run Valgrind on Python tests - if: matrix.valgrind - run: cmake --build build --target memcheck - - - # Testing on clang using the excellent silkeh clang docker images - clang: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - clang: - - dev - std: - - 11 - container_suffix: - - "" - include: - - clang: 5 - std: 14 - - clang: 11 - std: 20 - - clang: 12 - std: 20 - - clang: 13 - std: 20 - - clang: 14 - std: 20 - - clang: 15 - std: 20 - container_suffix: "-bullseye" - - clang: 16 - std: 20 - container_suffix: "-bullseye" - - clang: 17 - std: 20 - container_suffix: "-bookworm" - - clang: 18 - std: 20 - container_suffix: "-bookworm" - - name: "🐍 3 • Clang ${{ matrix.clang }} • C++${{ matrix.std }} • x64" - container: "silkeh/clang:${{ matrix.clang }}${{ matrix.container_suffix }}" - - steps: - - uses: actions/checkout@v4 - - - name: Add wget and python3 - run: apt-get update && apt-get install -y python3-dev python3-numpy python3-pytest libeigen3-dev - - - name: Configure - shell: bash - run: > - cmake -S . -B build - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DCMAKE_CXX_STANDARD=${{ matrix.std }} - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build - run: cmake --build build -j 2 - - - name: Python tests - run: cmake --build build --target pytest - - - name: C++ tests - run: cmake --build build --target cpptest - - - name: Interface test - run: cmake --build build --target test_cmake_build - - - # Testing NVCC; forces sources to behave like .cu files - cuda: - runs-on: ubuntu-latest - name: "🐍 3.10 • CUDA 12.2 • Ubuntu 22.04" - container: nvidia/cuda:12.2.0-devel-ubuntu22.04 - - steps: - - uses: actions/checkout@v4 - - # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND - - name: Install 🐍 3 - run: apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y cmake git python3-dev python3-pytest python3-numpy - - - name: Configure - run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build - run: cmake --build build -j2 --verbose - - - name: Python tests - run: cmake --build build --target pytest - - -# TODO: Internal compiler error - report to NVidia -# # Testing CentOS 8 + PGI compilers -# centos-nvhpc8: -# runs-on: ubuntu-latest -# name: "🐍 3 • CentOS8 / PGI 20.11 • x64" -# container: centos:8 -# -# steps: -# - uses: actions/checkout@v4 -# -# - name: Add Python 3 and a few requirements -# run: yum update -y && yum install -y git python3-devel python3-numpy python3-pytest make environment-modules -# -# - name: Install CMake with pip -# run: | -# python3 -m pip install --upgrade pip -# python3 -m pip install cmake --prefer-binary -# -# - name: Install NVidia HPC SDK -# run: > -# yum -y install -# https://developer.download.nvidia.com/hpc-sdk/20.11/nvhpc-20-11-20.11-1.x86_64.rpm -# https://developer.download.nvidia.com/hpc-sdk/20.11/nvhpc-2020-20.11-1.x86_64.rpm -# -# - name: Configure -# shell: bash -# run: | -# source /etc/profile.d/modules.sh -# module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/20.11 -# cmake -S . -B build -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=14 -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") -# -# - name: Build -# run: cmake --build build -j 2 --verbose -# -# - name: Python tests -# run: cmake --build build --target pytest -# -# - name: C++ tests -# run: cmake --build build --target cpptest -# -# - name: Interface test -# run: cmake --build build --target test_cmake_build - - - # Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds - ubuntu-nvhpc7: - runs-on: ubuntu-20.04 - name: "🐍 3 • NVHPC 23.5 • C++17 • x64" - - env: - # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND - DEBIAN_FRONTEND: 'noninteractive' - steps: - - uses: actions/checkout@v4 - - - name: Add NVHPC Repo - run: | - echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | \ - sudo tee /etc/apt/sources.list.d/nvhpc.list - - - name: Install 🐍 3 & NVHPC - run: | - sudo apt-get update -y && \ - sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \ - sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \ - sudo rm -rf /var/lib/apt/lists/* - python3 -m pip install --upgrade pip - python3 -m pip install --upgrade pytest - - # On some systems, you many need further workarounds: - # https://github.com/pybind/pybind11/pull/2475 - - name: Configure - shell: bash - run: | - source /etc/profile.d/modules.sh - module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5 - cmake -S . -B build -DDOWNLOAD_CATCH=ON \ - -DCMAKE_CXX_STANDARD=17 \ - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ - -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0 -DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" \ - -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" - - - name: Build - run: cmake --build build -j 2 --verbose - - - name: Python tests - run: cmake --build build --target pytest - - - name: C++ tests - run: cmake --build build --target cpptest - - - name: Interface test - run: cmake --build build --target test_cmake_build - - - # Testing on GCC using the GCC docker images (only recent images supported) - gcc: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - { gcc: 9, std: 20 } - - { gcc: 10, std: 17 } - - { gcc: 10, std: 20 } - - { gcc: 11, std: 20 } - - { gcc: 12, std: 20 } - - { gcc: 13, std: 20 } - - name: "🐍 3 • GCC ${{ matrix.gcc }} • C++${{ matrix.std }}• x64" - container: "gcc:${{ matrix.gcc }}" - - steps: - - uses: actions/checkout@v4 - - - name: Add Python 3 - run: apt-get update; apt-get install -y python3-dev python3-numpy python3-pytest python3-pip libeigen3-dev - - - name: Update pip - run: python3 -m pip install --upgrade pip - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.0 - - - name: Configure - shell: bash - run: > - cmake -S . -B build - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DCMAKE_CXX_STANDARD=${{ matrix.std }} - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build - run: cmake --build build -j 2 - - - name: Python tests - run: cmake --build build --target pytest - - - name: C++ tests - run: cmake --build build --target cpptest - - - name: Interface test - run: cmake --build build --target test_cmake_build - - - name: Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE - if: matrix.gcc == '12' - shell: bash - run: > - cmake -S . -B build_partial - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DCMAKE_CXX_STANDARD=${{ matrix.std }} - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - - - name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE - if: matrix.gcc == '12' - run: cmake --build build_partial -j 2 - - - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE - if: matrix.gcc == '12' - run: cmake --build build_partial --target pytest - - # Testing on ICC using the oneAPI apt repo - icc: - runs-on: ubuntu-20.04 - - name: "🐍 3 • ICC latest • x64" - - steps: - - uses: actions/checkout@v4 - - - name: Add apt repo - run: | - sudo apt-get update - sudo apt-get install -y wget build-essential pkg-config cmake ca-certificates gnupg - wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB - sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB - echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list - - - name: Add ICC & Python 3 - run: sudo apt-get update; sudo apt-get install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic cmake python3-dev python3-numpy python3-pytest python3-pip - - - name: Update pip - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - python3 -m pip install --upgrade pip - - - name: Install dependencies - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - python3 -m pip install -r tests/requirements.txt - - - name: Configure C++11 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake -S . -B build-11 \ - -DPYBIND11_WERROR=ON \ - -DDOWNLOAD_CATCH=ON \ - -DDOWNLOAD_EIGEN=OFF \ - -DCMAKE_CXX_STANDARD=11 \ - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" \ - -DCMAKE_CXX_COMPILER=$(which icpc) \ - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build C++11 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-11 -j 2 -v - - - name: Python tests C++11 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - sudo service apport stop - cmake --build build-11 --target check - - - name: C++ tests C++11 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-11 --target cpptest - - - name: Interface test C++11 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-11 --target test_cmake_build - - - name: Configure C++17 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake -S . -B build-17 \ - -DPYBIND11_WERROR=ON \ - -DDOWNLOAD_CATCH=ON \ - -DDOWNLOAD_EIGEN=OFF \ - -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" \ - -DCMAKE_CXX_COMPILER=$(which icpc) \ - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build C++17 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-17 -j 2 -v - - - name: Python tests C++17 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - sudo service apport stop - cmake --build build-17 --target check - - - name: C++ tests C++17 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-17 --target cpptest - - - name: Interface test C++17 - run: | - set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-17 --target test_cmake_build - - - # Testing on CentOS (manylinux uses a centos base). - centos: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - container: - - "almalinux:8" - - "almalinux:9" - - name: "🐍 3 • ${{ matrix.container }} • x64" - container: "${{ matrix.container }}" - - steps: - - name: Latest actions/checkout - uses: actions/checkout@v4 - - - name: Add Python 3.8 - if: matrix.container == 'almalinux:8' - run: dnf update -y && dnf install -y python38-devel gcc-c++ make git - - - name: Add Python 3 (default) - if: matrix.container != 'almalinux:8' - run: dnf update -y && dnf install -y python3-devel gcc-c++ make git - - - name: Update pip - run: python3 -m pip install --upgrade pip - - - name: Install dependencies - run: | - python3 -m pip install cmake -r tests/requirements.txt - - - name: Ensure NumPy 2 is used (required Python >= 3.9) - if: matrix.container == 'almalinux:9' - run: | - python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' - - - name: Configure - shell: bash - run: > - cmake -S . -B build - -DCMAKE_BUILD_TYPE=MinSizeRel - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=11 - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build - run: cmake --build build -j 2 - - - name: Python tests - run: cmake --build build --target pytest - - - name: C++ tests - run: cmake --build build --target cpptest - - - name: Interface test - run: cmake --build build --target test_cmake_build - - - # This tests an "install" with the CMake tools - install-classic: - name: "🐍 3.9 • Debian • x86 • Install" - runs-on: ubuntu-latest - container: i386/debian:bullseye - - steps: - - uses: actions/checkout@v1 # v1 is required to run inside docker - - - name: Install requirements - run: | - apt-get update - apt-get install -y git make cmake g++ libeigen3-dev python3-dev python3-pip - pip3 install "pytest==6.*" - - - name: Configure for install - run: > - cmake . - -DPYBIND11_INSTALL=1 -DPYBIND11_TEST=0 - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Make and install - run: make install - - - name: Copy tests to new directory - run: cp -a tests /pybind11-tests - - - name: Make a new test directory - run: mkdir /build-tests - - - name: Configure tests - run: > - cmake ../pybind11-tests - -DDOWNLOAD_CATCH=ON - -DPYBIND11_WERROR=ON - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - working-directory: /build-tests - - - name: Python tests - run: make pytest -j 2 - working-directory: /build-tests - - - # This verifies that the documentation is not horribly broken, and does a - # basic validation check on the SDist. - doxygen: - name: "Documentation build test" - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - - name: Install Doxygen - run: sudo apt-get install -y doxygen librsvg2-bin # Changed to rsvg-convert in 20.04 - - - name: Build docs - run: pipx run nox -s docs - - - name: Make SDist - run: pipx run nox -s build -- --sdist - - - run: git status --ignored - - - name: Check local include dir - run: > - ls pybind11; - python3 -c "import pybind11, pathlib; assert (a := pybind11.get_include()) == (b := str(pathlib.Path('include').resolve())), f'{a} != {b}'" - - - name: Compare Dists (headers only) - working-directory: include - run: | - python3 -m pip install --user -U ../dist/*.tar.gz - installed=$(python3 -c "import pybind11; print(pybind11.get_include() + '/pybind11')") - diff -rq $installed ./pybind11 - - win32: - strategy: - fail-fast: false - matrix: - python: - - '3.8' - - '3.9' - - '3.10' - - '3.11' - - '3.12' - - include: - - python: '3.12' - args: -DCMAKE_CXX_STANDARD=20 - - python: '3.11' - args: -DCMAKE_CXX_STANDARD=20 - - python: '3.10' - args: -DCMAKE_CXX_STANDARD=20 - - python: '3.9' - args: -DCMAKE_CXX_STANDARD=20 - - python: '3.8' - args: -DCMAKE_CXX_STANDARD=17 - - - name: "🐍 ${{ matrix.python }} • MSVC 2019 • x86 ${{ matrix.args }}" - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - architecture: x86 - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.0 - - - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.13.0 - with: - arch: x86 - - - name: Prepare env - run: | - python -m pip install -r tests/requirements.txt - - # First build - C++11 mode and inplace - - name: Configure ${{ matrix.args }} - run: > - cmake -S . -B build - -G "Visual Studio 16 2019" -A Win32 - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_FLAGS="/GR /EHsc /DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - ${{ matrix.args }} - - name: Build C++11 - run: cmake --build build -j 2 - - - name: Python tests - run: cmake --build build -t pytest - - win32-debug: - strategy: - fail-fast: false - matrix: - python: - - 3.8 - - 3.9 - - include: - - python: 3.9 - args: -DCMAKE_CXX_STANDARD=20 - - python: 3.8 - args: -DCMAKE_CXX_STANDARD=17 - - name: "🐍 ${{ matrix.python }} • MSVC 2019 (Debug) • x86 ${{ matrix.args }}" - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - architecture: x86 - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.0 - - - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.13.0 - with: - arch: x86 - - - name: Prepare env - run: | - python -m pip install -r tests/requirements.txt - - # First build - C++11 mode and inplace - - name: Configure ${{ matrix.args }} - run: > - cmake -S . -B build - -G "Visual Studio 16 2019" -A Win32 - -DCMAKE_BUILD_TYPE=Debug - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_FLAGS="/GR /EHsc /DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - ${{ matrix.args }} - - name: Build C++11 - run: cmake --build build --config Debug -j 2 - - - name: Python tests - run: cmake --build build --config Debug -t pytest - - - windows-2022: - strategy: - fail-fast: false - matrix: - python: - - 3.9 - - name: "🐍 ${{ matrix.python }} • MSVC 2022 C++20 • x64" - runs-on: windows-2022 - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - - name: Prepare env - # Ensure use of NumPy 2 (via NumPy nightlies but can be changed soon) - run: | - python3 -m pip install -r tests/requirements.txt - python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.0 - - - name: Configure C++20 - run: > - cmake -S . -B build - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=20 - -DCMAKE_CXX_FLAGS="/GR /EHsc /DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build C++20 - run: cmake --build build -j 2 - - - name: Python tests - run: cmake --build build --target pytest - - - name: C++20 tests - run: cmake --build build --target cpptest -j 2 - - - name: Interface test C++20 - run: cmake --build build --target test_cmake_build - - - name: Configure C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE - run: > - cmake -S . -B build_partial - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=20 - -DCMAKE_CXX_FLAGS="/GR /EHsc /DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - - - name: Build C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE - run: cmake --build build_partial -j 2 - - - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE - run: cmake --build build_partial --target pytest - - mingw: - name: "🐍 3 • windows-latest • ${{ matrix.sys }}" - runs-on: windows-latest - defaults: - run: - shell: msys2 {0} - strategy: - fail-fast: false - matrix: - include: - - { sys: mingw64, env: x86_64 } - - { sys: mingw32, env: i686 } - steps: - - uses: msys2/setup-msys2@v2 - with: - msystem: ${{matrix.sys}} - install: >- - git - mingw-w64-${{matrix.env}}-gcc - mingw-w64-${{matrix.env}}-python-pip - mingw-w64-${{matrix.env}}-python-numpy - mingw-w64-${{matrix.env}}-cmake - mingw-w64-${{matrix.env}}-make - mingw-w64-${{matrix.env}}-python-pytest - mingw-w64-${{matrix.env}}-boost - mingw-w64-${{matrix.env}}-catch - - - uses: msys2/setup-msys2@v2 - if: matrix.sys == 'mingw64' - with: - msystem: ${{matrix.sys}} - install: >- - git - mingw-w64-${{matrix.env}}-python-scipy - mingw-w64-${{matrix.env}}-eigen3 - - - uses: actions/checkout@v4 - - - name: Configure C++11 - # LTO leads to many undefined reference like - # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) - run: >- - cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -S . -B build - - - name: Build C++11 - run: cmake --build build -j 2 - - - name: Python tests C++11 - run: cmake --build build --target pytest -j 2 - - - name: C++11 tests - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target cpptest -j 2 - - - name: Interface test C++11 - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target test_cmake_build - - - name: Clean directory - run: git clean -fdx - - - name: Configure C++14 - run: >- - cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -S . -B build2 - - - name: Build C++14 - run: cmake --build build2 -j 2 - - - name: Python tests C++14 - run: cmake --build build2 --target pytest -j 2 - - - name: C++14 tests - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target cpptest -j 2 - - - name: Interface test C++14 - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target test_cmake_build - - - name: Clean directory - run: git clean -fdx - - - name: Configure C++17 - run: >- - cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -S . -B build3 - - - name: Build C++17 - run: cmake --build build3 -j 2 - - - name: Python tests C++17 - run: cmake --build build3 --target pytest -j 2 - - - name: C++17 tests - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target cpptest -j 2 - - - name: Interface test C++17 - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target test_cmake_build - - windows_clang: - - strategy: - matrix: - os: [windows-latest] - python: ['3.10'] - - runs-on: "${{ matrix.os }}" - - name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-latest" - - steps: - - name: Show env - run: env - - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Clang - uses: egor-tensin/setup-clang@v1 - - - name: Setup Python ${{ matrix.python }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.0 - - - name: Install ninja-build tool - uses: seanmiddleditch/gha-setup-ninja@v5 - - - name: Run pip installs - run: | - python -m pip install --upgrade pip - python -m pip install -r tests/requirements.txt - - - name: Show Clang++ version - run: clang++ --version - - - name: Show CMake version - run: cmake --version - - # TODO: WERROR=ON - - name: Configure Clang - run: > - cmake -G Ninja -S . -B . - -DPYBIND11_WERROR=OFF - -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_CXX_STANDARD=17 - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build - run: cmake --build . -j 2 - - - name: Python tests - run: cmake --build . --target pytest -j 2 - - - name: C++ tests - run: cmake --build . --target cpptest -j 2 - - - name: Interface test - run: cmake --build . --target test_cmake_build -j 2 - - - name: Clean directory - run: git clean -fdx - - macos_brew_install_llvm: - name: "macos-13 • brew install llvm" - runs-on: macos-13 - - env: - # https://apple.stackexchange.com/questions/227026/how-to-install-recent-clang-with-homebrew - LDFLAGS: '-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib' - - steps: - - name: Update PATH - run: echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH - - - name: Show env - run: env - - - name: Checkout - uses: actions/checkout@v4 - - - name: Show Clang++ version before brew install llvm - run: clang++ --version - - - name: brew install llvm - run: brew install llvm - - - name: Show Clang++ version after brew install llvm - run: clang++ --version - - - name: Update CMake - uses: jwlawson/actions-setup-cmake@v2.0 - - - name: Run pip installs - run: | - python3 -m pip install --upgrade pip - python3 -m pip install -r tests/requirements.txt - python3 -m pip install numpy - python3 -m pip install scipy - - - name: Show CMake version - run: cmake --version - - - name: CMake Configure - run: > - cmake -S . -B . - -DPYBIND11_WERROR=ON - -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_CXX_STANDARD=17 - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build - run: cmake --build . -j 2 - - - name: Python tests - run: cmake --build . --target pytest -j 2 - - - name: C++ tests - run: cmake --build . --target cpptest -j 2 - - - name: Interface test - run: cmake --build . --target test_cmake_build -j 2 - - - name: CMake Configure - Exercise cmake -DPYBIND11_TEST_OVERRIDE - run: > - cmake -S . -B build_partial - -DPYBIND11_WERROR=ON - -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_CXX_STANDARD=17 - -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - - - name: Build - Exercise cmake -DPYBIND11_TEST_OVERRIDE - run: cmake --build build_partial -j 2 - - - name: Python tests - Exercise cmake -DPYBIND11_TEST_OVERRIDE - run: cmake --build build_partial --target pytest -j 2 - - - name: Clean directory - run: git clean -fdx diff --git a/.github/workflows/ci_sh_def.yml.patch b/.github/workflows/ci_sh_def.yml.patch deleted file mode 100644 index ecfd7639c5..0000000000 --- a/.github/workflows/ci_sh_def.yml.patch +++ /dev/null @@ -1,223 +0,0 @@ ---- ci.yml 2024-07-30 11:20:28.997003056 -0700 -+++ ci_sh_def.yml 2024-07-30 11:21:39.724969167 -0700 -@@ -1,4 +1,16 @@ --name: CI -+# PLEASE KEEP THIS GROUP OF FILES IN SYNC AT ALL TIMES: -+# ci.yml -+# ci_sh_def.yml -+# ci_sh_def.yml.patch -+# To import changes made to ci.yml *** ESPECIALLY AFTER `git merge master` ***: -+# patch -i ci_sh_def.yml.patch -o ci_sh_def.yml -+# To update the patch file after making changes to ci_sh.yml: -+# diff -u ci.yml ci_sh_def.yml > ci_sh_def.yml.patch -+# git commit -a -m 'Tracking ci.yml changes from master.' -+# -+# Thanks a lot to @rhaschke for PR #2930! -+ -+name: "CI-SH-DEF" - - on: - workflow_dispatch: -@@ -13,7 +25,7 @@ - permissions: read-all - - concurrency: -- group: test-sh-avl${{ github.ref }} -+ group: test-sh-def-${{ github.ref }} - cancel-in-progress: true - - env: -@@ -126,6 +138,7 @@ - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=11 -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT ${{runner.os == 'Windows' && '/GR /EHsc' || ''}}" - ${{ matrix.args }} - - - name: Build C++11 -@@ -155,6 +168,7 @@ - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=17 -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT ${{runner.os == 'Windows' && '/GR /EHsc' || ''}}" - ${{ matrix.args }} - - - name: Build -@@ -174,6 +188,7 @@ - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=17 -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT ${{runner.os == 'Windows' && '/GR /EHsc' || ''}}" - -DPYBIND11_INTERNALS_VERSION=10000000 - ${{ matrix.args }} - -@@ -217,6 +232,7 @@ - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DPython_ROOT_DIR=.venv -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build C++11 - run: cmake --build build -j2 -@@ -290,6 +306,7 @@ - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=17 -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build - run: cmake --build build -j 2 -@@ -357,6 +374,7 @@ - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DCMAKE_CXX_STANDARD=${{ matrix.std }} -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build -@@ -386,7 +404,7 @@ - run: apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y cmake git python3-dev python3-pytest python3-numpy - - - name: Configure -- run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -+ run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build - run: cmake --build build -j2 --verbose -@@ -474,7 +492,7 @@ - cmake -S . -B build -DDOWNLOAD_CATCH=ON \ - -DCMAKE_CXX_STANDARD=17 \ - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -- -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ -+ -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0 -DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" \ - -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" - - - name: Build -@@ -526,6 +544,7 @@ - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DCMAKE_CXX_STANDARD=${{ matrix.std }} -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build -@@ -548,6 +567,7 @@ - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DCMAKE_CXX_STANDARD=${{ matrix.std }} -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - -@@ -597,6 +617,7 @@ - -DDOWNLOAD_CATCH=ON \ - -DDOWNLOAD_EIGEN=OFF \ - -DCMAKE_CXX_STANDARD=11 \ -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" \ - -DCMAKE_CXX_COMPILER=$(which icpc) \ - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - -@@ -629,6 +650,7 @@ - -DDOWNLOAD_CATCH=ON \ - -DDOWNLOAD_EIGEN=OFF \ - -DCMAKE_CXX_STANDARD=17 \ -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" \ - -DCMAKE_CXX_COMPILER=$(which icpc) \ - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - -@@ -700,6 +722,7 @@ - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=11 -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build -@@ -750,6 +773,7 @@ - cmake ../pybind11-tests - -DDOWNLOAD_CATCH=ON - -DPYBIND11_WERROR=ON -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - working-directory: /build-tests - -@@ -850,6 +874,7 @@ - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON -+ -DCMAKE_CXX_FLAGS="/GR /EHsc /DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - ${{ matrix.args }} - - name: Build C++11 - run: cmake --build build -j 2 -@@ -904,6 +929,7 @@ - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON -+ -DCMAKE_CXX_FLAGS="/GR /EHsc /DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - ${{ matrix.args }} - - name: Build C++11 - run: cmake --build build --config Debug -j 2 -@@ -946,6 +972,7 @@ - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=20 -+ -DCMAKE_CXX_FLAGS="/GR /EHsc /DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build C++20 - run: cmake --build build -j 2 -@@ -966,6 +993,7 @@ - -DDOWNLOAD_CATCH=ON - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=20 -+ -DCMAKE_CXX_FLAGS="/GR /EHsc /DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - - - name: Build C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE -@@ -1018,6 +1046,7 @@ - run: >- - cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -S . -B build - - - name: Build C++11 -@@ -1039,6 +1068,7 @@ - run: >- - cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -S . -B build2 - - - name: Build C++14 -@@ -1060,6 +1090,7 @@ - run: >- - cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -S . -B build3 - - - name: Build C++17 -@@ -1127,6 +1158,7 @@ - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_CXX_STANDARD=17 -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - - - name: Build - run: cmake --build . -j 2 -@@ -1192,6 +1224,7 @@ - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_CXX_STANDARD=17 -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - - name: Build -@@ -1215,6 +1248,7 @@ - -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_CXX_STANDARD=17 -+ -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT" - -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - diff --git a/.github/workflows/emscripten.yaml b/.github/workflows/emscripten.yaml index 14b2b9dc7d..fe4cd821c6 100644 --- a/.github/workflows/emscripten.yaml +++ b/.github/workflows/emscripten.yaml @@ -6,6 +6,7 @@ on: branches: - master - stable + - smart_holder - v* concurrency: From fc97cc41d575d44fa425a0e56765380e15474399 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 14 Aug 2024 12:36:45 -0400 Subject: [PATCH 12/26] Revert "fix: quote paths from pybind11-config (#5302)" (#5309) This reverts commit 8d9f4d50cc7baa461365e977a0645c6c0d7b1b12. --- pybind11/__main__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pybind11/__main__.py b/pybind11/__main__.py index dadf24f3cb..b656ce6fee 100644 --- a/pybind11/__main__.py +++ b/pybind11/__main__.py @@ -2,7 +2,6 @@ from __future__ import annotations import argparse -import shlex import sys import sysconfig @@ -23,7 +22,7 @@ def print_includes() -> None: if d and d not in unique_dirs: unique_dirs.append(d) - print(" ".join(shlex.quote(f"-I{d}") for d in unique_dirs)) + print(" ".join("-I" + d for d in unique_dirs)) def main() -> None: @@ -55,9 +54,9 @@ def main() -> None: if args.includes: print_includes() if args.cmakedir: - print(shlex.quote(get_cmake_dir())) + print(get_cmake_dir()) if args.pkgconfigdir: - print(shlex.quote(get_pkgconfig_dir())) + print(get_pkgconfig_dir()) if __name__ == "__main__": From d893f9723a938f187a9fc85e7231c9a68eba5d32 Mon Sep 17 00:00:00 2001 From: Markus Bauer Date: Wed, 14 Aug 2024 23:25:37 +0200 Subject: [PATCH 13/26] fix: escape paths with spaces in pybind11-config (#4874) * fix: Escape paths with spaces in include list from --includes * fix: --includes should not use shlex on Windows platforms * Apply suggestions from code review * fix: use custom impl Signed-off-by: Henry Schreiner * Support trailing backslashes Co-authored-by: Henry Schreiner --------- Signed-off-by: Henry Schreiner Co-authored-by: Markus Bauer Co-authored-by: Henry Schreiner --- pybind11/__main__.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/pybind11/__main__.py b/pybind11/__main__.py index b656ce6fee..0abc7e2117 100644 --- a/pybind11/__main__.py +++ b/pybind11/__main__.py @@ -2,12 +2,35 @@ from __future__ import annotations import argparse +import re import sys import sysconfig from ._version import __version__ from .commands import get_cmake_dir, get_include, get_pkgconfig_dir +# This is the conditional used for os.path being posixpath +if "posix" in sys.builtin_module_names: + from shlex import quote +elif "nt" in sys.builtin_module_names: + # See https://github.com/mesonbuild/meson/blob/db22551ed9d2dd7889abea01cc1c7bba02bf1c75/mesonbuild/utils/universal.py#L1092-L1121 + # and the original documents: + # https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments and + # https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ + UNSAFE = re.compile("[ \t\n\r]") + + def quote(s: str) -> str: + if s and not UNSAFE.search(s): + return s + + # Paths cannot contain a '"' on Windows, so we don't need to worry + # about nuanced counting here. + return f'"{s}\\"' if s.endswith("\\") else f'"{s}"' +else: + + def quote(s: str) -> str: + return s + def print_includes() -> None: dirs = [ @@ -22,7 +45,7 @@ def print_includes() -> None: if d and d not in unique_dirs: unique_dirs.append(d) - print(" ".join("-I" + d for d in unique_dirs)) + print(" ".join(quote(f"-I{d}") for d in unique_dirs)) def main() -> None: @@ -54,9 +77,9 @@ def main() -> None: if args.includes: print_includes() if args.cmakedir: - print(get_cmake_dir()) + print(quote(get_cmake_dir())) if args.pkgconfigdir: - print(get_pkgconfig_dir()) + print(quote(get_pkgconfig_dir())) if __name__ == "__main__": From 28dbce4157c89219705801b06e5a94b612d611a5 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 14 Aug 2024 18:21:04 -0400 Subject: [PATCH 14/26] feat: require CMake 3.15+ (#5304) * feat: require CMake 3.15+ Signed-off-by: Henry Schreiner * Apply suggestions from code review * Update CMakeLists.txt * fix: adapt for CMake 3.30+ (using 3.18+) Signed-off-by: Henry Schreiner --------- Signed-off-by: Henry Schreiner --- .github/CONTRIBUTING.md | 10 +- .github/workflows/configure.yml | 4 +- CMakeLists.txt | 57 ++-------- docs/advanced/embedding.rst | 2 +- docs/compiling.rst | 32 +++--- docs/faq.rst | 16 +-- tests/CMakeLists.txt | 50 +++------ tests/extra_python_package/test_files.py | 1 - tests/pyproject.toml | 4 - .../installed_embed/CMakeLists.txt | 11 +- .../installed_function/CMakeLists.txt | 12 +-- .../installed_target/CMakeLists.txt | 11 +- .../subdirectory_embed/CMakeLists.txt | 11 +- .../subdirectory_function/CMakeLists.txt | 11 +- .../subdirectory_target/CMakeLists.txt | 11 +- tools/pybind11Common.cmake | 101 ++++++------------ tools/pybind11GuessPythonExtSuffix.cmake | 2 +- tools/pybind11NewTools.cmake | 5 - tools/pybind11Tools.cmake | 51 +++------ tools/test-pybind11GuessPythonExtSuffix.cmake | 2 +- 20 files changed, 113 insertions(+), 291 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f5a08e2d78..f2d8007df4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -81,7 +81,7 @@ nox -s build ### Full setup To setup an ideal development environment, run the following commands on a -system with CMake 3.14+: +system with CMake 3.15+: ```bash python3 -m venv venv @@ -96,8 +96,8 @@ Tips: * You can use `virtualenv` (faster, from PyPI) instead of `venv`. * You can select any name for your environment folder; if it contains "env" it will be ignored by git. -* If you don't have CMake 3.14+, just add "cmake" to the pip install command. -* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython on CMake 3.12+ +* If you don't have CMake 3.15+, just add "cmake" to the pip install command. +* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython. * In classic mode, you may need to set `-DPYTHON_EXECUTABLE=/path/to/python`. FindPython uses `-DPython_ROOT_DIR=/path/to` or `-DPython_EXECUTABLE=/path/to/python`. @@ -149,8 +149,8 @@ To run the tests, you can "build" the check target: cmake --build build --target check ``` -`--target` can be spelled `-t` in CMake 3.15+. You can also run individual -tests with these targets: +`--target` can be spelled `-t`. You can also run individual tests with these +targets: * `pytest`: Python tests only, using the [pytest](https://docs.pytest.org/en/stable/) framework diff --git a/.github/workflows/configure.yml b/.github/workflows/configure.yml index 0583aa06a5..2031ec8236 100644 --- a/.github/workflows/configure.yml +++ b/.github/workflows/configure.yml @@ -31,7 +31,7 @@ jobs: include: - runs-on: ubuntu-20.04 arch: x64 - cmake: "3.5" + cmake: "3.15" - runs-on: ubuntu-20.04 arch: x64 @@ -39,7 +39,7 @@ jobs: - runs-on: macos-13 arch: x64 - cmake: "3.8" + cmake: "3.15" - runs-on: windows-2019 arch: x64 # x86 compilers seem to be missing on 2019 image diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e5b8c8f31..ed29719420 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,16 +10,7 @@ if(NOT CMAKE_VERSION VERSION_LESS "3.27") cmake_policy(GET CMP0148 _pybind11_cmp0148) endif() -cmake_minimum_required(VERSION 3.5) - -# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with -# some versions of VS that have a patched CMake 3.11. This forces us to emulate -# the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.29) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.29) -endif() +cmake_minimum_required(VERSION 3.15...3.30) if(_pybind11_cmp0148) cmake_policy(SET CMP0148 ${_pybind11_cmp0148}) @@ -27,9 +18,7 @@ if(_pybind11_cmp0148) endif() # Avoid infinite recursion if tests include this as a subdirectory -if(DEFINED PYBIND11_MASTER_PROJECT) - return() -endif() +include_guard(GLOBAL) # Extract project version from source file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/pybind11/detail/common.h" @@ -74,14 +63,6 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) set(PYBIND11_MASTER_PROJECT ON) - if(OSX AND CMAKE_VERSION VERSION_LESS 3.7) - # Bug in macOS CMake < 3.7 is unable to download catch - message(WARNING "CMAKE 3.7+ needed on macOS to download catch, and newer HIGHLY recommended") - elseif(WINDOWS AND CMAKE_VERSION VERSION_LESS 3.8) - # Only tested with 3.8+ in CI. - message(WARNING "CMAKE 3.8+ tested on Windows, previous versions untested") - endif() - message(STATUS "CMake ${CMAKE_VERSION}") if(CMAKE_CXX_STANDARD) @@ -133,8 +114,7 @@ cmake_dependent_option( "Install pybind11 headers in Python include directory instead of default installation prefix" OFF "PYBIND11_INSTALL" OFF) -cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" ${_pybind11_findpython_default} - "NOT CMAKE_VERSION VERSION_LESS 3.12" OFF) +option(PYBIND11_FINDPYTHON "Force new FindPython" ${_pybind11_findpython_default}) # Allow PYTHON_EXECUTABLE if in FINDPYTHON mode and building pybind11's tests # (makes transition easier while we support both modes). @@ -183,7 +163,7 @@ set(PYBIND11_HEADERS include/pybind11/typing.h) # Compare with grep and warn if mismatched -if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) +if(PYBIND11_MASTER_PROJECT) file( GLOB_RECURSE _pybind11_header_check LIST_DIRECTORIES false @@ -201,10 +181,7 @@ if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) endif() endif() -# CMake 3.12 added list(TRANSFORM PREPEND -# But we can't use it yet -string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/" PYBIND11_HEADERS - "${PYBIND11_HEADERS}") +list(TRANSFORM PYBIND11_HEADERS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") # Cache variable so this can be used in parent projects set(pybind11_INCLUDE_DIR @@ -274,25 +251,11 @@ if(PYBIND11_INSTALL) tools/${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) - if(CMAKE_VERSION VERSION_LESS 3.14) - # Remove CMAKE_SIZEOF_VOID_P from ConfigVersion.cmake since the library does - # not depend on architecture specific settings or libraries. - set(_PYBIND11_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) - unset(CMAKE_SIZEOF_VOID_P) - - write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY AnyNewerVersion) - - set(CMAKE_SIZEOF_VOID_P ${_PYBIND11_CMAKE_SIZEOF_VOID_P}) - else() - # CMake 3.14+ natively supports header-only libraries - write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT) - endif() + # CMake natively supports header-only libraries + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake diff --git a/docs/advanced/embedding.rst b/docs/advanced/embedding.rst index cbed82158e..e78a8f4ce6 100644 --- a/docs/advanced/embedding.rst +++ b/docs/advanced/embedding.rst @@ -18,7 +18,7 @@ information, see :doc:`/compiling`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.5...3.29) + cmake_minimum_required(VERSION 3.15...3.30) project(example) find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)` diff --git a/docs/compiling.rst b/docs/compiling.rst index 65db93c810..94042c3e5c 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -18,7 +18,7 @@ A Python extension module can be created with just a few lines of code: .. code-block:: cmake - cmake_minimum_required(VERSION 3.15...3.29) + cmake_minimum_required(VERSION 3.15...3.30) project(example LANGUAGES CXX) set(PYBIND11_FINDPYTHON ON) @@ -319,11 +319,11 @@ Building with CMake For C++ codebases that have an existing CMake-based build system, a Python extension module can be created with just a few lines of code, as seen above in -the module section. Pybind11 currently supports a lower minimum if you don't -use the modern FindPython, though be aware that CMake 3.27 removed the old -mechanism, so pybind11 will automatically switch if the old mechanism is not -available. Please opt into the new mechanism if at all possible. Our default -may change in future versions. This is the minimum required: +the module section. Pybind11 currently defaults to the old mechanism, though be +aware that CMake 3.27 removed the old mechanism, so pybind11 will automatically +switch if the old mechanism is not available. Please opt into the new mechanism +if at all possible. Our default may change in future versions. This is the +minimum required: @@ -333,6 +333,9 @@ may change in future versions. This is the minimum required: .. versionchanged:: 2.11 CMake 3.5+ is required. +.. versionchanged:: 2.14 + CMake 3.15+ is required. + Further information can be found at :doc:`cmake/index`. @@ -444,7 +447,7 @@ See the `Config file`_ docstring for details of relevant CMake variables. .. code-block:: cmake - cmake_minimum_required(VERSION 3.4...3.18) + cmake_minimum_required(VERSION 3.15...3.30) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) @@ -483,14 +486,13 @@ can refer to the same [cmake_example]_ repository for a full sample project FindPython mode --------------- -CMake 3.12+ (3.15+ recommended, 3.18.2+ ideal) added a new module called -FindPython that had a highly improved search algorithm and modern targets -and tools. If you use FindPython, pybind11 will detect this and use the -existing targets instead: +Modern CMake (3.18.2+ ideal) added a new module called FindPython that had a +highly improved search algorithm and modern targets and tools. If you use +FindPython, pybind11 will detect this and use the existing targets instead: .. code-block:: cmake - cmake_minimum_required(VERSION 3.15...3.22) + cmake_minimum_required(VERSION 3.15...3.30) project(example LANGUAGES CXX) find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED) @@ -541,7 +543,7 @@ available in all modes. The targets provided are: Just the "linking" part of pybind11:module ``pybind11::module`` - Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython CMake 3.15+) or ``pybind11::python_link_helper`` + Everything for extension modules - ``pybind11::pybind11`` + ``Python::Module`` (FindPython) or ``pybind11::python_link_helper`` ``pybind11::embed`` Everything for embedding the Python interpreter - ``pybind11::pybind11`` + ``Python::Python`` (FindPython) or Python libs @@ -568,7 +570,7 @@ You can use these targets to build complex applications. For example, the .. code-block:: cmake - cmake_minimum_required(VERSION 3.5...3.29) + cmake_minimum_required(VERSION 3.15...3.30) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) @@ -626,7 +628,7 @@ information about usage in C++, see :doc:`/advanced/embedding`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.5...3.29) + cmake_minimum_required(VERSION 3.15...3.30) project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) diff --git a/docs/faq.rst b/docs/faq.rst index 1eb00efada..92777b5b37 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -258,9 +258,9 @@ CMake configure line. (Replace ``$(which python)`` with a path to python if your prefer.) You can alternatively try ``-DPYBIND11_FINDPYTHON=ON``, which will activate the -new CMake FindPython support instead of pybind11's custom search. Requires -CMake 3.12+, and 3.15+ or 3.18.2+ are even better. You can set this in your -``CMakeLists.txt`` before adding or finding pybind11, as well. +new CMake FindPython support instead of pybind11's custom search. Newer CMake, +like, 3.18.2+, is recommended. You can set this in your ``CMakeLists.txt`` +before adding or finding pybind11, as well. Inconsistent detection of Python version in CMake and pybind11 ============================================================== @@ -281,11 +281,11 @@ There are three possible solutions: from CMake and rely on pybind11 in detecting Python version. If this is not possible, the CMake machinery should be called *before* including pybind11. 2. Set ``PYBIND11_FINDPYTHON`` to ``True`` or use ``find_package(Python - COMPONENTS Interpreter Development)`` on modern CMake (3.12+, 3.15+ better, - 3.18.2+ best). Pybind11 in these cases uses the new CMake FindPython instead - of the old, deprecated search tools, and these modules are much better at - finding the correct Python. If FindPythonLibs/Interp are not available - (CMake 3.27+), then this will be ignored and FindPython will be used. + COMPONENTS Interpreter Development)`` on modern CMake ( 3.18.2+ best). + Pybind11 in these cases uses the new CMake FindPython instead of the old, + deprecated search tools, and these modules are much better at finding the + correct Python. If FindPythonLibs/Interp are not available (CMake 3.27+), + then this will be ignored and FindPython will be used. 3. Set ``PYBIND11_NOPYTHON`` to ``TRUE``. Pybind11 will not search for Python. However, you will have to use the target-based system, and do more setup yourself, because it does not know about or include things that depend on diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5b7e3f801d..18308731ab 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,16 +5,7 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -cmake_minimum_required(VERSION 3.5) - -# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with -# some versions of VS that have a patched CMake 3.11. This forces us to emulate -# the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.29) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.29) -endif() +cmake_minimum_required(VERSION 3.15...3.30) # Filter out items; print an optional message if any items filtered. This ignores extensions. # @@ -76,8 +67,8 @@ project(pybind11_tests CXX) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../tools") option(PYBIND11_WERROR "Report all warnings as errors" OFF) -option(DOWNLOAD_EIGEN "Download EIGEN (requires CMake 3.11+)" OFF) -option(PYBIND11_CUDA_TESTS "Enable building CUDA tests (requires CMake 3.12+)" OFF) +option(DOWNLOAD_EIGEN "Download EIGEN" OFF) +option(PYBIND11_CUDA_TESTS "Enable building CUDA tests" OFF) set(PYBIND11_TEST_OVERRIDE "" CACHE STRING "Tests from ;-separated list of *.cpp files will be built instead of all tests") @@ -249,25 +240,21 @@ endif() if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) # Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake). # Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also - # produces a fatal error if loaded from a pre-3.0 cmake. if(DOWNLOAD_EIGEN) - if(CMAKE_VERSION VERSION_LESS 3.11) - message(FATAL_ERROR "CMake 3.11+ required when using DOWNLOAD_EIGEN") + if(CMAKE_VERSION VERSION_LESS 3.18) + set(_opts) + else() + set(_opts SOURCE_SUBDIR no-cmake-build) endif() - include(FetchContent) FetchContent_Declare( eigen GIT_REPOSITORY "${PYBIND11_EIGEN_REPO}" - GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}") - - FetchContent_GetProperties(eigen) - if(NOT eigen_POPULATED) - message( - STATUS - "Downloading Eigen ${PYBIND11_EIGEN_VERSION_STRING} (${PYBIND11_EIGEN_VERSION_HASH}) from ${PYBIND11_EIGEN_REPO}" - ) - FetchContent_Populate(eigen) + GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}" + ${_opts}) + FetchContent_MakeAvailable(eigen) + if(NOT CMAKE_VERSION VERSION_LESS 3.18) + set(EIGEN3_INCLUDE_DIR "${eigen_SOURCE_DIR}") endif() set(EIGEN3_INCLUDE_DIR ${eigen_SOURCE_DIR}) @@ -315,8 +302,7 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I}) endif() - message( - STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download") + message(STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON to download") endif() endif() @@ -501,12 +487,10 @@ foreach(target ${test_targets}) endforeach() # Provide nice organisation in IDEs -if(NOT CMAKE_VERSION VERSION_LESS 3.8) - source_group( - TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" - PREFIX "Header Files" - FILES ${PYBIND11_HEADERS}) -endif() +source_group( + TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" + PREFIX "Header Files" + FILES ${PYBIND11_HEADERS}) # Make sure pytest is found or produce a warning pybind11_find_import(pytest VERSION 3.1) diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index aedbdf1c1e..0a5db90179 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -9,7 +9,6 @@ import zipfile # These tests must be run explicitly -# They require CMake 3.15+ (--install) DIR = os.path.abspath(os.path.dirname(__file__)) MAIN_DIR = os.path.dirname(os.path.dirname(DIR)) diff --git a/tests/pyproject.toml b/tests/pyproject.toml index 044bf15c05..469c145dfd 100644 --- a/tests/pyproject.toml +++ b/tests/pyproject.toml @@ -10,10 +10,6 @@ name = "pybind11_tests" version = "0.0.1" dependencies = ["pytest", "pytest-timeout", "numpy", "scipy"] -[tool.scikit-build] -# Hide a warning while we also support CMake < 3.15 -cmake.version = ">=3.15" - [tool.scikit-build.cmake.define] PYBIND11_FINDPYTHON = true diff --git a/tests/test_cmake_build/installed_embed/CMakeLists.txt b/tests/test_cmake_build/installed_embed/CMakeLists.txt index 2be0aa6596..5c6267c72e 100644 --- a/tests/test_cmake_build/installed_embed/CMakeLists.txt +++ b/tests/test_cmake_build/installed_embed/CMakeLists.txt @@ -1,13 +1,4 @@ -cmake_minimum_required(VERSION 3.5) - -# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with -# some versions of VS that have a patched CMake 3.11. This forces us to emulate -# the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.29) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.29) -endif() +cmake_minimum_required(VERSION 3.15...3.30) project(test_installed_embed CXX) diff --git a/tests/test_cmake_build/installed_function/CMakeLists.txt b/tests/test_cmake_build/installed_function/CMakeLists.txt index fa7795e1e6..2945b3d2e6 100644 --- a/tests/test_cmake_build/installed_function/CMakeLists.txt +++ b/tests/test_cmake_build/installed_function/CMakeLists.txt @@ -1,14 +1,4 @@ -cmake_minimum_required(VERSION 3.5) -project(test_installed_module CXX) - -# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with -# some versions of VS that have a patched CMake 3.11. This forces us to emulate -# the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.29) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.29) -endif() +cmake_minimum_required(VERSION 3.15...3.30) project(test_installed_function CXX) diff --git a/tests/test_cmake_build/installed_target/CMakeLists.txt b/tests/test_cmake_build/installed_target/CMakeLists.txt index 7e73f42435..344c8bc698 100644 --- a/tests/test_cmake_build/installed_target/CMakeLists.txt +++ b/tests/test_cmake_build/installed_target/CMakeLists.txt @@ -1,13 +1,4 @@ -cmake_minimum_required(VERSION 3.5) - -# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with -# some versions of VS that have a patched CMake 3.11. This forces us to emulate -# the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.29) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.29) -endif() +cmake_minimum_required(VERSION 3.15...3.30) project(test_installed_target CXX) diff --git a/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt b/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt index 33a366472c..d0ae0798e5 100644 --- a/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt @@ -1,13 +1,4 @@ -cmake_minimum_required(VERSION 3.5) - -# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with -# some versions of VS that have a patched CMake 3.11. This forces us to emulate -# the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.29) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.29) -endif() +cmake_minimum_required(VERSION 3.15...3.30) project(test_subdirectory_embed CXX) diff --git a/tests/test_cmake_build/subdirectory_function/CMakeLists.txt b/tests/test_cmake_build/subdirectory_function/CMakeLists.txt index 76418a71f3..a521f33df6 100644 --- a/tests/test_cmake_build/subdirectory_function/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_function/CMakeLists.txt @@ -1,13 +1,4 @@ -cmake_minimum_required(VERSION 3.5) - -# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with -# some versions of VS that have a patched CMake 3.11. This forces us to emulate -# the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.29) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.29) -endif() +cmake_minimum_required(VERSION 3.15...3.30) project(test_subdirectory_function CXX) diff --git a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt index 28e9031878..4a5a7f2be9 100644 --- a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt @@ -1,13 +1,4 @@ -cmake_minimum_required(VERSION 3.5) - -# The `cmake_minimum_required(VERSION 3.5...3.29)` syntax does not work with -# some versions of VS that have a patched CMake 3.11. This forces us to emulate -# the behavior using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.29) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.29) -endif() +cmake_minimum_required(VERSION 3.15...3.30) project(test_subdirectory_target CXX) diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index 7d8d94b11d..450a141566 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -18,11 +18,7 @@ Adds the following functions:: #]======================================================] -# CMake 3.10 has an include_guard command, but we can't use that yet -# include_guard(global) (pre-CMake 3.10) -if(TARGET pybind11::pybind11) - return() -endif() +include_guard(GLOBAL) # If we are in subdirectory mode, all IMPORTED targets must be GLOBAL. If we # are in CONFIG mode, they should be "normal" targets instead. @@ -80,48 +76,32 @@ set_property( # Please open an issue if you need to use it; it will be removed if no one # needs it. if(CMAKE_SYSTEM_NAME MATCHES Emscripten AND NOT _pybind11_no_exceptions) - if(CMAKE_VERSION VERSION_LESS 3.13) - message(WARNING "CMake 3.13+ is required to build for Emscripten. Some flags will be missing") + if(is_config) + set(_tmp_config_target pybind11::pybind11_headers) else() - if(is_config) - set(_tmp_config_target pybind11::pybind11_headers) - else() - set(_tmp_config_target pybind11_headers) - endif() - - set_property( - TARGET ${_tmp_config_target} - APPEND - PROPERTY INTERFACE_LINK_OPTIONS -fexceptions) - set_property( - TARGET ${_tmp_config_target} - APPEND - PROPERTY INTERFACE_COMPILE_OPTIONS -fexceptions) - unset(_tmp_config_target) + set(_tmp_config_target pybind11_headers) endif() -endif() - -# --------------------------- link helper --------------------------- - -add_library(pybind11::python_link_helper IMPORTED INTERFACE ${optional_global}) -if(CMAKE_VERSION VERSION_LESS 3.13) - # In CMake 3.11+, you can set INTERFACE properties via the normal methods, and - # this would be simpler. set_property( - TARGET pybind11::python_link_helper + TARGET ${_tmp_config_target} APPEND - PROPERTY INTERFACE_LINK_LIBRARIES "$<$:-undefined dynamic_lookup>") -else() - # link_options was added in 3.13+ - # This is safer, because you are ensured the deduplication pass in CMake will not consider - # these separate and remove one but not the other. + PROPERTY INTERFACE_LINK_OPTIONS -fexceptions) set_property( - TARGET pybind11::python_link_helper + TARGET ${_tmp_config_target} APPEND - PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-undefined,dynamic_lookup>") + PROPERTY INTERFACE_COMPILE_OPTIONS -fexceptions) + unset(_tmp_config_target) endif() +# --------------------------- link helper --------------------------- + +add_library(pybind11::python_link_helper IMPORTED INTERFACE ${optional_global}) + +set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-undefined,dynamic_lookup>") + # ------------------------ Windows extras ------------------------- add_library(pybind11::windows_extras IMPORTED INTERFACE ${optional_global}) @@ -136,22 +116,14 @@ if(MSVC) # That's also clang-cl # /MP enables multithreaded builds (relevant when there are many files) for MSVC if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # no Clang no Intel - if(CMAKE_VERSION VERSION_LESS 3.11) - set_property( - TARGET pybind11::windows_extras - APPEND - PROPERTY INTERFACE_COMPILE_OPTIONS $<$>:/MP>) - else() - # Only set these options for C++ files. This is important so that, for - # instance, projects that include other types of source files like CUDA - # .cu files don't get these options propagated to nvcc since that would - # cause the build to fail. - set_property( - TARGET pybind11::windows_extras - APPEND - PROPERTY INTERFACE_COMPILE_OPTIONS - $<$>:$<$:/MP>>) - endif() + # Only set these options for C++ files. This is important so that, for + # instance, projects that include other types of source files like CUDA + # .cu files don't get these options propagated to nvcc since that would + # cause the build to fail. + set_property( + TARGET pybind11::windows_extras + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS $<$>:$<$:/MP>>) endif() endif() @@ -397,11 +369,7 @@ function(_pybind11_generate_lto target prefer_thin_lto) set(is_debug "$,$>") set(not_debug "$") set(cxx_lang "$") - if(MSVC AND CMAKE_VERSION VERSION_LESS 3.11) - set(genex "${not_debug}") - else() - set(genex "$") - endif() + set(genex "$") set_property( TARGET ${target} APPEND @@ -416,17 +384,10 @@ function(_pybind11_generate_lto target prefer_thin_lto) endif() if(PYBIND11_LTO_LINKER_FLAGS) - if(CMAKE_VERSION VERSION_LESS 3.11) - set_property( - TARGET ${target} - APPEND - PROPERTY INTERFACE_LINK_LIBRARIES "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") - else() - set_property( - TARGET ${target} - APPEND - PROPERTY INTERFACE_LINK_OPTIONS "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") - endif() + set_property( + TARGET ${target} + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") endif() endfunction() diff --git a/tools/pybind11GuessPythonExtSuffix.cmake b/tools/pybind11GuessPythonExtSuffix.cmake index c5fb3b42c9..b550f39350 100644 --- a/tools/pybind11GuessPythonExtSuffix.cmake +++ b/tools/pybind11GuessPythonExtSuffix.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.15...3.30) function(pybind11_guess_python_module_extension python) diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index 444f3aaf54..d3e938fab0 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -5,10 +5,6 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -if(CMAKE_VERSION VERSION_LESS 3.12) - message(FATAL_ERROR "You cannot use the new FindPython module with CMake < 3.12") -endif() - include_guard(DIRECTORY) get_property( @@ -236,7 +232,6 @@ if(TARGET ${_Python}::Python) PROPERTY INTERFACE_LINK_LIBRARIES ${_Python}::Python) endif() -# CMake 3.15+ has this if(TARGET ${_Python}::Module) set_property( TARGET pybind11::module diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 021038d954..d91e7670cc 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -5,13 +5,7 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. -# include_guard(global) (pre-CMake 3.10) -if(TARGET pybind11::python_headers) - return() -endif() - -# Built-in in CMake 3.5+ -include(CMakeParseArguments) +include_guard(GLOBAL) if(pybind11_FIND_QUIETLY) set(_pybind11_quiet QUIET) @@ -116,36 +110,19 @@ if(PYTHON_IS_DEBUG) PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) endif() -# The <3.11 code here does not support release/debug builds at the same time, like on vcpkg -if(CMAKE_VERSION VERSION_LESS 3.11) - set_property( - TARGET pybind11::module - APPEND - PROPERTY - INTERFACE_LINK_LIBRARIES - pybind11::python_link_helper - "$<$,$>:$>" - ) - - set_property( - TARGET pybind11::embed - APPEND - PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11 $) -else() - # The IMPORTED INTERFACE library here is to ensure that "debug" and "release" get processed outside - # of a generator expression - https://gitlab.kitware.com/cmake/cmake/-/issues/18424, as they are - # target_link_library keywords rather than real libraries. - add_library(pybind11::_ClassicPythonLibraries IMPORTED INTERFACE) - target_link_libraries(pybind11::_ClassicPythonLibraries INTERFACE ${PYTHON_LIBRARIES}) - target_link_libraries( - pybind11::module - INTERFACE - pybind11::python_link_helper - "$<$,$>:pybind11::_ClassicPythonLibraries>") - - target_link_libraries(pybind11::embed INTERFACE pybind11::pybind11 - pybind11::_ClassicPythonLibraries) -endif() +# The IMPORTED INTERFACE library here is to ensure that "debug" and "release" get processed outside +# of a generator expression - https://gitlab.kitware.com/cmake/cmake/-/issues/18424, as they are +# target_link_library keywords rather than real libraries. +add_library(pybind11::_ClassicPythonLibraries IMPORTED INTERFACE) +target_link_libraries(pybind11::_ClassicPythonLibraries INTERFACE ${PYTHON_LIBRARIES}) +target_link_libraries( + pybind11::module + INTERFACE + pybind11::python_link_helper + "$<$,$>:pybind11::_ClassicPythonLibraries>") + +target_link_libraries(pybind11::embed INTERFACE pybind11::pybind11 + pybind11::_ClassicPythonLibraries) function(pybind11_extension name) # The prefix and extension are provided by FindPythonLibsNew.cmake diff --git a/tools/test-pybind11GuessPythonExtSuffix.cmake b/tools/test-pybind11GuessPythonExtSuffix.cmake index 0de2c0169b..ac90e039bf 100644 --- a/tools/test-pybind11GuessPythonExtSuffix.cmake +++ b/tools/test-pybind11GuessPythonExtSuffix.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.15...3.30) # Tests for pybind11_guess_python_module_extension # Run using `cmake -P tools/test-pybind11GuessPythonExtSuffix.cmake` From bd5951b69194404be14e005da64b9f42fd4d080e Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 14 Aug 2024 23:57:37 -0400 Subject: [PATCH 15/26] docs: prepare for 2.13.4 (#5312) Signed-off-by: Henry Schreiner --- docs/changelog.rst | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index d19a901297..031d315820 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -20,7 +20,28 @@ New Features: * Support for Python 3.7 was removed. (Official end-of-life: 2023-06-27). `#5191 `_ -Support for CMake older than 3.15 and some older compilers will also be removed. +* stl.h ``list|set|map_caster`` were made more user friendly: it is no longer + necessary to explicitly convert Python iterables to ``tuple()``, ``set()``, + or ``map()`` in many common situations. + `#4686 `_ + +* Support for CMake older than 3.15 removed. CMake 3.15-3.30 supported. + `#5304 `_ + +Version 2.13.4 (August 14, 2024) +-------------------------------- + +Bug fixes: + +* Fix paths with spaces, including on Windows. + (Replaces regression from `#5302 `_) + `#4874 `_ + +Documentation: + +* Remove repetitive words. + `#5308 `_ + Version 2.13.3 (August 13, 2024) -------------------------------- From a1d00916b26b187e583f3bce39cd59c3b0652c32 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 15 Aug 2024 12:30:29 +0700 Subject: [PATCH 16/26] Backport of https://github.com/google/pywrapcc/pull/30034 (#5305) --- include/pybind11/stl.h | 92 +++++++++++++++++++++++++++++++++--------- tests/test_stl.cpp | 17 ++++++++ tests/test_stl.py | 7 ++++ 3 files changed, 98 insertions(+), 18 deletions(-) diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 096301bc72..6a148e7402 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -11,11 +11,14 @@ #include "pybind11.h" #include "detail/common.h" +#include "detail/descr.h" +#include "detail/type_caster_base.h" #include #include #include #include +#include #include #include #include @@ -349,36 +352,65 @@ struct type_caster> : list_caster struct type_caster> : list_caster, Type> {}; +template +ArrayType vector_to_array_impl(V &&v, index_sequence) { + return {{std::move(v[I])...}}; +} + +// Based on https://en.cppreference.com/w/cpp/container/array/to_array +template +ArrayType vector_to_array(V &&v) { + return vector_to_array_impl(std::forward(v), make_index_sequence{}); +} + template struct array_caster { using value_conv = make_caster; private: - template - bool require_size(enable_if_t size) { - if (value.size() != size) { - value.resize(size); + std::unique_ptr value; + + template = 0> + bool convert_elements(handle seq, bool convert) { + auto l = reinterpret_borrow(seq); + value.reset(new ArrayType{}); + // Using `resize` to preserve the behavior exactly as it was before PR #5305 + // For the `resize` to work, `Value` must be default constructible. + // For `std::valarray`, this is a requirement: + // https://en.cppreference.com/w/cpp/named_req/NumericType + value->resize(l.size()); + size_t ctr = 0; + for (const auto &it : l) { + value_conv conv; + if (!conv.load(it, convert)) { + return false; + } + (*value)[ctr++] = cast_op(std::move(conv)); } return true; } - template - bool require_size(enable_if_t size) { - return size == Size; - } + template = 0> bool convert_elements(handle seq, bool convert) { auto l = reinterpret_borrow(seq); - if (!require_size(l.size())) { + if (l.size() != Size) { return false; } - size_t ctr = 0; - for (const auto &it : l) { + // The `temp` storage is needed to support `Value` types that are not + // default-constructible. + // Deliberate choice: no template specializations, for simplicity, and + // because the compile time overhead for the specializations is deemed + // more significant than the runtime overhead for the `temp` storage. + std::vector temp; + temp.reserve(l.size()); + for (auto it : l) { value_conv conv; if (!conv.load(it, convert)) { return false; } - value[ctr++] = cast_op(std::move(conv)); + temp.emplace_back(cast_op(std::move(conv))); } + value.reset(new ArrayType(vector_to_array(std::move(temp)))); return true; } @@ -416,12 +448,36 @@ struct array_caster { return l.release(); } - PYBIND11_TYPE_CASTER(ArrayType, - const_name(const_name(""), const_name("Annotated[")) - + const_name("list[") + value_conv::name + const_name("]") - + const_name(const_name(""), - const_name(", FixedSize(") - + const_name() + const_name(")]"))); + // Code copied from PYBIND11_TYPE_CASTER macro. + // Intentionally preserving the behavior exactly as it was before PR #5305 + template >::value, int> = 0> + static handle cast(T_ *src, return_value_policy policy, handle parent) { + if (!src) { + return none().release(); + } + if (policy == return_value_policy::take_ownership) { + auto h = cast(std::move(*src), policy, parent); + delete src; // WARNING: Assumes `src` was allocated with `new`. + return h; + } + return cast(*src, policy, parent); + } + + // NOLINTNEXTLINE(google-explicit-constructor) + operator ArrayType *() { return &(*value); } + // NOLINTNEXTLINE(google-explicit-constructor) + operator ArrayType &() { return *value; } + // NOLINTNEXTLINE(google-explicit-constructor) + operator ArrayType &&() && { return std::move(*value); } + + template + using cast_op_type = movable_cast_op_type; + + static constexpr auto name + = const_name(const_name(""), const_name("Annotated[")) + const_name("list[") + + value_conv::name + const_name("]") + + const_name( + const_name(""), const_name(", FixedSize(") + const_name() + const_name(")]")); }; template diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index c35f0d4cad..6240524a09 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -201,6 +201,23 @@ TEST_SUBMODULE(stl, m) { m.def("cast_array", []() { return std::array{{1, 2}}; }); m.def("load_array", [](const std::array &a) { return a[0] == 1 && a[1] == 2; }); + struct NoDefaultCtor { + explicit constexpr NoDefaultCtor(int val) : val{val} {} + int val; + }; + + struct NoDefaultCtorArray { + explicit constexpr NoDefaultCtorArray(int i) + : arr{{NoDefaultCtor(10 + i), NoDefaultCtor(20 + i)}} {} + std::array arr; + }; + + // test_array_no_default_ctor + py::class_(m, "NoDefaultCtor").def_readonly("val", &NoDefaultCtor::val); + py::class_(m, "NoDefaultCtorArray") + .def(py::init()) + .def_readwrite("arr", &NoDefaultCtorArray::arr); + // test_valarray m.def("cast_valarray", []() { return std::valarray{1, 4, 9}; }); m.def("load_valarray", [](const std::valarray &v) { diff --git a/tests/test_stl.py b/tests/test_stl.py index 1896c32217..6f548789ed 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -48,6 +48,13 @@ def test_array(doc): ) +def test_array_no_default_ctor(): + lst = m.NoDefaultCtorArray(3) + assert [e.val for e in lst.arr] == [13, 23] + lst.arr = m.NoDefaultCtorArray(4).arr + assert [e.val for e in lst.arr] == [14, 24] + + def test_valarray(doc): """std::valarray <-> list""" lst = m.cast_valarray() From 01b6ccb7fa186d730cf41c256203583edebe9e1d Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 18 Aug 2024 04:02:39 +0700 Subject: [PATCH 17/26] Move smart_holder POC code to tests/pure_cpp directory. (#5315) * Rename detail/smart_holder_poc.h -> struct_smart_holder.h * Establish (empty) tests/pure_cpp/smart_holder_poc.h * Move code guarded by `PYBIND11_TESTS_PURE_CPP_SMART_HOLDER_POC_TEST_CPP` from struct_smart_holder.h to tests/pure_cpp/smart_holder_poc.h --- CMakeLists.txt | 2 +- ...art_holder_poc.h => struct_smart_holder.h} | 55 +------------ include/pybind11/detail/type_caster_base.h | 2 +- include/pybind11/detail/using_smart_holder.h | 2 +- tests/extra_python_package/test_files.py | 2 +- tests/pure_cpp/smart_holder_poc.h | 51 ++++++++++++ tests/pure_cpp/smart_holder_poc_test.cpp | 81 ++++++++++--------- 7 files changed, 97 insertions(+), 98 deletions(-) rename include/pybind11/detail/{smart_holder_poc.h => struct_smart_holder.h} (85%) create mode 100644 tests/pure_cpp/smart_holder_poc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aa9634eeeb..d0f8f7e0ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,7 +133,7 @@ set(PYBIND11_HEADERS include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h include/pybind11/detail/init.h include/pybind11/detail/internals.h - include/pybind11/detail/smart_holder_poc.h + include/pybind11/detail/struct_smart_holder.h include/pybind11/detail/type_caster_base.h include/pybind11/detail/typeid.h include/pybind11/detail/using_smart_holder.h diff --git a/include/pybind11/detail/smart_holder_poc.h b/include/pybind11/detail/struct_smart_holder.h similarity index 85% rename from include/pybind11/detail/smart_holder_poc.h rename to include/pybind11/detail/struct_smart_holder.h index 89742ab27e..6fdd95f4df 100644 --- a/include/pybind11/detail/smart_holder_poc.h +++ b/include/pybind11/detail/struct_smart_holder.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 The Pybind Development Team. +// Copyright (c) 2020-2024 The Pybind Development Team. // All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @@ -44,16 +44,6 @@ High-level aspects: * The `void_cast_raw_ptr` option is needed to make the `smart_holder` `vptr` member invisible to the `shared_from_this` mechanism, in case the lifetime of a `PyObject` is tied to the pointee. - -* Regarding `PYBIND11_TESTS_PURE_CPP_SMART_HOLDER_POC_TEST_CPP` below: - This define serves as a marker for code that is NOT used - from smart_holder_type_casters.h, but is exercised only from - tests/pure_cpp/smart_holder_poc_test.cpp. The marked code was useful - mainly for bootstrapping the smart_holder work. At this stage, with - smart_holder_type_casters.h in production use (at Google) since around - February 2021, it could be moved from here to tests/pure_cpp/ (help welcome). - It will probably be best in most cases to add tests for new functionality - under test/test_class_sh_*. */ #pragma once @@ -256,26 +246,6 @@ struct smart_holder { return static_cast(vptr.get()); } -#ifdef PYBIND11_TESTS_PURE_CPP_SMART_HOLDER_POC_TEST_CPP // See comment near top. - template - T &as_lvalue_ref() const { - static const char *context = "as_lvalue_ref"; - ensure_is_populated(context); - ensure_has_pointee(context); - return *as_raw_ptr_unowned(); - } -#endif - -#ifdef PYBIND11_TESTS_PURE_CPP_SMART_HOLDER_POC_TEST_CPP // See comment near top. - template - T &&as_rvalue_ref() const { - static const char *context = "as_rvalue_ref"; - ensure_is_populated(context); - ensure_has_pointee(context); - return std::move(*as_raw_ptr_unowned()); - } -#endif - template static smart_holder from_raw_ptr_take_ownership(T *raw_ptr, bool void_cast_raw_ptr = false) { ensure_pointee_is_destructible("from_raw_ptr_take_ownership"); @@ -319,16 +289,6 @@ struct smart_holder { release_disowned(); } -#ifdef PYBIND11_TESTS_PURE_CPP_SMART_HOLDER_POC_TEST_CPP // See comment near top. - template - T *as_raw_ptr_release_ownership(const char *context = "as_raw_ptr_release_ownership") { - ensure_can_release_ownership(context); - T *raw_ptr = as_raw_ptr_unowned(); - release_ownership(); - return raw_ptr; - } -#endif - template static smart_holder from_unique_ptr(std::unique_ptr &&unq_ptr, void *void_ptr = nullptr) { @@ -351,19 +311,6 @@ struct smart_holder { return hld; } -#ifdef PYBIND11_TESTS_PURE_CPP_SMART_HOLDER_POC_TEST_CPP // See comment near top. - template > - std::unique_ptr as_unique_ptr() { - static const char *context = "as_unique_ptr"; - ensure_compatible_rtti_uqp_del(context); - ensure_use_count_1(context); - T *raw_ptr = as_raw_ptr_unowned(); - release_ownership(); - // KNOWN DEFECT (see PR #4850): Does not copy the deleter. - return std::unique_ptr(raw_ptr); - } -#endif - template static smart_holder from_shared_ptr(std::shared_ptr shd_ptr) { smart_holder hld; diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 92415718fa..cc62cfa8e5 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -789,7 +789,7 @@ struct load_helper : value_and_holder_helper { auto *gd = std::get_deleter(holder().vptr); if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del() call above. - // In smart_holder_poc, a custom deleter is always stored in a guarded delete. + // In struct_smart_holder, a custom deleter is always stored in a guarded delete. // The guarded delete's std::function actually points at the // custom_deleter type, so we can verify it is of the custom deleter type and // finally extract its deleter. diff --git a/include/pybind11/detail/using_smart_holder.h b/include/pybind11/detail/using_smart_holder.h index 3d04632098..826e6b2c65 100644 --- a/include/pybind11/detail/using_smart_holder.h +++ b/include/pybind11/detail/using_smart_holder.h @@ -10,7 +10,7 @@ #include #ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT -# include "smart_holder_poc.h" +# include "struct_smart_holder.h" #endif PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index e6602ad79b..27c6f64210 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -58,7 +58,7 @@ "include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h", "include/pybind11/detail/init.h", "include/pybind11/detail/internals.h", - "include/pybind11/detail/smart_holder_poc.h", + "include/pybind11/detail/struct_smart_holder.h", "include/pybind11/detail/type_caster_base.h", "include/pybind11/detail/typeid.h", "include/pybind11/detail/using_smart_holder.h", diff --git a/tests/pure_cpp/smart_holder_poc.h b/tests/pure_cpp/smart_holder_poc.h new file mode 100644 index 0000000000..320311b7d6 --- /dev/null +++ b/tests/pure_cpp/smart_holder_poc.h @@ -0,0 +1,51 @@ +// Copyright (c) 2020-2024 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#pragma once + +#include "pybind11/detail/struct_smart_holder.h" + +namespace pybindit { +namespace memory { +namespace smart_holder_poc { // Proof-of-Concept implementations. + +template +T &as_lvalue_ref(const smart_holder &hld) { + static const char *context = "as_lvalue_ref"; + hld.ensure_is_populated(context); + hld.ensure_has_pointee(context); + return *hld.as_raw_ptr_unowned(); +} + +template +T &&as_rvalue_ref(const smart_holder &hld) { + static const char *context = "as_rvalue_ref"; + hld.ensure_is_populated(context); + hld.ensure_has_pointee(context); + return std::move(*hld.as_raw_ptr_unowned()); +} + +template +T *as_raw_ptr_release_ownership(smart_holder &hld, + const char *context = "as_raw_ptr_release_ownership") { + hld.ensure_can_release_ownership(context); + T *raw_ptr = hld.as_raw_ptr_unowned(); + hld.release_ownership(); + return raw_ptr; +} + +template > +std::unique_ptr as_unique_ptr(smart_holder &hld) { + static const char *context = "as_unique_ptr"; + hld.ensure_compatible_rtti_uqp_del(context); + hld.ensure_use_count_1(context); + T *raw_ptr = hld.as_raw_ptr_unowned(); + hld.release_ownership(); + // KNOWN DEFECT (see PR #4850): Does not copy the deleter. + return std::unique_ptr(raw_ptr); +} + +} // namespace smart_holder_poc +} // namespace memory +} // namespace pybindit diff --git a/tests/pure_cpp/smart_holder_poc_test.cpp b/tests/pure_cpp/smart_holder_poc_test.cpp index a9ad0364ef..24ab643eeb 100644 --- a/tests/pure_cpp/smart_holder_poc_test.cpp +++ b/tests/pure_cpp/smart_holder_poc_test.cpp @@ -1,6 +1,4 @@ -#define PYBIND11_TESTS_PURE_CPP_SMART_HOLDER_POC_TEST_CPP - -#include "pybind11/detail/smart_holder_poc.h" +#include "smart_holder_poc.h" #include #include @@ -16,6 +14,7 @@ #include "catch.hpp" using pybindit::memory::smart_holder; +namespace poc = pybindit::memory::smart_holder_poc; namespace helpers { @@ -70,14 +69,14 @@ TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") { TEST_CASE("from_raw_ptr_unowned+as_lvalue_ref", "[S]") { static int value = 19; auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); } TEST_CASE("from_raw_ptr_unowned+as_rvalue_ref", "[S]") { helpers::movable_int orig(19); { auto hld = smart_holder::from_raw_ptr_unowned(&orig); - helpers::movable_int othr(hld.as_rvalue_ref()); + helpers::movable_int othr(poc::as_rvalue_ref(hld)); REQUIRE(othr.valu == 19); REQUIRE(orig.valu == 91); } @@ -86,21 +85,21 @@ TEST_CASE("from_raw_ptr_unowned+as_rvalue_ref", "[S]") { TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_release_ownership", "[E]") { static int value = 19; auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership(), + REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), "Cannot disown non-owning holder (as_raw_ptr_release_ownership)."); } TEST_CASE("from_raw_ptr_unowned+as_unique_ptr", "[E]") { static int value = 19; auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE_THROWS_WITH(hld.as_unique_ptr(), + REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), "Cannot disown non-owning holder (as_unique_ptr)."); } TEST_CASE("from_raw_ptr_unowned+as_unique_ptr_with_deleter", "[E]") { static int value = 19; auto hld = smart_holder::from_raw_ptr_unowned(&value); - REQUIRE_THROWS_WITH((hld.as_unique_ptr>()), + REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), "Missing unique_ptr deleter (as_unique_ptr)."); } @@ -113,12 +112,12 @@ TEST_CASE("from_raw_ptr_unowned+as_shared_ptr", "[S]") { TEST_CASE("from_raw_ptr_take_ownership+as_lvalue_ref", "[S]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); REQUIRE(hld.has_pointee()); - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); } TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership1", "[S]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - auto new_owner = std::unique_ptr(hld.as_raw_ptr_release_ownership()); + auto new_owner = std::unique_ptr(poc::as_raw_ptr_release_ownership(hld)); REQUIRE(!hld.has_pointee()); REQUIRE(*new_owner == 19); } @@ -126,13 +125,13 @@ TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership1", "[S]") { TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership2", "[E]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); auto shd_ptr = hld.as_shared_ptr(); - REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership(), + REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), "Cannot disown use_count != 1 (as_raw_ptr_release_ownership)."); } TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr1", "[S]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - std::unique_ptr new_owner = hld.as_unique_ptr(); + std::unique_ptr new_owner = poc::as_unique_ptr(hld); REQUIRE(!hld.has_pointee()); REQUIRE(*new_owner == 19); } @@ -140,12 +139,13 @@ TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr1", "[S]") { TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr2", "[E]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); auto shd_ptr = hld.as_shared_ptr(); - REQUIRE_THROWS_WITH(hld.as_unique_ptr(), "Cannot disown use_count != 1 (as_unique_ptr)."); + REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), + "Cannot disown use_count != 1 (as_unique_ptr)."); } TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr_with_deleter", "[E]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - REQUIRE_THROWS_WITH((hld.as_unique_ptr>()), + REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), "Missing unique_ptr deleter (as_unique_ptr)."); } @@ -160,14 +160,14 @@ TEST_CASE("from_raw_ptr_take_ownership+disown+reclaim_disowned", "[S]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); std::unique_ptr new_owner(hld.as_raw_ptr_unowned()); hld.disown(); - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); REQUIRE(*new_owner == 19); hld.reclaim_disowned(); // Manually veriified: without this, clang++ -fsanitize=address reports // "detected memory leaks". // NOLINTNEXTLINE(bugprone-unused-return-value) (void) new_owner.release(); // Manually verified: without this, clang++ -fsanitize=address // reports "attempting double-free". - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); REQUIRE(new_owner.get() == nullptr); } @@ -175,7 +175,7 @@ TEST_CASE("from_raw_ptr_take_ownership+disown+release_disowned", "[S]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); std::unique_ptr new_owner(hld.as_raw_ptr_unowned()); hld.disown(); - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); REQUIRE(*new_owner == 19); hld.release_disowned(); REQUIRE(!hld.has_pointee()); @@ -195,14 +195,14 @@ TEST_CASE("from_unique_ptr+as_lvalue_ref", "[S]") { std::unique_ptr orig_owner(new int(19)); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); } TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership1", "[S]") { std::unique_ptr orig_owner(new int(19)); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - auto new_owner = std::unique_ptr(hld.as_raw_ptr_release_ownership()); + auto new_owner = std::unique_ptr(poc::as_raw_ptr_release_ownership(hld)); REQUIRE(!hld.has_pointee()); REQUIRE(*new_owner == 19); } @@ -212,7 +212,7 @@ TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership2", "[E]") { auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); auto shd_ptr = hld.as_shared_ptr(); - REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership(), + REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), "Cannot disown use_count != 1 (as_raw_ptr_release_ownership)."); } @@ -220,7 +220,7 @@ TEST_CASE("from_unique_ptr+as_unique_ptr1", "[S]") { std::unique_ptr orig_owner(new int(19)); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - std::unique_ptr new_owner = hld.as_unique_ptr(); + std::unique_ptr new_owner = poc::as_unique_ptr(hld); REQUIRE(!hld.has_pointee()); REQUIRE(*new_owner == 19); } @@ -230,14 +230,15 @@ TEST_CASE("from_unique_ptr+as_unique_ptr2", "[E]") { auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); auto shd_ptr = hld.as_shared_ptr(); - REQUIRE_THROWS_WITH(hld.as_unique_ptr(), "Cannot disown use_count != 1 (as_unique_ptr)."); + REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), + "Cannot disown use_count != 1 (as_unique_ptr)."); } TEST_CASE("from_unique_ptr+as_unique_ptr_with_deleter", "[E]") { std::unique_ptr orig_owner(new int(19)); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH((hld.as_unique_ptr>()), + REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), "Incompatible unique_ptr deleter (as_unique_ptr)."); } @@ -254,7 +255,7 @@ TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base", "[S]") { std::unique_ptr orig_owner(new helpers::derived()); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - std::unique_ptr new_owner = hld.as_unique_ptr(); + std::unique_ptr new_owner = poc::as_unique_ptr(hld); REQUIRE(!hld.has_pointee()); REQUIRE(new_owner->get() == 100); } @@ -265,7 +266,7 @@ TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base2", "[E]") { auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); REQUIRE_THROWS_WITH( - (hld.as_unique_ptr>()), + (poc::as_unique_ptr>(hld)), "Incompatible unique_ptr deleter (as_unique_ptr)."); } @@ -273,7 +274,7 @@ TEST_CASE("from_unique_ptr_with_deleter+as_lvalue_ref", "[S]") { std::unique_ptr> orig_owner(new int(19)); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); } TEST_CASE("from_unique_ptr_with_std_function_deleter+as_lvalue_ref", "[S]") { @@ -281,14 +282,14 @@ TEST_CASE("from_unique_ptr_with_std_function_deleter+as_lvalue_ref", "[S]") { new int(19), [](const int *raw_ptr) { delete raw_ptr; }); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); } TEST_CASE("from_unique_ptr_with_deleter+as_raw_ptr_release_ownership", "[E]") { std::unique_ptr> orig_owner(new int(19)); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership(), + REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), "Cannot disown custom deleter (as_raw_ptr_release_ownership)."); } @@ -296,7 +297,7 @@ TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr", "[E]") { std::unique_ptr> orig_owner(new int(19)); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH(hld.as_unique_ptr(), + REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), "Incompatible unique_ptr deleter (as_unique_ptr)."); } @@ -305,7 +306,7 @@ TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter1", "[S]") { auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); std::unique_ptr> new_owner - = hld.as_unique_ptr>(); + = poc::as_unique_ptr>(hld); REQUIRE(!hld.has_pointee()); REQUIRE(*new_owner == 19); } @@ -314,7 +315,7 @@ TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter2", "[E]") { std::unique_ptr> orig_owner(new int(19)); auto hld = smart_holder::from_unique_ptr(std::move(orig_owner)); REQUIRE(orig_owner.get() == nullptr); - REQUIRE_THROWS_WITH((hld.as_unique_ptr>()), + REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), "Incompatible unique_ptr deleter (as_unique_ptr)."); } @@ -330,27 +331,27 @@ TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr", "[S]") { TEST_CASE("from_shared_ptr+as_lvalue_ref", "[S]") { std::shared_ptr orig_owner(new int(19)); auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE(hld.as_lvalue_ref() == 19); + REQUIRE(poc::as_lvalue_ref(hld) == 19); } TEST_CASE("from_shared_ptr+as_raw_ptr_release_ownership", "[E]") { std::shared_ptr orig_owner(new int(19)); auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership(), + REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership(hld), "Cannot disown external shared_ptr (as_raw_ptr_release_ownership)."); } TEST_CASE("from_shared_ptr+as_unique_ptr", "[E]") { std::shared_ptr orig_owner(new int(19)); auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE_THROWS_WITH(hld.as_unique_ptr(), + REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), "Cannot disown external shared_ptr (as_unique_ptr)."); } TEST_CASE("from_shared_ptr+as_unique_ptr_with_deleter", "[E]") { std::shared_ptr orig_owner(new int(19)); auto hld = smart_holder::from_shared_ptr(orig_owner); - REQUIRE_THROWS_WITH((hld.as_unique_ptr>()), + REQUIRE_THROWS_WITH((poc::as_unique_ptr>(hld)), "Missing unique_ptr deleter (as_unique_ptr)."); } @@ -362,19 +363,19 @@ TEST_CASE("from_shared_ptr+as_shared_ptr", "[S]") { TEST_CASE("error_unpopulated_holder", "[E]") { smart_holder hld; - REQUIRE_THROWS_WITH(hld.as_lvalue_ref(), "Unpopulated holder (as_lvalue_ref)."); + REQUIRE_THROWS_WITH(poc::as_lvalue_ref(hld), "Unpopulated holder (as_lvalue_ref)."); } TEST_CASE("error_disowned_holder", "[E]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - hld.as_unique_ptr(); - REQUIRE_THROWS_WITH(hld.as_lvalue_ref(), "Disowned holder (as_lvalue_ref)."); + poc::as_unique_ptr(hld); + REQUIRE_THROWS_WITH(poc::as_lvalue_ref(hld), "Disowned holder (as_lvalue_ref)."); } TEST_CASE("error_cannot_disown_nullptr", "[E]") { auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19)); - hld.as_unique_ptr(); - REQUIRE_THROWS_WITH(hld.as_unique_ptr(), "Cannot disown nullptr (as_unique_ptr)."); + poc::as_unique_ptr(hld); + REQUIRE_THROWS_WITH(poc::as_unique_ptr(hld), "Cannot disown nullptr (as_unique_ptr)."); } TEST_CASE("indestructible_int-from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") { From 4672f2bd151381afd16d5bfcb6544ffd8d2afbc5 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Mon, 19 Aug 2024 03:07:29 +0700 Subject: [PATCH 18/26] Add `instance::is_alias` and migrate from using `smart_holder::pointee_depends_on_holder_owner` to that. (#5318) --- include/pybind11/cast.h | 18 ++++++++++++++---- include/pybind11/detail/common.h | 5 +++++ include/pybind11/detail/struct_smart_holder.h | 4 +--- include/pybind11/detail/type_caster_base.h | 13 +++++++++++-- include/pybind11/pybind11.h | 7 +++---- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index d3b3fe072e..0262296aa9 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -858,8 +858,12 @@ struct copyable_holder_caster< using base::value; bool load(handle src, bool convert) { - return base::template load_impl>>( - src, convert); + if (base::template load_impl>>( + src, convert)) { + sh_load_helper.maybe_set_python_instance_is_alias(src); + return true; + } + return false; } explicit operator std::shared_ptr *() { @@ -914,6 +918,7 @@ struct copyable_holder_caster< void load_value(value_and_holder &&v_h) { if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { sh_load_helper.loaded_v_h = v_h; + sh_load_helper.was_populated = true; value = sh_load_helper.get_void_ptr_or_nullptr(); return; } @@ -1041,14 +1046,19 @@ struct move_only_holder_caster< } bool load(handle src, bool convert) { - return base::template load_impl< - move_only_holder_caster>>(src, convert); + if (base::template load_impl< + move_only_holder_caster>>(src, convert)) { + sh_load_helper.maybe_set_python_instance_is_alias(src); + return true; + } + return false; } void load_value(value_and_holder &&v_h) { if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { sh_load_helper.loaded_v_h = v_h; sh_load_helper.loaded_v_h.type = typeinfo; + sh_load_helper.was_populated = true; value = sh_load_helper.get_void_ptr_or_nullptr(); return; } diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 7b02e83cc3..4d2cc70a5b 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -637,6 +637,11 @@ struct instance { bool simple_instance_registered : 1; /// If true, get_internals().patients has an entry for this object bool has_patients : 1; +// Cannot use PYBIND11_INTERNALS_VERSION >= 6 here without refactoring. +#if PYBIND11_VERSION_MAJOR >= 3 + /// If true, this Python object needs to be kept alive for the lifetime of the C++ value. + bool is_alias : 1; +#endif /// Initializes all of the above type/values/holders data (but not the instance values /// themselves) diff --git a/include/pybind11/detail/struct_smart_holder.h b/include/pybind11/detail/struct_smart_holder.h index 6fdd95f4df..ccd289716b 100644 --- a/include/pybind11/detail/struct_smart_holder.h +++ b/include/pybind11/detail/struct_smart_holder.h @@ -136,7 +136,6 @@ struct smart_holder { bool vptr_is_external_shared_ptr : 1; bool is_populated : 1; bool is_disowned : 1; - bool pointee_depends_on_holder_owner : 1; // SMART_HOLDER_WIP: See PR #2839. // Design choice: smart_holder is movable but not copyable. smart_holder(smart_holder &&) = default; @@ -146,8 +145,7 @@ struct smart_holder { smart_holder() : vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false}, - vptr_is_external_shared_ptr{false}, is_populated{false}, is_disowned{false}, - pointee_depends_on_holder_owner{false} {} + vptr_is_external_shared_ptr{false}, is_populated{false}, is_disowned{false} {} bool has_pointee() const { return vptr != nullptr; } diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index cc62cfa8e5..a3453334a8 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -704,6 +704,15 @@ inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr template struct load_helper : value_and_holder_helper { + bool was_populated = false; + bool python_instance_is_alias = false; + + void maybe_set_python_instance_is_alias(handle src) { + if (was_populated) { + python_instance_is_alias = reinterpret_cast(src.ptr())->is_alias; + } + } + static std::shared_ptr make_shared_ptr_with_responsible_parent(T *raw_ptr, handle parent) { return std::shared_ptr(raw_ptr, shared_ptr_parent_life_support(parent.ptr())); } @@ -724,7 +733,7 @@ struct load_helper : value_and_holder_helper { throw std::runtime_error("Non-owning holder (load_as_shared_ptr)."); } auto *type_raw_ptr = static_cast(void_raw_ptr); - if (hld.pointee_depends_on_holder_owner) { + if (python_instance_is_alias) { auto *vptr_gd_ptr = std::get_deleter(hld.vptr); if (vptr_gd_ptr != nullptr) { std::shared_ptr released_ptr = vptr_gd_ptr->released_ptr.lock(); @@ -778,7 +787,7 @@ struct load_helper : value_and_holder_helper { auto *self_life_support = dynamic_raw_ptr_cast_if_possible(raw_type_ptr); - if (self_life_support == nullptr && holder().pointee_depends_on_holder_owner) { + if (self_life_support == nullptr && python_instance_is_alias) { throw value_error("Alias class (also known as trampoline) does not inherit from " "py::trampoline_self_life_support, therefore the ownership of this " "instance cannot safely be transferred to C++."); diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 2a88a94aca..9f356828b4 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2267,7 +2267,8 @@ class class_ : public detail::generic_type { } auto *uninitialized_location = std::addressof(v_h.holder()); auto *value_ptr_w_t = v_h.value_ptr(); - bool pointee_depends_on_holder_owner + // Try downcast from `type` to `type_alias`: + inst->is_alias = detail::dynamic_raw_ptr_cast_if_possible(value_ptr_w_t) != nullptr; if (holder_void_ptr) { // Note: inst->owned ignored. @@ -2277,14 +2278,12 @@ class class_ : public detail::generic_type { uninitialized_location, value_ptr_w_t, value_ptr_w_t)) { if (inst->owned) { new (uninitialized_location) holder_type(holder_type::from_raw_ptr_take_ownership( - value_ptr_w_t, /*void_cast_raw_ptr*/ pointee_depends_on_holder_owner)); + value_ptr_w_t, /*void_cast_raw_ptr*/ inst->is_alias)); } else { new (uninitialized_location) holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t)); } } - v_h.holder().pointee_depends_on_holder_owner - = pointee_depends_on_holder_owner; v_h.set_holder_constructed(); } From 7d85baa6a194e6c5da46252c49f42ec1d87597c7 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 21 Aug 2024 13:16:49 -0400 Subject: [PATCH 19/26] fix: never use `..` in a header include (#5321) * fix: never use `..` in a header include Signed-off-by: Henry Schreiner * fix: one more parent include Signed-off-by: Henry Schreiner --------- Signed-off-by: Henry Schreiner --- include/pybind11/detail/class.h | 4 ++-- include/pybind11/detail/internals.h | 4 ++-- include/pybind11/detail/type_caster_base.h | 3 ++- include/pybind11/eigen/matrix.h | 3 ++- include/pybind11/eigen/tensor.h | 3 ++- include/pybind11/stl/filesystem.h | 10 +++++----- 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 485f7ac63d..454c186b9a 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -9,8 +9,8 @@ #pragma once -#include "../attr.h" -#include "../options.h" +#include +#include PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 329d89b93f..17e6df8244 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -12,10 +12,10 @@ #include "common.h" #if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) -# include "../gil.h" +# include #endif -#include "../pytypes.h" +#include #include #include diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 481b7c783c..bc42252f9d 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -9,7 +9,8 @@ #pragma once -#include "../pytypes.h" +#include + #include "common.h" #include "descr.h" #include "internals.h" diff --git a/include/pybind11/eigen/matrix.h b/include/pybind11/eigen/matrix.h index 8d4342f81b..5cf1f0a2a0 100644 --- a/include/pybind11/eigen/matrix.h +++ b/include/pybind11/eigen/matrix.h @@ -9,7 +9,8 @@ #pragma once -#include "../numpy.h" +#include + #include "common.h" /* HINT: To suppress warnings originating from the Eigen headers, use -isystem. diff --git a/include/pybind11/eigen/tensor.h b/include/pybind11/eigen/tensor.h index 14bb91c40f..0a9d7c2522 100644 --- a/include/pybind11/eigen/tensor.h +++ b/include/pybind11/eigen/tensor.h @@ -7,7 +7,8 @@ #pragma once -#include "../numpy.h" +#include + #include "common.h" #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) diff --git a/include/pybind11/stl/filesystem.h b/include/pybind11/stl/filesystem.h index 935d794815..c16a9ae5c2 100644 --- a/include/pybind11/stl/filesystem.h +++ b/include/pybind11/stl/filesystem.h @@ -4,11 +4,11 @@ #pragma once -#include "../pybind11.h" -#include "../detail/common.h" -#include "../detail/descr.h" -#include "../cast.h" -#include "../pytypes.h" +#include +#include +#include +#include +#include #include From 2baf9d683328aa3f91c53139cc61d977a0c2454a Mon Sep 17 00:00:00 2001 From: ObeliskGate <96614155+ObeliskGate@users.noreply.github.com> Date: Thu, 22 Aug 2024 03:33:06 +0800 Subject: [PATCH 20/26] fix: `` support for `py::tuple` and `py::list` (#5314) * feat: add `` support for `py::tuple` and `py::list` * fix: format the code * fix: disable `ranges` in clang < 16 * refactor: move `` test macro to `test_pytypes.h` * refactor: seperate `ranges` test into 3 funcs * style: compress the if statement * style: pre-commit fixes * style: better formatting --------- Co-authored-by: Henry Schreiner Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- include/pybind11/pytypes.h | 2 ++ tests/test_pytypes.cpp | 63 ++++++++++++++++++++++++++++++++++++++ tests/test_pytypes.py | 42 +++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index f26c307a87..1e76d7bc13 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -1259,6 +1259,7 @@ class sequence_fast_readonly { using pointer = arrow_proxy; sequence_fast_readonly(handle obj, ssize_t n) : ptr(PySequence_Fast_ITEMS(obj.ptr()) + n) {} + sequence_fast_readonly() = default; // NOLINTNEXTLINE(readability-const-return-type) // PR #3263 reference dereference() const { return *ptr; } @@ -1281,6 +1282,7 @@ class sequence_slow_readwrite { using pointer = arrow_proxy; sequence_slow_readwrite(handle obj, ssize_t index) : obj(obj), index(index) {} + sequence_slow_readwrite() = default; reference dereference() const { return {obj, static_cast(index)}; } void increment() { ++index; } diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index ecb44939aa..19f65ce7eb 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -13,6 +13,14 @@ #include +//__has_include has been part of C++17, no need to check it +#if defined(PYBIND11_CPP20) && __has_include() +# if !defined(PYBIND11_COMPILER_CLANG) || __clang_major__ >= 16 // llvm/llvm-project#52696 +# define PYBIND11_TEST_PYTYPES_HAS_RANGES +# include +# endif +#endif + namespace external { namespace detail { bool check(PyObject *o) { return PyFloat_Check(o) != 0; } @@ -923,4 +931,59 @@ TEST_SUBMODULE(pytypes, m) { #else m.attr("defined_PYBIND11_TYPING_H_HAS_STRING_LITERAL") = false; #endif + +#if defined(PYBIND11_TEST_PYTYPES_HAS_RANGES) + + // test_tuple_ranges + m.def("tuple_iterator_default_initialization", []() { + using TupleIterator = decltype(std::declval().begin()); + static_assert(std::random_access_iterator); + return TupleIterator{} == TupleIterator{}; + }); + + m.def("transform_tuple_plus_one", [](py::tuple &tpl) { + py::list ret{}; + for (auto it : tpl | std::views::transform([](auto &o) { return py::cast(o) + 1; })) { + ret.append(py::int_(it)); + } + return ret; + }); + + // test_list_ranges + m.def("list_iterator_default_initialization", []() { + using ListIterator = decltype(std::declval().begin()); + static_assert(std::random_access_iterator); + return ListIterator{} == ListIterator{}; + }); + + m.def("transform_list_plus_one", [](py::list &lst) { + py::list ret{}; + for (auto it : lst | std::views::transform([](auto &o) { return py::cast(o) + 1; })) { + ret.append(py::int_(it)); + } + return ret; + }); + + // test_dict_ranges + m.def("dict_iterator_default_initialization", []() { + using DictIterator = decltype(std::declval().begin()); + static_assert(std::forward_iterator); + return DictIterator{} == DictIterator{}; + }); + + m.def("transform_dict_plus_one", [](py::dict &dct) { + py::list ret{}; + for (auto it : dct | std::views::transform([](auto &o) { + return std::pair{py::cast(o.first) + 1, + py::cast(o.second) + 1}; + })) { + ret.append(py::make_tuple(py::int_(it.first), py::int_(it.second))); + } + return ret; + }); + + m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = true; +#else + m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false; +#endif } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 218092b434..1c6335f757 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1048,3 +1048,45 @@ def test_typevar(doc): assert doc(m.annotate_listT_to_T) == "annotate_listT_to_T(arg0: list[T]) -> T" assert doc(m.annotate_object_to_T) == "annotate_object_to_T(arg0: object) -> T" + + +@pytest.mark.skipif( + not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, + reason=" not available.", +) +@pytest.mark.parametrize( + ("tested_tuple", "expected"), + [((1,), [2]), ((3, 4), [4, 5]), ((7, 8, 9), [8, 9, 10])], +) +def test_tuple_ranges(tested_tuple, expected): + assert m.tuple_iterator_default_initialization() + assert m.transform_tuple_plus_one(tested_tuple) == expected + + +@pytest.mark.skipif( + not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, + reason=" not available.", +) +@pytest.mark.parametrize( + ("tested_list", "expected"), [([1], [2]), ([3, 4], [4, 5]), ([7, 8, 9], [8, 9, 10])] +) +def test_list_ranges(tested_list, expected): + assert m.list_iterator_default_initialization() + assert m.transform_list_plus_one(tested_list) == expected + + +@pytest.mark.skipif( + not m.defined_PYBIND11_TEST_PYTYPES_HAS_RANGES, + reason=" not available.", +) +@pytest.mark.parametrize( + ("tested_dict", "expected"), + [ + ({1: 2}, [(2, 3)]), + ({3: 4, 5: 6}, [(4, 5), (6, 7)]), + ({7: 8, 9: 10, 11: 12}, [(8, 9), (10, 11), (12, 13)]), + ], +) +def test_dict_ranges(tested_dict, expected): + assert m.dict_iterator_default_initialization() + assert m.transform_dict_plus_one(tested_dict) == expected From 9966ad409d5cecaacb19eddb6c6c8c2d45298863 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 22 Aug 2024 00:27:50 -0400 Subject: [PATCH 21/26] fix: allow -Wpedantic in C++20 mode (#5322) * fix: allow -Wpedantic again Signed-off-by: Henry Schreiner * tests: ignore pedantic warning for PYBIND11_DECLARE_HOLDER_TYPE Signed-off-by: Henry Schreiner * tests: try just turning off pedantic for one file Signed-off-by: Henry Schreiner * tests: only run pedantic in C++20 mode Signed-off-by: Henry Schreiner * Update tests/local_bindings.h --------- Signed-off-by: Henry Schreiner --- docs/advanced/cast/stl.rst | 6 +++--- docs/advanced/smart_ptrs.rst | 6 +++--- include/pybind11/detail/common.h | 3 +++ tests/CMakeLists.txt | 3 +++ tests/local_bindings.h | 16 ++++++++-------- tests/test_callbacks.cpp | 2 +- tests/test_eigen_matrix.cpp | 4 ++-- tests/test_opaque_types.cpp | 2 +- tests/test_sequences_and_iterators.cpp | 4 ++-- tests/test_smart_ptr.cpp | 15 +++++++++------ tests/test_stl.cpp | 2 +- tests/test_tagbased_polymorphic.cpp | 2 +- tests/test_virtual_functions.cpp | 2 +- 13 files changed, 38 insertions(+), 29 deletions(-) diff --git a/docs/advanced/cast/stl.rst b/docs/advanced/cast/stl.rst index 03d49b2950..42b85532d8 100644 --- a/docs/advanced/cast/stl.rst +++ b/docs/advanced/cast/stl.rst @@ -162,7 +162,7 @@ the declaration .. code-block:: cpp - PYBIND11_MAKE_OPAQUE(std::vector); + PYBIND11_MAKE_OPAQUE(std::vector) before any binding code (e.g. invocations to ``class_::def()``, etc.). This macro must be specified at the top level (and outside of any namespaces), since @@ -207,8 +207,8 @@ The following example showcases usage of :file:`pybind11/stl_bind.h`: // Don't forget this #include - PYBIND11_MAKE_OPAQUE(std::vector); - PYBIND11_MAKE_OPAQUE(std::map); + PYBIND11_MAKE_OPAQUE(std::vector) + PYBIND11_MAKE_OPAQUE(std::map) // ... diff --git a/docs/advanced/smart_ptrs.rst b/docs/advanced/smart_ptrs.rst index 3c40ce1237..b9f100cf8e 100644 --- a/docs/advanced/smart_ptrs.rst +++ b/docs/advanced/smart_ptrs.rst @@ -124,7 +124,7 @@ top namespace level before any binding code: .. code-block:: cpp - PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr); + PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr) The first argument of :func:`PYBIND11_DECLARE_HOLDER_TYPE` should be a placeholder name that is used as a template parameter of the second argument. @@ -136,7 +136,7 @@ by default. Specify .. code-block:: cpp - PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr, true); + PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr, true) if ``SmartPtr`` can always be initialized from a ``T*`` pointer without the risk of inconsistencies (such as multiple independent ``SmartPtr`` instances @@ -154,7 +154,7 @@ specialized: .. code-block:: cpp // Always needed for custom holder types - PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr); + PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr) // Only needed if the type's `.get()` goes by another name namespace PYBIND11_NAMESPACE { namespace detail { diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 643fd33d7e..2a39e88f37 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -479,6 +479,8 @@ PYBIND11_WARNING_POP } \endrst */ +PYBIND11_WARNING_PUSH +PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments") #define PYBIND11_MODULE(name, variable, ...) \ static ::pybind11::module_::module_def PYBIND11_CONCAT(pybind11_module_def_, name) \ PYBIND11_MAYBE_UNUSED; \ @@ -499,6 +501,7 @@ PYBIND11_WARNING_POP PYBIND11_CATCH_INIT_EXCEPTIONS \ } \ void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ & (variable)) +PYBIND11_WARNING_POP PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 18308731ab..cd94ef3e53 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -382,6 +382,9 @@ function(pybind11_enable_warnings target_name) -Wdeprecated -Wundef -Wnon-virtual-dtor) + if(DEFINED CMAKE_CXX_STANDARD AND NOT CMAKE_CXX_STANDARD VERSION_LESS 20) + target_compile_options(${target_name} PRIVATE -Wpedantic) + endif() endif() if(PYBIND11_WERROR) diff --git a/tests/local_bindings.h b/tests/local_bindings.h index 01d2785353..dea1813108 100644 --- a/tests/local_bindings.h +++ b/tests/local_bindings.h @@ -56,13 +56,13 @@ class LocalSimpleException : public std::exception { std::string message = ""; }; -PYBIND11_MAKE_OPAQUE(LocalVec); -PYBIND11_MAKE_OPAQUE(LocalVec2); -PYBIND11_MAKE_OPAQUE(LocalMap); -PYBIND11_MAKE_OPAQUE(NonLocalVec); -// PYBIND11_MAKE_OPAQUE(NonLocalVec2); // same type as LocalVec2 -PYBIND11_MAKE_OPAQUE(NonLocalMap); -PYBIND11_MAKE_OPAQUE(NonLocalMap2); +PYBIND11_MAKE_OPAQUE(LocalVec) +PYBIND11_MAKE_OPAQUE(LocalVec2) +PYBIND11_MAKE_OPAQUE(LocalMap) +PYBIND11_MAKE_OPAQUE(NonLocalVec) +// PYBIND11_MAKE_OPAQUE(NonLocalVec2) // same type as LocalVec2 +PYBIND11_MAKE_OPAQUE(NonLocalMap) +PYBIND11_MAKE_OPAQUE(NonLocalMap2) // Simple bindings (used with the above): template @@ -70,7 +70,7 @@ py::class_ bind_local(Args &&...args) { return py::class_(std::forward(args)...).def(py::init()).def("get", [](T &i) { return i.i + Adjust; }); -}; +} // Simulate a foreign library base class (to match the example in the docs): namespace pets { diff --git a/tests/test_callbacks.cpp b/tests/test_callbacks.cpp index 2fd05dec72..e303e76567 100644 --- a/tests/test_callbacks.cpp +++ b/tests/test_callbacks.cpp @@ -148,7 +148,7 @@ TEST_SUBMODULE(callbacks, m) { m.def("dummy_function2", [](int i, int j) { return i + j; }); m.def( "roundtrip", - [](std::function f, bool expect_none = false) { + [](std::function f, bool expect_none) { if (expect_none && f) { throw std::runtime_error("Expected None to be converted to empty std::function"); } diff --git a/tests/test_eigen_matrix.cpp b/tests/test_eigen_matrix.cpp index 21261bfc2f..cb8e8c6259 100644 --- a/tests/test_eigen_matrix.cpp +++ b/tests/test_eigen_matrix.cpp @@ -55,7 +55,7 @@ void reset_refs() { } // Returns element 2,1 from a matrix (used to test copy/nocopy) -double get_elem(const Eigen::Ref &m) { return m(2, 1); }; +double get_elem(const Eigen::Ref &m) { return m(2, 1); } // Returns a matrix with 10*r + 100*c added to each matrix element (to help test that the matrix // reference is referencing rows/columns correctly). @@ -76,7 +76,7 @@ struct CustomOperatorNew { Eigen::Matrix4d a = Eigen::Matrix4d::Zero(); Eigen::Matrix4d b = Eigen::Matrix4d::Identity(); - EIGEN_MAKE_ALIGNED_OPERATOR_NEW; + EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; TEST_SUBMODULE(eigen_matrix, m) { diff --git a/tests/test_opaque_types.cpp b/tests/test_opaque_types.cpp index 154d0a8d31..da3866cd00 100644 --- a/tests/test_opaque_types.cpp +++ b/tests/test_opaque_types.cpp @@ -18,7 +18,7 @@ // This also deliberately doesn't use the below StringList type alias to test // that MAKE_OPAQUE can handle a type containing a `,`. (The `std::allocator` // bit is just the default `std::vector` allocator). -PYBIND11_MAKE_OPAQUE(std::vector>); +PYBIND11_MAKE_OPAQUE(std::vector>) using StringList = std::vector>; diff --git a/tests/test_sequences_and_iterators.cpp b/tests/test_sequences_and_iterators.cpp index 4a1d37f4de..c997b73001 100644 --- a/tests/test_sequences_and_iterators.cpp +++ b/tests/test_sequences_and_iterators.cpp @@ -86,8 +86,8 @@ class NonCopyableInt { }; using NonCopyableIntPair = std::pair; -PYBIND11_MAKE_OPAQUE(std::vector); -PYBIND11_MAKE_OPAQUE(std::vector); +PYBIND11_MAKE_OPAQUE(std::vector) +PYBIND11_MAKE_OPAQUE(std::vector) template py::list test_random_access_iterator(PythonType x) { diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index 496073b3c1..4ab43953f1 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -11,6 +11,9 @@ #include "object.h" #include "pybind11_tests.h" +// This breaks on PYBIND11_DECLARE_HOLDER_TYPE +PYBIND11_WARNING_DISABLE_GCC("-Wpedantic") + namespace { // This is just a wrapper around unique_ptr, but with extra fields to deliberately bloat up the @@ -279,13 +282,13 @@ struct holder_helper> { } // namespace PYBIND11_NAMESPACE // Make pybind aware of the ref-counted wrapper type (s): -PYBIND11_DECLARE_HOLDER_TYPE(T, ref, true); +PYBIND11_DECLARE_HOLDER_TYPE(T, ref, true) // The following is not required anymore for std::shared_ptr, but it should compile without error: -PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); -PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr); -PYBIND11_DECLARE_HOLDER_TYPE(T, custom_unique_ptr); -PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_with_addressof_operator); -PYBIND11_DECLARE_HOLDER_TYPE(T, unique_ptr_with_addressof_operator); +PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) +PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr) +PYBIND11_DECLARE_HOLDER_TYPE(T, custom_unique_ptr) +PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_with_addressof_operator) +PYBIND11_DECLARE_HOLDER_TYPE(T, unique_ptr_with_addressof_operator) TEST_SUBMODULE(smart_ptr, m) { // Please do not interleave `struct` and `class` definitions with bindings code, diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index 6240524a09..dd93d51d0a 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -59,7 +59,7 @@ struct visit_helper { } // namespace PYBIND11_NAMESPACE #endif -PYBIND11_MAKE_OPAQUE(std::vector>); +PYBIND11_MAKE_OPAQUE(std::vector>) /// Issue #528: templated constructor struct TplCtorClass { diff --git a/tests/test_tagbased_polymorphic.cpp b/tests/test_tagbased_polymorphic.cpp index 24b49021b4..13e5ed3198 100644 --- a/tests/test_tagbased_polymorphic.cpp +++ b/tests/test_tagbased_polymorphic.cpp @@ -145,4 +145,4 @@ TEST_SUBMODULE(tagbased_polymorphic, m) { .def(py::init()) .def("purr", &Panther::purr); m.def("create_zoo", &create_zoo); -}; +} diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index 93b136ad3c..a6164eb81d 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -589,4 +589,4 @@ void initialize_inherited_virtuals(py::module_ &m) { // Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7) m.def("test_gil", &test_gil); m.def("test_gil_from_thread", &test_gil_from_thread); -}; +} From efa2b20d69cd7daa57b41a559a71117b2c34a369 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Thu, 22 Aug 2024 00:29:21 -0400 Subject: [PATCH 22/26] docs: clarify requirements for including pybind11 (#5326) * DOC: Clarify requirements for including pybind11 Inherited from requirements for including Python.h Closes #4999 * style: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/basics.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/basics.rst b/docs/basics.rst index e9b24c7fa7..cd97c100ec 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -78,6 +78,13 @@ For brevity, all code examples assume that the following two lines are present: namespace py = pybind11; +.. note:: + + ``pybind11/pybind11.h`` includes ``Python.h``, as such it must be the first file + included in any source file or header for `the same reasons as Python.h`_. + +.. _`the same reasons as Python.h`: https://docs.python.org/3/extending/extending.html#a-simple-example + Some features may require additional headers, but those will be specified as needed. .. _simple_example: From c2291e597ffe4fb028ae4ff97c5cd4fc20a8a7f9 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 22 Aug 2024 14:12:17 -0400 Subject: [PATCH 23/26] docs: prepare for 2.13.5 (#5327) * docs: prepare for 2.13.5 Signed-off-by: Henry Schreiner * Update docs/changelog.rst --------- Signed-off-by: Henry Schreiner --- docs/changelog.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 031d315820..0145317743 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -28,6 +28,23 @@ New Features: * Support for CMake older than 3.15 removed. CMake 3.15-3.30 supported. `#5304 `_ +* The ``array_caster`` in pybind11/stl.h was enhanced to support value types that are not default-constructible. + `#5305 `_ + +Version 2.13.5 (August 22, 2024) +-------------------------------- + +Bug fixes: + +* Fix includes when using Windows long paths (``\\?\`` prefix). + `#5321 `_ + +* Support ``-Wpedantic`` in C++20 mode. + `#5322 `_ + +* Fix and test ```` support for ``py::tuple`` and ``py::list``. + `#5314 `_ + Version 2.13.4 (August 14, 2024) -------------------------------- From bf54ecdf9ced78819f1bc4851241c314fe4b8677 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 25 Aug 2024 22:41:16 +0700 Subject: [PATCH 24/26] Split out non-functional changes from https://github.com/pybind/pybind11/pull/5332 (#5333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PREPARATION for: PR #5332 — Fix handling of const unique_ptr & (do not disown). Splitting out so that the functional changes under PR #5332 will be more obvious. --- include/pybind11/cast.h | 16 ++++++++-------- ...test_class_sh_trampoline_shared_from_this.cpp | 6 ++++-- .../test_class_sh_trampoline_shared_from_this.py | 3 ++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 0262296aa9..0cba8a1284 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -871,14 +871,14 @@ struct copyable_holder_caster< pybind11_fail("Passing `std::shared_ptr *` from Python to C++ is not supported " "(inherently unsafe)."); } - return std::addressof(shared_ptr_holder); + return std::addressof(shared_ptr_storage); } explicit operator std::shared_ptr &() { if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { - shared_ptr_holder = sh_load_helper.load_as_shared_ptr(value); + shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value); } - return shared_ptr_holder; + return shared_ptr_storage; } static handle @@ -924,7 +924,7 @@ struct copyable_holder_caster< } if (v_h.holder_constructed()) { value = v_h.value_ptr(); - shared_ptr_holder = v_h.template holder>(); + shared_ptr_storage = v_h.template holder>(); return; } throw cast_error("Unable to cast from non-held to held instance (T& to Holder) " @@ -953,8 +953,8 @@ struct copyable_holder_caster< if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h; } else { - shared_ptr_holder - = std::shared_ptr(sub_caster.shared_ptr_holder, (type *) value); + shared_ptr_storage + = std::shared_ptr(sub_caster.shared_ptr_storage, (type *) value); } return true; } @@ -964,8 +964,8 @@ struct copyable_holder_caster< static bool try_direct_conversions(handle) { return false; } - std::shared_ptr shared_ptr_holder; smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl + std::shared_ptr shared_ptr_storage; }; #endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT @@ -1040,7 +1040,7 @@ struct move_only_holder_caster< policy = return_value_policy::reference_internal; } if (policy != return_value_policy::reference_internal) { - throw cast_error("Invalid return_value_policy for unique_ptr&"); + throw cast_error("Invalid return_value_policy for const unique_ptr&"); } return type_caster_base::cast(src.get(), policy, parent); } diff --git a/tests/test_class_sh_trampoline_shared_from_this.cpp b/tests/test_class_sh_trampoline_shared_from_this.cpp index e5d688af66..f664cac518 100644 --- a/tests/test_class_sh_trampoline_shared_from_this.cpp +++ b/tests/test_class_sh_trampoline_shared_from_this.cpp @@ -87,7 +87,9 @@ long pass_shared_ptr(const std::shared_ptr &obj) { return sft.use_count(); } -void pass_unique_ptr(const std::unique_ptr &) {} +void pass_unique_ptr_cref(const std::unique_ptr &) { + throw std::runtime_error("Expected to not be reached."); +} Sft *make_pure_cpp_sft_raw_ptr(const std::string &history_seed) { return new Sft{history_seed}; } @@ -135,7 +137,7 @@ TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) { m.def("use_count", use_count); m.def("pass_shared_ptr", pass_shared_ptr); - m.def("pass_unique_ptr", pass_unique_ptr); + m.def("pass_unique_ptr_cref", pass_unique_ptr_cref); m.def("make_pure_cpp_sft_raw_ptr", make_pure_cpp_sft_raw_ptr); m.def("make_pure_cpp_sft_unq_ptr", make_pure_cpp_sft_unq_ptr); m.def("make_pure_cpp_sft_shd_ptr", make_pure_cpp_sft_shd_ptr); diff --git a/tests/test_class_sh_trampoline_shared_from_this.py b/tests/test_class_sh_trampoline_shared_from_this.py index a074b20a17..b112bb5d9d 100644 --- a/tests/test_class_sh_trampoline_shared_from_this.py +++ b/tests/test_class_sh_trampoline_shared_from_this.py @@ -138,10 +138,11 @@ def test_pass_released_shared_ptr_as_unique_ptr(): stash1 = m.SftSharedPtrStash(1) stash1.Add(obj) # Releases shared_ptr to C++. with pytest.raises(ValueError) as exc_info: - m.pass_unique_ptr(obj) + m.pass_unique_ptr_cref(obj) assert str(exc_info.value) == ( "Python instance is currently owned by a std::shared_ptr." ) + assert obj.history == "PySft_Stash1Add" @pytest.mark.parametrize( From 0e494631694ea5d8f73541453889ecb96c82b792 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 25 Aug 2024 22:56:22 +0700 Subject: [PATCH 25/26] Split out (almost) pure refactoring from https://github.com/pybind/pybind11/pull/5332 (#5334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PREPARATION for: PR #5332 — Fix handling of const unique_ptr & (do not disown). Splitting out so that the functional changes under PR #5332 will be more obvious. The only functional change under this PR is that ``` assert(custom_deleter_ptr != nullptr); ``` is replaced with: ``` if (custom_deleter_ptr == nullptr) { throw std::runtime_error( std::string("smart_holder::extract_deleter() precondition failure (") + context + ")."); } ``` --- include/pybind11/detail/struct_smart_holder.h | 16 ++++++++++++++++ include/pybind11/detail/type_caster_base.h | 17 +---------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/include/pybind11/detail/struct_smart_holder.h b/include/pybind11/detail/struct_smart_holder.h index ccd289716b..401a6fd1b2 100644 --- a/include/pybind11/detail/struct_smart_holder.h +++ b/include/pybind11/detail/struct_smart_holder.h @@ -231,6 +231,22 @@ struct smart_holder { vptr_del_ptr->armed_flag = armed_flag; } + // Caller is responsible for precondition: ensure_compatible_rtti_uqp_del() must succeed. + template + std::unique_ptr extract_deleter(const char *context) const { + auto *gd = std::get_deleter(vptr); + if (gd && gd->use_del_fun) { + const auto &custom_deleter_ptr = gd->del_fun.template target>(); + if (custom_deleter_ptr == nullptr) { + throw std::runtime_error( + std::string("smart_holder::extract_deleter() precondition failure (") + context + + ")."); + } + return std::unique_ptr(new D(std::move(custom_deleter_ptr->deleter))); + } + return nullptr; + } + static smart_holder from_raw_ptr_unowned(void *raw_ptr) { smart_holder hld; hld.vptr.reset(raw_ptr, [](void *) {}); diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 9edfe20228..8fb0b9e3c8 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -794,22 +794,7 @@ struct load_helper : value_and_holder_helper { "instance cannot safely be transferred to C++."); } - // Temporary variable to store the extracted deleter in. - std::unique_ptr extracted_deleter; - - auto *gd = std::get_deleter(holder().vptr); - if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del() call above. - // In struct_smart_holder, a custom deleter is always stored in a guarded delete. - // The guarded delete's std::function actually points at the - // custom_deleter type, so we can verify it is of the custom deleter type and - // finally extract its deleter. - using custom_deleter_D = pybindit::memory::custom_deleter; - const auto &custom_deleter_ptr = gd->del_fun.template target(); - assert(custom_deleter_ptr != nullptr); - // Now that we have confirmed the type of the deleter matches the desired return - // value we can extract the function. - extracted_deleter = std::unique_ptr(new D(std::move(custom_deleter_ptr->deleter))); - } + std::unique_ptr extracted_deleter = holder().template extract_deleter(context); // Critical transfer-of-ownership section. This must stay together. if (self_life_support != nullptr) { From 04d9f84f2626dc0d2cda3dfac98fa3befc980468 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Mon, 26 Aug 2024 00:57:48 +0700 Subject: [PATCH 26/26] [smart_holder] Fix handling of `const unique_ptr &` (do not disown). (#5332) * Replace `unique_ptr_cref_roundtrip()` with `pass_unique_ptr_cref()`, `rtrn_unique_ptr_cref()` to make the current behavior obvious. * add in unique_ptr_storage, unique_ptr_storage_deleter * Add shared_ptr_storage (with that disowning fails as expected). * Add load_as_const_unique_ptr() * Restore original struct_smart_holder.h * factor out `smart_holder::extract_deleter()` * Better error message. * Misc cleanup/tidying. * Use `re.match("ctor_arg(_MvCtor)*_MvCtor", ...)` for compatibility with MSVC, NVHPC, ICC * Add small comments. * Fix small, inconsequential oversight in test code. * Apply suggestion by @iwanders under PR #5334 * Remove `std::move()` in `smart_holder::extract_deleter()` * Add `static_assert()` following a suggestion by @iwanders under PR #5334 --- include/pybind11/cast.h | 34 +++++++++++++++++-- include/pybind11/detail/struct_smart_holder.h | 6 ++-- include/pybind11/detail/type_caster_base.h | 13 +++++++ tests/test_class_sh_basic.cpp | 14 ++++++++ tests/test_class_sh_basic.py | 34 +++++++++++++------ ...t_class_sh_trampoline_shared_from_this.cpp | 6 +++- ...st_class_sh_trampoline_shared_from_this.py | 4 ++- 7 files changed, 94 insertions(+), 17 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 0cba8a1284..8ecc20f25c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1067,8 +1067,14 @@ struct move_only_holder_caster< + clean_type_id(typeinfo->cpptype->name()) + ")"); } - template - using cast_op_type = std::unique_ptr; + template + using cast_op_type + = conditional_t::type, + const std::unique_ptr &>::value + || std::is_same::type, + const std::unique_ptr &>::value, + const std::unique_ptr &, + std::unique_ptr>; explicit operator std::unique_ptr() { if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { @@ -1077,6 +1083,28 @@ struct move_only_holder_caster< pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__)); } + explicit operator const std::unique_ptr &() { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + // Get shared_ptr to ensure that the Python object is not disowned elsewhere. + shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value); + // Build a temporary unique_ptr that is meant to never expire. + unique_ptr_storage = std::shared_ptr>( + new std::unique_ptr{ + sh_load_helper.template load_as_const_unique_ptr( + shared_ptr_storage.get())}, + [](std::unique_ptr *ptr) { + if (!ptr) { + pybind11_fail("FATAL: `const std::unique_ptr &` was disowned " + "(EXPECT UNDEFINED BEHAVIOR)."); + } + (void) ptr->release(); + delete ptr; + }); + return *unique_ptr_storage; + } + pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__)); + } + bool try_implicit_casts(handle src, bool convert) { for (auto &cast : typeinfo->implicit_casts) { move_only_holder_caster sub_caster(*cast.first); @@ -1097,6 +1125,8 @@ struct move_only_holder_caster< static bool try_direct_conversions(handle) { return false; } smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl + std::shared_ptr shared_ptr_storage; // Serves as a pseudo lock. + std::shared_ptr> unique_ptr_storage; }; #endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT diff --git a/include/pybind11/detail/struct_smart_holder.h b/include/pybind11/detail/struct_smart_holder.h index 401a6fd1b2..b1e24d7bb3 100644 --- a/include/pybind11/detail/struct_smart_holder.h +++ b/include/pybind11/detail/struct_smart_holder.h @@ -234,7 +234,7 @@ struct smart_holder { // Caller is responsible for precondition: ensure_compatible_rtti_uqp_del() must succeed. template std::unique_ptr extract_deleter(const char *context) const { - auto *gd = std::get_deleter(vptr); + const auto *gd = std::get_deleter(vptr); if (gd && gd->use_del_fun) { const auto &custom_deleter_ptr = gd->del_fun.template target>(); if (custom_deleter_ptr == nullptr) { @@ -242,7 +242,9 @@ struct smart_holder { std::string("smart_holder::extract_deleter() precondition failure (") + context + ")."); } - return std::unique_ptr(new D(std::move(custom_deleter_ptr->deleter))); + static_assert(std::is_copy_constructible::value, + "Required for compatibility with smart_holder functionality."); + return std::unique_ptr(new D(custom_deleter_ptr->deleter)); } return nullptr; } diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 8fb0b9e3c8..f1a5d0d8dc 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -814,6 +814,19 @@ struct load_helper : value_and_holder_helper { return result; } + + // This assumes load_as_shared_ptr succeeded(), and the returned shared_ptr is still alive. + // The returned unique_ptr is meant to never expire (the behavior is undefined otherwise). + template + std::unique_ptr + load_as_const_unique_ptr(T *raw_type_ptr, const char *context = "load_as_const_unique_ptr") { + if (!have_holder()) { + return unique_with_deleter(nullptr, std::unique_ptr()); + } + holder().template ensure_compatible_rtti_uqp_del(context); + return unique_with_deleter( + raw_type_ptr, std::move(holder().template extract_deleter(context))); + } }; PYBIND11_NAMESPACE_END(smart_holder_type_caster_support) diff --git a/tests/test_class_sh_basic.cpp b/tests/test_class_sh_basic.cpp index 9602387b34..ac19a750bf 100644 --- a/tests/test_class_sh_basic.cpp +++ b/tests/test_class_sh_basic.cpp @@ -120,6 +120,17 @@ std::string get_mtxt(atyp const &obj) { return obj.mtxt; } std::ptrdiff_t get_ptr(atyp const &obj) { return reinterpret_cast(&obj); } std::unique_ptr unique_ptr_roundtrip(std::unique_ptr obj) { return obj; } + +std::string pass_unique_ptr_cref(const std::unique_ptr &obj) { return obj->mtxt; } + +const std::unique_ptr &rtrn_unique_ptr_cref(const std::string &mtxt) { + static std::unique_ptr obj{new atyp{"static_ctor_arg"}}; + if (!mtxt.empty()) { + obj->mtxt = mtxt; + } + return obj; +} + const std::unique_ptr &unique_ptr_cref_roundtrip(const std::unique_ptr &obj) { return obj; } @@ -217,6 +228,9 @@ TEST_SUBMODULE(class_sh_basic, m) { m.def("get_ptr", get_ptr); // pass_cref m.def("unique_ptr_roundtrip", unique_ptr_roundtrip); // pass_uqmp, rtrn_uqmp + + m.def("pass_unique_ptr_cref", pass_unique_ptr_cref); + m.def("rtrn_unique_ptr_cref", rtrn_unique_ptr_cref); m.def("unique_ptr_cref_roundtrip", unique_ptr_cref_roundtrip); py::classh(m, "SharedPtrStash") diff --git a/tests/test_class_sh_basic.py b/tests/test_class_sh_basic.py index 87f1f8f09f..7db7d31b7a 100644 --- a/tests/test_class_sh_basic.py +++ b/tests/test_class_sh_basic.py @@ -151,19 +151,31 @@ def test_unique_ptr_roundtrip(num_round_trips=1000): id_orig = id_rtrn -# This currently fails, because a unique_ptr is always loaded by value -# due to pybind11/detail/smart_holder_type_casters.h:689 -# I think, we need to provide more cast operators. -@pytest.mark.skip() -def test_unique_ptr_cref_roundtrip(): +def test_pass_unique_ptr_cref(): + obj = m.atyp("ctor_arg") + assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj)) + assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.pass_unique_ptr_cref(obj)) + assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj)) + + +def test_rtrn_unique_ptr_cref(): + obj0 = m.rtrn_unique_ptr_cref("") + assert m.get_mtxt(obj0) == "static_ctor_arg" + obj1 = m.rtrn_unique_ptr_cref("passed_mtxt_1") + assert m.get_mtxt(obj1) == "passed_mtxt_1" + assert m.get_mtxt(obj0) == "passed_mtxt_1" + assert obj0 is obj1 + + +def test_unique_ptr_cref_roundtrip(num_round_trips=1000): + # Multiple roundtrips to stress-test implementation. orig = m.atyp("passenger") - id_orig = id(orig) mtxt_orig = m.get_mtxt(orig) - - recycled = m.unique_ptr_cref_roundtrip(orig) - assert m.get_mtxt(orig) == mtxt_orig - assert m.get_mtxt(recycled) == mtxt_orig - assert id(recycled) == id_orig + recycled = orig + for _ in range(num_round_trips): + recycled = m.unique_ptr_cref_roundtrip(recycled) + assert recycled is orig + assert m.get_mtxt(recycled) == mtxt_orig @pytest.mark.parametrize( diff --git a/tests/test_class_sh_trampoline_shared_from_this.cpp b/tests/test_class_sh_trampoline_shared_from_this.cpp index f664cac518..9c2e4ec762 100644 --- a/tests/test_class_sh_trampoline_shared_from_this.cpp +++ b/tests/test_class_sh_trampoline_shared_from_this.cpp @@ -87,7 +87,10 @@ long pass_shared_ptr(const std::shared_ptr &obj) { return sft.use_count(); } -void pass_unique_ptr_cref(const std::unique_ptr &) { +std::string pass_unique_ptr_cref(const std::unique_ptr &obj) { + return obj ? obj->history : ""; +} +void pass_unique_ptr_rref(std::unique_ptr &&) { throw std::runtime_error("Expected to not be reached."); } @@ -138,6 +141,7 @@ TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) { m.def("use_count", use_count); m.def("pass_shared_ptr", pass_shared_ptr); m.def("pass_unique_ptr_cref", pass_unique_ptr_cref); + m.def("pass_unique_ptr_rref", pass_unique_ptr_rref); m.def("make_pure_cpp_sft_raw_ptr", make_pure_cpp_sft_raw_ptr); m.def("make_pure_cpp_sft_unq_ptr", make_pure_cpp_sft_unq_ptr); m.def("make_pure_cpp_sft_shd_ptr", make_pure_cpp_sft_shd_ptr); diff --git a/tests/test_class_sh_trampoline_shared_from_this.py b/tests/test_class_sh_trampoline_shared_from_this.py index b112bb5d9d..7c5ee0e8ef 100644 --- a/tests/test_class_sh_trampoline_shared_from_this.py +++ b/tests/test_class_sh_trampoline_shared_from_this.py @@ -137,8 +137,10 @@ def test_pass_released_shared_ptr_as_unique_ptr(): obj = PySft("PySft") stash1 = m.SftSharedPtrStash(1) stash1.Add(obj) # Releases shared_ptr to C++. + assert m.pass_unique_ptr_cref(obj) == "PySft_Stash1Add" + assert obj.history == "PySft_Stash1Add" with pytest.raises(ValueError) as exc_info: - m.pass_unique_ptr_cref(obj) + m.pass_unique_ptr_rref(obj) assert str(exc_info.value) == ( "Python instance is currently owned by a std::shared_ptr." )