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

Conversation

loriab
Copy link
Contributor

@loriab loriab commented Nov 17, 2016

Motivation

Acquiring the pybind11 repo (for large projects where the pybind11_add_module isn't appropriate) is admittedly not hard either as a git submodule or as a CMake ExternalProject plus writing a Findpybind11.cmake file to search out the header location. But for the ExternalProject case, the better CMake route is to provide a pybind11Config.cmake in the installation so that consuming projects can use pre-built (installed) pybind11 without writing their own detection.

PR Changes

  • Builds a minimal interface library target (pb11) and exports it so that pybind11Config.cmake and pybind11Targets.cmake are installed along with the headers. Instructions for detection and use given in tools/pybind11Config.cmake.in
  • Allows passing -DCMAKE_INSTALL_INCLUDEDIR to specify non-standard install location. Defaults to include. Note that this isn't affecting the pybind11_add_module function since it's not exporting interface include dirs.

Questions

  • Used pybind11::pybind11 for imported target name. If you want any other capitalization pattern for the project name, let me know and I'll adjust everything.
  • I've hard-coded the version at 1.8, but the write_basic_package_version_file(...) four lines and the ConfigVersion.cmake installation can be commented out, if you don't want to deal with versioning in development mode.
  • I'm medium proficient at CMake, so please let me know if you see any holes in the procedure, would like some edits, or want the PR split.

CMake Version

@wjakob
Copy link
Member

wjakob commented Nov 17, 2016

Quick comment: Could you please separate out the ICPC patch, since it is not related to cmake?

@loriab
Copy link
Contributor Author

loriab commented Nov 17, 2016

Sure thing, done.

Copy link
Member

@dean0x7d dean0x7d left a comment

Choose a reason for hiding this comment

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

Nice. This should be quite useful. I left some smaller comments in the code. But I think the main thing to work out is the CMake version compatibility and testing.

How certain is the CMake > v3.0.1 requirement? I'm not that familiar with this part of CMake, but I seem to remember that exports work in v2.8.12. However, I think there are some issues with defining header-only libraries on versions older than 3.1.0.

It would be very good to set up a minimal build test which can be invoked from pytest. The various configurations on Travis and AppVeyor have both CMake v2.8.12 and > v3.0. It would also make sure everything works cross-platform. A subdirectory could be added to tests containing a CMakeLists.txt and a simple cpp source file. A pytest function could run the shell commands to build it and make sure everything checks out.

"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR})
write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
VERSION 1.8
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to parse the version number from common.h:L55-L57 to avoid duplication and possible mismatches.

Copy link
Member

Choose a reason for hiding this comment

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

(And this is already a mismatch: current master version is 1.9.dev0).

@loriab - this should work to extract it:

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

message(STATUS "pybind11 version ${PYBIND11_VERSION_MAJOR}.${PYBIND11_VERSION_MINOR}.${PYBIND11_VERSION_PATCH}")

# PYBIND11 cmake module.
# This module sets the following variables in your project:
#
# ::
Copy link
Member

Choose a reason for hiding this comment

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

Any reason not to include this on the last text line? ... in your project::

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No reason at all except that I copied the FindZLIB.cmake long ago and never noticed the formatting error. Good point.

#
# pybind11::pybind11 - the main pybind11 interface library (i.e., headers)
#
# find_package(PythonLibsNew REQUIRED)
Copy link
Member

Choose a reason for hiding this comment

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

FindPythonLibsNew is not a standard CMake module and it's unlikely that users will have it. It should be added to the install files along with the pybind11 config package. That would be pretty convenient.

Copy link
Member

Choose a reason for hiding this comment

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

Yep, and it looks like our version will even have some modifications.

@jagerman
Copy link
Member

have both CMake v2.8.12 and > v3.0.

Just to clarify: the gcc 4.8 builds are 2.8.12, while the gcc 6, OS X, and MSVC builds are all currently on 3.6.2 (and will probably get 3.7.0 soon).

So we don't actually have much in the middle: just the three-year old 2.8.12, and the two-month old 3.6.2, in case that matters.

@loriab
Copy link
Contributor Author

loriab commented Nov 18, 2016

On the CMake version, testing locally on what I could find, 2.8.11 didn't work and 3.0.1 did (and if I recall correctly, the installed Configs matched my 3.3-generated ones). So when the Travis tests failed (first two and barebones), I concluded 2.8.12 wasn't safe and just excluded it. I think that leaves only 3.0.0 uncertain.

Thanks for all the comments. I'll see about patching them up.

@jagerman
Copy link
Member

first two and barebones

Yeah, those are the only ones with old cmake.

@wjakob
Copy link
Member

wjakob commented Nov 18, 2016

A general question: I'm wondering: what is the benefit (if any) to using ExternalProject in Cmake? I used that for a while but ran into so many issues with race conditions on different backends (Ninja, Visual studio build systems) that I eventually switched all of my projects to recursive cmake projects with the add_subdirectory mechanism.

It doesn't seem to me that this is really a question about size of the project. For instance, Clang/LLVM is a very big project, but it doesn't use ExternalProject_Add

@wjakob
Copy link
Member

wjakob commented Nov 18, 2016

(I'm also not sure what seems wrong with pybind11_add_module. IMO that function should work for any case where pybind11 is used to build an extension module. All it does is make a shared library with the right flags, and it's possible to customize the parameters in virtually any way)

@loriab
Copy link
Contributor Author

loriab commented Nov 18, 2016

We (the Psi4 project) find ExternalProject useful both to not store others' code in our codebase (having proven that our developers can't handle git submodules) and also to be able to transparently use both detected pre-built (especially conda packages) and internally built dependencies and extension projects.

Admittedly, I've never dealt with any non-GnuMake backends to CMake, but having converted to a CMake superbuild scheme (thanks to @bennybp), haven't had any race condition problems.

Regarding pybind11_add_module, we're just not in a position to be able to pass all the source in to the function (and neither, I don't think, is Ben's project.

@dean0x7d
Copy link
Member

CMake has add_subdirectory, find_package and ExternalProject_Add which all do similar but different things. The config file here targets find_package and could do something similar to add_subdirectory with the difference that it uses pybind11 from an external location instead of a subdirectory (it's not deferred to make-time). ExternalProject_Add doesn't necessary need to come into play here.

(The superbuild method actually separates ExternalProject_Add and find_package into two completely different invocations of CMake in order to avoid the make-time issues of ExternalProject_Add.)

@dean0x7d
Copy link
Member

It should also be possible to add pybind11_add_module to the config file which would make add_subdirectory and find_package equivalent. This could be done in addition to the exported target (and doesn't necessarily need to be part of this PR).

@loriab said:
Regarding pybind11_add_module, we're just not in a position to be able to pass all the source in to the function (and neither, I don't think, is Ben's project).

I'm not sure I understand this. pybind11_add_module works just like add_library. There shouldn't be any issues.

@loriab
Copy link
Contributor Author

loriab commented Nov 18, 2016

Thanks, @dean0x7d, for expressing more clearly than I was that the Config file is primarily to locate the pybind11 installation. We could disallow detection of pre-built pybind11 and instead build internally so that we always know were the pybind headers were to be found and thus didn't require either Findpybind11.cmake or pybind11Config.cmake but then a project wanting to ensure the same pybind11 version was in use for Psi4 and their own project wouldn't be able to do so without git tag coordination (not sure how much of an issue that is, but that's what I hear from one cubicle over :-) ).

@dean0x7d said:
I'm not sure I understand this. pybind11_add_module works just like add_library. There shouldn't be any issues.

Looking again, I think I agree the add_module could be used by us, if it was accessible from the installed pybind. Some low barriers are (1) right now we use the add_library(... SHARED ...) rather than add_library(... MODULE ...) signatures since our plugins target_link_library us and (2) it's nice to have the target so can read properties off of it to hand to object libraries.

Thanks for the suggestion of adding pybind_add_module to the installation. I've not written something like that before, but I've seen models and will investigate.

@bennybp
Copy link
Contributor

bennybp commented Nov 18, 2016

1.) ExternalProjects come in handy if you have very different packages with different building mechanisms, build flags, different languages, etc. Unfortunately, this often happens in science (for example, the external project may run a code generator first). In addition, it can help keep projects separate during the build, keeping them from inheriting incompatible build flags, etc.

2.) pybind11_add_module is handy for some projects, but unconventional, and somewhat inflexible and prescriptive. For example, we may not what IPO or we might want to use a different (but compatible) C++ standard.

This feels like pybind11 is becoming the top project, whereas it really should be treated as a header library. Also, if there was another library that had its own function like add_module, a project couldn't use both.

@dean0x7d
Copy link
Member

dean0x7d commented Nov 19, 2016

Personally, I think it would be useful to have both: pybind11_add_module as the easy-to-use solution with good defaults, and a header-only library target for more specific needs. The latter does require CMake > v3.0.

Slightly off topic, but note that it would be pretty easy to add options to pybind11_add_module. Just like add_library has

add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])

we could have something similar:

pybind11_add_module(<name> [MODULE | SHARED] [LTO] [EXCLUDE_FROM_ALL] source1 [source2 ...])

@loriab said:
Looking again, I think I agree the add_module could be used by us, if it was accessible from the installed pybind. Some low barriers are (1) right now we use the add_library(... SHARED ...) rather than add_library(... MODULE ...) signatures since our plugins target_link_library us and (2) it's nice to have the target so can read properties off of it to hand to object libraries.

(2) is not a problem. pybind11_add_module creates a normal target just like add_library (which is called internally). All the usual CMake rules still apply. (1) could be solved by adding an option as mentioned above.

@bennybp said:
This feels like pybind11 is becoming the top project, whereas it really should be treated as a header library. Also, if there was another library that had its own function like add_module, a project couldn't use both.

I think there might be some misunderstanding of the extent of pybind11_add_module. It really is just a wrapper around add_library. It doesn't take over or interfere with anything else in the top level project. Other such functions exist, e.g. cuda_add_library and cuda_add_executable and there is no issue using both at the same time:

cuda_add_library(foo ${cuda_sources})
pybind11_add_library(bar ${binding_sources})
target_link_libraries(bar PRIVATE foo)

@loriab
Copy link
Contributor Author

loriab commented Nov 21, 2016

Thanks, @jagerman, for the versioning snippet.

CMake isn't very flexible in its allowed versions, and it can't do version comparison with the string "dev0" patch level (at least not without additional training). With allowing CMake to write its own (non-customized) ConfigVersion.cmake file, versioned package detection is working pretty much as expected: sensible for MAJOR.minor, unaware of pre- or dev-releases, unable to cope with strings.

for an installation of 1.9.dev0:

  • found suitable package:

    • find_package(pybind11 CONFIG)
    • find_package(pybind11 1.9 CONFIG)
    • find_package(pybind11 1.8 CONFIG)
    • find_package(pybind11 1.9.0 CONFIG)
  • no suitable package

    • find_package(pybind11 1.9 EXACT CONFIG)
    • find_package(pybind11 2.0 CONFIG)
    • find_package(pybind11 1.9.1 CONFIG)
    • find_package(pybind11 1.9.0 EXACT CONFIG)
  • no suitable package w/Error: "find_package called with invalid argument "1.9.dev0" "

    • find_package(pybind11 1.9.dev0 CONFIG)

So basically, is the behavior above (users recommended not to find_package with version except at releases) satisfactory, or do you want to describe in detail your versioning scheme to me and see if I can write a custom ConfigVersion.cmake?

@dean0x7d
Copy link
Member

That looks perfectly fine to me. The dev0 part will become 0 for a stable release, so CMake shouldn't have any issues then.

# defs because (1) that's really the domain of the upstream project to decide,
# (2) we can get away with it since installation (headers) don't care about
# python version or C++ standard, and (3) this will make the easily target relocatable.
add_library(pb11 INTERFACE)
Copy link
Member

Choose a reason for hiding this comment

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

Can't we call it pybind11? pb11 seems a bit obscure to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, done. It was just a dummy name so I didn't claim pybind11 as a target name that you might have wanted to use.

set_target_properties(pb11 PROPERTIES EXPORT_NAME "pybind11")
target_include_directories(pb11 INTERFACE $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
if(APPLE)
target_link_libraries(pb11 INTERFACE "-undefined dynamic_lookup")
Copy link
Member

Choose a reason for hiding this comment

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

It's weird to add linker flags for APPLE here but then not link to the Python library on WIN32/64.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That can't be done exactly here because we don't want paths to system libraries embedded in the ConfigTarget*.cmake file. If it's going to be done, Python will need to be redetected in the pybind11Config.cmake and appended there. See full comment for more on this possibility.

@wjakob
Copy link
Member

wjakob commented Nov 22, 2016

I had a more careful look at this PR now. I think the interface library approach is problematic because it just does a small subset of the things that are needed to ensure good behavior when linking pybind11 modules across various platforms. In practice, users will need to add a number of flags in addition to using the interface library, essentially replicating the contents of pybind11_add_module in the process. I think that this is likely to cause more confusion than help.

I suggest the following course of action:

  1. Remove the interface library

  2. Improve pybind11_add_module to make it suit your needs. This could involve making certain parts of it optional by adding extra parameters (e.g. LTO is not strictly needed even though it's probably a bad idea not to use it in most cases, a similar thing holds for stripping binaries in release mode)

  3. Ensure that pybind11_add_module is defined in the pybind*.cmake. This will also involve installing our version of FindPythonNew.

  4. All of this will need to be documented (docs/compiling.rst) so that people know that the functionality is there.

@loriab
Copy link
Contributor Author

loriab commented Nov 22, 2016

The recently added commits mostly address comments before this morning. Sorry about the mac-psinet user authorship – I forgot the defaults on that computer, and I daresay this history will all be overwritten.

Current state of the code

  • Extracted the version from common.h and stored in variable ${pybind11_VERSION}
  • Installed FindPythonLibsNew.cmake alongside pybind11Config.cmake and extended CMAKE_MODULE_PATH so find-able. Confirmed that a project-local version of that module will be preferentially detected.
  • Collected 3.0.0 release notes evidence that add_library(... INTERFACE started then and adjusted ifs accordingly.
  • With the FindPythonLibsNew in place, I could run Py detection and move 11/14 std into its own file so that it could be installed, and then actually attach the Py headers and c++ std compile def to the imported target. But that would require real changes to the main CMakeLists.txt . Similarly, I could make the pybind11_add_module function useable from an external target. But that, too, means taking it out of the main CMakeLists.txt (or duplicating code). So let me know if you'd actually prefer a dismemberment of the main CMakeLists.txt
  • I'm afraid I don't understand pytest, or at least how pytest should imitate a bash script. The requested test is complicated by depending on an installed pybind11. What I ended up doing was making a test (stole the main.cpp from pybind/cmake_example; let me know if you'd rather I hadn't), driving it with bash (I know, useless on Windows), and driving that with a CMake custom command appended to the pytest target. It's working on all the Travis tests except Mac 3.5, and I'm working on that one. If you give me more clues on how the pytest is supposed to work in this situation, I can maybe convert.

Comments on this morning's comments

  • It sounds like you'd really like the pybind11_add_module functionality available from pybind11Config.cmake. As mentioned above, I believe this means extracting parts of the main CMakeLists.txt into separate files that can be installed (or duplicating code). Does this sound right and acceptable?

  • If yes on the above, I'll have a go at extracting pybind11_add_module and making it flexible enough for our needs. But even so, it might be better to separate the attributes of the pybind header library and the recommended attributes of the python module built with pybind. That is, continue to have an interface library target (with essentially the python headers and cxx standard properties) and use that library target in pybind11_add_module as a link library as well as applying the LTO and binary stripping attributes to the python module.

  • Glad to write docs when everything's converged.

@dean0x7d
Copy link
Member

dean0x7d commented Nov 22, 2016

Just a quick comment about test execution: In my original suggestion for pytest integration, I forgot about the need to forward CMake variables. Rather than pytest or bash, this could be done purely with CMake scripting. Let me just get to a computer to test this -- I have something in mind, I'll get back to you with a CMake code snippet a little later today.

@loriab
Copy link
Contributor Author

loriab commented Nov 23, 2016

The most recent two commits are an unpolished implementation of having pybind11_add_module being accessible from installed pybind. Essentially the main CMakeLists.txt contains only code relevant to installation (I've kept the interface lib), while tools/pybind11Tools.cmake contains all the portion of main CMakeLists.txt devoted to building a module. What can be done from a pybind11 installation:

  • import a dependency-bare pybind11::pybind11 interface lib through pybind11Config, then attach Py headers and c++11/14 compile options, then build lib (this is what this PR used to do)
  • import a dependency-rich pybind11::pybind11 interface lib through pybind11Config. Py headers and c++11/14 are detected and attached at runtime and are sensitive to PYBIND11_CPP_STANDARD, etc. Works cmake >= 3.0
  • locate pybind11 through pybind11Config, then include(pybind11Tools) so that can pybind11_add_module(name SHARED sources ...). Works cmake >= 2.8.12

There are now two additional tests (still bash-driven). test_installed_target covers the second bullet above, and test_installed_module covers the third bullet point. Note that I had to change the travis install dir b/c I wasn't allowed to install to /usr/local on the 2.8.12 builds. All the Travis tests should now be passing.

@dean0x7d
Copy link
Member

Regarding test execution, the following CMake code should do the job nicely:

add_custom_target(test_install
  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_cmake_target"
                           "${CMAKE_CURRENT_BINARY_DIR}/test_cmake_target"
          --build-noclean
          --build-generator ${CMAKE_GENERATOR}
          $<$<BOOL:${CMAKE_GENERATOR_PLATFORM}>:--build-generator-platform> ${CMAKE_GENERATOR_PLATFORM}
          --build-makeprogram ${CMAKE_MAKE_PROGRAM}
          --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}"
)

The first command is essentially cmake -P cmake_install.cmake and it's equivalent to make install but works with any platform/build system. The destination is set to the build directory test_install to avoid install location issues on CI or polluting system directories during development.

The second command uses ctest to build the test project, just like the bash script but this one also works on Windows. See diff dean0x7d@a239031 for more details -- green CI on all platforms.

I recommend giving this test its own target rather than attaching to pytest POST_BUILD.

Copy link
Member

@dean0x7d dean0x7d left a comment

Choose a reason for hiding this comment

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

The approach with tools/pybind11Tools.cmake looks great. I left a few comments. It's a bit hard to look through the full changes right now. I'll do another pass when it's polished up a bit. After converting the bash test to CMake all the CI platforms should also pass.

@@ -0,0 +1,48 @@
// C++ file taken from https://github.com/pybind/cmake_example/blob/master/src/main.cpp
Copy link
Member

Choose a reason for hiding this comment

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

You can simplify the test cpp file to just:

#include <pybind11/pybind11.h>
namespace py = pybind11;

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

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

    return m.ptr();
}

Nothing else is really needed.

@@ -17,32 +17,32 @@ endif()

option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT})
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.

# (2) we can get away with it since installation (headers) don't care about
# python version or C++ standard, and (3) this will make the easily target relocatable.
add_library(pybind11 INTERFACE)
target_include_directories(pybind11 INTERFACE $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
Copy link
Member

Choose a reason for hiding this comment

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

Include $<BUILD_INTERFACE:${PYBIND11_INCLUDE_DIR}> so that the target is also usable with add_subdirectory.

@dean0x7d
Copy link
Member

The error on AppVeyor is because MSVC outputs the extension module in a subdirectory (Debug\x64). The quickest solution is:

  1. Add the following target to test_installed_module/CMakeLists.txt and test_installed_target/CMakeLists.txt:
add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_installed_module>
                  ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test.py)
  1. In tests/CMakeLists.txt replace --test-command with --build-target check (it must precede --build-options).

  2. sys.path.append(os.getcwd()) is no longer needed because the path is taken from the env variable.

@@ -153,6 +45,8 @@ function(pybind11_enable_warnings target_name)
endif()
endfunction()

include(pybind11Tools)
Copy link
Member

Choose a reason for hiding this comment

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

This include needs to happen before the variables are cached (line 24), otherwise it will break add_subdirectory in parent projects.

@loriab
Copy link
Contributor Author

loriab commented Nov 25, 2016

Thanks to tremendous help from @dean0x7d, testing of installed pybind11 is thoroughly passing (though the 2.8.12 cases aren't being tested as much as they could be). I think I've addressed other requests except for removing the interface library (which I still think is good CMake practice and useful to projects) and adding docs (which will be the last step). Let me know if anything looks amiss.

@dean0x7d
Copy link
Member

I think the target makes a lot more sense now that the Python headers and library are also included. The target should also be useful in the future to support embedding the Python interpreter (pybind11_add_executable doesn't make sense the way pybind11_add_module does).

My only remaining questions:

  1. I'm not sure I understand the find_path(_temp_h ...) step. Isn't the relative include dir already know at configuration time? Wouldn't it be enough to exactly set ${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_INCLUDEDIR@?

  2. There are three variables which hold the include dir: PYBIND11_INCLUDE_DIR, pybind11_INCLUDE_DIR and pybind11_INCLUDE_DIRS. I guess we can rationalize the uppercase one as an internal detail of the project (CACHE INTERNAL) while the lowercase is exported as per CMake package conventions. But can we lose one of the lowercase ones? DIRS is preferred for newer packages AFAIK.

Other than these nitpicks, the rest looks good to me. In light of these changes, I'd like to make some tweaks to add_subdirectory handling, but that's a separate issue for another PR. The main thing here is that the exported config is good and the tests cover the use case so that we don't accidentally break it going forward.

(There was also mention of making LTO and strip optional in pybind11_add_module, but that's not strictly related to this PR and I can work on that -- I have something specific in mind to also support ThinLTO.)

@loriab
Copy link
Contributor Author

loriab commented Nov 26, 2016

I think the target makes a lot more sense now that the Python headers and library are also included. The target should also be useful in the future to support embedding the Python interpreter (pybind11_add_executable doesn't make sense the way pybind11_add_module does).

Glad the target can stay. We (Psi4) actually were embedding Python when we first switched to pybind11 and started using the target in our fork. It should be easy enough to export an extra target that will (at find_package time) always attach the python library to be used with an executable.

I'm not sure I understand the find_path(_temp_h ...) step. Isn't the relative include dir already know at configuration time? Wouldn't it be enough to exactly set ${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_INCLUDEDIR@?

Yes, it's redundant. I include those sections out of habit, as a check that the installation hasn't been tampered with, and as a guide to what a FindProject.cmake would do, but your expression is equivalent. Removed.

There are three variables which hold the include dir: PYBIND11_INCLUDE_DIR, pybind11_INCLUDE_DIR and pybind11_INCLUDE_DIRS. I guess we can rationalize the uppercase one as an internal detail of the project (CACHE INTERNAL) while the lowercase is exported as per CMake package conventions. But can we lose one of the lowercase ones? DIRS is preferred for newer packages AFAIK.

My understanding of the convention is that singular DIR and LIBRARY hold the project's contributions to those variables, while plural DIRS and LIBRARIES hold both the project's and the project's dependencies' contributions. I've reworked the variables and their descriptions along those lines.

(There was also mention of making LTO and strip optional in pybind11_add_module, but that's not strictly related to this PR and I can work on that -- I have something specific in mind to also support ThinLTO.)

Once I realized you could temporarily set build type Debug to avoid all the LTO and strip operations, I figured that would do in a pinch, so not handling LTO. Note that I'm doing some argument checking that, so long as the signature so nearly matches add_library and you're willing for errors to pass through, could be eliminated.

Added some basic docs.

When this gets closer to acceptance, let me know if you'd like a rebase and history cleanup, or just a rebase, or whatever.

@dean0x7d
Copy link
Member

@KerstinKeller That's a good point about include(pybind11Tools).

@loriab I see pybind11Tools is already included in the config, but I'm guessing the extra include is required because of variable scope. In that case, the cache variables should just be moved from CMakeLists.txt into pybind11Tools.cmake to make everything work.

If CMAKECONFIG_INSTALL_DIR is made a CACHE variable, it should be prefixed with PYBIND11_.

@loriab
Copy link
Contributor Author

loriab commented Dec 10, 2016

Thanks, @KerstinKeller, I'm glad this may be useful beyond the two project I know of.

  • The Config install dir is now a cache variable, so cmake -DPYBIND11_CMAKECONFIG_INSTALL_DIR=my/config/choice -DCMAKE_INSTALL_INCLUDEDIR=my/include/choice can fully specify the installed directory structure.
  • Earlier in this conversation @dean0x7d mentioned that enable_warnings shouldn't be exported, so I haven't moved that.
  • I'm confused about the proposed need to add include(pybind11Tools) in the Config and possible need to move the cache variable block from main CMakeLists.txt to tools/pybind11Tools.cmake. The include(pybind11Tools) is already in Config, and I just removed the same line from the tests/test_installed_module/CMakeLists.txt. I think it had been there before to allow CMake 2.8.12 to pass. Could that have been the CMake you were testing with, @KerstinKeller? In that case, I think I just need to adjust the positions of the CMake version check, the target existing check, and the include(pybind11Tools) statement in the Config. According to @dean0x7d's suggestion, I tried moving the cache variables block from CMakeLists.txt to tools/pybind11Tools.cmake, but that just caused problems with PYBIND11_INCLUDE_DIR not available in main CMakeLists.txt. So this item is still undone since I don't want to change things blindly.

The Travis failure seems to be with Sphinx (error below). I don't think I'm the originator. Maybe this PR needs a rebase?

Running Sphinx v1.5
making output directory...
/home/travis/build/pybind/pybind11/venv/local/lib/python2.7/site-packages/sphinx/util/requests.py:32: UserWarning: Some links may return broken results due to being unable to check the Server Name Indication (SNI) in the returned SSL cert against the hostname in the url requested. Recommended to install "requests[security]" as a dependency or upgrade to a python version with SNI support (Python 3 and Python 2.7.9+).
  'Some links may return broken results due to being unable to '
loading pickled environment... not yet created
building [mo]: targets for 0 po files that are out of date
building [html]: targets for 26 source files that are out of date
updating environment: 26 added, 0 changed, 0 removed
reading sources... [100%] reference
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [ 69%] benchmark
Exception occurred:
  File "/home/travis/build/pybind/pybind11/venv/local/lib/python2.7/site-packages/docutils/writers/_html_base.py", line 671, in depart_document
    assert not self.context, 'len(context) = %s' % len(self.context)
AssertionError: len(context) = 2
;01mThe full traceback has been saved in /tmp/sphinx-err-KAF5PW.log, if you want to report the issue to the developers.
Please also report this if it was a user error, so that a better error message can be provided next time.
A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!
make: *** [html] Error 1
make: Leaving directory `/home/travis/build/pybind/pybind11/docs'
The command "make -C docs html SPHINX_OPTIONS=-W" exited with 2.

@dean0x7d
Copy link
Member

PYBIND11_CMAKECONFIG_INSTALL_DIR shouldn't be INTERNAL if it's meant to be configurable -- it's going to be missing from the CMake GUI. Also, it should stay inside the if(PYBIND11_INSTALL) block since it's only relevant there.

For include(pybind11tools), if it works without moving the variables, even better. I just thought that was the reason for the previous redundant include(pybind11tools) calls. You should also remove the mention and example of include(pybind11tools) from the docs (compiling.rst), since find_package(pybind11) already includes everything that's needed.

Don't worry about the sphinx error. It originates from the newly released sphinx 1.5 / docutils 0.13.1.

Copy link
Member

@wjakob wjakob left a comment

Choose a reason for hiding this comment

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

Hi,

I did a pass and left a few more comments.

Overall, I still think that the proposed interface library somewhat dangerous. It misses important compilation flags like "/bigobj, /LTCG, /GL" on Windows and "-fvisibility=hidden" and "-flto" on Linux. Remember that this is a metatemplate library, which will lead to horribly bloated binaries unless these kinds of parameters are specified (http://pybind11.readthedocs.io/en/latest/faq.html#how-can-i-create-smaller-binaries discusses some of the reasons). I am very worried that people will start using the interface library and then forget to pass these extra parameters, leading to a bad user experience when using this library. Unless these flags can be incorporated, I would rather not add the interface library to the build system.

The other changes involving the installation targets look good to me.

Thanks,
Wenzel


# Cache variables so pybind11_add_module can be used in parent projects
set(PYBIND11_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include" CACHE INTERNAL "")
# explicit "share" not GNUInstallDirs "DATADIR" for CMake search path
Copy link
Member

Choose a reason for hiding this comment

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

This comment doesn't parse for me. What does this mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Admittedly, it's obscure. If you were following GNUInstallDirs, you might expect everything for include to go into the user-configurable CMAKE_INSTALL_INCLUDEDIR and everything for share to go into CMAKE_INSTALL_DATADIR. But for purposes of hunting down packages, CMake doesn't care about GNUInstallDirs, it looks explicitly in share. I think it's worth notating. I changed the wording to GNUInstallDirs "DATADIR" wrong here; CMake search path wants "share".

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 influential CMake variables.
Copy link
Member

Choose a reason for hiding this comment

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

influental -> relevant

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 CMake >= 2.8.12 for the consuming project. A
Copy link
Member

Choose a reason for hiding this comment

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

when CMake -> when using CMake
remove "for the consuming project", sounds somewhat awkward and is not needed


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

For consuming project CMake >= 3.0, pybind11 is additionally available
Copy link
Member

Choose a reason for hiding this comment

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

"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"

#
function(pybind11_add_module target_name)
set(lib_type "MODULE")
set(do_lto True)
Copy link
Member

Choose a reason for hiding this comment

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

It appears that this is not used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, it's unused. I kept it since @dean0x7d said he had plans for it.

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

set(Python_ADDITIONAL_VERSIONS 3.4 3.5 3.6 3.7)
Copy link
Member

Choose a reason for hiding this comment

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

This line can go away. Python_ADDITIONAL_VERSIONS was used by find_package(PythonLibs), which we haven't been using for a while now.

Copy link
Member

@dean0x7d dean0x7d Dec 11, 2016

Choose a reason for hiding this comment

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

The variable is still needed because FindPythonLibsNew calls find_package(PythonInterp) and on older CMake version FindPythonInterp cannot find new Python 3.x executables (e.g. CMake 2.8.12 only checks up to 3.3).

Copy link
Member

Choose a reason for hiding this comment

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

Good catch! Should the order maybe be different in that case? (try to find the newest version first)

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, newest-first would probably be a better match for expected behavior. I just checked and CMake also uses newest-first order internally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Kept in Tools and switched to set(Python_ADDITIONAL_VERSIONS 3.7 3.6 3.5 3.4)


target_include_directories(${target_name}
PRIVATE ${PYBIND11_INCLUDE_DIR} # from project CMakeLists.txt
PRIVATE ${pybind11_INCLUDE_DIR} # from pybind11Config
Copy link
Member

Choose a reason for hiding this comment

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

Is the expectation that one of these two variables will be undefined based on how pybind11 is used/included?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. The former is present in your accustomed add_directory sense, and the project has already established the PYBIND11_INCLUDE_DIR name. For the latter, once you've selected a capitalization for the project name, the variable names are pretty rigid. (That's why I asked about your preferred capitalization in the original post.)

include("${CMAKE_CURRENT_LIST_DIR}/${PN}Targets.cmake")

find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED)
set_property(TARGET ${PN}::pybind11 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS})
Copy link
Member

Choose a reason for hiding this comment

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

This was quite confusing to me, but maybe I just don't understand cmake well enough. What is this pybind11::pybind11 interface library (and why is it, e.g., not just called pybind11 similar to when the project is included via add_directory?). How come we are able to attach properties here even though there was no corresponding add_target call?

include("${CMAKE_CURRENT_LIST_DIR}/${PN}Targets.cmake")

find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED)
set_property(TARGET ${PN}::pybind11 APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS})
Copy link
Member

Choose a reason for hiding this comment

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

This was quite confusing to me, but maybe I just don't understand cmake well enough. What is this pybind11::pybind11 interface library (and why is it, e.g., not just called pybind11 similar to the interface library name when the project is included via add_directory?). How come we are able to attach properties here even though there was no corresponding add_target call?

Copy link
Member

Choose a reason for hiding this comment

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

The pybind11Targets.cmake file defines the target (included 3 lines up). That file was created with the export command in the main CMakeLists.txt file and it just basically creates a copy of the pybind11 target from that CMakeLists.txt. The only different is that it attaches a namespace and marks the targets as IMPORTED.

@@ -0,0 +1,91 @@
# pybind11Config.cmake
Copy link
Member

Choose a reason for hiding this comment

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

pybind11Config.cmake and pybind11ConfigVersion.cmake should be added to .gitignore so that they don't show up when doing in-tree builds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added share/cmake/pybind11/pybind11*cmake, though that'll only catch the default install case. Hope that's right for you purposes.

@loriab
Copy link
Contributor Author

loriab commented Dec 12, 2016

PYBIND11_CMAKECONFIG_INSTALL_DIR shouldn't be INTERNAL if it's meant to be configurable -- it's going to be missing from the CMake GUI. Also, it should stay inside the if(PYBIND11_INSTALL) block since it's only relevant there.

Moved back inside install block. I'm not familiar with CACHE variables, but I switched to set(PYBIND11_CMAKECONFIG_INSTALL_DIR ... CACHE STRING ...)", which I think is what you and @KerstinKeller want. It allows me to default it and override it with -D.

For include(pybind11tools), if it works without moving the variables, even better. I just thought that was the reason for the previous redundant include(pybind11tools) calls. You should also remove the mention and example of include(pybind11tools) from the docs (compiling.rst), since find_package(pybind11) already includes everything that's needed.

I suspect the former redundant include(pybind11tools) was in case the the installation was performed by CMake 2.8.12, in which case there'd be no target, in which case Config never imported Tools. I've moved the include(pybind11tools) outside the version and target tests, so it should now always be available. And edited docs.

Overall, I still think that the proposed interface library somewhat dangerous. It misses important compilation flags like "/bigobj, /LTCG, /GL" on Windows and "-fvisibility=hidden" and "-flto" on Linux. Remember that this is a metatemplate library, which will lead to horribly bloated binaries unless these kinds of parameters are specified (http://pybind11.readthedocs.io/en/latest/faq.html#how-can-i-create-smaller-binaries discusses some of the reasons). I am very worried that people will start using the interface library and then forget to pass these extra parameters, leading to a bad user experience when using this library. Unless these flags can be incorporated, I would rather not add the interface library to the build system.

The current state of the PR really isn't allowing anything that can't already be done through add_subdirectory(pybind11)\n add_library(mycode ...)\n target_include_directories(pybind11/include/pybind11). And heavy binaries and less-than-best practices not withstanding, projects are using pybind11 satisfactorily without those important compilation flags. One thought (though I don't know much about this aspect), if projects want their pybind11-bound module to be linkable in the ordinary Linux shared library sense, I think the hidden visibility setting would interfere unless the desired exported symbols were carefully managed. I'm also not sure if adding the important compilation flags is consistent with the CMake docs:

Note that usage requirements are not designed as a way to make downstreams use particular COMPILE_OPTIONS or COMPILE_DEFINITIONS etc for convenience only. The contents of the properties must be requirements, not merely recommendations or convenience.

Nevertheless, I think the logic you want can be implemented for the interface library. To optimally avoid code repetition, the pybind11_add_module should be using the interface library. But that's impossible while still supporting CMake 2.8.12, which can't create an interface library. So the two are going to have to keep mirroring each other. Since it sounds like @dean0x7d has some plans for enhancing the MODULE/SHARED and lto flexibility of the function, I propose that the interface library stay as it is for now. Then when the function is updated, I'll see about mirroring it in the Config. I've added a warning to the docs about the target containing only build requirements, not recommendations.

Also did all the docs edits requested and not directly responded to above.

@wjakob
Copy link
Member

wjakob commented Dec 12, 2016

And heavy binaries and less-than-best practices not withstanding, projects are using pybind11 satisfactorily without those important compilation flags. One thought (though I don't know much about this aspect), if projects want their pybind11-bound module to be linkable in the ordinary Linux shared library sense, I think the hidden visibility setting would interfere unless the desired exported symbols were carefully managed. I'm also not sure if adding the important compilation flags is consistent with the CMake docs:

Hm, that's precisely what I am concerned about. Can we at least add a big fat warning sign to the documentation? Something like:

..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 FAQ (see :ref:`faq <faq:symhidden>`) contains an
    explanation on why these are needed.

One more very minor thing: for the .gitignore, what I meant was that the .cmake files are just dropped in to the main directory when doing an in-tree build. Can you apply the following patch to your PR?

diff --git a/.gitignore b/.gitignore
index 5982538..d421e92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,4 +33,4 @@ MANIFEST
 /cmake/
 .cache/
 sosize-*.txt
-share/cmake/pybind11/pybind11*cmake
+pybind11*cmake

@dean0x7d
Copy link
Member

.gitignore
+pybind11*cmake

I think that will also ignore pybind11Tools.cmake. Perhaps the following, although I haven't tried an in-source build:

pybind11Config*.cmake
pybind11Targets.cmake

@wjakob
Copy link
Member

wjakob commented Dec 13, 2016

Ok, it looks good to me now. This is a fairly big change to the build system -- any comments from others? (@jagerman, @aldanor, @SylvainCorlay)? Otherwise I'll merge it soon.

@wjakob
Copy link
Member

wjakob commented Dec 13, 2016

(thanks for incorporating all our change requests, @loriab!)

@SylvainCorlay
Copy link
Member

Ping @JohanMabille who was looking at cmake configuration and finder for xtensor lately.

@loriab
Copy link
Contributor Author

loriab commented Dec 13, 2016

(thanks for incorporating all our change requests, @loriab!)

You're welcome. Thanks for the thorough review; I've learned some useful testing patterns from @dean0x7d along the way.

Let me know if you'd like a rebase to master and if you'd like me to give it a new and shorter history (and remove the offending space).

@wjakob
Copy link
Member

wjakob commented Dec 13, 2016

It would be great if you could rebase onto master and crunch it down into a small number of commits. I was going to squash the PR, but that is perhaps a bit drastic.

@jagerman
Copy link
Member

I think it looks like a great addition.

One really minor comment: when rebasing/squashing, perhaps also reflow the text in compiling.rst for those of us who read the docs in an editor rather than a browser; some of it is pretty choppy right now.

@loriab
Copy link
Contributor Author

loriab commented Dec 13, 2016

Thanks. I've got the compiling.rst reflow locally; just want to see if other comments come through to combine with it.

@wjakob
Copy link
Member

wjakob commented Dec 13, 2016

Thanks. I've got the compiling.rst reflow locally; just want to see if other comments come through to combine with it.

None from me. With @jagerman and @dean0x7d having reviewed the change, I think we have enough of a quorum to merge this PR. Do you want to push that last bit?

@jagerman
Copy link
Member

I plan on converting a project to use this to load pybind11 externally rather than in a submodule and see how it goes sometime over the next couple of days. I don't think that's a reason to hold it up though--if I run into any major or minor issues we can always fix them with a follow-up PR.

@wjakob
Copy link
Member

wjakob commented Dec 13, 2016

Merged, thank you!

@wjakob wjakob merged commit f4b81b3 into pybind:master Dec 13, 2016
@loriab
Copy link
Contributor Author

loriab commented Dec 13, 2016

@jagerman I'd be interested to hear of any issues you have using the Config. On the slight chance it's helpful, the psi4 use is this, this, and this (though it's using Python detection circa the start, not the end, of this PR).

@wjakob Great, thank you!

@wjakob wjakob mentioned this pull request Dec 14, 2016
7 tasks
@KerstinKeller
Copy link

Thanks everyone (also for considering my request)! I am now able to install pybind11 to a destination of my choice and use it from there without any issues. Just works out of the box 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants