Lately I have worked a lot with the build framework/system in the project of my current assignment in automotive. Doing so I have noticed the benefit of writing CMake conforming with what is called ’Modern CMake’, or rather the draw backs of not doing it. Therefore, I’d like to take the opportunity to share my experience.
This will not be a complete description of what Modern CMake is, there are loads of articles about that, and here is a good entry point to Modern CMake. However, I’ll give you a few Do’s and Don’ts along with the issues I had when these were not followed. But my main advice is to think of CMake as any other production code and demand quality, readability and maintainability.
- Do not use global functions such as include_directories or link_libraries. These often shroud what targets actually use and need. Use functions as target_include_directories and target_link_libraries to modify each target explicitly instead.
- Do not modify the CMAKE_CXX_FLAGS in subprojects. The project might change to a compiler that do not support all the flags the old did. This kind of variable should be modified on the top level CMakeLists.txt or preferably in a toolchain file.
- Use ALIAS targets so that add_subdirectory and find_package exports the same name for targets. The issue I saw in my current project was when we started to build an external library instead of using prebuilds (or vice versa), and the target_link_libraries of all the dependent components needed to be updated.
- Do not use target_inlude_directories with paths reaching outside the directory of the component. The project might change its file structure and all these paths need to be updated. Instead, export the needed header files from the other component, either with target_include_directories with PUBLIC properties or simply export an INTERFACE library.
- Provide well defined and documented functions for adding tests on a project level. The main benefit is that it is easier to change the behaviour of the tests and how they are used in CI Gates if naming conventions and label use are centrally enforced/implemented once.
- Use cmake_parse_arguments when implementing custom functions. Implementing the same functionality, yourself might introduce unnecessary complexity or obscurity.
- Do not overdo the use of variables. When debugging CMake and/or the binaries, it might prove challenging to expand all the variables in your head.
As I mentioned before, there a multitude of articles about Modern CMake and how to follow it, and here are some of them:
Hope you enjoyed my blog post!