From 3488494a814c025b19c9a223e8bbd6b0599b176b Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 2 Oct 2020 22:34:22 -0400 Subject: [PATCH] refactor: import check as a common function (#2526) * feat: import check as a common function * docs: add cmake to docs --- docs/changelog.rst | 3 + docs/cmake/index.rst | 9 ++ docs/compiling.rst | 2 + docs/conf.py | 2 +- docs/index.rst | 1 + docs/requirements.txt | 1 + tests/CMakeLists.txt | 19 +--- tools/pybind11Common.cmake | 73 ++++++++++++- tools/pybind11Config.cmake.in | 187 +++++++++++++++++++++++++--------- tools/pybind11Tools.cmake | 3 + 10 files changed, 230 insertions(+), 70 deletions(-) create mode 100644 docs/cmake/index.rst diff --git a/docs/changelog.rst b/docs/changelog.rst index b19cd2eab..33a3206e0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -85,6 +85,9 @@ See :ref:`upgrade-guide-2.6` for help upgrading to the new version. ``CMAKE_INTERPROCEDURAL_OPTIMIZATION``, ``set(CMAKE_CXX_VISIBILITY_PRESET hidden)``. + * Helper functions ``pybind11_strip``, ``pybind11_extension``, + ``pybind11_find_import`` added, see :doc:`cmake/index`. + * Optional :ref:`find-python-mode` and :ref:`nopython-mode` with CMake. `#2370 `_ diff --git a/docs/cmake/index.rst b/docs/cmake/index.rst new file mode 100644 index 000000000..492930008 --- /dev/null +++ b/docs/cmake/index.rst @@ -0,0 +1,9 @@ +CMake helpers +------------- + +Pybind11 can be used with ``add_subdirectory(extern/pybind11)``, or from an +install with ``find_package(pybind11 CONFIG)``. The interface provided in +either case is functionally identical. + +.. cmake-module:: ../../tools/pybind11Config.cmake.in + diff --git a/docs/compiling.rst b/docs/compiling.rst index 39cde8c52..d4b3767e1 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -201,6 +201,8 @@ PyPI integration, can be found in the [cmake_example]_ repository. .. versionchanged:: 2.6 CMake 3.4+ is required. +Further information can be found at :doc:`cmake/index`. + pybind11_add_module ------------------- diff --git a/docs/conf.py b/docs/conf.py index 506e6f36a..831cf6f58 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,7 @@ import subprocess # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['breathe', 'sphinxcontrib.rsvgconverter'] +extensions = ['breathe', 'sphinxcontrib.rsvgconverter', 'sphinxcontrib.moderncmakedomain'] breathe_projects = {'pybind11': '.build/doxygenxml/'} breathe_default_project = 'pybind11' diff --git a/docs/index.rst b/docs/index.rst index 699e69200..bf79108f7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,3 +40,4 @@ benchmark limitations reference + cmake/index diff --git a/docs/requirements.txt b/docs/requirements.txt index 6e43af053..70091d089 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,3 +4,4 @@ recommonmark==0.6.0 sphinx==3.2.1 sphinx_rtd_theme==0.5.0 sphinxcontrib-svg2pdfconverter==1.1.0 +sphinxcontrib-moderncmakedomain==3.13 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5dbc296a4..c73da610f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -347,24 +347,7 @@ foreach(target ${test_targets}) endforeach() # Make sure pytest is found or produce a warning -if(NOT PYBIND11_PYTEST_FOUND) - execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c "import pytest; print(pytest.__version__)" - RESULT_VARIABLE pytest_not_found - OUTPUT_VARIABLE pytest_version - ERROR_QUIET) - if(pytest_not_found) - message(WARNING "Running the tests requires pytest. Please install it manually" - " (try: ${PYTHON_EXECUTABLE} -m pip install pytest)") - elseif(pytest_version VERSION_LESS 3.1) - message(WARNING "Running the tests requires pytest >= 3.1. Found: ${pytest_version}" - "Please update it (try: ${PYTHON_EXECUTABLE} -m pip install -U pytest)") - else() - set(PYBIND11_PYTEST_FOUND - TRUE - CACHE INTERNAL "") - endif() -endif() +pybind11_find_import(pytest VERSION 3.1) if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) # This is not used later in the build, so it's okay to regenerate each time. diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index 26a1e0489..db73a4a4d 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -15,7 +15,7 @@ Adds the following targets:: Adds the following functions:: pybind11_strip(target) - strip target after building on linux/macOS - + pybind11_find_import(module) - See if a module is installed. #]======================================================] @@ -196,6 +196,77 @@ else() endif() +# --------------------- pybind11_check_import ------------------------------- + +if(NOT _pybind11_nopython) + # Check to see if modules are importable. Use REQUIRED to force an error if + # one of the modules is not found. _FOUND will be set if the + # package was found (underscores replace dashes if present). QUIET will hide + # the found message, and VERSION will require a minimum version. A successful + # find will cache the result. + function(pybind11_find_import PYPI_NAME) + # CMake variables need underscores (PyPI doesn't care) + string(REPLACE "-" "_" NORM_PYPI_NAME "${PYPI_NAME}") + + # Return if found previously + if(${NORM_PYPI_NAME}_FOUND) + return() + endif() + + set(options "REQUIRED;QUIET") + set(oneValueArgs "VERSION") + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "" ${ARGN}) + + if(ARG_REQUIRED) + set(status_level FATAL_ERROR) + else() + set(status_level WARNING) + endif() + + execute_process( + COMMAND + ${${_Python}_EXECUTABLE} -c + "from pkg_resources import get_distribution; print(get_distribution('${PYPI_NAME}').version)" + RESULT_VARIABLE RESULT_PRESENT + OUTPUT_VARIABLE PKG_VERSION + ERROR_QUIET) + + string(STRIP "${PKG_VERSION}" PKG_VERSION) + + # If a result is present, this failed + if(RESULT_PRESENT) + set(${NORM_PYPI_NAME}_FOUND + ${NORM_PYPI_NAME}-NOTFOUND + CACHE INTERNAL "") + # Always warn or error + message( + ${status_level} + "Missing: ${PYPI_NAME} ${ARG_VERSION}\nTry: ${${_Python}_EXECUTABLE} -m pip install ${PYPI_NAME}" + ) + else() + if(ARG_VERSION AND PKG_VERSION VERSION_LESS ARG_VERSION) + message( + ${status_level} + "Version incorrect: ${PYPI_NAME} ${PKG_VERSION} found, ${ARG_VERSION} required - try upgrading" + ) + else() + set(${NORM_PYPI_NAME}_FOUND + YES + CACHE INTERNAL "") + set(${NORM_PYPI_NAME}_VERSION + ${PKG_VERSION} + CACHE INTERNAL "") + endif() + if(NOT ARG_QUIET) + message(STATUS "Found ${PYPI_NAME} ${PKG_VERSION}") + endif() + endif() + if(NOT ARG_VERSION OR (NOT PKG_VERSION VERSION_LESS ARG_VERSION)) + # We have successfully found a good version, cache to avoid calling again. + endif() + endfunction() +endif() + # --------------------- LTO ------------------------------- include(CheckCXXCompilerFlag) diff --git a/tools/pybind11Config.cmake.in b/tools/pybind11Config.cmake.in index 3f1117296..9808f3d25 100644 --- a/tools/pybind11Config.cmake.in +++ b/tools/pybind11Config.cmake.in @@ -1,57 +1,80 @@ -#[=============================================================================[.rst +#[=============================================================================[.rst: pybind11Config.cmake --------------------- +#################### -PYBIND11 cmake module. -This module sets the following variables in your project:: +Exported variables +================== - pybind11_FOUND - true if pybind11 and all required components found on the system - pybind11_VERSION - pybind11 version in format Major.Minor.Release - pybind11_VERSION_TYPE - pybind11 version type (dev, release) - pybind11_INCLUDE_DIRS - Directories where pybind11 and python headers are located. - pybind11_INCLUDE_DIR - Directory where pybind11 headers are located. - pybind11_DEFINITIONS - Definitions necessary to use pybind11, namely USING_pybind11. - pybind11_LIBRARIES - compile flags and python libraries (as needed) to link against. - pybind11_LIBRARY - empty. +This module sets the following variables in your project: +``pybind11_FOUND`` + true if pybind11 and all required components found on the system +``pybind11_VERSION`` + pybind11 version in format Major.Minor.Release +``pybind11_VERSION_TYPE`` + pybind11 version type (dev, release) +``pybind11_INCLUDE_DIRS`` + Directories where pybind11 and python headers are located. +``pybind11_INCLUDE_DIR`` + Directory where pybind11 headers are located. +``pybind11_DEFINITIONS`` + Definitions necessary to use pybind11, namely USING_pybind11. +``pybind11_LIBRARIES`` + Compile flags and python libraries (as needed) to link against. +``pybind11_LIBRARY`` + Empty. Available components: None -Exported targets:: +Exported targets +================ -If pybind11 is found, this module defines the following :prop_tgt:`IMPORTED` -interface library targets:: +If pybind11 is found, this module defines the following ``IMPORTED`` +interface library targets: - pybind11::module - for extension modules - pybind11::embed - for embedding the Python interpreter +``pybind11::module`` + for extension modules. +``pybind11::embed`` + for embedding the Python interpreter. Python headers, libraries (as needed by platform), and the C++ standard are attached to the target. Advanced targets are also supplied - these are primary for users building -complex applications, and they are available in all modes:: +complex applications, and they are available in all modes: - pybind11::headers - Just the pybind11 headers and minimum compile requirements - pybind11::pybind11 - Python headers too - pybind11::python_link_helper - Just the "linking" part of pybind11:module, for CMake < 3.15 - pybind11::python2_no_register - Quiets the warning/error when mixing C++14+ and Python 2, also included in pybind11::module - pybind11::thin_lto - An alternative to INTERPROCEDURAL_OPTIMIZATION - pybind11::lto - An alternative to INTERPROCEDURAL_OPTIMIZATION (also avoids thin LTO on clang) - pybind11::windows_extras - Adds bigobj and mp for MSVC +``pybind11::headers`` + Just the pybind11 headers and minimum compile requirements. +``pybind11::pybind11`` + Python headers too. +``pybind11::python_link_helper`` + Just the "linking" part of ``pybind11:module``, for CMake < 3.15. +``pybind11::python2_no_register`` + Quiets the warning/error when mixing C++14+ and Python 2, also included in ``pybind11::module``. +``pybind11::thin_lto`` + An alternative to ``INTERPROCEDURAL_OPTIMIZATION``. +``pybind11::lto`` + An alternative to ``INTERPROCEDURAL_OPTIMIZATION`` (also avoids thin LTO on clang). +``pybind11::windows_extras`` + Adds bigobj and mp for MSVC. -Modes:: +Modes +===== There are two modes provided; classic, which is built on the old Python discovery packages in CMake, or the new FindPython mode, which uses FindPython from 3.12+ forward (3.15+ _highly_ recommended). -New FindPython mode:: +New FindPython mode +^^^^^^^^^^^^^^^^^^^ To activate this mode, either call ``find_package(Python COMPONENTS Interpreter Development)`` before finding this package, or set the ``PYBIND11_FINDPYTHON`` variable to ON. In this mode, -you can either use the basic targets, or use the FindPython tools:: +you can either use the basic targets, or use the FindPython tools: + +.. code-block:: cmake find_package(Python COMPONENTS Interpreter Development) find_package(pybind11 CONFIG) @@ -70,10 +93,13 @@ you can either use the basic targets, or use the FindPython tools:: If you build targets yourself, you may be interested in stripping the output for reduced size; this is the one other feature that the helper function gives you. -Classic mode:: +Classic mode +^^^^^^^^^^^^ Set PythonLibsNew variables to influence python detection and -CMAKE_CXX_STANDARD to influence standard setting. :: +CMAKE_CXX_STANDARD to influence standard setting. + +.. code-block:: cmake find_package(pybind11 CONFIG REQUIRED) @@ -85,31 +111,92 @@ CMAKE_CXX_STANDARD to influence standard setting. :: add_executable(myexe main.cpp) target_link_libraries(myexe PUBLIC pybind11::embed) -Suggested usage:: -find_package with version info is not recommended except for release versions. :: +Hints +===== + +The following variables can be set to guide the search for this package: + +``pybind11_DIR`` + CMake variable, set to directory containing this Config file. +``CMAKE_PREFIX_PATH`` + CMake variable, set to root directory of this package. +``PATH`` + Environment variable, set to bin directory of this package. +``CMAKE_DISABLE_FIND_PACKAGE_pybind11`` + CMake variable, disables ``find_package(pybind11)`` when not ``REQUIRED``, + perhaps to force internal build. + +Commands +======== + +pybind11_add_module +^^^^^^^^^^^^^^^^^^^ + +This module defines the following commands to assist with creating Python modules: + +.. code-block:: cmake + + pybind11_add_module( + [STATIC|SHARED|MODULE] + [THIN_LTO] [OPT_SIZE] [NO_EXTRAS] [WITHOUT_SOBAI] + ... + ) + +Add a module and setup all helpers. You can select the type of the library; the +default is ``MODULE``. There are several options: + +``OPT_SIZE`` + Optimize for size, even if the ``CMAKE_BUILD_TYPE`` is not ``RelSize``. +``THIN_LTO`` + Use thin TLO instead of regular if there's a choice (pybind11's selection + is disabled if ``CMAKE_INTERPROCEDURAL_OPTIMIZATIONS`` is set). +``WITHOUT_SOABI`` + Disable the SOABI component (``PYBIND11_NEWPYTHON`` mode only). +``NO_EXTRAS`` + Disable all extras, exit immediately after making the module. + +pybind11_strip +^^^^^^^^^^^^^^ + +.. code-block:: cmake + + pybind11_strip() + +Strip a target after building it (linux/macOS), called by ``pybind11_add_module``. + +pybind11_extension +^^^^^^^^^^^^^^^^^^ + +.. code-block:: cmake + + pybind11_extension() + +Sets the Python extension name correctly for Python on your platform, called by +``pybind11_add_module``. + +pybind11_find_import(module) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: cmake + + pybind11_find_import( [VERSION ] [REQUIRED] [QUIET]) + +See if a module is installed. Use the registered name (the one on PyPI). You +can specify a ``VERSION``, and you can specify ``REQUIRED`` or ``QUIET``. Only available if +``NOPYTHON`` mode is not active. Sets ``module_VERSION`` and ``module_FOUND``. Caches the +result once a valid install is found. + +Suggested usage +=============== + +Using ``find_package`` with version info is not recommended except for release versions. + +.. code-block:: cmake find_package(pybind11 CONFIG) find_package(pybind11 2.0 EXACT CONFIG REQUIRED) - -The following variables can be set to guide the search for this package:: - - pybind11_DIR - CMake variable, set to directory containing this Config file - CMAKE_PREFIX_PATH - CMake variable, set to root directory of this package - PATH - environment variable, set to bin directory of this package - CMAKE_DISABLE_FIND_PACKAGE_pybind11 - CMake variable, disables - find_package(pybind11) when not REQUIRED, perhaps to force internal build - -Helper functions:: - - pybind11_add_module(...) - Add a library and setup all helpers - pybind11_strip(target) - Strip a target after building it (linux/macOS) - pybind11_extension(target) - Injects the Python extension name - -See ``pybind11Tools.cmake`` or ``pybind11NewTools.cmake`` for details on -``pybind11_add_module``. - #]=============================================================================] @PACKAGE_INIT@ diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index a0a3b60eb..7b7152815 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -189,3 +189,6 @@ function(pybind11_add_module target_name) target_link_libraries(${target_name} PRIVATE pybind11::opt_size) endif() endfunction() + +# Provide general way to call common Python commands in "common" file. +set(_Python PYTHON)