Support exported CMake targets
This is a proposal to support CMake targets for linking Charm++, as opposed to using the charmc compiler wrapper.
In our numerical relativity code SpECTRE (https://github.com/sxs-collaboration/spectre) that is based on Charm++ we are currently attempting to link Charm++ as a CMake target, in order to make our build system more robust. This is similar to how MPI is linked in modern CMake build systems:
find_package(MPI REQUIRED)
target_link_libraries(MyExecutable PRIVATE MPI::MPI_CXX)
So instead of using the MPI compiler wrappers, the CMake target MPI::MPI_CXX defines all the include directories, library paths, compiler flags etc. and can be linked into executables that need it. The MPI::MPI_CXX target is defined by CMake's native FindMPI module as an "imported target". For SpECTRE we are currently writing a FindCharm module that defines a Charmxx::charmxx imported target, using the extremely helpful charmc -print-building-blocks feature (see https://github.com/sxs-collaboration/spectre/pull/2680). Then, we link Charm++ like this:
find_package(Charm 6.10.2 EXACT REQUIRED COMPONENTS EveryLB ScotchLB)
target_link_library(MyExecutable PRIVATE Charmxx::charmxx)
This is working quite well, but it would be even more robust if Charm++ would define the target and export it, using the build-system information it has natively. CMake explains imported and exported targets in their documentation:
I believe a CMake integration based on exported targets would make it significantly easier to build executables on top of Charm++. Since Charm++ has been moving to a CMake-based build system recently, the foundation for this feature is already there. If you agree with my proposal, I would be happy to help if I can.
Hi @nilsleiffischer, this is a great idea! If you would like, please feel free to open a PR to Charm++ with the FindCharm module and we'd be happy to add it.
Does #3547 / #3548 go in the right direction?
Hi @nilsleiffischer, For us to not rely on charmc, additional functionalities are required (something akin to add_charm_executable as opposed to add_executable). But the recent additions will allow users to find charm++ using find_package(Charm) within CMake and set the CMAKE_CXX_COMPILER to charmc. It works well and we're using it to build charmlite. To use Charm++, the user requires to append CMAKE_PREFIX_PATH with the installation (or build) directory of Charm++. The users will also have access to the flags set by Charm++ in its build system to better assist with the flags required for the testing/benchmarking framework.
I believe it is a step in the right direction. It will require tinkering with the whole build system but if there's enough support for it - I can implement linking to charm++ as opposed to utilizing charmc in general.
@matthiasdiener @NK-Nikunj This is excellent, thank you so much for your efforts in this direction! Here are a few things I noticed:
- I recommend you use CMake's
configure_package_config_fileandwrite_basic_package_version_file. See:- https://cmake.org/cmake/help/git-stage/guide/importing-exporting/index.html#creating-packages
- https://cmake.org/cmake/help/git-stage/module/CMakePackageConfigHelpers.html
- I recommend you use exported CMake targets for everything like include directories, linked libs, etc. The keywords
PRIVATE,PUBLICandINTERFACEhelp control which properties propagate to the exported targets. See:- https://cmake.org/cmake/help/git-stage/guide/importing-exporting/index.html#exporting-targets
- Dependencies like MPI, Scotch etc can be found in the CMake config file and linked to the exported targets.
- In the
FindCharmmodule that I'm writing (https://github.com/sxs-collaboration/spectre/pull/2680) I define the targetsCharm::charm(all libs),Charm::pup(only PUP serialization) andCharm::main(for linking executables with amainfunction). Linking withCharm::charmis enough for things like our Python bindings and our parallelization libraries that use Charm++. More basic libraries that define data structures etc only need to link withCharm::pup. Executables link withCharm::main. Then, to eliminatecharmcas the compiler wrapper only a few extra things are necessary for executables:- I generate
moduleinitfiles by linking executables to a custom targetCharmModuleInit, which invokescharmcwith some dummy flags. - We have an
add_charm_moduleCMake function that generates${MODULE}.decl.hand${MODULE}.def.hfiles from.cifiles by invokingcharmc.
- I generate
All of this is just to give you an idea how we are currently handling things. Feel free to pick and choose what works best for you!
I had a very similar implementation detail in mind wrt targets and exports. BTW, we also added the --install-prefix to install charm++ instead of building in the source. We're adding features to the build system in Charm++ as we progress with Charmlite and require cmake flags/features from Charm++.
The methods you suggested require a fair bit of code reinventions given the tight coupling in our build scripts but it's certainly achievable.
Sounds great @NK-Nikunj! I intend to test our build system with our FindCharm module for a while, and then see if we can contribute some of the features upstream to Charm++, coordinating with you of course.
@NK-Nikunj I looked at the charmlite repository a bit. Do I understand correctly that it is a more C++-native interface to Charm++, that is based on templates rather than .ci files, and that eliminating charmc in favor of the standard cmake compiler aligns well with the project? I think it looks super useful already, and personally I would love to see that sort of thing migrate to upstream Charm++ at some point, if this is something you are considering at all :)
@nilsleiffischer Yes, charmlite is experimental work on top of converse that relies on template metaprogramming instead of .ci files. We do use charmc as the compiler (see this) but it can easily be switched to use default cmake compilers and targets once we have updated Charm++'s build system.
Unfortunately, charmlite doesn't support most features yet and is only for research purposes to highlight the overheads within charm++. @jszaday should be able to elucidate more on what goes back into charm++.