This recipe has covered a lot of ground; let us make sense of it. CMake targets are a very useful abstraction for the operations that the build system will perform. Using the PRIVATE, PUBLIC, and INTERFACE keywords, we can set how targets within the same project will interact with each other. In practice, this lets us define how dependencies of target A will affect target B, which depends on A. The full power of this mechanism can be appreciated when other projects want to use a library as a dependency. If the proper CMake configuration files are made available by the library maintainers, then all dependencies can be easily resolved with very few CMake commands.
This problem can be solved by following the pattern outlined in the recipe for the message-static, message-shared, hello-world_wDSO, and hello-world_wAR targets. We will analyze the CMake commands for the message-shared target alone, but the discussion here is general:
- Generate your target and lay out its dependencies within the project build. The need to link against the UUID library is a PUBLIC requirement for message-shared, since it will be used both to build targets within the project and targets in downstream projects. Compile definitions and include directories need to be set both at the PUBLIC or at the INTERFACE level. Some of them will, in fact be needed to build targets within the project, others are only relevant for downstream projects. Moreover, some of these will only be relevant after the project has been installed. This is where the $<BUILD_INTERFACE:...> and $<INSTALL_INTERFACE:...> generator expressions come in. Only downstream targets external to the message library will need these, that is they will only be made visible once the target is installed. In our example, the following applies:
- $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}> will expand to ${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR} only when the message-shared library target is used within our project.
- $<INSTALL_INTERFACE:${INSTALL_INCLUDEDIR}> will expand to ${INSTALL_INCLUDEDIR} only when the message-shared library target is used as an exported target within another build tree.
- Describe the install rules for the target, including the name of the EXPORT file CMake will have to generate.
- Describe the install rules for the export file CMake has generated. The messageTargets.cmake file will be installed to INSTALL_CMAKEDIR. The NAMESPACE option to the install rule for the target export files will prepend the given string to the name of the targets. This is helpful to avoid potential name clashes between targets from different projects. The INSTALL_CMAKEDIR variable was set in the root CMakeLists.txt file:
if(WIN32 AND NOT CYGWIN)
set(DEF_INSTALL_CMAKEDIR CMake)
else()
set(DEF_INSTALL_CMAKEDIR share/cmake/${PROJECT_NAME})
endif()
set(INSTALL_CMAKEDIR ${DEF_INSTALL_CMAKEDIR} CACHE PATH "Installation directory for CMake files")
The final part of our CMakeLists.txt generates the configuration files. After including the CMakePackageConfigHelpers.cmake module, this is done in three steps:
- We call the write_basic_package_version_file CMake function to generate a package version file. The first argument to the macro is the path to the versioning file: messageConfigVersion.cmake. We then specify the version in the Major.Minor.Patch format, using the PROJECT_VERSION CMake variable. Compatibility with newer versions of the library can also be specified. In our case, we guarantee compatibility when the library has the same major version, hence the SameMajorVersion argument.
- Next, we configure our template file, messageConfig.cmake.in; this file is located in the cmake subdirectory of the project.
- Finally, we set the install rules for the newly generated files. Both will be installed under INSTALL_CMAKEDIR.