Zisa
|
Here we briefly describe features of CMake directly relevant to Zisa.
The most simple and often practical way of using CMake is on the command line to generate Make files. This can be done by
$ cmake ${MANY_FLAGS} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -B ${BUILD_DIR}
which will set up everything in the folder ${BUILD_DIR}
mostly called build
and virtually never .
. Nothing stops you from having several build folders each using either different dependencies or compilers.
In order to actually compile one can type
$ cmake --build ${BUILD_DIR} --parallel $(nproc)
Finally, it pays to check if the compilation commands correspond to something meaningful. Which is achieved by make VERBOSE=1
.
Equipped with this knowledge using CMake revolves around finding the correct value for ${MANY_FLAGS}
, see Project specific flags for help with this problem.
The convincing argument for not globbing source files is that if one switches Git branches files might appear or vanish; and if CMakeLists.txt
does not change (very likely), then cmake
won't be run again, resulting in a misconfigured build.
Obviously, adding the files manually is unacceptable. However, nothing is stopping us from have a script add them manually for us. This script is called bin/update_cmake.py
. For every subfolder it simply lists the source files contained in that folder in a new CMakeLists.txt
located in that subfolder. A simple add_subdirectory
pulls in the files.
Personally, when facing strange issues with CMake, I think it helps to point CMake to a fresh location, either by changing the ${BUILD_DIR}
or by deleting it.
There are now two ways of finding dependencies. Either the library provides a XYZConfig.cmake
which contains all the details about the targets, i.e. the new way. Alternatively, someone can write a FindXYZ.cmake
module which contains all the details about how to use the library. Where does CMake look to find these things?
CMAKE_PREFIX_PATH
this is where CMake looks for XYZConfig.cmake
.CMAKE_MODULE_PATH
this is where CMake looks for FindXYZ.cmake
.Well, there and the standard OS dependent locations.
CMake compiles and installs with relative ease. Therefore, if the cluster doesn't support a modern version, it's easy to install a modern version locally. The Zisa libraries contain a script which automates all steps involved.
As of 3.18
CUDA works reasonably well with CMake. To use it, we add the language CUDA
to the project. This unfortunately must also be done in every project using a CUDA dependency. Next we find CUDA
by
find_package(CUDAToolkit REQUIRED)
which provides the target CUDA::cudart
and many more. The current situation isn't without quirks:
MPI
we found that CMP0105
must be set to OLD
. Since otherwise liker related flags are passed to the compiler incorrectly.target_compile_options
. In particular, flags that should be passed down to the C++/host compiler should be defined as TARGET
should be replaced by the target, SCOPE
should be one of INTERFACE
, PRIVATE
or PUBLIC
; and FLAG
be replaced by the desired flag. Analogously, one should guard flags that are to be passed only to nvcc
but not the host compiler, e.g., --expt-relaxed-constexpr
and vice-versa.CMake has a templating system, to generate files with certain parts substituted when the project is built/installed. For our purposes it pretty simple, any CMake variable surrounded by @
will be replaced by its value. To create the configured file, we must call
It can be very useful to refer to certain targets by two names, one with the namespace and one without, e.g., memory
and Zisa::memory
. This is possible through
This creates the symmetry required to incorporate Zisa libraries either directly via add_subdirectory
or as an external dependency via find_package
.
In modern CMake a library is installed along with a *Config.cmake
file which describes the targets and their peculiarities. The definition of the targets, etc. can be done automatically by CMake, e.g., we tell CMake to generate the file for us called ZisaTargets.cmake
. This is not the ZisaConfig.cmake
that CMake will be looking for. Let's look at that file next. It contains the line
which includes the autogenerated file. If the library has no dependencies, we could simply install the ZisaConfig.cmake
. Job done.
When dependencies come into play more needs to be done. The first thing you'll notice about this solution, is that the dependencies must be found again in every project using our library. This feels redundant. The second issue we have in Zisa is that we have many optional dependencies. Therefore, this system makes it easy to forget say a -DZISA_HAS_HDF5=1
in some project using ZisaMemory. This results in a broken build. Therefore, there are two more problems to be solved
The first problem is "solved" by repeating the logic w.r.t. find_package
in the ZisaConfig.cmake
making sure to replace find_package
with find_dependency
just incase someone has an optional dependency on us. The second problem is solved by moving ZisaConfig.cmake
to ZisaConfig.cmake.in
and using configure_file
to make the relevant flags persistent. Please look at some of the cmake/Zisa*Config.cmake.in
. In particular, the one in ZisaCore
is relevant.
We're almost there. What if we have a dependency which can't be found by CMake out of the box? Typically, we'll also distribute a FindXYZ.cmake
. Let's make things concrete. In Zisa this is the case for in ZisaMemory with NetCDF. We distribute a find module for NetCDF written for VTK. Now, if anyone where to use ZisaMemory, the find_dependency
would fail because it can't find NetCDF, because VTK's FindNetCDF.cmake
isn't present. Therefore, we must also install any FindXYZ.cmake
that we rely on.
Note: This solution is currently working smoothly, but hasn't been tested well enough to make a definite statement. To me it feels very error prone to force the user of our library to call find_dependency
. If you're mimicking the strategy here, consider adding a flag which can turn off the find_dependency
for each of you external dependencies. If you're using Zisa and have exactly this problem. Please report it as an issue.