Build with CMake
Background
CMake is a build system generator supporting multiple build systems and programming languages, specifically Fortran is a first-class citizen there, allowing, for example, out-of-the-box handling of the inter-module dependencies. A build system generator there means that description of the build procedure written in the CMake-script language is used by the cmake tool to generate the actual build system, for example using Unix Makefiles or Ninja generator. Thus, all modifications should be performed on the CMake-script level and not within the generated build system as these changes will be overwritten when re-running cmake at some point.
Why providing yet another alternative for building HARMONIE-AROME? Well, makeup does a very good job building the system, however it's an in-house solution which has a number of limitations:
makeupis an in-house build system, so there are components that require more maintenance compared to a standardized build toolmakeupuses a considerable number of sequential steps, which increase the total build time- the
configurestep takes quite some time, although in some cases it can be skipped, but users have to remember when they must re-runconfigureand this dependency is not enforced bymakeup - not all the dependencies are tracked by
makeup, for example updating configure files does not trigger a re-build
In an attempt to fix these limitation of makeup, CMake was chosen as an alternative. CMake has a mature Fortran support and improves upon some shortcomings of makeup with little effort (well, it obviously has its own fair share of quirks, but that's a different story...). Additionally, using CMake allows us to enforce usage requirements and dependencies between different components of HARMONIE-AROME, for example, it's a good idea to ensure that SURFEX routines do not directly call cloud microphysics functions. Currently makeup does not enforce these boundaries and this task is left to the developers who implement the new code. Of course, something like this can also be implemented with makeup, but it would require considerable development efforts.
Getting started with CMake
Selecting the CMake-based build system when installing HARMONIE-AROME
If all the config files are available, building HARMONIE-AROME with CMake should be as simple as setting the BUILD_WITH variable when invoking Harmonie:
config-sh/Harmonie install BUILD_WITH=cmakeor alternatively, setting the desired option in ecf/config_exp.h.
Building HARMONIE-AROME with CMake from the command line
Sometimes calling Harmonie install is not the best choice and one might want to compile the code from the command line. In this case compilation of HARMONIE-AROME with CMake consists of three individual steps:
- compiling the auxiliary libraries (
gribexand such) - compiling the main code of HARMONIE-AROME
- optionally, compile some additional tools (for example,
gl)
1. Compiling the auxiliary libraries
This step is rather straightforward, assuming that HARMONIE-AROME code is located under the path stored in the HM_LIB environment variable one can adapt the following snippet to compile all the required libraries:
CMAKE_FLAGS="-DCONFIG_FILE=<path to your JSON config>"
INSTALL_DIR="<directory where the auxiliary libraries should be installed>"
AUX_LIBS='bufr_405 gribex_370 rgb_001 dummies_006/mpidummy'
for project in $AUX_LIBS; do
echo "Compiling $project"
current_project_dir=$HM_LIB/util/auxlibs/$project
current_build_dir="build-`echo $project | sed 's|/|-|g'`"
mkdir -p $current_build_dir && cd $current_build_dir
# CMake build type can be changed to Debug, if needed
cmake $current_project_dir -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR $CMAKE_FLAGS
# Here -j tells CMake how many parallel compilation processes to use
cmake --build . --target install -j16
cd ..
doneIf a specific config file is not there, you can try your luck with using generic config files provided for different compiler types. To do so, just drop the -DCONFIG_FILE from the list of CMake command line arguments and CMake will try to load a suitable configuration file, if available.
2. Compiling the main code of HARMONIE-AROME
Following the procedure described in the previous step, one can use a similar approach to compile the main code (here, one of the generic configuration files is used, of course it can be replaced with a different one or dropped but it should be the same config file which was used to compile auxiliary libraries):
mkdir build && cd build
# Configure and generate the build system
cmake $HM_LIB/src \
-G Ninja # Use Ninja to build HARMONIE-AROME, drop to build with Makefiles
-DCMAKE_BUILD_TYPE=Release \
-DCONFIG_FILE=$HM_LIB/util/util/cmake/config.GNU.cmake \
-Dbufr_DIR=$INSTALL_DIR/lib/cmake/bufr \
-Dgribex_DIR=$INSTALL_DIR/lib/cmake/gribex \
-Drgb_DIR=$INSTALL_DIR/lib/cmake/rgb \
-DCMAKE_INSTALL_PREFIX=$INSTALL_DIR
# Build and install HARMONIE-AROME
cmake --build . --target install -j16Obviously, when compiling from command line, additional command line arguments might be provided to CMake at the configure step as needed. However, a preferred solution is to use a configuration file to handle as much of the machine-specific details as possible.
3. Compiling the tools
The approach is the same as with the main code, however, you might want to add -Dharmonie_DIR=$INSTALL_DIR/lib/cmake/harmonie if the tool in question needs HARMONIE-AROME libraries for compilation.
Configuration files
Configuration files, similarly to makeup, are used to provide compilation flags, define external libraries to use when compiling the code et cetera. Thus, having a correct configuration file is one of the key elements of successful building HARMONIE-AROME. The CMake-based build system of HARMONIE-AROME uses configuration files written in JSON format. JSON was chosen to make these files more declarative and, hopefully, easier to maintain and modify than plain CMake-script-based files would be.
The main config file, which is used to build auxiliary libraries and the main HARMONIE-AROME code should be placed under util/cmake/config directory. This file has a following top-level structure:
{
"build_tools":[],
"dependencies":[],
"programs":[],
"configure":{},
"compile":[],
"compile_single":[],
"compile_double":[],
"custom_compile":{},
"link":[]
}there all the sections except configure, custom_compile and link are mandatory. In the following a detailed description of all the config file section is provided.
The build_tools section
This section lists the external tools required for compiling HARMONIE-AROME, excluding compilers. Currently, this section should always contain the two following entries: FLEX and BISON, but in future this list might be extended. So, currently this section is always defined as:
"build_tools":["BISON", "FLEX"]The dependencies section
This section provides a list of external (external here means "not found within the src directory of HARMONIE-AROME", so, for example, gribex is also an external library for CMake build) libraries required to compile and link HARMONIE-AROME code. Since finding a correct library can be a tricky task, this section allows a number of options for specifying external dependencies:
You can completely rely on CMake and delegate it all the work for finding a dependency. In this case, a dependency is added as a simple string to the
dependenciessection, for example:"dependencies":["OpenMP", "LAPACK"]This option is for packages like
OpenMPwhich do not involve finding libraries located in unusual places as often happens when using environment modules.You can still rely on CMake to find the package, but provide a bit of detail on how to find it. In this case a dependency is added as a JSON object of the following form (using the
NetCDFlibrary as an example):{ "pkg":"NetCDF", "use_cmake_config":false, "components":["C","Fortran"], "hints":["$ENV{NETCDF_DIR}","$ENV{NETCDF_F_DIR}"], "cmake":{"NETCDF_USE_DEFAULT_PATHS":true} }There the
use_cmake_configfield tells CMake which mechanism it should use to find the library in question. Whenuse_cmake_configis set totrueCMake will look for CMake configuration files installed with the library, which is a recommended option in modern CMake. Even though it's a recommended by the CMake authors option, not all the libraries provide CMake configuration files so just settinguse_cmake_configtotruedoes not work all the time (at least it works for the auxiliary libraries compiled with CMake). You might want to provide a-D<package name>_DIR=<path to CMake config files>as an argument to thecmakecommand when configuring the build if CMake fails to find a package.Another alternative is setting
use_cmake_configtofalse, then CMake will try to find the required dependency using the hand-written scripts provided by the authors of CMake (or found under theutil/cmakedirectory of HARMONIE-AROME). These scripts usually do quite some work trying to find a dependency and sometimes fail even if library is there, for example when it's located in a very unusual place or has an unexpectedpkg-configname.When using
"use_cmake_config":falseone may add acomponentslist, if only a language-specific version of the dependency is wanted. For example, having:{"pkg":"NetCDF", "use_cmake_config":false, "components":["C"]}CMake would not try to find the Fortran version of NetCDF library, which can be useful sometimes. Use this option of defining external dependencies for such libraries as MPI, which can have multiple vendors and subtle differences between libraries provided (for example CMake should be able to figure out the correct MPI libraries for both MPICH and Open-MPI).
The
hintslist tells CMake which directories it should check when looking for a library.Note Elements of the
hintslist are simply added to the<PackageName>_ROOTCMake variable. If CMake'sfind_package(<PackageName>)does not use this variable providinghintswould have no effect.Finally, the
cmakesection provides a key-value set of elements, which will be converted to corresponding CMake variables set before callingfind_package(<PackageName>). Thus, it can be used to control the behaviour offind_package.When nothing of the above works, you can provide all the flags manually. To do so, use the following form for a dependency entry:
{ "pkg":"HDF5", "raw_lib":{ "include":"$ENV{HDF5_DIR}/include", "lib_directory":"$ENV{HDF5_DIR}/lib", "lib":["-lhdf5hl_fortran", "-lhdf5_fortran", "-lhdf5_hl", "-lhdf5"] } }where the
raw_libcomponent provides all the needed include and link directories as well as the link libraries. If some some fields of theraw_libobject are unneeded they can be set tonull:{"pkg":"rt", "raw_lib":{"include":null, "lib_directory":null, "lib":"-lrt"}}Note that all the members of the
raw_libobject can be defined as lists:{ "pkg":"HDF5", "raw_lib":{ "include":["$ENV{HDF5_DIR}/include","$ENV{HDF5_DIR}/include_fortran"], "lib_directory":["$ENV{HDF5_DIR}/lib","$ENV{HDF5_DIR}/lib64"], "lib":["-lhdf5hl_fortran", "-lhdf5_fortran", "-lhdf5_hl", "-lhdf5"] } }When providing the required libraries in the
libsection one can skip the-lprefix, thus having"lib":"-lrt"and"lib":"rt"would have the same effect.Sometimes it can be useful to define a dummy library in CMake without actually looking for the library files, for example when compiling a tool which uses only a subset of HARMONIE-AROME libraries. When loading HARMONIE-AROME as a CMake package all the targets associated with external dependencies should be present, but some of these dependencies might be not needed for successful linking (or these are added implicitly by the programming environment and adding them for the second time in CMake won't make any difference). In this case you can use the following:
{"pkg":"gribex", "dummy":true}
The programs section
This section provides a list of HARMONIE-AROME programs to build (excluding MASTERODB which is always built by the CMake build system), for example:
"programs":["BATOR", "oulan", "ioassign", "LSMIX"]CMake will try to find the corresponding Fortran source files and will complain if unable to do so. Currently it is not possible to explicitly tell CMake via JSON config which program should be compiled from which source file. If CMake is unable to figure out how to compile a program the CMake-code should be altered to tell it how to do so.
The configure section
This section provides various configure-time flags controlling the build system or selecting features. Currently in the main HARMONIE-AROME config file this section is defined as follows:
"configure":{
"use_flexfix":true
, "precision":"double"
},There the use_flexfix option controls the usage of the flexfix wrapper, set it to true to use the flexfix wrapper when generating lexers for the Blacklist and ODB compilers. Having use_flexfix as false results in using the flex tool directly. The precision option controls the floating point precision of the build, with possible values of double and single. This is a mandatory option, removing it would result in a CMake fatal error at the configure time.
The configure file for gl has the following options in the configure section:
"configure":{
"use_aladin":true
, "use_netcdf":true
, "check_preferlocalconcepts_bug":true
}Set use_aladin to true to compile with FA support (requires HARMONIE-AROME libraries). Set use_netcdf to true to enable NetCDF support in gl. Set check_preferlocalconcepts_bug to true to perform a configure-time auto-detection test checking whether the supplied eccodes version is affected by the preferLocalConcepts bug. This test can be skipped, although in such a case corresponding CPP definitions should be manually added to the config file if a 'bad' eccodes version is used.
If an option is removed from the configure section it will be treated by CMake as set to false in case of boolean flags or empty string for string options.
Adding a new configure option
There's no predefined list of configure section members of CMake JSON config, any element found in this section will be available as CONFIG_<option name> from CMake code. For example having:
"configure":{
"use_flexfix":true
, "precision":"double"
, "new_flag":true
},would define the following variables available in CMake scripts (after call to the hm_get_json_configure_options function): CONFIG_USE_FLEXFIX, CONFIG_PRECISION and CONFIG_NEW_FLAG. How these newly introduced options are used when configuring the build is up to the developer.
The compile section
This section defines compiler flags and preprocessor definitions which should be used when compiling various components of HARMONIE-AROME. Generally this section should consists of a list of objects with the following structure where the first of these objects should always have the project name set to null:
{
"project":null
, "flags_fortran":{"any":[], "debug":[], "release":[]}
, "flags_c":[]
, "defs_fortran":[]
, "defs_c":[]
, "exclusive_defs":true
, "exclusive_flags":true
},Individual members of these objects are defined as follows:
projectis the name of the CMake target for which the provided setting should be applied. CMake matches this field against the names of its targets, which means that having, for example,"project":"surf"would apply the compilation flags for all CMake targets namedsurfor with names starting withsurf-, as insurf-module. When CMake finds a suitable project-specific section it will append project-specific compilation flags to the global compilation flags for the project in question (if not explicitly asked to not use global flags). If there's no suitable section with project-specific options, only the global options will be used.flags_fortrandefine compiler flags to be used when compiling Fortran source files. Flags can be defined in three different ways: as a string (e.g.,"flags_fortran":"-std=f2003"), as a list (e.g.,"flags_fortran":["-std=f2003", "-Wall"]) or as an object. Providing the compiler flags as a JSON object allows for a fine-grained control applying different flags for different build configurations (the first two forms will apply the same flags for any build configuration). This object contains three subsections:any,debugandrelease. Compiler flags put under theanysection will be used for any build type, while flags underdebugandreleasewill be used only whenCMAKE_BUILD_TYPEis set toDebugorRelease, respectively. Thus if having, for example,"flags_fortran":{"any":["-std=f2003"], "debug":["-O0"], "release":["-O2"]}Fortran sources will be compiled using-std=f2003 -O0whenCMAKE_BUILD_TYPE=Debugand-std=f2003 -O2whenCMAKE_BUILD_TYPE=Release.Note Technically, the compiler flags there are not limited to the
debugandreleaseconfigurations. CMake allows defining customCMAKE_BUILD_TYPEand it will look for a correspondingly named element under theflags_fortranobject. However, this feature is not used in the current version of CMake-based build system for HARMONIE-AROMEflags_csame asflags_fortranbut for compiling C code.defs_fortrandefined in the same way asflags_fortran(allowing the same three forms: a string, a list or an object) but contains preprocessor definitions.defs_csame asdefs_fortranbut for the C code.exclusive_defsandexclusive_flagsflags. In some situations it can be useful to ignore the global compilation flags defined by"project":nulland use only project-specific flags or preprocessor definitions. To do so, addexclusive_defsand/orexclusive_flagsto the project-specific element of thecompilesection, for example having:{ "project":null , "flags_fortran":"-Wall" }, { "project":"gribex" , "exclusive_defs":true , "exclusive_flags":true , "flags_fortran":"-O0" }, { "project":"surfex" , "flags_fortran":"-O0" }will result in having
gribexto be always compiled with just-O0without using any other flags or preprocessor definitions, butsurfexwill be compiled with-Wall -O0.
The compile_single and compile_double sections
These sections repeat the structure of the compile section. Compiler flags defined by these sections are appended to the flags defined in the compile section of the config based on the value of the value of the precision flag from the configure section.
Do not add auto-double flags (e.g., GNU Fortran's -fdefault-real-8) there, they are handled differently.
The custom_compile section
Sometimes it might be desirable to add some specific compile flags for a single source file. This can be achieved by using the custom_compile section of JSON config as follows:
"custom_compile":{
"sufpf.F90":["-O0", "-Wextra"]
},Unlike makeup, compilation flags found in the custom_compile are always appended to the list of compiler flags for the specified source file and there's no option to replace them.
When using the Unix Makefiles generator, per-source compile flags are set on the target level. Thus, for example, adding (or updating) a custom compile flag for a source file within arpifs will result in recompilation of the whole arpifs project. The Ninja generator does not have such limitation, and only the source file in question will be recompiled (possibly triggering a recompilation cascade if other Fortran sources depend on it).
The link section
This section provides a list of linker flags to be used when linking HARMONIE-AROME executables, for example:
"link":["LINKER:-export-dynamic,--as-needed", "-rdynamic"]All flags that should be passed directly to ld can be grouped in strings of the following form "LINKER:-option1,-option2" and CMake will figure out how to pass them to the linker.
Using environment variables in JSON configuration files
Current version of CMake JSON config implemented in HARMONIE-AROME allows using environment variables as $ENV{VARIABLE} in the following contexts:
- string-valued members of the
configuresection, e.g.,"configure":{"precision":"$ENV{VARIABLE}"} - any elements of JSON config which allow a list of strings as one of the possible values, for example:
"link":["LINKER:-export-dynamic,--as-needed", "$ENV{VARIABLE}"]works since thelinksection expects a list of strings"compile_double":[{"project":null, "defs_fortran":"$ENV{VARIABLE}"}]also works becausedefs_fortranallows a list of strings as an option{"pkg":"$ENV{VARIABLE}","use_cmake_config":true}does not work becausepkgis expected to provide a single string
Currently CMake recognises only the first $ENV{VAR} when reading a JSON string. Thus, having "include":"$ENV{HDF5_DIR}/include" works as expected, but "include":"$ENV{HDF5_DIR}/$ENV{PRG_ENV}/include" would keep the second $ENV as it is. This is not CMake's limitation, but rather a technical detail of the current JSON config for HARMONIE-AROME, which can be changed in the future, if desired.
Note on the structure of the JSON configuration files
The structure of JSON-based configuration files is not automagically understood by CMake, there is some boilerplate code to be added to CMake scripts. Thus, the described structure of the CMake config file fully applies only to the main build. Other components of the system try to use the same structure of the config file, but some sections might be not handled correctly yet (for example, if a project does not use configure-time flags adding a configure section without modifying the CMake code would not have any effect and corresponding CMake variable won't be added).
Note on the auto-double flags
The compiler flags defining the preferred precision of the floating point variables in CMake build for HARMONIE-AROME are provided on the per-compiler and not per-config level. Thus, for each compiler type recognized by CMake a file named as FortranCompilerFlags.<compiler type>.cmake should be added to the util/cmake directory. This file should define the following CMake variables Fortran_DEFAULT_FLOAT_32, Fortran_DEFAULT_FLOAT_64, Fortran_DEFAULT_INT_32, Fortran_DEFAULT_INT_64 (some of them may be empty if a compiler does not provide corresponding flags). For example for the GNU compilers FortranCompilerFlags.GNU.cmake is defined as:
set(Fortran_DEFAULT_FLOAT_32 "")
set(Fortran_DEFAULT_FLOAT_64 "-fdefault-double-8 -fdefault-real-8")
set(Fortran_DEFAULT_INT_32 "")
set(Fortran_DEFAULT_INT_64 "-fdefault-integer-8")When running cmake configure, and depending on the build precision, a subset of these flags is added to the CMAKE_Fortran_FLAGS variable thus affecting all the Fortran targets. Currently, DEFAULT_INT variables are not used in CMake build, but are provided for consistency.
When creating FortranCompilerFlags.<compiler type>.cmake, <compiler type> should follow the naming provided by CMAKE_Fortran_COMPILER_ID, for example, GNU for gfortran and Intel for ifort. See the CMake documentation for a list of all supported compiler vendors.
Note on generating different build systems with CMake
CMake is a build system generator and it can create different native build systems from the same CMakeLists.txt. The full list of supported generators is available in the CMake documentation, however in practice when building HARMONIE-AROME on a Linux machine (or on a UNIX-like one in general) there are two options: the Unix Makefiles generator and the Ninja generator:
Unix Makefilesgenerator produces a build system based on the standard makefiles and does not use "exotic" tools. This is a default generator for CMake running on Linux and it usually works pretty well. However, when building withUnix Makefiles, CMake relies on its own Fortran parsers to scan the source tree and determine the build dependencies. Thus, in some rare cases of heavy CPP usage in Fortran code CMake can get inter-module dependencies wrong. TheUnix Makefilesbuild is not parallel by default but it can be controlled, as with any conventional makefile-based build, by passing the desired-jflags tomake. Additionally, when invoking the build viacmake --buildcommand, a-j(or--parallel) flag can be used for setting the number of parallel jobs in a build-system-agnostic way, see CMake documentation.Ninjais a modern alternative to Make. Ninja is built with focus on speed and Ninja build is parallel by default, however, unlike Make, the build files for Ninja are very cumbersome to hand-write and they are usually machine-generated. When building Fortran code with CMake Ninja generator, an explicit preprocessing step is added, thus the inter-module dependencies should be always correct (or at least these corner cases whereUnix Makefilesstruggles to get correct dependencies are handled correctly by Ninja). In some cases usingNinjagenerator can reduce the build time due to better parallelization of the build, however since Ninja has a separate preprocessing step, it generates more output and, if the file system is a bottleneck, Ninja build can be slower than Unix Makefiles build. Using theNinjagenerator in CMake requires theninjatool to be available in the$PATHat the configure time.
Specific CMake generator can be selected at the configure time by passing the correct -G <gen> flag to cmake. For example, cmake -G Ninja <...other CMake args...> or cmake -G "Unix Makefiles" <...other CMake args...>.
Practical considerations
When to re-run CMake configure in my experiment?
In principle, it should be enough to run CMake configure only once to generate the build system and after that any modification of the source code or configuration files should be detected by the build system triggering the required re-build steps. The only time, when CMake configure should be explicitly re-run is when you add a new source file to HARMONIE-AROME. The current implementation of the CMake build scans the file system looking for the source files to compile, so just putting a new file under, say, src/surfex/SURFEX/ and re-running the build isn't enough since this new file would be still unknown to the build system, thus the need of rerunning the configure step first.
I added some code and CMake build stopped working
Unlike makeup, CMake build for HARMONIE-AROME enforces inter-project boundaries and each project has an explicit list of its dependencies. For example, it is not possible to use modules from arpifs in surfex, but it is possible to use mse modules. If after a code modification CMake starts complaining about missing module files, then it means that this modification violates the project dependencies in the build. To fix this problem, please update your changeset to use only the available modules. If you believe that your modification is sound with respect to inter-project dependencies of HARMONIE-AROME and it's the CMake build which misses a dependency, please open a new GitHub issue explaining the problem.
Can I move/copy my build directory to another directory and re-use it?
No, it's generally a bad idea. CMake loves absolute paths and uses them in many parts of the generated build system, thus simply moving the build directory would break the build.
Something went wrong and CMake doesn't behave anymore, can I refresh the build without nuking the whole build directory?
You can try deleting just the CMakeCache.txt file from the build directory.
CMake picks a wrong compiler
Sometimes CMake selects a system default compiler instead of the compiler provided, for example, by loading a module. There are a few options available to force CMake to use a specific compiler, a straightforward one is to set the compiler via commonly-used environment variables (for example, export FC=ifort for a Fortran compiler). Another way, is to set the correct compilers in command-line arguments when configuring the CMake build (for example adding -DCMAKE_Fortran_COMPILER=ifort to the list of CMake arguments). CMake recognizes CMAKE_<LANG>_COMPILER passed from the command line where <LANG> can be Fortran, C or CXX.
Can I get more verbose output when compiling with CMake?
To get detailed information about individual steps and commands issued when compiling HARMONIE-AROME with CMake add -v to your build command:
cmake --build . --target install -vIs there a way to visualise dependencies between individual targets of HARMONIE-AROME in CMake build?
Since all the inter-target dependencies are defined in CMake scripts it can be useful to have an option to produce a graphical overview of the dependency graph of HARMONIE-AROME without grepping all the CMakeLists.txt files. This can be achieved by adding the --graphviz=<output file name> to the list of CMake arguments, for example:
cmake $HM_LIB/src --graphviz=harmonie.dotthen the produced dependency graph can be visualized using the dot tool:
dot -Tx11 harmonie.dotThe full dependency graph may be very cluttered and take quite some time to render, so it might be a good idea to plot dependencies of a single target, for example:
dot -Tx11 harmonie.dot.surf-staticSee the CMake documentation on graphviz for additional information about fine-tuning of the generated graphs.
I need more information about CMake, where do I find documentation?
CMake documentation portal is a great source of detailed information about the various aspects of the CMake build system.