wlav / cppyy

Other
404 stars 42 forks source link

Unable to wrap CMake based project #206

Open maichmueller opened 9 months ago

maichmueller commented 9 months ago

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" "m.aichmueller@gmail.com" 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 ^~~~~~~~~~~~~~ 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 ^~~~~~~~~~~~~~ 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 commented 9 months ago

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

wlav commented 8 months ago

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.