diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 00b1fea4cf..3a7b380ba0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -116,7 +116,8 @@ 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) +* `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests and the static library target) +* `-DPYBIND11_BUILD_STATIC_LIB=OFF`: Don't build a static library target. * `-DBUILD_TESTING=ON`: Enable the tests * `-DDOWNLOAD_CATCH=ON`: Download catch to build the C++ tests * `-DDOWNLOAD_EIGEN=ON`: Download Eigen for the NumPy tests diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 31d893c479..d8d9766ef9 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -43,6 +43,8 @@ jobs: - name: Install requirements run: apt-get update && apt-get install -y python3-dev python3-pytest + # The static library mode is disabled so Clang-Tidy doesn't complain about + # non-inline definitions in header files ('*-inl.h'). - name: Configure run: > cmake -S . -B build @@ -50,6 +52,7 @@ jobs: -DDOWNLOAD_EIGEN=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=17 + -DPYBIND11_BUILD_STATIC_LIB=OFF - name: Build run: cmake --build build -j 2 -- --keep-going diff --git a/CMakeLists.txt b/CMakeLists.txt index ee0975bc1f..f1638e82fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,8 @@ endif() option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_NOPYTHON "Disable search for Python" OFF) +option(PYBIND11_BUILD_STATIC_LIB + "Create a static library target for pybind11 that is not header-only (default)" ON) set(PYBIND11_INTERNALS_VERSION "" CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") @@ -107,6 +109,7 @@ cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" OFF set(PYBIND11_HEADERS include/pybind11/detail/class.h include/pybind11/detail/common.h + include/pybind11/detail/common-inl.h include/pybind11/detail/descr.h include/pybind11/detail/init.h include/pybind11/detail/internals.h @@ -133,6 +136,25 @@ set(PYBIND11_HEADERS include/pybind11/stl_bind.h include/pybind11/stl/filesystem.h) +file(GLOB_RECURSE PYBIND11_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) +# `embed.cc` is the TU to support embedding a python interpreter. But PyPy does not +# support embedding, so remove it from sources. +if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy") + # Pypy does not support embedding. + list(REMOVE_ITEM PYBIND11_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/embed.cc) +endif() + +# If we're not configured to be header-only, we need to build a shared library +# and define the pybind11::lib target. +if(PYBIND11_BUILD_STATIC_LIB) + add_library(pybind11_static STATIC ${PYBIND11_SOURCES} ${PYBIND11_HEADERS}) + set_property(TARGET pybind11_static PROPERTY POSITION_INDEPENDENT_CODE ON) + target_compile_definitions(pybind11_static PUBLIC -DPYBIND11_AS_STATIC_LIBRARY=1) + add_library(pybind11::static ALIAS pybind11_static) + target_link_libraries(pybind11_static PRIVATE pybind11::pybind11) + target_link_libraries(pybind11_static PUBLIC pybind11::headers) +endif() + # Compare with grep and warn if mismatched if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) file( @@ -304,6 +326,10 @@ else() endif() endif() +if(_pybind11_nopython AND PYBIND11_BUILD_STATIC_LIB) + message(FATAL_ERROR "Cannot build static library in NOPYTHON mode") +endif() + # Better symmetry with find_package(pybind11 CONFIG) mode. if(NOT PYBIND11_MASTER_PROJECT) set(pybind11_FOUND diff --git a/docs/Doxyfile b/docs/Doxyfile index 09138db364..f348dc5201 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -11,6 +11,7 @@ XML_PROGRAMLISTING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES EXPAND_AS_DEFINED = PYBIND11_RUNTIME_EXCEPTION +EXCLUDE_PATTERNS = *-inl.h ALIASES = "rst=\verbatim embed:rst" ALIASES += "endrst=\endverbatim" diff --git a/docs/compiling.rst b/docs/compiling.rst index 2b543be0be..aa431a904e 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -482,6 +482,10 @@ available in all modes. The targets provided are: ``pybind11::opt_size`` ``/Os`` for MSVC, ``-Os`` for other compilers. Does nothing for debug builds. + ``pybind11::static`` + Statically link against libpybind11, and configure the pybind11 headers to not provide definitions that they would provide in the default header-only mode. + Currently this configuration is only available in subdirectory mode. + Two helper functions are also provided: ``pybind11_strip(target)`` diff --git a/include/pybind11/detail/common-inl.h b/include/pybind11/detail/common-inl.h new file mode 100644 index 0000000000..551080fbb5 --- /dev/null +++ b/include/pybind11/detail/common-inl.h @@ -0,0 +1,25 @@ +/* + pybind11/detail/common-inl.h -- Basic macros definitions + + Copyright (c) 2016-2022 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. +*/ +#include "pybind11/detail/common.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void pybind11_fail(const char *reason) { + assert(!PyErr_Occurred()); + throw std::runtime_error(reason); +} +PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void pybind11_fail(const std::string &reason) { + assert(!PyErr_Occurred()); + throw std::runtime_error(reason); +} + +PYBIND11_INLINE error_scope::error_scope() { PyErr_Fetch(&type, &value, &trace); } +PYBIND11_INLINE error_scope::~error_scope() { PyErr_Restore(type, value, trace); }; + +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 9e6947daa3..329e1b1812 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -117,17 +117,29 @@ # define PYBIND11_NOINLINE_DISABLED #endif -// The PYBIND11_NOINLINE macro is for function DEFINITIONS. -// In contrast, FORWARD DECLARATIONS should never use this macro: -// https://stackoverflow.com/questions/9317473/forward-declaration-of-inline-functions +// PYBIND11_INLINE should be used for function definitions in '-inl' files, so they +// can be made non-inline when compiles as a static library. +#if defined(PYBIND11_AS_STATIC_LIBRARY) +# define PYBIND11_INLINE +#else +# define PYBIND11_INLINE inline +#endif + #if defined(PYBIND11_NOINLINE_DISABLED) // Option for maximum portability and experimentation. -# define PYBIND11_NOINLINE inline +# define PYBIND11_NOINLINE_ATTR #elif defined(_MSC_VER) -# define PYBIND11_NOINLINE __declspec(noinline) inline +# define PYBIND11_NOINLINE_ATTR __declspec(noinline) #else -# define PYBIND11_NOINLINE __attribute__((noinline)) inline +# define PYBIND11_NOINLINE_ATTR __attribute__((noinline)) #endif +// The PYBIND11_NOINLINE macro is for function DEFINITIONS. +// In contrast, FORWARD DECLARATIONS should never use this macro: +// https://stackoverflow.com/questions/9317473/forward-declaration-of-inline-functions +// This macro shouldn't be used in '-inl' files. Instead, use `PYBIND11_NOINLINE_ATTR +// PYBIND11_INLINE`. +#define PYBIND11_NOINLINE PYBIND11_NOINLINE_ATTR inline + #if defined(__MINGW32__) // For unknown reasons all PYBIND11_DEPRECATED member trigger a warning when declared // whether it is used or not @@ -936,14 +948,8 @@ PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybin /// casting error PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally -[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const char *reason) { - assert(!PyErr_Occurred()); - throw std::runtime_error(reason); -} -[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const std::string &reason) { - assert(!PyErr_Occurred()); - throw std::runtime_error(reason); -} +[[noreturn]] void pybind11_fail(const char *reason); +[[noreturn]] void pybind11_fail(const std::string &reason); template struct format_descriptor {}; @@ -992,10 +998,10 @@ constexpr const char /// RAII wrapper that temporarily clears any Python error state struct error_scope { PyObject *type, *value, *trace; - error_scope() { PyErr_Fetch(&type, &value, &trace); } + error_scope(); error_scope(const error_scope &) = delete; error_scope &operator=(const error_scope &) = delete; - ~error_scope() { PyErr_Restore(type, value, trace); } + ~error_scope(); }; /// Dummy destructor wrapper that can be used to expose classes with a private destructor @@ -1185,3 +1191,7 @@ constexpr inline bool silence_msvc_c4127(bool cond) { return cond; } PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +#ifndef PYBIND11_AS_STATIC_LIBRARY +# include "common-inl.h" +#endif diff --git a/pyproject.toml b/pyproject.toml index 3ba1b4b22f..88f50e42b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,11 @@ build-backend = "setuptools.build_meta" [tool.check-manifest] ignore = [ - "tests/**", "docs/**", - "tools/**", "include/**", + "src/**", + "tests/**", + "tools/**", ".*", "pybind11/include/**", "pybind11/share/**", diff --git a/setup.py b/setup.py index 68573519c1..3159c5c39a 100644 --- a/setup.py +++ b/setup.py @@ -128,6 +128,7 @@ def remove_output(*sources: str) -> Iterator[None]: "-DBUILD_TESTING=OFF", "-DPYBIND11_NOPYTHON=ON", "-Dprefix_for_pc_file=${pcfiledir}/../../", + "-DPYBIND11_BUILD_STATIC_LIB=OFF", ] if "CMAKE_ARGS" in os.environ: fcommand = [ diff --git a/src/detail/common.cc b/src/detail/common.cc new file mode 100644 index 0000000000..feb4297ffa --- /dev/null +++ b/src/detail/common.cc @@ -0,0 +1 @@ +#include "pybind11/detail/common-inl.h" diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 8e1ddd8508..83f24f1b23 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -48,6 +48,7 @@ detail_headers = { "include/pybind11/detail/class.h", "include/pybind11/detail/common.h", + "include/pybind11/detail/common-inl.h", "include/pybind11/detail/descr.h", "include/pybind11/detail/init.h", "include/pybind11/detail/internals.h", diff --git a/tests/test_cmake_build/CMakeLists.txt b/tests/test_cmake_build/CMakeLists.txt index 8bfaa386ae..164cefaecf 100644 --- a/tests/test_cmake_build/CMakeLists.txt +++ b/tests/test_cmake_build/CMakeLists.txt @@ -37,7 +37,8 @@ function(pybind11_add_build_test name) "${CMAKE_CURRENT_SOURCE_DIR}/${name}" "${CMAKE_CURRENT_BINARY_DIR}/${name}" --build-config - Release + "$<$:Debug>" + "$<$:Release>" --build-noclean --build-generator ${CMAKE_GENERATOR} @@ -59,6 +60,9 @@ possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID) pybind11_add_build_test(subdirectory_function) pybind11_add_build_test(subdirectory_target) +if(PYBIND11_BUILD_STATIC_LIB) + pybind11_add_build_test(subdirectory_static) +endif() if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy") message(STATUS "Skipping embed test on PyPy") else() diff --git a/tests/test_cmake_build/subdirectory_static/CMakeLists.txt b/tests/test_cmake_build/subdirectory_static/CMakeLists.txt new file mode 100644 index 0000000000..6082a6c4ff --- /dev/null +++ b/tests/test_cmake_build/subdirectory_static/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.4) + +# 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) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.18) +endif() + +project(test_subdirectory_static CXX) + +add_subdirectory("${pybind11_SOURCE_DIR}" pybind11) + +add_library(test_subdirectory_static MODULE ../main.cpp) +set_target_properties(test_subdirectory_static PROPERTIES OUTPUT_NAME test_cmake_build) + +target_link_libraries(test_subdirectory_static PRIVATE pybind11::module pybind11::static) + +# Make sure result is, for example, test_installed_static.so, not libtest_installed_static.dylib +pybind11_extension(test_subdirectory_static) + +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_static + ${CMAKE_COMMAND} + -E + env + PYTHONPATH=$ + ${_Python_EXECUTABLE} + ${PROJECT_SOURCE_DIR}/../test.py + ${PROJECT_NAME} + DEPENDS test_subdirectory_static) diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index e1fb601acc..3ab831e4e6 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -9,7 +9,8 @@ Adds the following targets:: pybind11::thin_lto - Link time optimizations (manual selection) pybind11::python_link_helper - Adds link to Python libraries pybind11::windows_extras - MSVC bigobj and mp for building multithreaded - pybind11::opt_size - avoid optimizations that increase code size + pybind11::opt_size - Avoid optimizations that increase code size + pybind11::static - Use pybind11 as a static library. Adds the following functions::