We would like to conclude this discussion by pointing out some common pitfalls when moving to CMake.
- Global variables are code smell: This is true for any programming language, and CMake is no exception. Variables that are carried across CMake files, and in particular "upwards" from leaf to parent CMakeLists.txt files, indicate code smell. There is typically a better way to transfer dependencies. Ideally, dependencies should be imported through targets. Instead of assembling a list of libraries into a variable and carrying the variable across files, link to libraries one by one close to where they are defined. Instead of assembling source files into variables, add source files using target_sources. When linking to libraries use imported targets when available, instead of variables.
- Minimize the effect of order: CMake is not a declarative language, but we should not approach it with an imperative paradigm either. CMake sources that enforce a strict order tend to be brittle. This also connects to the discussion about variables (see previous paragraph). Some order of statements and modules will be necessary, but to arrive at robust CMake frameworks, we should avoid unnecessary enforcement of order. Use target_sources, target_compile_definitions, target_include_directories, and target_link_libraries. Avoid global scope statements such as add_definitions, include_directories, and link_libraries. Avoid global definitions of compile flags. If possible, define compile flags per target.
- Do not place generated files outside the build directory: It is highly recommended to never place generated files outside the build directory. The reason for this is that generated files often depend on the chosen options or compiler or build type, and by writing into the source tree, we give up the possibility to maintain several builds with the same source and we complicate reproducibility of the build steps.
- Prefer functions over macros: They have different scopes and the function scope is limited. All variable modifications need to be explicitly marked, which also signals variable redefinitions to the reader. Use a macro when you must but prefer functions if you can.
- Avoid shell commands: They may not be portable to other platforms (such as Windows). Prefer using CMake equivalents. If no CMake equivalent is available, consider calling a Python script.
- In Fortran projects, be careful with the suffix case: Fortran sources that need to be preprocessed should have an uppercase .F90 suffix. Sources that are not to be preprocessed should carry the .f90 suffix.
- Avoid explicit paths: This is true both when defining targets and when referencing files. Use CMAKE_CURRENT_LIST_DIR when referring to the current path. The advantage of this is that when you move or rename a directory, it will still work.
- Module includes should not be function calls: Modularizing the CMake code into modules is a good strategy, but including a module should ideally not execute CMake code. Instead, wrap CMake code into functions and macros and explicitly call these after including the module. This protects against unintended effects when accidentally including a module multiple times and makes the action of executing CMake code modules more explicit to the reader.