Skip to content

Commit 3c32e9c

Browse files
royjacobsoncirosantilli2
authored and
root
committed
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 8756f16 commit 3c32e9c

File tree

15 files changed

+206
-21
lines changed

15 files changed

+206
-21
lines changed

.github/CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ The valid options are:
116116
* `-DCMAKE_BUILD_TYPE`: Release, Debug, MinSizeRel, RelWithDebInfo
117117
* `-DPYBIND11_FINDPYTHON=ON`: Use CMake 3.12+'s FindPython instead of the
118118
classic, deprecated, custom FindPythonLibs
119-
* `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests)
119+
* `-DPYBIND11_NOPYTHON=ON`: Disable all Python searching (disables tests and the static library target)
120+
* `-DPYBIND11_BUILD_STATIC_LIB=OFF`: Don't build a static library target.
120121
* `-DBUILD_TESTING=ON`: Enable the tests
121122
* `-DDOWNLOAD_CATCH=ON`: Download catch to build the C++ tests
122123
* `-DDOWNLOAD_EIGEN=ON`: Download Eigen for the NumPy tests

CMakeLists.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ 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_BUILD_STATIC_LIB
95+
"Create a static library target for pybind11 that is not header-only (default)" ON)
9496
set(PYBIND11_INTERNALS_VERSION
9597
""
9698
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
107109
set(PYBIND11_HEADERS
108110
include/pybind11/detail/class.h
109111
include/pybind11/detail/common.h
112+
include/pybind11/detail/common-inl.h
110113
include/pybind11/detail/descr.h
111114
include/pybind11/detail/init.h
112115
include/pybind11/detail/internals.h
@@ -133,6 +136,30 @@ set(PYBIND11_HEADERS
133136
include/pybind11/stl_bind.h
134137
include/pybind11/stl/filesystem.h)
135138

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

261288
install(TARGETS pybind11_headers EXPORT "${PYBIND11_EXPORT_NAME}")
289+
if(PYBIND11_BUILD_STATIC_LIB)
290+
install(
291+
TARGETS pybind11_static
292+
EXPORT "${PYBIND11_EXPORT_NAME}"
293+
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
294+
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
295+
endif()
262296

263297
install(
264298
EXPORT "${PYBIND11_EXPORT_NAME}"
@@ -304,6 +338,10 @@ else()
304338
endif()
305339
endif()
306340

341+
if(_pybind11_nopython AND PYBIND11_BUILD_STATIC_LIB)
342+
message(FATAL_ERROR "Cannot build static library in NOPYTHON mode")
343+
endif()
344+
307345
# Better symmetry with find_package(pybind11 CONFIG) mode.
308346
if(NOT PYBIND11_MASTER_PROJECT)
309347
set(pybind11_FOUND

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
recursive-include pybind11/include/pybind11 *.h
2+
recursive-include src *.cc
23
recursive-include pybind11 *.py
34
recursive-include pybind11 py.typed
45
include pybind11/share/cmake/pybind11/*.cmake

docs/Doxyfile

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 25 additions & 0 deletions
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

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,29 @@
117117
# define PYBIND11_NOINLINE_DISABLED
118118
#endif
119119

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

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
140+
// PYBIND11_INLINE`.
141+
#define PYBIND11_NOINLINE PYBIND11_NOINLINE_ATTR inline
142+
131143
#if defined(__MINGW32__)
132144
// For unknown reasons all PYBIND11_DEPRECATED member trigger a warning when declared
133145
// whether it is used or not
@@ -936,14 +948,8 @@ PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybin
936948
/// casting error
937949
PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally
938950

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

948954
template <typename T, typename SFINAE = void>
949955
struct format_descriptor {};
@@ -992,10 +998,10 @@ constexpr const char
992998
/// RAII wrapper that temporarily clears any Python error state
993999
struct error_scope {
9941000
PyObject *type, *value, *trace;
995-
error_scope() { PyErr_Fetch(&type, &value, &trace); }
1001+
error_scope();
9961002
error_scope(const error_scope &) = delete;
9971003
error_scope &operator=(const error_scope &) = delete;
998-
~error_scope() { PyErr_Restore(type, value, trace); }
1004+
~error_scope();
9991005
};
10001006

10011007
/// 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; }
11851191

11861192
PYBIND11_NAMESPACE_END(detail)
11871193
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
1194+
1195+
#ifndef PYBIND11_AS_STATIC_LIBRARY
1196+
# include "common-inl.h"
1197+
#endif

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def remove_output(*sources: str) -> Iterator[None]:
128128
"-DBUILD_TESTING=OFF",
129129
"-DPYBIND11_NOPYTHON=ON",
130130
"-Dprefix_for_pc_file=${pcfiledir}/../../",
131+
"-DPYBIND11_BUILD_STATIC_LIB=OFF",
131132
]
132133
if "CMAKE_ARGS" in os.environ:
133134
fcommand = [

src/detail/common.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "pybind11/detail/common-inl.h"

tests/extra_python_package/test_files.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
detail_headers = {
4949
"include/pybind11/detail/class.h",
5050
"include/pybind11/detail/common.h",
51+
"include/pybind11/detail/common-inl.h",
5152
"include/pybind11/detail/descr.h",
5253
"include/pybind11/detail/init.h",
5354
"include/pybind11/detail/internals.h",

tests/test_cmake_build/CMakeLists.txt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ function(pybind11_add_build_test name)
3737
"${CMAKE_CURRENT_SOURCE_DIR}/${name}"
3838
"${CMAKE_CURRENT_BINARY_DIR}/${name}"
3939
--build-config
40-
Release
40+
"$<$<CONFIG:DEBUG>:Debug>"
41+
"$<$<CONFIG:RELEASE>:Release>"
4142
--build-noclean
4243
--build-generator
4344
${CMAKE_GENERATOR}
@@ -59,6 +60,9 @@ possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID)
5960

6061
pybind11_add_build_test(subdirectory_function)
6162
pybind11_add_build_test(subdirectory_target)
63+
if(PYBIND11_BUILD_STATIC_LIB)
64+
pybind11_add_build_test(subdirectory_static)
65+
endif()
6266
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy")
6367
message(STATUS "Skipping embed test on PyPy")
6468
else()
@@ -67,11 +71,15 @@ endif()
6771

6872
if(PYBIND11_INSTALL)
6973
add_custom_target(
70-
mock_install ${CMAKE_COMMAND} "-DCMAKE_INSTALL_PREFIX=${pybind11_BINARY_DIR}/mock_install" -P
71-
"${pybind11_BINARY_DIR}/cmake_install.cmake")
74+
mock_install ${CMAKE_COMMAND} "-DCMAKE_INSTALL_PREFIX=${pybind11_BINARY_DIR}/mock_install"
75+
"-DBUILD_TYPE=" "$<$<CONFIG:DEBUG>:Debug>" "$<$<CONFIG:RELEASE>:Release>" -P "${pybind11_BINARY_DIR}/cmake_install.cmake")
7276

7377
pybind11_add_build_test(installed_function INSTALL)
7478
pybind11_add_build_test(installed_target INSTALL)
79+
if(PYBIND11_BUILD_STATIC_LIB)
80+
pybind11_add_build_test(installed_static INSTALL)
81+
add_dependencies(mock_install pybind11::static)
82+
endif()
7583
if(NOT ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy"
7684
))
7785
pybind11_add_build_test(installed_embed INSTALL)
Lines changed: 46 additions & 0 deletions
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)
Lines changed: 41 additions & 0 deletions
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 7 additions & 0 deletions
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_BUILD_STATIC_LIB@)
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)