From 3e448c0b5e3abcd179781dd718df2bd2340ddb06 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Tue, 4 Aug 2020 14:45:55 +0200 Subject: [PATCH 001/295] Enable py::ellipsis on Python 2 (#2360) * Enable py::ellipsis on Python 2 * Enable py::ellipsis tests on Python 2 and mention `Ellipsis` in the docs --- docs/advanced/pycpp/numpy.rst | 2 ++ include/pybind11/pytypes.h | 4 ---- tests/test_numpy_array.cpp | 8 +++----- tests/test_numpy_array.py | 1 - 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/advanced/pycpp/numpy.rst b/docs/advanced/pycpp/numpy.rst index f1941392fa..3b6a5dfc7d 100644 --- a/docs/advanced/pycpp/numpy.rst +++ b/docs/advanced/pycpp/numpy.rst @@ -371,6 +371,8 @@ Ellipsis Python 3 provides a convenient ``...`` ellipsis notation that is often used to slice multidimensional arrays. For instance, the following snippet extracts the middle dimensions of a tensor with the first and last index set to zero. +In Python 2, the syntactic sugar ``...`` is not available, but the singleton +``Ellipsis`` (of type ``ellipsis``) can still be used directly. .. code-block:: python diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 536835f7da..9000668b91 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -736,9 +736,7 @@ inline bool PyIterable_Check(PyObject *obj) { } inline bool PyNone_Check(PyObject *o) { return o == Py_None; } -#if PY_MAJOR_VERSION >= 3 inline bool PyEllipsis_Check(PyObject *o) { return o == Py_Ellipsis; } -#endif inline bool PyUnicode_Check_Permissive(PyObject *o) { return PyUnicode_Check(o) || PYBIND11_BYTES_CHECK(o); } @@ -1020,13 +1018,11 @@ class none : public object { none() : object(Py_None, borrowed_t{}) { } }; -#if PY_MAJOR_VERSION >= 3 class ellipsis : public object { public: PYBIND11_OBJECT(ellipsis, object, detail::PyEllipsis_Check) ellipsis() : object(Py_Ellipsis, borrowed_t{}) { } }; -#endif class bool_ : public object { public: diff --git a/tests/test_numpy_array.cpp b/tests/test_numpy_array.cpp index 156a3bfa8e..e37beb5a5c 100644 --- a/tests/test_numpy_array.cpp +++ b/tests/test_numpy_array.cpp @@ -382,9 +382,7 @@ TEST_SUBMODULE(numpy_array, sm) { return a; }); -#if PY_MAJOR_VERSION >= 3 - sm.def("index_using_ellipsis", [](py::array a) { - return a[py::make_tuple(0, py::ellipsis(), 0)]; - }); -#endif + sm.def("index_using_ellipsis", [](py::array a) { + return a[py::make_tuple(0, py::ellipsis(), 0)]; + }); } diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 2c977cd62e..1b6599dfe4 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -431,7 +431,6 @@ def test_array_create_and_resize(msg): assert(np.all(a == 42.)) -@pytest.unsupported_on_py2 def test_index_using_ellipsis(): a = m.index_using_ellipsis(np.zeros((5, 6, 7))) assert a.shape == (6,) From df115977df9de70710d1d445253c99f47f1a58bd Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 31 Jul 2020 22:45:19 -0400 Subject: [PATCH 002/295] chore: cleanup --- .github/workflows/ci.yml | 11 +- CMakeLists.txt | 22 +-- tests/CMakeLists.txt | 16 +- tests/test_cmake_build/CMakeLists.txt | 2 +- .../installed_embed/CMakeLists.txt | 10 ++ .../installed_function/CMakeLists.txt | 13 +- .../installed_target/CMakeLists.txt | 10 ++ .../subdirectory_embed/CMakeLists.txt | 10 ++ .../subdirectory_function/CMakeLists.txt | 10 ++ .../subdirectory_target/CMakeLists.txt | 10 ++ tools/FindPythonLibsNew.cmake | 1 + tools/pybind11Config.cmake.in | 152 +++++++++--------- tools/pybind11Tools.cmake | 25 ++- 13 files changed, 183 insertions(+), 109 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc9d04c924..044312b53e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -286,7 +286,10 @@ jobs: apt-get install -y git make cmake g++ libeigen3-dev python3-dev python3-pip python3-pytest - name: Configure for install - run: cmake -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") -DPYBIND11_INSTALL=1 -DPYBIND11_TEST=0 . + run: > + cmake . + -DPYBIND11_INSTALL=1 -DPYBIND11_TEST=0 + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Make and install run: make install @@ -298,7 +301,11 @@ jobs: run: mkdir /build-tests - name: Configure tests - run: cmake -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") ../pybind11-tests -DPYBIND11_WERROR=ON + run: > + cmake ../pybind11-tests + -DDOWNLOAD_CATCH=ON + -DPYBIND11_WERROR=ON + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") working-directory: /build-tests - name: Run tests diff --git a/CMakeLists.txt b/CMakeLists.txt index 7aa8ed72c9..3571e33e62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,9 @@ cmake_minimum_required(VERSION 3.7) -# VERSION 3.7...3.18, but some versions of MCVS have a patched CMake 3.11 -# that do not work properly with this syntax, so using the following workaround: +# The `cmake_minimum_required(VERSION 3.7...3.18)` 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.18) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() @@ -26,7 +27,7 @@ foreach(ver ${pybind11_version_defines}) endforeach() if(PYBIND11_VERSION_PATCH MATCHES [[([a-zA-Z]+)]]) - set(PYBIND11_VERSION_TYPE "${CMAKE_MATCH_1}") + set(pybind11_VERSION_TYPE "${CMAKE_MATCH_1}") endif() string(REGEX MATCH "[0-9]+" PYBIND11_VERSION_PATCH "${PYBIND11_VERSION_PATCH}") @@ -40,7 +41,7 @@ include(GNUInstallDirs) include(CMakePackageConfigHelpers) include(CMakeDependentOption) -message(STATUS "pybind11 v${pybind11_VERSION} ${PYBIND11_VERSION_TYPE}") +message(STATUS "pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}") # Check if pybind11 is being used directly or via add_subdirectory if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) @@ -56,9 +57,11 @@ else() set(pybind11_system SYSTEM) endif() +# Options option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_CLASSIC_LTO "Use the classic LTO flag algorithm, even on CMake 3.9+" OFF) + cmake_dependent_option( USE_PYTHON_INCLUDE_DIR "Install pybind11 headers in Python include directory instead of default installation prefix" @@ -117,8 +120,7 @@ string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/" PYBIND11_HEADER # Classic mode -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools") -include(pybind11Tools) +include("${CMAKE_CURRENT_LIST_DIR}/tools/pybind11Tools.cmake") # Cache variables so pybind11_add_module can be used in parent projects set(PYBIND11_INCLUDE_DIR @@ -146,10 +148,6 @@ set(PYTHON_IS_DEBUG "${PYTHON_IS_DEBUG}" CACHE INTERNAL "") -if(PYBIND11_TEST OR (BUILD_TESTING AND PYBIND11_MASTER_PROJECT)) - add_subdirectory(tests) -endif() - if(USE_PYTHON_INCLUDE_DIR) file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${PYTHON_INCLUDE_DIRS}) endif() @@ -273,3 +271,7 @@ if(PYBIND11_INSTALL) ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) endif() endif() + +if(PYBIND11_TEST OR (BUILD_TESTING AND PYBIND11_MASTER_PROJECT)) + add_subdirectory(tests) +endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5dea63acd1..f19fa28c19 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,8 +7,9 @@ cmake_minimum_required(VERSION 3.7) -# VERSION 3.7...3.18, but some versions of VS have a patched CMake 3.11 -# that do not work properly with this syntax, so using the following workaround: +# The `cmake_minimum_required(VERSION 3.7...3.18)` 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.18) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() @@ -18,6 +19,9 @@ endif() # There's no harm in including a project in a project project(pybind11_tests CXX) +# Access FindCatch and more +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) set(PYBIND11_TEST_OVERRIDE @@ -89,12 +93,8 @@ endif() # Skip test_async for Python < 3.5 list(FIND PYBIND11_TEST_FILES test_async.cpp PYBIND11_TEST_FILES_ASYNC_I) -if((PYBIND11_TEST_FILES_ASYNC_I GREATER -1) AND ("${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" - VERSION_LESS 3.5)) - message( - STATUS - "Skipping test_async because Python version ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} < 3.5" - ) +if((PYBIND11_TEST_FILES_ASYNC_I GREATER -1) AND (PYTHON_VERSION VERSION_LESS 3.5)) + message(STATUS "Skipping test_async because Python version ${PYTHON_VERSION} < 3.5") list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_ASYNC_I}) endif() diff --git a/tests/test_cmake_build/CMakeLists.txt b/tests/test_cmake_build/CMakeLists.txt index 53228f0eb4..07db30023a 100644 --- a/tests/test_cmake_build/CMakeLists.txt +++ b/tests/test_cmake_build/CMakeLists.txt @@ -8,7 +8,7 @@ function(pybind11_add_build_test name) "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE}") - if(CMAKE_CXX_STANDARD) + if(DEFINED CMAKE_CXX_STANDARD) list(APPEND build_options "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}") endif() diff --git a/tests/test_cmake_build/installed_embed/CMakeLists.txt b/tests/test_cmake_build/installed_embed/CMakeLists.txt index 78855afa26..509bce201d 100644 --- a/tests/test_cmake_build/installed_embed/CMakeLists.txt +++ b/tests/test_cmake_build/installed_embed/CMakeLists.txt @@ -1,4 +1,14 @@ cmake_minimum_required(VERSION 3.7) + +# The `cmake_minimum_required(VERSION 3.7...3.18)` 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.18) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.18) +endif() + project(test_installed_embed CXX) set(CMAKE_MODULE_PATH "") diff --git a/tests/test_cmake_build/installed_function/CMakeLists.txt b/tests/test_cmake_build/installed_function/CMakeLists.txt index 3ad5445e3f..f55389549b 100644 --- a/tests/test_cmake_build/installed_function/CMakeLists.txt +++ b/tests/test_cmake_build/installed_function/CMakeLists.txt @@ -1,10 +1,21 @@ cmake_minimum_required(VERSION 3.7) + +# The `cmake_minimum_required(VERSION 3.7...3.18)` 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.18) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.18) +endif() + project(test_installed_module CXX) set(CMAKE_MODULE_PATH "") find_package(pybind11 CONFIG REQUIRED) -message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}") +message( + STATUS "Found pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}: ${pybind11_INCLUDE_DIRS}") pybind11_add_module(test_cmake_build SHARED NO_EXTRAS ../main.cpp) diff --git a/tests/test_cmake_build/installed_target/CMakeLists.txt b/tests/test_cmake_build/installed_target/CMakeLists.txt index 348c419cd3..9392c9da6c 100644 --- a/tests/test_cmake_build/installed_target/CMakeLists.txt +++ b/tests/test_cmake_build/installed_target/CMakeLists.txt @@ -1,4 +1,14 @@ cmake_minimum_required(VERSION 3.7) + +# The `cmake_minimum_required(VERSION 3.7...3.18)` 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.18) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.18) +endif() + project(test_installed_target CXX) set(CMAKE_MODULE_PATH "") diff --git a/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt b/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt index eea0eeea3d..0afe4a3013 100644 --- a/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt @@ -1,4 +1,14 @@ cmake_minimum_required(VERSION 3.7) + +# The `cmake_minimum_required(VERSION 3.7...3.18)` 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.18) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.18) +endif() + project(test_subdirectory_embed CXX) set(PYBIND11_INSTALL diff --git a/tests/test_cmake_build/subdirectory_function/CMakeLists.txt b/tests/test_cmake_build/subdirectory_function/CMakeLists.txt index e4518044ed..366a82ba58 100644 --- a/tests/test_cmake_build/subdirectory_function/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_function/CMakeLists.txt @@ -1,4 +1,14 @@ cmake_minimum_required(VERSION 3.7) + +# The `cmake_minimum_required(VERSION 3.7...3.18)` 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.18) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.18) +endif() + project(test_subdirectory_module CXX) add_subdirectory(${PYBIND11_PROJECT_DIR} pybind11) diff --git a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt index f84140ce04..91a12e3ee4 100644 --- a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt @@ -1,4 +1,14 @@ cmake_minimum_required(VERSION 3.7) + +# The `cmake_minimum_required(VERSION 3.7...3.18)` 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.18) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.18) +endif() + project(test_subdirectory_target CXX) add_subdirectory(${PYBIND11_PROJECT_DIR} pybind11) diff --git a/tools/FindPythonLibsNew.cmake b/tools/FindPythonLibsNew.cmake index cf2a13f67b..cc56351a9a 100644 --- a/tools/FindPythonLibsNew.cmake +++ b/tools/FindPythonLibsNew.cmake @@ -140,6 +140,7 @@ string(REGEX REPLACE "\\." ";" _PYTHON_VERSION_LIST ${_PYTHON_VERSION_LIST}) list(GET _PYTHON_VERSION_LIST 0 PYTHON_VERSION_MAJOR) list(GET _PYTHON_VERSION_LIST 1 PYTHON_VERSION_MINOR) list(GET _PYTHON_VERSION_LIST 2 PYTHON_VERSION_PATCH) +set(PYTHON_VERSION "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}.${PYTHON_VERSION_PATCH}") # Make sure all directory separators are '/' string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX "${PYTHON_PREFIX}") diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index bcc74d6bfb..a8b3cb1f10 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -1,62 +1,65 @@ -# pybind11Config.cmake -# -------------------- -# -# PYBIND11 cmake module. -# This module sets the following variables in your project:: -# -# pybind11_FOUND - true if pybind11 and all required components found on the system -# pybind11_VERSION - pybind11 version in format Major.Minor.Release -# pybind11_INCLUDE_DIRS - Directories where pybind11 and python headers are located. -# pybind11_INCLUDE_DIR - Directory where pybind11 headers are located. -# pybind11_DEFINITIONS - Definitions necessary to use pybind11, namely USING_pybind11. -# pybind11_LIBRARIES - compile flags and python libraries (as needed) to link against. -# pybind11_LIBRARY - empty. -# CMAKE_MODULE_PATH - appends location of accompanying FindPythonLibsNew.cmake and -# pybind11Tools.cmake modules. -# -# -# Available components: None -# -# -# Exported targets:: -# -# If pybind11 is found, this module defines the following :prop_tgt:`IMPORTED` -# interface library targets:: -# -# pybind11::module - for extension modules -# pybind11::embed - for embedding the Python interpreter -# -# Python headers, libraries (as needed by platform), and the C++ standard -# are attached to the target. Set PythonLibsNew variables to influence -# python detection and CMAKE_CXX_STANDARD (11 or 14) to influence standard -# setting. :: -# -# find_package(pybind11 CONFIG REQUIRED) -# message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}") -# -# # Create an extension module -# add_library(mylib MODULE main.cpp) -# target_link_libraries(mylib pybind11::module) -# -# # Or embed the Python interpreter into an executable -# add_executable(myexe main.cpp) -# target_link_libraries(myexe pybind11::embed) -# -# Suggested usage:: -# -# find_package with version info is not recommended except for release versions. :: -# -# find_package(pybind11 CONFIG) -# find_package(pybind11 2.0 EXACT CONFIG REQUIRED) -# -# -# The following variables can be set to guide the search for this package:: -# -# pybind11_DIR - CMake variable, set to directory containing this Config file -# CMAKE_PREFIX_PATH - CMake variable, set to root directory of this package -# PATH - environment variable, set to bin directory of this package -# CMAKE_DISABLE_FIND_PACKAGE_pybind11 - CMake variable, disables -# find_package(pybind11) when not REQUIRED, perhaps to force internal build +#[=============================================================================[.rst + +pybind11Config.cmake +-------------------- + +PYBIND11 cmake module. +This module sets the following variables in your project:: + + pybind11_FOUND - true if pybind11 and all required components found on the system + pybind11_VERSION - pybind11 version in format Major.Minor.Release + pybind11_INCLUDE_DIRS - Directories where pybind11 and python headers are located. + pybind11_INCLUDE_DIR - Directory where pybind11 headers are located. + pybind11_DEFINITIONS - Definitions necessary to use pybind11, namely USING_pybind11. + pybind11_LIBRARIES - compile flags and python libraries (as needed) to link against. + pybind11_LIBRARY - empty. + + +Available components: None + + +Exported targets:: + +If pybind11 is found, this module defines the following :prop_tgt:`IMPORTED` +interface library targets:: + + pybind11::module - for extension modules + pybind11::embed - for embedding the Python interpreter + +Python headers, libraries (as needed by platform), and the C++ standard +are attached to the target. +Classic mode:: + +Set PythonLibsNew variables to influence python detection and +CMAKE_CXX_STANDARD to influence standard setting. :: + + find_package(pybind11 CONFIG REQUIRED) + message(STATUS "Found pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}: ${pybind11_INCLUDE_DIRS}") + + # Create an extension module + add_library(mylib MODULE main.cpp) + target_link_libraries(mylib pybind11::module) + + # Or embed the Python interpreter into an executable + add_executable(myexe main.cpp) + target_link_libraries(myexe pybind11::embed) + +Suggested usage:: + +find_package with version info is not recommended except for release versions. :: + + find_package(pybind11 CONFIG) + find_package(pybind11 2.0 EXACT CONFIG REQUIRED) + + +The following variables can be set to guide the search for this package:: + + pybind11_DIR - CMake variable, set to directory containing this Config file + CMAKE_PREFIX_PATH - CMake variable, set to root directory of this package + PATH - environment variable, set to bin directory of this package + CMAKE_DISABLE_FIND_PACKAGE_pybind11 - CMake variable, disables + find_package(pybind11) when not REQUIRED, perhaps to force internal build +#]=============================================================================] @PACKAGE_INIT@ @@ -65,32 +68,35 @@ set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_INCLUDEDIR@") set(pybind11_LIBRARY "") set(pybind11_DEFINITIONS USING_pybind11) +set(pybind11_VERSION_TYPE "@pybind11_VERSION_TYPE@") check_required_components(pybind11) -# Make the FindPythonLibsNew.cmake module available -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) -include(pybind11Tools) +include("${CMAKE_CURRENT_LIST_DIR}/pybind11Tools.cmake") #----------------------------------------------------------------------------- # Don't include targets if this file is being picked up by another # project which has already built this as a subproject #----------------------------------------------------------------------------- if(NOT TARGET pybind11::pybind11) - include("${CMAKE_CURRENT_LIST_DIR}/pybind11Targets.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/pybind11Targets.cmake") + + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED) + list(REMOVE_AT CMAKE_MODULE_PATH -1) - find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED) + set_property(TARGET pybind11::pybind11 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) + set_property(TARGET pybind11::pybind11 APPEND PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) - set_property(TARGET pybind11::pybind11 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) - set_property(TARGET pybind11::pybind11 APPEND PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) + set_property(TARGET pybind11::embed APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${PYTHON_LIBRARIES}) + set_property(TARGET pybind11::module APPEND PROPERTY INTERFACE_LINK_LIBRARIES + "$<$,$>:$>") - set_property(TARGET pybind11::embed APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${PYTHON_LIBRARIES}) - set_property(TARGET pybind11::module APPEND PROPERTY INTERFACE_LINK_LIBRARIES - "$<$,$>:$>") + get_property(_iid TARGET pybind11::pybind11 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + get_property(_ill TARGET pybind11::module PROPERTY INTERFACE_LINK_LIBRARIES) + set(pybind11_INCLUDE_DIRS ${_iid}) + set(pybind11_LIBRARIES ${_ico} ${_ill}) - get_property(_iid TARGET pybind11::pybind11 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - get_property(_ill TARGET pybind11::module PROPERTY INTERFACE_LINK_LIBRARIES) - set(pybind11_INCLUDE_DIRS ${_iid}) - set(pybind11_LIBRARIES ${_ico} ${_ill}) + include("${CMAKE_CURRENT_LIST_DIR}/pybind11Tools.cmake") endif() diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 49876eb9f6..258aaac00c 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -5,16 +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. -cmake_minimum_required(VERSION 3.7) - -# VERSION 3.7...3.18, but some versions of VS have a patched CMake 3.11 -# that do not work properly with this syntax, so using the following workaround: -if(${CMAKE_VERSION} VERSION_LESS 3.18) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.18) -endif() - # Add a CMake parameter for choosing a desired Python version if(NOT PYBIND11_PYTHON_VERSION) set(PYBIND11_PYTHON_VERSION @@ -26,10 +16,12 @@ endif() set(Python_ADDITIONAL_VERSIONS "3.9;3.8;3.7;3.6;3.5;3.4" CACHE INTERNAL "") -find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} REQUIRED) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED) +list(REMOVE_AT CMAKE_MODULE_PATH -1) include(CheckCXXCompilerFlag) -include(CMakeParseArguments) # Warn or error if old variable name used if(PYBIND11_CPP_STANDARD) @@ -131,7 +123,7 @@ endfunction() # function(pybind11_add_module target_name) set(options MODULE SHARED EXCLUDE_FROM_ALL NO_EXTRAS SYSTEM THIN_LTO) - cmake_parse_arguments(ARG "${options}" "" "" ${ARGN}) + cmake_parse_arguments(PARSE_ARGV 2 ARG "${options}" "" "") if(ARG_MODULE AND ARG_SHARED) message(FATAL_ERROR "Can't be both MODULE and SHARED") @@ -185,9 +177,14 @@ function(pybind11_add_module target_name) _pybind11_add_lto_flags(${target_name} ${ARG_THIN_LTO}) else() include(CheckIPOSupported) - check_ipo_supported(RESULT supported OUTPUT error) + check_ipo_supported( + RESULT supported + OUTPUT error + LANGUAGES CXX) if(supported) set_property(TARGET ${target_name} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(WARNING "IPO is not supported: ${output}") endif() endif() From da803eb0a5113cc570a397a2acb1ecbc4d5bad78 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 1 Aug 2020 15:53:11 -0400 Subject: [PATCH 003/295] fix: duplicate target names removed --- tests/test_cmake_build/CMakeLists.txt | 12 +++++++----- .../installed_embed/CMakeLists.txt | 11 ++++++----- .../installed_function/CMakeLists.txt | 11 +++++------ .../installed_target/CMakeLists.txt | 17 ++++++++--------- .../subdirectory_embed/CMakeLists.txt | 8 +++++--- .../subdirectory_function/CMakeLists.txt | 11 ++++++----- .../subdirectory_target/CMakeLists.txt | 13 +++++++------ 7 files changed, 44 insertions(+), 39 deletions(-) diff --git a/tests/test_cmake_build/CMakeLists.txt b/tests/test_cmake_build/CMakeLists.txt index 07db30023a..0be071f2fc 100644 --- a/tests/test_cmake_build/CMakeLists.txt +++ b/tests/test_cmake_build/CMakeLists.txt @@ -1,7 +1,7 @@ add_custom_target(test_cmake_build) function(pybind11_add_build_test name) - cmake_parse_arguments(ARG "INSTALL" "" "" ${ARGN}) + cmake_parse_arguments(PARSE_ARGV 1 ARG "INSTALL" "" "") set(build_options "-DCMAKE_PREFIX_PATH=${pybind11_BINARY_DIR}/mock_install" @@ -14,10 +14,12 @@ function(pybind11_add_build_test name) if(NOT ARG_INSTALL) list(APPEND build_options "-DPYBIND11_PROJECT_DIR=${pybind11_SOURCE_DIR}") + else() + list(APPEND build_options "-DCMAKE_PREFIX_PATH=${pybind11_BINARY_DIR}/mock_install") endif() add_custom_target( - test_${name} + test_build_${name} ${CMAKE_CTEST_COMMAND} --build-and-test "${CMAKE_CURRENT_SOURCE_DIR}/${name}" @@ -32,13 +34,13 @@ function(pybind11_add_build_test name) --build-makeprogram ${CMAKE_MAKE_PROGRAM} --build-target - check + check_${name} --build-options ${build_options}) if(ARG_INSTALL) - add_dependencies(test_${name} mock_install) + add_dependencies(test_build_${name} mock_install) endif() - add_dependencies(test_cmake_build test_${name}) + add_dependencies(test_cmake_build test_build_${name}) endfunction() pybind11_add_build_test(subdirectory_function) diff --git a/tests/test_cmake_build/installed_embed/CMakeLists.txt b/tests/test_cmake_build/installed_embed/CMakeLists.txt index 509bce201d..6b773c7c55 100644 --- a/tests/test_cmake_build/installed_embed/CMakeLists.txt +++ b/tests/test_cmake_build/installed_embed/CMakeLists.txt @@ -11,15 +11,16 @@ endif() project(test_installed_embed CXX) -set(CMAKE_MODULE_PATH "") find_package(pybind11 CONFIG REQUIRED) message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}") -add_executable(test_cmake_build ../embed.cpp) -target_link_libraries(test_cmake_build PRIVATE pybind11::embed) +add_executable(test_installed_embed ../embed.cpp) +target_link_libraries(test_installed_embed PRIVATE pybind11::embed) +set_target_properties(test_installed_embed PROPERTIES OUTPUT_NAME test_cmake_build) # Do not treat includes from IMPORTED target as SYSTEM (Python headers in pybind11::embed). # This may be needed to resolve header conflicts, e.g. between Python release and debug headers. -set_target_properties(test_cmake_build PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) +set_target_properties(test_installed_embed PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) -add_custom_target(check $ ${PROJECT_SOURCE_DIR}/../test.py) +add_custom_target(check_installed_embed $ + ${PROJECT_SOURCE_DIR}/../test.py) diff --git a/tests/test_cmake_build/installed_function/CMakeLists.txt b/tests/test_cmake_build/installed_function/CMakeLists.txt index f55389549b..db8213c983 100644 --- a/tests/test_cmake_build/installed_function/CMakeLists.txt +++ b/tests/test_cmake_build/installed_function/CMakeLists.txt @@ -9,22 +9,21 @@ else() cmake_policy(VERSION 3.18) endif() -project(test_installed_module CXX) - -set(CMAKE_MODULE_PATH "") +project(test_installed_function CXX) find_package(pybind11 CONFIG REQUIRED) message( STATUS "Found pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}: ${pybind11_INCLUDE_DIRS}") -pybind11_add_module(test_cmake_build SHARED NO_EXTRAS ../main.cpp) +pybind11_add_module(test_installed_function SHARED NO_EXTRAS ../main.cpp) +set_target_properties(test_installed_function PROPERTIES OUTPUT_NAME test_cmake_build) add_custom_target( - check + check_installed_function ${CMAKE_COMMAND} -E env - PYTHONPATH=$ + PYTHONPATH=$ ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME}) diff --git a/tests/test_cmake_build/installed_target/CMakeLists.txt b/tests/test_cmake_build/installed_target/CMakeLists.txt index 9392c9da6c..1a124b9c27 100644 --- a/tests/test_cmake_build/installed_target/CMakeLists.txt +++ b/tests/test_cmake_build/installed_target/CMakeLists.txt @@ -11,29 +11,28 @@ endif() project(test_installed_target CXX) -set(CMAKE_MODULE_PATH "") - find_package(pybind11 CONFIG REQUIRED) message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}") -add_library(test_cmake_build MODULE ../main.cpp) +add_library(test_installed_target MODULE ../main.cpp) -target_link_libraries(test_cmake_build PRIVATE pybind11::module) +target_link_libraries(test_installed_target PRIVATE pybind11::module) +set_target_properties(test_installed_target PROPERTIES OUTPUT_NAME test_cmake_build) # make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib -set_target_properties(test_cmake_build PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}") +set_target_properties(test_installed_target PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}") # Do not treat includes from IMPORTED target as SYSTEM (Python headers in pybind11::module). # This may be needed to resolve header conflicts, e.g. between Python release and debug headers. -set_target_properties(test_cmake_build PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) +set_target_properties(test_installed_target PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) add_custom_target( - check + check_installed_target ${CMAKE_COMMAND} -E env - PYTHONPATH=$ + PYTHONPATH=$ ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME}) diff --git a/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt b/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt index 0afe4a3013..4571e87ff6 100644 --- a/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt @@ -19,10 +19,12 @@ set(PYBIND11_EXPORT_NAME test_export) add_subdirectory(${PYBIND11_PROJECT_DIR} pybind11) # Test basic target functionality -add_executable(test_cmake_build ../embed.cpp) -target_link_libraries(test_cmake_build PRIVATE pybind11::embed) +add_executable(test_subdirectory_embed ../embed.cpp) +target_link_libraries(test_subdirectory_embed PRIVATE pybind11::embed) +set_target_properties(test_subdirectory_embed PROPERTIES OUTPUT_NAME test_cmake_build) -add_custom_target(check $ ${PROJECT_SOURCE_DIR}/../test.py) +add_custom_target(check_subdirectory_embed $ + ${PROJECT_SOURCE_DIR}/../test.py) # Test custom export group -- PYBIND11_EXPORT_NAME add_library(test_embed_lib ../embed.cpp) diff --git a/tests/test_cmake_build/subdirectory_function/CMakeLists.txt b/tests/test_cmake_build/subdirectory_function/CMakeLists.txt index 366a82ba58..697f881433 100644 --- a/tests/test_cmake_build/subdirectory_function/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_function/CMakeLists.txt @@ -9,17 +9,18 @@ else() cmake_policy(VERSION 3.18) endif() -project(test_subdirectory_module CXX) +project(test_subdirectory_function CXX) -add_subdirectory(${PYBIND11_PROJECT_DIR} pybind11) -pybind11_add_module(test_cmake_build THIN_LTO ../main.cpp) +add_subdirectory("${PYBIND11_PROJECT_DIR}" pybind11) +pybind11_add_module(test_subdirectory_function THIN_LTO ../main.cpp) +set_target_properties(test_subdirectory_function PROPERTIES OUTPUT_NAME test_cmake_build) add_custom_target( - check + check_subdirectory_function ${CMAKE_COMMAND} -E env - PYTHONPATH=$ + PYTHONPATH=$ ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME}) diff --git a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt index 91a12e3ee4..4f2312ee64 100644 --- a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt @@ -13,20 +13,21 @@ project(test_subdirectory_target CXX) add_subdirectory(${PYBIND11_PROJECT_DIR} pybind11) -add_library(test_cmake_build MODULE ../main.cpp) +add_library(test_subdirectory_target MODULE ../main.cpp) +set_target_properties(test_subdirectory_target PROPERTIES OUTPUT_NAME test_cmake_build) -target_link_libraries(test_cmake_build PRIVATE pybind11::module) +target_link_libraries(test_subdirectory_target PRIVATE pybind11::module) # make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib -set_target_properties(test_cmake_build PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}") +set_target_properties(test_subdirectory_target PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}") add_custom_target( - check + check_subdirectory_target ${CMAKE_COMMAND} -E env - PYTHONPATH=$ + PYTHONPATH=$ ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME}) From ed6de125c92ba1db4ccafc4e9c8a1a1d905a0347 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 1 Aug 2020 23:47:47 -0400 Subject: [PATCH 004/295] format: include .in files --- .pre-commit-config.yaml | 4 +++- tools/FindPythonLibsNew.cmake | 3 ++- tools/cmake_uninstall.cmake.in | 6 +++--- tools/pybind11Config.cmake.in | 39 +++++++++++++++++++++++++--------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7fb3a13b70..a046c6fcfe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,10 +27,12 @@ repos: exclude: ^(docs/.*|tools/.*)$ - repo: https://github.com/cheshirekow/cmake-format-precommit - rev: v0.6.10 + rev: v0.6.11 hooks: - id: cmake-format additional_dependencies: [pyyaml] + types: [file] + files: (\.cmake|CMakeLists.txt)(.in)?$ - repo: local hooks: diff --git a/tools/FindPythonLibsNew.cmake b/tools/FindPythonLibsNew.cmake index cc56351a9a..822ec7718a 100644 --- a/tools/FindPythonLibsNew.cmake +++ b/tools/FindPythonLibsNew.cmake @@ -191,7 +191,8 @@ else() find_library( PYTHON_LIBRARY NAMES "python${PYTHON_LIBRARY_SUFFIX}" - PATHS ${_PYTHON_LIBS_SEARCH} NO_DEFAULT_PATH) + PATHS ${_PYTHON_LIBS_SEARCH} + NO_DEFAULT_PATH) # If all else fails, just set the name/version and let the linker figure out the path. if(NOT PYTHON_LIBRARY) diff --git a/tools/cmake_uninstall.cmake.in b/tools/cmake_uninstall.cmake.in index 53b16fb95e..1e5d2bb876 100644 --- a/tools/cmake_uninstall.cmake.in +++ b/tools/cmake_uninstall.cmake.in @@ -10,10 +10,10 @@ foreach(file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program( - "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + "@CMAKE_COMMAND@" ARGS + "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) + RETURN_VALUE rm_retval) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif() diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index a8b3cb1f10..c86e0dea25 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -72,7 +72,6 @@ set(pybind11_VERSION_TYPE "@pybind11_VERSION_TYPE@") check_required_components(pybind11) - include("${CMAKE_CURRENT_LIST_DIR}/pybind11Tools.cmake") #----------------------------------------------------------------------------- @@ -86,15 +85,35 @@ if(NOT TARGET pybind11::pybind11) find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED) list(REMOVE_AT CMAKE_MODULE_PATH -1) - set_property(TARGET pybind11::pybind11 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) - set_property(TARGET pybind11::pybind11 APPEND PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) - - set_property(TARGET pybind11::embed APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${PYTHON_LIBRARIES}) - set_property(TARGET pybind11::module APPEND PROPERTY INTERFACE_LINK_LIBRARIES - "$<$,$>:$>") - - get_property(_iid TARGET pybind11::pybind11 PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - get_property(_ill TARGET pybind11::module PROPERTY INTERFACE_LINK_LIBRARIES) + set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) + set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) + + set_property( + TARGET pybind11::embed + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES ${PYTHON_LIBRARIES}) + set_property( + TARGET pybind11::module + APPEND + PROPERTY + INTERFACE_LINK_LIBRARIES + "$<$,$>:$>" + ) + + get_property( + _iid + TARGET pybind11::pybind11 + PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + get_property( + _ill + TARGET pybind11::module + PROPERTY INTERFACE_LINK_LIBRARIES) set(pybind11_INCLUDE_DIRS ${_iid}) set(pybind11_LIBRARIES ${_ico} ${_ill}) From c664d55757ea20903da78f7320de8415da497378 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 1 Aug 2020 23:09:32 -0400 Subject: [PATCH 005/295] ci: better output / more config --- .github/workflows/ci.yml | 8 ++++ .github/workflows/configure.yml | 73 ++++++++++++++------------------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 044312b53e..5216e29ca4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,14 @@ jobs: python: 3.7 arch: x86 max-cxx-std: 14 + - runs-on: windows-latest + python: 3.6 + arch: x64 + max-cxx-std: 17 + - runs-on: windows-latest + python: 3.7 + arch: x64 + max-cxx-std: 17 exclude: # Currently 32bit only, and we build 64bit diff --git a/.github/workflows/configure.yml b/.github/workflows/configure.yml index 96904341de..934b2d73ba 100644 --- a/.github/workflows/configure.yml +++ b/.github/workflows/configure.yml @@ -1,4 +1,4 @@ -name: Configure +name: Config on: workflow_dispatch: @@ -14,63 +14,50 @@ jobs: strategy: fail-fast: false matrix: - python: - - 2.7 - - 3.8 + runs-on: [ubuntu-latest, macos-latest] + arch: [x64] + cmake: [3.7, 3.18] - name: CMake ${{ matrix.cmake }} Python ${{ matrix.python }} on ubuntu - runs-on: ubuntu-latest + include: + - runs-on: windows-latest + arch: x64 + cmake: 3.18 + + # TODO: 3.8 + - runs-on: windows-2016 + arch: x86 + cmake: 3.11 + + - runs-on: windows-2016 + arch: x86 + cmake: 3.18 + + name: 🐍 3.7 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }} + runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v2 - - name: Setup Python ${{ matrix.python }} + - name: Setup Python 3.7 uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python }} + python-version: 3.7 + architecture: ${{ matrix.arch }} - name: Prepare env run: python -m pip install -r tests/requirements.txt - - name: Make build directories - run: | - mkdir build3.7 - mkdir build3.11 - mkdir build3.18 - - - name: Setup CMake 3.7 + - name: Setup CMake ${{ matrix.cmake }} uses: jwlawson/actions-setup-cmake@v1.3 with: - cmake-version: 3.7 - - - name: Configure 3.7 - working-directory: build3.7 - run: > - cmake .. - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") + cmake-version: ${{ matrix.cmake }} - - name: Setup CMake 3.11 - uses: jwlawson/actions-setup-cmake@v1.3 - with: - cmake-version: 3.11 - - - name: Configure 3.11 - working-directory: build3.11 - run: > - cmake .. - -DPYBIND11_WERROR=ON - -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") - - - name: Setup CMake 3.18 - uses: jwlawson/actions-setup-cmake@v1.3 - with: - cmake-version: 3.18 + - name: Make build directories + run: mkdir "build dir" - - name: Configure 3.18 - working-directory: build3.18 + - name: Configure + working-directory: build dir + shell: bash run: > cmake .. -DPYBIND11_WERROR=ON From 227170dc2f209795974fdeea2df96c699de981a0 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 3 Aug 2020 11:44:16 -0400 Subject: [PATCH 006/295] fix: better handling of PYBIND11_CPP_STANDARD --- tools/pybind11Tools.cmake | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 258aaac00c..703c08d02b 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -25,14 +25,23 @@ include(CheckCXXCompilerFlag) # Warn or error if old variable name used if(PYBIND11_CPP_STANDARD) - if(NOT CMAKE_CXX_STANDARD) - string(REGEX MATCH [=[..^]=] VAL "${PYBIND11_CPP_STANDARD}") + string(REGEX MATCH [[..$]] VAL "${PYBIND11_CPP_STANDARD}") + if(CMAKE_CXX_STANDARD) + if(NOT CMAKE_CXX_STANDARD STREQUAL VAL) + message(WARNING "CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} does not match " + "PYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}, " + "please remove PYBIND11_CPP_STANDARD from your cache") + endif() + else() set(supported_standards 11 14 17 20) if("${VAL}" IN_LIST supported_standards) message(WARNING "USE -DCMAKE_CXX_STANDARD=${VAL} instead of PYBIND11_PYTHON_VERSION") - set(CMAKE_CXX_STANDARD ${VAL}) + set(CMAKE_CXX_STANDARD + ${VAL} + CACHE STRING "From PYBIND11_CPP_STANDARD") else() - message(FATAL_ERROR "PYBIND11_CPP_STANDARD should be replaced with CMAKE_CXX_STANDARD") + message(FATAL_ERROR "PYBIND11_CPP_STANDARD should be replaced with CMAKE_CXX_STANDARD " + "(last two chars: ${VAL} not understood as a valid CXX std)") endif() endif() endif() From 0af7fe6c1943e6a9043e4e01c4bc9059108a6c98 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 8 Aug 2020 17:34:38 -0400 Subject: [PATCH 007/295] fix: typo in pybind11_add_module (#2374) --- tools/pybind11Tools.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 703c08d02b..6e666957d9 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -132,7 +132,7 @@ endfunction() # function(pybind11_add_module target_name) set(options MODULE SHARED EXCLUDE_FROM_ALL NO_EXTRAS SYSTEM THIN_LTO) - cmake_parse_arguments(PARSE_ARGV 2 ARG "${options}" "" "") + cmake_parse_arguments(PARSE_ARGV 1 ARG "${options}" "" "") if(ARG_MODULE AND ARG_SHARED) message(FATAL_ERROR "Can't be both MODULE and SHARED") From 6f3470f757cc5162d5f9115ea9e280e071c212fa Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Mon, 10 Aug 2020 16:10:45 -0400 Subject: [PATCH 008/295] Add robotpy-build to list of tools (#2359) --- docs/compiling.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/compiling.rst b/docs/compiling.rst index 07f93e7cc5..3935dda134 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -306,3 +306,10 @@ extensible, and applies to very complex C++ libraries, composed of thousands of classes or incorporating modern meta-programming constructs. .. [AutoWIG] https://github.com/StatisKit/AutoWIG + +[robotpy-build]_ is a is a pure python, cross platform build tool that aims to +simplify creation of python wheels for pybind11 projects, and provide +cross-project dependency management. Additionally, it is able to autogenerate +customizable pybind11-based wrappers by parsing C++ header files. + +.. [robotpy-build] https://robotpy-build.readthedocs.io From 173204639e5c555dadbf5465e8d8b9b0d6b736ae Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Mon, 10 Aug 2020 17:49:14 -0700 Subject: [PATCH 009/295] Adding tests specifically to exercise pybind11::str::raw_str. (#2366) These tests will also alert us to any behavior changes across Python and PyPy versions. Hardening tests in preparation for changing `pybind11::str` to only hold `PyUnicodeObject` (NOT also `bytes`). Note that this test exposes that `pybind11::str` can also hold `bytes`. --- tests/test_pytypes.cpp | 2 ++ tests/test_pytypes.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 9f7bc37dc6..9dae6e7d62 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -237,6 +237,8 @@ TEST_SUBMODULE(pytypes, m) { ); }); + m.def("convert_to_pybind11_str", [](py::object o) { return py::str(o); }); + m.def("get_implicit_casting", []() { py::dict d; d["char*_i1"] = "abc"; diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 4cfc707a32..1c7b1dd1f2 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -220,6 +220,38 @@ def test_constructors(): assert noconv2[k] is expected[k] +def test_pybind11_str_raw_str(): + # specifically to exercise pybind11::str::raw_str + cvt = m.convert_to_pybind11_str + assert cvt(u"Str") == u"Str" + assert cvt(b'Bytes') == u"Bytes" if str is bytes else "b'Bytes'" + assert cvt(None) == u"None" + assert cvt(False) == u"False" + assert cvt(True) == u"True" + assert cvt(42) == u"42" + assert cvt(2**65) == u"36893488147419103232" + assert cvt(-1.50) == u"-1.5" + assert cvt(()) == u"()" + assert cvt((18,)) == u"(18,)" + assert cvt([]) == u"[]" + assert cvt([28]) == u"[28]" + assert cvt({}) == u"{}" + assert cvt({3: 4}) == u"{3: 4}" + assert cvt(set()) == u"set([])" if str is bytes else "set()" + assert cvt({3, 3}) == u"set([3])" if str is bytes else "{3}" + + valid_orig = u"DZ" + valid_utf8 = valid_orig.encode("utf-8") + valid_cvt = cvt(valid_utf8) + assert type(valid_cvt) == bytes # Probably surprising. + assert valid_cvt == b'\xc7\xb1' + + malformed_utf8 = b'\x80' + malformed_cvt = cvt(malformed_utf8) + assert type(malformed_cvt) == bytes # Probably surprising. + assert malformed_cvt == b'\x80' + + def test_implicit_casting(): """Tests implicit casting when assigning or appending to dicts and lists.""" z = m.get_implicit_casting() From f7abac66895d2c19f9992625241af32a8d8836d7 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 12 Aug 2020 09:33:08 -0400 Subject: [PATCH 010/295] fix: boost's include dir was listed first (#2384) --- tests/CMakeLists.txt | 4 ++-- tests/test_embed/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f19fa28c19..17f2a5e9bd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -236,13 +236,13 @@ foreach(target ${test_targets}) if(PYBIND11_EIGEN_VIA_TARGET) target_link_libraries(${target} PRIVATE Eigen3::Eigen) else() - target_include_directories(${target} PRIVATE ${EIGEN3_INCLUDE_DIR}) + target_include_directories(${target} SYSTEM PRIVATE ${EIGEN3_INCLUDE_DIR}) endif() target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_EIGEN) endif() if(Boost_FOUND) - target_include_directories(${target} PRIVATE ${Boost_INCLUDE_DIRS}) + target_include_directories(${target} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_BOOST) endif() diff --git a/tests/test_embed/CMakeLists.txt b/tests/test_embed/CMakeLists.txt index 25972701fc..c358d33220 100644 --- a/tests/test_embed/CMakeLists.txt +++ b/tests/test_embed/CMakeLists.txt @@ -14,7 +14,7 @@ else() endif() add_executable(test_embed catch.cpp test_interpreter.cpp) -target_include_directories(test_embed PRIVATE "${CATCH_INCLUDE_DIR}") +target_include_directories(test_embed SYSTEM PRIVATE "${CATCH_INCLUDE_DIR}") pybind11_enable_warnings(test_embed) target_link_libraries(test_embed PRIVATE pybind11::embed) From 1534e17e44a74274b164a65f76880764b8e4cc8d Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 12 Aug 2020 14:09:42 -0400 Subject: [PATCH 011/295] ci: include Python 3.9 RC1 (#2387) --- .github/workflows/ci.yml | 6 ------ tests/requirements.txt | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5216e29ca4..b946fc832b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,12 +58,6 @@ jobs: arch: x64 max-cxx-std: 17 - # Currently can't build due to warning, fixed in CPython > 3.9b5 - - runs-on: macos-latest - python: 3.9-dev - arch: x64 - max-cxx-std: 17 - # Currently broken on embed_test - runs-on: windows-latest python: 3.8 diff --git a/tests/requirements.txt b/tests/requirements.txt index c9750e473f..39bd57a1c7 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,7 +1,7 @@ --extra-index-url https://antocuni.github.io/pypy-wheels/manylinux2010/ numpy==1.16.6; python_version<"3.6" numpy==1.18.0; platform_python_implementation=="PyPy" and sys_platform=="darwin" and python_version>="3.6" -numpy==1.19.1; (platform_python_implementation!="PyPy" or sys_platform!="darwin") and python_version>="3.6" +numpy==1.19.1; (platform_python_implementation!="PyPy" or sys_platform!="darwin") and python_version>="3.6" and python_version<"3.9" pytest==4.6.9; python_version<"3.5" pytest==5.4.3; python_version>="3.5" scipy==1.2.3; (platform_python_implementation!="PyPy" or sys_platform!="darwin") and python_version<"3.6" From 830adda850d4ef9e680ead522b3189877cfc2827 Mon Sep 17 00:00:00 2001 From: marc-chiesa Date: Thu, 13 Aug 2020 16:47:23 -0400 Subject: [PATCH 012/295] Modified Vector STL bind initialization from a buffer type with optimization for simple arrays (#2298) * Modified Vector STL bind initialization from a buffer type with optimization for simple arrays * Add subtests to demonstrate processing Python buffer protocol objects with step > 1 * Fixed memoryview step test to only run on Python 3+ * Modified Vector constructor from buffer to return by value for readability --- include/pybind11/stl_bind.h | 15 ++++++++++----- tests/test_stl_binders.py | 9 +++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index 61b94b6220..47368f0280 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -397,14 +397,19 @@ vector_buffer(Class_& cl) { if (!detail::compare_buffer_info::compare(info) || (ssize_t) sizeof(T) != info.itemsize) throw type_error("Format mismatch (Python: " + info.format + " C++: " + format_descriptor::format() + ")"); - auto vec = std::unique_ptr(new Vector()); - vec->reserve((size_t) info.shape[0]); T *p = static_cast(info.ptr); ssize_t step = info.strides[0] / static_cast(sizeof(T)); T *end = p + info.shape[0] * step; - for (; p != end; p += step) - vec->push_back(*p); - return vec.release(); + if (step == 1) { + return Vector(p, end); + } + else { + Vector vec; + vec.reserve((size_t) info.shape[0]); + for (; p != end; p += step) + vec.push_back(*p); + return vec; + } })); return; diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py index 27b326f070..baa0f4e7fc 100644 --- a/tests/test_stl_binders.py +++ b/tests/test_stl_binders.py @@ -85,6 +85,11 @@ def test_vector_buffer(): mv[2] = '\x06' assert v[2] == 6 + if sys.version_info.major > 2: + mv = memoryview(b) + v = m.VectorUChar(mv[::2]) + assert v[1] == 3 + with pytest.raises(RuntimeError) as excinfo: m.create_undeclstruct() # Undeclared struct contents, no buffer interface assert "NumPy type info missing for " in str(excinfo.value) @@ -119,6 +124,10 @@ def test_vector_buffer_numpy(): ('y', 'float64'), ('z', 'bool')], align=True))) assert len(v) == 3 + b = np.array([1, 2, 3, 4], dtype=np.uint8) + v = m.VectorUChar(b[::2]) + assert v[1] == 3 + def test_vector_bool(): import pybind11_cross_module_tests as cm From 2e2de8c87a65aba78db65f60915a90b6f20e9c46 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 13 Aug 2020 20:13:16 -0400 Subject: [PATCH 013/295] fix: add missing signature (#2363) * fix: add missing signature * fix: add to array_t too --- include/pybind11/numpy.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 5b6cea2b4d..674450a631 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -611,8 +611,8 @@ class array : public buffer { template explicit array(ssize_t count, const T *ptr, handle base = handle()) : array({count}, {}, ptr, base) { } - explicit array(const buffer_info &info) - : array(pybind11::dtype(info), info.shape, info.strides, info.ptr) { } + explicit array(const buffer_info &info, handle base = handle()) + : array(pybind11::dtype(info), info.shape, info.strides, info.ptr, base) { } /// Array descriptor (dtype) pybind11::dtype dtype() const { @@ -858,7 +858,7 @@ template class array_t : public if (!m_ptr) throw error_already_set(); } - explicit array_t(const buffer_info& info) : array(info) { } + explicit array_t(const buffer_info& info, handle base = handle()) : array(info, base) { } array_t(ShapeContainer shape, StridesContainer strides, const T *ptr = nullptr, handle base = handle()) : array(std::move(shape), std::move(strides), ptr, base) { } From fb042d692ffbbc9ec39546c8e734596ddaf93b7e Mon Sep 17 00:00:00 2001 From: Michael Goulding Date: Fri, 14 Aug 2020 09:15:50 -0700 Subject: [PATCH 014/295] Fix warning C26817 on copying in `for (auto vh : value_and_holder(...))` (#2382) * Fix warning C26817: Potentially expensive copy of variable 'vh' in range-for loop. Consider making it a const reference (es.71). * Replace another instance of `for (auto vh : values_and_holders(...))` with `auto vh &` (found by @bstaletic) Co-authored-by: Michael Goulding Co-authored-by: Yannick Jadoul --- include/pybind11/cast.h | 2 +- include/pybind11/detail/class.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index c1d0bbc34d..4901ea3911 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -458,7 +458,7 @@ PYBIND11_NOINLINE inline handle get_object_handle(const void *ptr, const detail: auto &instances = get_internals().registered_instances; auto range = instances.equal_range(ptr); for (auto it = range.first; it != range.second; ++it) { - for (auto vh : values_and_holders(it->second)) { + for (const auto &vh : values_and_holders(it->second)) { if (vh.type == type) return handle((PyObject *) it->second); } diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index d58f04e612..8d36744f27 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -169,7 +169,7 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P auto instance = reinterpret_cast(self); // Ensure that the base __init__ function(s) were called - for (auto vh : values_and_holders(instance)) { + for (const auto &vh : values_and_holders(instance)) { if (!vh.holder_constructed()) { PyErr_Format(PyExc_TypeError, "%.200s.__init__() must be called when overriding __init__", vh.type->type->tp_name); From cba4a98546ed0e054fb85d1a7eb70a37154d8fd4 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 14 Aug 2020 12:24:58 -0400 Subject: [PATCH 015/295] ci: include Boost (#2393) --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b946fc832b..9c65967da3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,8 +80,16 @@ jobs: python-version: ${{ matrix.python }} architecture: ${{ matrix.arch }} + - name: Setup Boost + if: runner.os != 'macOS' + run: echo "::set-env name=BOOST_ROOT::$BOOST_ROOT_1_72_0" + + - name: Setup Boost (macOS) + if: runner.os == 'macOS' + run: brew install boost + - name: Cache wheels - if: startsWith(runner.os, 'macOS') + if: runner.os == 'macOS' uses: actions/cache@v2 with: # This path is specific to macOS - we really only need it for PyPy NumPy wheels From ebdd0d368cb53f6e8f4bafb4368332f8426f2001 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Fri, 14 Aug 2020 14:03:43 -0400 Subject: [PATCH 016/295] tests: Consolidate version (2 vs. 3) and platform (CPython vs. PyPy) checks (#2376) Fix logic in test_bytes_to_string Co-authored-by: Henry Schreiner --- tests/conftest.py | 16 ++++++++++------ tests/test_buffers.py | 9 +++------ tests/test_builtin_casters.py | 23 ++++++++++++++--------- tests/test_kwargs_and_defaults.py | 7 +------ tests/test_pytypes.py | 10 +++++----- tests/test_stl_binders.py | 5 ++--- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d317c49dbc..45a264a3ad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -205,7 +205,11 @@ def pytest_configure(): from pybind11_tests.eigen import have_eigen except ImportError: have_eigen = False - pypy = platform.python_implementation() == "PyPy" + + # Provide simple `six`-like aliases. + pytest.PY2 = (sys.version_info.major == 2) + pytest.CPYTHON = (platform.python_implementation() == "CPython") + pytest.PYPY = (platform.python_implementation() == "PyPy") skipif = pytest.mark.skipif pytest.suppress = suppress @@ -215,13 +219,13 @@ def pytest_configure(): reason="eigen and/or numpy are not installed") pytest.requires_eigen_and_scipy = skipif( not have_eigen or not scipy, reason="eigen and/or scipy are not installed") - pytest.unsupported_on_pypy = skipif(pypy, reason="unsupported on PyPy") - pytest.bug_in_pypy = pytest.mark.xfail(pypy, reason="bug in PyPy") - pytest.unsupported_on_pypy3 = skipif(pypy and sys.version_info.major >= 3, + pytest.unsupported_on_pypy = skipif(pytest.PYPY, reason="unsupported on PyPy") + pytest.bug_in_pypy = pytest.mark.xfail(pytest.PYPY, reason="bug in PyPy") + pytest.unsupported_on_pypy3 = skipif(pytest.PYPY and not pytest.PY2, reason="unsupported on PyPy3") - pytest.unsupported_on_pypy_lt_6 = skipif(pypy and sys.pypy_version_info[0] < 6, + pytest.unsupported_on_pypy_lt_6 = skipif(pytest.PYPY and sys.pypy_version_info[0] < 6, reason="unsupported on PyPy<6") - pytest.unsupported_on_py2 = skipif(sys.version_info.major < 3, + pytest.unsupported_on_py2 = skipif(pytest.PY2, reason="unsupported on Python 2.x") pytest.gc_collect = gc_collect diff --git a/tests/test_buffers.py b/tests/test_buffers.py index e264311d7c..db1871e6ae 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -1,15 +1,12 @@ # -*- coding: utf-8 -*- import io import struct -import sys import pytest from pybind11_tests import buffers as m from pybind11_tests import ConstructorStats -PY3 = sys.version_info[0] >= 3 - pytestmark = pytest.requires_numpy with pytest.suppress(ImportError): @@ -98,7 +95,7 @@ def test_pointer_to_member_fn(): def test_readonly_buffer(): buf = m.BufferReadOnly(0x64) view = memoryview(buf) - assert view[0] == 0x64 if PY3 else b'd' + assert view[0] == b'd' if pytest.PY2 else 0x64 assert view.readonly @@ -106,7 +103,7 @@ def test_readonly_buffer(): def test_selective_readonly_buffer(): buf = m.BufferReadOnlySelect() - memoryview(buf)[0] = 0x64 if PY3 else b'd' + memoryview(buf)[0] = b'd' if pytest.PY2 else 0x64 assert buf.value == 0x64 io.BytesIO(b'A').readinto(buf) @@ -114,6 +111,6 @@ def test_selective_readonly_buffer(): buf.readonly = True with pytest.raises(TypeError): - memoryview(buf)[0] = 0 if PY3 else b'\0' + memoryview(buf)[0] = b'\0' if pytest.PY2 else 0 with pytest.raises(TypeError): io.BytesIO(b'1').readinto(buf) diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index af44bda71e..c905766f83 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -115,13 +115,19 @@ def test_bytes_to_string(): """Tests the ability to pass bytes to C++ string-accepting functions. Note that this is one-way: the only way to return bytes to Python is via the pybind11::bytes class.""" # Issue #816 - import sys - byte = bytes if sys.version_info[0] < 3 else str - assert m.strlen(byte("hi")) == 2 - assert m.string_length(byte("world")) == 5 - assert m.string_length(byte("a\x00b")) == 3 - assert m.strlen(byte("a\x00b")) == 1 # C-string limitation + def to_bytes(s): + if pytest.PY2: + b = s + else: + b = s.encode("utf8") + assert isinstance(b, bytes) + return b + + assert m.strlen(to_bytes("hi")) == 2 + assert m.string_length(to_bytes("world")) == 5 + assert m.string_length(to_bytes("a\x00b")) == 3 + assert m.strlen(to_bytes("a\x00b")) == 1 # C-string limitation # passing in a utf8 encoded string should work assert m.string_length(u'💩'.encode("utf8")) == 4 @@ -187,12 +193,11 @@ def test_string_view(capture): def test_integer_casting(): """Issue #929 - out-of-range integer values shouldn't be accepted""" - import sys assert m.i32_str(-1) == "-1" assert m.i64_str(-1) == "-1" assert m.i32_str(2000000000) == "2000000000" assert m.u32_str(2000000000) == "2000000000" - if sys.version_info < (3,): + if pytest.PY2: assert m.i32_str(long(-1)) == "-1" # noqa: F821 undefined name 'long' assert m.i64_str(long(-1)) == "-1" # noqa: F821 undefined name 'long' assert m.i64_str(long(-999999999999)) == "-999999999999" # noqa: F821 undefined name @@ -214,7 +219,7 @@ def test_integer_casting(): m.i32_str(3000000000) assert "incompatible function arguments" in str(excinfo.value) - if sys.version_info < (3,): + if pytest.PY2: with pytest.raises(TypeError) as excinfo: m.u32_str(long(-1)) # noqa: F821 undefined name 'long' assert "incompatible function arguments" in str(excinfo.value) diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index dad40dbebf..df354ad3fa 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -2,11 +2,6 @@ import pytest from pybind11_tests import kwargs_and_defaults as m -import platform -import sys - -pypy = platform.python_implementation() == "PyPy" - def test_function_signatures(doc): assert doc(m.kw_func0) == "kw_func0(arg0: int, arg1: int) -> str" @@ -151,7 +146,7 @@ def test_keyword_only_args(msg): """ -@pytest.mark.xfail(pypy and sys.version_info < (3, 0), +@pytest.mark.xfail(pytest.PYPY and pytest.PY2, reason="PyPy2 doesn't seem to double count") def test_args_refcount(): """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 1c7b1dd1f2..e5d8355d7d 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -113,7 +113,7 @@ def test_bytes(doc): assert m.bytes_from_str().decode() == "bar" assert doc(m.bytes_from_str) == "bytes_from_str() -> {}".format( - "bytes" if sys.version_info[0] == 3 else "str" + "str" if pytest.PY2 else "bytes" ) @@ -324,7 +324,7 @@ def test_memoryview(method, args, fmt, expected_view): view = method(*args) assert isinstance(view, memoryview) assert view.format == fmt - if isinstance(expected_view, bytes) or sys.version_info[0] >= 3: + if isinstance(expected_view, bytes) or not pytest.PY2: view_as_list = list(view) else: # Using max to pick non-zero byte (big-endian vs little-endian). @@ -352,7 +352,7 @@ def test_memoryview_from_buffer_empty_shape(): view = m.test_memoryview_from_buffer_empty_shape() assert isinstance(view, memoryview) assert view.format == 'B' - if sys.version_info.major < 3: + if pytest.PY2: # Python 2 behavior is weird, but Python 3 (the future) is fine. # PyPy3 has 2: + if not pytest.PY2: assert mv[2] == 5 mv[2] = 6 else: @@ -85,7 +84,7 @@ def test_vector_buffer(): mv[2] = '\x06' assert v[2] == 6 - if sys.version_info.major > 2: + if not pytest.PY2: mv = memoryview(b) v = m.VectorUChar(mv[::2]) assert v[1] == 3 From 5a3ff72348103ec00cab2832a25d5d4937f9eec8 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Fri, 14 Aug 2020 14:16:38 -0400 Subject: [PATCH 017/295] ci: Remove "Setup Boost (macOS)" step (#2395) --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c65967da3..a3acbe9d3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,10 +84,6 @@ jobs: if: runner.os != 'macOS' run: echo "::set-env name=BOOST_ROOT::$BOOST_ROOT_1_72_0" - - name: Setup Boost (macOS) - if: runner.os == 'macOS' - run: brew install boost - - name: Cache wheels if: runner.os == 'macOS' uses: actions/cache@v2 From cd856992121b0a98bbe6e15d9a886f2b43f3d972 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 14 Aug 2020 13:53:41 -0700 Subject: [PATCH 018/295] Using recently added `pytest.PY2` instead of `str is bytes`. (#2396) Important gain: uniformity & therefore easier cleanup when we drop PY2 support. Very slight loss: it was nice to have `str is bytes` as a reminder in this specific context. --- tests/test_pytypes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index e5d8355d7d..289b4aab49 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -224,7 +224,7 @@ def test_pybind11_str_raw_str(): # specifically to exercise pybind11::str::raw_str cvt = m.convert_to_pybind11_str assert cvt(u"Str") == u"Str" - assert cvt(b'Bytes') == u"Bytes" if str is bytes else "b'Bytes'" + assert cvt(b'Bytes') == u"Bytes" if pytest.PY2 else "b'Bytes'" assert cvt(None) == u"None" assert cvt(False) == u"False" assert cvt(True) == u"True" @@ -237,8 +237,8 @@ def test_pybind11_str_raw_str(): assert cvt([28]) == u"[28]" assert cvt({}) == u"{}" assert cvt({3: 4}) == u"{3: 4}" - assert cvt(set()) == u"set([])" if str is bytes else "set()" - assert cvt({3, 3}) == u"set([3])" if str is bytes else "{3}" + assert cvt(set()) == u"set([])" if pytest.PY2 else "set()" + assert cvt({3, 3}) == u"set([3])" if pytest.PY2 else "{3}" valid_orig = u"DZ" valid_utf8 = valid_orig.encode("utf-8") From a876aac2cf779a7d82b454f0c7ae06add172dc97 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sun, 16 Aug 2020 11:18:47 -0400 Subject: [PATCH 019/295] tests: loosen test, not valid on some systems (#2399) --- tests/test_chrono.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_chrono.py b/tests/test_chrono.py index f1817e44f6..5392f8ff0d 100644 --- a/tests/test_chrono.py +++ b/tests/test_chrono.py @@ -15,14 +15,12 @@ def test_chrono_system_clock(): # The numbers should vary by a very small amount (time it took to execute) diff = abs(date1 - date2) - # There should never be a days/seconds difference + # There should never be a days difference assert diff.days == 0 - assert diff.seconds == 0 - # We test that no more than about 0.5 seconds passes here - # This makes sure that the dates created are very close to the same - # but if the testing system is incredibly overloaded this should still pass - assert diff.microseconds < 500000 + # Since datetime.datetime.today() calls time.time(), and on some platforms + # that has 1 second accuracy, we should always be less than 2 seconds. + assert diff.seconds < 2 def test_chrono_system_clock_roundtrip(): From 3618bea2aadc220055ed0f00ac113909da7b9466 Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 8 Aug 2020 03:07:14 -0700 Subject: [PATCH 020/295] Add and document py::error_already_set::discard_as_unraisable() To deal with exceptions that hit destructors or other noexcept functions. Includes fixes to support Python 2.7 and extends documentation on error handling. @virtuald and @YannickJadoul both contributed to this PR. --- docs/advanced/classes.rst | 38 ++++++++++++++++++++++++++ docs/advanced/exceptions.rst | 52 +++++++++++++++++++++++++++++++++--- include/pybind11/pytypes.h | 14 ++++++++++ tests/test_exceptions.cpp | 24 +++++++++++++++++ tests/test_exceptions.py | 29 ++++++++++++++++++++ 5 files changed, 154 insertions(+), 3 deletions(-) diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index 031484c9d0..e8940d814b 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -559,6 +559,44 @@ crucial that instances are deallocated on the C++ side to avoid memory leaks. py::class_>(m, "MyClass") .def(py::init<>()) +.. _destructors_that_call_python: + +Destructors that call Python +============================ + +If a Python function is invoked from a C++ destructor, an exception may be thrown +of type :class:`error_already_set`. If this error is thrown out of a class destructor, +``std::terminate()`` will be called, terminating the process. Class destructors +must catch all exceptions of type :class:`error_already_set` to discard the Python +exception using :func:`error_already_set::discard_as_unraisable`. + +Every Python function should be treated as *possibly throwing*. When a Python generator +stops yielding items, Python will throw a ``StopIteration`` exception, which can pass +though C++ destructors if the generator's stack frame holds the last reference to C++ +objects. + +For more information, see :ref:`the documentation on exceptions `. + +.. code-block:: cpp + + class MyClass { + public: + ~MyClass() { + try { + py::print("Even printing is dangerous in a destructor"); + py::exec("raise ValueError('This is an unraisable exception')"); + } catch (py::error_already_set &e) { + // error_context should be information about where/why the occurred, + // e.g. use __func__ to get the name of the current function + e.discard_as_unraisable(__func__); + } + } + }; + +.. note:: + + pybind11 does not support C++ destructors marked ``noexcept(false)``. + .. _implicit_conversions: Implicit conversions diff --git a/docs/advanced/exceptions.rst b/docs/advanced/exceptions.rst index 75ad7f7f4a..4fa53caaa7 100644 --- a/docs/advanced/exceptions.rst +++ b/docs/advanced/exceptions.rst @@ -53,9 +53,15 @@ exceptions: | | a Python exception back to Python. | +--------------------------------------+--------------------------------------+ -When a Python function invoked from C++ throws an exception, it is converted -into a C++ exception of type :class:`error_already_set` whose string payload -contains a textual summary. +When a Python function invoked from C++ throws an exception, pybind11 will convert +it into a C++ exception of type :class:`error_already_set` whose string payload +contains a textual summary. If you call the Python C-API directly, and it +returns an error, you should ``throw py::error_already_set();``, which allows +pybind11 to deal with the exception and pass it back to the Python interpreter. +(Another option is to call ``PyErr_Clear`` in the +`Python C-API `_ +to clear the error. The Python error must be thrown or cleared, or Python/pybind11 +will be left in an invalid state.) There is also a special exception :class:`cast_error` that is thrown by :func:`handle::call` when the input arguments cannot be converted to Python @@ -142,3 +148,43 @@ section. Exceptions that you do not plan to handle should simply not be caught, or may be explicitly (re-)thrown to delegate it to the other, previously-declared existing exception translators. + +.. _unraisable_exceptions: + +Handling unraisable exceptions +============================== + +If a Python function invoked from a C++ destructor or any function marked +``noexcept(true)`` (collectively, "noexcept functions") throws an exception, there +is no way to propagate the exception, as such functions may not throw at +run-time. + +Neither Python nor C++ allow exceptions raised in a noexcept function to propagate. In +Python, an exception raised in a class's ``__del__`` method is logged as an +unraisable error. In Python 3.8+, a system hook is triggered and an auditing +event is logged. In C++, ``std::terminate()`` is called to abort immediately. + +Any noexcept function should have a try-catch block that traps +class:`error_already_set` (or any other exception that can occur). Note that pybind11 +wrappers around Python exceptions such as :class:`pybind11::value_error` are *not* +Python exceptions; they are C++ exceptions that pybind11 catches and converts to +Python exceptions. Noexcept functions cannot propagate these exceptions either. +You can convert them to Python exceptions and then discard as unraisable. + +.. code-block:: cpp + + void nonthrowing_func() noexcept(true) { + try { + // ... + } catch (py::error_already_set &eas) { + // Discard the Python error using Python APIs, using the C++ magic + // variable __func__. Python already knows the type and value and of the + // exception object. + eas.discard_as_unraisable(__func__); + } catch (const std::exception &e) { + // Log and discard C++ exceptions. + // (We cannot use discard_as_unraisable, since we have a generic C++ + // exception, not an exception that originated from Python.) + third_party::log(e); + } + } diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 9000668b91..c322ff27ba 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -337,6 +337,20 @@ class error_already_set : public std::runtime_error { /// error variables (but the `.what()` string is still available). void restore() { PyErr_Restore(m_type.release().ptr(), m_value.release().ptr(), m_trace.release().ptr()); } + /// If it is impossible to raise the currently-held error, such as in destructor, we can write + /// it out using Python's unraisable hook (sys.unraisablehook). The error context should be + /// some object whose repr() helps identify the location of the error. Python already knows the + /// type and value of the error, so there is no need to repeat that. For example, __func__ could + /// be helpful. After this call, the current object no longer stores the error variables, + /// and neither does Python. + void discard_as_unraisable(object err_context) { + restore(); + PyErr_WriteUnraisable(err_context.ptr()); + } + void discard_as_unraisable(const char *err_context) { + discard_as_unraisable(reinterpret_steal(PYBIND11_FROM_STRING(err_context))); + } + // Does nothing; provided for backwards compatibility. PYBIND11_DEPRECATED("Use of error_already_set.clear() is deprecated") void clear() {} diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index 56cd9bc48f..372d0aebf4 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -65,6 +65,25 @@ struct PythonCallInDestructor { py::dict d; }; + + +struct PythonAlreadySetInDestructor { + PythonAlreadySetInDestructor(const py::str &s) : s(s) {} + ~PythonAlreadySetInDestructor() { + py::dict foo; + try { + // Assign to a py::object to force read access of nonexistent dict entry + py::object o = foo["bar"]; + } + catch (py::error_already_set& ex) { + ex.discard_as_unraisable(s); + } + } + + py::str s; +}; + + TEST_SUBMODULE(exceptions, m) { m.def("throw_std_exception", []() { throw std::runtime_error("This exception was intentionally thrown."); @@ -183,6 +202,11 @@ TEST_SUBMODULE(exceptions, m) { return false; }); + m.def("python_alreadyset_in_destructor", [](py::str s) { + PythonAlreadySetInDestructor alreadyset_in_destructor(s); + return true; + }); + // test_nested_throws m.def("try_catch", [m](py::object exc_type, py::function f, py::args args) { try { f(*args); } diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 053e7d4a28..83a46bfc8a 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import sys + import pytest from pybind11_tests import exceptions as m @@ -48,6 +50,33 @@ def test_python_call_in_catch(): assert d["good"] is True +def test_python_alreadyset_in_destructor(monkeypatch, capsys): + hooked = False + triggered = [False] # mutable, so Python 2.7 closure can modify it + + if hasattr(sys, 'unraisablehook'): # Python 3.8+ + hooked = True + default_hook = sys.unraisablehook + + def hook(unraisable_hook_args): + exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args + if obj == 'already_set demo': + triggered[0] = True + default_hook(unraisable_hook_args) + return + + # Use monkeypatch so pytest can apply and remove the patch as appropriate + monkeypatch.setattr(sys, 'unraisablehook', hook) + + assert m.python_alreadyset_in_destructor('already_set demo') is True + if hooked: + assert triggered[0] is True + + _, captured_stderr = capsys.readouterr() + # Error message is different in Python 2 and 3, check for words that appear in both + assert 'ignored' in captured_stderr and 'already_set demo' in captured_stderr + + def test_exception_matches(): assert m.exception_matches() assert m.exception_matches_base() From 4d9024ec71f30223fc161b7a01fcc486abce800b Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sun, 16 Aug 2020 16:02:12 -0400 Subject: [PATCH 021/295] tests: cleanup and ci hardening (#2397) * tests: refactor and cleanup * refactor: more consistent * tests: vendor six * tests: more xfails, nicer system * tests: simplify to info * tests: suggestions from @YannickJadoul and @bstaletic * tests: restore some pypy tests that now pass * tests: rename info to env * tests: strict False/True * tests: drop explicit strict=True again * tests: reduce minimum PyTest to 3.1 --- .github/workflows/ci.yml | 27 +++++++++- tests/CMakeLists.txt | 4 +- tests/conftest.py | 77 +++------------------------- tests/env.py | 12 +++++ tests/pybind11_tests.cpp | 2 - tests/pytest.ini | 5 +- tests/test_async.py | 5 +- tests/test_buffers.py | 21 +++----- tests/test_builtin_casters.py | 15 +++--- tests/test_call_policies.py | 8 ++- tests/test_class.py | 12 +++-- tests/test_eigen.cpp | 2 - tests/test_eigen.py | 20 ++++---- tests/test_eval.py | 6 ++- tests/test_factory_constructors.py | 4 +- tests/test_kwargs_and_defaults.py | 6 ++- tests/test_local_bindings.py | 4 +- tests/test_methods_and_attributes.py | 11 ++-- tests/test_multiple_inheritance.py | 12 +++-- tests/test_numpy_array.py | 13 +++-- tests/test_numpy_dtypes.py | 11 ++-- tests/test_numpy_vectorize.py | 5 +- tests/test_pickling.py | 5 +- tests/test_pytypes.py | 22 ++++---- tests/test_stl_binders.py | 16 +++--- tests/test_virtual_functions.py | 4 +- 26 files changed, 158 insertions(+), 171 deletions(-) create mode 100644 tests/env.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3acbe9d3c..30829631d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,11 +17,11 @@ jobs: runs-on: [ubuntu-latest, windows-latest, macos-latest] arch: [x64] max-cxx-std: [17] + dev: [false] python: - 2.7 - 3.5 - 3.8 - - 3.9-dev - pypy2 - pypy3 @@ -30,22 +30,38 @@ jobs: python: 3.6 arch: x64 max-cxx-std: 17 + dev: false - runs-on: macos-latest python: 3.7 arch: x64 max-cxx-std: 17 + dev: false - runs-on: windows-2016 python: 3.7 arch: x86 max-cxx-std: 14 + dev: false - runs-on: windows-latest python: 3.6 arch: x64 max-cxx-std: 17 + dev: false - runs-on: windows-latest python: 3.7 arch: x64 max-cxx-std: 17 + dev: false + + - runs-on: ubuntu-latest + python: 3.9-dev + arch: x64 + max-cxx-std: 17 + dev: true + - runs-on: macos-latest + python: 3.9-dev + arch: x64 + max-cxx-std: 17 + dev: true exclude: # Currently 32bit only, and we build 64bit @@ -53,23 +69,29 @@ jobs: python: pypy2 arch: x64 max-cxx-std: 17 + dev: false - runs-on: windows-latest python: pypy3 arch: x64 max-cxx-std: 17 + dev: false # Currently broken on embed_test - runs-on: windows-latest python: 3.8 arch: x64 max-cxx-std: 17 + dev: false - runs-on: windows-latest python: 3.9-dev arch: x64 max-cxx-std: 17 + dev: false + name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • ${{ matrix.arch }}" runs-on: ${{ matrix.runs-on }} + continue-on-error: ${{ matrix.dev }} steps: - uses: actions/checkout@v2 @@ -289,7 +311,8 @@ jobs: - name: Install requirements run: | apt-get update - apt-get install -y git make cmake g++ libeigen3-dev python3-dev python3-pip python3-pytest + apt-get install -y git make cmake g++ libeigen3-dev python3-dev python3-pip + pip3 install "pytest==3.1.*" - name: Configure for install run: > diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 17f2a5e9bd..2a077c6eb5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -266,8 +266,8 @@ if(NOT PYBIND11_PYTEST_FOUND) if(pytest_not_found) message(FATAL_ERROR "Running the tests requires pytest. Please install it manually" " (try: ${PYTHON_EXECUTABLE} -m pip install pytest)") - elseif(pytest_version VERSION_LESS 3.0) - message(FATAL_ERROR "Running the tests requires pytest >= 3.0. Found: ${pytest_version}" + elseif(pytest_version VERSION_LESS 3.1) + message(FATAL_ERROR "Running the tests requires pytest >= 3.1. Found: ${pytest_version}" "Please update it (try: ${PYTHON_EXECUTABLE} -m pip install -U pytest)") endif() set(PYBIND11_PYTEST_FOUND diff --git a/tests/conftest.py b/tests/conftest.py index 45a264a3ad..8b6e47dc2e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,24 +5,21 @@ Adds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences. """ -import pytest -import textwrap -import difflib -import re -import sys import contextlib -import platform +import difflib import gc +import re +import textwrap + +import pytest + +# Early diagnostic for failed imports +import pybind11_tests # noqa: F401 _unicode_marker = re.compile(r'u(\'[^\']*\')') _long_marker = re.compile(r'([0-9])L') _hexadecimal = re.compile(r'0x[0-9a-fA-F]+') -# test_async.py requires support for async and await -collect_ignore = [] -if sys.version_info[:2] < (3, 5): - collect_ignore.append("test_async.py") - def _strip_and_dedent(s): """For triple-quote strings""" @@ -192,63 +189,5 @@ def gc_collect(): def pytest_configure(): - """Add import suppression and test requirements to `pytest` namespace""" - try: - import numpy as np - except ImportError: - np = None - try: - import scipy - except ImportError: - scipy = None - try: - from pybind11_tests.eigen import have_eigen - except ImportError: - have_eigen = False - - # Provide simple `six`-like aliases. - pytest.PY2 = (sys.version_info.major == 2) - pytest.CPYTHON = (platform.python_implementation() == "CPython") - pytest.PYPY = (platform.python_implementation() == "PyPy") - - skipif = pytest.mark.skipif pytest.suppress = suppress - pytest.requires_numpy = skipif(not np, reason="numpy is not installed") - pytest.requires_scipy = skipif(not np, reason="scipy is not installed") - pytest.requires_eigen_and_numpy = skipif(not have_eigen or not np, - reason="eigen and/or numpy are not installed") - pytest.requires_eigen_and_scipy = skipif( - not have_eigen or not scipy, reason="eigen and/or scipy are not installed") - pytest.unsupported_on_pypy = skipif(pytest.PYPY, reason="unsupported on PyPy") - pytest.bug_in_pypy = pytest.mark.xfail(pytest.PYPY, reason="bug in PyPy") - pytest.unsupported_on_pypy3 = skipif(pytest.PYPY and not pytest.PY2, - reason="unsupported on PyPy3") - pytest.unsupported_on_pypy_lt_6 = skipif(pytest.PYPY and sys.pypy_version_info[0] < 6, - reason="unsupported on PyPy<6") - pytest.unsupported_on_py2 = skipif(pytest.PY2, - reason="unsupported on Python 2.x") pytest.gc_collect = gc_collect - - -def _test_import_pybind11(): - """Early diagnostic for test module initialization errors - - When there is an error during initialization, the first import will report the - real error while all subsequent imports will report nonsense. This import test - is done early (in the pytest configuration file, before any tests) in order to - avoid the noise of having all tests fail with identical error messages. - - Any possible exception is caught here and reported manually *without* the stack - trace. This further reduces noise since the trace would only show pytest internals - which are not useful for debugging pybind11 module issues. - """ - # noinspection PyBroadException - try: - import pybind11_tests # noqa: F401 imported but unused - except Exception as e: - print("Failed to import pybind11_tests from pytest:") - print(" {}: {}".format(type(e).__name__, e)) - sys.exit(1) - - -_test_import_pybind11() diff --git a/tests/env.py b/tests/env.py new file mode 100644 index 0000000000..f246b082bc --- /dev/null +++ b/tests/env.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +import platform +import sys + +LINUX = sys.platform.startswith("linux") +MACOS = sys.platform.startswith("darwin") +WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin") + +CPYTHON = platform.python_implementation() == "CPython" +PYPY = platform.python_implementation() == "PyPy" + +PY2 = sys.version_info.major == 2 diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp index bc7d2c3e7a..76e0298e83 100644 --- a/tests/pybind11_tests.cpp +++ b/tests/pybind11_tests.cpp @@ -88,6 +88,4 @@ PYBIND11_MODULE(pybind11_tests, m) { for (const auto &initializer : initializers()) initializer(m); - - if (!py::hasattr(m, "have_eigen")) m.attr("have_eigen") = false; } diff --git a/tests/pytest.ini b/tests/pytest.ini index f209964a47..6d758ea6ac 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,11 +1,14 @@ [pytest] -minversion = 3.0 +minversion = 3.1 norecursedirs = test_cmake_build test_embed +xfail_strict = True addopts = # show summary of skipped tests -rs # capture only Python print and C++ py::print, but not C output (low-level Python errors) --capture=sys + # enable all warnings + -Wa filterwarnings = # make warnings into errors but ignore certain third-party extension issues error diff --git a/tests/test_async.py b/tests/test_async.py index e9292c9d9c..df4489c499 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -import asyncio import pytest -from pybind11_tests import async_module as m + +asyncio = pytest.importorskip("asyncio") +m = pytest.importorskip("pybind11_tests.async_module") @pytest.fixture diff --git a/tests/test_buffers.py b/tests/test_buffers.py index db1871e6ae..d6adaf1f5e 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -4,13 +4,12 @@ import pytest +import env # noqa: F401 + from pybind11_tests import buffers as m from pybind11_tests import ConstructorStats -pytestmark = pytest.requires_numpy - -with pytest.suppress(ImportError): - import numpy as np +np = pytest.importorskip("numpy") def test_from_python(): @@ -36,9 +35,7 @@ def test_from_python(): assert cstats.move_assignments == 0 -# PyPy: Memory leak in the "np.array(m, copy=False)" call -# https://bitbucket.org/pypy/pypy/issues/2444 -@pytest.unsupported_on_pypy +# https://foss.heptapod.net/pypy/pypy/-/issues/2444 def test_to_python(): mat = m.Matrix(5, 4) assert memoryview(mat).shape == (5, 4) @@ -73,7 +70,6 @@ def test_to_python(): assert cstats.move_assignments == 0 -@pytest.unsupported_on_pypy def test_inherited_protocol(): """SquareMatrix is derived from Matrix and inherits the buffer protocol""" @@ -82,7 +78,6 @@ def test_inherited_protocol(): assert np.asarray(matrix).shape == (5, 5) -@pytest.unsupported_on_pypy def test_pointer_to_member_fn(): for cls in [m.Buffer, m.ConstBuffer, m.DerivedBuffer]: buf = cls() @@ -91,19 +86,17 @@ def test_pointer_to_member_fn(): assert value == 0x12345678 -@pytest.unsupported_on_pypy def test_readonly_buffer(): buf = m.BufferReadOnly(0x64) view = memoryview(buf) - assert view[0] == b'd' if pytest.PY2 else 0x64 + assert view[0] == b'd' if env.PY2 else 0x64 assert view.readonly -@pytest.unsupported_on_pypy def test_selective_readonly_buffer(): buf = m.BufferReadOnlySelect() - memoryview(buf)[0] = b'd' if pytest.PY2 else 0x64 + memoryview(buf)[0] = b'd' if env.PY2 else 0x64 assert buf.value == 0x64 io.BytesIO(b'A').readinto(buf) @@ -111,6 +104,6 @@ def test_selective_readonly_buffer(): buf.readonly = True with pytest.raises(TypeError): - memoryview(buf)[0] = b'\0' if pytest.PY2 else 0 + memoryview(buf)[0] = b'\0' if env.PY2 else 0 with pytest.raises(TypeError): io.BytesIO(b'1').readinto(buf) diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index c905766f83..08d38bc154 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import pytest +import env # noqa: F401 + from pybind11_tests import builtin_casters as m from pybind11_tests import UserType, IncType @@ -117,10 +119,7 @@ def test_bytes_to_string(): # Issue #816 def to_bytes(s): - if pytest.PY2: - b = s - else: - b = s.encode("utf8") + b = s if env.PY2 else s.encode("utf8") assert isinstance(b, bytes) return b @@ -197,7 +196,7 @@ def test_integer_casting(): assert m.i64_str(-1) == "-1" assert m.i32_str(2000000000) == "2000000000" assert m.u32_str(2000000000) == "2000000000" - if pytest.PY2: + if env.PY2: assert m.i32_str(long(-1)) == "-1" # noqa: F821 undefined name 'long' assert m.i64_str(long(-1)) == "-1" # noqa: F821 undefined name 'long' assert m.i64_str(long(-999999999999)) == "-999999999999" # noqa: F821 undefined name @@ -219,7 +218,7 @@ def test_integer_casting(): m.i32_str(3000000000) assert "incompatible function arguments" in str(excinfo.value) - if pytest.PY2: + if env.PY2: with pytest.raises(TypeError) as excinfo: m.u32_str(long(-1)) # noqa: F821 undefined name 'long' assert "incompatible function arguments" in str(excinfo.value) @@ -360,9 +359,9 @@ class B(object): assert convert(A(False)) is False -@pytest.requires_numpy def test_numpy_bool(): - import numpy as np + np = pytest.importorskip("numpy") + convert, noconvert = m.bool_passthrough, m.bool_passthrough_noconvert def cant_convert(v): diff --git a/tests/test_call_policies.py b/tests/test_call_policies.py index 0e3230c573..ec005c132f 100644 --- a/tests/test_call_policies.py +++ b/tests/test_call_policies.py @@ -1,9 +1,13 @@ # -*- coding: utf-8 -*- import pytest + +import env # noqa: F401 + from pybind11_tests import call_policies as m from pybind11_tests import ConstructorStats +@pytest.mark.xfail("env.PYPY", reason="sometimes comes out 1 off on PyPy", strict=False) def test_keep_alive_argument(capture): n_inst = ConstructorStats.detail_reg_inst() with capture: @@ -70,8 +74,8 @@ def test_keep_alive_return_value(capture): """ -# https://bitbucket.org/pypy/pypy/issues/2447 -@pytest.unsupported_on_pypy +# https://foss.heptapod.net/pypy/pypy/-/issues/2447 +@pytest.mark.xfail("env.PYPY", reason="_PyObject_GetDictPtr is unimplemented") def test_alive_gc(capture): n_inst = ConstructorStats.detail_reg_inst() p = m.ParentGC() diff --git a/tests/test_class.py b/tests/test_class.py index bbf8481a4d..4214fe79d7 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import pytest +import env # noqa: F401 + from pybind11_tests import class_ as m from pybind11_tests import UserType, ConstructorStats @@ -261,7 +263,7 @@ def test_brace_initialization(): assert b.vec == [123, 456] -@pytest.unsupported_on_pypy +@pytest.mark.xfail("env.PYPY") def test_class_refcount(): """Instances must correctly increase/decrease the reference count of their types (#1029)""" from sys import getrefcount @@ -307,8 +309,8 @@ def test_aligned(): assert p % 1024 == 0 -# https://bitbucket.org/pypy/pypy/issues/2742 -@pytest.unsupported_on_pypy +# https://foss.heptapod.net/pypy/pypy/-/issues/2742 +@pytest.mark.xfail("env.PYPY") def test_final(): with pytest.raises(TypeError) as exc_info: class PyFinalChild(m.IsFinal): @@ -316,8 +318,8 @@ class PyFinalChild(m.IsFinal): assert str(exc_info.value).endswith("is not an acceptable base type") -# https://bitbucket.org/pypy/pypy/issues/2742 -@pytest.unsupported_on_pypy +# https://foss.heptapod.net/pypy/pypy/-/issues/2742 +@pytest.mark.xfail("env.PYPY") def test_non_final_final(): with pytest.raises(TypeError) as exc_info: class PyNonFinalFinalChild(m.IsNonFinalFinal): diff --git a/tests/test_eigen.cpp b/tests/test_eigen.cpp index aba088d72b..56aa1a4a6f 100644 --- a/tests/test_eigen.cpp +++ b/tests/test_eigen.cpp @@ -87,8 +87,6 @@ TEST_SUBMODULE(eigen, m) { using SparseMatrixR = Eigen::SparseMatrix; using SparseMatrixC = Eigen::SparseMatrix; - m.attr("have_eigen") = true; - // various tests m.def("double_col", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return 2.0f * x; }); m.def("double_row", [](const Eigen::RowVectorXf &x) -> Eigen::RowVectorXf { return 2.0f * x; }); diff --git a/tests/test_eigen.py b/tests/test_eigen.py index ae868da513..ac68471474 100644 --- a/tests/test_eigen.py +++ b/tests/test_eigen.py @@ -2,17 +2,15 @@ import pytest from pybind11_tests import ConstructorStats -pytestmark = pytest.requires_eigen_and_numpy +np = pytest.importorskip("numpy") +m = pytest.importorskip("pybind11_tests.eigen") -with pytest.suppress(ImportError): - from pybind11_tests import eigen as m - import numpy as np - ref = np.array([[ 0., 3, 0, 0, 0, 11], - [22, 0, 0, 0, 17, 11], - [ 7, 5, 0, 1, 0, 11], - [ 0, 0, 0, 0, 0, 11], - [ 0, 0, 14, 0, 8, 11]]) +ref = np.array([[ 0., 3, 0, 0, 0, 11], + [22, 0, 0, 0, 17, 11], + [ 7, 5, 0, 1, 0, 11], + [ 0, 0, 0, 0, 0, 11], + [ 0, 0, 14, 0, 8, 11]]) def assert_equal_ref(mat): @@ -646,8 +644,8 @@ def test_named_arguments(): assert str(excinfo.value) == 'Nonconformable matrices!' -@pytest.requires_eigen_and_scipy def test_sparse(): + pytest.importorskip("scipy") assert_sparse_equal_ref(m.sparse_r()) assert_sparse_equal_ref(m.sparse_c()) assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_r())) @@ -656,8 +654,8 @@ def test_sparse(): assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_r())) -@pytest.requires_eigen_and_scipy def test_sparse_signature(doc): + pytest.importorskip("scipy") assert doc(m.sparse_copy_r) == """ sparse_copy_r(arg0: scipy.sparse.csr_matrix[numpy.float32]) -> scipy.sparse.csr_matrix[numpy.float32] """ # noqa: E501 line too long diff --git a/tests/test_eval.py b/tests/test_eval.py index 66bec55f8b..b6f9d1881d 100644 --- a/tests/test_eval.py +++ b/tests/test_eval.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- import os + import pytest + +import env # noqa: F401 + from pybind11_tests import eval_ as m @@ -15,7 +19,7 @@ def test_evals(capture): assert m.test_eval_failure() -@pytest.unsupported_on_pypy3 +@pytest.mark.xfail("env.PYPY and not env.PY2", raises=RuntimeError) def test_eval_file(): filename = os.path.join(os.path.dirname(__file__), "test_eval_call.py") assert m.test_eval_file(filename) diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py index 49e6f4f331..8465c59e3f 100644 --- a/tests/test_factory_constructors.py +++ b/tests/test_factory_constructors.py @@ -2,6 +2,8 @@ import pytest import re +import env # noqa: F401 + from pybind11_tests import factory_constructors as m from pybind11_tests.factory_constructors import tag from pybind11_tests import ConstructorStats @@ -418,7 +420,7 @@ def test_reallocations(capture, msg): """) -@pytest.unsupported_on_py2 +@pytest.mark.skipif("env.PY2") def test_invalid_self(): """Tests invocation of the pybind-registered base class with an invalid `self` argument. You can only actually do this on Python 3: Python 2 raises an exception itself if you try.""" diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index df354ad3fa..5257e0cd30 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- import pytest + +import env # noqa: F401 + from pybind11_tests import kwargs_and_defaults as m @@ -146,8 +149,7 @@ def test_keyword_only_args(msg): """ -@pytest.mark.xfail(pytest.PYPY and pytest.PY2, - reason="PyPy2 doesn't seem to double count") +@pytest.mark.xfail("env.PYPY and env.PY2", reason="PyPy2 doesn't double count") def test_args_refcount(): """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular arguments""" diff --git a/tests/test_local_bindings.py b/tests/test_local_bindings.py index 913cf0ee5b..5460727e1d 100644 --- a/tests/test_local_bindings.py +++ b/tests/test_local_bindings.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- import pytest +import env # noqa: F401 + from pybind11_tests import local_bindings as m @@ -153,7 +155,7 @@ def test_internal_locals_differ(): assert m.local_cpp_types_addr() != cm.local_cpp_types_addr() -@pytest.bug_in_pypy +@pytest.mark.xfail("env.PYPY") def test_stl_caster_vs_stl_bind(msg): """One module uses a generic vector caster from `` while the other exports `std::vector` via `py:bind_vector` and `py::module_local`""" diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index 25a01c7186..c296b6868d 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- import pytest + +import env # noqa: F401 + from pybind11_tests import methods_and_attributes as m from pybind11_tests import ConstructorStats @@ -257,8 +260,8 @@ def test_property_rvalue_policy(): assert os.value == 1 -# https://bitbucket.org/pypy/pypy/issues/2447 -@pytest.unsupported_on_pypy +# https://foss.heptapod.net/pypy/pypy/-/issues/2447 +@pytest.mark.xfail("env.PYPY") def test_dynamic_attributes(): instance = m.DynamicClass() assert not hasattr(instance, "foo") @@ -299,8 +302,8 @@ class PythonDerivedDynamicClass(m.DynamicClass): assert cstats.alive() == 0 -# https://bitbucket.org/pypy/pypy/issues/2447 -@pytest.unsupported_on_pypy +# https://foss.heptapod.net/pypy/pypy/-/issues/2447 +@pytest.mark.xfail("env.PYPY") def test_cyclic_gc(): # One object references itself instance = m.DynamicClass() diff --git a/tests/test_multiple_inheritance.py b/tests/test_multiple_inheritance.py index bb602f84bb..7a0259d214 100644 --- a/tests/test_multiple_inheritance.py +++ b/tests/test_multiple_inheritance.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- import pytest + +import env # noqa: F401 + from pybind11_tests import ConstructorStats from pybind11_tests import multiple_inheritance as m @@ -11,7 +14,8 @@ def test_multiple_inheritance_cpp(): assert mt.bar() == 4 -@pytest.bug_in_pypy +@pytest.mark.skipif("env.PYPY and env.PY2") +@pytest.mark.xfail("env.PYPY and not env.PY2") def test_multiple_inheritance_mix1(): class Base1: def __init__(self, i): @@ -32,7 +36,6 @@ def __init__(self, i, j): def test_multiple_inheritance_mix2(): - class Base2: def __init__(self, i): self.i = i @@ -51,7 +54,8 @@ def __init__(self, i, j): assert mt.bar() == 4 -@pytest.bug_in_pypy +@pytest.mark.skipif("env.PYPY and env.PY2") +@pytest.mark.xfail("env.PYPY and not env.PY2") def test_multiple_inheritance_python(): class MI1(m.Base1, m.Base2): @@ -256,7 +260,7 @@ def test_mi_static_properties(): assert d.static_value == 0 -@pytest.unsupported_on_pypy_lt_6 +# Requires PyPy 6+ def test_mi_dynamic_attributes(): """Mixing bases with and without dynamic attribute support""" diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 1b6599dfe4..ad3ca58c1a 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- import pytest -from pybind11_tests import numpy_array as m -pytestmark = pytest.requires_numpy +import env # noqa: F401 + +from pybind11_tests import numpy_array as m -with pytest.suppress(ImportError): - import numpy as np +np = pytest.importorskip("numpy") def test_dtypes(): @@ -243,7 +243,6 @@ def test_numpy_view(capture): """ -@pytest.unsupported_on_pypy def test_cast_numpy_int64_to_uint64(): m.function_taking_uint64(123) m.function_taking_uint64(np.uint64(123)) @@ -424,7 +423,7 @@ def test_array_resize(msg): assert(b.shape == (8, 8)) -@pytest.unsupported_on_pypy +@pytest.mark.xfail("env.PYPY") def test_array_create_and_resize(msg): a = m.create_and_resize(2) assert(a.size == 4) @@ -436,7 +435,7 @@ def test_index_using_ellipsis(): assert a.shape == (6,) -@pytest.unsupported_on_pypy +@pytest.mark.xfail("env.PYPY") def test_dtype_refcount_leak(): from sys import getrefcount dtype = np.dtype(np.float_) diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index d173435fe6..417d6f1cff 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- import re + import pytest -from pybind11_tests import numpy_dtypes as m -pytestmark = pytest.requires_numpy +import env # noqa: F401 + +from pybind11_tests import numpy_dtypes as m -with pytest.suppress(ImportError): - import numpy as np +np = pytest.importorskip("numpy") @pytest.fixture(scope='module') @@ -294,7 +295,7 @@ def test_register_dtype(): assert 'dtype is already registered' in str(excinfo.value) -@pytest.unsupported_on_pypy +@pytest.mark.xfail("env.PYPY") def test_str_leak(): from sys import getrefcount fmt = "f4" diff --git a/tests/test_numpy_vectorize.py b/tests/test_numpy_vectorize.py index bd3c01347c..54e44cd8d3 100644 --- a/tests/test_numpy_vectorize.py +++ b/tests/test_numpy_vectorize.py @@ -2,10 +2,7 @@ import pytest from pybind11_tests import numpy_vectorize as m -pytestmark = pytest.requires_numpy - -with pytest.suppress(ImportError): - import numpy as np +np = pytest.importorskip("numpy") def test_vectorize(capture): diff --git a/tests/test_pickling.py b/tests/test_pickling.py index 58d67a6339..9aee70505d 100644 --- a/tests/test_pickling.py +++ b/tests/test_pickling.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- import pytest + +import env # noqa: F401 + from pybind11_tests import pickling as m try: @@ -22,7 +25,7 @@ def test_roundtrip(cls_name): assert p2.extra2() == p.extra2() -@pytest.unsupported_on_pypy +@pytest.mark.xfail("env.PYPY") @pytest.mark.parametrize("cls_name", ["PickleableWithDict", "PickleableWithDictNew"]) def test_roundtrip_with_dict(cls_name): cls = getattr(m, cls_name) diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 289b4aab49..c21ad61146 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -3,6 +3,8 @@ import pytest import sys +import env # noqa: F401 + from pybind11_tests import pytypes as m from pybind11_tests import debug_enabled @@ -113,7 +115,7 @@ def test_bytes(doc): assert m.bytes_from_str().decode() == "bar" assert doc(m.bytes_from_str) == "bytes_from_str() -> {}".format( - "str" if pytest.PY2 else "bytes" + "str" if env.PY2 else "bytes" ) @@ -224,7 +226,7 @@ def test_pybind11_str_raw_str(): # specifically to exercise pybind11::str::raw_str cvt = m.convert_to_pybind11_str assert cvt(u"Str") == u"Str" - assert cvt(b'Bytes') == u"Bytes" if pytest.PY2 else "b'Bytes'" + assert cvt(b'Bytes') == u"Bytes" if env.PY2 else "b'Bytes'" assert cvt(None) == u"None" assert cvt(False) == u"False" assert cvt(True) == u"True" @@ -237,8 +239,8 @@ def test_pybind11_str_raw_str(): assert cvt([28]) == u"[28]" assert cvt({}) == u"{}" assert cvt({3: 4}) == u"{3: 4}" - assert cvt(set()) == u"set([])" if pytest.PY2 else "set()" - assert cvt({3, 3}) == u"set([3])" if pytest.PY2 else "{3}" + assert cvt(set()) == u"set([])" if env.PY2 else "set()" + assert cvt({3, 3}) == u"set([3])" if env.PY2 else "{3}" valid_orig = u"DZ" valid_utf8 = valid_orig.encode("utf-8") @@ -324,7 +326,7 @@ def test_memoryview(method, args, fmt, expected_view): view = method(*args) assert isinstance(view, memoryview) assert view.format == fmt - if isinstance(expected_view, bytes) or not pytest.PY2: + if isinstance(expected_view, bytes) or not env.PY2: view_as_list = list(view) else: # Using max to pick non-zero byte (big-endian vs little-endian). @@ -332,9 +334,7 @@ def test_memoryview(method, args, fmt, expected_view): assert view_as_list == list(expected_view) -@pytest.mark.skipif( - not hasattr(sys, 'getrefcount'), - reason='getrefcount is not available') +@pytest.mark.xfail("env.PYPY", reason="getrefcount is not available") @pytest.mark.parametrize('method', [ m.test_memoryview_object, m.test_memoryview_buffer_info, @@ -352,7 +352,7 @@ def test_memoryview_from_buffer_empty_shape(): view = m.test_memoryview_from_buffer_empty_shape() assert isinstance(view, memoryview) assert view.format == 'B' - if pytest.PY2: + if env.PY2: # Python 2 behavior is weird, but Python 3 (the future) is fine. # PyPy3 has 1 causes call with noncopyable instance # to fail in ncv1.print_nc() -@pytest.unsupported_on_pypy +@pytest.mark.xfail("env.PYPY") @pytest.mark.skipif(not hasattr(m, "NCVirt"), reason="NCVirt test broken on ICPC") def test_move_support(): class NCVirtExt(m.NCVirt): From 64040997121d5660394463db9c117bae8db9f42c Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 17 Aug 2020 10:14:23 -0400 Subject: [PATCH 022/295] docs: contrib/issue templates (#2377) * docs: move helpers to .github where allowed * docs: more guidelines in CONTRIBUTING * chore: update issue templates * fix: review from @bstaletic * refactor: a few points from @rwgk * docs: more touchup, review changes --- .github/ISSUE_TEMPLATE/bug-report.md | 28 ++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature-request.md | 16 +++ .github/ISSUE_TEMPLATE/question.md | 21 +++ CONTRIBUTING.md | 149 +++++++++++++++++++--- ISSUE_TEMPLATE.md | 17 --- LICENSE | 2 +- tests/CMakeLists.txt | 2 +- 8 files changed, 204 insertions(+), 36 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/question.md delete mode 100644 ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000000..ae36ea6508 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,28 @@ +--- +name: Bug Report +about: File an issue about a bug +title: "[BUG] " +--- + + +Make sure you've completed the following steps before submitting your issue -- thank you! + +1. Make sure you've read the [documentation][]. Your issue may be addressed there. +2. Search the [issue tracker][] to verify that this hasn't already been reported. +1 or comment there if it has. +3. Consider asking first in the [Gitter chat room][]. +4. Include a self-contained and minimal piece of code that reproduces the problem. If that's not possible, try to make the description as clear as possible. + a. If possible, make a PR with a new, failing test to give us a starting point to work on! + +[documentation]: https://pybind11.readthedocs.io +[issue tracker]: https://github.com/pybind/pybind11/issues +[Gitter chat room]: https://gitter.im/pybind/Lobby + +*After reading, remove this checklist and the template text in parentheses below.* + +## Issue description + +(Provide a short description, state the expected behavior and what actually happens.) + +## Reproducible example code + +(The code should be minimal, have no external dependencies, isolate the function(s) that cause breakage. Submit matched and complete C++ and Python snippets that can be easily compiled and run to diagnose the issue.) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..20e743136f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Gitter room + url: https://gitter.im/pybind/Lobby + about: A room for discussing pybind11 with an active community diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000000..5f6ec81ec9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,16 @@ +--- +name: Feature Request +about: File an issue about adding a feature +title: "[FEAT] " +--- + + +Make sure you've completed the following steps before submitting your issue -- thank you! + +1. Check if your feature has already been mentioned / rejected / planned in other issues. +2. If those resources didn't help, consider asking in the [Gitter chat room][] to see if this is interesting / useful to a larger audience and possible to implement reasonably, +4. If you have a useful feature that passes the previous items (or not suitable for chat), please fill in the details below. + +[Gitter chat room]: https://gitter.im/pybind/Lobby + +*After reading, remove this checklist.* diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000..b199b6ee8a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,21 @@ +--- +name: Question +about: File an issue about unexplained behavior +title: "[QUESTION] " +--- + +If you have a question, please check the following first: + +1. Check if your question has already been answered in the [FAQ][] section. +2. Make sure you've read the [documentation][]. Your issue may be addressed there. +3. If those resources didn't help and you only have a short question (not a bug report), consider asking in the [Gitter chat room][] +4. Search the [issue tracker][], including the closed issues, to see if your question has already been asked/answered. +1 or comment if it has been asked but has no answer. +5. If you have a more complex question which is not answered in the previous items (or not suitable for chat), please fill in the details below. +6. Include a self-contained and minimal piece of code that illustrates your question. If that's not possible, try to make the description as clear as possible. + +[FAQ]: http://pybind11.readthedocs.io/en/latest/faq.html +[documentation]: https://pybind11.readthedocs.io +[issue tracker]: https://github.com/pybind/pybind11/issues +[Gitter chat room]: https://gitter.im/pybind/Lobby + +*After reading, remove this checklist.* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9dd3bd6f6c..1ec1f1d532 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,39 +3,37 @@ sections on how to contribute code and bug reports. ### Reporting bugs -At the moment, this project is run in the spare time of a single person -([Wenzel Jakob](http://rgl.epfl.ch/people/wjakob)) with very limited resources -for issue tracker tickets. Thus, before submitting a question or bug report, -please take a moment of your time and ensure that your issue isn't already -discussed in the project documentation provided at -[http://pybind11.readthedocs.org/en/latest](http://pybind11.readthedocs.org/en/latest). +Before submitting a question or bug report, please take a moment of your time +and ensure that your issue isn't already discussed in the project documentation +provided at [pybind11.readthedocs.org][] or in the [issue tracker][]. You can +also check [gitter][] to see if it came up before. Assuming that you have identified a previously unknown problem or an important question, it's essential that you submit a self-contained and minimal piece of code that reproduces the problem. In other words: no external dependencies, isolate the function(s) that cause breakage, submit matched and complete C++ -and Python snippets that can be easily compiled and run on my end. +and Python snippets that can be easily compiled and run in isolation; or +ideally make a small PR with a failing test case that can be used as a starting +point. ## Pull requests -Contributions are submitted, reviewed, and accepted using Github pull requests. -Please refer to [this -article](https://help.github.com/articles/using-pull-requests) for details and -adhere to the following rules to make the process as smooth as possible: + +Contributions are submitted, reviewed, and accepted using GitHub pull requests. +Please refer to [this article][using pull requests] for details and adhere to +the following rules to make the process as smooth as possible: * Make a new branch for every feature you're working on. * Make small and clean pull requests that are easy to review but make sure they do add value by themselves. -* Add tests for any new functionality and run the test suite (``make pytest``) - to ensure that no existing features break. -* Please run [``pre-commit``][pre-commit] to check your code matches the - project style. (Note that ``gawk`` is required.) Use `pre-commit run +* Add tests for any new functionality and run the test suite (`cmake --build + build --target pytest`) to ensure that no existing features break. +* Please run [`pre-commit`][pre-commit] to check your code matches the + project style. (Note that `gawk` is required.) Use `pre-commit run --all-files` before committing (or use installed-mode, check pre-commit docs) to verify your code passes before pushing to save time. * This project has a strong focus on providing general solutions using a minimal amount of code, thus small pull requests are greatly preferred. -[pre-commit]: https://pre-commit.com - ### Licensing of contributions pybind11 is provided under a BSD-style license that can be found in the @@ -51,3 +49,120 @@ hereby grant the following license: a non-exclusive, royalty-free perpetual license to install, use, modify, prepare derivative works, incorporate into other computer software, distribute, and sublicense such enhancements or derivative works thereof, in binary and source code form. + + +## Development of pybind11 + +To setup an ideal development environment, run the following commands on a +system with CMake 3.14+: + +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r tests/requirements.txt +cmake -S . -B build -DPYTHON_EXECUTABLE=$(which python) -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON +cmake --build build -j4 +``` + +Tips: + +* You can use `virtualenv` (from PyPI) instead of `venv` (which is Python 3 + only). +* 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` instead of setting the + `PYTHON_EXECUTABLE` - the new search algorithm can find virtual environments, + Conda, and more. + +### Configuration options + +In CMake, configuration options are given with “-D”. Options are stored in the +build directory, in the `CMakeCache.txt` file, so they are remembered for each +build directory. Two selections are special - the generator, given with `-G`, +and the compiler, which is selected based on environment variables `CXX` and +similar, or `-DCMAKE_CXX_COMPILER=`. Unlike the others, these cannot be changed +after the initial run. + +The valid options are: + +* `-DCMAKE_BUILD_TYPE`: Release, Debug, MinSizeRel, RelWithDebInfo +* `-DPYBIND11_FINDPYTHON=ON`: Use CMake 3.12+’s FindPython instead of the + classic, deprecated, custom FindPythonLibs +* `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests) +* `-DBUILD_TESTING=ON`: Enable the tests +* `-DDOWNLOAD_CATCH=ON`: Download catch to build the C++ tests +* `-DOWNLOAD_EIGEN=ON`: Download Eigen for the NumPy tests +* `-DPYBIND11_INSTALL=ON/OFF`: Enable the install target (on by default for the + master project) +* `-DUSE_PYTHON_INSTALL_DIR=ON`: Try to install into the python dir + + +
A few standard CMake tricks: (click to expand)

+ +* Use `cmake --build build -v` to see the commands used to build the files. +* Use `cmake build -LH` to list the CMake options with help. +* Use `ccmake` if available to see a curses (terminal) gui, or `cmake-gui` for + a completely graphical interface (not present in the PyPI package). +* Use `-G` and the name of a generator to use something different, like `Ninja` + (automatic multithreading!). `cmake --help` lists the generators available. +* Open the `CMakeLists.txt` with QtCreator to generate for that IDE. +* Use `cmake --build build -j12` to build with 12 cores (for example). +* You can use `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate the `.json` file + that some tools expect. + +

+ + +To run the tests, you can "build" the check target: + +```bash +cmake --build build --target check +``` + +`--target` can be spelled `-t` in CMake 3.15+. You can also run individual +tests with these targets: + +* `pytest`: Python tests only +* `cpptest`: C++ tests only +* `test_cmake_build`: Install / subdirectory tests + +If you want to build just a subset of tests, use +`-DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_pickling.cpp"`. If this is +empty, all tests will be built. + +### Formatting + +All formatting is handled by pre-commit. + +Install with brew (macOS) or pip (any OS): + +```bash +# Any OS +python3 -m pip install pre-commit + +# OR macOS with homebrew: +brew install pre-commit +``` + +Then, you can run it on the items you've added to your staging area, or all +files: + +```bash +pre-commit run +# OR +pre-commit run --all-files +``` + +And, if you want to always use it, you can install it as a git hook (hence the +name, pre-commit): + +```bash +pre-commit install +``` + +[pre-commit]: https://pre-commit.com +[pybind11.readthedocs.org]: http://pybind11.readthedocs.org/en/latest +[issue tracker]: https://github.com/pybind/pybind11/issues +[gitter]: https://gitter.im/pybind/Lobby +[using pull requests]: https://help.github.com/articles/using-pull-requests diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 75df39981a..0000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,17 +0,0 @@ -Make sure you've completed the following steps before submitting your issue -- thank you! - -1. Check if your question has already been answered in the [FAQ](http://pybind11.readthedocs.io/en/latest/faq.html) section. -2. Make sure you've read the [documentation](http://pybind11.readthedocs.io/en/latest/). Your issue may be addressed there. -3. If those resources didn't help and you only have a short question (not a bug report), consider asking in the [Gitter chat room](https://gitter.im/pybind/Lobby). -4. If you have a genuine bug report or a more complex question which is not answered in the previous items (or not suitable for chat), please fill in the details below. -5. Include a self-contained and minimal piece of code that reproduces the problem. If that's not possible, try to make the description as clear as possible. - -*After reading, remove this checklist and the template text in parentheses below.* - -## Issue description - -(Provide a short description, state the expected behavior and what actually happens.) - -## Reproducible example code - -(The code should be minimal, have no external dependencies, isolate the function(s) that cause breakage. Submit matched and complete C++ and Python snippets that can be easily compiled and run to diagnose the issue.) diff --git a/LICENSE b/LICENSE index 6f15578cc4..e466b0dfda 100644 --- a/LICENSE +++ b/LICENSE @@ -25,5 +25,5 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Please also refer to the file CONTRIBUTING.md, which clarifies licensing of +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of external contributions to this project including patches, pull requests, etc. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2a077c6eb5..9a97ec5bdc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -84,7 +84,7 @@ set(PYBIND11_TEST_FILES test_virtual_functions.cpp) # Invoking cmake with something like: -# cmake -DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_picking.cpp" .. +# cmake -DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_pickling.cpp" .. # lets you override the tests that get compiled and run. You can restore to all tests with: # cmake -DPYBIND11_TEST_OVERRIDE= .. if(PYBIND11_TEST_OVERRIDE) From 7dd2bdb0b3374f14817b4625f26e865d9825c679 Mon Sep 17 00:00:00 2001 From: Mosalam Ebrahimi Date: Tue, 18 Aug 2020 03:46:23 -0700 Subject: [PATCH 023/295] docs: fix typo (#2405) --- docs/classes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/classes.rst b/docs/classes.rst index a63f6a1969..1d44a5931d 100644 --- a/docs/classes.rst +++ b/docs/classes.rst @@ -74,7 +74,7 @@ Note how ``print(p)`` produced a rather useless summary of our data structure in >>> print(p) -To address this, we could bind an utility function that returns a human-readable +To address this, we could bind a utility function that returns a human-readable summary to the special method slot named ``__repr__``. Unfortunately, there is no suitable functionality in the ``Pet`` data structure, and it would be nice if we did not have to change it. This can easily be accomplished by binding a From cf0a64596ea067f244deb49fa40c402b176a7176 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 18 Aug 2020 07:14:34 -0400 Subject: [PATCH 024/295] fix: throwing repr caused a segfault (#2389) * fix: throwing repr caused a segfault * fixup! ci: include Python 3.9 RC1 (#2387) --- include/pybind11/pybind11.h | 13 +++++++++++-- tests/test_exceptions.cpp | 3 +++ tests/test_exceptions.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index d34c92c24a..3a7d7b8849 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -776,7 +776,11 @@ class cpp_function : public function { for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) { if (!some_args) some_args = true; else msg += ", "; - msg += pybind11::repr(args_[ti]); + try { + msg += pybind11::repr(args_[ti]); + } catch (const error_already_set&) { + msg += ""; + } } if (kwargs_in) { auto kwargs = reinterpret_borrow(kwargs_in); @@ -787,7 +791,12 @@ class cpp_function : public function { for (auto kwarg : kwargs) { if (first) first = false; else msg += ", "; - msg += pybind11::str("{}={!r}").format(kwarg.first, kwarg.second); + msg += pybind11::str("{}=").format(kwarg.first); + try { + msg += pybind11::repr(kwarg.second); + } catch (const error_already_set&) { + msg += ""; + } } } } diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index 372d0aebf4..537819d987 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -218,4 +218,7 @@ TEST_SUBMODULE(exceptions, m) { } }); + // Test repr that cannot be displayed + m.def("simple_bool_passthrough", [](bool x) {return x;}); + } diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 83a46bfc8a..7d7088d00b 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -178,3 +178,14 @@ def pycatch(exctype, f, *args): with pytest.raises(m.MyException5) as excinfo: m.try_catch(m.MyException, pycatch, m.MyException, m.throws5) assert str(excinfo.value) == "this is a helper-defined translated exception" + + +# This can often happen if you wrap a pybind11 class in a Python wrapper +def test_invalid_repr(): + + class MyRepr(object): + def __repr__(self): + raise AttributeError("Example error") + + with pytest.raises(TypeError): + m.simple_bool_passthrough(MyRepr()) From 69821d9e755b4bc16f644067272a5399a5afcc4c Mon Sep 17 00:00:00 2001 From: Paul Fultz II Date: Tue, 18 Aug 2020 07:34:18 -0500 Subject: [PATCH 025/295] Disable testing when using BUILD_TESTING (#1682) --- CMakeLists.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3571e33e62..f38a8f89b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,6 +272,13 @@ if(PYBIND11_INSTALL) endif() endif() -if(PYBIND11_TEST OR (BUILD_TESTING AND PYBIND11_MASTER_PROJECT)) - add_subdirectory(tests) +# BUILD_TESTING takes priority, but only if this is the master project +if(PYBIND11_MASTER_PROJECT AND DEFINED BUILD_TESTING) + if(BUILD_TESTING) + add_subdirectory(tests) + endif() +else() + if(PYBIND11_TEST) + add_subdirectory(tests) + endif() endif() From 1729aae96fe5da9b978fa6f448c6bb8f56da6b94 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 19 Aug 2020 12:26:26 -0400 Subject: [PATCH 026/295] feat: new FindPython support (#2370) * feat: FindPython support * refactor: rename to PYBIND11_FINDPYTHON * docs: Caps fixes * feat: NOPYTHON mode * test: check simple call * docs: add changelog/upgrade guide * feat: Support Python3 and Python2 * refactor: Use targets in tests * fix: support CMake 3.4+ * feat: classic search also finds virtual environments * docs: some updates from @wjakob's review * fix: wrong name for QUIET mode variable, reported by @skoslowski * refactor: cleaner output messaging * fix: support debug Python's in FindPython mode too * fixup! refactor: cleaner output messaging * fix: missing pybind11_FOUND and pybind11_INCLUDE_DIR restored to subdir mode * fix: nicer reporting of Python / PyPy * fix: out-of-order variable fix * docs: minor last-minute cleanup --- .github/workflows/ci.yml | 24 +- .github/workflows/configure.yml | 25 +- .gitignore | 2 +- CMakeLists.txt | 151 ++++----- docs/advanced/embedding.rst | 2 +- docs/changelog.rst | 33 ++ docs/compiling.rst | 159 +++++++--- docs/requirements.txt | 4 +- docs/upgrade.rst | 47 +++ tests/CMakeLists.txt | 44 ++- tests/test_cmake_build/CMakeLists.txt | 29 +- .../installed_embed/CMakeLists.txt | 4 +- .../installed_function/CMakeLists.txt | 15 +- .../installed_target/CMakeLists.txt | 19 +- .../subdirectory_embed/CMakeLists.txt | 4 +- .../subdirectory_function/CMakeLists.txt | 16 +- .../subdirectory_target/CMakeLists.txt | 19 +- tests/test_embed/CMakeLists.txt | 11 +- tools/FindCatch.cmake | 3 + tools/FindPythonLibsNew.cmake | 43 ++- tools/pybind11Common.cmake | 296 ++++++++++++++++++ tools/pybind11Config.cmake.in | 117 ++++--- tools/pybind11NewTools.cmake | 203 ++++++++++++ tools/pybind11Tools.cmake | 238 ++++++-------- 24 files changed, 1115 insertions(+), 393 deletions(-) create mode 100644 tools/pybind11Common.cmake create mode 100644 tools/pybind11NewTools.cmake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30829631d5..4f9ebac9f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,11 +31,13 @@ jobs: arch: x64 max-cxx-std: 17 dev: false + args: "-DPYBIND11_FINDPYTHON=ON" - runs-on: macos-latest python: 3.7 arch: x64 max-cxx-std: 17 dev: false + args: "-DPYBIND11_FINDPYTHON=ON" - runs-on: windows-2016 python: 3.7 arch: x86 @@ -46,6 +48,7 @@ jobs: arch: x64 max-cxx-std: 17 dev: false + args: "-DPYBIND11_FINDPYTHON=ON" - runs-on: windows-latest python: 3.7 arch: x64 @@ -89,7 +92,7 @@ jobs: dev: false - name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • ${{ matrix.arch }}" + name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • ${{ matrix.arch }} ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} continue-on-error: ${{ matrix.dev }} @@ -106,6 +109,9 @@ jobs: if: runner.os != 'macOS' run: echo "::set-env name=BOOST_ROOT::$BOOST_ROOT_1_72_0" + - name: Update CMake + uses: jwlawson/actions-setup-cmake@v1.3 + - name: Cache wheels if: runner.os == 'macOS' uses: actions/cache@v2 @@ -120,7 +126,7 @@ jobs: - name: Prepare env run: python -m pip install -r tests/requirements.txt - - name: Configure C++11 + - name: Configure C++11 ${{ matrix.args }} shell: bash run: > cmake -S . -B build @@ -128,7 +134,7 @@ jobs: -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") + ${{ matrix.args }} - name: Build C++11 run: cmake --build build -j 2 @@ -140,9 +146,9 @@ jobs: run: cmake --build build --target cpptest -j 2 - name: Interface test C++11 - run: cmake --build build --target test_cmake_build + run: cmake --build build --target test_cmake_build -v - - name: Configure C++${{ matrix.max-cxx-std }} + - name: Configure C++${{ matrix.max-cxx-std }} ${{ matrix.args }} shell: bash run: > cmake -S . -B build2 @@ -150,7 +156,7 @@ jobs: -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=${{ matrix.max-cxx-std }} - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") + ${{ matrix.args }} - name: Build C++${{ matrix.max-cxx-std }} run: cmake --build build2 -j 2 @@ -350,14 +356,14 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install requirements + - name: Install system requirements run: apk add doxygen python3-dev - name: Ensure pip run: python3 -m ensurepip - - name: Install python docs requirements - run: python3 -m pip install "sphinx<3" sphinx_rtd_theme breathe==4.13.1 pytest setuptools + - name: Install docs & setup requirements + run: python3 -m pip install -r docs/requirements.txt pytest setuptools - name: Build docs run: python3 -m sphinx -W -b html docs docs/.build diff --git a/.github/workflows/configure.yml b/.github/workflows/configure.yml index 934b2d73ba..d472f4b191 100644 --- a/.github/workflows/configure.yml +++ b/.github/workflows/configure.yml @@ -14,19 +14,22 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-latest, macos-latest] + runs-on: [ubuntu-latest, macos-latest, windows-latest] arch: [x64] - cmake: [3.7, 3.18] + cmake: [3.18] include: - - runs-on: windows-latest + - runs-on: ubuntu-latest arch: x64 - cmake: 3.18 + cmake: 3.4 + + - runs-on: macos-latest + arch: x64 + cmake: 3.7 - # TODO: 3.8 - runs-on: windows-2016 arch: x86 - cmake: 3.11 + cmake: 3.8 - runs-on: windows-2016 arch: x86 @@ -63,3 +66,13 @@ jobs: -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") + + - name: Build + working-directory: build dir + if: github.event_name == 'workflow_dispatch' + run: cmake --build . --config Release + + - name: Test + working-directory: build dir + if: github.event_name == 'workflow_dispatch' + run: cmake --build . --config Release --target check diff --git a/.gitignore b/.gitignore index 54a1f92601..6d65838b50 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,7 @@ MANIFEST .*.swp .DS_Store /dist -/build +/build* .cache/ sosize-*.txt pybind11Config*.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f38a8f89b4..427975258a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,9 @@ # 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.7) +cmake_minimum_required(VERSION 3.4) -# The `cmake_minimum_required(VERSION 3.7...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.4...3.18)` 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.18) @@ -41,11 +41,22 @@ include(GNUInstallDirs) include(CMakePackageConfigHelpers) include(CMakeDependentOption) -message(STATUS "pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}") +if(NOT pybind11_FIND_QUIETLY) + message(STATUS "pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}") +endif() # Check if pybind11 is being used directly or via add_subdirectory if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_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) @@ -60,13 +71,16 @@ endif() # Options option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) -option(PYBIND11_CLASSIC_LTO "Use the classic LTO flag algorithm, even on CMake 3.9+" OFF) +option(PYBIND11_NOPYTHON "Disable search for Python" OFF) cmake_dependent_option( USE_PYTHON_INCLUDE_DIR "Install pybind11 headers in Python include directory instead of default installation prefix" OFF "PYBIND11_INSTALL" OFF) +cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" OFF + "NOT CMAKE_VERSION VERSION_LESS 3.12" OFF) + # NB: when adding a header don't forget to also add it to setup.py set(PYBIND11_HEADERS include/pybind11/detail/class.h @@ -118,101 +132,41 @@ endif() string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/" PYBIND11_HEADERS "${PYBIND11_HEADERS}") -# Classic mode - -include("${CMAKE_CURRENT_LIST_DIR}/tools/pybind11Tools.cmake") - # Cache variables so pybind11_add_module can be used in parent projects set(PYBIND11_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include" CACHE INTERNAL "") -set(PYTHON_INCLUDE_DIRS - ${PYTHON_INCLUDE_DIRS} - CACHE INTERNAL "") -set(PYTHON_LIBRARIES - ${PYTHON_LIBRARIES} - CACHE INTERNAL "") -set(PYTHON_MODULE_PREFIX - ${PYTHON_MODULE_PREFIX} - CACHE INTERNAL "") -set(PYTHON_MODULE_EXTENSION - ${PYTHON_MODULE_EXTENSION} - CACHE INTERNAL "") -set(PYTHON_VERSION_MAJOR - ${PYTHON_VERSION_MAJOR} - CACHE INTERNAL "") -set(PYTHON_VERSION_MINOR - ${PYTHON_VERSION_MINOR} - CACHE INTERNAL "") -set(PYTHON_IS_DEBUG - "${PYTHON_IS_DEBUG}" - CACHE INTERNAL "") - -if(USE_PYTHON_INCLUDE_DIR) - file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${PYTHON_INCLUDE_DIRS}) -endif() # Note: when creating targets, you cannot use if statements at configure time - # you need generator expressions, because those will be placed in the target file. # You can also place ifs *in* the Config.in, but not here. -# Build an interface library target: -add_library(pybind11 INTERFACE) -add_library(pybind11::pybind11 ALIAS pybind11) # to match exported target - -target_include_directories( - pybind11 ${pybind11_system} INTERFACE $ - $) -# Only add Python for build - must be added during the import for config since it has to be re-discovered. -target_include_directories(pybind11 SYSTEM INTERFACE $) - -if(CMAKE_VERSION VERSION_LESS 3.13) - target_compile_features(pybind11 INTERFACE cxx_inheriting_constructors cxx_user_literals - cxx_right_angle_brackets) -else() - # This was added in CMake 3.8, but we are keeping a consistent breaking - # point for the config file at 3.13. A config generated by CMake 3.13+ - # can only be read in 3.13+ due to the SHELL usage later, so this is safe to do. - target_compile_features(pybind11 INTERFACE cxx_std_11) -endif() +# This section builds targets, but does *not* touch Python -add_library(module INTERFACE) -add_library(pybind11::module ALIAS module) +# Build the headers-only target (no Python included): +add_library(headers INTERFACE) +add_library(pybind11::headers ALIAS headers) # to match exported target -target_link_libraries(module INTERFACE pybind11::pybind11) +include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake") -# See https://github.com/Kitware/CMake/blob/master/Modules/CMakePlatformId.h.in for platform IDs -# Note: CMake 3.15 allows $ -target_link_libraries( - module - INTERFACE - "$<$,$>:$>") +if(NOT PYBIND11_MASTER_PROJECT AND NOT pybind11_FIND_QUIETLY) + message(STATUS "Using pybind11: (version \"${pybind11_VERSION}\" ${pybind11_VERSION_TYPE})") +endif() -if(CMAKE_VERSION VERSION_LESS 3.13) - target_link_libraries(module INTERFACE "$<$:-undefined dynamic_lookup>") -else() - # SHELL (3.12+) forces this to remain together, and 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. - target_link_options(module INTERFACE "$<$:SHELL:-undefined dynamic_lookup>") +# Relative directory setting +if(USE_PYTHON_INCLUDE_DIR AND DEFINED Python_INCLUDE_DIRS) + file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${Python_INCLUDE_DIRS}) +elseif(USE_PYTHON_INCLUDE_DIR AND DEFINED PYTHON_INCLUDE_DIR) + file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${PYTHON_INCLUDE_DIRS}) endif() -# Workaround for Python 2.7 and C++17 (C++14 as a warning) incompatibility -# This adds the flags -Wno-register and -Wno-deprecated-register if the compiler -# is Clang 3.9+ or AppleClang and the compile language is CXX, or /wd5033 for MSVC (all languages, -# since MSVC didn't recognize COMPILE_LANGUAGE until CMake 3.11+). -set(clang_4plus - "$,$,3.9>>>") -set(no_register "$>") -set(cxx_no_register "$,${no_register}>") -set(msvc "$") -target_compile_options( - pybind11 INTERFACE "$<${cxx_no_register}:-Wno-register;-Wno-deprecated-register>" - "$<${msvc}:/wd5033>") - -add_library(embed INTERFACE) -add_library(pybind11::embed ALIAS embed) -target_link_libraries(embed INTERFACE pybind11::pybind11 $) +# Fill in headers target +target_include_directories( + headers ${pybind11_system} INTERFACE $ + $) + +target_compile_features(headers INTERFACE cxx_inheriting_constructors cxx_user_literals + cxx_right_angle_brackets) if(PYBIND11_INSTALL) install(DIRECTORY ${PYBIND11_INCLUDE_DIR}/pybind11 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) @@ -248,14 +202,17 @@ if(PYBIND11_INSTALL) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake - tools/FindPythonLibsNew.cmake tools/pybind11Tools.cmake + tools/FindPythonLibsNew.cmake + tools/pybind11Common.cmake + tools/pybind11Tools.cmake + tools/pybind11NewTools.cmake DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) if(NOT PYBIND11_EXPORT_NAME) set(PYBIND11_EXPORT_NAME "${PROJECT_NAME}Targets") endif() - install(TARGETS pybind11 module embed EXPORT "${PYBIND11_EXPORT_NAME}") + install(TARGETS headers EXPORT "${PYBIND11_EXPORT_NAME}") install( EXPORT "${PYBIND11_EXPORT_NAME}" @@ -275,10 +232,28 @@ endif() # BUILD_TESTING takes priority, but only if this is the master project if(PYBIND11_MASTER_PROJECT AND DEFINED BUILD_TESTING) if(BUILD_TESTING) - add_subdirectory(tests) + if(_pybind11_nopython) + message(FATAL_ERROR "Cannot activate tests in NOPYTHON mode") + else() + add_subdirectory(tests) + endif() endif() else() if(PYBIND11_TEST) - add_subdirectory(tests) + if(_pybind11_nopython) + message(FATAL_ERROR "Cannot activate tests in NOPYTHON mode") + else() + add_subdirectory(tests) + endif() endif() endif() + +# Better symmetry with find_package(pybind11 CONFIG) mode. +if(NOT PYBIND11_MASTER_PROJECT) + set(pybind11_FOUND + TRUE + CACHE INTERNAL "true if pybind11 and all required components found on the system") + set(pybind11_INCLUDE_DIR + "${PYBIND11_INCLUDE_DIR}" + CACHE INTERNAL "Directory where pybind11 headers are located") +endif() diff --git a/docs/advanced/embedding.rst b/docs/advanced/embedding.rst index 3930316032..98a5c52190 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.0) + cmake_minimum_required(VERSION 3.4) project(example) find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)` diff --git a/docs/changelog.rst b/docs/changelog.rst index 2def2b0719..fbb3667f27 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,39 @@ Changelog Starting with version 1.8.0, pybind11 releases use a `semantic versioning `_ policy. +v2.6.0 (IN PROGRESS) +-------------------- + +See :ref:`upgrade-guide-2.6` for help upgrading to the new version. + +* Minimum CMake required increased to 3.4. + `#2338 `_ and + `#2370 `_ + + * Full integration with CMake’s C++ standard system replaces + ``PYBIND11_CPP_STANDARD``. + + * Generated config file is now portable to different Python/compiler/CMake + versions. + + * Virtual environments prioritized if ``PYTHON_EXECUTABLE`` is not set + (``venv``, ``virtualenv``, and ``conda``) (similar to the new FindPython + mode). + + * Other CMake features now natively supported, like + ``CMAKE_INTERPROCEDURAL_OPTIMIZATION``, ``set(CMAKE_CXX_VISIBILITY_PRESET + hidden)``. + +* Optional :ref:`find-python-mode` and :ref:`nopython-mode` with CMake. + `#2370 `_ + +* Uninstall target added. + `#2265 `_ and + `#2346 `_ + + + + v2.5.0 (Mar 31, 2020) ----------------------------------------------------- diff --git a/docs/compiling.rst b/docs/compiling.rst index 3935dda134..72b0c1eecf 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -33,8 +33,8 @@ extension module can be created with just a few lines of code: .. code-block:: cmake - cmake_minimum_required(VERSION 3.7) - project(example) + cmake_minimum_required(VERSION 3.4...3.18) + project(example LANGUAGES CXX) add_subdirectory(pybind11) pybind11_add_module(example example.cpp) @@ -50,6 +50,9 @@ PyPI integration, can be found in the [cmake_example]_ repository. .. [cmake_example] https://github.com/pybind/cmake_example +.. versionchanged:: 2.6 + CMake 3.4+ is required. + pybind11_add_module ------------------- @@ -89,7 +92,9 @@ will result in code bloat and is generally not recommended. As stated above, LTO is enabled by default. Some newer compilers also support different flavors of LTO such as `ThinLTO`_. Setting ``THIN_LTO`` will cause the function to prefer this flavor if available. The function falls back to -regular LTO if ``-flto=thin`` is not available. +regular LTO if ``-flto=thin`` is not available. If +``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` is set (either ON or OFF), then that +will be respected instead of the built-in flag search. .. _ThinLTO: http://clang.llvm.org/docs/ThinLTO.html @@ -113,9 +118,9 @@ the ``-D=`` flag. You can also manually set ``CXX_STANDARD`` on a target or use ``target_compile_features`` on your targets - anything that CMake supports. -The target Python version can be selected by setting ``PYBIND11_PYTHON_VERSION`` -or an exact Python installation can be specified with ``PYTHON_EXECUTABLE``. -For example: +Classic Python support: The target Python version can be selected by setting +``PYBIND11_PYTHON_VERSION`` or an exact Python installation can be specified +with ``PYTHON_EXECUTABLE``. For example: .. code-block:: bash @@ -127,6 +132,7 @@ For example: # This often is a good way to get the current Python, works in environments: cmake -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") .. + find_package vs. add_subdirectory --------------------------------- @@ -136,8 +142,8 @@ See the `Config file`_ docstring for details of relevant CMake variables. .. code-block:: cmake - cmake_minimum_required(VERSION 3.7) - project(example) + cmake_minimum_required(VERSION 3.4...3.18) + project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) pybind11_add_module(example example.cpp) @@ -169,52 +175,131 @@ can refer to the same [cmake_example]_ repository for a full sample project .. _Config file: https://github.com/pybind/pybind11/blob/master/tools/pybind11Config.cmake.in -Advanced: interface library target ----------------------------------- -When using a version of CMake greater than 3.0, pybind11 can additionally -be used as a special *interface library* . The target ``pybind11::module`` -is available with pybind11 headers, Python headers and libraries as needed, -and C++ compile features attached. This target is suitable for linking -to an independently constructed (through ``add_library``, not -``pybind11_add_module``) target in the consuming project. +.. _find-python-mode: + +FindPython mode +--------------- + +CMake 3.12+ (3.15+ recommended) 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.7) - project(example) + cmake_minumum_required(VERSION 3.15...3.18) + project(example LANGUAGES CXX) + + find_package(Python COMPONENTS Interpreter Development REQUIRED) + find_package(pybind11 CONFIG REQUIRED) + # or add_subdirectory(pybind11) + + pybind11_add_module(example example.cpp) + +You can also use the targets (as listed below) with FindPython. If you define +``PYBIND11_FINDPYTHON``, pybind11 will perform the FindPython step for you +(mostly useful when building pybind11's own tests, or as a way to change search +algorithms from the CMake invocation, with ``-DPYBIND11_FINDPYTHON=ON``. + +.. warning:: + + If you use FindPython2 and FindPython3 to dual-target Python, use the + individual targets listed below, and avoid targets that directly include + Python parts. + +There are `many ways to hint or force a discovery of a specific Python +installation `_), +setting ``Python_ROOT_DIR`` may be the most common one (though with +virtualenv/venv support, and Conda support, this tends to find the correct +Python version more often than the old system did). + +.. versionadded:: 2.6 + +Advanced: interface library targets +----------------------------------- + +Pybind11 supports modern CMake usage patterns with a set of interface targets, +available in all modes. The targets provided are: + + ``pybind11::headers`` + Just the pybind11 headers and minimum compile requirements + + ``pybind11::python2_no_register`` + Quiets the warning/error when mixing C++14 or higher and Python 2 + + ``pybind11::pybind11`` + Python headers + ``pybind11::headers`` + ``pybind11::python2_no_register`` (Python 2 only) + + ``pybind11::python_link_helper`` + 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`` + + ``pybind11::embed`` + Everything for embedding the Python interpreter - ``pybind11::pybind11`` + ``Python::Embed`` (FindPython) or Python libs + + ``pybind11::lto`` / ``pybind11::thin_lto`` + An alternative to `INTERPROCEDURAL_OPTIMIZATION` for adding link-time optimization. + + ``pybind11::windows_extras`` + ``/bigobj`` and ``/mp`` for MSVC. + +Two helper functions are also provided: + + ``pybind11_strip(target)`` + Strips a target (uses ``CMAKE_STRIP`` after the target is built) + + ``pybind11_extension(target)`` + Sets the correct extension (with SOABI) for a target. + +You can use these targets to build complex applications. For example, the +``add_python_module`` function is identical to: + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.4) + project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) add_library(example MODULE main.cpp) - target_link_libraries(example PRIVATE pybind11::module) - set_target_properties(example PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}") + + target_link_libraries(example PRIVATE pybind11::module pybind11::lto pybind11::windows_extras) + + pybind11_extension(example) + pybind11_strip(example) + + set_target_properties(example PROPERTIES CXX_VISIBILITY_PRESET "hidden" + CUDA_VISIBILITY_PRESET "hidden") + +Instead of setting properties, you can set ``CMAKE_*`` variables to initialize these correctly. .. warning:: Since pybind11 is a metatemplate library, it is crucial that certain compiler flags are provided to ensure high quality code generation. In contrast to the ``pybind11_add_module()`` command, the CMake interface - library only provides the *minimal* set of parameters to ensure that the - code using pybind11 compiles, but it does **not** pass these extra compiler - flags (i.e. this is up to you). + provides a *composable* set of targets to ensure that you retain flexibility. + It can be expecially important to provide or set these properties; the + :ref:`FAQ ` contains an explanation on why these are needed. - These include Link Time Optimization (``-flto`` on GCC/Clang/ICPC, ``/GL`` - and ``/LTCG`` on Visual Studio) and .OBJ files with many sections on Visual - Studio (``/bigobj``). The :ref:`FAQ ` contains an - explanation on why these are needed. +.. versionadded:: 2.6 - If you want to add these in yourself, you can use: +.. _nopython-mode: - .. code-block:: cmake +Advanced: NOPYTHON mode +----------------------- - set(CMAKE_CXX_VISIBILITY_PRESET hidden) - set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # CMake 3.9+ required +If you want complete control, you can set ``PYBIND11_NOPYTHON`` to completely +disable Python integration (this also happens if you run ``FindPython2`` and +``FindPython3`` without running ``FindPython``). This gives you complete +freedom to integrate into an existing system (like `Scikit-Build's +`_ ``PythonExtensions``). +``pybind11_add_module`` and ``pybind11_extension`` will be unavailable, and the +targets will be missing any Python specific behavior. - or set the corresponding property (without the ``CMAKE_``) on the targets - manually. +.. versionadded:: 2.6 Embedding the Python interpreter -------------------------------- @@ -228,8 +313,8 @@ information about usage in C++, see :doc:`/advanced/embedding`. .. code-block:: cmake - cmake_minimum_required(VERSION 3.7) - project(example) + cmake_minimum_required(VERSION 3.4...3.18) + project(example LANGUAGES CXX) find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11) diff --git a/docs/requirements.txt b/docs/requirements.txt index 3818fe80ee..2eb7f2020a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,3 @@ -breathe == 4.5.0 +breathe==4.13.1 +sphinx<3 +sphinx_rtd_theme diff --git a/docs/upgrade.rst b/docs/upgrade.rst index 3f5697391b..98285eb6e8 100644 --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -8,6 +8,53 @@ to a new version. But it goes into more detail. This includes things like deprecated APIs and their replacements, build system changes, general code modernization and other useful information. +.. _upgrade-guide-2.6: + +v2.6 +==== + +CMake support: +-------------- + +The minimum required version of CMake is now 3.4. Several details of the CMake +support have been deprecated; warnings will be shown if you need to change +something. The changes are: + +* ``PYBIND11_CPP_STANDARD=`` is deprecated, please use + ``CMAKE_CXX_STANDARD=`` instead, or any other valid CMake CXX or CUDA + standard selection method, like ``target_compile_features``. + +* If you do not request a standard, PyBind11 targets will compile with the + compiler default, but not less than C++11, instead of forcing C++14 always. + If you depend on the old behavior, please use ``set(CMAKE_CXX_STANDARD 14)`` + instead. + +* Direct ``pybind11::module`` usage should always be accompanied by at least + ``set(CMAKE_CXX_VISIBILITY_PRESET hidden)`` or similar - it used to try to + manually force this compiler flag (but not correctly on all compilers or with + CUDA). + +* ``pybind11_add_module``'s ``SYSTEM`` argument is deprecated and does nothing; + linking now behaves like other imported libraries consistently in both + config and submodule mode, and behaves like a ``SYSTEM`` library by + default. + +* If ``PYTHON_EXECUTABLE`` is not set, virtual environments (``venv``, + ``virtualenv``, and ``conda``) are prioritized over the standard search + (similar to the new FindPython mode). + +In addition, the following changes may be of interest: + +* ``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` will be respected by + ``pybind11_add_module`` if set instead of linking to ``pybind11::lto`` or + ``pybind11::thin_lto``. + +* Using ``find_package(Python COMPONENTS Interpreter Development)`` before + pybind11 will cause pybind11 to use the new Python mechanisms instead of its + own custom search, based on a patched version of classic + FindPythonInterp/FindPythonLibs. In the future, this may become the default. + + v2.2 ==== diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9a97ec5bdc..895cfe75eb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,9 +5,9 @@ # 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.7) +cmake_minimum_required(VERSION 3.4) -# The `cmake_minimum_required(VERSION 3.7...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.4...3.18)` 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.18) @@ -16,6 +16,12 @@ else() cmake_policy(VERSION 3.18) endif() +# New Python support +if(DEFINED Python_EXECUTABLE) + set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") + set(PYTHON_VERSION "${Python_VERSION}") +endif() + # There's no harm in including a project in a project project(pybind11_tests CXX) @@ -137,13 +143,9 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) set(EIGEN3_INCLUDE_DIR ${eigen_SOURCE_DIR}) set(EIGEN3_FOUND TRUE) + else() find_package(Eigen3 3.2.7 QUIET CONFIG) - if(EIGEN3_FOUND) - if(EIGEN3_VERSION_STRING AND NOT EIGEN3_VERSION_STRING VERSION_LESS 3.3.1) - set(PYBIND11_EIGEN_VIA_TARGET TRUE) - endif() - endif() if(NOT EIGEN3_FOUND) # Couldn't load via target, so fall back to allowing module mode finding, which will pick up @@ -153,6 +155,12 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) endif() if(EIGEN3_FOUND) + if(NOT TARGET Eigen3::Eigen) + add_library(Eigen3::Eigen IMPORTED INTERFACE) + set_property(TARGET Eigen3::Eigen PROPERTY INTERFACE_INCLUDE_DIRECTORIES + "${EIGEN3_INCLUDE_DIR}") + endif() + # Eigen 3.3.1+ cmake sets EIGEN3_VERSION_STRING (and hard codes the version when installed # rather than looking it up in the cmake script); older versions, and the # tools/FindEigen3.cmake, set EIGEN3_VERSION instead. @@ -169,6 +177,20 @@ endif() # Optional dependency for some tests (boost::variant is only supported with version >= 1.56) find_package(Boost 1.56) +if(Boost_FOUND) + if(NOT TARGET Boost::headers) + if(TARGET Boost::boost) + # Classic FindBoost + add_library(Boost::headers ALIAS Boost::boost) + else() + # Very old FindBoost, or newer Boost than CMake in older CMakes + add_library(Boost::headers IMPORTED INTERFACE) + set_property(TARGET Boost::headers PROPERTY INTERFACE_INCLUDE_DIRECTORIES + ${Boost_INCLUDE_DIRS}) + endif() + endif() +endif() + # Compile with compiler warnings turned on function(pybind11_enable_warnings target_name) if(MSVC) @@ -233,16 +255,12 @@ foreach(target ${test_targets}) endif() if(EIGEN3_FOUND) - if(PYBIND11_EIGEN_VIA_TARGET) - target_link_libraries(${target} PRIVATE Eigen3::Eigen) - else() - target_include_directories(${target} SYSTEM PRIVATE ${EIGEN3_INCLUDE_DIR}) - endif() + target_link_libraries(${target} PRIVATE Eigen3::Eigen) target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_EIGEN) endif() if(Boost_FOUND) - target_include_directories(${target} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) + target_link_libraries(${target} PRIVATE Boost::headers) target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_BOOST) endif() diff --git a/tests/test_cmake_build/CMakeLists.txt b/tests/test_cmake_build/CMakeLists.txt index 0be071f2fc..0c0578ad3d 100644 --- a/tests/test_cmake_build/CMakeLists.txt +++ b/tests/test_cmake_build/CMakeLists.txt @@ -1,12 +1,24 @@ +# Built-in in CMake 3.5+ +include(CMakeParseArguments) + add_custom_target(test_cmake_build) function(pybind11_add_build_test name) - cmake_parse_arguments(PARSE_ARGV 1 ARG "INSTALL" "" "") + cmake_parse_arguments(ARG "INSTALL" "" "" ${ARGN}) + + set(build_options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") + + if(PYBIND11_FINDPYTHON) + list(APPEND build_options "-DPYBIND11_FINDPYTHON=${PYBIND11_FINDPYTHON}") - set(build_options - "-DCMAKE_PREFIX_PATH=${pybind11_BINARY_DIR}/mock_install" - "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE}") + if(DEFINED Python_ROOT_DIR) + list(APPEND build_options "-DPython_ROOT_DIR=${Python_ROOT_DIR}") + endif() + + list(APPEND build_options "-DPython_EXECUTABLE=${Python_EXECUTABLE}") + else() + list(APPEND build_options "-DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}") + endif() if(DEFINED CMAKE_CXX_STANDARD) list(APPEND build_options "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}") @@ -45,7 +57,9 @@ endfunction() pybind11_add_build_test(subdirectory_function) pybind11_add_build_test(subdirectory_target) -if(NOT ${PYTHON_MODULE_EXTENSION} MATCHES "pypy") +if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy") + message(STATUS "Skipping embed test on PyPy") +else() pybind11_add_build_test(subdirectory_embed) endif() @@ -56,7 +70,8 @@ if(PYBIND11_INSTALL) pybind11_add_build_test(installed_function INSTALL) pybind11_add_build_test(installed_target INSTALL) - if(NOT ${PYTHON_MODULE_EXTENSION} MATCHES "pypy") + if(NOT ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" + )) pybind11_add_build_test(installed_embed INSTALL) endif() endif() diff --git a/tests/test_cmake_build/installed_embed/CMakeLists.txt b/tests/test_cmake_build/installed_embed/CMakeLists.txt index 6b773c7c55..64ae5c4bff 100644 --- a/tests/test_cmake_build/installed_embed/CMakeLists.txt +++ b/tests/test_cmake_build/installed_embed/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.4) -# The `cmake_minimum_required(VERSION 3.7...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.4...3.18)` 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.18) diff --git a/tests/test_cmake_build/installed_function/CMakeLists.txt b/tests/test_cmake_build/installed_function/CMakeLists.txt index db8213c983..1a502863c0 100644 --- a/tests/test_cmake_build/installed_function/CMakeLists.txt +++ b/tests/test_cmake_build/installed_function/CMakeLists.txt @@ -1,6 +1,7 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.4) +project(test_installed_module CXX) -# The `cmake_minimum_required(VERSION 3.7...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.4...3.18)` 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.18) @@ -18,12 +19,20 @@ message( pybind11_add_module(test_installed_function SHARED NO_EXTRAS ../main.cpp) set_target_properties(test_installed_function PROPERTIES OUTPUT_NAME test_cmake_build) +if(DEFINED Python_EXECUTABLE) + set(_Python_EXECUTABLE "${Python_EXECUTABLE}") +elseif(DEFINED PYTHON_EXECUTABLE) + set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}") +else() + message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)") +endif() + add_custom_target( check_installed_function ${CMAKE_COMMAND} -E env PYTHONPATH=$ - ${PYTHON_EXECUTABLE} + ${_Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME}) diff --git a/tests/test_cmake_build/installed_target/CMakeLists.txt b/tests/test_cmake_build/installed_target/CMakeLists.txt index 1a124b9c27..b38eb77470 100644 --- a/tests/test_cmake_build/installed_target/CMakeLists.txt +++ b/tests/test_cmake_build/installed_target/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.4) -# The `cmake_minimum_required(VERSION 3.7...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.4...3.18)` 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.18) @@ -19,20 +19,27 @@ add_library(test_installed_target MODULE ../main.cpp) target_link_libraries(test_installed_target PRIVATE pybind11::module) set_target_properties(test_installed_target PROPERTIES OUTPUT_NAME test_cmake_build) -# make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib -set_target_properties(test_installed_target PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}") +# Make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib +pybind11_extension(test_installed_target) # Do not treat includes from IMPORTED target as SYSTEM (Python headers in pybind11::module). # This may be needed to resolve header conflicts, e.g. between Python release and debug headers. set_target_properties(test_installed_target PROPERTIES NO_SYSTEM_FROM_IMPORTED ON) +if(DEFINED Python_EXECUTABLE) + set(_Python_EXECUTABLE "${Python_EXECUTABLE}") +elseif(DEFINED PYTHON_EXECUTABLE) + set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}") +else() + message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)") +endif() + add_custom_target( check_installed_target ${CMAKE_COMMAND} -E env PYTHONPATH=$ - ${PYTHON_EXECUTABLE} + ${_Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME}) diff --git a/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt b/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt index 4571e87ff6..c7df0cf77c 100644 --- a/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.4) -# The `cmake_minimum_required(VERSION 3.7...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.4...3.18)` 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.18) diff --git a/tests/test_cmake_build/subdirectory_function/CMakeLists.txt b/tests/test_cmake_build/subdirectory_function/CMakeLists.txt index 697f881433..624c600f85 100644 --- a/tests/test_cmake_build/subdirectory_function/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_function/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.4) -# The `cmake_minimum_required(VERSION 3.7...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.4...3.18)` 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.18) @@ -12,15 +12,23 @@ endif() project(test_subdirectory_function CXX) add_subdirectory("${PYBIND11_PROJECT_DIR}" pybind11) -pybind11_add_module(test_subdirectory_function THIN_LTO ../main.cpp) +pybind11_add_module(test_subdirectory_function ../main.cpp) set_target_properties(test_subdirectory_function PROPERTIES OUTPUT_NAME test_cmake_build) +if(DEFINED Python_EXECUTABLE) + set(_Python_EXECUTABLE "${Python_EXECUTABLE}") +elseif(DEFINED PYTHON_EXECUTABLE) + set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}") +else() + message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)") +endif() + add_custom_target( check_subdirectory_function ${CMAKE_COMMAND} -E env PYTHONPATH=$ - ${PYTHON_EXECUTABLE} + ${_Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME}) diff --git a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt index 4f2312ee64..2471941fb6 100644 --- a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt @@ -1,6 +1,6 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.4) -# The `cmake_minimum_required(VERSION 3.7...3.18)` syntax does not work with +# The `cmake_minimum_required(VERSION 3.4...3.18)` 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.18) @@ -18,9 +18,16 @@ set_target_properties(test_subdirectory_target PROPERTIES OUTPUT_NAME test_cmake target_link_libraries(test_subdirectory_target PRIVATE pybind11::module) -# make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib -set_target_properties(test_subdirectory_target PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}") +# Make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib +pybind11_extension(test_subdirectory_target) + +if(DEFINED Python_EXECUTABLE) + set(_Python_EXECUTABLE "${Python_EXECUTABLE}") +elseif(DEFINED PYTHON_EXECUTABLE) + set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}") +else() + message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)") +endif() add_custom_target( check_subdirectory_target @@ -28,6 +35,6 @@ add_custom_target( -E env PYTHONPATH=$ - ${PYTHON_EXECUTABLE} + ${_Python_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME}) diff --git a/tests/test_embed/CMakeLists.txt b/tests/test_embed/CMakeLists.txt index c358d33220..1495c77753 100644 --- a/tests/test_embed/CMakeLists.txt +++ b/tests/test_embed/CMakeLists.txt @@ -1,10 +1,11 @@ -if(${PYTHON_MODULE_EXTENSION} MATCHES "pypy") +if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy") add_custom_target(cpptest) # Dummy target on PyPy. Embedding is not supported. set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}") return() endif() find_package(Catch 2.13.0) + if(CATCH_FOUND) message(STATUS "Building interpreter tests using Catch v${CATCH_VERSION}") else() @@ -13,14 +14,12 @@ else() return() endif() +find_package(Threads REQUIRED) + add_executable(test_embed catch.cpp test_interpreter.cpp) -target_include_directories(test_embed SYSTEM PRIVATE "${CATCH_INCLUDE_DIR}") pybind11_enable_warnings(test_embed) -target_link_libraries(test_embed PRIVATE pybind11::embed) - -find_package(Threads REQUIRED) -target_link_libraries(test_embed PUBLIC Threads::Threads) +target_link_libraries(test_embed PRIVATE pybind11::embed Catch2::Catch2 Threads::Threads) add_custom_target( cpptest diff --git a/tools/FindCatch.cmake b/tools/FindCatch.cmake index ade66c79e6..4d6bffcf68 100644 --- a/tools/FindCatch.cmake +++ b/tools/FindCatch.cmake @@ -64,4 +64,7 @@ if(NOT CATCH_VERSION OR CATCH_VERSION VERSION_LESS ${Catch_FIND_VERSION}) endif() endif() +add_library(Catch2::Catch2 IMPORTED INTERFACE) +set_property(TARGET Catch2::Catch2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CATCH_INCLUDE_DIR}") + set(CATCH_FOUND TRUE) diff --git a/tools/FindPythonLibsNew.cmake b/tools/FindPythonLibsNew.cmake index 822ec7718a..c1c72c763c 100644 --- a/tools/FindPythonLibsNew.cmake +++ b/tools/FindPythonLibsNew.cmake @@ -55,15 +55,46 @@ if(PYTHONLIBS_FOUND AND PYTHON_MODULE_EXTENSION) return() endif() +if(PythonLibsNew_FIND_QUIETLY) + set(_pythonlibs_quiet QUIET) +endif() + +if(PythonLibsNew_FIND_REQUIRED) + set(_pythonlibs_required REQUIRED) +endif() + +# Check to see if the `python` command is present and from a virtual +# environment, conda, or GHA activation - if it is, try to use that. + +if(NOT DEFINED PYTHON_EXECUTABLE) + if(DEFINED ENV{VIRTUAL_ENV}) + find_program( + PYTHON_EXECUTABLE python + PATHS "$ENV{VIRTUAL_ENV}" "$ENV{VIRTUAL_ENV}/bin" + NO_DEFAULT_PATH) + elseif(DEFINED ENV{CONDA_PREFIX}) + find_program( + PYTHON_EXECUTABLE python + PATHS "$ENV{CONDA_PREFIX}" "$ENV{CONDA_PREFIX}/bin" + NO_DEFAULT_PATH) + elseif(DEFINED ENV{pythonLocation}) + find_program( + PYTHON_EXECUTABLE python + PATHS "$ENV{pythonLocation}" "$ENV{pythonLocation}/bin" + NO_DEFAULT_PATH) + endif() + if(NOT PYTHON_EXECUTABLE) + unset(PYTHON_EXECUTABLE) + endif() +endif() + # Use the Python interpreter to find the libs. if(NOT PythonLibsNew_FIND_VERSION) set(PythonLibsNew_FIND_VERSION "") endif() -if(PythonLibsNew_FIND_REQUIRED) - find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} REQUIRED) -else() - find_package(PythonInterp ${PythonLibsNew_FIND_VERSION}) -endif() + +find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} ${_pythonlibs_required} + ${_pythonlibs_quiet}) if(NOT PYTHONINTERP_FOUND) set(PYTHONLIBS_FOUND FALSE) @@ -71,7 +102,7 @@ if(NOT PYTHONINTERP_FOUND) return() endif() -# According to http://stackoverflow.com/questions/646518/python-how-to-detect-debug-interpreter +# According to https://stackoverflow.com/questions/646518/python-how-to-detect-debug-interpreter # testing whether sys has the gettotalrefcount function is a reliable, cross-platform # way to detect a CPython debug interpreter. # diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake new file mode 100644 index 0000000000..f05845179f --- /dev/null +++ b/tools/pybind11Common.cmake @@ -0,0 +1,296 @@ +#[======================================================[.rst + +Adds the following targets:: + + pybind11::pybind11 - link to headers and pybind11 + pybind11::module - Adds module links + pybind11::embed - Adds embed links + pybind11::lto - Link time optimizations (manual selection) + pybind11::thin_lto - Link time optimizations (manual selection) + pybind11::python_link_helper - Adds link to Python libraries + pybind11::python2_no_register - Avoid warning/error with Python 2 + C++14/7 + pybind11::windows_extras - MSVC bigobj and mp for building multithreaded + +Adds the following functions:: + + pybind11_strip(target) - strip target after building on linux/macOS + + +#]======================================================] + +# CMake 3.10 has an include_guard command, but we can't use that yet +if(TARGET pybind11::lto) + return() +endif() + +# If we are in subdirectory mode, all IMPORTED targets must be GLOBAL. If we +# are in CONFIG mode, they should be "normal" targets instead. +# In CMake 3.11+ you can promote a target to global after you create it, +# which might be simpler than this check. +get_property( + is_config + TARGET pybind11::headers + PROPERTY IMPORTED) +if(NOT is_config) + set(optional_global GLOBAL) +endif() + +# --------------------- Shared targets ---------------------------- + +# Build an interface library target: +add_library(pybind11::pybind11 IMPORTED INTERFACE ${optional_global}) +set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::headers) + +# Build a module target: +add_library(pybind11::module IMPORTED INTERFACE ${optional_global}) +set_property( + TARGET pybind11::module + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11) + +# Build an embed library target: +add_library(pybind11::embed IMPORTED INTERFACE ${optional_global}) +set_property( + TARGET pybind11::embed + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11) + +# ----------------------- no register ---------------------- + +# Workaround for Python 2.7 and C++17 (C++14 as a warning) incompatibility +# This adds the flags -Wno-register and -Wno-deprecated-register if the compiler +# is Clang 3.9+ or AppleClang and the compile language is CXX, or /wd5033 for MSVC (all languages, +# since MSVC didn't recognize COMPILE_LANGUAGE until CMake 3.11+). + +add_library(pybind11::python2_no_register INTERFACE IMPORTED ${optional_global}) +set(clang_4plus + "$,$,3.9>>>") +set(no_register "$>") + +if(MSVC AND CMAKE_VERSION VERSION_LESS 3.11) + set(cxx_no_register "${no_register}") +else() + set(cxx_no_register "$,${no_register}>") +endif() + +set(msvc "$") + +set_property( + TARGET pybind11::python2_no_register + PROPERTY INTERFACE_COMPILE_OPTIONS + "$<${cxx_no_register}:-Wno-register;-Wno-deprecated-register>" "$<${msvc}:/wd5033>") + +# --------------------------- 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 + 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. + set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-undefined,dynamic_lookup>") +endif() + +# ------------------------ Windows extras ------------------------- + +add_library(pybind11::windows_extras IMPORTED INTERFACE ${optional_global}) + +if(MSVC) + # /MP enables multithreaded builds (relevant when there are many files), /bigobj is + # needed for bigger binding projects due to the limit to 64k addressable sections + set_property( + TARGET pybind11::windows_extras + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS /bigobj) + + 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() +endif() + +# ----------------------- Legacy option -------------------------- + +# Warn or error if old variable name used +if(PYBIND11_CPP_STANDARD) + string(REGEX MATCH [[..$]] VAL "${PYBIND11_CPP_STANDARD}") + if(CMAKE_CXX_STANDARD) + if(NOT CMAKE_CXX_STANDARD STREQUAL VAL) + message(WARNING "CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} does not match " + "PYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}, " + "please remove PYBIND11_CPP_STANDARD from your cache") + endif() + else() + set(supported_standards 11 14 17 20) + if("${VAL}" IN_LIST supported_standards) + message(WARNING "USE -DCMAKE_CXX_STANDARD=${VAL} instead of PYBIND11_PYTHON_VERSION") + set(CMAKE_CXX_STANDARD + ${VAL} + CACHE STRING "From PYBIND11_CPP_STANDARD") + else() + message(FATAL_ERROR "PYBIND11_CPP_STANDARD should be replaced with CMAKE_CXX_STANDARD " + "(last two chars: ${VAL} not understood as a valid CXX std)") + endif() + endif() +endif() + +# --------------------- Python specifics ------------------------- + +# Check to see which Python mode we are in, new, old, or no python +if(PYBIND11_NOPYTHON) + set(_pybind11_nopython ON) +elseif( + PYBIND11_FINDPYTHON + OR Python_FOUND + OR Python2_FOUND + OR Python3_FOUND) + # New mode + include("${CMAKE_CURRENT_LIST_DIR}/pybind11NewTools.cmake") + +else() + + # Classic mode + include("${CMAKE_CURRENT_LIST_DIR}/pybind11Tools.cmake") + +endif() + +# --------------------- LTO ------------------------------- + +include(CheckCXXCompilerFlag) + +# Checks whether the given CXX/linker flags can compile and link a cxx file. +# cxxflags and linkerflags are lists of flags to use. The result variable is a +# unique variable name for each set of flags: the compilation result will be +# cached base on the result variable. If the flags work, sets them in +# cxxflags_out/linkerflags_out internal cache variables (in addition to +# ${result}). +function(_pybind11_return_if_cxx_and_linker_flags_work result cxxflags linkerflags cxxflags_out + linkerflags_out) + set(CMAKE_REQUIRED_LIBRARIES ${linkerflags}) + check_cxx_compiler_flag("${cxxflags}" ${result}) + if(${result}) + set(${cxxflags_out} + "${cxxflags}" + PARENT_SCOPE) + set(${linkerflags_out} + "${linkerflags}" + PARENT_SCOPE) + endif() +endfunction() + +function(_pybind11_generate_lto target prefer_thin_lto) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(cxx_append "") + set(linker_append "") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT APPLE) + # Clang Gold plugin does not support -Os; append -O3 to MinSizeRel builds to override it + set(linker_append ";$<$:-O3>") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(cxx_append ";-fno-fat-lto-objects") + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND prefer_thin_lto) + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_FLTO_THIN "-flto=thin${cxx_append}" "-flto=thin${linker_append}" + PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + endif() + + if(NOT HAS_FLTO_THIN) + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_FLTO "-flto${cxx_append}" "-flto${linker_append}" PYBIND11_LTO_CXX_FLAGS + PYBIND11_LTO_LINKER_FLAGS) + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel") + # Intel equivalent to LTO is called IPO + _pybind11_return_if_cxx_and_linker_flags_work(HAS_INTEL_IPO "-ipo" "-ipo" + PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + elseif(MSVC) + # cmake only interprets libraries as linker flags when they start with a - (otherwise it + # converts /LTCG to \LTCG as if it was a Windows path). Luckily MSVC supports passing flags + # with - instead of /, even if it is a bit non-standard: + _pybind11_return_if_cxx_and_linker_flags_work(HAS_MSVC_GL_LTCG "/GL" "-LTCG" + PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + endif() + + # Enable LTO flags if found, except for Debug builds + if(PYBIND11_LTO_CXX_FLAGS) + set(not_debug "$>") + set(cxx_lang "$") + if(MSVC AND CMAKE_VERSION VERSION_LESS 3.11) + set(genex "${not_debug}") + else() + set(genex "$") + endif() + set_property( + TARGET ${target} + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS "$<${genex}:${PYBIND11_LTO_CXX_FLAGS}>") + if(CMAKE_PROJECT_NAME STREQUAL "pybind11") + message(STATUS "${target} enabled") + endif() + else() + if(CMAKE_PROJECT_NAME STREQUAL "pybind11") + message(STATUS "${target} disabled (not supported by the compiler and/or linker)") + endif() + 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() + endif() +endfunction() + +add_library(pybind11::lto IMPORTED INTERFACE ${optional_global}) +_pybind11_generate_lto(pybind11::lto FALSE) + +add_library(pybind11::thin_lto IMPORTED INTERFACE ${optional_global}) +_pybind11_generate_lto(pybind11::thin_lto TRUE) + +# ---------------------- pybind11_strip ----------------------------- + +function(pybind11_strip target_name) + # Strip unnecessary sections of the binary on Linux/Mac OS + if(CMAKE_STRIP) + if(APPLE) + set(x_opt -x) + endif() + + add_custom_command( + TARGET ${target_name} + POST_BUILD + COMMAND ${CMAKE_STRIP} ${x_opt} $) + endif() +endfunction() diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index c86e0dea25..4f0500a5f4 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -8,6 +8,7 @@ This module sets the following variables in your project:: pybind11_FOUND - true if pybind11 and all required components found on the system pybind11_VERSION - pybind11 version in format Major.Minor.Release + pybind11_VERSION_TYPE - pybind11 version type (dev, release) pybind11_INCLUDE_DIRS - Directories where pybind11 and python headers are located. pybind11_INCLUDE_DIR - Directory where pybind11 headers are located. pybind11_DEFINITIONS - Definitions necessary to use pybind11, namely USING_pybind11. @@ -28,21 +29,61 @@ interface library targets:: Python headers, libraries (as needed by platform), and the C++ standard are attached to the target. + +Advanced targets are also supplied - these are primary for users building +complex applications, and they are available in all modes:: + + pybind11::headers - Just the pybind11 headers and minimum compile requirements + pybind11::pybind11 - Python headers too + pybind11::python_link_helper - Just the "linking" part of pybind11:module, for CMake < 3.15 + pybind11::python2_no_register - Quiets the warning/error when mixing C++14+ and Python 2, also included in pybind11::module + pybind11::thin_lto - An alternative to INTERPROCEDURAL_OPTIMIZATION + pybind11::lto - An alternative to INTERPROCEDURAL_OPTIMIZATION (also avoids thin LTO on clang) + pybind11::windows_extras - Adds bigobj and mp for MSVC + +Modes:: + +There are two modes provided; classic, which is built on the old Python +discovery packages in CMake, or the new FindPython mode, which uses FindPython +from 3.12+ forward (3.15+ _highly_ recommended). + +New FindPython mode:: + +To activate this mode, either call ``find_package(Python COMPONENTS Interpreter Development)`` +before finding this package, or set the ``PYBIND11_FINDPYTHON`` variable to ON. In this mode, +you can either use the basic targets, or use the FindPython tools:: + + find_package(Python COMPONENTS Interpreter Development) + find_package(pybind11 CONFIG) + + # pybind11 method: + pybind11_add_module(MyModule1 src1.cpp) + + # Python method: + Python_add_library(MyModule2 src2.cpp) + target_link_libraries(MyModule2 pybind11::headers) + set_target_properties(MyModule2 PROPERTIES + INTERPROCEDURAL_OPTIMIZATION ON + CXX__VISIBILITY_PRESET ON + VISIBLITY_INLINES_HIDDEN ON) + +If you build targets yourself, you may be interested in stripping the output +for reduced size; this is the one other feature that the helper function gives you. + Classic mode:: Set PythonLibsNew variables to influence python detection and CMAKE_CXX_STANDARD to influence standard setting. :: find_package(pybind11 CONFIG REQUIRED) - message(STATUS "Found pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}: ${pybind11_INCLUDE_DIRS}") # Create an extension module add_library(mylib MODULE main.cpp) - target_link_libraries(mylib pybind11::module) + target_link_libraries(mylib PUBLIC pybind11::module) # Or embed the Python interpreter into an executable add_executable(myexe main.cpp) - target_link_libraries(myexe pybind11::embed) + target_link_libraries(myexe PUBLIC pybind11::embed) Suggested usage:: @@ -59,8 +100,17 @@ The following variables can be set to guide the search for this package:: PATH - environment variable, set to bin directory of this package CMAKE_DISABLE_FIND_PACKAGE_pybind11 - CMake variable, disables find_package(pybind11) when not REQUIRED, perhaps to force internal build -#]=============================================================================] +Helper functions:: + + pybind11_add_module(...) - Add a library and setup all helpers + pybind11_strip(target) - Strip a target after building it (linux/macOS) + pybind11_extension(target) - Injects the Python extension name + +See ``pybind11Tools.cmake`` or ``pybind11NewTools.cmake`` for details on +``pybind11_add_module``. + +#]=============================================================================] @PACKAGE_INIT@ # Location of pybind11/pybind11.h @@ -72,50 +122,19 @@ set(pybind11_VERSION_TYPE "@pybind11_VERSION_TYPE@") check_required_components(pybind11) -include("${CMAKE_CURRENT_LIST_DIR}/pybind11Tools.cmake") - -#----------------------------------------------------------------------------- -# Don't include targets if this file is being picked up by another -# project which has already built this as a subproject -#----------------------------------------------------------------------------- -if(NOT TARGET pybind11::pybind11) - include("${CMAKE_CURRENT_LIST_DIR}/pybind11Targets.cmake") - - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") - find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED) - list(REMOVE_AT CMAKE_MODULE_PATH -1) - - set_property( - TARGET pybind11::pybind11 - APPEND - PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) - set_property( - TARGET pybind11::pybind11 - APPEND - PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) - - set_property( - TARGET pybind11::embed - APPEND - PROPERTY INTERFACE_LINK_LIBRARIES ${PYTHON_LIBRARIES}) - set_property( - TARGET pybind11::module - APPEND - PROPERTY - INTERFACE_LINK_LIBRARIES - "$<$,$>:$>" - ) +if(TARGET pybind11::python_link_helper) + # This has already been setup elsewhere, such as with a previous call or + # add_subdirectory + return() +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/pybind11Targets.cmake") - get_property( - _iid - TARGET pybind11::pybind11 - PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - get_property( - _ill - TARGET pybind11::module - PROPERTY INTERFACE_LINK_LIBRARIES) - set(pybind11_INCLUDE_DIRS ${_iid}) - set(pybind11_LIBRARIES ${_ico} ${_ill}) - - include("${CMAKE_CURRENT_LIST_DIR}/pybind11Tools.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/pybind11Common.cmake") + +if(NOT pybind11_FIND_QUIETLY) + message( + STATUS + "Found pybind11: ${pybind11_INCLUDE_DIR} (found version \"${pybind11_VERSION}\" ${pybind11_VERSION_TYPE})" + ) endif() diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake new file mode 100644 index 0000000000..2e7a9310f1 --- /dev/null +++ b/tools/pybind11NewTools.cmake @@ -0,0 +1,203 @@ +# tools/pybind11NewTools.cmake -- Build system for the pybind11 modules +# +# Copyright (c) 2020 Wenzel Jakob and Henry Schreiner +# +# All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +get_property( + is_config + TARGET pybind11::headers + PROPERTY IMPORTED) + +if(pybind11_FIND_QUIETLY) + set(_pybind11_quiet QUIET) +endif() + +if(CMAKE_VERSION VERSION_LESS 3.12) + message(FATAL_ERROR "You cannot use the new FindPython module with CMake < 3.12") +endif() + +if(NOT Python_FOUND + AND NOT Python3_FOUND + AND NOT Python2_FOUND) + if(NOT DEFINED Python_FIND_IMPLEMENTATIONS) + set(Python_FIND_IMPLEMENTATIONS CPython PyPy) + endif() + + # GitHub Actions like activation + if(NOT DEFINED Python_ROOT_DIR AND DEFINED ENV{pythonLocation}) + set(Python_ROOT_DIR "$ENV{pythonLocation}") + endif() + + find_package(Python REQUIRED COMPONENTS Interpreter Development ${_pybind11_quiet}) + + # If we are in submodule mode, export the Python targets to global targets. + # If this behavior is not desired, FindPython _before_ pybind11. + if(NOT is_config) + set_property(TARGET Python::Python PROPERTY IMPORTED_GLOBAL TRUE) + set_property(TARGET Python::Interpreter PROPERTY IMPORTED_GLOBAL TRUE) + if(TARGET Python::Module) + set_property(TARGET Python::Module PROPERTY IMPORTED_GLOBAL TRUE) + endif() + endif() +endif() + +if(Python_FOUND) + set(_Python + Python + CACHE INTERNAL "" FORCE) +elseif(Python3_FOUND AND NOT Python2_FOUND) + set(_Python + Python3 + CACHE INTERNAL "" FORCE) +elseif(Python2_FOUND AND NOT Python3_FOUND) + set(_Python + Python2 + CACHE INTERNAL "" FORCE) +else() + message(AUTHOR_WARNING "Python2 and Python3 both present, pybind11 in " + "PYBIND11_NOPYTHON mode (manually activate to silence warning)") + set(_pybind11_nopython ON) + return() +endif() + +if(PYBIND11_MASTER_PROJECT) + if(${_Python}_INTERPRETER_ID MATCHES "PyPy") + message(STATUS "PyPy ${${_Python}_PyPy_VERSION} (Py ${${_Python}_VERSION})") + else() + message(STATUS "${_Python} ${${_Python}_VERSION}") + endif() +endif() + +# Debug check - see https://stackoverflow.com/questions/646518/python-how-to-detect-debug-Interpreter +execute_process(COMMAND ${_Python}::Python -c "import sys; print(hasattr(sys, 'gettotalrefcount'))" + OUTPUT_VARIABLE PYTHON_IS_DEBUG) + +# Python debug libraries expose slightly different objects before 3.8 +# https://docs.python.org/3.6/c-api/intro.html#debugging-builds +# https://stackoverflow.com/questions/39161202/how-to-work-around-missing-pymodule-create2-in-amd64-win-python35-d-lib +if(PYTHON_IS_DEBUG) + set_property( + TARGET pybind::pybind11 + APPEND + PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) +endif() + +# Check on every access - since Python2 and Python3 could have been used - do nothing in that case. + +if(DEFINED ${_Python}_INCLUDE_DIRS) + set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_INCLUDE_DIRECTORIES $) +endif() + +if(DEFINED ${_Python}_VERSION AND ${_Python}_VERSION VERSION_LESS 3) + set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::python2_no_register) +endif() + +# In CMake 3.18+, you can find these separately, so include an if +if(TARGET ${_Python}::${_Python}) + set_property( + TARGET pybind11::embed + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES ${_Python}::${_Python}) +endif() + +# CMake 3.15+ has this +if(TARGET ${_Python}::Module) + set_property( + TARGET pybind11::module + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES ${_Python}::Module) +else() + set_property( + TARGET pybind11::module + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::python_link_helper) +endif() + +function(pybind11_add_module target_name) + cmake_parse_arguments(PARSE_ARGV 1 ARG "STATIC;SHARED;MODULE;THIN_LTO;NO_EXTRAS" "" "") + + if(ARG_ADD_LIBRARY_STATIC) + set(type STATIC) + elseif(ARG_ADD_LIBRARY_SHARED) + set(type SHARED) + else() + set(type MODULE) + endif() + + if("${_Python}" STREQUAL "Python") + python_add_library(${target_name} ${type} WITH_SOABI ${ARG_UNPARSED_ARGUMENTS}) + elseif("${_Python}" STREQUAL "Python3") + python3_add_library(${target_name} ${type} WITH_SOABI ${ARG_UNPARSED_ARGUMENTS}) + elseif("${_Python}" STREQUAL "Python2") + python2_add_library(${target_name} ${type} WITH_SOABI ${ARG_UNPARSED_ARGUMENTS}) + else() + message(FATAL_ERROR "Cannot detect FindPython version: ${_Python}") + endif() + + target_link_libraries(${target_name} PRIVATE pybind11::headers) + + if(type STREQUAL "MODULE") + target_link_libraries(${target_name} PRIVATE pybind11::module) + else() + target_link_libraries(${target_name} PRIVATE pybind11::embed) + endif() + + if(MSVC) + target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) + endif() + + if(DEFINED ${_Python}_VERSION AND ${_Python}_VERSION VERSION_LESS 3) + target_link_libraries(${target_name} PRIVATE pybind11::python2_no_register) + endif() + + set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden" + CUDA_VISIBILITY_PRESET "hidden") + + if(ARG_NO_EXTRAS) + return() + endif() + + if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) + if(ARG_THIN_LTO) + target_link_libraries(${target_name} PRIVATE pybind11::thin_lto) + else() + target_link_libraries(${target_name} PRIVATE pybind11::lto) + endif() + endif() + + if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) + # Strip unnecessary sections of the binary on Linux/Mac OS + pybind11_strip(${target_name}) + endif() + + if(MSVC) + target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) + endif() +endfunction() + +function(pybind11_extension name) + set_property(TARGET ${name} PROPERTY PREFIX "") + + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set_property(TARGET ${name} PROPERTY SUFFIX ".pyd") + endif() + + if(${_Python}_SOABI) + get_property( + suffix + TARGET ${name} + PROPERTY SUFFIX) + if(NOT suffix) + set(suffix "${CMAKE_SHARED_MODULE_SUFFIX}") + endif() + set_property(TARGET ${name} PROPERTY SUFFIX ".${${_Python}_SOABI}${suffix}") + endif() +endfunction() diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 6e666957d9..29885ae998 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -5,6 +5,13 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. +# Built-in in CMake 3.5+ +include(CMakeParseArguments) + +if(pybind11_FIND_QUIETLY) + set(_pybind11_quiet QUIET) +endif() + # Add a CMake parameter for choosing a desired Python version if(NOT PYBIND11_PYTHON_VERSION) set(PYBIND11_PYTHON_VERSION @@ -18,112 +25,91 @@ set(Python_ADDITIONAL_VERSIONS CACHE INTERNAL "") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") -find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED) +find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED ${_pybind11_quiet}) list(REMOVE_AT CMAKE_MODULE_PATH -1) -include(CheckCXXCompilerFlag) +# Cache variables so pybind11_add_module can be used in parent projects +set(PYTHON_INCLUDE_DIRS + ${PYTHON_INCLUDE_DIRS} + CACHE INTERNAL "") +set(PYTHON_LIBRARIES + ${PYTHON_LIBRARIES} + CACHE INTERNAL "") +set(PYTHON_MODULE_PREFIX + ${PYTHON_MODULE_PREFIX} + CACHE INTERNAL "") +set(PYTHON_MODULE_EXTENSION + ${PYTHON_MODULE_EXTENSION} + CACHE INTERNAL "") +set(PYTHON_VERSION_MAJOR + ${PYTHON_VERSION_MAJOR} + CACHE INTERNAL "") +set(PYTHON_VERSION_MINOR + ${PYTHON_VERSION_MINOR} + CACHE INTERNAL "") +set(PYTHON_VERSION + ${PYTHON_VERSION} + CACHE INTERNAL "") +set(PYTHON_IS_DEBUG + "${PYTHON_IS_DEBUG}" + CACHE INTERNAL "") -# Warn or error if old variable name used -if(PYBIND11_CPP_STANDARD) - string(REGEX MATCH [[..$]] VAL "${PYBIND11_CPP_STANDARD}") - if(CMAKE_CXX_STANDARD) - if(NOT CMAKE_CXX_STANDARD STREQUAL VAL) - message(WARNING "CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} does not match " - "PYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}, " - "please remove PYBIND11_CPP_STANDARD from your cache") +if(PYBIND11_MASTER_PROJECT) + if(PYTHON_MODULE_EXTENSION MATCHES "pypy") + if(NOT DEFINED PYPY_VERSION) + execute_process( + COMMAND ${PYTHON_EXECUTABLE} -c + [=[import sys; print(".".join(map(str, sys.pypy_version_info[:3])))]=] + OUTPUT_VARIABLE pypy_version) + set(PYPY_VERSION + ${pypy_version} + CACHE INTERNAL "") endif() + message(STATUS "PYPY ${PYPY_VERSION} (Py ${PYTHON_VERSION})") else() - set(supported_standards 11 14 17 20) - if("${VAL}" IN_LIST supported_standards) - message(WARNING "USE -DCMAKE_CXX_STANDARD=${VAL} instead of PYBIND11_PYTHON_VERSION") - set(CMAKE_CXX_STANDARD - ${VAL} - CACHE STRING "From PYBIND11_CPP_STANDARD") - else() - message(FATAL_ERROR "PYBIND11_CPP_STANDARD should be replaced with CMAKE_CXX_STANDARD " - "(last two chars: ${VAL} not understood as a valid CXX std)") - endif() + message(STATUS "PYTHON ${PYTHON_VERSION}") endif() endif() -# Checks whether the given CXX/linker flags can compile and link a cxx file. cxxflags and -# linkerflags are lists of flags to use. The result variable is a unique variable name for each set -# of flags: the compilation result will be cached base on the result variable. If the flags work, -# sets them in cxxflags_out/linkerflags_out internal cache variables (in addition to ${result}). -function(_pybind11_return_if_cxx_and_linker_flags_work result cxxflags linkerflags cxxflags_out - linkerflags_out) - set(CMAKE_REQUIRED_LIBRARIES ${linkerflags}) - check_cxx_compiler_flag("${cxxflags}" ${result}) - if(${result}) - set(${cxxflags_out} - "${cxxflags}" - CACHE INTERNAL "" FORCE) - set(${linkerflags_out} - "${linkerflags}" - CACHE INTERNAL "" FORCE) - endif() -endfunction() +# Only add Python for build - must be added during the import for config since it has to be re-discovered. +set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_INCLUDE_DIRECTORIES $) + +# Python debug libraries expose slightly different objects before 3.8 +# https://docs.python.org/3.6/c-api/intro.html#debugging-builds +# https://stackoverflow.com/questions/39161202/how-to-work-around-missing-pymodule-create2-in-amd64-win-python35-d-lib +if(PYTHON_IS_DEBUG) + set_property( + TARGET pybind::pybind11 + APPEND + PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) +endif() -# Internal: find the appropriate link time optimization flags for this compiler -function(_pybind11_add_lto_flags target_name prefer_thin_lto) - if(NOT DEFINED PYBIND11_LTO_CXX_FLAGS) - set(PYBIND11_LTO_CXX_FLAGS - "" - CACHE INTERNAL "") - set(PYBIND11_LTO_LINKER_FLAGS - "" - CACHE INTERNAL "") - - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(cxx_append "") - set(linker_append "") - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT APPLE) - # Clang Gold plugin does not support -Os; append -O3 to MinSizeRel builds to override it - set(linker_append ";$<$:-O3>") - elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") - set(cxx_append ";-fno-fat-lto-objects") - endif() - - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND prefer_thin_lto) - _pybind11_return_if_cxx_and_linker_flags_work( - HAS_FLTO_THIN "-flto=thin${cxx_append}" "-flto=thin${linker_append}" - PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) - endif() - - if(NOT HAS_FLTO_THIN) - _pybind11_return_if_cxx_and_linker_flags_work( - HAS_FLTO "-flto${cxx_append}" "-flto${linker_append}" PYBIND11_LTO_CXX_FLAGS - PYBIND11_LTO_LINKER_FLAGS) - endif() - elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel") - # Intel equivalent to LTO is called IPO - _pybind11_return_if_cxx_and_linker_flags_work( - HAS_INTEL_IPO "-ipo" "-ipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) - elseif(MSVC) - # cmake only interprets libraries as linker flags when they start with a - (otherwise it - # converts /LTCG to \LTCG as if it was a Windows path). Luckily MSVC supports passing flags - # with - instead of /, even if it is a bit non-standard: - _pybind11_return_if_cxx_and_linker_flags_work( - HAS_MSVC_GL_LTCG "/GL" "-LTCG" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) - endif() +set_property( + TARGET pybind11::module + APPEND + PROPERTY + INTERFACE_LINK_LIBRARIES pybind11::python_link_helper + "$<$,$>:$>") + +if(PYTHON_VERSION VERSION_LESS 3) + set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::python2_no_register) +endif() - if(PYBIND11_LTO_CXX_FLAGS) - message(STATUS "LTO enabled") - else() - message(STATUS "LTO disabled (not supported by the compiler and/or linker)") - endif() - endif() +set_property( + TARGET pybind11::embed + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11 $) - # Enable LTO flags if found, except for Debug builds - if(PYBIND11_LTO_CXX_FLAGS) - set(not_debug "$>") - set(cxx_lang "$") - target_compile_options(${target_name} - PRIVATE "$<$:${PYBIND11_LTO_CXX_FLAGS}>") - endif() - if(PYBIND11_LTO_LINKER_FLAGS) - target_link_libraries(${target_name} PRIVATE "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") - endif() +function(pybind11_extension name) + # The prefix and extension are provided by FindPythonLibsNew.cmake + set_target_properties(${name} PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}") endfunction() # Build a Python extension module: @@ -132,7 +118,7 @@ endfunction() # function(pybind11_add_module target_name) set(options MODULE SHARED EXCLUDE_FROM_ALL NO_EXTRAS SYSTEM THIN_LTO) - cmake_parse_arguments(PARSE_ARGV 1 ARG "${options}" "" "") + cmake_parse_arguments(ARG "${options}" "" "" ${ARGN}) if(ARG_MODULE AND ARG_SHARED) message(FATAL_ERROR "Can't be both MODULE and SHARED") @@ -159,74 +145,34 @@ function(pybind11_add_module target_name) ) endif() - # Python debug libraries expose slightly different objects before 3.8 - # https://docs.python.org/3.6/c-api/intro.html#debugging-builds - # https://stackoverflow.com/questions/39161202/how-to-work-around-missing-pymodule-create2-in-amd64-win-python35-d-lib - if(PYTHON_IS_DEBUG) - target_compile_definitions(${target_name} PRIVATE Py_DEBUG) - endif() - - # The prefix and extension are provided by FindPythonLibsNew.cmake - set_target_properties(${target_name} PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}") - set_target_properties(${target_name} PROPERTIES SUFFIX "${PYTHON_MODULE_EXTENSION}") + pybind11_extension(${target_name}) # -fvisibility=hidden is required to allow multiple modules compiled against # different pybind versions to work properly, and for some features (e.g. # py::module_local). We force it on everything inside the `pybind11` # namespace; also turning it on for a pybind module compilation here avoids # potential warnings or issues from having mixed hidden/non-hidden types. - set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden") - set_target_properties(${target_name} PROPERTIES CUDA_VISIBILITY_PRESET "hidden") + set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden" + CUDA_VISIBILITY_PRESET "hidden") if(ARG_NO_EXTRAS) return() endif() - if(CMAKE_VERSION VERSION_LESS 3.9 OR PYBIND11_CLASSIC_LTO) - _pybind11_add_lto_flags(${target_name} ${ARG_THIN_LTO}) - else() - include(CheckIPOSupported) - check_ipo_supported( - RESULT supported - OUTPUT error - LANGUAGES CXX) - if(supported) - set_property(TARGET ${target_name} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) + if(ARG_THIN_LTO) + target_link_libraries(${target_name} PRIVATE pybind11::thin_lto) else() - message(WARNING "IPO is not supported: ${output}") + target_link_libraries(${target_name} PRIVATE pybind11::lto) endif() endif() if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) - # Strip unnecessary sections of the binary on Linux/Mac OS - if(CMAKE_STRIP) - if(APPLE) - add_custom_command( - TARGET ${target_name} - POST_BUILD - COMMAND ${CMAKE_STRIP} -x $) - else() - add_custom_command( - TARGET ${target_name} - POST_BUILD - COMMAND ${CMAKE_STRIP} $) - endif() - endif() + pybind11_strip(${target_name}) endif() if(MSVC) - # /MP enables multithreaded builds (relevant when there are many files), /bigobj is - # needed for bigger binding projects due to the limit to 64k addressable sections - target_compile_options(${target_name} PRIVATE /bigobj) - if(CMAKE_VERSION VERSION_LESS 3.11) - target_compile_options(${target_name} PRIVATE $<$>:/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. - target_compile_options(${target_name} - PRIVATE $<$>:$<$:/MP>>) - endif() + target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) endif() + endfunction() From 04fdc44f5065f8b56754fae9e0e93473bd3e7e60 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 19 Aug 2020 13:11:57 -0400 Subject: [PATCH 027/295] tests: avoid putting build products into source directory (#2353) * tests: keep source dir clean * ci: make first build inplace * ci: drop dev setting (wasn't doing anything) * tests: warn if source directory is dirty --- .github/workflows/ci.yml | 26 +++++++---------------- .gitignore | 2 +- CMakeLists.txt | 12 ++++++++++- tests/CMakeLists.txt | 37 ++++++++++++++++++++++++++++----- tests/conftest.py | 7 +++++++ tests/test_embed/CMakeLists.txt | 10 ++++++--- 6 files changed, 66 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f9ebac9f7..0f01b1d26e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,6 @@ jobs: runs-on: [ubuntu-latest, windows-latest, macos-latest] arch: [x64] max-cxx-std: [17] - dev: [false] python: - 2.7 - 3.5 @@ -30,41 +29,34 @@ jobs: python: 3.6 arch: x64 max-cxx-std: 17 - dev: false args: "-DPYBIND11_FINDPYTHON=ON" - runs-on: macos-latest python: 3.7 arch: x64 max-cxx-std: 17 - dev: false args: "-DPYBIND11_FINDPYTHON=ON" - runs-on: windows-2016 python: 3.7 arch: x86 max-cxx-std: 14 - dev: false - runs-on: windows-latest python: 3.6 arch: x64 max-cxx-std: 17 - dev: false args: "-DPYBIND11_FINDPYTHON=ON" - runs-on: windows-latest python: 3.7 arch: x64 max-cxx-std: 17 - dev: false - runs-on: ubuntu-latest python: 3.9-dev arch: x64 max-cxx-std: 17 - dev: true - runs-on: macos-latest python: 3.9-dev arch: x64 max-cxx-std: 17 - dev: true exclude: # Currently 32bit only, and we build 64bit @@ -72,29 +64,24 @@ jobs: python: pypy2 arch: x64 max-cxx-std: 17 - dev: false - runs-on: windows-latest python: pypy3 arch: x64 max-cxx-std: 17 - dev: false # Currently broken on embed_test - runs-on: windows-latest python: 3.8 arch: x64 max-cxx-std: 17 - dev: false - runs-on: windows-latest python: 3.9-dev arch: x64 max-cxx-std: 17 - dev: false name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • ${{ matrix.arch }} ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} - continue-on-error: ${{ matrix.dev }} steps: - uses: actions/checkout@v2 @@ -129,7 +116,7 @@ jobs: - name: Configure C++11 ${{ matrix.args }} shell: bash run: > - cmake -S . -B build + cmake -S . -B . -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON @@ -137,16 +124,19 @@ jobs: ${{ matrix.args }} - name: Build C++11 - run: cmake --build build -j 2 + run: cmake --build . -j 2 - name: Python tests C++11 - run: cmake --build build --target pytest -j 2 + run: cmake --build . --target pytest -j 2 - name: C++11 tests - run: cmake --build build --target cpptest -j 2 + run: cmake --build . --target cpptest -j 2 - name: Interface test C++11 - run: cmake --build build --target test_cmake_build -v + run: cmake --build . --target test_cmake_build + + - name: Clean directory + run: git clean -fdx - name: Configure C++${{ matrix.max-cxx-std }} ${{ matrix.args }} shell: bash diff --git a/.gitignore b/.gitignore index 6d65838b50..47e010ce27 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,7 @@ MANIFEST .*.swp .DS_Store /dist -/build* +/*build* .cache/ sosize-*.txt pybind11Config*.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 427975258a..00e39bc0e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,17 @@ if(NOT pybind11_FIND_QUIETLY) endif() # Check if pybind11 is being used directly or via add_subdirectory -if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + ### Warn if not an out-of-source builds + if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + set(lines + "You are building in-place. If that is not what you intended to " + "do, you can clean the source directory with:\n" + "rm -r CMakeCache.txt CMakeFiles/ cmake_uninstall.cmake pybind11Config.cmake " + "pybind11ConfigVersion.cmake tests/CMakeFiles/\n") + message(AUTHOR_WARNING ${lines}) + endif() + set(PYBIND11_MASTER_PROJECT ON) if(OSX AND CMAKE_VERSION VERSION_LESS 3.7) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 895cfe75eb..72de21018a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -239,7 +239,6 @@ foreach(t ${PYBIND11_CROSS_MODULE_GIL_TESTS}) endif() endforeach() -set(testdir ${CMAKE_CURRENT_SOURCE_DIR}) foreach(target ${test_targets}) set(test_files ${PYBIND11_TEST_FILES}) if(NOT "${target}" STREQUAL "pybind11_tests") @@ -250,6 +249,18 @@ foreach(target ${test_targets}) pybind11_add_module(${target} THIN_LTO ${target}.cpp ${test_files} ${PYBIND11_HEADERS}) pybind11_enable_warnings(${target}) + if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + get_property( + suffix + TARGET ${target} + PROPERTY SUFFIX) + set(source_output "${CMAKE_CURRENT_SOURCE_DIR}/${target}${suffix}") + if(suffix AND EXISTS "${source_output}") + message(WARNING "Output file also in source directory; " + "please remove to avoid confusion: ${source_output}") + endif() + endif() + if(MSVC) target_compile_options(${target} PRIVATE /utf-8) endif() @@ -266,10 +277,12 @@ foreach(target ${test_targets}) # Always write the output file directly into the 'tests' directory (even on MSVC) if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) - set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${testdir}") + set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY + "${CMAKE_CURRENT_BINARY_DIR}") foreach(config ${CMAKE_CONFIGURATION_TYPES}) string(TOUPPER ${config} config) - set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config} "${testdir}") + set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config} + "${CMAKE_CURRENT_BINARY_DIR}") endforeach() endif() endforeach() @@ -293,12 +306,26 @@ if(NOT PYBIND11_PYTEST_FOUND) CACHE INTERNAL "") endif() +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + # This is not used later in the build, so it's okay to regenerate each time. + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/pytest.ini" "${CMAKE_CURRENT_BINARY_DIR}/pytest.ini" + COPYONLY) + file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/pytest.ini" + "\ntestpaths = \"${CMAKE_CURRENT_SOURCE_DIR}\"") + +endif() + +# cmake 3.12 added list(transform prepend +# but we can't use it yet +string(REPLACE "test_" "${CMAKE_CURRENT_BINARY_DIR}/test_" PYBIND11_BINARY_TEST_FILES + "${PYBIND11_PYTEST_FILES}") + # A single command to compile and run the tests add_custom_target( pytest - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${PYBIND11_PYTEST_FILES} + COMMAND ${PYTHON_EXECUTABLE} -m pytest ${PYBIND11_BINARY_PYTEST_FILES} DEPENDS ${test_targets} - WORKING_DIRECTORY ${testdir} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} USES_TERMINAL) if(PYBIND11_TEST_OVERRIDE) diff --git a/tests/conftest.py b/tests/conftest.py index 8b6e47dc2e..a2350d041f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,8 @@ import pytest +import env + # Early diagnostic for failed imports import pybind11_tests # noqa: F401 @@ -20,6 +22,11 @@ _long_marker = re.compile(r'([0-9])L') _hexadecimal = re.compile(r'0x[0-9a-fA-F]+') +# Avoid collecting Python3 only files +collect_ignore = [] +if env.PY2: + collect_ignore.append("test_async.py") + def _strip_and_dedent(s): """For triple-quote strings""" diff --git a/tests/test_embed/CMakeLists.txt b/tests/test_embed/CMakeLists.txt index 1495c77753..2e298fa7e4 100644 --- a/tests/test_embed/CMakeLists.txt +++ b/tests/test_embed/CMakeLists.txt @@ -21,18 +21,22 @@ pybind11_enable_warnings(test_embed) target_link_libraries(test_embed PRIVATE pybind11::embed Catch2::Catch2 Threads::Threads) +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + file(COPY test_interpreter.py DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +endif() + add_custom_target( cpptest COMMAND "$" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") pybind11_add_module(external_module THIN_LTO external_module.cpp) set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}") + "${CMAKE_CURRENT_BINARY_DIR}") foreach(config ${CMAKE_CONFIGURATION_TYPES}) string(TOUPPER ${config} config) set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config} - "${CMAKE_CURRENT_SOURCE_DIR}") + "${CMAKE_CURRENT_BINARY_DIR}") endforeach() add_dependencies(cpptest external_module) From 24dffe46afb1c6d9d2946fd0b162aeed4c9c1daa Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 19 Aug 2020 16:49:08 -0400 Subject: [PATCH 028/295] fix: PYBIND11_MASTER_PROJECT always ON (#2412) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 00e39bc0e5..6234f76fe4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,7 @@ if(NOT pybind11_FIND_QUIETLY) endif() # Check if pybind11 is being used directly or via add_subdirectory -if(CMAKE_CURRENT_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) ### Warn if not an out-of-source builds if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) set(lines From 110e6c12ce46cc83ecbb1aaf76c327e24ca76a31 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 20 Aug 2020 11:58:34 -0400 Subject: [PATCH 029/295] ci: reduce flakiness a little (#2418) --- tests/env.py | 2 ++ tests/test_gil_scoped.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/tests/env.py b/tests/env.py index f246b082bc..5cded44127 100644 --- a/tests/env.py +++ b/tests/env.py @@ -10,3 +10,5 @@ PYPY = platform.python_implementation() == "PyPy" PY2 = sys.version_info.major == 2 + +PY = sys.version_info diff --git a/tests/test_gil_scoped.py b/tests/test_gil_scoped.py index 1307712ad3..c85eb7c72b 100644 --- a/tests/test_gil_scoped.py +++ b/tests/test_gil_scoped.py @@ -1,6 +1,11 @@ # -*- coding: utf-8 -*- import multiprocessing import threading + +import pytest + +import env # noqa: F401 + from pybind11_tests import gil_scoped as m @@ -57,6 +62,8 @@ def test_python_to_cpp_to_python_from_thread(): assert _run_in_process(_python_to_cpp_to_python_from_threads, 1) == 0 +# TODO: FIXME +@pytest.mark.xfail("env.PY > (3,8) and env.MACOS", strict=False) def test_python_to_cpp_to_python_from_thread_multiple_parallel(): """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel. @@ -73,6 +80,8 @@ def test_python_to_cpp_to_python_from_thread_multiple_sequential(): assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=False) == 0 +# TODO: FIXME +@pytest.mark.xfail("env.PY > (3,8) and env.MACOS", strict=False) def test_python_to_cpp_to_python_from_process(): """Makes sure there is no GIL deadlock when using processes. From a6887b604aa4d0df7fb63c3d6d7911cb44906cfa Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 19 Aug 2020 14:53:59 -0400 Subject: [PATCH 030/295] docs: update changelog and versionadded --- CONTRIBUTING.md | 17 +++++++----- docs/advanced/classes.rst | 7 +++-- docs/advanced/exceptions.rst | 2 ++ docs/advanced/functions.rst | 2 ++ docs/advanced/pycpp/numpy.rst | 6 +++++ docs/changelog.rst | 51 +++++++++++++++++++++++++++++++++++ docs/upgrade.rst | 8 ++++++ 7 files changed, 84 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ec1f1d532..f61011d540 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,7 +60,7 @@ system with CMake 3.14+: python3 -m venv venv source venv/bin/activate pip install -r tests/requirements.txt -cmake -S . -B build -DPYTHON_EXECUTABLE=$(which python) -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON +cmake -S . -B build -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON cmake --build build -j4 ``` @@ -71,9 +71,10 @@ Tips: * 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` instead of setting the - `PYTHON_EXECUTABLE` - the new search algorithm can find virtual environments, - Conda, and more. +* You can use `-DPYBIND11_FINDPYTHON=ON` to use FindPython on CMake 3.12+ +* 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`. ### Configuration options @@ -104,10 +105,12 @@ The valid options are: * Use `cmake build -LH` to list the CMake options with help. * Use `ccmake` if available to see a curses (terminal) gui, or `cmake-gui` for a completely graphical interface (not present in the PyPI package). -* Use `-G` and the name of a generator to use something different, like `Ninja` - (automatic multithreading!). `cmake --help` lists the generators available. -* Open the `CMakeLists.txt` with QtCreator to generate for that IDE. * Use `cmake --build build -j12` to build with 12 cores (for example). +* Use `-G` and the name of a generator to use something different. `cmake + --help` lists the generators available. + - On Unix, setting `CMAKE_GENERATER=Ninja` in your environment will give + you automatic mulithreading on all your CMake projects! +* Open the `CMakeLists.txt` with QtCreator to generate for that IDE. * You can use `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate the `.json` file that some tools expect. diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index e8940d814b..f7db3eadf1 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -149,8 +149,7 @@ memory for the C++ portion of the instance will be left uninitialized, which will generally leave the C++ instance in an invalid state and cause undefined behavior if the C++ instance is subsequently used. -.. versionadded:: 2.5.1 - +.. versionchanged:: 2.6 The default pybind11 metaclass will throw a ``TypeError`` when it detects that ``__init__`` was not called by a derived class. @@ -597,6 +596,8 @@ For more information, see :ref:`the documentation on exceptions `_ + +* Perfect forwarding support for methods. + `#2048 `_ + +* Added ``py::error_already_set::discard_as_unraisable()``. + `#2372 `_ + +* ``py::hash`` is now public. + `#2217 `_ + +* ``py::is_final()`` class modifier to block subclassing (CPython only). + `#2151 `_ + +* ``py::memoryview`` update and documentation. + `#2223 `_ + * Minimum CMake required increased to 3.4. `#2338 `_ and `#2370 `_ @@ -36,6 +54,39 @@ See :ref:`upgrade-guide-2.6` for help upgrading to the new version. `#2265 `_ and `#2346 `_ +Smaller or developer focused features: + +* Error now thrown when ``__init__`` is forgotten on subclasses. + `#2152 `_ + +* If ``__eq__`` defined but not ``__hash__``, ``__hash__`` is now set to + ``None``. + `#2291 `_ + +* ``py::ellipsis`` now also works on Python 2 + `#2360 `_ + +* Added missing signature for ``py::array`` + `#2363 `_ + +* Bugfixes related to more extensive testing + `#2321 `_ + +* Pointer to ``std::tuple`` & ``std::pair`` supported in cast. + `#2334 `_ + +* Small fixes in NumPy support. ``py::array`` now uses ``py::ssize_t`` as first + argument type. + `#2293 `_ + +* PyPy fixes, including support for PyPy3 and PyPy 7. + `#2146 `_ + +* CPython 3.9 fixes. + `#2253 `_ + +* Debug Python interpreter support. + `#2025 `_ diff --git a/docs/upgrade.rst b/docs/upgrade.rst index 98285eb6e8..7c3f1c3280 100644 --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -13,6 +13,14 @@ modernization and other useful information. v2.6 ==== +An error is now thrown when ``__init__`` is forgotten on subclasses. This was +incorrect before, but was not checked. Add a call to ``__init__`` if it is +missing. + +If ``__eq__`` defined but not ``__hash__``, ``__hash__`` is now set to +``None``, as in normal CPython. You should add ``__hash__`` if you intended the +class to be hashable, possibly using the new ``py::hash`` shortcut. + CMake support: -------------- From 2fa18431cee49b7e782c577b63a7e2ca7d0213d2 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 20 Aug 2020 12:00:28 -0400 Subject: [PATCH 031/295] docs: pin versions for readthedocs --- .github/workflows/ci.yml | 4 ++-- docs/requirements.txt | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f01b1d26e..fc92101b2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,7 +111,7 @@ jobs: key: ${{ runner.os }}-pip-${{ matrix.python }}-${{ matrix.arch }}-${{ hashFiles('tests/requirements.txt') }} - name: Prepare env - run: python -m pip install -r tests/requirements.txt + run: python -m pip install -r tests/requirements.txt --prefer-binary - name: Configure C++11 ${{ matrix.args }} shell: bash @@ -272,7 +272,7 @@ jobs: run: python3 -m pip install --upgrade pip - name: Install dependencies - run: python3 -m pip install cmake -r tests/requirements.txt + run: python3 -m pip install cmake -r tests/requirements.txt --prefer-binary - name: Configure shell: bash diff --git a/docs/requirements.txt b/docs/requirements.txt index 2eb7f2020a..f4c3dc2e0b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,5 @@ -breathe==4.13.1 -sphinx<3 -sphinx_rtd_theme +breathe==4.20.0 +commonmark==0.9.1 +recommonmark==0.6.0 +sphinx==3.2.1 +sphinx_rtd_theme==0.5.0 From f31df738f7fbd77382ae88d36fbd17b01b912953 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 20 Aug 2020 15:42:07 -0400 Subject: [PATCH 032/295] docs: move CONTRIBUTING (#2402) * docs: move CONTRIBUTING * docs: clarify PyPy promise --- CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 MANIFEST.in | 2 +- README.md | 60 +++++++++++++--------- 3 files changed, 38 insertions(+), 24 deletions(-) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md diff --git a/MANIFEST.in b/MANIFEST.in index 6e57baeeef..6fe84ced8d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ recursive-include include/pybind11 *.h -include LICENSE README.md CONTRIBUTING.md +include LICENSE README.md .github/CONTRIBUTING.md diff --git a/README.md b/README.md index 5eee2dc9fe..7634972cb4 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,11 @@ [![CI](https://github.com/pybind/pybind11/workflows/CI/badge.svg)](https://github.com/pybind/pybind11/actions) [![Build status](https://ci.appveyor.com/api/projects/status/riaj54pn4h08xy40?svg=true)](https://ci.appveyor.com/project/wjakob/pybind11) -**pybind11** is a lightweight header-only library that exposes C++ types in Python -and vice versa, mainly to create Python bindings of existing C++ code. Its -goals and syntax are similar to the excellent -[Boost.Python](http://www.boost.org/doc/libs/1_58_0/libs/python/doc/) library -by David Abrahams: to minimize boilerplate code in traditional extension -modules by inferring type information using compile-time introspection. +**pybind11** is a lightweight header-only library that exposes C++ types in +Python and vice versa, mainly to create Python bindings of existing C++ code. +Its goals and syntax are similar to the excellent [Boost.Python][] library by +David Abrahams: to minimize boilerplate code in traditional extension modules +by inferring type information using compile-time introspection. The main issue with Boost.Python—and the reason for creating such a similar project—is Boost. Boost is an enormously large and complex suite of utility @@ -26,19 +25,18 @@ become an excessively large and unnecessary dependency. Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn't relevant for binding generation. Without comments, the core header files only require ~4K lines of code and depend on -Python (2.7 or 3.x, or PyPy2.7 >= 5.7) and the C++ standard library. This -compact implementation was possible thanks to some of the new C++11 language -features (specifically: tuples, lambda functions and variadic templates). Since -its creation, this library has grown beyond Boost.Python in many ways, leading -to dramatically simpler binding code in many common situations. +Python (2.7 or 3.5+, or PyPy) and the C++ standard library. This compact +implementation was possible thanks to some of the new C++11 language features +(specifically: tuples, lambda functions and variadic templates). Since its +creation, this library has grown beyond Boost.Python in many ways, leading to +dramatically simpler binding code in many common situations. Tutorial and reference documentation is provided at -[http://pybind11.readthedocs.org/en/master](http://pybind11.readthedocs.org/en/master). -A PDF version of the manual is available -[here](https://media.readthedocs.org/pdf/pybind11/master/pybind11.pdf). +[pybind11.readthedocs.org][]. A PDF version of the manual is available +[here][docs-pdf]. ## Core features -pybind11 can map the following core C++ features to Python +pybind11 can map the following core C++ features to Python: - Functions accepting and returning custom data structures per value, reference, or pointer - Instance methods and static methods @@ -51,15 +49,15 @@ pybind11 can map the following core C++ features to Python - Custom operators - Single and multiple inheritance - STL data structures -- Smart pointers with reference counting like ``std::shared_ptr`` +- Smart pointers with reference counting like `std::shared_ptr` - Internal references with correct reference counting - C++ classes with virtual (and pure virtual) methods can be extended in Python ## Goodies In addition to the core functionality, pybind11 provides some extra goodies: -- Python 2.7, 3.x, and PyPy (PyPy2.7 >= 5.7) are supported with an - implementation-agnostic interface. +- Python 2.7, 3.5+, and PyPy (tested on 7.3) are supported with an implementation-agnostic + interface. - It is possible to bind C++11 lambda functions with captured variables. The lambda capture data is stored inside the resulting Python function object. @@ -83,10 +81,10 @@ In addition to the core functionality, pybind11 provides some extra goodies: - Binaries are generally smaller by a factor of at least 2 compared to equivalent bindings generated by Boost.Python. A recent pybind11 conversion of PyRosetta, an enormous Boost.Python binding project, - [reported](http://graylab.jhu.edu/RosettaCon2016/PyRosetta-4.pdf) a binary - size reduction of **5.4x** and compile time reduction by **5.8x**. + [reported][pyrosetta-report] a binary size reduction of **5.4x** and compile + time reduction by **5.8x**. -- Function signatures are precomputed at compile time (using ``constexpr``), +- Function signatures are precomputed at compile time (using `constexpr`), leading to smaller binaries. - With little extra effort, C++ types can be pickled and unpickled similar to @@ -97,7 +95,8 @@ In addition to the core functionality, pybind11 provides some extra goodies: 1. Clang/LLVM 3.3 or newer (for Apple Xcode's clang, this is 5.0.0 or newer) 2. GCC 4.8 or newer 3. Microsoft Visual Studio 2015 Update 3 or newer -4. Intel C++ compiler 17 or newer (16 with pybind11 v2.0 and 15 with pybind11 v2.0 and a [workaround](https://github.com/pybind/pybind11/issues/276)) +4. Intel C++ compiler 17 or newer (16 with pybind11 v2.0 and 15 with pybind11 + v2.0 and a [workaround][intel-15-workaround]) 5. Cygwin/GCC (tested on 2.5.1) ## About @@ -122,8 +121,23 @@ Henry Schreiner, Ivan Smirnov, and Patrick Stewart. +### Contributing + +See the [contributing guide][] for information on building and contributing to +pybind11. + + ### License pybind11 is provided under a BSD-style license that can be found in the -``LICENSE`` file. By using, distributing, or contributing to this project, +[`LICENSE`][] file. By using, distributing, or contributing to this project, you agree to the terms and conditions of this license. + + +[pybind11.readthedocs.org]: http://pybind11.readthedocs.org/en/master +[docs-pdf]: https://media.readthedocs.org/pdf/pybind11/master/pybind11.pdf +[Boost.Python]: http://www.boost.org/doc/libs/1_58_0/libs/python/doc/ +[pyrosetta-report]: http://graylab.jhu.edu/RosettaCon2016/PyRosetta-4.pdf +[contributing guide]: https://github.com/pybind/pybind11/blob/master/CONTRIBUTING.md +[`LICENSE`]: https://github.com/pybind/pybind11/blob/master/LICENSE +[intel-15-workaround]: https://github.com/pybind/pybind11/issues/276 From d4d7ef5d27b48e7773b5fef8b646625210532c91 Mon Sep 17 00:00:00 2001 From: Bjorn Date: Fri, 21 Aug 2020 20:52:38 +0200 Subject: [PATCH 033/295] Update pybind11Tools.cmake (#2419) CPython configured with `--with-pydebug` could not use `pybind11_add_module` --- tools/pybind11Tools.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 29885ae998..809cc5876d 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -82,7 +82,7 @@ set_property( # https://stackoverflow.com/questions/39161202/how-to-work-around-missing-pymodule-create2-in-amd64-win-python35-d-lib if(PYTHON_IS_DEBUG) set_property( - TARGET pybind::pybind11 + TARGET pybind11::pybind11 APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) endif() From 56df3c4649170dba69662271693f3352beb37796 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 21 Aug 2020 15:27:21 -0400 Subject: [PATCH 034/295] fix: a couple more places where pybind11 is missing 11 (#2421) --- include/pybind11/cast.h | 2 +- tools/pybind11NewTools.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 4901ea3911..5711004df9 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1767,7 +1767,7 @@ detail::enable_if_t::value, T> move(object &&obj) { return ret; } -// Calling cast() on an rvalue calls pybind::cast with the object rvalue, which does: +// Calling cast() on an rvalue calls pybind11::cast with the object rvalue, which does: // - If we have to move (because T has no copy constructor), do it. This will fail if the moved // object has multiple references, but trying to copy will fail to compile. // - If both movable and copyable, check ref count: if 1, move; otherwise copy diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index 2e7a9310f1..8f771acd24 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -79,7 +79,7 @@ execute_process(COMMAND ${_Python}::Python -c "import sys; print(hasattr(sys, 'g # https://stackoverflow.com/questions/39161202/how-to-work-around-missing-pymodule-create2-in-amd64-win-python35-d-lib if(PYTHON_IS_DEBUG) set_property( - TARGET pybind::pybind11 + TARGET pybind11::pybind11 APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) endif() From c58f7b745b778897ccb5cb122fdb95085f7e5cc4 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 22 Aug 2020 09:06:01 -0400 Subject: [PATCH 035/295] fix: reduce target collision in add_submodule mode (#2423) * fix: reduce target collision in add_submodule mode Closes #2420 * fix: update CMakeLists.txt --- CMakeLists.txt | 16 +++++++++------- tools/pybind11Config.cmake.in | 5 +++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6234f76fe4..3b460494a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,8 +154,10 @@ set(PYBIND11_INCLUDE_DIR # This section builds targets, but does *not* touch Python # Build the headers-only target (no Python included): -add_library(headers INTERFACE) -add_library(pybind11::headers ALIAS headers) # to match exported target +# (long name used here to keep this from clashing in subdirectory mode) +add_library(pybind11_headers INTERFACE) +add_library(pybind11::pybind11_headers ALIAS pybind11_headers) # to match exported target +add_library(pybind11::headers ALIAS pybind11_headers) # easier to use/remember include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake") @@ -172,11 +174,11 @@ endif() # Fill in headers target target_include_directories( - headers ${pybind11_system} INTERFACE $ - $) + pybind11_headers ${pybind11_system} INTERFACE $ + $) -target_compile_features(headers INTERFACE cxx_inheriting_constructors cxx_user_literals - cxx_right_angle_brackets) +target_compile_features(pybind11_headers INTERFACE cxx_inheriting_constructors cxx_user_literals + cxx_right_angle_brackets) if(PYBIND11_INSTALL) install(DIRECTORY ${PYBIND11_INCLUDE_DIR}/pybind11 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) @@ -222,7 +224,7 @@ if(PYBIND11_INSTALL) set(PYBIND11_EXPORT_NAME "${PROJECT_NAME}Targets") endif() - install(TARGETS headers EXPORT "${PYBIND11_EXPORT_NAME}") + install(TARGETS pybind11_headers EXPORT "${PYBIND11_EXPORT_NAME}") install( EXPORT "${PYBIND11_EXPORT_NAME}" diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index 4f0500a5f4..3f11172963 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -130,6 +130,11 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/pybind11Targets.cmake") +# Easier to use / remember +add_library(pybind11::headers IMPORTED INTERFACE) +set_target_properties(pybind11::headers PROPERTIES INTERFACE_LINK_LIBRARIES + pybind11::pybind11_headers) + include("${CMAKE_CURRENT_LIST_DIR}/pybind11Common.cmake") if(NOT pybind11_FIND_QUIETLY) From b8863698d6f53ea86dd26c681eeaa837888c66d6 Mon Sep 17 00:00:00 2001 From: jbarlow83 Date: Sat, 22 Aug 2020 15:11:09 -0700 Subject: [PATCH 036/295] Improve documentation of Python and C++ exceptions (#2408) The main change is to treat error_already_set as a separate category of exception that arises in different circumstances and needs to be handled differently. The asymmetry between Python and C++ exceptions is further emphasized. --- .gitignore | 1 + docs/advanced/exceptions.rst | 167 +++++++++++++++++++++++++-------- docs/advanced/pycpp/object.rst | 10 ++ 3 files changed, 141 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 47e010ce27..5613b367d2 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ sosize-*.txt pybind11Config*.cmake pybind11Targets.cmake /*env* +/.vscode diff --git a/docs/advanced/exceptions.rst b/docs/advanced/exceptions.rst index 72063108c5..b7d36014a6 100644 --- a/docs/advanced/exceptions.rst +++ b/docs/advanced/exceptions.rst @@ -1,18 +1,24 @@ Exceptions ########## -Built-in exception translation -============================== +Built-in C++ to Python exception translation +============================================ + +When Python calls C++ code through pybind11, pybind11 provides a C++ exception handler +that will trap C++ exceptions, translate them to the corresponding Python exception, +and raise them so that Python code can handle them. -When C++ code invoked from Python throws an ``std::exception``, it is -automatically converted into a Python ``Exception``. pybind11 defines multiple -special exception classes that will map to different types of Python -exceptions: +pybind11 defines translations for ``std::exception`` and its standard +subclasses, and several special exception classes that translate to specific +Python exceptions. Note that these are not actually Python exceptions, so they +cannot be examined using the Python C API. Instead, they are pure C++ objects +that pybind11 will translate the corresponding Python exception when they arrive +at its exception handler. .. tabularcolumns:: |p{0.5\textwidth}|p{0.45\textwidth}| +--------------------------------------+--------------------------------------+ -| C++ exception type | Python exception type | +| Exception thrown by C++ | Translated to Python exception type | +======================================+======================================+ | :class:`std::exception` | ``RuntimeError`` | +--------------------------------------+--------------------------------------+ @@ -46,22 +52,11 @@ exceptions: | | ``__setitem__`` in dict-like | | | objects, etc.) | +--------------------------------------+--------------------------------------+ -| :class:`pybind11::error_already_set` | Indicates that the Python exception | -| | flag has already been set via Python | -| | API calls from C++ code; this C++ | -| | exception is used to propagate such | -| | a Python exception back to Python. | -+--------------------------------------+--------------------------------------+ -When a Python function invoked from C++ throws an exception, pybind11 will convert -it into a C++ exception of type :class:`error_already_set` whose string payload -contains a textual summary. If you call the Python C-API directly, and it -returns an error, you should ``throw py::error_already_set();``, which allows -pybind11 to deal with the exception and pass it back to the Python interpreter. -(Another option is to call ``PyErr_Clear`` in the -`Python C-API `_ -to clear the error. The Python error must be thrown or cleared, or Python/pybind11 -will be left in an invalid state.) +Exception translation is not bidirectional. That is, *catching* the C++ +exceptions defined above above will not trap exceptions that originate from +Python. For that, catch :class:`pybind11::error_already_set`. See :ref:`below +` for further details. There is also a special exception :class:`cast_error` that is thrown by :func:`handle::call` when the input arguments cannot be converted to Python @@ -106,7 +101,6 @@ and use this in the associated exception translator (note: it is often useful to make this a static declaration when using it inside a lambda expression without requiring capturing). - The following example demonstrates this for a hypothetical exception classes ``MyCustomException`` and ``OtherException``: the first is translated to a custom python exception ``MyCustomError``, while the second is translated to a @@ -140,7 +134,7 @@ section. .. note:: - You must call either ``PyErr_SetString`` or a custom exception's call + Call either ``PyErr_SetString`` or a custom exception's call operator (``exc(string)``) for every exception caught in a custom exception translator. Failure to do so will cause Python to crash with ``SystemError: error return without exception set``. @@ -149,6 +143,103 @@ section. may be explicitly (re-)thrown to delegate it to the other, previously-declared existing exception translators. +.. _handling_python_exceptions_cpp: + +Handling exceptions from Python in C++ +====================================== + +When C++ calls Python functions, such as in a callback function or when +manipulating Python objects, and Python raises an ``Exception``, pybind11 +converts the Python exception into a C++ exception of type +:class:`pybind11::error_already_set` whose payload contains a C++ string textual +summary and the actual Python exception. ``error_already_set`` is used to +propagate Python exception back to Python (or possibly, handle them in C++). + +.. tabularcolumns:: |p{0.5\textwidth}|p{0.45\textwidth}| + ++--------------------------------------+--------------------------------------+ +| Exception raised in Python | Thrown as C++ exception type | ++======================================+======================================+ +| Any Python ``Exception`` | :class:`pybind11::error_already_set` | ++--------------------------------------+--------------------------------------+ + +For example: + +.. code-block:: cpp + + try { + // open("missing.txt", "r") + auto file = py::module::import("io").attr("open")("missing.txt", "r"); + auto text = file.attr("read")(); + file.attr("close")(); + } catch (py::error_already_set &e) { + if (e.matches(PyExc_FileNotFoundError)) { + py::print("missing.txt not found"); + } else if (e.match(PyExc_PermissionError)) { + py::print("missing.txt found but not accessible"); + } else { + throw; + } + } + +Note that C++ to Python exception translation does not apply here, since that is +a method for translating C++ exceptions to Python, not vice versa. The error raised +from Python is always ``error_already_set``. + +This example illustrates this behavior: + +.. code-block:: cpp + + try { + py::eval("raise ValueError('The Ring')"); + } catch (py::value_error &boromir) { + // Boromir never gets the ring + assert(false); + } catch (py::error_already_set &frodo) { + // Frodo gets the ring + py::print("I will take the ring"); + } + + try { + // py::value_error is a request for pybind11 to raise a Python exception + throw py::value_error("The ball"); + } catch (py::error_already_set &cat) { + // cat won't catch the ball since + // py::value_error is not a Python exception + assert(false); + } catch (py::value_error &dog) { + // dog will catch the ball + py::print("Run Spot run"); + throw; // Throw it again (pybind11 will raise ValueError) + } + +Handling errors from the Python C API +===================================== + +Where possible, use :ref:`pybind11 wrappers ` instead of calling +the Python C API directly. When calling the Python C API directly, in +addition to manually managing reference counts, one must follow the pybind11 +error protocol, which is outlined here. + +After calling the Python C API, if Python returns an error, +``throw py::error_already_set();``, which allows pybind11 to deal with the +exception and pass it back to the Python interpreter. This includes calls to +the error setting functions such as ``PyErr_SetString``. + +.. code-block:: cpp + + PyErr_SetString(PyExc_TypeError, "C API type error demo"); + throw py::error_already_set(); + + // But it would be easier to simply... + throw py::type_error("pybind11 wrapper type error"); + +Alternately, to ignore the error, call `PyErr_Clear +`_. + +Any Python error must be thrown or cleared, or Python/pybind11 will be left in +an invalid state. + .. _unraisable_exceptions: Handling unraisable exceptions @@ -156,20 +247,24 @@ Handling unraisable exceptions If a Python function invoked from a C++ destructor or any function marked ``noexcept(true)`` (collectively, "noexcept functions") throws an exception, there -is no way to propagate the exception, as such functions may not throw at -run-time. +is no way to propagate the exception, as such functions may not throw. +Should they throw or fail to catch any exceptions in their call graph, +the C++ runtime calls ``std::terminate()`` to abort immediately. -Neither Python nor C++ allow exceptions raised in a noexcept function to propagate. In -Python, an exception raised in a class's ``__del__`` method is logged as an -unraisable error. In Python 3.8+, a system hook is triggered and an auditing -event is logged. In C++, ``std::terminate()`` is called to abort immediately. +Similarly, Python exceptions raised in a class's ``__del__`` method do not +propagate, but are logged by Python as an unraisable error. In Python 3.8+, a +`system hook is triggered +`_ +and an auditing event is logged. Any noexcept function should have a try-catch block that traps -class:`error_already_set` (or any other exception that can occur). Note that pybind11 -wrappers around Python exceptions such as :class:`pybind11::value_error` are *not* -Python exceptions; they are C++ exceptions that pybind11 catches and converts to -Python exceptions. Noexcept functions cannot propagate these exceptions either. -You can convert them to Python exceptions and then discard as unraisable. +class:`error_already_set` (or any other exception that can occur). Note that +pybind11 wrappers around Python exceptions such as +:class:`pybind11::value_error` are *not* Python exceptions; they are C++ +exceptions that pybind11 catches and converts to Python exceptions. Noexcept +functions cannot propagate these exceptions either. A useful approach is to +convert them to Python exceptions and then ``discard_as_unraisable`` as shown +below. .. code-block:: cpp @@ -183,8 +278,6 @@ You can convert them to Python exceptions and then discard as unraisable. eas.discard_as_unraisable(__func__); } catch (const std::exception &e) { // Log and discard C++ exceptions. - // (We cannot use discard_as_unraisable, since we have a generic C++ - // exception, not an exception that originated from Python.) third_party::log(e); } } diff --git a/docs/advanced/pycpp/object.rst b/docs/advanced/pycpp/object.rst index 19a226a876..07525d0dc7 100644 --- a/docs/advanced/pycpp/object.rst +++ b/docs/advanced/pycpp/object.rst @@ -1,6 +1,8 @@ Python types ############ +.. _wrappers: + Available wrappers ================== @@ -168,3 +170,11 @@ Generalized unpacking according to PEP448_ is also supported: Python functions from C++, including keywords arguments and unpacking. .. _PEP448: https://www.python.org/dev/peps/pep-0448/ + +Handling exceptions +=================== + +Python exceptions from wrapper classes will be thrown as a ``py::error_already_set``. +See :ref:`Handling exceptions from Python in C++ +` for more information on handling exceptions +raised when calling C++ wrapper classes. From 4493751a5f8c74b63e64088594e2d59af2f84639 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Sun, 23 Aug 2020 18:35:51 +0200 Subject: [PATCH 037/295] Fix new-style __init__ usage in numpy docs (#2426) --- docs/advanced/pycpp/numpy.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/advanced/pycpp/numpy.rst b/docs/advanced/pycpp/numpy.rst index 39cfa73650..8e5c6092c4 100644 --- a/docs/advanced/pycpp/numpy.rst +++ b/docs/advanced/pycpp/numpy.rst @@ -81,7 +81,7 @@ buffer objects (e.g. a NumPy matrix). constexpr bool rowMajor = Matrix::Flags & Eigen::RowMajorBit; py::class_(m, "Matrix", py::buffer_protocol()) - .def("__init__", [](py::buffer b) { + .def(py::init([](py::buffer b) { typedef Eigen::Stride Strides; /* Request a buffer descriptor from Python */ @@ -101,8 +101,8 @@ buffer objects (e.g. a NumPy matrix). auto map = Eigen::Map( static_cast(info.ptr), info.shape[0], info.shape[1], strides); - return Matrix(m); - }); + return Matrix(map); + })); For reference, the ``def_buffer()`` call for this Eigen data type should look as follows: From b3d8fec066285c831b69fd77c092f1cc70e38882 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Mon, 24 Aug 2020 00:00:12 +0200 Subject: [PATCH 038/295] Adapt code example in advanced/classes.rst to new handling of forgetting to call the superclass __init__ (#2429) --- docs/advanced/classes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index f7db3eadf1..f4efc68f8b 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -159,7 +159,7 @@ Here is an example: class Dachshund(Dog): def __init__(self, name): - Dog.__init__(self) # Without this, undefined behavior may occur if the C++ portions are referenced. + Dog.__init__(self) # Without this, a TypeError is raised. self.name = name def bark(self): return "yap!" From 43f390ad854028d25b64bc143597bcb646f851d5 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Mon, 24 Aug 2020 20:31:20 +0200 Subject: [PATCH 039/295] Add note that VS2017 requires /permissive- to build in C++17 mode (#2431) * Add note that VS2017 requires /permissive- to build in C++17 mode * ci: test C++17 on MSVC 2017 * ci: args1/2, use args to override max cxx Co-authored-by: Henry Schreiner --- .github/workflows/ci.yml | 46 +++++++++++++++------------------------- docs/basics.rst | 8 +++++++ 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc92101b2e..1db67a8eb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ jobs: matrix: runs-on: [ubuntu-latest, windows-latest, macos-latest] arch: [x64] - max-cxx-std: [17] python: - 2.7 - 3.5 @@ -28,56 +27,47 @@ jobs: - runs-on: ubuntu-latest python: 3.6 arch: x64 - max-cxx-std: 17 - args: "-DPYBIND11_FINDPYTHON=ON" - - runs-on: macos-latest - python: 3.7 - arch: x64 - max-cxx-std: 17 - args: "-DPYBIND11_FINDPYTHON=ON" + args: > + -DPYBIND11_FINDPYTHON=ON - runs-on: windows-2016 python: 3.7 arch: x86 - max-cxx-std: 14 + args2: > + -DCMAKE_CXX_FLAGS="/permissive- /EHsc /GR" - runs-on: windows-latest python: 3.6 arch: x64 - max-cxx-std: 17 - args: "-DPYBIND11_FINDPYTHON=ON" + args: > + -DPYBIND11_FINDPYTHON=ON - runs-on: windows-latest python: 3.7 arch: x64 - max-cxx-std: 17 - runs-on: ubuntu-latest python: 3.9-dev arch: x64 - max-cxx-std: 17 - runs-on: macos-latest python: 3.9-dev arch: x64 - max-cxx-std: 17 + args: > + -DPYBIND11_FINDPYTHON=ON exclude: # Currently 32bit only, and we build 64bit - runs-on: windows-latest python: pypy2 arch: x64 - max-cxx-std: 17 - runs-on: windows-latest python: pypy3 arch: x64 - max-cxx-std: 17 # Currently broken on embed_test - runs-on: windows-latest python: 3.8 arch: x64 - max-cxx-std: 17 - runs-on: windows-latest python: 3.9-dev arch: x64 - max-cxx-std: 17 name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • ${{ matrix.arch }} ${{ matrix.args }}" @@ -92,8 +82,7 @@ jobs: python-version: ${{ matrix.python }} architecture: ${{ matrix.arch }} - - name: Setup Boost - if: runner.os != 'macOS' + - name: Setup Boost (Windows / Linux latest) run: echo "::set-env name=BOOST_ROOT::$BOOST_ROOT_1_72_0" - name: Update CMake @@ -113,8 +102,7 @@ jobs: - name: Prepare env run: python -m pip install -r tests/requirements.txt --prefer-binary - - name: Configure C++11 ${{ matrix.args }} - shell: bash + - name: Configure C++11 ${{ matrix.args1 }} run: > cmake -S . -B . -DPYBIND11_WERROR=ON @@ -138,26 +126,26 @@ jobs: - name: Clean directory run: git clean -fdx - - name: Configure C++${{ matrix.max-cxx-std }} ${{ matrix.args }} - shell: bash + - name: Configure ${{ matrix.args2 }} run: > cmake -S . -B build2 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=${{ matrix.max-cxx-std }} + -DCMAKE_CXX_STANDARD=17 ${{ matrix.args }} + ${{ matrix.args2 }} - - name: Build C++${{ matrix.max-cxx-std }} + - name: Build run: cmake --build build2 -j 2 - - name: Python tests C++${{ matrix.max-cxx-std }} + - name: Python tests run: cmake --build build2 --target pytest - - name: C++${{ matrix.max-cxx-std }} tests + - name: C++ tests run: cmake --build build2 --target cpptest - - name: Interface test C++${{ matrix.max-cxx-std }} + - name: Interface test run: cmake --build build2 --target test_cmake_build clang: diff --git a/docs/basics.rst b/docs/basics.rst index 7bf4d426d3..6bb5f98222 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -35,6 +35,14 @@ Windows On Windows, only **Visual Studio 2015** and newer are supported since pybind11 relies on various C++11 language features that break older versions of Visual Studio. +.. Note:: + + To use the C++17 in Visual Studio 2017 (MSVC 14.1), pybind11 requires the flag + ``/permissive-`` to be passed to the compiler `to enforce standard conformance`_. When + building with Visual Studio 2019, this is not strictly necessary, but still adviced. + +.. _`to enforce standard conformance`: https://docs.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance?view=vs-2017 + To compile and run the tests: .. code-block:: batch From 5b59b7b2638ce7461e96f12c41015134733fb290 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 24 Aug 2020 18:04:37 -0400 Subject: [PATCH 040/295] ci: gha annotations (#2427) --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1db67a8eb3..825631beae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,6 @@ on: jobs: standard: strategy: - fail-fast: false matrix: runs-on: [ubuntu-latest, windows-latest, macos-latest] arch: [x64] @@ -72,6 +71,7 @@ jobs: name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • ${{ matrix.arch }} ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} + continue-on-error: ${{ endsWith(matrix.python, 'dev') }} steps: - uses: actions/checkout@v2 @@ -102,7 +102,10 @@ jobs: - name: Prepare env run: python -m pip install -r tests/requirements.txt --prefer-binary - - name: Configure C++11 ${{ matrix.args1 }} + - name: Setup annotations + run: python -m pip install pytest-github-actions-annotate-failures + + - name: Configure C++11 ${{ matrix.args }} run: > cmake -S . -B . -DPYBIND11_WERROR=ON From a2bb297b325827765853929ac6dc220ac5b15658 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Tue, 25 Aug 2020 18:51:06 +0200 Subject: [PATCH 041/295] Throw exception on returning a unique_ptr or shared_ptr nullptr (or any other holder type) from py::init, rather than crashing (#2430) --- include/pybind11/detail/init.h | 1 + tests/test_factory_constructors.cpp | 4 ++++ tests/test_factory_constructors.py | 9 ++++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index 35b95bcfae..3ef78c1179 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -132,6 +132,7 @@ void construct(value_and_holder &v_h, Alias *alias_ptr, bool) { template void construct(value_and_holder &v_h, Holder holder, bool need_alias) { auto *ptr = holder_helper>::get(holder); + no_nullptr(ptr); // If we need an alias, check that the held pointer is actually an alias instance if (Class::has_alias && need_alias && !is_alias(ptr)) throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance " diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index 5cfbfdc3f8..61cf33d16e 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -154,6 +154,8 @@ TEST_SUBMODULE(factory_constructors, m) { MAKE_TAG_TYPE(TF4); MAKE_TAG_TYPE(TF5); MAKE_TAG_TYPE(null_ptr); + MAKE_TAG_TYPE(null_unique_ptr); + MAKE_TAG_TYPE(null_shared_ptr); MAKE_TAG_TYPE(base); MAKE_TAG_TYPE(invalid_base); MAKE_TAG_TYPE(alias); @@ -194,6 +196,8 @@ TEST_SUBMODULE(factory_constructors, m) { // Returns nullptr: .def(py::init([](null_ptr_tag) { return (TestFactory3 *) nullptr; })) + .def(py::init([](null_unique_ptr_tag) { return std::unique_ptr(); })) + .def(py::init([](null_shared_ptr_tag) { return std::shared_ptr(); })) .def_readwrite("value", &TestFactory3::value) ; diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py index 8465c59e3f..6c4bed165f 100644 --- a/tests/test_factory_constructors.py +++ b/tests/test_factory_constructors.py @@ -41,9 +41,12 @@ def test_init_factory_basic(): z3 = m.TestFactory3("bye") assert z3.value == "bye" - with pytest.raises(TypeError) as excinfo: - m.TestFactory3(tag.null_ptr) - assert str(excinfo.value) == "pybind11::init(): factory function returned nullptr" + for null_ptr_kind in [tag.null_ptr, + tag.null_unique_ptr, + tag.null_shared_ptr]: + with pytest.raises(TypeError) as excinfo: + m.TestFactory3(null_ptr_kind) + assert str(excinfo.value) == "pybind11::init(): factory function returned nullptr" assert [i.alive() for i in cstats] == [3, 3, 3] assert ConstructorStats.detail_reg_inst() == n_inst + 9 From 03b3d59d10b2b8fa67c4049fd3e505b7b9322dfe Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Wed, 26 Aug 2020 05:51:07 +0200 Subject: [PATCH 042/295] tests: fix CI by including to stop MSVC from complaining about std::count_if in tests/test_sequences_and_iterators.cpp (#2435) --- tests/test_sequences_and_iterators.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_sequences_and_iterators.cpp b/tests/test_sequences_and_iterators.cpp index 05f999bb3b..1ce0451092 100644 --- a/tests/test_sequences_and_iterators.cpp +++ b/tests/test_sequences_and_iterators.cpp @@ -13,6 +13,8 @@ #include #include +#include + template class NonZeroIterator { const T* ptr_; From 9b8cb02030e45443a8a4c5526cdb8093caa481eb Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 26 Aug 2020 09:07:30 -0400 Subject: [PATCH 043/295] fix: respect PYTHON_VERSION if set in classic mode (#2414) * fix: respect PYTHON_VERSION if set in classic mode * fix: add warning when using PYTHON_VERSION --- tools/pybind11Common.cmake | 2 +- tools/pybind11Tools.cmake | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index f05845179f..8f7f57b517 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -147,7 +147,7 @@ if(PYBIND11_CPP_STANDARD) else() set(supported_standards 11 14 17 20) if("${VAL}" IN_LIST supported_standards) - message(WARNING "USE -DCMAKE_CXX_STANDARD=${VAL} instead of PYBIND11_PYTHON_VERSION") + message(WARNING "USE -DCMAKE_CXX_STANDARD=${VAL} instead of PYBIND11_CPP_STANDARD") set(CMAKE_CXX_STANDARD ${VAL} CACHE STRING "From PYBIND11_CPP_STANDARD") diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 809cc5876d..10f15a3091 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -12,10 +12,20 @@ if(pybind11_FIND_QUIETLY) set(_pybind11_quiet QUIET) endif() -# Add a CMake parameter for choosing a desired Python version -if(NOT PYBIND11_PYTHON_VERSION) +# If this is the first run, PYTHON_VERSION can stand in for PYBIND11_PYTHON_VERSION +if(NOT DEFINED PYBIND11_PYTHON_VERSION AND DEFINED PYTHON_VERSION) + message(WARNING "Set PYBIND11_PYTHON_VERSION to search for a specific version, not " + "PYTHON_VERSION (which is an output). Assuming that is what you " + "meant to do and continuing anyway.") set(PYBIND11_PYTHON_VERSION - "" + "${PYTHON_VERSION}" + CACHE STRING "Python version to use for compiling modules") + unset(PYTHON_VERSION) + unset(PYTHON_VERSION CACHE) +else() + # If this is set as a normal variable, promote it, otherwise, make an empty cache variable. + set(PYBIND11_PYTHON_VERSION + "${PYBIND11_PYTHON_VERSION}" CACHE STRING "Python version to use for compiling modules") endif() From 1abc4a9de5d3dfbe3b4b174ba144259d130a59ff Mon Sep 17 00:00:00 2001 From: Dekken Date: Thu, 27 Aug 2020 00:55:18 +0200 Subject: [PATCH 044/295] fix: doc typo, drop second use of 'without' (#2439) --- include/pybind11/pytypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index c322ff27ba..e2b63757d7 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -240,7 +240,7 @@ class object : public handle { ~object() { dec_ref(); } /** \rst - Resets the internal pointer to ``nullptr`` without without decreasing the + Resets the internal pointer to ``nullptr`` without decreasing the object's reference count. The function returns a raw handle to the original Python object. \endrst */ From 6a192781fca28fd997f0220989605b759d65d6ad Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Fri, 28 Aug 2020 15:21:43 +0200 Subject: [PATCH 045/295] Fix bug roundtripping datetime.time objects after midnight in eastern hemisphere timezones (#2417) (#2438) * Fix bug roundtripping datetime.time objects after midnight in eastern hemisphere timezones (#2417) * tests: check more timezones * Fix review remarks: remove useless comment and skip setting TZ environment variable on Windows --- include/pybind11/chrono.h | 17 ++++++++++++----- tests/test_chrono.cpp | 1 + tests/test_chrono.py | 29 +++++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/include/pybind11/chrono.h b/include/pybind11/chrono.h index 6b9ab9b82a..6127c659bd 100644 --- a/include/pybind11/chrono.h +++ b/include/pybind11/chrono.h @@ -150,21 +150,28 @@ template class type_caster(src)); + // Get out microseconds, and make sure they are positive, to avoid bug in eastern hemisphere time zones + // (cfr. https://github.com/pybind/pybind11/issues/2417) + using us_t = duration; + auto us = duration_cast(src.time_since_epoch() % seconds(1)); + if (us.count() < 0) + us += seconds(1); + + // Subtract microseconds BEFORE `system_clock::to_time_t`, because: + // > If std::time_t has lower precision, it is implementation-defined whether the value is rounded or truncated. + // (https://en.cppreference.com/w/cpp/chrono/system_clock/to_time_t) + std::time_t tt = system_clock::to_time_t(time_point_cast(src - us)); // this function uses static memory so it's best to copy it out asap just in case // otherwise other code that is using localtime may break this (not just python code) std::tm localtime = *std::localtime(&tt); - // Declare these special duration types so the conversions happen with the correct primitive types (int) - using us_t = duration; - return PyDateTime_FromDateAndTime(localtime.tm_year + 1900, localtime.tm_mon + 1, localtime.tm_mday, localtime.tm_hour, localtime.tm_min, localtime.tm_sec, - (duration_cast(src.time_since_epoch() % seconds(1))).count()); + us.count()); } PYBIND11_TYPE_CASTER(type, _("datetime.datetime")); }; diff --git a/tests/test_chrono.cpp b/tests/test_chrono.cpp index 899d08d8d8..1d79d4b6ca 100644 --- a/tests/test_chrono.cpp +++ b/tests/test_chrono.cpp @@ -10,6 +10,7 @@ #include "pybind11_tests.h" #include +#include TEST_SUBMODULE(chrono, m) { using system_time = std::chrono::system_clock::time_point; diff --git a/tests/test_chrono.py b/tests/test_chrono.py index 5392f8ff0d..f94d5ba979 100644 --- a/tests/test_chrono.py +++ b/tests/test_chrono.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- from pybind11_tests import chrono as m import datetime +import pytest + +import env # noqa: F401 def test_chrono_system_clock(): @@ -70,8 +73,30 @@ def test_chrono_system_clock_roundtrip_date(): assert time2.microsecond == 0 -def test_chrono_system_clock_roundtrip_time(): - time1 = datetime.datetime.today().time() +SKIP_TZ_ENV_ON_WIN = pytest.mark.skipif( + "env.WIN", reason="TZ environment variable only supported on POSIX" +) + + +@pytest.mark.parametrize("time1", [ + datetime.datetime.today().time(), + datetime.time(0, 0, 0), + datetime.time(0, 0, 0, 1), + datetime.time(0, 28, 45, 109827), + datetime.time(0, 59, 59, 999999), + datetime.time(1, 0, 0), + datetime.time(5, 59, 59, 0), + datetime.time(5, 59, 59, 1), +]) +@pytest.mark.parametrize("tz", [ + None, + pytest.param("Europe/Brussels", marks=SKIP_TZ_ENV_ON_WIN), + pytest.param("Asia/Pyongyang", marks=SKIP_TZ_ENV_ON_WIN), + pytest.param("America/New_York", marks=SKIP_TZ_ENV_ON_WIN), +]) +def test_chrono_system_clock_roundtrip_time(time1, tz, monkeypatch): + if tz is not None: + monkeypatch.setenv("TZ", "/usr/share/zoneinfo/{}".format(tz)) # Roundtrip the time datetime2 = m.test_chrono2(time1) From 3c061f216899485399fbb1e6b7732fa0051733f1 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 27 Aug 2020 17:49:52 -0700 Subject: [PATCH 046/295] Fixing `pybind11::bytes()` ambiguous conversion issue. Adding missing `bytes` type to `test_constructors()`, to exercise the code change. The changes in the PR were cherry-picked from PR #2409 (with a very minor modification in test_pytypes.py related to flake8). Via PR #2409, these changes were extensively tested in the Google environment, as summarized here: https://docs.google.com/document/d/1TPL-J__mph_yHa1quDvsO12E_F5OZnvBaZlW9IIrz8M/ The changes in this PR did not cause an issues at all. Note that `test_constructors()` before this PR passes for Python 2 only because `pybind11::str` can hold `PyUnicodeObject` or `PyBytesObject`. As a side-effect of this PR, `test_constructors()` no longer relies on this permissive `pybind11::str` behavior. However, the permissive behavior is still exercised/exposed via the existing `test_pybind11_str_raw_str()`. The test code change is designed to enable easy removal later, when Python 2 support is dropped. For completeness: confusingly, the non-test code changes travelled through PR Example `ambiguous conversion` error fixed by this PR: ``` pybind11/tests/test_pytypes.cpp:214:23: error: ambiguous conversion for functional-style cast from 'pybind11::detail::item_accessor' (aka 'accessor') to 'py::bytes' "bytes"_a=py::bytes(d["bytes"]), ^~~~~~~~~~~~~~~~~~~~ pybind11/include/pybind11/detail/../pytypes.h:957:21: note: candidate constructor PYBIND11_OBJECT(bytes, object, PYBIND11_BYTES_CHECK) ^ pybind11/include/pybind11/detail/../pytypes.h:957:21: note: candidate constructor pybind11/include/pybind11/detail/../pytypes.h:987:15: note: candidate constructor inline bytes::bytes(const pybind11::str &s) { ^ 1 error generated. ``` --- include/pybind11/pytypes.h | 8 ++++---- tests/test_pytypes.cpp | 3 +++ tests/test_pytypes.py | 13 ++++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index e2b63757d7..bea34cd936 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -796,7 +796,9 @@ PYBIND11_NAMESPACE_END(detail) Name(handle h, stolen_t) : Parent(h, stolen_t{}) { } \ PYBIND11_DEPRECATED("Use py::isinstance(obj) instead") \ bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); } \ - static bool check_(handle h) { return h.ptr() != nullptr && CheckFun(h.ptr()); } + static bool check_(handle h) { return h.ptr() != nullptr && CheckFun(h.ptr()); } \ + template \ + Name(const ::pybind11::detail::accessor &a) : Name(object(a)) { } #define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \ PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \ @@ -806,9 +808,7 @@ PYBIND11_NAMESPACE_END(detail) { if (!m_ptr) throw error_already_set(); } \ Name(object &&o) \ : Parent(check_(o) ? o.release().ptr() : ConvertFun(o.ptr()), stolen_t{}) \ - { if (!m_ptr) throw error_already_set(); } \ - template \ - Name(const ::pybind11::detail::accessor &a) : Name(object(a)) { } + { if (!m_ptr) throw error_already_set(); } #define PYBIND11_OBJECT(Name, Parent, CheckFun) \ PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \ diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 9dae6e7d62..0f8d56410f 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -197,6 +197,7 @@ TEST_SUBMODULE(pytypes, m) { // test_constructors m.def("default_constructors", []() { return py::dict( + "bytes"_a=py::bytes(), "str"_a=py::str(), "bool"_a=py::bool_(), "int"_a=py::int_(), @@ -210,6 +211,7 @@ TEST_SUBMODULE(pytypes, m) { m.def("converting_constructors", [](py::dict d) { return py::dict( + "bytes"_a=py::bytes(d["bytes"]), "str"_a=py::str(d["str"]), "bool"_a=py::bool_(d["bool"]), "int"_a=py::int_(d["int"]), @@ -225,6 +227,7 @@ TEST_SUBMODULE(pytypes, m) { m.def("cast_functions", [](py::dict d) { // When converting between Python types, obj.cast() should be the same as T(obj) return py::dict( + "bytes"_a=d["bytes"].cast(), "str"_a=d["str"].cast(), "bool"_a=d["bool"].cast(), "int"_a=d["int"].cast(), diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index c21ad61146..95cc94af8c 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -190,11 +190,17 @@ def func(self, x, *args): def test_constructors(): """C++ default and converting constructors are equivalent to type calls in Python""" - types = [str, bool, int, float, tuple, list, dict, set] + types = [bytes, str, bool, int, float, tuple, list, dict, set] expected = {t.__name__: t() for t in types} + if env.PY2: + # Note that bytes.__name__ == 'str' in Python 2. + # pybind11::str is unicode even under Python 2. + expected["bytes"] = bytes() + expected["str"] = unicode() # noqa: F821 assert m.default_constructors() == expected data = { + bytes: b'41', # Currently no supported or working conversions. str: 42, bool: "Not empty", int: "42", @@ -207,6 +213,11 @@ def test_constructors(): } inputs = {k.__name__: v for k, v in data.items()} expected = {k.__name__: k(v) for k, v in data.items()} + if env.PY2: # Similar to the above. See comments above. + inputs["bytes"] = b'41' + inputs["str"] = 42 + expected["bytes"] = b'41' + expected["str"] = u"42" assert m.converting_constructors(inputs) == expected assert m.cast_functions(inputs) == expected From fb0a3a0e82ae5f62a382b5c59ac53873f33089db Mon Sep 17 00:00:00 2001 From: Daniel Saxton <2658661+dsaxton@users.noreply.github.com> Date: Mon, 31 Aug 2020 09:01:08 -0500 Subject: [PATCH 047/295] Fix broken README link (#2449) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7634972cb4..bae6cf2b5c 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,6 @@ you agree to the terms and conditions of this license. [docs-pdf]: https://media.readthedocs.org/pdf/pybind11/master/pybind11.pdf [Boost.Python]: http://www.boost.org/doc/libs/1_58_0/libs/python/doc/ [pyrosetta-report]: http://graylab.jhu.edu/RosettaCon2016/PyRosetta-4.pdf -[contributing guide]: https://github.com/pybind/pybind11/blob/master/CONTRIBUTING.md +[contributing guide]: https://github.com/pybind/pybind11/blob/master/.github/CONTRIBUTING.md [`LICENSE`]: https://github.com/pybind/pybind11/blob/master/LICENSE [intel-15-workaround]: https://github.com/pybind/pybind11/issues/276 From 3a89bffac0d95a856d1d2367ca1b9e28ac4ef663 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 31 Aug 2020 14:28:07 -0400 Subject: [PATCH 048/295] ci: harden chrono test, mark another macos 4.9 dev failure (#2448) * ci: harden chrono test, mark another macos 4.9 dev failure This should help with a little of the flakiness seen with the timing test * Update tests/test_chrono.py * Can also fail --- tests/test_chrono.py | 6 ++++-- tests/test_gil_scoped.py | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_chrono.py b/tests/test_chrono.py index f94d5ba979..76783905a3 100644 --- a/tests/test_chrono.py +++ b/tests/test_chrono.py @@ -9,6 +9,7 @@ def test_chrono_system_clock(): # Get the time from both c++ and datetime + date0 = datetime.datetime.today() date1 = m.test_chrono1() date2 = datetime.datetime.today() @@ -16,14 +17,15 @@ def test_chrono_system_clock(): assert isinstance(date1, datetime.datetime) # The numbers should vary by a very small amount (time it took to execute) + diff_python = abs(date2 - date0) diff = abs(date1 - date2) # There should never be a days difference assert diff.days == 0 # Since datetime.datetime.today() calls time.time(), and on some platforms - # that has 1 second accuracy, we should always be less than 2 seconds. - assert diff.seconds < 2 + # that has 1 second accuracy, we compare this way + assert diff.seconds <= diff_python.seconds def test_chrono_system_clock_roundtrip(): diff --git a/tests/test_gil_scoped.py b/tests/test_gil_scoped.py index c85eb7c72b..27122cca28 100644 --- a/tests/test_gil_scoped.py +++ b/tests/test_gil_scoped.py @@ -54,6 +54,8 @@ def _python_to_cpp_to_python_from_threads(num_threads, parallel=False): thread.join() +# TODO: FIXME, sometimes returns -11 instead of 0 +@pytest.mark.xfail("env.PY > (3,8) and env.MACOS", strict=False) def test_python_to_cpp_to_python_from_thread(): """Makes sure there is no GIL deadlock when running in a thread. @@ -72,6 +74,8 @@ def test_python_to_cpp_to_python_from_thread_multiple_parallel(): assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=True) == 0 +# TODO: FIXME +@pytest.mark.xfail("env.PY > (3,8) and env.MACOS", strict=False) def test_python_to_cpp_to_python_from_thread_multiple_sequential(): """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially. From 4c36fb7b1236fce25e00b63f357ccc36dc006662 Mon Sep 17 00:00:00 2001 From: Sergei Izmailov Date: Tue, 1 Sep 2020 15:56:43 +0300 Subject: [PATCH 049/295] [DOC] avoid C++ types in docstrings (#2441) * doc: avoid C++ types in docstrings * A bit of rewording * Another bit of rewording * Third rewording --- docs/advanced/misc.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index 7798462df7..0a73dae7e7 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -304,3 +304,34 @@ the default settings are restored to prevent unwanted side effects. .. [#f4] http://www.sphinx-doc.org .. [#f5] http://github.com/pybind/python_example + +.. _avoiding-cpp-types-in-docstrings: + +Avoiding C++ types in docstrings +================================ + +Docstrings are generated at the time of the declaration, e.g. when ``.def(...)`` is called. +At this point parameter and return types should be known to pybind11. +If a custom type is not exposed yet through a ``py::class_`` constructor or a custom type caster, +its C++ type name will be used instead to generate the signature in the docstring: + +.. code-block:: text + + | __init__(...) + | __init__(self: example.Foo, arg0: ns::Bar) -> None + ^^^^^^^ + + +This limitation can be circumvented by ensuring that C++ classes are registered with pybind11 +before they are used as a parameter or return type of a function: + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + + auto pyFoo = py::class_(m, "Foo"); + auto pyBar = py::class_(m, "Bar"); + + pyFoo.def(py::init()); + pyBar.def(py::init()); + } From 72b06b86b3824781f31c790dfce67e26e6307816 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 3 Sep 2020 12:47:30 -0400 Subject: [PATCH 050/295] ci: Eigen moved --- .appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index b33a4ccf4e..149a8a3dc9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -21,9 +21,9 @@ install: python -W ignore -m pip install --upgrade pip wheel python -W ignore -m pip install pytest numpy --no-warn-script-location - ps: | - Start-FileDownload 'http://bitbucket.org/eigen/eigen/get/3.3.3.zip' - 7z x 3.3.3.zip -y > $null - $env:CMAKE_INCLUDE_PATH = "eigen-eigen-67e894c6cd8f;$env:CMAKE_INCLUDE_PATH" + Start-FileDownload 'https://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.zip' + 7z x eigen-3.3.7.zip -y > $null + $env:CMAKE_INCLUDE_PATH = "eigen-3.3.7;$env:CMAKE_INCLUDE_PATH" build_script: - cmake -G "%CMAKE_GENERATOR%" -A "%CMAKE_ARCH%" -DCMAKE_CXX_STANDARD=14 From 44fa79ca8064a0f476fa9095c9c1436c26d2fb12 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Fri, 4 Sep 2020 19:26:57 -0400 Subject: [PATCH 051/295] pytypes: Add Gotchas section about default-constructed wrapper types and py::none() (#2362) --- docs/advanced/pycpp/object.rst | 27 +++++++++++++++++++++++++++ tests/test_pytypes.cpp | 10 ++++++++++ tests/test_pytypes.py | 8 ++++++++ 3 files changed, 45 insertions(+) diff --git a/docs/advanced/pycpp/object.rst b/docs/advanced/pycpp/object.rst index 07525d0dc7..c6c3b1b75b 100644 --- a/docs/advanced/pycpp/object.rst +++ b/docs/advanced/pycpp/object.rst @@ -15,6 +15,11 @@ Available types include :class:`handle`, :class:`object`, :class:`bool_`, :class:`iterable`, :class:`iterator`, :class:`function`, :class:`buffer`, :class:`array`, and :class:`array_t`. +.. warning:: + + Be sure to review the :ref:`pytypes_gotchas` before using this heavily in + your C++ API. + Casting back and forth ====================== @@ -178,3 +183,25 @@ Python exceptions from wrapper classes will be thrown as a ``py::error_already_s See :ref:`Handling exceptions from Python in C++ ` for more information on handling exceptions raised when calling C++ wrapper classes. + +.. _pytypes_gotchas: + +Gotchas +======= + +Default-Constructed Wrappers +---------------------------- + +When a wrapper type is default-constructed, it is **not** a valid Python object (i.e. it is not ``py::none()``). It is simply the same as +``PyObject*`` null pointer. To check for this, use +``static_cast(my_wrapper)``. + +Assigning py::none() to wrappers +-------------------------------- + +You may be tempted to use types like ``py::str`` and ``py::dict`` in C++ +signatures (either pure C++, or in bound signatures), and assign them default +values of ``py::none()``. However, in a best case scenario, it will fail fast +because ``None`` is not convertible to that type (e.g. ``py::dict``), or in a +worse case scenario, it will silently work but corrupt the types you want to +work with (e.g. ``py::str(py::none())`` will yield ``"None"`` in Python). diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 0f8d56410f..925d6ffd27 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -324,6 +324,16 @@ TEST_SUBMODULE(pytypes, m) { return a[py::slice(0, -1, 2)]; }); + // See #2361 + m.def("issue2361_str_implicit_copy_none", []() { + py::str is_this_none = py::none(); + return is_this_none; + }); + m.def("issue2361_dict_implicit_copy_none", []() { + py::dict is_this_none = py::none(); + return is_this_none; + }); + m.def("test_memoryview_object", [](py::buffer b) { return py::memoryview(b); }); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 95cc94af8c..277c170ebe 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -326,6 +326,14 @@ def test_list_slicing(): assert li[::2] == m.test_list_slicing(li) +def test_issue2361(): + # See issue #2361 + assert m.issue2361_str_implicit_copy_none() == "None" + with pytest.raises(TypeError) as excinfo: + assert m.issue2361_dict_implicit_copy_none() + assert "'NoneType' object is not iterable" in str(excinfo.value) + + @pytest.mark.parametrize('method, args, fmt, expected_view', [ (m.test_memoryview_object, (b'red',), 'B', b'red'), (m.test_memoryview_buffer_info, (b'green',), 'B', b'green'), From 0dbda6e80ba41df50edaef40475011d0395aba16 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 4 Sep 2020 20:02:05 -0400 Subject: [PATCH 052/295] feat: py::pos_only (#2459) * feat: py::pos_only * fix: review points from @YannickJadoul * fix: review points from @bstaletic * refactor: kwonly -> kw_only --- docs/advanced/functions.rst | 26 +++++++-- docs/changelog.rst | 4 +- include/pybind11/attr.h | 34 +++++++----- include/pybind11/cast.h | 7 ++- include/pybind11/pybind11.h | 40 ++++++++++++-- tests/test_kwargs_and_defaults.cpp | 45 ++++++++++------ tests/test_kwargs_and_defaults.py | 84 +++++++++++++++++++++++------- 7 files changed, 182 insertions(+), 58 deletions(-) diff --git a/docs/advanced/functions.rst b/docs/advanced/functions.rst index 3e33c9cf7d..2814adfbaa 100644 --- a/docs/advanced/functions.rst +++ b/docs/advanced/functions.rst @@ -378,17 +378,35 @@ argument in a function definition: f(1, b=2) # good f(1, 2) # TypeError: f() takes 1 positional argument but 2 were given -Pybind11 provides a ``py::kwonly`` object that allows you to implement +Pybind11 provides a ``py::kw_only`` object that allows you to implement the same behaviour by specifying the object between positional and keyword-only argument annotations when registering the function: .. code-block:: cpp m.def("f", [](int a, int b) { /* ... */ }, - py::arg("a"), py::kwonly(), py::arg("b")); + py::arg("a"), py::kw_only(), py::arg("b")); -Note that, as in Python, you cannot combine this with a ``py::args`` argument. -This feature does *not* require Python 3 to work. +Note that you currently cannot combine this with a ``py::args`` argument. This +feature does *not* require Python 3 to work. + +.. versionadded:: 2.6 + +Positional-only arguments +========================= + +Python 3.8 introduced a new positional-only argument syntax, using ``/`` in the +function definition (note that this has been a convention for CPython +positional arguments, such as in ``pow()``, since Python 2). You can +do the same thing in any version of Python using ``py::pos_only()``: + +.. code-block:: cpp + + m.def("f", [](int a, int b) { /* ... */ }, + py::arg("a"), py::pos_only(), py::arg("b")); + +You now cannot give argument ``a`` by keyword. This can be combined with +keyword-only arguments, as well. .. versionadded:: 2.6 diff --git a/docs/changelog.rst b/docs/changelog.rst index 0e15621d28..36381eb537 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,9 +11,11 @@ v2.6.0 (IN PROGRESS) See :ref:`upgrade-guide-2.6` for help upgrading to the new version. -* Keyword only argument supported in Python 2 or 3 with ``py::kwonly()``. +* Keyword-only argument supported in Python 2 or 3 with ``py::kw_only()``. `#2100 `_ +* Positional-only argument supported in Python 2 or 3 with ``py::pos_only()``. + * Perfect forwarding support for methods. `#2048 `_ diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 54065fc9e1..aedbf62f1c 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -138,7 +138,7 @@ struct function_record { function_record() : is_constructor(false), is_new_style_constructor(false), is_stateless(false), is_operator(false), is_method(false), - has_args(false), has_kwargs(false), has_kwonly_args(false) { } + has_args(false), has_kwargs(false), has_kw_only_args(false) { } /// Function name char *name = nullptr; /* why no C++ strings? They generate heavier code.. */ @@ -185,14 +185,17 @@ struct function_record { /// True if the function has a '**kwargs' argument bool has_kwargs : 1; - /// True once a 'py::kwonly' is encountered (any following args are keyword-only) - bool has_kwonly_args : 1; + /// True once a 'py::kw_only' is encountered (any following args are keyword-only) + bool has_kw_only_args : 1; /// Number of arguments (including py::args and/or py::kwargs, if present) std::uint16_t nargs; /// Number of trailing arguments (counted in `nargs`) that are keyword-only - std::uint16_t nargs_kwonly = 0; + std::uint16_t nargs_kw_only = 0; + + /// Number of leading arguments (counted in `nargs`) that are positional-only + std::uint16_t nargs_pos_only = 0; /// Python method object PyMethodDef *def = nullptr; @@ -366,10 +369,10 @@ template <> struct process_attribute : process_attribu static void init(const is_new_style_constructor &, function_record *r) { r->is_new_style_constructor = true; } }; -inline void process_kwonly_arg(const arg &a, function_record *r) { +inline void process_kw_only_arg(const arg &a, function_record *r) { if (!a.name || strlen(a.name) == 0) - pybind11_fail("arg(): cannot specify an unnamed argument after an kwonly() annotation"); - ++r->nargs_kwonly; + pybind11_fail("arg(): cannot specify an unnamed argument after an kw_only() annotation"); + ++r->nargs_kw_only; } /// Process a keyword argument attribute (*without* a default value) @@ -379,7 +382,7 @@ template <> struct process_attribute : process_attribute_default { r->args.emplace_back("self", nullptr, handle(), true /*convert*/, false /*none not allowed*/); r->args.emplace_back(a.name, nullptr, handle(), !a.flag_noconvert, a.flag_none); - if (r->has_kwonly_args) process_kwonly_arg(a, r); + if (r->has_kw_only_args) process_kw_only_arg(a, r); } }; @@ -412,14 +415,21 @@ template <> struct process_attribute : process_attribute_default { } r->args.emplace_back(a.name, a.descr, a.value.inc_ref(), !a.flag_noconvert, a.flag_none); - if (r->has_kwonly_args) process_kwonly_arg(a, r); + if (r->has_kw_only_args) process_kw_only_arg(a, r); } }; /// Process a keyword-only-arguments-follow pseudo argument -template <> struct process_attribute : process_attribute_default { - static void init(const kwonly &, function_record *r) { - r->has_kwonly_args = true; +template <> struct process_attribute : process_attribute_default { + static void init(const kw_only &, function_record *r) { + r->has_kw_only_args = true; + } +}; + +/// Process a positional-only-argument maker +template <> struct process_attribute : process_attribute_default { + static void init(const pos_only &, function_record *r) { + r->nargs_pos_only = static_cast(r->args.size()); } }; diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 5711004df9..bae69c8ac0 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1910,7 +1910,12 @@ struct arg_v : arg { /// \ingroup annotations /// Annotation indicating that all following arguments are keyword-only; the is the equivalent of an /// unnamed '*' argument (in Python 3) -struct kwonly {}; +struct kw_only {}; + +/// \ingroup annotations +/// Annotation indicating that all previous arguments are positional-only; the is the equivalent of an +/// unnamed '/' argument (in Python 3.8) +struct pos_only {}; template arg_v arg::operator=(T &&value) const { return {std::move(*this), std::forward(value)}; } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 3a7d7b8849..602d5790a2 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -187,11 +187,13 @@ class cpp_function : public function { process_attributes::init(extra..., rec); { - constexpr bool has_kwonly_args = any_of...>::value, + constexpr bool has_kw_only_args = any_of...>::value, + has_pos_only_args = any_of...>::value, has_args = any_of...>::value, has_arg_annotations = any_of...>::value; - static_assert(has_arg_annotations || !has_kwonly_args, "py::kwonly requires the use of argument annotations"); - static_assert(!(has_args && has_kwonly_args), "py::kwonly cannot be combined with a py::args argument"); + static_assert(has_arg_annotations || !has_kw_only_args, "py::kw_only requires the use of argument annotations"); + static_assert(has_arg_annotations || !has_pos_only_args, "py::pos_only requires the use of argument annotations (for docstrings and aligning the annotations to the argument)"); + static_assert(!(has_args && has_kw_only_args), "py::kw_only cannot be combined with a py::args argument"); } /* Generate a readable signature describing the function's arguments and return value types */ @@ -257,7 +259,10 @@ class cpp_function : public function { // Write arg name for everything except *args and **kwargs. if (*(pc + 1) == '*') continue; - + // Separator for keyword-only arguments, placed before the kw + // arguments start + if (rec->nargs_kw_only > 0 && arg_index + rec->nargs_kw_only == args) + signature += "*, "; if (arg_index < rec->args.size() && rec->args[arg_index].name) { signature += rec->args[arg_index].name; } else if (arg_index == 0 && rec->is_method) { @@ -272,6 +277,10 @@ class cpp_function : public function { signature += " = "; signature += rec->args[arg_index].descr; } + // Separator for positional-only arguments (placed after the + // argument, rather than before like * + if (rec->nargs_pos_only > 0 && (arg_index + 1) == rec->nargs_pos_only) + signature += ", /"; arg_index++; } else if (c == '%') { const std::type_info *t = types[type_index++]; @@ -297,6 +306,7 @@ class cpp_function : public function { signature += c; } } + if (arg_index != args || types[type_index] != nullptr) pybind11_fail("Internal error while parsing type signature (2)"); @@ -512,7 +522,7 @@ class cpp_function : public function { size_t num_args = func.nargs; // Number of positional arguments that we need if (func.has_args) --num_args; // (but don't count py::args if (func.has_kwargs) --num_args; // or py::kwargs) - size_t pos_args = num_args - func.nargs_kwonly; + size_t pos_args = num_args - func.nargs_kw_only; if (!func.has_args && n_args_in > pos_args) continue; // Too many positional arguments for this overload @@ -561,6 +571,26 @@ class cpp_function : public function { // We'll need to copy this if we steal some kwargs for defaults dict kwargs = reinterpret_borrow(kwargs_in); + // 1.5. Fill in any missing pos_only args from defaults if they exist + if (args_copied < func.nargs_pos_only) { + for (; args_copied < func.nargs_pos_only; ++args_copied) { + const auto &arg = func.args[args_copied]; + handle value; + + if (arg.value) { + value = arg.value; + } + if (value) { + call.args.push_back(value); + call.args_convert.push_back(arg.convert); + } else + break; + } + + if (args_copied < func.nargs_pos_only) + continue; // Not enough defaults to fill the positional arguments + } + // 2. Check kwargs and, failing that, defaults that may help complete the list if (args_copied < num_args) { bool copied_kwargs = false; diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp index 64bc2377b2..641ec88c45 100644 --- a/tests/test_kwargs_and_defaults.cpp +++ b/tests/test_kwargs_and_defaults.cpp @@ -95,28 +95,39 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { // m.def("bad_args7", [](py::kwargs, py::kwargs) {}); // test_keyword_only_args - m.def("kwonly_all", [](int i, int j) { return py::make_tuple(i, j); }, - py::kwonly(), py::arg("i"), py::arg("j")); - m.def("kwonly_some", [](int i, int j, int k) { return py::make_tuple(i, j, k); }, - py::arg(), py::kwonly(), py::arg("j"), py::arg("k")); - m.def("kwonly_with_defaults", [](int i, int j, int k, int z) { return py::make_tuple(i, j, k, z); }, - py::arg() = 3, "j"_a = 4, py::kwonly(), "k"_a = 5, "z"_a); - m.def("kwonly_mixed", [](int i, int j) { return py::make_tuple(i, j); }, - "i"_a, py::kwonly(), "j"_a); - m.def("kwonly_plus_more", [](int i, int j, int k, py::kwargs kwargs) { + m.def("kw_only_all", [](int i, int j) { return py::make_tuple(i, j); }, + py::kw_only(), py::arg("i"), py::arg("j")); + m.def("kw_only_some", [](int i, int j, int k) { return py::make_tuple(i, j, k); }, + py::arg(), py::kw_only(), py::arg("j"), py::arg("k")); + m.def("kw_only_with_defaults", [](int i, int j, int k, int z) { return py::make_tuple(i, j, k, z); }, + py::arg() = 3, "j"_a = 4, py::kw_only(), "k"_a = 5, "z"_a); + m.def("kw_only_mixed", [](int i, int j) { return py::make_tuple(i, j); }, + "i"_a, py::kw_only(), "j"_a); + m.def("kw_only_plus_more", [](int i, int j, int k, py::kwargs kwargs) { return py::make_tuple(i, j, k, kwargs); }, - py::arg() /* positional */, py::arg("j") = -1 /* both */, py::kwonly(), py::arg("k") /* kw-only */); + py::arg() /* positional */, py::arg("j") = -1 /* both */, py::kw_only(), py::arg("k") /* kw-only */); - m.def("register_invalid_kwonly", [](py::module m) { - m.def("bad_kwonly", [](int i, int j) { return py::make_tuple(i, j); }, - py::kwonly(), py::arg() /* invalid unnamed argument */, "j"_a); + m.def("register_invalid_kw_only", [](py::module m) { + m.def("bad_kw_only", [](int i, int j) { return py::make_tuple(i, j); }, + py::kw_only(), py::arg() /* invalid unnamed argument */, "j"_a); }); + // test_positional_only_args + m.def("pos_only_all", [](int i, int j) { return py::make_tuple(i, j); }, + py::arg("i"), py::arg("j"), py::pos_only()); + m.def("pos_only_mix", [](int i, int j) { return py::make_tuple(i, j); }, + py::arg("i"), py::pos_only(), py::arg("j")); + m.def("pos_kw_only_mix", [](int i, int j, int k) { return py::make_tuple(i, j, k); }, + py::arg("i"), py::pos_only(), py::arg("j"), py::kw_only(), py::arg("k")); + m.def("pos_only_def_mix", [](int i, int j, int k) { return py::make_tuple(i, j, k); }, + py::arg("i"), py::arg("j") = 2, py::pos_only(), py::arg("k") = 3); + + // These should fail to compile: - // argument annotations are required when using kwonly -// m.def("bad_kwonly1", [](int) {}, py::kwonly()); - // can't specify both `py::kwonly` and a `py::args` argument -// m.def("bad_kwonly2", [](int i, py::args) {}, py::kwonly(), "i"_a); + // argument annotations are required when using kw_only +// m.def("bad_kw_only1", [](int) {}, py::kw_only()); + // can't specify both `py::kw_only` and a `py::args` argument +// m.def("bad_kw_only2", [](int i, py::args) {}, py::kw_only(), "i"_a); // test_function_signatures (along with most of the above) struct KWClass { void foo(int, float) {} }; diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index 5257e0cd30..2a81dbdc50 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -112,43 +112,91 @@ def test_mixed_args_and_kwargs(msg): def test_keyword_only_args(msg): - assert m.kwonly_all(i=1, j=2) == (1, 2) - assert m.kwonly_all(j=1, i=2) == (2, 1) + assert m.kw_only_all(i=1, j=2) == (1, 2) + assert m.kw_only_all(j=1, i=2) == (2, 1) with pytest.raises(TypeError) as excinfo: - assert m.kwonly_all(i=1) == (1,) + assert m.kw_only_all(i=1) == (1,) assert "incompatible function arguments" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: - assert m.kwonly_all(1, 2) == (1, 2) + assert m.kw_only_all(1, 2) == (1, 2) assert "incompatible function arguments" in str(excinfo.value) - assert m.kwonly_some(1, k=3, j=2) == (1, 2, 3) + assert m.kw_only_some(1, k=3, j=2) == (1, 2, 3) - assert m.kwonly_with_defaults(z=8) == (3, 4, 5, 8) - assert m.kwonly_with_defaults(2, z=8) == (2, 4, 5, 8) - assert m.kwonly_with_defaults(2, j=7, k=8, z=9) == (2, 7, 8, 9) - assert m.kwonly_with_defaults(2, 7, z=9, k=8) == (2, 7, 8, 9) + assert m.kw_only_with_defaults(z=8) == (3, 4, 5, 8) + assert m.kw_only_with_defaults(2, z=8) == (2, 4, 5, 8) + assert m.kw_only_with_defaults(2, j=7, k=8, z=9) == (2, 7, 8, 9) + assert m.kw_only_with_defaults(2, 7, z=9, k=8) == (2, 7, 8, 9) - assert m.kwonly_mixed(1, j=2) == (1, 2) - assert m.kwonly_mixed(j=2, i=3) == (3, 2) - assert m.kwonly_mixed(i=2, j=3) == (2, 3) + assert m.kw_only_mixed(1, j=2) == (1, 2) + assert m.kw_only_mixed(j=2, i=3) == (3, 2) + assert m.kw_only_mixed(i=2, j=3) == (2, 3) - assert m.kwonly_plus_more(4, 5, k=6, extra=7) == (4, 5, 6, {'extra': 7}) - assert m.kwonly_plus_more(3, k=5, j=4, extra=6) == (3, 4, 5, {'extra': 6}) - assert m.kwonly_plus_more(2, k=3, extra=4) == (2, -1, 3, {'extra': 4}) + assert m.kw_only_plus_more(4, 5, k=6, extra=7) == (4, 5, 6, {'extra': 7}) + assert m.kw_only_plus_more(3, k=5, j=4, extra=6) == (3, 4, 5, {'extra': 6}) + assert m.kw_only_plus_more(2, k=3, extra=4) == (2, -1, 3, {'extra': 4}) with pytest.raises(TypeError) as excinfo: - assert m.kwonly_mixed(i=1) == (1,) + assert m.kw_only_mixed(i=1) == (1,) assert "incompatible function arguments" in str(excinfo.value) with pytest.raises(RuntimeError) as excinfo: - m.register_invalid_kwonly(m) + m.register_invalid_kw_only(m) assert msg(excinfo.value) == """ - arg(): cannot specify an unnamed argument after an kwonly() annotation + arg(): cannot specify an unnamed argument after an kw_only() annotation """ +def test_positional_only_args(msg): + assert m.pos_only_all(1, 2) == (1, 2) + assert m.pos_only_all(2, 1) == (2, 1) + + with pytest.raises(TypeError) as excinfo: + m.pos_only_all(i=1, j=2) + assert "incompatible function arguments" in str(excinfo.value) + + assert m.pos_only_mix(1, 2) == (1, 2) + assert m.pos_only_mix(2, j=1) == (2, 1) + + with pytest.raises(TypeError) as excinfo: + m.pos_only_mix(i=1, j=2) + assert "incompatible function arguments" in str(excinfo.value) + + assert m.pos_kw_only_mix(1, 2, k=3) == (1, 2, 3) + assert m.pos_kw_only_mix(1, j=2, k=3) == (1, 2, 3) + + with pytest.raises(TypeError) as excinfo: + m.pos_kw_only_mix(i=1, j=2, k=3) + assert "incompatible function arguments" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + m.pos_kw_only_mix(1, 2, 3) + assert "incompatible function arguments" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + m.pos_only_def_mix() + assert "incompatible function arguments" in str(excinfo.value) + + assert m.pos_only_def_mix(1) == (1, 2, 3) + assert m.pos_only_def_mix(1, 4) == (1, 4, 3) + assert m.pos_only_def_mix(1, 4, 7) == (1, 4, 7) + assert m.pos_only_def_mix(1, 4, k=7) == (1, 4, 7) + + with pytest.raises(TypeError) as excinfo: + m.pos_only_def_mix(1, j=4) + assert "incompatible function arguments" in str(excinfo.value) + + +def test_signatures(): + assert "kw_only_all(*, i: int, j: int) -> tuple\n" == m.kw_only_all.__doc__ + assert "kw_only_mixed(i: int, *, j: int) -> tuple\n" == m.kw_only_mixed.__doc__ + assert "pos_only_all(i: int, j: int, /) -> tuple\n" == m.pos_only_all.__doc__ + assert "pos_only_mix(i: int, /, j: int) -> tuple\n" == m.pos_only_mix.__doc__ + assert "pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n" == m.pos_kw_only_mix.__doc__ + + @pytest.mark.xfail("env.PYPY and env.PY2", reason="PyPy2 doesn't double count") def test_args_refcount(): """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular From ce1a07ef45d204e6e72d7039ca6764cac90b026e Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 4 Sep 2020 21:54:09 -0400 Subject: [PATCH 053/295] fix: use classic extension handling unless otherwise requested (#2462) * fix: use classic extension handling unless otherwise requested * fix: variable must be cached to be used externally --- tools/pybind11NewTools.cmake | 52 +++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index 8f771acd24..bf321e5e6a 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -71,8 +71,22 @@ if(PYBIND11_MASTER_PROJECT) endif() # Debug check - see https://stackoverflow.com/questions/646518/python-how-to-detect-debug-Interpreter -execute_process(COMMAND ${_Python}::Python -c "import sys; print(hasattr(sys, 'gettotalrefcount'))" - OUTPUT_VARIABLE PYTHON_IS_DEBUG) +execute_process( + COMMAND "${${_Python}_EXECUTABLE}" "-c" "import sys; sys.exit(hasattr(sys, 'gettotalrefcount'))" + RESULT_VARIABLE PYTHON_IS_DEBUG) + +# Get the suffix - SO is deprecated, should use EXT_SUFFIX, but this is +# required for PyPy3 (as of 7.3.1) +execute_process( + COMMAND "${${_Python}_EXECUTABLE}" "-c" + "from distutils import sysconfig; print(sysconfig.get_config_var('SO'))" + OUTPUT_VARIABLE _PYTHON_MODULE_EXTENSION + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + +# This needs to be available for the pybind11_extension function +set(PYTHON_MODULE_EXTENSION + "${_PYTHON_MODULE_EXTENSION}" + CACHE INTERNAL "") # Python debug libraries expose slightly different objects before 3.8 # https://docs.python.org/3.6/c-api/intro.html#debugging-builds @@ -121,8 +135,11 @@ else() PROPERTY INTERFACE_LINK_LIBRARIES pybind11::python_link_helper) endif() +# WITHOUT_SOABI and WITH_SOABI will disable the custom extension handling used by pybind11. +# WITH_SOABI is passed on to python_add_library. function(pybind11_add_module target_name) - cmake_parse_arguments(PARSE_ARGV 1 ARG "STATIC;SHARED;MODULE;THIN_LTO;NO_EXTRAS" "" "") + cmake_parse_arguments(PARSE_ARGV 1 ARG "STATIC;SHARED;MODULE;THIN_LTO;NO_EXTRAS;WITHOUT_SOABI" + "" "") if(ARG_ADD_LIBRARY_STATIC) set(type STATIC) @@ -133,11 +150,11 @@ function(pybind11_add_module target_name) endif() if("${_Python}" STREQUAL "Python") - python_add_library(${target_name} ${type} WITH_SOABI ${ARG_UNPARSED_ARGUMENTS}) + python_add_library(${target_name} ${type} ${ARG_UNPARSED_ARGUMENTS}) elseif("${_Python}" STREQUAL "Python3") - python3_add_library(${target_name} ${type} WITH_SOABI ${ARG_UNPARSED_ARGUMENTS}) + python3_add_library(${target_name} ${type} ${ARG_UNPARSED_ARGUMENTS}) elseif("${_Python}" STREQUAL "Python2") - python2_add_library(${target_name} ${type} WITH_SOABI ${ARG_UNPARSED_ARGUMENTS}) + python2_add_library(${target_name} ${type} ${ARG_UNPARSED_ARGUMENTS}) else() message(FATAL_ERROR "Cannot detect FindPython version: ${_Python}") endif() @@ -161,6 +178,12 @@ function(pybind11_add_module target_name) set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden" CUDA_VISIBILITY_PRESET "hidden") + # If we don't pass a WITH_SOABI or WITHOUT_SOABI, use our own default handling of extensions + if("${type}" STREQUAL "MODULE" AND (NOT ARG_WITHOUT_SOABI OR NOT "WITH_SOABI" IN_LIST + ARG_UNPARSED_ARGUMENTS)) + pybind11_extension(${target_name}) + endif() + if(ARG_NO_EXTRAS) return() endif() @@ -184,20 +207,7 @@ function(pybind11_add_module target_name) endfunction() function(pybind11_extension name) - set_property(TARGET ${name} PROPERTY PREFIX "") - - if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set_property(TARGET ${name} PROPERTY SUFFIX ".pyd") - endif() + # The extension is precomputed + set_target_properties(${name} PROPERTIES PREFIX "" SUFFIX "${PYTHON_MODULE_EXTENSION}") - if(${_Python}_SOABI) - get_property( - suffix - TARGET ${name} - PROPERTY SUFFIX) - if(NOT suffix) - set(suffix "${CMAKE_SHARED_MODULE_SUFFIX}") - endif() - set_property(TARGET ${name} PROPERTY SUFFIX ".${${_Python}_SOABI}${suffix}") - endif() endfunction() From 3bd0d7a8d58afed91a4aacf5d0cdb8443e107825 Mon Sep 17 00:00:00 2001 From: michalsustr Date: Sun, 6 Sep 2020 05:35:53 -0600 Subject: [PATCH 054/295] Add note about specifying custom base class for Exceptions. (#2465) * Add note about specifying custom base. * Update exception docs based on PR feedback. * Fix trailing whitespace. Co-authored-by: Michal Sustr --- docs/advanced/exceptions.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/advanced/exceptions.rst b/docs/advanced/exceptions.rst index b7d36014a6..874af4cc4c 100644 --- a/docs/advanced/exceptions.rst +++ b/docs/advanced/exceptions.rst @@ -79,6 +79,19 @@ This call creates a Python exception class with the name ``PyExp`` in the given module and automatically converts any encountered exceptions of type ``CppExp`` into Python exceptions of type ``PyExp``. +It is possible to specify base class for the exception using the third +parameter, a pointer to `PyObject`: + +.. code-block:: cpp + + py::register_exception(module, "PyExp", PyExc_RuntimeError); + +Then `PyExp` can be caught both as `PyExp` and `RuntimeError`. + +The class objects of the built-in Python exceptions are listed in the Python +documentation on `Standard Exceptions `_. +The default base class is `PyExc_Exception`. + When more advanced exception translation is needed, the function ``py::register_exception_translator(translator)`` can be used to register functions that can translate arbitrary exception types (and which may include From 36c666f02758f416d038284f24dc16a5a6454fdb Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 4 Sep 2020 23:31:05 +0200 Subject: [PATCH 055/295] pybind11_add_module(): OPT_SIZE target --- docs/changelog.rst | 6 ++++++ docs/compiling.rst | 18 +++++++++++++++++- tools/pybind11Common.cmake | 18 ++++++++++++++++++ tools/pybind11NewTools.cmake | 8 ++++++-- tools/pybind11Tools.cmake | 9 ++++++--- 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 36381eb537..461002e3fb 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,12 @@ v2.6.0 (IN PROGRESS) See :ref:`upgrade-guide-2.6` for help upgrading to the new version. +* ``pybind11_add_module()`` now accepts an optional ``OPT_SIZE`` flag that + switches the binding target to size-based optimization regardless global + CMake build type (except in debug mode, where optimizations remain disabled). + This reduces binary size quite substantially (~25%). + `#2463 `_ + * Keyword-only argument supported in Python 2 or 3 with ``py::kw_only()``. `#2100 `_ diff --git a/docs/compiling.rst b/docs/compiling.rst index 72b0c1eecf..ca4dc756e6 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -62,7 +62,7 @@ function with the following signature: .. code-block:: cmake pybind11_add_module( [MODULE | SHARED] [EXCLUDE_FROM_ALL] - [NO_EXTRAS] [THIN_LTO] source1 [source2 ...]) + [NO_EXTRAS] [THIN_LTO] [OPT_SIZE] source1 [source2 ...]) This function behaves very much like CMake's builtin ``add_library`` (in fact, it's a wrapper function around that command). It will add a library target @@ -96,6 +96,19 @@ regular LTO if ``-flto=thin`` is not available. If ``CMAKE_INTERPROCEDURAL_OPTIMIZATION`` is set (either ON or OFF), then 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 +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), +and specify ``OPT_SIZE`` for the binding target, where size might be the main +concern as performance is often less critical here. A ~25% size reduction has +been observed in practice. This flag only changes the optimization behavior at +a per-target level and takes precedence over the global CMake build type +(``Release``, ``RelWithDebInfo``) except for ``Debug`` builds, where +optimizations remain disabled. + .. _ThinLTO: http://clang.llvm.org/docs/ThinLTO.html Configuration variables @@ -245,6 +258,9 @@ available in all modes. The targets provided are: ``pybind11::windows_extras`` ``/bigobj`` and ``/mp`` for MSVC. + ``pybind11::opt_size`` + ``/Os`` for MSVC, ``-Os`` for other compilers. Does nothing for debug builds. + Two helper functions are also provided: ``pybind11_strip(target)`` diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index 8f7f57b517..96e958e646 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -10,6 +10,7 @@ Adds the following targets:: pybind11::python_link_helper - Adds link to Python libraries pybind11::python2_no_register - Avoid warning/error with Python 2 + C++14/7 pybind11::windows_extras - MSVC bigobj and mp for building multithreaded + pybind11::opt_size - avoid optimizations that increase code size Adds the following functions:: @@ -133,6 +134,23 @@ if(MSVC) endif() endif() +# ----------------------- Optimize binary size -------------------------- + +add_library(pybind11::opt_size IMPORTED INTERFACE ${optional_global}) + +if(MSVC) + set(PYBIND11_OPT_SIZE /Os) +else() + set(PYBIND11_OPT_SIZE -Os) +endif() + +set_property( + TARGET pybind11::opt_size + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS $<$:${PYBIND11_OPT_SIZE}> + $<$:${PYBIND11_OPT_SIZE}> + $<$:${PYBIND11_OPT_SIZE}>) + # ----------------------- Legacy option -------------------------- # Warn or error if old variable name used diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index bf321e5e6a..812ec094aa 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -138,8 +138,8 @@ endif() # WITHOUT_SOABI and WITH_SOABI will disable the custom extension handling used by pybind11. # WITH_SOABI is passed on to python_add_library. function(pybind11_add_module target_name) - cmake_parse_arguments(PARSE_ARGV 1 ARG "STATIC;SHARED;MODULE;THIN_LTO;NO_EXTRAS;WITHOUT_SOABI" - "" "") + cmake_parse_arguments(PARSE_ARGV 1 ARG + "STATIC;SHARED;MODULE;THIN_LTO;OPT_SIZE;NO_EXTRAS;WITHOUT_SOABI" "" "") if(ARG_ADD_LIBRARY_STATIC) set(type STATIC) @@ -204,6 +204,10 @@ function(pybind11_add_module target_name) if(MSVC) target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) endif() + + if(ARG_OPT_SIZE) + target_link_libraries(${target_name} PRIVATE pybind11::opt_size) + endif() endfunction() function(pybind11_extension name) diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 10f15a3091..a0a3b60eb1 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -1,6 +1,6 @@ # tools/pybind11Tools.cmake -- Build system for the pybind11 modules # -# Copyright (c) 2015 Wenzel Jakob +# Copyright (c) 2020 Wenzel Jakob # # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. @@ -124,10 +124,10 @@ endfunction() # Build a Python extension module: # pybind11_add_module( [MODULE | SHARED] [EXCLUDE_FROM_ALL] -# [NO_EXTRAS] [THIN_LTO] source1 [source2 ...]) +# [NO_EXTRAS] [THIN_LTO] [OPT_SIZE] source1 [source2 ...]) # function(pybind11_add_module target_name) - set(options MODULE SHARED EXCLUDE_FROM_ALL NO_EXTRAS SYSTEM THIN_LTO) + set(options "MODULE;SHARED;EXCLUDE_FROM_ALL;NO_EXTRAS;SYSTEM;THIN_LTO;OPT_SIZE") cmake_parse_arguments(ARG "${options}" "" "" ${ARGN}) if(ARG_MODULE AND ARG_SHARED) @@ -185,4 +185,7 @@ function(pybind11_add_module target_name) target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) endif() + if(ARG_OPT_SIZE) + target_link_libraries(${target_name} PRIVATE pybind11::opt_size) + endif() endfunction() From 064a03a49be23d5b19109ca4910bf6ebdd272354 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 4 Sep 2020 23:31:19 +0200 Subject: [PATCH 056/295] main CMakeLists.txt file: be less noisy --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b460494a8..67287d54ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,10 +161,6 @@ add_library(pybind11::headers ALIAS pybind11_headers) # easier to use/remember include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake") -if(NOT PYBIND11_MASTER_PROJECT AND NOT pybind11_FIND_QUIETLY) - message(STATUS "Using pybind11: (version \"${pybind11_VERSION}\" ${pybind11_VERSION_TYPE})") -endif() - # Relative directory setting if(USE_PYTHON_INCLUDE_DIR AND DEFINED Python_INCLUDE_DIRS) file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${Python_INCLUDE_DIRS}) From 37f845a1dc171d981149a53f01c13c6ba85e0f2e Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 8 Sep 2020 09:26:50 -0400 Subject: [PATCH 057/295] ci: disallow some common capitalization mistakes (#2472) * ci: only annotate linux for now * style: block some common mistakes --- .github/workflows/ci.yml | 2 ++ .pre-commit-config.yaml | 8 ++++++++ docs/advanced/cast/eigen.rst | 2 +- docs/changelog.rst | 4 ++-- docs/faq.rst | 2 +- docs/upgrade.rst | 2 +- tests/test_numpy_vectorize.cpp | 2 +- 7 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 825631beae..1ebc9dc776 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ on: jobs: standard: strategy: + fail-fast: false matrix: runs-on: [ubuntu-latest, windows-latest, macos-latest] arch: [x64] @@ -103,6 +104,7 @@ jobs: run: python -m pip install -r tests/requirements.txt --prefer-binary - name: Setup annotations + if: runner.os == 'Linux' run: python -m pip install pytest-github-actions-annotate-failures - name: Configure C++11 ${{ matrix.args }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a046c6fcfe..6863f4c495 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,6 +34,14 @@ repos: types: [file] files: (\.cmake|CMakeLists.txt)(.in)?$ +- repo: local + hooks: + - id: disallow-caps + name: Disallow improper capitalization + language: pygrep + entry: PyBind|Numpy|Cmake + exclude: .pre-commit-config.yaml + - repo: local hooks: - id: check-style diff --git a/docs/advanced/cast/eigen.rst b/docs/advanced/cast/eigen.rst index 59ba08c3c4..e01472d5ae 100644 --- a/docs/advanced/cast/eigen.rst +++ b/docs/advanced/cast/eigen.rst @@ -274,7 +274,7 @@ Vectors versus column/row matrices Eigen and numpy have fundamentally different notions of a vector. In Eigen, a vector is simply a matrix with the number of columns or rows set to 1 at -compile time (for a column vector or row vector, respectively). Numpy, in +compile time (for a column vector or row vector, respectively). NumPy, in contrast, has comparable 2-dimensional 1xN and Nx1 arrays, but *also* has 1-dimensional arrays of size N. diff --git a/docs/changelog.rst b/docs/changelog.rst index 461002e3fb..ca025f9d83 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -628,7 +628,7 @@ v2.2.0 (August 31, 2017) in reference cycles. `#856 `_. -* Numpy and buffer protocol related improvements: +* NumPy and buffer protocol related improvements: 1. Support for negative strides in Python buffer objects/numpy arrays. This required changing integers from unsigned to signed for the related C++ APIs. @@ -1359,7 +1359,7 @@ Happy Christmas! * Improved support for ``std::shared_ptr<>`` conversions * Initial support for ``std::set<>`` conversions * Fixed type resolution issue for types defined in a separate plugin module -* Cmake build system improvements +* CMake build system improvements * Factored out generic functionality to non-templated code (smaller code size) * Added a code size / compile time benchmark vs Boost.Python * Added an appveyor CI script diff --git a/docs/faq.rst b/docs/faq.rst index b68562910a..5f7866fa76 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -285,7 +285,7 @@ CMake code. Conflicts can arise, however, when using pybind11 in a project that Python detection in a system with several Python versions installed. This difference may cause inconsistencies and errors if *both* mechanisms are used in the same project. Consider the following -Cmake code executed in a system with Python 2.7 and 3.x installed: +CMake code executed in a system with Python 2.7 and 3.x installed: .. code-block:: cmake diff --git a/docs/upgrade.rst b/docs/upgrade.rst index 7c3f1c3280..894c65fdcb 100644 --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -32,7 +32,7 @@ something. The changes are: ``CMAKE_CXX_STANDARD=`` instead, or any other valid CMake CXX or CUDA standard selection method, like ``target_compile_features``. -* If you do not request a standard, PyBind11 targets will compile with the +* If you do not request a standard, pybind11 targets will compile with the compiler default, but not less than C++11, instead of forcing C++14 always. If you depend on the old behavior, please use ``set(CMAKE_CXX_STANDARD 14)`` instead. diff --git a/tests/test_numpy_vectorize.cpp b/tests/test_numpy_vectorize.cpp index a875a74b99..e76e462cbf 100644 --- a/tests/test_numpy_vectorize.cpp +++ b/tests/test_numpy_vectorize.cpp @@ -37,7 +37,7 @@ TEST_SUBMODULE(numpy_vectorize, m) { )); // test_type_selection - // Numpy function which only accepts specific data types + // NumPy function which only accepts specific data types m.def("selective_func", [](py::array_t) { return "Int branch taken."; }); m.def("selective_func", [](py::array_t) { return "Float branch taken."; }); m.def("selective_func", [](py::array_t, py::array::c_style>) { return "Complex float branch taken."; }); From fbc7563623247f410b119f7cee44841411059cd7 Mon Sep 17 00:00:00 2001 From: Holger Kohr Date: Wed, 9 Sep 2020 16:39:20 +0200 Subject: [PATCH 058/295] Add py::object casting example to embedding docs (#2466) * Add py::object casting example to embedding docs * Move implicit cast example to object.rst * Move to bottom and improve implicit casting text * Fix xref * Improve wording as per @bstaletic's suggestion --- docs/advanced/pycpp/object.rst | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/advanced/pycpp/object.rst b/docs/advanced/pycpp/object.rst index c6c3b1b75b..70e493acd9 100644 --- a/docs/advanced/pycpp/object.rst +++ b/docs/advanced/pycpp/object.rst @@ -20,6 +20,8 @@ Available types include :class:`handle`, :class:`object`, :class:`bool_`, Be sure to review the :ref:`pytypes_gotchas` before using this heavily in your C++ API. +.. _casting_back_and_forth: + Casting back and forth ====================== @@ -62,6 +64,7 @@ This example obtains a reference to the Python ``Decimal`` class. py::object scipy = py::module::import("scipy"); return scipy.attr("__version__"); + .. _calling_python_functions: Calling Python functions @@ -176,6 +179,47 @@ Generalized unpacking according to PEP448_ is also supported: .. _PEP448: https://www.python.org/dev/peps/pep-0448/ +.. _implicit_casting: + +Implicit casting +================ + +When using the C++ interface for Python types, or calling Python functions, +objects of type :class:`object` are returned. It is possible to invoke implicit +conversions to subclasses like :class:`dict`. The same holds for the proxy objects +returned by ``operator[]`` or ``obj.attr()``. +Casting to subtypes improves code readability and allows values to be passed to +C++ functions that require a specific subtype rather than a generic :class:`object`. + +.. code-block:: cpp + + #include + using namespace pybind11::literals; + + py::module os = py::module::import("os"); + py::module path = py::module::import("os.path"); // like 'import os.path as path' + py::module np = py::module::import("numpy"); // like 'import numpy as np' + + py::str curdir_abs = path.attr("abspath")(path.attr("curdir")); + py::print(py::str("Current directory: ") + curdir_abs); + py::dict environ = os.attr("environ"); + py::print(environ["HOME"]); + py::array_t arr = np.attr("ones")(3, "dtype"_a="float32"); + py::print(py::repr(arr + py::int_(1))); + +These implicit conversions are available for subclasses of :class:`object`; there +is no need to call ``obj.cast()`` explicitly as for custom classes, see +:ref:`casting_back_and_forth`. + +.. note:: + If a trivial conversion via move constructor is not possible, both implicit and + explicit casting (calling ``obj.cast()``) will attempt a "rich" conversion. + For instance, ``py::list env = os.attr("environ");`` will succeed and is + equivalent to the Python code ``env = list(os.environ)`` that produces a + list of the dict keys. + +.. TODO: Adapt text once PR #2349 has landed + Handling exceptions =================== From 621906b3e7b0492b5a4271c94a9435853dd20141 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 10 Sep 2020 11:49:26 -0400 Subject: [PATCH 059/295] fix: support nvcc and test (#2461) * fix: support nvcc and test * fixup! fix: support nvcc and test * docs: mention what compilers fail * fix: much simpler logic * refactor: slightly faster / clearer --- .github/workflows/ci.yml | 22 ++++++++++++++ README.md | 1 + include/pybind11/cast.h | 9 +++--- include/pybind11/numpy.h | 7 +++++ tests/CMakeLists.txt | 43 +++++++++++++++++++++++---- tests/test_constants_and_functions.py | 4 ++- tests/test_copy_move.cpp | 16 ++++++---- tests/test_virtual_functions.cpp | 4 +-- 8 files changed, 89 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ebc9dc776..3104f49e5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -289,6 +289,28 @@ jobs: - name: Interface test run: cmake --build build --target test_cmake_build + cuda: + runs-on: ubuntu-latest + name: "🐍 3.8 • CUDA 11 • Ubuntu 20.04" + container: nvidia/cuda:11.0-devel-ubuntu20.04 + + steps: + - uses: actions/checkout@v2 + + # 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 python3-dev python3-pytest + + - name: Configure + run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON + + - name: Build + run: cmake --build build -j2 -v + + - name: Python tests + run: cmake --build build --target pytest + + install-classic: name: "🐍 3.5 • Debian • x86 • Install" runs-on: ubuntu-latest diff --git a/README.md b/README.md index bae6cf2b5c..633231f74a 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ In addition to the core functionality, pybind11 provides some extra goodies: 4. Intel C++ compiler 17 or newer (16 with pybind11 v2.0 and 15 with pybind11 v2.0 and a [workaround][intel-15-workaround]) 5. Cygwin/GCC (tested on 2.5.1) +6. NVCC (CUDA 11 tested) ## About diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index bae69c8ac0..be62610a72 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1006,6 +1006,7 @@ template using is_std_char_type = any_of< std::is_same /* std::wstring */ >; + template struct type_caster::value && !is_std_char_type::value>> { using _py_type_0 = conditional_t; @@ -1034,12 +1035,12 @@ struct type_caster::value && !is_std_char_t : (py_type) PYBIND11_LONG_AS_LONGLONG(src.ptr()); } + // Python API reported an error bool py_err = py_value == (py_type) -1 && PyErr_Occurred(); - // Protect std::numeric_limits::min/max with parentheses - if (py_err || (std::is_integral::value && sizeof(py_type) != sizeof(T) && - (py_value < (py_type) (std::numeric_limits::min)() || - py_value > (py_type) (std::numeric_limits::max)()))) { + // Check to see if the conversion is valid (integers should match exactly) + // Signed/unsigned checks happen elsewhere + if (py_err || (std::is_integral::value && sizeof(py_type) != sizeof(T) && py_value != (py_type) (T) py_value)) { bool type_error = py_err && PyErr_ExceptionMatches( #if PY_VERSION_HEX < 0x03000000 && !defined(PYPY_VERSION) PyExc_SystemError diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 674450a631..0192a8b17b 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -1483,7 +1483,14 @@ struct vectorize_arg { template struct vectorize_helper { + +// NVCC for some reason breaks if NVectorized is private +#ifdef __CUDACC__ +public: +#else private: +#endif + static constexpr size_t N = sizeof...(Args); static constexpr size_t NVectorized = constexpr_sum(vectorize_arg::vectorize...); static_assert(NVectorized >= 1, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 72de21018a..54f13fd543 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ 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) set(PYBIND11_TEST_OVERRIDE "" CACHE STRING "Tests from ;-separated list of *.cpp files will be built instead of all tests") @@ -49,6 +50,14 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) "RelWithDebInfo") endif() +if(PYBIND11_CUDA_TESTS) + enable_language(CUDA) + if(DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CUDA_STANDARD ${CMAKE_CXX_STANDARD}) + endif() + set(CMAKE_CUDA_STANDARD_REQUIRED ON) +endif() + # Full set of test files (you can override these; see below) set(PYBIND11_TEST_FILES test_async.cpp @@ -104,6 +113,16 @@ if((PYBIND11_TEST_FILES_ASYNC_I GREATER -1) AND (PYTHON_VERSION VERSION_LESS 3.5 list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_ASYNC_I}) endif() +# Skip tests for CUDA check: +# /pybind11/tests/test_constants_and_functions.cpp(125): +# error: incompatible exception specifications +list(FIND PYBIND11_TEST_FILES test_constants_and_functions.cpp PYBIND11_TEST_FILES_CAF_I) +if((PYBIND11_TEST_FILES_CAF_I GREATER -1) AND PYBIND11_CUDA_TESTS) + message( + STATUS "Skipping test_constants_and_functions due to incompatible exception specifications") + list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_CAF_I}) +endif() + string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}") # Contains the set of test files that require pybind11_cross_module_tests to be @@ -195,7 +214,7 @@ endif() function(pybind11_enable_warnings target_name) if(MSVC) target_compile_options(${target_name} PRIVATE /W4) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)" AND NOT PYBIND11_CUDA_TESTS) target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wconversion -Wcast-qual -Wdeprecated) endif() @@ -203,6 +222,8 @@ function(pybind11_enable_warnings target_name) if(PYBIND11_WERROR) if(MSVC) target_compile_options(${target_name} PRIVATE /WX) + elseif(PYBIND11_CUDA_TESTS) + target_compile_options(${target_name} PRIVATE "SHELL:-Werror all-warnings") elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)") target_compile_options(${target_name} PRIVATE -Werror) endif() @@ -239,12 +260,22 @@ foreach(t ${PYBIND11_CROSS_MODULE_GIL_TESTS}) endif() endforeach() +# Support CUDA testing by forcing the target file to compile with NVCC +if(PYBIND11_CUDA_TESTS) + set_property(SOURCE ${PYBIND11_TEST_FILES} PROPERTY LANGUAGE CUDA) +endif() + foreach(target ${test_targets}) set(test_files ${PYBIND11_TEST_FILES}) if(NOT "${target}" STREQUAL "pybind11_tests") set(test_files "") endif() + # Support CUDA testing by forcing the target file to compile with NVCC + if(PYBIND11_CUDA_TESTS) + set_property(SOURCE ${target}.cpp PROPERTY LANGUAGE CUDA) + endif() + # Create the binding library pybind11_add_module(${target} THIN_LTO ${target}.cpp ${test_files} ${PYBIND11_HEADERS}) pybind11_enable_warnings(${target}) @@ -354,8 +385,10 @@ add_custom_command( $ ${CMAKE_CURRENT_BINARY_DIR}/sosize-$.txt) -# Test embedding the interpreter. Provides the `cpptest` target. -add_subdirectory(test_embed) +if(NOT PYBIND11_CUDA_TESTS) + # Test embedding the interpreter. Provides the `cpptest` target. + add_subdirectory(test_embed) -# Test CMake build using functions and targets from subdirectory or installed location -add_subdirectory(test_cmake_build) + # Test CMake build using functions and targets from subdirectory or installed location + add_subdirectory(test_cmake_build) +endif() diff --git a/tests/test_constants_and_functions.py b/tests/test_constants_and_functions.py index 36b1aa64b1..b980ccf1cc 100644 --- a/tests/test_constants_and_functions.py +++ b/tests/test_constants_and_functions.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- -from pybind11_tests import constants_and_functions as m +import pytest + +m = pytest.importorskip("pybind11_tests.constants_and_functions") def test_constants(): diff --git a/tests/test_copy_move.cpp b/tests/test_copy_move.cpp index 0f698bdf05..34f1c618d5 100644 --- a/tests/test_copy_move.cpp +++ b/tests/test_copy_move.cpp @@ -175,14 +175,20 @@ TEST_SUBMODULE(copy_move_policies, m) { m.attr("has_optional") = false; #endif - // #70 compilation issue if operator new is not public + // #70 compilation issue if operator new is not public - simple body added + // but not needed on most compilers; MSVC and nvcc don't like a local + // struct not having a method defined when declared, since it can not be + // added later. struct PrivateOpNew { int value = 1; private: -#if defined(_MSC_VER) -# pragma warning(disable: 4822) // warning C4822: local class member function does not have a body -#endif - void *operator new(size_t bytes); + void *operator new(size_t bytes) { + void *ptr = std::malloc(bytes); + if (ptr) + return ptr; + else + throw std::bad_alloc{}; + } }; py::class_(m, "PrivateOpNew").def_readonly("value", &PrivateOpNew::value); m.def("private_op_new_value", []() { return PrivateOpNew(); }); diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index 583c1e647e..6dcf294153 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -139,7 +139,7 @@ class NCVirt { std::string print_movable(int a, int b) { return get_movable(a, b).get_value(); } }; class NCVirtTrampoline : public NCVirt { -#if !defined(__INTEL_COMPILER) +#if !defined(__INTEL_COMPILER) && !defined(__CUDACC__) NonCopyable get_noncopyable(int a, int b) override { PYBIND11_OVERLOAD(NonCopyable, NCVirt, get_noncopyable, a, b); } @@ -205,7 +205,7 @@ TEST_SUBMODULE(virtual_functions, m) { .def(py::init()); // test_move_support -#if !defined(__INTEL_COMPILER) +#if !defined(__INTEL_COMPILER) && !defined(__CUDACC__) py::class_(m, "NCVirt") .def(py::init<>()) .def("get_noncopyable", &NCVirt::get_noncopyable) From b47efd35fb436abe5c93db36b4ff5a1b5fdf593f Mon Sep 17 00:00:00 2001 From: Ciro Santilli <36707998+cirosantilli2@users.noreply.github.com> Date: Thu, 10 Sep 2020 18:58:26 +0100 Subject: [PATCH 060/295] Use defined for some preprocessor variables that might be undefined (#2476) The variables PYBIND11_HAS_OPTIONAL, PYBIND11_HAS_EXP_OPTIONAL, PYBIND11_HAS_VARIANT, __clang__, __APPLE__ were not checked for defined in a minortity of instances. If the project using pybind11 sets -Wundef, the warnings will show. The test build is also modified to catch the problem. --- include/pybind11/stl.h | 6 +++--- tests/CMakeLists.txt | 2 +- tests/test_operator_overloading.cpp | 4 ++-- tests/test_stl.cpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 6c2bebda87..721bb669f0 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -289,7 +289,7 @@ template struct optional_caster { PYBIND11_TYPE_CASTER(T, _("Optional[") + value_conv::name + _("]")); }; -#if PYBIND11_HAS_OPTIONAL +#if defined(PYBIND11_HAS_OPTIONAL) template struct type_caster> : public optional_caster> {}; @@ -297,7 +297,7 @@ template<> struct type_caster : public void_caster {}; #endif -#if PYBIND11_HAS_EXP_OPTIONAL +#if defined(PYBIND11_HAS_EXP_OPTIONAL) template struct type_caster> : public optional_caster> {}; @@ -369,7 +369,7 @@ struct variant_caster> { PYBIND11_TYPE_CASTER(Type, _("Union[") + detail::concat(make_caster::name...) + _("]")); }; -#if PYBIND11_HAS_VARIANT +#if defined(PYBIND11_HAS_VARIANT) template struct type_caster> : variant_caster> { }; #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 54f13fd543..e59c75ce88 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -216,7 +216,7 @@ function(pybind11_enable_warnings target_name) target_compile_options(${target_name} PRIVATE /W4) elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)" AND NOT PYBIND11_CUDA_TESTS) target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wconversion -Wcast-qual - -Wdeprecated) + -Wdeprecated -Wundef) endif() if(PYBIND11_WERROR) diff --git a/tests/test_operator_overloading.cpp b/tests/test_operator_overloading.cpp index f3c2eaafa9..d176c4644b 100644 --- a/tests/test_operator_overloading.cpp +++ b/tests/test_operator_overloading.cpp @@ -88,11 +88,11 @@ std::string abs(const Vector2&) { // Here, we suppress the warning using `#pragma diagnostic`. // Taken from: https://github.com/RobotLocomotion/drake/commit/aaf84b46 // TODO(eric): This could be resolved using a function / functor (e.g. `py::self()`). - #if (__APPLE__) && (__clang__) + #if defined(__APPLE__) && defined(__clang__) #if (__clang_major__ >= 10) && (__clang_minor__ >= 0) && (__clang_patchlevel__ >= 1) #pragma GCC diagnostic ignored "-Wself-assign-overloaded" #endif - #elif (__clang__) + #elif defined(__clang__) #if (__clang_major__ >= 7) #pragma GCC diagnostic ignored "-Wself-assign-overloaded" #endif diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index 928635788e..b230717d2f 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -15,7 +15,7 @@ #include // Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14 -#if PYBIND11_HAS_VARIANT +#if defined(PYBIND11_HAS_VARIANT) using std::variant; #elif defined(PYBIND11_TEST_BOOST) && (!defined(_MSC_VER) || _MSC_VER >= 1910) # include From fe9ee86ba882634ee7ec46dfdc72ac6cbbfb5b50 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Fri, 11 Sep 2020 19:53:04 +0200 Subject: [PATCH 061/295] Add check if `str(handle)` correctly converted the object, and throw py::error_already_set if not (bis) (#2477) * Add check if `str(handle)` correctly converted the object, and throw py::error_already_set if not * Fix tests on Python 3 * Apply @rwgk's fixes to cherry-picked commits from #2392 --- include/pybind11/pytypes.h | 4 ++-- tests/test_pytypes.cpp | 1 + tests/test_pytypes.py | 12 ++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index bea34cd936..00c791aada 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -920,7 +920,7 @@ class str : public object { Return a string representation of the object. This is analogous to the ``str()`` function in Python. \endrst */ - explicit str(handle h) : object(raw_str(h.ptr()), stolen_t{}) { } + explicit str(handle h) : object(raw_str(h.ptr()), stolen_t{}) { if (!m_ptr) throw error_already_set(); } operator std::string() const { object temp = *this; @@ -945,8 +945,8 @@ class str : public object { /// Return string representation -- always returns a new reference, even if already a str static PyObject *raw_str(PyObject *op) { PyObject *str_value = PyObject_Str(op); - if (!str_value) throw error_already_set(); #if PY_MAJOR_VERSION < 3 + if (!str_value) throw error_already_set(); PyObject *unicode = PyUnicode_FromEncodedObject(str_value, "utf-8", nullptr); Py_XDECREF(str_value); str_value = unicode; #endif diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 925d6ffd27..4ef1b9ff0b 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -80,6 +80,7 @@ TEST_SUBMODULE(pytypes, m) { m.def("str_from_bytes", []() { return py::str(py::bytes("boo", 3)); }); m.def("str_from_object", [](const py::object& obj) { return py::str(obj); }); m.def("repr_from_object", [](const py::object& obj) { return py::repr(obj); }); + m.def("str_from_handle", [](py::handle h) { return py::str(h); }); m.def("str_format", []() { auto s1 = "{} + {} = {}"_s.format(1, 2, 3); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 277c170ebe..0618cd54c9 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -104,11 +104,23 @@ def __repr__(self): assert m.str_from_object(A()) == "this is a str" assert m.repr_from_object(A()) == "this is a repr" + assert m.str_from_handle(A()) == "this is a str" s1, s2 = m.str_format() assert s1 == "1 + 2 = 3" assert s1 == s2 + malformed_utf8 = b"\x80" + assert m.str_from_object(malformed_utf8) is malformed_utf8 # To be fixed; see #2380 + if env.PY2: + # with pytest.raises(UnicodeDecodeError): + # m.str_from_object(malformed_utf8) + with pytest.raises(UnicodeDecodeError): + m.str_from_handle(malformed_utf8) + else: + # assert m.str_from_object(malformed_utf8) == "b'\\x80'" + assert m.str_from_handle(malformed_utf8) == "b'\\x80'" + def test_bytes(doc): assert m.bytes_from_string().decode() == "foo" From 38370a87f417001f3984cfa929860dabce6b69e7 Mon Sep 17 00:00:00 2001 From: andriish Date: Sat, 12 Sep 2020 04:06:52 +0200 Subject: [PATCH 062/295] fix: support NVIDIA-PGI HPC SDK (#2475) * Added guards to the includes Added new CI config Added new trigger Changed CI workflow name Debug CI Debug CI Debug CI Debug CI Added flags fro PGI Disable Eigen Removed tests that fail Uncomment lines * fix: missing include fix: minor style cleanup tests: support skipping ci: remove and tighten a bit fix: try msvc workaround for pgic * tests: split up prealoc tests * fix: PGI compiler fix * fix: PGI void_t only * fix: try to appease nvcc * ci: better ordering for slow tests * ci: minor improvements to testing * ci: Add NumPy to testing * ci: Eigen generates CUDA warnings / PGI errors * Added CentOS7 back for a moment * Fix YAML * ci: runs-on missing * centos7 is missing pytest * ci: use C++11 on CentOS 7 * ci: test something else * Try just adding flags on CentOS 7 * fix: CentOS 7 * refactor: move include to shared location * Added verbose flag * Try to use system cmake3 on CI * Try to use system cmake3 on CI, attempt2 * Try to use system cmake3 on CI, attempt3 * tests: not finding pytest should be a warning, not a fatal error * tests: cleanup * Weird issue? * fix: final polish Co-authored-by: Andrii Verbytskyi Co-authored-by: Henry Schreiner Co-authored-by: Andrii Verbytskyi --- .github/CONTRIBUTING.md | 23 +++++ .github/workflows/ci.yml | 131 +++++++++++++++++++++++----- README.md | 1 + docs/changelog.rst | 10 +++ include/pybind11/detail/common.h | 9 ++ tests/CMakeLists.txt | 65 ++++++++++---- tests/test_factory_constructors.cpp | 1 + tests/test_factory_constructors.py | 17 +++- tests/test_smart_ptr.py | 5 +- tests/test_virtual_functions.cpp | 4 +- tests/test_virtual_functions.py | 4 +- 11 files changed, 222 insertions(+), 48 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f61011d540..e83c31565d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -164,6 +164,29 @@ name, pre-commit): pre-commit install ``` +### Build recipes + +This builds with the Intel compiler (assuming it is in your path, along with a +recent CMake and Python 3): + +```bash +python3 -m venv venv +. venv/bin/activate +pip install pytest +cmake -S . -B build-intel -DCMAKE_CXX_COMPILER=$(which icpc) -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DPYBIND11_WERROR=ON +``` + +This will test the PGI compilers: + +```bash +docker run --rm -it -v $PWD:/pybind11 nvcr.io/hpc/pgi-compilers:ce +apt-get update && apt-get install -y python3-dev python3-pip python3-pytest +wget -qO- "https://cmake.org/files/v3.18/cmake-3.18.2-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local +cmake -S pybind11/ -B build +cmake --build build +``` + + [pre-commit]: https://pre-commit.com [pybind11.readthedocs.org]: http://pybind11.readthedocs.org/en/latest [issue tracker]: https://github.com/pybind/pybind11/issues diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3104f49e5f..530d8caf38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,6 @@ jobs: python: 3.9-dev arch: x64 - name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • ${{ matrix.arch }} ${{ matrix.args }}" runs-on: ${{ matrix.runs-on }} continue-on-error: ${{ endsWith(matrix.python, 'dev') }} @@ -196,6 +195,114 @@ jobs: - name: Interface test run: cmake --build build --target test_cmake_build + + cuda: + runs-on: ubuntu-latest + name: "🐍 3.8 • CUDA 11 • Ubuntu 20.04" + container: nvidia/cuda:11.0-devel-ubuntu20.04 + + steps: + - uses: actions/checkout@v2 + + # 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 + + - name: Build + run: cmake --build build -j2 --verbose + + - name: Python tests + run: cmake --build build --target pytest + + + centos-nvhpc8: + runs-on: ubuntu-latest + name: "🐍 3 • CentOS8 / PGI 20.7 • x64" + container: centos:8 + + steps: + - uses: actions/checkout@v2 + + - 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/nvhpc-20-7-20.7-1.x86_64.rpm https://developer.download.nvidia.com/hpc-sdk/nvhpc-2020-20.7-1.x86_64.rpm + + - name: Configure + shell: bash + run: | + source /etc/profile.d/modules.sh + module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/20.7 + 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 + + centos-nvhpc7: + runs-on: ubuntu-latest + name: "🐍 3 • CentOS7 / PGI 20.7 • x64" + container: centos:7 + + steps: + - uses: actions/checkout@v2 + + - name: Add Python 3 and a few requirements + run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 + + - name: Install NVidia HPC SDK + run: yum -y install https://developer.download.nvidia.com/hpc-sdk/nvhpc-20-7-20.7-1.x86_64.rpm https://developer.download.nvidia.com/hpc-sdk/nvhpc-2020-20.7-1.x86_64.rpm + + # On CentOS 7, we have to filter a few tests (compiler internal error) + # and allow deeper templete recursion (not needed on CentOS 8 with a newer + # standard library). 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/20.7 + cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ + -DCMAKE_CXX_STANDARD=11 \ + -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ + -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ + -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp;test_virtual_functions.cpp" + + # Building before installing Pip should produce a warning but not an error + - name: Build + run: cmake3 --build build -j 2 --verbose + + - name: Install CMake with pip + run: | + python3 -m pip install --upgrade pip + python3 -m pip install pytest + + - name: Python tests + run: cmake3 --build build --target pytest + + - name: C++ tests + run: cmake3 --build build --target cpptest + + - name: Interface test + run: cmake3 --build build --target test_cmake_build + gcc: runs-on: ubuntu-latest strategy: @@ -243,6 +350,7 @@ jobs: - name: Interface test run: cmake --build build --target test_cmake_build + centos: runs-on: ubuntu-latest strategy: @@ -289,27 +397,6 @@ jobs: - name: Interface test run: cmake --build build --target test_cmake_build - cuda: - runs-on: ubuntu-latest - name: "🐍 3.8 • CUDA 11 • Ubuntu 20.04" - container: nvidia/cuda:11.0-devel-ubuntu20.04 - - steps: - - uses: actions/checkout@v2 - - # 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 python3-dev python3-pytest - - - name: Configure - run: cmake -S . -B build -DPYBIND11_CUDA_TESTS=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON - - - name: Build - run: cmake --build build -j2 -v - - - name: Python tests - run: cmake --build build --target pytest - install-classic: name: "🐍 3.5 • Debian • x86 • Install" diff --git a/README.md b/README.md index 633231f74a..69a0fc90b2 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ In addition to the core functionality, pybind11 provides some extra goodies: v2.0 and a [workaround][intel-15-workaround]) 5. Cygwin/GCC (tested on 2.5.1) 6. NVCC (CUDA 11 tested) +7. NVIDIA PGI (20.7 tested) ## About diff --git a/docs/changelog.rst b/docs/changelog.rst index ca025f9d83..77fd441735 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -80,6 +80,9 @@ Smaller or developer focused features: * Bugfixes related to more extensive testing `#2321 `_ +* Bug in timezone issue in Eastern hemisphere midnight fixed. + `#2438 `_ + * Pointer to ``std::tuple`` & ``std::pair`` supported in cast. `#2334 `_ @@ -96,6 +99,13 @@ Smaller or developer focused features: * Debug Python interpreter support. `#2025 `_ +* NVCC (CUDA 11) now supported and tested in CI. + `#2461 `_ + +* NVIDIA PGI compilers now supported and tested in CI. + `#2475 `_ + + v2.5.0 (Mar 31, 2020) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 8923faef76..7d6530cc80 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -154,6 +154,7 @@ #include #include #include +#include #include #include #include @@ -501,8 +502,16 @@ template using select_indices = typename select_indices_impl using bool_constant = std::integral_constant; template struct negation : bool_constant { }; +// PGI cannot detect operator delete with the "compatible" void_t impl, so +// using the new one (C++14 defect, so generally works on newer compilers, even +// if not in C++17 mode) +#if defined(__PGIC__) +template using void_t = void; +#else template struct void_t_impl { using type = void; }; template using void_t = typename void_t_impl::type; +#endif + /// Compile-time all/any/none of that check the boolean value of all template types #if defined(__cpp_fold_expressions) && !(defined(_MSC_VER) && (_MSC_VER < 1916)) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e59c75ce88..45e094b080 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,29 @@ else() cmake_policy(VERSION 3.18) endif() +# Only needed for CMake < 3.5 support +include(CMakeParseArguments) + +# Filter out items; print an optional message if any items filtered +# +# Usage: +# pybind11_filter_tests(LISTNAME file1.cpp file2.cpp ... MESSAGE "") +# +macro(PYBIND11_FILTER_TESTS LISTNAME) + cmake_parse_arguments(ARG "" "MESSAGE" "" ${ARGN}) + set(PYBIND11_FILTER_TESTS_FOUND OFF) + foreach(filename IN LISTS ARG_UNPARSED_ARGUMENTS) + list(FIND ${LISTNAME} ${filename} _FILE_FOUND) + if(_FILE_FOUND GREATER -1) + list(REMOVE_AT ${LISTNAME} ${_FILE_FOUND}) + set(PYBIND11_FILTER_TESTS_FOUND ON) + endif() + endforeach() + if(PYBIND11_FILTER_TESTS_FOUND AND ARG_MESSAGE) + message(STATUS "${ARG_MESSAGE}") + endif() +endmacro() + # New Python support if(DEFINED Python_EXECUTABLE) set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") @@ -34,6 +57,9 @@ option(PYBIND11_CUDA_TESTS "Enable building CUDA tests (requires CMake 3.12+)" O set(PYBIND11_TEST_OVERRIDE "" CACHE STRING "Tests from ;-separated list of *.cpp files will be built instead of all tests") +set(PYBIND11_TEST_FILTER + "" + CACHE STRING "Tests from ;-separated list of *.cpp files will be removed from all tests") if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # We're being loaded directly, i.e. not via add_subdirectory, so make this @@ -106,21 +132,23 @@ if(PYBIND11_TEST_OVERRIDE) set(PYBIND11_TEST_FILES ${PYBIND11_TEST_OVERRIDE}) endif() -# Skip test_async for Python < 3.5 -list(FIND PYBIND11_TEST_FILES test_async.cpp PYBIND11_TEST_FILES_ASYNC_I) -if((PYBIND11_TEST_FILES_ASYNC_I GREATER -1) AND (PYTHON_VERSION VERSION_LESS 3.5)) - message(STATUS "Skipping test_async because Python version ${PYTHON_VERSION} < 3.5") - list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_ASYNC_I}) +# You can also filter tests: +if(PYBIND11_TEST_FILTER) + pybind11_filter_tests(PYBIND11_TEST_FILES ${PYBIND11_TEST_FILTER}) +endif() + +if(PYTHON_VERSION VERSION_LESS 3.5) + pybind11_filter_tests(PYBIND11_TEST_FILES test_async.cpp MESSAGE + "Skipping test_async on Python 2") endif() # Skip tests for CUDA check: # /pybind11/tests/test_constants_and_functions.cpp(125): # error: incompatible exception specifications -list(FIND PYBIND11_TEST_FILES test_constants_and_functions.cpp PYBIND11_TEST_FILES_CAF_I) -if((PYBIND11_TEST_FILES_CAF_I GREATER -1) AND PYBIND11_CUDA_TESTS) - message( - STATUS "Skipping test_constants_and_functions due to incompatible exception specifications") - list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_CAF_I}) +if(PYBIND11_CUDA_TESTS) + pybind11_filter_tests( + PYBIND11_TEST_FILES test_constants_and_functions.cpp MESSAGE + "Skipping test_constants_and_functions due to incompatible exception specifications") endif() string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}") @@ -318,7 +346,7 @@ foreach(target ${test_targets}) endif() endforeach() -# Make sure pytest is found or produce a fatal error +# Make sure pytest is found or produce a warning if(NOT PYBIND11_PYTEST_FOUND) execute_process( COMMAND ${PYTHON_EXECUTABLE} -c "import pytest; print(pytest.__version__)" @@ -326,15 +354,16 @@ if(NOT PYBIND11_PYTEST_FOUND) OUTPUT_VARIABLE pytest_version ERROR_QUIET) if(pytest_not_found) - message(FATAL_ERROR "Running the tests requires pytest. Please install it manually" - " (try: ${PYTHON_EXECUTABLE} -m pip install pytest)") + message(WARNING "Running the tests requires pytest. Please install it manually" + " (try: ${PYTHON_EXECUTABLE} -m pip install pytest)") elseif(pytest_version VERSION_LESS 3.1) - message(FATAL_ERROR "Running the tests requires pytest >= 3.1. Found: ${pytest_version}" - "Please update it (try: ${PYTHON_EXECUTABLE} -m pip install -U pytest)") + message(WARNING "Running the tests requires pytest >= 3.1. Found: ${pytest_version}" + "Please update it (try: ${PYTHON_EXECUTABLE} -m pip install -U pytest)") + else() + set(PYBIND11_PYTEST_FOUND + TRUE + CACHE INTERNAL "") endif() - set(PYBIND11_PYTEST_FOUND - TRUE - CACHE INTERNAL "") endif() if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index 61cf33d16e..f614743460 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -11,6 +11,7 @@ #include "pybind11_tests.h" #include "constructor_stats.h" #include +#include // Classes for testing python construction via C++ factory function: // Not publicly constructible, copyable, or movable: diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py index 6c4bed165f..b141c13de9 100644 --- a/tests/test_factory_constructors.py +++ b/tests/test_factory_constructors.py @@ -336,10 +336,10 @@ def strip_comments(s): return re.sub(r'\s+#.*', '', s) -def test_reallocations(capture, msg): +def test_reallocation_a(capture, msg): """When the constructor is overloaded, previous overloads can require a preallocated value. This test makes sure that such preallocated values only happen when they might be necessary, - and that they are deallocated properly""" + and that they are deallocated properly.""" pytest.gc_collect() @@ -353,6 +353,9 @@ def test_reallocations(capture, msg): ~NoisyAlloc() noisy delete """ + + +def test_reallocation_b(capture, msg): with capture: create_and_destroy(1.5) assert msg(capture) == strip_comments(""" @@ -365,6 +368,8 @@ def test_reallocations(capture, msg): noisy delete # operator delete """) + +def test_reallocation_c(capture, msg): with capture: create_and_destroy(2, 3) assert msg(capture) == strip_comments(""" @@ -375,6 +380,8 @@ def test_reallocations(capture, msg): noisy delete # operator delete """) + +def test_reallocation_d(capture, msg): with capture: create_and_destroy(2.5, 3) assert msg(capture) == strip_comments(""" @@ -386,6 +393,8 @@ def test_reallocations(capture, msg): noisy delete # operator delete """) + +def test_reallocation_e(capture, msg): with capture: create_and_destroy(3.5, 4.5) assert msg(capture) == strip_comments(""" @@ -397,6 +406,8 @@ def test_reallocations(capture, msg): noisy delete # operator delete """) + +def test_reallocation_f(capture, msg): with capture: create_and_destroy(4, 0.5) assert msg(capture) == strip_comments(""" @@ -409,6 +420,8 @@ def test_reallocations(capture, msg): noisy delete # operator delete """) + +def test_reallocation_g(capture, msg): with capture: create_and_destroy(5, "hi") assert msg(capture) == strip_comments(""" diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py index c9267f6878..0b1ca45b5a 100644 --- a/tests/test_smart_ptr.py +++ b/tests/test_smart_ptr.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- import pytest -from pybind11_tests import smart_ptr as m -from pybind11_tests import ConstructorStats + +m = pytest.importorskip("pybind11_tests.smart_ptr") +from pybind11_tests import ConstructorStats # noqa: E402 def test_smart_ptr(capture): diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index 6dcf294153..c5ee6365fb 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -139,7 +139,7 @@ class NCVirt { std::string print_movable(int a, int b) { return get_movable(a, b).get_value(); } }; class NCVirtTrampoline : public NCVirt { -#if !defined(__INTEL_COMPILER) && !defined(__CUDACC__) +#if !defined(__INTEL_COMPILER) && !defined(__CUDACC__) && !defined(__PGIC__) NonCopyable get_noncopyable(int a, int b) override { PYBIND11_OVERLOAD(NonCopyable, NCVirt, get_noncopyable, a, b); } @@ -205,7 +205,7 @@ TEST_SUBMODULE(virtual_functions, m) { .def(py::init()); // test_move_support -#if !defined(__INTEL_COMPILER) && !defined(__CUDACC__) +#if !defined(__INTEL_COMPILER) && !defined(__CUDACC__) && !defined(__PGIC__) py::class_(m, "NCVirt") .def(py::init<>()) .def("get_noncopyable", &NCVirt::get_noncopyable) diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index b7bd5badf0..66a353ae7f 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -3,8 +3,8 @@ import env # noqa: F401 -from pybind11_tests import virtual_functions as m -from pybind11_tests import ConstructorStats +m = pytest.importorskip("pybind11_tests.virtual_functions") +from pybind11_tests import ConstructorStats # noqa: E402 def test_override(capture, msg): From cc982ac1cd3ec31040c0cfff3a6dd0183f7c1cac Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sun, 13 Sep 2020 16:24:00 +0200 Subject: [PATCH 063/295] fix: allow assignment of time points of resolutions other than that of a system clock (#2481) --- include/pybind11/chrono.h | 2 +- tests/test_chrono.cpp | 28 ++++++++++++++++++++++++++++ tests/test_chrono.py | 10 ++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/include/pybind11/chrono.h b/include/pybind11/chrono.h index 6127c659bd..ac3d34e06a 100644 --- a/include/pybind11/chrono.h +++ b/include/pybind11/chrono.h @@ -140,7 +140,7 @@ template class type_caster(system_clock::from_time_t(std::mktime(&cal)) + msecs); return true; } diff --git a/tests/test_chrono.cpp b/tests/test_chrono.cpp index 1d79d4b6ca..6537050803 100644 --- a/tests/test_chrono.cpp +++ b/tests/test_chrono.cpp @@ -12,6 +12,24 @@ #include #include +struct different_resolutions { + using time_point_h = std::chrono::time_point< + std::chrono::system_clock, std::chrono::hours>; + using time_point_m = std::chrono::time_point< + std::chrono::system_clock, std::chrono::minutes>; + using time_point_s = std::chrono::time_point< + std::chrono::system_clock, std::chrono::seconds>; + using time_point_ms = std::chrono::time_point< + std::chrono::system_clock, std::chrono::milliseconds>; + using time_point_us = std::chrono::time_point< + std::chrono::system_clock, std::chrono::microseconds>; + time_point_h timestamp_h; + time_point_m timestamp_m; + time_point_s timestamp_s; + time_point_ms timestamp_ms; + time_point_us timestamp_us; +}; + TEST_SUBMODULE(chrono, m) { using system_time = std::chrono::system_clock::time_point; using steady_time = std::chrono::steady_clock::time_point; @@ -53,4 +71,14 @@ TEST_SUBMODULE(chrono, m) { m.def("test_nano_timepoint", [](timestamp start, timespan delta) -> timestamp { return start + delta; }); + + // Test different resolutions + py::class_(m, "different_resolutions") + .def(py::init<>()) + .def_readwrite("timestamp_h", &different_resolutions::timestamp_h) + .def_readwrite("timestamp_m", &different_resolutions::timestamp_m) + .def_readwrite("timestamp_s", &different_resolutions::timestamp_s) + .def_readwrite("timestamp_ms", &different_resolutions::timestamp_ms) + .def_readwrite("timestamp_us", &different_resolutions::timestamp_us) + ; } diff --git a/tests/test_chrono.py b/tests/test_chrono.py index 76783905a3..ae24b7dda2 100644 --- a/tests/test_chrono.py +++ b/tests/test_chrono.py @@ -200,3 +200,13 @@ def test_nano_timepoint(): time = datetime.datetime.now() time1 = m.test_nano_timepoint(time, datetime.timedelta(seconds=60)) assert(time1 == time + datetime.timedelta(seconds=60)) + + +def test_chrono_different_resolutions(): + resolutions = m.different_resolutions() + time = datetime.datetime.now() + resolutions.timestamp_h = time + resolutions.timestamp_m = time + resolutions.timestamp_s = time + resolutions.timestamp_ms = time + resolutions.timestamp_us = time From 32bb9071aa3cac01c6edcf7246305c0b0bae453f Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Mon, 14 Sep 2020 20:07:29 +0200 Subject: [PATCH 064/295] Avoid C-style casts for pointers in docs (#2487) Why only for pointers? Because C casts are hard to grep for. --- docs/advanced/functions.rst | 2 +- docs/advanced/misc.rst | 4 ++-- docs/advanced/pycpp/numpy.rst | 6 +++--- docs/classes.rst | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/advanced/functions.rst b/docs/advanced/functions.rst index 2814adfbaa..c895517c50 100644 --- a/docs/advanced/functions.rst +++ b/docs/advanced/functions.rst @@ -360,7 +360,7 @@ like so: .. code-block:: cpp py::class_("MyClass") - .def("myFunction", py::arg("arg") = (SomeType *) nullptr); + .def("myFunction", py::arg("arg") = static_cast(nullptr)); Keyword-only arguments ====================== diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index 0a73dae7e7..8342210bcc 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -176,9 +176,9 @@ pybind11 version. Consider the following example: .. code-block:: cpp - auto data = (MyData *) py::get_shared_data("mydata"); + auto data = reinterpret_cast(py::get_shared_data("mydata")); if (!data) - data = (MyData *) py::set_shared_data("mydata", new MyData(42)); + data = static_cast(py::set_shared_data("mydata", new MyData(42))); If the above snippet was used in several separately compiled extension modules, the first one to be imported would create a ``MyData`` instance and associate diff --git a/docs/advanced/pycpp/numpy.rst b/docs/advanced/pycpp/numpy.rst index 8e5c6092c4..e50d24a991 100644 --- a/docs/advanced/pycpp/numpy.rst +++ b/docs/advanced/pycpp/numpy.rst @@ -274,9 +274,9 @@ simply using ``vectorize``). py::buffer_info buf3 = result.request(); - double *ptr1 = (double *) buf1.ptr, - *ptr2 = (double *) buf2.ptr, - *ptr3 = (double *) buf3.ptr; + double *ptr1 = static_cast(buf1.ptr); + double *ptr2 = static_cast(buf2.ptr); + double *ptr3 = static_cast(buf3.ptr); for (size_t idx = 0; idx < buf1.shape[0]; idx++) ptr3[idx] = ptr1[idx] + ptr2[idx]; diff --git a/docs/classes.rst b/docs/classes.rst index 1d44a5931d..f3610ef367 100644 --- a/docs/classes.rst +++ b/docs/classes.rst @@ -373,8 +373,8 @@ sequence. py::class_(m, "Pet") .def(py::init()) - .def("set", (void (Pet::*)(int)) &Pet::set, "Set the pet's age") - .def("set", (void (Pet::*)(const std::string &)) &Pet::set, "Set the pet's name"); + .def("set", static_cast(&Pet::set), "Set the pet's age") + .def("set", static_cast(&Pet::set), "Set the pet's name"); The overload signatures are also visible in the method's docstring: From f12ec00d70189869ce46f9a7f534d435cf9ea173 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 14 Sep 2020 18:06:26 -0400 Subject: [PATCH 065/295] feat: py::type::of() and py::type::of(h) (#2364) * feat: type() * refactor: using py::type as class * refactor: py::object as base * wip: tigher api * refactor: fix conversion and limit API further * docs: some added notes from @EricCousineau-TRI * refactor: use py::type::of --- docs/advanced/cast/index.rst | 2 ++ docs/advanced/classes.rst | 18 ++++++++++++++++++ include/pybind11/cast.h | 12 ++++++++++++ include/pybind11/pytypes.h | 16 ++++++++++++++++ tests/test_class.cpp | 26 ++++++++++++++++++++++++++ tests/test_class.py | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 108 insertions(+) diff --git a/docs/advanced/cast/index.rst b/docs/advanced/cast/index.rst index 724585c920..3ce9ea0286 100644 --- a/docs/advanced/cast/index.rst +++ b/docs/advanced/cast/index.rst @@ -1,3 +1,5 @@ +.. _type-conversions: + Type conversions ################ diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index f4efc68f8b..b91e8a1fce 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -1232,3 +1232,21 @@ appropriate derived-class pointer (e.g. using more complete example, including a demonstration of how to provide automatic downcasting for an entire class hierarchy without writing one get() function for each class. + +Accessing the type object +========================= + +You can get the type object from a C++ class that has already been registered using: + +.. code-block:: python + + py::type T_py = py::type::of(); + +You can directly use ``py::type::of(ob)`` to get the type object from any python +object, just like ``type(ob)`` in Python. + +.. note:: + + Other types, like ``py::type::of()``, do not work, see :ref:`type-conversions`. + +.. versionadded:: 2.6 diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index be62610a72..5601f2ec83 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -2204,6 +2204,18 @@ object object_api::call(Args &&...args) const { PYBIND11_NAMESPACE_END(detail) + +template +type type::of() { + static_assert( + std::is_base_of>::value, + "py::type::of only supports the case where T is a registered C++ types." + ); + + return type((PyObject*) detail::get_type_handle(typeid(T), true).ptr(), borrowed_t()); +} + + #define PYBIND11_MAKE_OPAQUE(...) \ namespace pybind11 { namespace detail { \ template<> class type_caster<__VA_ARGS__> : public type_caster_base<__VA_ARGS__> { }; \ diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 00c791aada..c1219fc2eb 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -19,6 +19,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) /* A few forward declarations */ class handle; class object; class str; class iterator; +class type; struct arg; struct arg_v; PYBIND11_NAMESPACE_BEGIN(detail) @@ -890,6 +891,21 @@ class iterator : public object { object value = {}; }; + + +class type : public object { +public: + PYBIND11_OBJECT(type, object, PyType_Check) + + static type of(handle h) { return type((PyObject*) Py_TYPE(h.ptr()), borrowed_t{}); } + + /// Convert C++ type to py::type if previously registered. Does not convert + // standard types, like int, float. etc. yet. + // See https://github.com/pybind/pybind11/issues/2486 + template + static type of(); +}; + class iterable : public object { public: PYBIND11_OBJECT_DEFAULT(iterable, object, detail::PyIterable_Check) diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 5369cb064c..b7d52a1b5b 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -134,6 +134,32 @@ TEST_SUBMODULE(class_, m) { ); }); + struct Invalid {}; + + // test_type + m.def("check_type", [](int category) { + // Currently not supported (via a fail at compile time) + // See https://github.com/pybind/pybind11/issues/2486 + // if (category == 2) + // return py::type::of(); + if (category == 1) + return py::type::of(); + else + return py::type::of(); + }); + + m.def("get_type_of", [](py::object ob) { + return py::type::of(ob); + }); + + m.def("as_type", [](py::object ob) { + auto tp = py::type(ob); + if (py::isinstance(ob)) + return tp; + else + throw std::runtime_error("Invalid type"); + }); + // test_mismatched_holder struct MismatchBase1 { }; struct MismatchDerived1 : MismatchBase1 { }; diff --git a/tests/test_class.py b/tests/test_class.py index 4214fe79d7..be21f3709f 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -26,6 +26,40 @@ def test_instance(msg): assert cstats.alive() == 0 +def test_type(): + assert m.check_type(1) == m.DerivedClass1 + with pytest.raises(RuntimeError) as execinfo: + m.check_type(0) + + assert 'pybind11::detail::get_type_info: unable to find type info' in str(execinfo.value) + assert 'Invalid' in str(execinfo.value) + + # Currently not supported + # See https://github.com/pybind/pybind11/issues/2486 + # assert m.check_type(2) == int + + +def test_type_of_py(): + assert m.get_type_of(1) == int + assert m.get_type_of(m.DerivedClass1()) == m.DerivedClass1 + assert m.get_type_of(int) == type + + +def test_type_of_py_nodelete(): + # If the above test deleted the class, this will segfault + assert m.get_type_of(m.DerivedClass1()) == m.DerivedClass1 + + +def test_as_type_py(): + assert m.as_type(int) == int + + with pytest.raises(RuntimeError): + assert m.as_type(1) == int + + with pytest.raises(RuntimeError): + assert m.as_type(m.DerivedClass1()) == m.DerivedClass1 + + def test_docstrings(doc): assert doc(UserType) == "A `py::class_` type for testing" assert UserType.__name__ == "UserType" From 9df13835c851753b7d385717eb79d8a06906e0c4 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Tue, 15 Sep 2020 14:50:51 +0200 Subject: [PATCH 066/295] Stop py::array_t arguments from accepting arrays that do not match the C- or F-contiguity flags (#2484) * Stop py::array_t arguments from accepting arrays that do not match the C- or F-contiguity flags * Add trivially-contiguous arrays to the tests --- include/pybind11/numpy.h | 3 ++- tests/test_numpy_array.cpp | 38 +++++++++++++++++++++++++++++++ tests/test_numpy_array.py | 46 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 0192a8b17b..c0b38ce202 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -934,7 +934,8 @@ template class array_t : public static bool check_(handle h) { const auto &api = detail::npy_api::get(); return api.PyArray_Check_(h.ptr()) - && api.PyArray_EquivTypes_(detail::array_proxy(h.ptr())->descr, dtype::of().ptr()); + && api.PyArray_EquivTypes_(detail::array_proxy(h.ptr())->descr, dtype::of().ptr()) + && detail::check_flags(h.ptr(), ExtraFlags & (array::c_style | array::f_style)); } protected: diff --git a/tests/test_numpy_array.cpp b/tests/test_numpy_array.cpp index e37beb5a5c..caa052549c 100644 --- a/tests/test_numpy_array.cpp +++ b/tests/test_numpy_array.cpp @@ -385,4 +385,42 @@ TEST_SUBMODULE(numpy_array, sm) { sm.def("index_using_ellipsis", [](py::array a) { return a[py::make_tuple(0, py::ellipsis(), 0)]; }); + + // test_argument_conversions + sm.def("accept_double", + [](py::array_t) {}, + py::arg("a")); + sm.def("accept_double_forcecast", + [](py::array_t) {}, + py::arg("a")); + sm.def("accept_double_c_style", + [](py::array_t) {}, + py::arg("a")); + sm.def("accept_double_c_style_forcecast", + [](py::array_t) {}, + py::arg("a")); + sm.def("accept_double_f_style", + [](py::array_t) {}, + py::arg("a")); + sm.def("accept_double_f_style_forcecast", + [](py::array_t) {}, + py::arg("a")); + sm.def("accept_double_noconvert", + [](py::array_t) {}, + py::arg("a").noconvert()); + sm.def("accept_double_forcecast_noconvert", + [](py::array_t) {}, + py::arg("a").noconvert()); + sm.def("accept_double_c_style_noconvert", + [](py::array_t) {}, + py::arg("a").noconvert()); + sm.def("accept_double_c_style_forcecast_noconvert", + [](py::array_t) {}, + py::arg("a").noconvert()); + sm.def("accept_double_f_style_noconvert", + [](py::array_t) {}, + py::arg("a").noconvert()); + sm.def("accept_double_f_style_forcecast_noconvert", + [](py::array_t) {}, + py::arg("a").noconvert()); } diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index ad3ca58c1a..a36e707c1d 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -435,6 +435,52 @@ def test_index_using_ellipsis(): assert a.shape == (6,) +@pytest.mark.parametrize("forcecast", [False, True]) +@pytest.mark.parametrize("contiguity", [None, 'C', 'F']) +@pytest.mark.parametrize("noconvert", [False, True]) +@pytest.mark.filterwarnings( + "ignore:Casting complex values to real discards the imaginary part:numpy.ComplexWarning" +) +def test_argument_conversions(forcecast, contiguity, noconvert): + function_name = "accept_double" + if contiguity == 'C': + function_name += "_c_style" + elif contiguity == 'F': + function_name += "_f_style" + if forcecast: + function_name += "_forcecast" + if noconvert: + function_name += "_noconvert" + function = getattr(m, function_name) + + for dtype in [np.dtype('float32'), np.dtype('float64'), np.dtype('complex128')]: + for order in ['C', 'F']: + for shape in [(2, 2), (1, 3, 1, 1), (1, 1, 1), (0,)]: + if not noconvert: + # If noconvert is not passed, only complex128 needs to be truncated and + # "cannot be safely obtained". So without `forcecast`, the argument shouldn't + # be accepted. + should_raise = dtype.name == 'complex128' and not forcecast + else: + # If noconvert is passed, only float64 and the matching order is accepted. + # If at most one dimension has a size greater than 1, the array is also + # trivially contiguous. + trivially_contiguous = sum(1 for d in shape if d > 1) <= 1 + should_raise = ( + dtype.name != 'float64' or + (contiguity is not None and + contiguity != order and + not trivially_contiguous) + ) + + array = np.zeros(shape, dtype=dtype, order=order) + if not should_raise: + function(array) + else: + with pytest.raises(TypeError, match="incompatible function arguments"): + function(array) + + @pytest.mark.xfail("env.PYPY") def test_dtype_refcount_leak(): from sys import getrefcount From d65e34d61d5f49ffbe7c2e7864391366f1124b0e Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Tue, 15 Sep 2020 14:56:20 +0200 Subject: [PATCH 067/295] Resolve empty statement warning when using PYBIND11_OVERLOAD_PURE_NAME and PYBIND11_OVERLOAD_PURE (#2325) * Wrap PYBIND11_OVERLOAD_NAME and PYBIND11_OVERLOAD_PURE_NAME in do { ... } while (false), and resolve trailing semicolon * Deprecate PYBIND11_OVERLOAD_* and get_overload in favor of PYBIND11_OVERRIDE_* and get_override * Correct erroneous usage of 'overload' instead of 'override' in the implementation and internals * Fix tests to use non-deprecated PYBIND11_OVERRIDE_* macros * Update docs to use override instead of overload where appropriate, and add warning about deprecated aliases * Add semicolons to deprecated PYBIND11_OVERLOAD macros to match original behavior * Remove deprecation of PYBIND11_OVERLOAD_* macros and get_overload * Add note to changelog and upgrade guide --- docs/advanced/cast/custom.rst | 6 +- docs/advanced/cast/stl.rst | 2 +- docs/advanced/classes.rst | 65 ++++++++++-------- docs/advanced/misc.rst | 10 +-- docs/changelog.rst | 6 ++ docs/reference.rst | 10 +-- docs/upgrade.rst | 4 ++ include/pybind11/cast.h | 10 +-- include/pybind11/detail/internals.h | 6 +- include/pybind11/pybind11.h | 97 ++++++++++++++++++--------- tests/test_class.cpp | 2 +- tests/test_embed/test_interpreter.cpp | 2 +- tests/test_factory_constructors.cpp | 4 +- tests/test_gil_scoped.cpp | 4 +- tests/test_virtual_functions.cpp | 60 ++++++++--------- 15 files changed, 169 insertions(+), 119 deletions(-) diff --git a/docs/advanced/cast/custom.rst b/docs/advanced/cast/custom.rst index e4f99ac5b0..a779444c24 100644 --- a/docs/advanced/cast/custom.rst +++ b/docs/advanced/cast/custom.rst @@ -29,9 +29,9 @@ The following Python snippet demonstrates the intended usage from the Python sid from example import print print(A()) -To register the necessary conversion routines, it is necessary to add -a partial overload to the ``pybind11::detail::type_caster`` template. -Although this is an implementation detail, adding partial overloads to this +To register the necessary conversion routines, it is necessary to add an +instantiation of the ``pybind11::detail::type_caster`` template. +Although this is an implementation detail, adding an instantiation of this type is explicitly allowed. .. code-block:: cpp diff --git a/docs/advanced/cast/stl.rst b/docs/advanced/cast/stl.rst index e48409f025..7f708b81ea 100644 --- a/docs/advanced/cast/stl.rst +++ b/docs/advanced/cast/stl.rst @@ -157,7 +157,7 @@ the declaration 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 -it instantiates a partial template overload. If your binding code consists of +it adds a template instantiation of ``type_caster``. If your binding code consists of multiple compilation units, it must be present in every file (typically via a common header) preceding any usage of ``std::vector``. Opaque types must also have a corresponding ``class_`` declaration to associate them with a name diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index b91e8a1fce..82812069b7 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -71,7 +71,7 @@ helper class that is defined as follows: /* Trampoline (need one for each virtual function) */ std::string go(int n_times) override { - PYBIND11_OVERLOAD_PURE( + PYBIND11_OVERRIDE_PURE( std::string, /* Return type */ Animal, /* Parent class */ go, /* Name of function in C++ (must match Python name) */ @@ -80,10 +80,10 @@ helper class that is defined as follows: } }; -The macro :c:macro:`PYBIND11_OVERLOAD_PURE` should be used for pure virtual -functions, and :c:macro:`PYBIND11_OVERLOAD` should be used for functions which have +The macro :c:macro:`PYBIND11_OVERRIDE_PURE` should be used for pure virtual +functions, and :c:macro:`PYBIND11_OVERRIDE` should be used for functions which have a default implementation. There are also two alternate macros -:c:macro:`PYBIND11_OVERLOAD_PURE_NAME` and :c:macro:`PYBIND11_OVERLOAD_NAME` which +:c:macro:`PYBIND11_OVERRIDE_PURE_NAME` and :c:macro:`PYBIND11_OVERRIDE_NAME` which take a string-valued name argument between the *Parent class* and *Name of the function* slots, which defines the name of function in Python. This is required when the C++ and Python versions of the @@ -122,7 +122,7 @@ Bindings should be made against the actual class, not the trampoline helper clas Note, however, that the above is sufficient for allowing python classes to extend ``Animal``, but not ``Dog``: see :ref:`virtual_and_inheritance` for the -necessary steps required to providing proper overload support for inherited +necessary steps required to providing proper overriding support for inherited classes. The Python session below shows how to override ``Animal::go`` and invoke it via @@ -181,15 +181,24 @@ Please take a look at the :ref:`macro_notes` before using this feature. - because in these cases there is no C++ variable to reference (the value is stored in the referenced Python variable), pybind11 provides one in - the PYBIND11_OVERLOAD macros (when needed) with static storage duration. - Note that this means that invoking the overloaded method on *any* + the PYBIND11_OVERRIDE macros (when needed) with static storage duration. + Note that this means that invoking the overridden method on *any* instance will change the referenced value stored in *all* instances of that type. - Attempts to modify a non-const reference will not have the desired effect: it will change only the static cache variable, but this change will not propagate to underlying Python instance, and the change will be - replaced the next time the overload is invoked. + replaced the next time the override is invoked. + +.. warning:: + + The :c:macro:`PYBIND11_OVERRIDE` and accompanying macros used to be called + ``PYBIND11_OVERLOAD`` up until pybind11 v2.5.0, and :func:`get_override` + used to be called ``get_overload``. This naming was corrected and the older + macro and function names have been deprecated, in order to reduce confusion + with overloaded functions and methods and ``py::overload_cast`` (see + :ref:`classes`). .. seealso:: @@ -237,20 +246,20 @@ override the ``name()`` method): class PyAnimal : public Animal { public: using Animal::Animal; // Inherit constructors - std::string go(int n_times) override { PYBIND11_OVERLOAD_PURE(std::string, Animal, go, n_times); } - std::string name() override { PYBIND11_OVERLOAD(std::string, Animal, name, ); } + std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Animal, go, n_times); } + std::string name() override { PYBIND11_OVERRIDE(std::string, Animal, name, ); } }; class PyDog : public Dog { public: using Dog::Dog; // Inherit constructors - std::string go(int n_times) override { PYBIND11_OVERLOAD(std::string, Dog, go, n_times); } - std::string name() override { PYBIND11_OVERLOAD(std::string, Dog, name, ); } - std::string bark() override { PYBIND11_OVERLOAD(std::string, Dog, bark, ); } + std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, Dog, go, n_times); } + std::string name() override { PYBIND11_OVERRIDE(std::string, Dog, name, ); } + std::string bark() override { PYBIND11_OVERRIDE(std::string, Dog, bark, ); } }; .. note:: - Note the trailing commas in the ``PYBIND11_OVERLOAD`` calls to ``name()`` + Note the trailing commas in the ``PYBIND11_OVERIDE`` calls to ``name()`` and ``bark()``. These are needed to portably implement a trampoline for a function that does not take any arguments. For functions that take a nonzero number of arguments, the trailing comma must be omitted. @@ -265,9 +274,9 @@ declare or override any virtual methods itself: class PyHusky : public Husky { public: using Husky::Husky; // Inherit constructors - std::string go(int n_times) override { PYBIND11_OVERLOAD_PURE(std::string, Husky, go, n_times); } - std::string name() override { PYBIND11_OVERLOAD(std::string, Husky, name, ); } - std::string bark() override { PYBIND11_OVERLOAD(std::string, Husky, bark, ); } + std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, Husky, go, n_times); } + std::string name() override { PYBIND11_OVERRIDE(std::string, Husky, name, ); } + std::string bark() override { PYBIND11_OVERRIDE(std::string, Husky, bark, ); } }; There is, however, a technique that can be used to avoid this duplication @@ -280,15 +289,15 @@ follows: template class PyAnimal : public AnimalBase { public: using AnimalBase::AnimalBase; // Inherit constructors - std::string go(int n_times) override { PYBIND11_OVERLOAD_PURE(std::string, AnimalBase, go, n_times); } - std::string name() override { PYBIND11_OVERLOAD(std::string, AnimalBase, name, ); } + std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE(std::string, AnimalBase, go, n_times); } + std::string name() override { PYBIND11_OVERRIDE(std::string, AnimalBase, name, ); } }; template class PyDog : public PyAnimal { public: using PyAnimal::PyAnimal; // Inherit constructors // Override PyAnimal's pure virtual go() with a non-pure one: - std::string go(int n_times) override { PYBIND11_OVERLOAD(std::string, DogBase, go, n_times); } - std::string bark() override { PYBIND11_OVERLOAD(std::string, DogBase, bark, ); } + std::string go(int n_times) override { PYBIND11_OVERRIDE(std::string, DogBase, go, n_times); } + std::string bark() override { PYBIND11_OVERRIDE(std::string, DogBase, bark, ); } }; This technique has the advantage of requiring just one trampoline method to be @@ -341,7 +350,7 @@ valid for the trampoline class but not the registered class. This is primarily for performance reasons: when the trampoline class is not needed for anything except virtual method dispatching, not initializing the trampoline class improves performance by avoiding needing to do a run-time check to see if the -inheriting python instance has an overloaded method. +inheriting python instance has an overridden method. Sometimes, however, it is useful to always initialize a trampoline class as an intermediate class that does more than just handle virtual method dispatching. @@ -372,7 +381,7 @@ references (See also :ref:`faq_reference_arguments`). Another way of solving this is to use the method body of the trampoline class to do conversions to the input and return of the Python method. -The main building block to do so is the :func:`get_overload`, this function +The main building block to do so is the :func:`get_override`, this function allows retrieving a method implemented in Python from within the trampoline's methods. Consider for example a C++ method which has the signature ``bool myMethod(int32_t& value)``, where the return indicates whether @@ -384,10 +393,10 @@ Python side by allowing the Python function to return ``None`` or an ``int``: bool MyClass::myMethod(int32_t& value) { pybind11::gil_scoped_acquire gil; // Acquire the GIL while in this scope. - // Try to look up the overloaded method on the Python side. - pybind11::function overload = pybind11::get_overload(this, "myMethod"); - if (overload) { // method is found - auto obj = overload(value); // Call the Python function. + // Try to look up the overridden method on the Python side. + pybind11::function override = pybind11::get_override(this, "myMethod"); + if (override) { // method is found + auto obj = override(value); // Call the Python function. if (py::isinstance(obj)) { // check if it returned a Python integer type value = obj.cast(); // Cast it and assign it to the value. return true; // Return true; value should be used. @@ -1104,7 +1113,7 @@ described trampoline: class Trampoline : public A { public: - int foo() const override { PYBIND11_OVERLOAD(int, A, foo, ); } + int foo() const override { PYBIND11_OVERRIDE(int, A, foo, ); } }; class Publicist : public A { diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index 8342210bcc..a5899c67a4 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -7,14 +7,14 @@ General notes regarding convenience macros ========================================== pybind11 provides a few convenience macros such as -:func:`PYBIND11_DECLARE_HOLDER_TYPE` and ``PYBIND11_OVERLOAD_*``. Since these +:func:`PYBIND11_DECLARE_HOLDER_TYPE` and ``PYBIND11_OVERRIDE_*``. Since these are "just" macros that are evaluated in the preprocessor (which has no concept of types), they *will* get confused by commas in a template argument; for example, consider: .. code-block:: cpp - PYBIND11_OVERLOAD(MyReturnType, Class, func) + PYBIND11_OVERRIDE(MyReturnType, Class, func) The limitation of the C preprocessor interprets this as five arguments (with new arguments beginning after each comma) rather than three. To get around this, @@ -26,10 +26,10 @@ using the ``PYBIND11_TYPE`` macro: // Version 1: using a type alias using ReturnType = MyReturnType; using ClassType = Class; - PYBIND11_OVERLOAD(ReturnType, ClassType, func); + PYBIND11_OVERRIDE(ReturnType, ClassType, func); // Version 2: using the PYBIND11_TYPE macro: - PYBIND11_OVERLOAD(PYBIND11_TYPE(MyReturnType), + PYBIND11_OVERRIDE(PYBIND11_TYPE(MyReturnType), PYBIND11_TYPE(Class), func) The ``PYBIND11_MAKE_OPAQUE`` macro does *not* require the above workarounds. @@ -59,7 +59,7 @@ could be realized as follows (important changes highlighted): /* Acquire GIL before calling Python code */ py::gil_scoped_acquire acquire; - PYBIND11_OVERLOAD_PURE( + PYBIND11_OVERRIDE_PURE( std::string, /* Return type */ Animal, /* Parent class */ go, /* Name of function */ diff --git a/docs/changelog.rst b/docs/changelog.rst index 77fd441735..3546040033 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -62,6 +62,12 @@ See :ref:`upgrade-guide-2.6` for help upgrading to the new version. `#2265 `_ and `#2346 `_ +* ``PYBIND11_OVERLOAD*`` macros and ``get_overload`` function replaced by + correctly-named ``PYBIND11_OVERRIDE*`` and ``get_override``, fixing + inconsistencies in the presene of a closing ``;`` in these macros. + ``get_type_overload`` is deprecated. + `#2325 `_ + Smaller or developer focused features: * Error now thrown when ``__init__`` is forgotten on subclasses. diff --git a/docs/reference.rst b/docs/reference.rst index a9fbe60015..752dfed2d6 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -91,15 +91,15 @@ Inheritance See :doc:`/classes` and :doc:`/advanced/classes` for more detail. -.. doxygendefine:: PYBIND11_OVERLOAD +.. doxygendefine:: PYBIND11_OVERRIDE -.. doxygendefine:: PYBIND11_OVERLOAD_PURE +.. doxygendefine:: PYBIND11_OVERRIDE_PURE -.. doxygendefine:: PYBIND11_OVERLOAD_NAME +.. doxygendefine:: PYBIND11_OVERRIDE_NAME -.. doxygendefine:: PYBIND11_OVERLOAD_PURE_NAME +.. doxygendefine:: PYBIND11_OVERRIDE_PURE_NAME -.. doxygenfunction:: get_overload +.. doxygenfunction:: get_override Exceptions ========== diff --git a/docs/upgrade.rst b/docs/upgrade.rst index 894c65fdcb..502ce76eed 100644 --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -21,6 +21,10 @@ If ``__eq__`` defined but not ``__hash__``, ``__hash__`` is now set to ``None``, as in normal CPython. You should add ``__hash__`` if you intended the class to be hashable, possibly using the new ``py::hash`` shortcut. +Usage of the ``PYBIND11_OVERLOAD*`` macros and ``get_overload`` function should +be replaced by ``PYBIND11_OVERRIDE*`` and ``get_override``. In the future, the +old macros may be deprecated and removed. + CMake support: -------------- diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 5601f2ec83..c588bd2f38 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1797,16 +1797,16 @@ PYBIND11_NAMESPACE_BEGIN(detail) template ::value, int>> object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } -struct overload_unused {}; // Placeholder type for the unneeded (and dead code) static variable in the OVERLOAD_INT macro -template using overload_caster_t = conditional_t< - cast_is_temporary_value_reference::value, make_caster, overload_unused>; +struct override_unused {}; // Placeholder type for the unneeded (and dead code) static variable in the PYBIND11_OVERRIDE_OVERRIDE macro +template using override_caster_t = conditional_t< + cast_is_temporary_value_reference::value, make_caster, override_unused>; // Trampoline use: for reference/pointer types to value-converted values, we do a value cast, then // store the result in the given variable. For other types, this is a no-op. template enable_if_t::value, T> cast_ref(object &&o, make_caster &caster) { return cast_op(load_type(caster, o)); } -template enable_if_t::value, T> cast_ref(object &&, overload_unused &) { +template enable_if_t::value, T> cast_ref(object &&, override_unused &) { pybind11_fail("Internal error: cast_ref fallback invoked"); } // Trampoline use: Having a pybind11::cast with an invalid reference type is going to static_assert, even @@ -2222,7 +2222,7 @@ type type::of() { }} /// Lets you pass a type containing a `,` through a macro parameter without needing a separate -/// typedef, e.g.: `PYBIND11_OVERLOAD(PYBIND11_TYPE(ReturnType), PYBIND11_TYPE(Parent), f, arg)` +/// typedef, e.g.: `PYBIND11_OVERRIDE(PYBIND11_TYPE(ReturnType), PYBIND11_TYPE(Parent), f, arg)` #define PYBIND11_TYPE(...) __VA_ARGS__ PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index cf40e9fe99..133d2f4c83 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -82,10 +82,10 @@ struct type_equal_to { template using type_map = std::unordered_map; -struct overload_hash { +struct override_hash { inline size_t operator()(const std::pair& v) const { size_t value = std::hash()(v.first); - value ^= std::hash()(v.second) + 0x9e3779b9 + (value<<6) + (value>>2); + value ^= std::hash()(v.second) + 0x9e3779b9 + (value<<6) + (value>>2); return value; } }; @@ -97,7 +97,7 @@ struct internals { type_map registered_types_cpp; // std::type_index -> pybind11's type information std::unordered_map> registered_types_py; // PyTypeObject* -> base type_info(s) std::unordered_multimap registered_instances; // void * -> instance* - std::unordered_set, overload_hash> inactive_overload_cache; + std::unordered_set, override_hash> inactive_override_cache; type_map> direct_conversions; std::unordered_map> patients; std::forward_list registered_exception_translators; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 602d5790a2..866a62dc0f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2110,21 +2110,22 @@ error_already_set::~error_already_set() { } } -inline function get_type_overload(const void *this_ptr, const detail::type_info *this_type, const char *name) { - handle self = detail::get_object_handle(this_ptr, this_type); +PYBIND11_NAMESPACE_BEGIN(detail) +inline function get_type_override(const void *this_ptr, const type_info *this_type, const char *name) { + handle self = get_object_handle(this_ptr, this_type); if (!self) return function(); handle type = self.get_type(); auto key = std::make_pair(type.ptr(), name); - /* Cache functions that aren't overloaded in Python to avoid + /* Cache functions that aren't overridden in Python to avoid many costly Python dictionary lookups below */ - auto &cache = detail::get_internals().inactive_overload_cache; + auto &cache = get_internals().inactive_override_cache; if (cache.find(key) != cache.end()) return function(); - function overload = getattr(self, name, function()); - if (overload.is_cpp_function()) { + function override = getattr(self, name, function()); + if (override.is_cpp_function()) { cache.insert(key); return function(); } @@ -2164,34 +2165,36 @@ inline function get_type_overload(const void *this_ptr, const detail::type_info Py_DECREF(result); #endif - return overload; + return override; } +PYBIND11_NAMESPACE_END(detail) /** \rst Try to retrieve a python method by the provided name from the instance pointed to by the this_ptr. - :this_ptr: The pointer to the object the overload should be retrieved for. This should be the first - non-trampoline class encountered in the inheritance chain. - :name: The name of the overloaded Python method to retrieve. + :this_ptr: The pointer to the object the overriden method should be retrieved for. This should be + the first non-trampoline class encountered in the inheritance chain. + :name: The name of the overridden Python method to retrieve. :return: The Python method by this name from the object or an empty function wrapper. \endrst */ -template function get_overload(const T *this_ptr, const char *name) { +template function get_override(const T *this_ptr, const char *name) { auto tinfo = detail::get_type_info(typeid(T)); - return tinfo ? get_type_overload(this_ptr, tinfo, name) : function(); + return tinfo ? detail::get_type_override(this_ptr, tinfo, name) : function(); } -#define PYBIND11_OVERLOAD_INT(ret_type, cname, name, ...) { \ +#define PYBIND11_OVERRIDE_IMPL(ret_type, cname, name, ...) \ + do { \ pybind11::gil_scoped_acquire gil; \ - pybind11::function overload = pybind11::get_overload(static_cast(this), name); \ - if (overload) { \ - auto o = overload(__VA_ARGS__); \ + pybind11::function override = pybind11::get_override(static_cast(this), name); \ + if (override) { \ + auto o = override(__VA_ARGS__); \ if (pybind11::detail::cast_is_temporary_value_reference::value) { \ - static pybind11::detail::overload_caster_t caster; \ + static pybind11::detail::override_caster_t caster; \ return pybind11::detail::cast_ref(std::move(o), caster); \ } \ else return pybind11::detail::cast_safe(std::move(o)); \ } \ - } + } while (false) /** \rst Macro to populate the virtual method in the trampoline class. This macro tries to look up a method named 'fn' @@ -2202,7 +2205,7 @@ template function get_overload(const T *this_ptr, const char *name) { .. code-block:: cpp std::string toString() override { - PYBIND11_OVERLOAD_NAME( + PYBIND11_OVERRIDE_NAME( std::string, // Return type (ret_type) Animal, // Parent class (cname) "__str__", // Name of method in Python (name) @@ -2210,17 +2213,21 @@ template function get_overload(const T *this_ptr, const char *name) { ); } \endrst */ -#define PYBIND11_OVERLOAD_NAME(ret_type, cname, name, fn, ...) \ - PYBIND11_OVERLOAD_INT(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, __VA_ARGS__) \ - return cname::fn(__VA_ARGS__) +#define PYBIND11_OVERRIDE_NAME(ret_type, cname, name, fn, ...) \ + do { \ + PYBIND11_OVERRIDE_IMPL(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, __VA_ARGS__); \ + return cname::fn(__VA_ARGS__); \ + } while (false) /** \rst - Macro for pure virtual functions, this function is identical to :c:macro:`PYBIND11_OVERLOAD_NAME`, except that it - throws if no overload can be found. + Macro for pure virtual functions, this function is identical to :c:macro:`PYBIND11_OVERRIDE_NAME`, except that it + throws if no override can be found. \endrst */ -#define PYBIND11_OVERLOAD_PURE_NAME(ret_type, cname, name, fn, ...) \ - PYBIND11_OVERLOAD_INT(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, __VA_ARGS__) \ - pybind11::pybind11_fail("Tried to call pure virtual function \"" PYBIND11_STRINGIFY(cname) "::" name "\""); +#define PYBIND11_OVERRIDE_PURE_NAME(ret_type, cname, name, fn, ...) \ + do { \ + PYBIND11_OVERRIDE_IMPL(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, __VA_ARGS__); \ + pybind11::pybind11_fail("Tried to call pure virtual function \"" PYBIND11_STRINGIFY(cname) "::" name "\""); \ + } while (false) /** \rst Macro to populate the virtual method in the trampoline class. This macro tries to look up the method @@ -2237,7 +2244,7 @@ template function get_overload(const T *this_ptr, const char *name) { // Trampoline (need one for each virtual function) std::string go(int n_times) override { - PYBIND11_OVERLOAD_PURE( + PYBIND11_OVERRIDE_PURE( std::string, // Return type (ret_type) Animal, // Parent class (cname) go, // Name of function in C++ (must match Python name) (fn) @@ -2246,15 +2253,39 @@ template function get_overload(const T *this_ptr, const char *name) { } }; \endrst */ -#define PYBIND11_OVERLOAD(ret_type, cname, fn, ...) \ - PYBIND11_OVERLOAD_NAME(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), #fn, fn, __VA_ARGS__) +#define PYBIND11_OVERRIDE(ret_type, cname, fn, ...) \ + PYBIND11_OVERRIDE_NAME(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), #fn, fn, __VA_ARGS__) /** \rst - Macro for pure virtual functions, this function is identical to :c:macro:`PYBIND11_OVERLOAD`, except that it throws - if no overload can be found. + Macro for pure virtual functions, this function is identical to :c:macro:`PYBIND11_OVERRIDE`, except that it throws + if no override can be found. \endrst */ +#define PYBIND11_OVERRIDE_PURE(ret_type, cname, fn, ...) \ + PYBIND11_OVERRIDE_PURE_NAME(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), #fn, fn, __VA_ARGS__) + + +// Deprecated versions + +PYBIND11_DEPRECATED("get_type_overload has been deprecated") +inline function get_type_overload(const void *this_ptr, const detail::type_info *this_type, const char *name) { + return detail::get_type_override(this_ptr, this_type, name); +} + +template +inline function get_overload(const T *this_ptr, const char *name) { + return get_override(this_ptr, name); +} + +#define PYBIND11_OVERLOAD_INT(ret_type, cname, name, ...) \ + PYBIND11_OVERRIDE_IMPL(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, __VA_ARGS__) +#define PYBIND11_OVERLOAD_NAME(ret_type, cname, name, fn, ...) \ + PYBIND11_OVERRIDE_NAME(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, fn, __VA_ARGS__) +#define PYBIND11_OVERLOAD_PURE_NAME(ret_type, cname, name, fn, ...) \ + PYBIND11_OVERRIDE_PURE_NAME(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), name, fn, __VA_ARGS__); +#define PYBIND11_OVERLOAD(ret_type, cname, fn, ...) \ + PYBIND11_OVERRIDE(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), fn, __VA_ARGS__) #define PYBIND11_OVERLOAD_PURE(ret_type, cname, fn, ...) \ - PYBIND11_OVERLOAD_PURE_NAME(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), #fn, fn, __VA_ARGS__) + PYBIND11_OVERRIDE_PURE(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), fn, __VA_ARGS__); PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/tests/test_class.cpp b/tests/test_class.cpp index b7d52a1b5b..e7eaa83eaa 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -317,7 +317,7 @@ TEST_SUBMODULE(class_, m) { class TrampolineB : public ProtectedB { public: - int foo() const override { PYBIND11_OVERLOAD(int, ProtectedB, foo, ); } + int foo() const override { PYBIND11_OVERRIDE(int, ProtectedB, foo, ); } }; class PublicistB : public ProtectedB { diff --git a/tests/test_embed/test_interpreter.cpp b/tests/test_embed/test_interpreter.cpp index 222bd565fb..753ce54dcd 100644 --- a/tests/test_embed/test_interpreter.cpp +++ b/tests/test_embed/test_interpreter.cpp @@ -30,7 +30,7 @@ class Widget { class PyWidget final : public Widget { using Widget::Widget; - int the_answer() const override { PYBIND11_OVERLOAD_PURE(int, Widget, the_answer); } + int the_answer() const override { PYBIND11_OVERRIDE_PURE(int, Widget, the_answer); } }; PYBIND11_EMBEDDED_MODULE(widget_module, m) { diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index f614743460..f70ed3335b 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -89,7 +89,7 @@ class PyTF6 : public TestFactory6 { PyTF6(const PyTF6 &f) : TestFactory6(f) { print_copy_created(this); } PyTF6(std::string s) : TestFactory6((int) s.size()) { alias = true; print_created(this, s); } virtual ~PyTF6() { print_destroyed(this); } - int get() override { PYBIND11_OVERLOAD(int, TestFactory6, get, /*no args*/); } + int get() override { PYBIND11_OVERRIDE(int, TestFactory6, get, /*no args*/); } }; class TestFactory7 { @@ -110,7 +110,7 @@ class PyTF7 : public TestFactory7 { PyTF7(PyTF7 &&f) : TestFactory7(std::move(f)) { print_move_created(this); } PyTF7(const PyTF7 &f) : TestFactory7(f) { print_copy_created(this); } virtual ~PyTF7() { print_destroyed(this); } - int get() override { PYBIND11_OVERLOAD(int, TestFactory7, get, /*no args*/); } + int get() override { PYBIND11_OVERRIDE(int, TestFactory7, get, /*no args*/); } }; diff --git a/tests/test_gil_scoped.cpp b/tests/test_gil_scoped.cpp index dc9b7ed224..eb6308956c 100644 --- a/tests/test_gil_scoped.cpp +++ b/tests/test_gil_scoped.cpp @@ -22,10 +22,10 @@ class VirtClass { class PyVirtClass : public VirtClass { void virtual_func() override { - PYBIND11_OVERLOAD(void, VirtClass, virtual_func,); + PYBIND11_OVERRIDE(void, VirtClass, virtual_func,); } void pure_virtual_func() override { - PYBIND11_OVERLOAD_PURE(void, VirtClass, pure_virtual_func,); + PYBIND11_OVERRIDE_PURE(void, VirtClass, pure_virtual_func,); } }; diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index c5ee6365fb..0210695e25 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -47,7 +47,7 @@ class PyExampleVirt : public ExampleVirt { int run(int value) override { /* Generate wrapping code that enables native function overloading */ - PYBIND11_OVERLOAD( + PYBIND11_OVERRIDE( int, /* Return type */ ExampleVirt, /* Parent class */ run, /* Name of function */ @@ -56,7 +56,7 @@ class PyExampleVirt : public ExampleVirt { } bool run_bool() override { - PYBIND11_OVERLOAD_PURE( + PYBIND11_OVERRIDE_PURE( bool, /* Return type */ ExampleVirt, /* Parent class */ run_bool, /* Name of function */ @@ -66,7 +66,7 @@ class PyExampleVirt : public ExampleVirt { } void pure_virtual() override { - PYBIND11_OVERLOAD_PURE( + PYBIND11_OVERRIDE_PURE( void, /* Return type */ ExampleVirt, /* Parent class */ pure_virtual, /* Name of function */ @@ -78,7 +78,7 @@ class PyExampleVirt : public ExampleVirt { // We can return reference types for compatibility with C++ virtual interfaces that do so, but // note they have some significant limitations (see the documentation). const std::string &get_string1() override { - PYBIND11_OVERLOAD( + PYBIND11_OVERRIDE( const std::string &, /* Return type */ ExampleVirt, /* Parent class */ get_string1, /* Name of function */ @@ -87,7 +87,7 @@ class PyExampleVirt : public ExampleVirt { } const std::string *get_string2() override { - PYBIND11_OVERLOAD( + PYBIND11_OVERRIDE( const std::string *, /* Return type */ ExampleVirt, /* Parent class */ get_string2, /* Name of function */ @@ -141,11 +141,11 @@ class NCVirt { class NCVirtTrampoline : public NCVirt { #if !defined(__INTEL_COMPILER) && !defined(__CUDACC__) && !defined(__PGIC__) NonCopyable get_noncopyable(int a, int b) override { - PYBIND11_OVERLOAD(NonCopyable, NCVirt, get_noncopyable, a, b); + PYBIND11_OVERRIDE(NonCopyable, NCVirt, get_noncopyable, a, b); } #endif Movable get_movable(int a, int b) override { - PYBIND11_OVERLOAD_PURE(Movable, NCVirt, get_movable, a, b); + PYBIND11_OVERRIDE_PURE(Movable, NCVirt, get_movable, a, b); } }; @@ -159,7 +159,7 @@ struct Base { struct DispatchIssue : Base { virtual std::string dispatch() const { - PYBIND11_OVERLOAD_PURE(std::string, Base, dispatch, /* no arguments */); + PYBIND11_OVERRIDE_PURE(std::string, Base, dispatch, /* no arguments */); } }; @@ -240,7 +240,7 @@ TEST_SUBMODULE(virtual_functions, m) { py::print("PyA.f()"); // This convolution just gives a `void`, but tests that PYBIND11_TYPE() works to protect // a type containing a , - PYBIND11_OVERLOAD(PYBIND11_TYPE(typename std::enable_if::type), A, f); + PYBIND11_OVERRIDE(PYBIND11_TYPE(typename std::enable_if::type), A, f); } }; @@ -265,7 +265,7 @@ TEST_SUBMODULE(virtual_functions, m) { ~PyA2() { py::print("PyA2.~PyA2()"); } void f() override { py::print("PyA2.f()"); - PYBIND11_OVERLOAD(void, A2, f); + PYBIND11_OVERRIDE(void, A2, f); } }; @@ -304,19 +304,19 @@ TEST_SUBMODULE(virtual_functions, m) { class PyOverrideTest : public OverrideTest { public: using OverrideTest::OverrideTest; - std::string str_value() override { PYBIND11_OVERLOAD(std::string, OverrideTest, str_value); } + std::string str_value() override { PYBIND11_OVERRIDE(std::string, OverrideTest, str_value); } // Not allowed (uncommenting should hit a static_assert failure): we can't get a reference // to a python numeric value, since we only copy values in the numeric type caster: -// std::string &str_ref() override { PYBIND11_OVERLOAD(std::string &, OverrideTest, str_ref); } +// std::string &str_ref() override { PYBIND11_OVERRIDE(std::string &, OverrideTest, str_ref); } // But we can work around it like this: private: std::string _tmp; - std::string str_ref_helper() { PYBIND11_OVERLOAD(std::string, OverrideTest, str_ref); } + std::string str_ref_helper() { PYBIND11_OVERRIDE(std::string, OverrideTest, str_ref); } public: std::string &str_ref() override { return _tmp = str_ref_helper(); } - A A_value() override { PYBIND11_OVERLOAD(A, OverrideTest, A_value); } - A &A_ref() override { PYBIND11_OVERLOAD(A &, OverrideTest, A_ref); } + A A_value() override { PYBIND11_OVERRIDE(A, OverrideTest, A_value); } + A &A_ref() override { PYBIND11_OVERRIDE(A &, OverrideTest, A_ref); } }; py::class_(m, "OverrideTest_A") @@ -393,29 +393,29 @@ class D_Tpl : public C_Tpl { D_METHODS }; class PyA_Repeat : public A_Repeat { public: using A_Repeat::A_Repeat; - int unlucky_number() override { PYBIND11_OVERLOAD_PURE(int, A_Repeat, unlucky_number, ); } - std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, A_Repeat, say_something, times); } + int unlucky_number() override { PYBIND11_OVERRIDE_PURE(int, A_Repeat, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERRIDE(std::string, A_Repeat, say_something, times); } }; class PyB_Repeat : public B_Repeat { public: using B_Repeat::B_Repeat; - int unlucky_number() override { PYBIND11_OVERLOAD(int, B_Repeat, unlucky_number, ); } - std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, B_Repeat, say_something, times); } - double lucky_number() override { PYBIND11_OVERLOAD(double, B_Repeat, lucky_number, ); } + int unlucky_number() override { PYBIND11_OVERRIDE(int, B_Repeat, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERRIDE(std::string, B_Repeat, say_something, times); } + double lucky_number() override { PYBIND11_OVERRIDE(double, B_Repeat, lucky_number, ); } }; class PyC_Repeat : public C_Repeat { public: using C_Repeat::C_Repeat; - int unlucky_number() override { PYBIND11_OVERLOAD(int, C_Repeat, unlucky_number, ); } - std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, C_Repeat, say_something, times); } - double lucky_number() override { PYBIND11_OVERLOAD(double, C_Repeat, lucky_number, ); } + int unlucky_number() override { PYBIND11_OVERRIDE(int, C_Repeat, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERRIDE(std::string, C_Repeat, say_something, times); } + double lucky_number() override { PYBIND11_OVERRIDE(double, C_Repeat, lucky_number, ); } }; class PyD_Repeat : public D_Repeat { public: using D_Repeat::D_Repeat; - int unlucky_number() override { PYBIND11_OVERLOAD(int, D_Repeat, unlucky_number, ); } - std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, D_Repeat, say_something, times); } - double lucky_number() override { PYBIND11_OVERLOAD(double, D_Repeat, lucky_number, ); } + int unlucky_number() override { PYBIND11_OVERRIDE(int, D_Repeat, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERRIDE(std::string, D_Repeat, say_something, times); } + double lucky_number() override { PYBIND11_OVERRIDE(double, D_Repeat, lucky_number, ); } }; // Inheritance approach 2: templated trampoline classes. @@ -436,15 +436,15 @@ template class PyA_Tpl : public Base { public: using Base::Base; // Inherit constructors - int unlucky_number() override { PYBIND11_OVERLOAD_PURE(int, Base, unlucky_number, ); } - std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, Base, say_something, times); } + int unlucky_number() override { PYBIND11_OVERRIDE_PURE(int, Base, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERRIDE(std::string, Base, say_something, times); } }; template class PyB_Tpl : public PyA_Tpl { public: using PyA_Tpl::PyA_Tpl; // Inherit constructors (via PyA_Tpl's inherited constructors) - int unlucky_number() override { PYBIND11_OVERLOAD(int, Base, unlucky_number, ); } - double lucky_number() override { PYBIND11_OVERLOAD(double, Base, lucky_number, ); } + int unlucky_number() override { PYBIND11_OVERRIDE(int, Base, unlucky_number, ); } + double lucky_number() override { PYBIND11_OVERRIDE(double, Base, lucky_number, ); } }; // Since C_Tpl and D_Tpl don't declare any new virtual methods, we don't actually need these (we can // use PyB_Tpl and PyB_Tpl for the trampoline classes instead): From 8dc31c7b29663eb7d0636c4e6117a042fa8dc85f Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 10 Sep 2020 21:16:40 -0400 Subject: [PATCH 068/295] style: clang-tidy: llvm-namespace-comment --- .clang-tidy | 8 ++++++++ .github/CONTRIBUTING.md | 25 ++++++++++++++++++++++++- .github/workflows/format.yml | 16 ++++++++++++++++ include/pybind11/cast.h | 2 +- include/pybind11/pytypes.h | 4 ++-- tests/local_bindings.h | 2 +- tests/test_constants_and_functions.cpp | 2 +- tests/test_custom_type_casters.cpp | 6 ++++-- tests/test_operator_overloading.cpp | 2 +- tests/test_smart_ptr.cpp | 3 ++- tests/test_stl.cpp | 2 +- tests/test_tagbased_polymorphic.cpp | 2 +- 12 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000000..798e676d57 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,8 @@ +FormatStyle: file + +Checks: ' +-*, +llvm-namespace-comment, +' + +HeaderFilterRegex: 'pybind11/.*h' diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e83c31565d..4cdd7aab02 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -164,6 +164,30 @@ name, pre-commit): pre-commit install ``` +### Clang-Tidy + +To run Clang tidy, the following recipe should work. Files will be modified in +place, so you can use git to monitor the changes. + +```bash +docker run --rm -v $PWD:/pybind11 -it silkeh/clang:10 +apt-get update && apt-get install python3-dev python3-pytest +cmake -S pybind11/ -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);-fix" +cmake --build build +``` + +### Include what you use + +To run include what you use, install (`brew install include-what-you-use` on +macOS), then run: + +```bash +cmake -S . -B build-iwyu -DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=$(which include-what-you-use) +cmake --build build +``` + +The report is sent to stderr; you can pip it into a file if you wish. + ### Build recipes This builds with the Intel compiler (assuming it is in your path, along with a @@ -186,7 +210,6 @@ cmake -S pybind11/ -B build cmake --build build ``` - [pre-commit]: https://pre-commit.com [pybind11.readthedocs.org]: http://pybind11.readthedocs.org/en/latest [issue tracker]: https://github.com/pybind/pybind11/issues diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index e92f96e6ef..191219326d 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -17,3 +17,19 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - uses: pre-commit/action@v2.0.0 + + clang-tidy: + name: Clang-Tidy + runs-on: ubuntu-latest + container: silkeh/clang:10 + steps: + - uses: actions/checkout@v2 + + - name: Install requirements + run: apt-get update && apt-get install -y python3-dev python3-pytest + + - name: Configure + run: cmake -S . -B build -DCMAKE_CXX_CLANG_TIDY="$(which clang-tidy);--warnings-as-errors=*" + + - name: Build + run: cmake --build build -j 2 diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index c588bd2f38..47a54aff03 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1929,7 +1929,7 @@ inline namespace literals { String literal version of `arg` \endrst */ constexpr arg operator"" _a(const char *name, size_t) { return arg(name); } -} +} // namespace literals PYBIND11_NAMESPACE_BEGIN(detail) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index c1219fc2eb..8e05261a30 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -35,7 +35,7 @@ namespace accessor_policies { struct sequence_item; struct list_item; struct tuple_item; -} +} // namespace accessor_policies using obj_attr_accessor = accessor; using str_attr_accessor = accessor; using item_accessor = accessor; @@ -976,7 +976,7 @@ inline namespace literals { String literal version of `str` \endrst */ inline str operator"" _s(const char *s, size_t size) { return {s, size}; } -} +} // namespace literals /// \addtogroup pytypes /// @{ diff --git a/tests/local_bindings.h b/tests/local_bindings.h index b6afb80866..22537b13ad 100644 --- a/tests/local_bindings.h +++ b/tests/local_bindings.h @@ -58,7 +58,7 @@ class Pet { std::string name_; const std::string &name() { return name_; } }; -} +} // namespace pets struct MixGL { int i; MixGL(int i) : i{i} {} }; struct MixGL2 { int i; MixGL2(int i) : i{i} {} }; diff --git a/tests/test_constants_and_functions.cpp b/tests/test_constants_and_functions.cpp index e8ec74b7bc..f607795593 100644 --- a/tests/test_constants_and_functions.cpp +++ b/tests/test_constants_and_functions.cpp @@ -74,7 +74,7 @@ struct C { # pragma GCC diagnostic pop #endif }; -} +} // namespace test_exc_sp TEST_SUBMODULE(constants_and_functions, m) { diff --git a/tests/test_custom_type_casters.cpp b/tests/test_custom_type_casters.cpp index 9485d3cdb2..d565add264 100644 --- a/tests/test_custom_type_casters.cpp +++ b/tests/test_custom_type_casters.cpp @@ -58,7 +58,8 @@ template <> struct type_caster { return py::none().release(); } }; -}} +} // namespace detail +} // namespace pybind11 // test_custom_caster_destruction class DestructionTester { @@ -79,7 +80,8 @@ template <> struct type_caster { return py::bool_(true).release(); } }; -}} +} // namespace detail +} // namespace pybind11 TEST_SUBMODULE(custom_type_casters, m) { // test_custom_type_casters diff --git a/tests/test_operator_overloading.cpp b/tests/test_operator_overloading.cpp index d176c4644b..d55495471a 100644 --- a/tests/test_operator_overloading.cpp +++ b/tests/test_operator_overloading.cpp @@ -73,7 +73,7 @@ namespace std { // Not a good hash function, but easy to test size_t operator()(const Vector2 &) { return 4; } }; -} +} // namespace std // Not a good abs function, but easy to test. std::string abs(const Vector2&) { diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index bea90691d4..af7b86ebca 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -27,7 +27,8 @@ namespace pybind11 { namespace detail { struct holder_helper> { static const T *get(const ref &p) { return p.get_ptr(); } }; -}} +} // namespace detail +} // namespace pybind11 // The following is not required anymore for std::shared_ptr, but it should compile without error: PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index b230717d2f..0590162770 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -47,7 +47,7 @@ struct TplCtorClass { namespace std { template <> struct hash { size_t operator()(const TplCtorClass &) const { return 0; } }; -} +} // namespace std template