Skip to content

export interface lib through pybind11Config.cmake #506

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ build_script:
- cmake -A "%CMAKE_ARCH%" -DPYBIND11_WERROR=ON
- set MSBuildLogger="C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
- cmake --build . --config Release --target pytest -- /v:m /logger:%MSBuildLogger%
- cmake --build . --config Release --target test_install -- /v:m /logger:%MSBuildLogger%
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ MANIFEST
/cmake/
.cache/
sosize-*.txt
pybind11Config*.cmake
pybind11Targets.cmake
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,6 @@ script:
-DPYBIND11_CPP_STANDARD=$CPP
-DPYBIND11_WERROR=ON
- $SCRIPT_RUN_PREFIX make pytest -j 2
- $SCRIPT_RUN_PREFIX make test_install
after_script:
- if [ -n "$DOCKER" ]; then docker stop "$containerid"; docker rm "$containerid"; fi
157 changes: 49 additions & 108 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,9 @@ option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJE
option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT})
option(PYBIND11_WERROR "Report all warnings as errors" OFF)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option and the related pybind11_enable_warnings function should stay in the main CMakeLists.txt file. It's not for export.


# Add a CMake parameter for choosing a desired Python version
set(PYBIND11_PYTHON_VERSION "" CACHE STRING "Python version to use for compiling modules")

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/tools")
set(Python_ADDITIONAL_VERSIONS 3.4 3.5 3.6 3.7)
find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} REQUIRED)

include(CheckCXXCompilerFlag)

if(NOT MSVC AND NOT PYBIND11_CPP_STANDARD)
check_cxx_compiler_flag("-std=c++14" HAS_CPP14_FLAG)
check_cxx_compiler_flag("-std=c++11" HAS_CPP11_FLAG)

if (HAS_CPP14_FLAG)
set(PYBIND11_CPP_STANDARD -std=c++14)
elseif (HAS_CPP11_FLAG)
set(PYBIND11_CPP_STANDARD -std=c++11)
else()
message(FATAL_ERROR "Unsupported compiler -- pybind11 requires C++11 support!")
endif()

set(PYBIND11_CPP_STANDARD ${PYBIND11_CPP_STANDARD} CACHE STRING
"C++ standard flag, e.g. -std=c++11 or -std=c++14. Defaults to latest available." FORCE)
endif()
include(pybind11Tools)

# Cache variables so pybind11_add_module can be used in parent projects
set(PYBIND11_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include" CACHE INTERNAL "")
Expand All @@ -51,91 +30,6 @@ 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 "")

# Build a Python extension module:
# pybind11_add_module(<name> source1 [source2 ...])
#
function(pybind11_add_module target_name)
add_library(${target_name} MODULE ${ARGN})
target_include_directories(${target_name}
PRIVATE ${PYBIND11_INCLUDE_DIR}
PRIVATE ${PYTHON_INCLUDE_DIRS})

# 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}")

if(WIN32 OR CYGWIN)
# Link against the Python shared library on Windows
target_link_libraries(${target_name} PRIVATE ${PYTHON_LIBRARIES})
elseif(APPLE)
# It's quite common to have multiple copies of the same Python version
# installed on one's system. E.g.: one copy from the OS and another copy
# that's statically linked into an application like Blender or Maya.
# If we link our plugin library against the OS Python here and import it
# into Blender or Maya later on, this will cause segfaults when multiple
# conflicting Python instances are active at the same time (even when they
# are of the same version).

# Windows is not affected by this issue since it handles DLL imports
# differently. The solution for Linux and Mac OS is simple: we just don't
# link against the Python library. The resulting shared library will have
# missing symbols, but that's perfectly fine -- they will be resolved at
# import time.

target_link_libraries(${target_name} PRIVATE "-undefined dynamic_lookup")
endif()

if(NOT MSVC)
# Make sure C++11/14 are enabled
target_compile_options(${target_name} PUBLIC ${PYBIND11_CPP_STANDARD})

# Enable link time optimization and set the default symbol
# visibility to hidden (very important to obtain small binaries)
string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE)
if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG)
# Check for Link Time Optimization support (GCC/Clang)
check_cxx_compiler_flag("-flto" HAS_LTO_FLAG)
if(HAS_LTO_FLAG AND NOT CYGWIN)
target_compile_options(${target_name} PRIVATE -flto)
endif()

# Intel equivalent to LTO is called IPO
if(CMAKE_CXX_COMPILER_ID MATCHES "Intel")
check_cxx_compiler_flag("-ipo" HAS_IPO_FLAG)
if(HAS_IPO_FLAG)
target_compile_options(${target_name} PRIVATE -ipo)
endif()
endif()

# Default symbol visibility
target_compile_options(${target_name} PRIVATE "-fvisibility=hidden")

# 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} -u -r $<TARGET_FILE:${target_name}>)
else()
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_STRIP} $<TARGET_FILE:${target_name}>)
endif()
endif()
endif()
elseif(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 /MP /bigobj)

# Enforce link time code generation on MSVC, except in debug mode
target_compile_options(${target_name} PRIVATE $<$<NOT:$<CONFIG:Debug>>:/GL>)

# Fancy generator expressions don't work with linker flags, for reasons unknown
set_property(TARGET ${target_name} APPEND_STRING PROPERTY LINK_FLAGS_RELEASE /LTCG)
set_property(TARGET ${target_name} APPEND_STRING PROPERTY LINK_FLAGS_MINSIZEREL /LTCG)
set_property(TARGET ${target_name} APPEND_STRING PROPERTY LINK_FLAGS_RELWITHDEBINFO /LTCG)
endif()
endfunction()

# Compile with compiler warnings turned on
function(pybind11_enable_warnings target_name)
if(MSVC)
Expand Down Expand Up @@ -179,6 +73,53 @@ if (PYBIND11_TEST)
add_subdirectory(tests)
endif()

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

# extract project version from source
file(STRINGS "${PYBIND11_INCLUDE_DIR}/pybind11/common.h" pybind11_version_defines
REGEX "#define PYBIND11_VERSION_(MAJOR|MINOR|PATCH) ")
foreach(ver ${pybind11_version_defines})
if (ver MATCHES "#define PYBIND11_VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$")
set(PYBIND11_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "")
endif()
endforeach()
set(${PROJECT_NAME}_VERSION ${PYBIND11_VERSION_MAJOR}.${PYBIND11_VERSION_MINOR}.${PYBIND11_VERSION_PATCH})

if(NOT (CMAKE_VERSION VERSION_LESS 3.0)) # CMake >= 3.0
# Build an interface library target:
add_library(pybind11 INTERFACE)
target_include_directories(pybind11 INTERFACE $<BUILD_INTERFACE:${PYBIND11_INCLUDE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
if(APPLE)
target_link_libraries(pybind11 INTERFACE "-undefined dynamic_lookup")
endif()
endif()

if (PYBIND11_INSTALL)
install(FILES ${PYBIND11_HEADERS} DESTINATION include/pybind11)
install(FILES ${PYBIND11_HEADERS}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/pybind11)
# GNUInstallDirs "DATADIR" wrong here; CMake search path wants "share".
set(PYBIND11_CMAKECONFIG_INSTALL_DIR "share/cmake/${PROJECT_NAME}" CACHE STRING "install path for pybind11Config.cmake")

configure_package_config_file(tools/${PROJECT_NAME}Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
VERSION ${${PROJECT_NAME}_VERSION}
COMPATIBILITY AnyNewerVersion)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
tools/FindPythonLibsNew.cmake
tools/pybind11Tools.cmake
DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})

if(NOT (CMAKE_VERSION VERSION_LESS 3.0))
install(TARGETS pybind11
EXPORT "${PROJECT_NAME}Targets")
install(EXPORT "${PROJECT_NAME}Targets"
NAMESPACE "${PROJECT_NAME}::"
DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
message(STATUS "Exporting ${PROJECT_NAME}::pybind11 interface library target version ${${PROJECT_NAME}_VERSION}")
endif()
endif()
63 changes: 63 additions & 0 deletions docs/compiling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,66 @@ A working sample project, including a way to invoke CMake from :file:`setup.py`
PyPI integration, can be found in the [cmake_example]_ repository.

.. [cmake_example] https://github.com/pybind/cmake_example

For CMake-based projects that don't include the pybind11
repository internally, an external installation can be detected
through `find_package(pybind11 ... CONFIG ...)`. See the `Config file
<https://github.com/pybind/pybind11/blob/master/tools/pybind11Config.cmake.in>`_
docstring for details of relevant CMake variables.

Once detected, and after setting any variables to guide Python and
C++ standard detection, the aforementioned ``pybind11_add_module``
wrapper to ``add_library`` can be employed as described above (after
``include(pybind11Tools)``). This procedure is available when using CMake
>= 2.8.12. A working example can be found at [test_installed_module]_ .

.. code-block:: cmake

cmake_minimum_required(VERSION 2.8.12)
project(example)

find_package(pybind11 REQUIRED)
pybind11_add_module(example example.cpp)

.. [test_installed_module] https://github.com/pybind/pybind11/blob/master/tests/test_installed_module/CMakeLists.txt

When using a version of CMake greater than 3.0, pybind11 can
additionally be used as a special *interface library* following the
call to ``find_package``. CMake variables to guide Python and C++
standard detection should be set *before* ``find_package``. When
``find_package`` returns, the target ``pybind11::pybind11`` is
available with pybind11 headers, Python headers and libraries as
needed, and C++ compile definitions attached. This target is suitable
for linking to an independently constructed (through ``add_library``,
not ``pybind11_add_module``) target in the consuming project. A working
example can be found at [test_installed_target]_ .

.. code-block:: cmake

cmake_minimum_required(VERSION 3.0)
project(example)

add_library(example MODULE main.cpp)

find_package(pybind11 REQUIRED)
target_link_libraries(example PRIVATE pybind11::pybind11)
set_target_properties(example PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}"
SUFFIX "${PYTHON_MODULE_EXTENSION}")

.. 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).

These include Link Time Optimization (``-flto`` on GCC/Clang/ICPC, ``/GL``
and ``/LTCG`` on Visual Studio). Default-hidden symbols on GCC/Clang/ICPC
(``-fvisibility=hidden``) and .OBJ files with many sections on Visual Studio
(``/bigobj``). The :ref:`FAQ <faq:symhidden>` contains an
explanation on why these are needed.

.. [test_installed_target] https://github.com/pybind/pybind11/blob/master/tests/test_installed_target/CMakeLists.txt

2 changes: 2 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ culprit is generally the generation of function signatures at compile time
using C++14 template metaprogramming.


.. _`faq:symhidden`:

How can I create smaller binaries?
==================================

Expand Down
48 changes: 48 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,54 @@ if(PYBIND11_TEST_OVERRIDE)
COMMAND ${CMAKE_COMMAND} -E echo "Note: not all tests run: -DPYBIND11_TEST_OVERRIDE is in effect")
endif()

# test use of installation
if(PYBIND11_INSTALL)
# 2.8.12 needed for test_installed_module
# 3.0 needed for interface library for test_installed_target
# 3.1 needed for cmake -E env for testing
if(NOT CMAKE_VERSION VERSION_LESS 3.1)
add_custom_target(test_installed_target
COMMAND ${CMAKE_COMMAND}
"-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/test_install"
-P "${PROJECT_BINARY_DIR}/cmake_install.cmake"
COMMAND ${CMAKE_CTEST_COMMAND}
--build-and-test "${CMAKE_CURRENT_SOURCE_DIR}/test_installed_target"
"${CMAKE_CURRENT_BINARY_DIR}/test_installed_target"
--build-noclean
--build-generator ${CMAKE_GENERATOR}
$<$<BOOL:${CMAKE_GENERATOR_PLATFORM}>:--build-generator-platform> ${CMAKE_GENERATOR_PLATFORM}
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
--build-target check
--build-options "-DCMAKE_PREFIX_PATH=${PROJECT_BINARY_DIR}/test_install"
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}"
"-DPYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}"
)
add_custom_target(test_installed_module
COMMAND ${CMAKE_COMMAND}
"-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/test_install"
-P "${PROJECT_BINARY_DIR}/cmake_install.cmake"
COMMAND ${CMAKE_CTEST_COMMAND}
--build-and-test "${CMAKE_CURRENT_SOURCE_DIR}/test_installed_module"
"${CMAKE_CURRENT_BINARY_DIR}/test_installed_module"
--build-noclean
--build-generator ${CMAKE_GENERATOR}
$<$<BOOL:${CMAKE_GENERATOR_PLATFORM}>:--build-generator-platform> ${CMAKE_GENERATOR_PLATFORM}
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
--build-target check
--build-options "-DCMAKE_PREFIX_PATH=${PROJECT_BINARY_DIR}/test_install"
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}"
"-DPYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}"
)
else()
add_custom_target(test_installed_target)
add_custom_target(test_installed_module)
endif()
add_custom_target(test_install)
add_dependencies(test_install test_installed_target test_installed_module)
endif()

# And another to show the .so size and, if a previous size, compare it:
add_custom_command(TARGET pybind11_tests POST_BUILD
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/libsize.py
Expand Down
14 changes: 14 additions & 0 deletions tests/test_installed_module/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 2.8.12)
project(test_installed_module CXX)

set(CMAKE_MODULE_PATH "")

find_package(pybind11 CONFIG REQUIRED)

message(STATUS "Found pybind11: ${pybind11_INCLUDE_DIRS} (found version ${pybind11_VERSION})")
message(STATUS "Found Python: ${PYTHON_INCLUDE_DIRS} (found version ${PYTHON_VERSION_STRING})")

pybind11_add_module(test_installed_module SHARED main.cpp)

add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_installed_module>
${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test.py)
10 changes: 10 additions & 0 deletions tests/test_installed_module/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <pybind11/pybind11.h>
namespace py = pybind11;

PYBIND11_PLUGIN(test_installed_module) {
py::module m("test_installed_module");

m.def("add", [](int i, int j) { return i + j; });

return m.ptr();
}
3 changes: 3 additions & 0 deletions tests/test_installed_module/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import test_installed_module
assert test_installed_module.add(11, 22) == 33
print('test_installed_module imports, runs, and adds: 11 + 22 = 33')
20 changes: 20 additions & 0 deletions tests/test_installed_target/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.0)
project(test_installed_target CXX)

set(CMAKE_MODULE_PATH "")

find_package(pybind11 CONFIG REQUIRED)

message(STATUS "Found pybind11: ${pybind11_INCLUDE_DIRS} (found version ${pybind11_VERSION})")
message(STATUS "Found Python: ${PYTHON_INCLUDE_DIRS} (found version ${PYTHON_VERSION_STRING})")

add_library(test_installed_target MODULE main.cpp)

target_link_libraries(test_installed_target PRIVATE pybind11::pybind11)

# 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}")

add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_installed_target>
${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test.py)
10 changes: 10 additions & 0 deletions tests/test_installed_target/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <pybind11/pybind11.h>
namespace py = pybind11;

PYBIND11_PLUGIN(test_installed_target) {
py::module m("test_installed_target");

m.def("add", [](int i, int j) { return i + j; });

return m.ptr();
}
3 changes: 3 additions & 0 deletions tests/test_installed_target/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import test_installed_target
assert test_installed_target.add(1, 2) == 3
print('test_installed_target imports, runs, and adds: 1 + 2 = 3')
Loading