cppyy icon indicating copy to clipboard operation
cppyy copied to clipboard

Unable to wrap CMake based project

Open maichmueller opened this issue 2 years ago • 2 comments

Hi,

first of thanks for this library! It is exactly what I am looking for to wrap some of my (template) heavy code for easy use in python. Alas, I cannot get it to work with existing CMake based projects and hope I can get some help here on how to get this to work reliably.

Since the documentation on how to marry cppyy with a cmake project is a little sparse, I relied on Camille Scotts's template for doing just that with some minor adaptations (this template is the basis for some of the repos mentioned in the documentation that use cppyy).

To showcase my attempts I have setup an example repo using this template. I will try to explain this repo quickly...

My process aims to build a single shared lib cmake target fancy_shared which links against fmt and is then wrapped by cppyy. External dependencies are loaded with Conan, i.e fmt as a static lib. I provide two header files formatter.hpp and a collector header fancy.hpp which merely includes formatter.hpp. I provide a single cpp implementation file formatter.cpp. The cmake configuration shows:

CMakeLists.txt essential details
find_package(fmt REQUIRED)

add_library(
        fancy_shared
        SHARED
        ${PROJECT_SRC_DIR}/libfancy/impl/formatter.cpp
)
set_target_properties(
        fancy_shared
        PROPERTIES
        LINKER_LANGUAGE CXX
)
target_include_directories(
        fancy_shared
        PUBLIC
        ${PROJECT_SRC_DIR}/libfancy/include
)
target_link_libraries(
        fancy_shared
        PUBLIC
        fmt::fmt
)

set(
        BINDING_HEADERS
        formatter.hpp
        fancy.hpp
)
list(TRANSFORM BINDING_HEADERS PREPEND "${PROJECT_SRC_DIR}/libfancy/include/fancy")

cppyy_add_bindings(
        "fancy" "${PROJECT_VERSION}" "Michael Aichmueller" "[email protected]"
        LICENSE "MIT"
        LANGUAGE_STANDARD "20"
        SELECTION_XML ${CMAKE_SOURCE_DIR}/py/selection.xml
        INTERFACE_FILE ${CMAKE_SOURCE_DIR}/py/interface.hpp
        HEADERS ${BINDING_HEADERS}
        INCLUDE_DIRS #nothing here since linking against fancy_shared should propagate all relevant include directories
        LINK_LIBRARIES fancy_shared
        NAMESPACES fancy
        README_FILE README.md
)

Since the template relies on a conda env to find and configure all the cppyy dependencies I do that as well:

conda create -n cppyy python=3.10 python-clang
conda activate cppyy
pip install cppyy conan

The env list this builds on my system can be found here:

cppyy conda env
Name Version Build Channel
bzip2 1.0.8 h620ffc9_4
ca-certificates 2023.12.12 hca03da5_0
certifi 2023.11.17 pypi_0 pypi
charset-normalizer 3.3.2 pypi_0 pypi
colorama 0.4.6 pypi_0 pypi
conan 2.0.14 pypi_0 pypi
cppyy 3.1.2 pypi_0 pypi
cppyy-backend 1.15.2 pypi_0 pypi
cppyy-cling 6.30.0 pypi_0 pypi
cpycppyy 1.12.16 pypi_0 pypi
fasteners 0.19 pypi_0 pypi
idna 3.6 pypi_0 pypi
jinja2 3.1.2 pypi_0 pypi
libclang 14.0.6 default_h1b80db6_1
libclang13 14.0.6 default_h24352ff_1
libcxx 14.0.6 h848a8c0_0
libffi 3.4.4 hca03da5_0
libllvm14 14.0.6 h7ec7a93_3
markupsafe 2.1.3 pypi_0 pypi
ncurses 6.4 h313beb8_0
openssl 3.0.12 h1a28f6b_0
patch-ng 1.17.4 pypi_0 pypi
pip 23.3.1 py310hca03da5_0
python 3.10.13 hb885b13_0
python-clang 14.0.6 default_py310h2b98e1d_1
python-dateutil 2.8.2 pypi_0 pypi
pyyaml 6.0.1 pypi_0 pypi
readline 8.2 h1a28f6b_0
requests 2.31.0 pypi_0 pypi
setuptools 68.2.2 py310hca03da5_0
six 1.16.0 pypi_0 pypi
sqlite 3.41.2 h80987f9_0
tk 8.6.12 hb8d0fd4_0
tzdata 2023c h04d1e81_0
urllib3 1.26.18 pypi_0 pypi
wheel 0.41.2 py310hca03da5_0
xz 5.4.5 h80987f9_0
zlib 1.2.13 h5a0b063_0

I fought a long battle with this setup and using cmake-conan wrappers to call the conan install commands, only to realize that they interfere somehow with finding the necessary cppyy Packages. So I ended up creating a configure.bash script and build.bash script to do what their names suggest manually. Running the shell scripts works well, all cppyy libs are found! Unfortunately, this is where the build runs into the first obstacle:

build error log
╰─ ./build.bash                                                                                                                                                                         ─╯
[ 14%] Building CXX object CMakeFiles/fancy_shared.dir/src/libfancy/impl/formatter.cpp.o
[ 28%] Linking CXX shared library libfancy_shared.dylib
[ 28%] Built target fancy_shared
[ 42%] Generating fancy.cpp, fancy/libfancyCppyy.rootmap, fancy/libfancyCppyy_rdict.pcm
In file included from input_line_7:3:
/Users/maichmueller/GitHub/fanciful/py/interface.hpp:1:10: fatal error: 'fancy/fancy.hpp' file not found
#include "fancy/fancy.hpp"
         ^~~~~~~~~~~~~~~~~
Error: rootcling: compilation failure (./libfancyCppyy578970b8bd_dictUmbrella.h)
make[3]: *** [fancy.cpp] Error 1
make[2]: *** [CMakeFiles/fancyCppyy.dir/all] Error 2
make[1]: *** [CMakeFiles/wheel.dir/rule] Error 2
make: *** [wheel] Error 2

My first question is: Since fancy_shared builds correctly, all relevant header files are included in this target. Linking against a cmake target which includes directories publicly propagates these directories, so shouldn't the target fancyCppyy (which is configured by cppyy_add_bindings) receive this propagation here as well?

Even if I don't understand why it is necessary, I can simply provide these directories again to hopefully make it compile:

cppyy_add_bindings(
        ...
        INCLUDE_DIRS $<TARGET_PROPERTY:fancy_shared,INTERFACE_INCLUDE_DIRECTORIES> 
        LINK_LIBRARIES fancy_shared fmt::fmt
        ...
)

but to no avail either :( ...

build error log
[ 42%] Generating fancy.cpp, fancy/libfancyCppyy.rootmap, fancy/libfancyCppyy_rdict.pcm
In file included from input_line_7:3:
In file included from /Users/maichmueller/GitHub/fanciful/py/interface.hpp:1:
In file included from /Users/maichmueller/GitHub/fanciful/src/libfancy/include/fancy/fancy.hpp:3:
/Users/maichmueller/GitHub/fanciful/src/libfancy/include/fancy/formatter.hpp:4:10: fatal error: 'fmt/format.h' file not found
#include <fmt/format.h>
         ^~~~~~~~~~~~~~
Error: rootcling: compilation failure (./libfancyCppyyab5489b690_dictUmbrella.h)
/bin/sh: /Users/maichmueller/.conan2/p/b/fmtd0efce1662886/p/include: is a directory

fmt is not found by libfancyCppyy although libfancy finds it. Yet, also

cppyy_add_bindings(
        ...
        INCLUDE_DIRS $<TARGET_PROPERTY:fancy_shared,INTERFACE_INCLUDE_DIRECTORIES> $<TARGET_PROPERTY:fmt::fmt,INTERFACE_INCLUDE_DIRECTORIES> 
        ...
)

does not change the error message of not finding the format headers either. Changing to Ninja as generator also reveals clearly that both the fmt include directory as well as the fancy include dirs are definitely included in the compilation call:

Ninja build error log
[1/4] Generating fancy.cpp, fancy/libfancyCppyy.rootmap, fancy/libfancyCppyy_rdict.pcm
FAILED: fancy.cpp fancy/libfancyCppyy.rootmap fancy/libfancyCppyy_rdict.pcm /Users/maichmueller/GitHub/fanciful/cmake-build-release-on-build/fancy.cpp /Users/maichmueller/GitHub/fanciful/cmake-build-release-on-build/fancy/libfancyCppyy.rootmap /Users/maichmueller/GitHub/fanciful/cmake-build-release-on-build/fancy/libfancyCppyy_rdict.pcm 
cd /Users/maichmueller/GitHub/fanciful/cmake-build-release-on-build/fancy && /Users/maichmueller/miniconda3/envs/cppyy/bin/genreflex /Users/maichmueller/GitHub/fanciful/py/interface.hpp --selection=/Users/maichmueller/GitHub/fanciful/py/selection.xml -o /Users/maichmueller/GitHub/fanciful/cmake-build-release-on-build/fancy.cpp --rootmap=/Users/maichmueller/GitHub/fanciful/cmake-build-release-on-build/fancy/libfancyCppyy.rootmap --rootmap-lib=libfancyCppyy.dylib -l libfancyCppyy.dylib -I/Users/maichmueller/GitHub/fanciful/src/libfancy/include;/Users/maichmueller/.conan2/p/b/fmtd0efce1662886/p/include --cxxflags -std=c++20
In file included from input_line_7:3:
In file included from /Users/maichmueller/GitHub/fanciful/py/interface.hpp:1:
In file included from /Users/maichmueller/GitHub/fanciful/src/libfancy/include/fancy/fancy.hpp:3:
/Users/maichmueller/GitHub/fanciful/src/libfancy/include/fancy/formatter.hpp:5:10: fatal error: 'fmt/format.h' file not found
#include <fmt/format.h>
         ^~~~~~~~~~~~~~
Error: rootcling: compilation failure (./libfancyCppyybe23cbab64_dictUmbrella.h)
/bin/sh: /Users/maichmueller/.conan2/p/b/fmtd0efce1662886/p/include: is a directory

Notice the -I/Users/maichmueller/GitHub/fanciful/src/libfancy/include;/Users/maichmueller/.conan2/p/b/fmtd0efce1662886/p/include.

The last line, rootcling 's complaint that the include directory of fmt is - in fact - a directory, is also mysterious to me. This is where I am lost without more insight into how rootlcing operates.

This all is happening on Apple Silicon M2 using macos 14.1. However, I am running into the same (and more) trouble on an amd64 linux setup with the same process.

Any help on what I am doing wrong would be greatly appreciated!

maichmueller avatar Dec 20 '23 13:12 maichmueller

I tested out some more configurations and noticed that if I completely remove fmt from the repo, then libfancyCppyy will compile, but the linker even throws errors:

[0/2] Re-checking globbed directories...
[3/7] Generating fancy/fancy.map
CRITICAL: While parsing: /Users/maichmueller/GitHub/fanciful/src/libfancy/include/fancy/formatter.hpp:3[10] 'vector' file not found
[4/7] Generating fancy.cpp, fancy/libfancyCppyy.rootmap, fancy/libfancyCppyy_rdict.pcm
Warning: Unused class rule: fancy::TemplateVectorFormatter<*>
[6/7] Linking CXX shared library fancy/libfancyCppyy.dylib
FAILED: fancy/libfancyCppyy.dylib 
: && /Library/Developer/CommandLineTools/usr/bin/c++ -stdlib=libc++ -O3 -DNDEBUG -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -dynamiclib -Wl,-headerpad_max_install_names  -o fancy/libfancyCppyy.dylib -install_name @rpath/libfancyCppyy.dylib CMakeFiles/fancyCppyy.dir/fancy.cpp.o  -Wl,-rpath,/Users/maichmueller/miniconda3/envs/cppyy/lib/python3.10/site-packages/cppyy_backend/lib -Wl,-rpath,/Users/maichmueller/GitHub/fanciful/cmake-build-release-on-build  /Users/maichmueller/miniconda3/envs/cppyy/lib/python3.10/site-packages/cppyy_backend/lib/libCling.so  libfancy_shared.dylib && :
ld: Undefined symbols:
  CppyyLegacy::TVersionCheck::TVersionCheck(int), referenced from:
      __GLOBAL__sub_I_fancy.cpp in fancy.cpp.o
  CppyyLegacy::TGenericClassInfo::SetDestructor(void (*)(void*)), referenced from:
      CppyyLegacy::GenerateInitInstanceLocal(fancy::VectorFormatter const*) in fancy.cpp.o
  CppyyLegacy::TGenericClassInfo::SetDeleteArray(void (*)(void*)), referenced from:
      CppyyLegacy::GenerateInitInstanceLocal(fancy::VectorFormatter const*) in fancy.cpp.o
  CppyyLegacy::TGenericClassInfo::GetClass(), referenced from:
      CppyyLegacy::fancycLcLVectorFormatter_Dictionary() in fancy.cpp.o
  CppyyLegacy::TGenericClassInfo::SetDelete(void (*)(void*)), referenced from:
      CppyyLegacy::GenerateInitInstanceLocal(fancy::VectorFormatter const*) in fancy.cpp.o
  CppyyLegacy::TGenericClassInfo::TGenericClassInfo(char const*, char const*, int, std::type_info const&, CppyyLegacy::Internal::TInitBehavior const*, CppyyLegacy::TClass* (*)(), CppyyLegacy::TVirtualIsAProxy*, int, int), referenced from:
      CppyyLegacy::GenerateInitInstanceLocal(fancy::VectorFormatter const*) in fancy.cpp.o
  CppyyLegacy::TGenericClassInfo::~TGenericClassInfo(), referenced from:
      CppyyLegacy::GenerateInitInstanceLocal(fancy::VectorFormatter const*) in fancy.cpp.o
  CppyyLegacy::TROOT::RegisterModule(char const*, char const**, char const**, char const*, char const*, void (*)(), std::__1::vector<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, int>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, int>>> const&, char const**, bool), referenced from:
      (anonymous namespace)::TriggerDictionaryInitialization_libfancyCppyy_Impl() in fancy.cpp.o
  CppyyLegacy::Internal::DefineBehavior(void*, void*), referenced from:
      CppyyLegacy::GenerateInitInstanceLocal(fancy::VectorFormatter const*) in fancy.cpp.o
  CppyyLegacy::TIsAProxy::TIsAProxy(std::type_info const&), referenced from:
      CppyyLegacy::GenerateInitInstanceLocal(fancy::VectorFormatter const*) in fancy.cpp.o
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.

I don't know where these symbols are normally found. I cannot figure out what is still broken exactly in this setup...

maichmueller avatar Dec 23 '23 18:12 maichmueller

Since the documentation on how to marry cppyy with a cmake project is a little sparse,

Yes, the cmake based code was contributed and I'm not familiar enough with cmake to clean up some of its rough edges. It also generates modules that force bindings to most C++ entities, mostly for the benefit of dir() and tab-completion (although cppyy's support for both has significantly improved since), which I consider wasteful. Additionally, to create the modules to force those bindings, libclang is used to parse the headers, adding that additional dependency.

Although NWChemEx does not currently use cppyy anymore, it's cmake files off a better start: https://github.com/NWChemEx/NWXCMake/tree/master/cmake

cppyy conda env

Interesting, I wasn't aware that the conda folks had updated to 3.1.2. I thought they had remaining issues, but apparently those are for ARM and PPC.

build error log

Can you run with VERBOSE=1 to see the full rootcling command? I suspect that it, too, needs to have the include headers handed to it (using -I).

The last set of symbols are defined in the C++ code that rootcling generates. Here, too, it would be useful to run with VERSBOSE=1 to see the full linker command.

wlav avatar Jan 09 '24 19:01 wlav