Skip to content

Various CMake improvements #565

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 4 commits into from
Dec 19, 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
3 changes: 2 additions & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ 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%
- cmake --build . --config Release --target test_cmake_build -- /v:m /logger:%MSBuildLogger%
on_failure: type tests\test_cmake_build\*.log
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ script:
-DPYBIND11_CPP_STANDARD=$CPP
-DPYBIND11_WERROR=ON
- $SCRIPT_RUN_PREFIX make pytest -j 2
- $SCRIPT_RUN_PREFIX make test_install
- $SCRIPT_RUN_PREFIX make test_cmake_build
after_failure: cat tests/test_cmake_build/*.log
after_script:
- if [ -n "$DOCKER" ]; then docker stop "$containerid"; docker rm "$containerid"; fi
20 changes: 13 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,22 @@ foreach(ver ${pybind11_version_defines})
endif()
endforeach()
set(${PROJECT_NAME}_VERSION ${PYBIND11_VERSION_MAJOR}.${PYBIND11_VERSION_MINOR}.${PYBIND11_VERSION_PATCH})
message(STATUS "pybind11 v${${PROJECT_NAME}_VERSION}")

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")
add_library(module INTERFACE)
target_include_directories(module INTERFACE $<BUILD_INTERFACE:${PYBIND11_INCLUDE_DIR}>
$<BUILD_INTERFACE:${PYTHON_INCLUDE_DIRS}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
if(WIN32 OR CYGWIN)
target_link_libraries(module INTERFACE $<BUILD_INTERFACE:${PYTHON_LIBRARIES}>)
elseif(APPLE)
target_link_libraries(module INTERFACE "-undefined dynamic_lookup")
endif()
target_compile_options(module INTERFACE $<BUILD_INTERFACE:${PYBIND11_CPP_STANDARD}>)

add_library(pybind11::module ALIAS module) # to match exported target
endif()

if (PYBIND11_INSTALL)
Expand All @@ -115,11 +122,10 @@ if (PYBIND11_INSTALL)
DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})

if(NOT (CMAKE_VERSION VERSION_LESS 3.0))
install(TARGETS pybind11
install(TARGETS module
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()
128 changes: 94 additions & 34 deletions docs/compiling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,88 @@ extension module can be created with just a few lines of code:

This assumes that the pybind11 repository is located in a subdirectory named
:file:`pybind11` and that the code is located in a file named :file:`example.cpp`.
The CMake command ``add_subdirectory`` will import a function with the signature
``pybind11_add_module(<name> source1 [source2 ...])``. It will take care of all
the details needed to build a Python extension module on any platform.

The target Python version can be selected by setting the ``PYBIND11_PYTHON_VERSION``
variable before adding the pybind11 subdirectory. Alternatively, an exact Python
installation can be specified by setting ``PYTHON_EXECUTABLE``.
The CMake command ``add_subdirectory`` will import the pybind11 project which
provides the ``pybind11_add_module`` function. It will take care of all the
details needed to build a Python extension module on any platform.

A working sample project, including a way to invoke CMake from :file:`setup.py` for
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.
pybind11_add_module
-------------------

To ease the creation of Python extension modules, pybind11 provides a CMake
function with the following signature:

.. code-block:: cmake

pybind11_add_module(<name> [MODULE | SHARED] [EXCLUDE_FROM_ALL]
[NO_EXTRAS] [THIN_LTO] 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
called ``<name>`` to be built from the listed source files. In addition, it
will take care of all the Python-specific compiler and linker flags as well
as the OS- and Python-version-specific file extension. The produced target
``<name>`` can be further manipulated with regular CMake commands.

``MODULE`` or ``SHARED`` may be given to specify the type of library. If no
type is given, ``MODULE`` is used by default which ensures the creation of a
Python-exclusive module. Specifying ``SHARED`` will create a more traditional
dynamic library which can also be linked from elsewhere. ``EXCLUDE_FROM_ALL``
removes this target from the default build (see CMake docs for details).

Since pybind11 is a template library, ``pybind11_add_module`` adds compiler
flags to ensure high quality code generation without bloat arising from long
symbol names and duplication of code in different translation units. The
additional flags enable LTO (Link Time Optimization), set default visibility
to *hidden* and strip unneeded symbols. See the :ref:`FAQ entry <faq:symhidden>`
for a more detailed explanation. These optimizations are never applied in
``Debug`` mode. If ``NO_EXTRAS`` is given, they will always be disabled, even
in ``Release`` mode. However, this 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.

.. _ThinLTO: http://clang.llvm.org/docs/ThinLTO.html

Configuration variables
-----------------------

By default, pybind11 will compile modules with the latest C++ standard
available on the target compiler. To override this, the standard flag can
be given explicitly in ``PYBIND11_CPP_STANDARD``:

.. code-block:: cmake

set(PYBIND11_CPP_STANDARD -std=c++11)
add_subdirectory(pybind11) # or find_package(pybind11)

Note that this and all other configuration variables must be set **before** the
call to ``add_subdiretory`` or ``find_package``. The variables can also be set
when calling CMake from the command line using the ``-D<variable>=<value>`` flag.

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:

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:: bash

cmake -DPYBIND11_PYTHON_VERSION=3.6 ..
# or
cmake -DPYTHON_EXECUTABLE=path/to/python ..

find_package vs. add_subdirectory
---------------------------------

For CMake-based projects that don't include the pybind11 repository internally,
an external installation can be detected through ``find_package(pybind11)``.
See the `Config file`_ docstring for details of relevant CMake variables.

.. code-block:: cmake

Expand All @@ -72,28 +130,33 @@ wrapper to ``add_library`` can be employed as described above (after
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
Once detected, the aforementioned ``pybind11_add_module`` can be employed as
before. The function usage and configuration variables are identical no matter
if pybind11 is added as a subdirectory or found as an installed package. You
can refer to the same [cmake_example]_ repository for a full sample project
-- just swap out ``add_subdirectory`` for ``find_package``.

.. _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* 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]_ .
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 definitions attached. This target is suitable for linking
to an independently constructed (through ``add_library``, not
``pybind11_add_module``) target in the consuming project.

.. code-block:: cmake

cmake_minimum_required(VERSION 3.0)
project(example)

add_library(example MODULE main.cpp)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)

find_package(pybind11 REQUIRED)
target_link_libraries(example PRIVATE pybind11::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}")

Expand All @@ -111,6 +174,3 @@ example can be found at [test_installed_target]_ .
(``-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

97 changes: 51 additions & 46 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,55 +105,60 @@ 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
# 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
$<TARGET_FILE:pybind11_tests> ${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)

# Test CMake build using functions and targets from subdirectory or installed location
add_custom_target(test_cmake_build)
if(NOT CMAKE_VERSION VERSION_LESS 3.1)
# 3.0 needed for interface library for subdirectory_target/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}"

include(CMakeParseArguments)
function(pybind11_add_build_test name)
cmake_parse_arguments(ARG "INSTALL" "" "" ${ARGN})

set(build_options "-DCMAKE_PREFIX_PATH=${PROJECT_BINARY_DIR}/mock_install"
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}"
"-DPYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}")
if(NOT ARG_INSTALL)
list(APPEND build_options "-DPYBIND11_PROJECT_DIR=${PROJECT_SOURCE_DIR}")
endif()

add_custom_target(test_${name} ${CMAKE_CTEST_COMMAND}
--quiet --output-log test_cmake_build/${name}.log
--build-and-test "${CMAKE_CURRENT_SOURCE_DIR}/test_cmake_build/${name}"
"${CMAKE_CURRENT_BINARY_DIR}/test_cmake_build/${name}"
--build-config Release
--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 ${build_options}
)
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}"
if(ARG_INSTALL)
add_dependencies(test_${name} mock_install)
endif()
add_dependencies(test_cmake_build test_${name})
endfunction()

pybind11_add_build_test(subdirectory_function)
pybind11_add_build_test(subdirectory_target)

if(PYBIND11_INSTALL)
add_custom_target(mock_install ${CMAKE_COMMAND}
"-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/mock_install"
-P "${PROJECT_BINARY_DIR}/cmake_install.cmake"
)
else()
add_custom_target(test_installed_target)
add_custom_target(test_installed_module)

pybind11_add_build_test(installed_function INSTALL)
pybind11_add_build_test(installed_target INSTALL)
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
$<TARGET_FILE:pybind11_tests> ${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
# Run all the tests
add_custom_target(check DEPENDS pytest test_cmake_build)
12 changes: 12 additions & 0 deletions tests/test_cmake_build/installed_function/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}")

pybind11_add_module(test_cmake_build SHARED NO_EXTRAS ../main.cpp)

add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_cmake_build>
${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME})
18 changes: 18 additions & 0 deletions tests/test_cmake_build/installed_target/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.0)
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)

target_link_libraries(test_cmake_build 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}")

add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_cmake_build>
${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME})
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include <pybind11/pybind11.h>
namespace py = pybind11;

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

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

Expand Down
8 changes: 8 additions & 0 deletions tests/test_cmake_build/subdirectory_function/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 2.8.12)
project(test_subdirectory_module CXX)

add_subdirectory(${PYBIND11_PROJECT_DIR} pybind11)
pybind11_add_module(test_cmake_build THIN_LTO ../main.cpp)

add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_cmake_build>
${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME})
15 changes: 15 additions & 0 deletions tests/test_cmake_build/subdirectory_target/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.0)
project(test_subdirectory_target CXX)

add_subdirectory(${PYBIND11_PROJECT_DIR} pybind11)

add_library(test_cmake_build MODULE ../main.cpp)

target_link_libraries(test_cmake_build 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}")

add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_cmake_build>
${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/../test.py ${PROJECT_NAME})
5 changes: 5 additions & 0 deletions tests/test_cmake_build/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys
import test_cmake_build

assert test_cmake_build.add(1, 2) == 3
print("{} imports, runs, and adds: 1 + 2 = 3".format(sys.argv[1]))
Loading