Skip to content

Commit aa7d132

Browse files
Adds a new target 'pybind11::static' that allows compiling pybind11 as a static library and linking against it.
This consists of the following changes: 1. CMake infrastructure to support exporting and installing this target, with an option to disable it. 2. Defining some new macros to support this dual mode compilation. 3. Creating one .cc file that is compiled into the new static library. The rest of the code could be refactored in subsequent PRs. 4. Creating tests that compiling against the new target works. Co-authored-by: Ciro Santilli <[email protected]>
1 parent 58802de commit aa7d132

File tree

11 files changed

+188
-17
lines changed

11 files changed

+188
-17
lines changed

CMakeLists.txt

+29
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ endif()
9191
option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT})
9292
option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT})
9393
option(PYBIND11_NOPYTHON "Disable search for Python" OFF)
94+
option(PYBIND11_COMPILE_LIBRARY "Create a library target for pybind11 that is not header-only (default)" ON)
9495
set(PYBIND11_INTERNALS_VERSION
9596
""
9697
CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.")
@@ -107,6 +108,7 @@ cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" OFF
107108
set(PYBIND11_HEADERS
108109
include/pybind11/detail/class.h
109110
include/pybind11/detail/common.h
111+
include/pybind11/detail/common-inl.h
110112
include/pybind11/detail/descr.h
111113
include/pybind11/detail/init.h
112114
include/pybind11/detail/internals.h
@@ -133,6 +135,30 @@ set(PYBIND11_HEADERS
133135
include/pybind11/stl_bind.h
134136
include/pybind11/stl/filesystem.h)
135137

138+
file(GLOB_RECURSE PYBIND11_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc)
139+
# `embed.cc` is the TU to support embedding a python interpreter. But PyPy does not
140+
# support embedding, so remove it from sources.
141+
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy")
142+
# Pypy does not support embedding.
143+
list(REMOVE_ITEM PYBIND11_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/embed.cc)
144+
endif()
145+
146+
# If we're not configured to be -onheaderly, we need to build a shared library
147+
# and define the pybind11::lib target.
148+
if(PYBIND11_COMPILE_LIBRARY)
149+
add_library(pybind11_static STATIC ${PYBIND11_SOURCES} ${PYBIND11_HEADERS})
150+
set_property(TARGET pybind11_static PROPERTY POSITION_INDEPENDENT_CODE ON)
151+
target_compile_definitions(pybind11_static PUBLIC -DPYBIND11_AS_STATIC_LIBRARY=1)
152+
add_library(pybind11::static ALIAS pybind11_static)
153+
target_link_libraries(pybind11_static PRIVATE pybind11::pybind11)
154+
target_link_libraries(pybind11_static PUBLIC pybind11::headers)
155+
install(TARGETS pybind11_static
156+
EXPORT pybind11::static
157+
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
158+
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
159+
)
160+
endif()
161+
136162
# Compare with grep and warn if mismatched
137163
if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12)
138164
file(
@@ -256,6 +282,9 @@ if(PYBIND11_INSTALL)
256282
endif()
257283

258284
install(TARGETS pybind11_headers EXPORT "${PYBIND11_EXPORT_NAME}")
285+
if(PYBIND11_COMPILE_LIBRARY)
286+
install(TARGETS pybind11_static EXPORT "${PYBIND11_EXPORT_NAME}")
287+
endif()
259288

260289
install(
261290
EXPORT "${PYBIND11_EXPORT_NAME}"

docs/Doxyfile

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ XML_PROGRAMLISTING = YES
1111
MACRO_EXPANSION = YES
1212
EXPAND_ONLY_PREDEF = YES
1313
EXPAND_AS_DEFINED = PYBIND11_RUNTIME_EXCEPTION
14+
EXCLUDE_PATTERNS = *-inl.h
1415

1516
ALIASES = "rst=\verbatim embed:rst"
1617
ALIASES += "endrst=\endverbatim"

docs/compiling.rst

+3
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ available in all modes. The targets provided are:
482482
``pybind11::opt_size``
483483
``/Os`` for MSVC, ``-Os`` for other compilers. Does nothing for debug builds.
484484

485+
``pybind11::static``
486+
Statically link against libpybind11, and configure the pybind11 headers to not provide definitions that they would provide in the default header-only mode.
487+
485488
Two helper functions are also provided:
486489

487490
``pybind11_strip(target)``

include/pybind11/detail/common-inl.h

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
pybind11/detail/common-inl.h -- Basic macros definitions
3+
4+
Copyright (c) 2016-2022 Wenzel Jakob <[email protected]>
5+
6+
All rights reserved. Use of this source code is governed by a
7+
BSD-style license that can be found in the LICENSE file.
8+
*/
9+
#include "pybind11/detail/common.h"
10+
11+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
12+
13+
PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void pybind11_fail(const char *reason) {
14+
assert(!PyErr_Occurred());
15+
throw std::runtime_error(reason);
16+
}
17+
PYBIND11_NOINLINE_ATTR PYBIND11_INLINE void pybind11_fail(const std::string &reason) {
18+
assert(!PyErr_Occurred());
19+
throw std::runtime_error(reason);
20+
}
21+
22+
PYBIND11_INLINE error_scope::error_scope() { PyErr_Fetch(&type, &value, &trace); }
23+
PYBIND11_INLINE error_scope::~error_scope() { PyErr_Restore(type, value, trace); };
24+
25+
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/detail/common.h

+26-16
Original file line numberDiff line numberDiff line change
@@ -116,17 +116,29 @@
116116
# define PYBIND11_NOINLINE_DISABLED
117117
#endif
118118

119-
// The PYBIND11_NOINLINE macro is for function DEFINITIONS.
120-
// In contrast, FORWARD DECLARATIONS should never use this macro:
121-
// https://stackoverflow.com/questions/9317473/forward-declaration-of-inline-functions
119+
// PYBIND11_INLINE should be used for function definitions in '-inl' files, so they
120+
// can be made non-inline when compiles as a static library.
121+
#if defined(PYBIND11_AS_STATIC_LIBRARY)
122+
# define PYBIND11_INLINE
123+
#else
124+
# define PYBIND11_INLINE inline
125+
#endif
126+
127+
122128
#if defined(PYBIND11_NOINLINE_DISABLED) // Option for maximum portability and experimentation.
123-
# define PYBIND11_NOINLINE inline
129+
# define PYBIND11_NOINLINE_ATTR
124130
#elif defined(_MSC_VER)
125-
# define PYBIND11_NOINLINE __declspec(noinline) inline
131+
# define PYBIND11_NOINLINE_ATTR __declspec(noinline)
126132
#else
127-
# define PYBIND11_NOINLINE __attribute__((noinline)) inline
133+
# define PYBIND11_NOINLINE_ATTR __attribute__((noinline))
128134
#endif
129135

136+
// The PYBIND11_NOINLINE macro is for function DEFINITIONS.
137+
// In contrast, FORWARD DECLARATIONS should never use this macro:
138+
// https://stackoverflow.com/questions/9317473/forward-declaration-of-inline-functions
139+
// This macro shouldn't be used in '-inl' files. Instead, use `PYBIND11_NOINLINE_ATTR PYBIND11_INLINE`.
140+
#define PYBIND11_NOINLINE PYBIND11_NOINLINE_ATTR inline
141+
130142
#if defined(__MINGW32__)
131143
// For unknown reasons all PYBIND11_DEPRECATED member trigger a warning when declared
132144
// whether it is used or not
@@ -935,14 +947,8 @@ PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybin
935947
/// casting error
936948
PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally
937949

938-
[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const char *reason) {
939-
assert(!PyErr_Occurred());
940-
throw std::runtime_error(reason);
941-
}
942-
[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const std::string &reason) {
943-
assert(!PyErr_Occurred());
944-
throw std::runtime_error(reason);
945-
}
950+
[[noreturn]] void pybind11_fail(const char *reason);
951+
[[noreturn]] void pybind11_fail(const std::string &reason);
946952

947953
template <typename T, typename SFINAE = void>
948954
struct format_descriptor {};
@@ -991,10 +997,10 @@ constexpr const char
991997
/// RAII wrapper that temporarily clears any Python error state
992998
struct error_scope {
993999
PyObject *type, *value, *trace;
994-
error_scope() { PyErr_Fetch(&type, &value, &trace); }
1000+
error_scope();
9951001
error_scope(const error_scope &) = delete;
9961002
error_scope &operator=(const error_scope &) = delete;
997-
~error_scope() { PyErr_Restore(type, value, trace); }
1003+
~error_scope();
9981004
};
9991005

10001006
/// Dummy destructor wrapper that can be used to expose classes with a private destructor
@@ -1166,3 +1172,7 @@ constexpr inline bool silence_msvc_c4127(bool cond) { return cond; }
11661172

11671173
PYBIND11_NAMESPACE_END(detail)
11681174
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
1175+
1176+
#ifndef PYBIND11_AS_STATIC_LIBRARY
1177+
# include "common-inl.h"
1178+
#endif

src/detail/common.cc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "pybind11/detail/common-inl.h"

tests/test_cmake_build/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID)
5959

6060
pybind11_add_build_test(subdirectory_function)
6161
pybind11_add_build_test(subdirectory_target)
62+
if(PYBIND11_COMPILE_LIBRARY)
63+
pybind11_add_build_test(subdirectory_static)
64+
endif()
6265
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy")
6366
message(STATUS "Skipping embed test on PyPy")
6467
else()
@@ -72,6 +75,10 @@ if(PYBIND11_INSTALL)
7275

7376
pybind11_add_build_test(installed_function INSTALL)
7477
pybind11_add_build_test(installed_target INSTALL)
78+
if(PYBIND11_COMPILE_LIBRARY)
79+
pybind11_add_build_test(installed_static INSTALL)
80+
add_dependencies(mock_install pybind11::static)
81+
endif()
7582
if(NOT ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy"
7683
))
7784
pybind11_add_build_test(installed_embed INSTALL)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
cmake_minimum_required(VERSION 3.4)
2+
3+
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
4+
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
5+
# the behavior using the following workaround:
6+
if(${CMAKE_VERSION} VERSION_LESS 3.18)
7+
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
8+
else()
9+
cmake_policy(VERSION 3.18)
10+
endif()
11+
12+
project(test_installed_static CXX)
13+
14+
find_package(pybind11 CONFIG REQUIRED)
15+
message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}")
16+
17+
add_library(test_installed_static MODULE ../main.cpp)
18+
19+
target_link_libraries(test_installed_static PRIVATE pybind11::module pybind11::static)
20+
set_target_properties(test_installed_static PROPERTIES OUTPUT_NAME test_cmake_build)
21+
22+
# Make sure result is, for example, test_installed_static.so, not libtest_installed_static.dylib
23+
pybind11_extension(test_installed_static)
24+
25+
# Do not treat includes from IMPORTED target as SYSTEM (Python headers in pybind11::module).
26+
# This may be needed to resolve header conflicts, e.g. between Python release and debug headers.
27+
set_target_properties(test_installed_static PROPERTIES NO_SYSTEM_FROM_IMPORTED ON)
28+
29+
if(DEFINED Python_EXECUTABLE)
30+
set(_Python_EXECUTABLE "${Python_EXECUTABLE}")
31+
elseif(DEFINED PYTHON_EXECUTABLE)
32+
set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
33+
else()
34+
message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)")
35+
endif()
36+
37+
add_custom_target(
38+
check_installed_static
39+
${CMAKE_COMMAND}
40+
-E
41+
env
42+
PYTHONPATH=$<TARGET_FILE_DIR:test_installed_static>
43+
${_Python_EXECUTABLE}
44+
${PROJECT_SOURCE_DIR}/../test.py
45+
${PROJECT_NAME}
46+
DEPENDS test_installed_static)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
cmake_minimum_required(VERSION 3.4)
2+
3+
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
4+
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
5+
# the behavior using the following workaround:
6+
if(${CMAKE_VERSION} VERSION_LESS 3.18)
7+
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
8+
else()
9+
cmake_policy(VERSION 3.18)
10+
endif()
11+
12+
project(test_subdirectory_static CXX)
13+
14+
add_subdirectory("${pybind11_SOURCE_DIR}" pybind11)
15+
16+
add_library(test_subdirectory_static MODULE ../main.cpp)
17+
set_target_properties(test_subdirectory_static PROPERTIES OUTPUT_NAME test_cmake_build)
18+
19+
target_link_libraries(test_subdirectory_static PRIVATE pybind11::module pybind11::static)
20+
21+
# Make sure result is, for example, test_installed_static.so, not libtest_installed_static.dylib
22+
pybind11_extension(test_subdirectory_static)
23+
24+
if(DEFINED Python_EXECUTABLE)
25+
set(_Python_EXECUTABLE "${Python_EXECUTABLE}")
26+
elseif(DEFINED PYTHON_EXECUTABLE)
27+
set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
28+
else()
29+
message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)")
30+
endif()
31+
32+
add_custom_target(
33+
check_subdirectory_static
34+
${CMAKE_COMMAND}
35+
-E
36+
env
37+
PYTHONPATH=$<TARGET_FILE_DIR:test_subdirectory_static>
38+
${_Python_EXECUTABLE}
39+
${PROJECT_SOURCE_DIR}/../test.py
40+
${PROJECT_NAME}
41+
DEPENDS test_subdirectory_static)

tools/pybind11Common.cmake

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ Adds the following targets::
99
pybind11::thin_lto - Link time optimizations (manual selection)
1010
pybind11::python_link_helper - Adds link to Python libraries
1111
pybind11::windows_extras - MSVC bigobj and mp for building multithreaded
12-
pybind11::opt_size - avoid optimizations that increase code size
12+
pybind11::opt_size - Avoid optimizations that increase code size
13+
pybind11::static - Use pybind11 as a static library.
1314
1415
Adds the following functions::
1516

tools/pybind11Config.cmake.in

+7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ complex applications, and they are available in all modes:
4949
Just the pybind11 headers and minimum compile requirements.
5050
``pybind11::pybind11``
5151
Python headers too.
52+
``pybind11::static``
53+
A static library to link against instead of the default header-only.
5254
``pybind11::python_link_helper``
5355
Just the "linking" part of ``pybind11:module``, for CMake < 3.15.
5456
``pybind11::thin_lto``
@@ -220,6 +222,11 @@ include("${CMAKE_CURRENT_LIST_DIR}/pybind11Targets.cmake")
220222
add_library(pybind11::headers IMPORTED INTERFACE)
221223
set_target_properties(pybind11::headers PROPERTIES INTERFACE_LINK_LIBRARIES
222224
pybind11::pybind11_headers)
225+
if (@PYBIND11_COMPILE_LIBRARY@)
226+
add_library(pybind11::static IMPORTED INTERFACE)
227+
set_target_properties(pybind11::static PROPERTIES INTERFACE_LINK_LIBRARIES
228+
pybind11::pybind11_static)
229+
endif()
223230

224231
include("${CMAKE_CURRENT_LIST_DIR}/pybind11Common.cmake")
225232

0 commit comments

Comments
 (0)