If only software build systems would do what we intuitively expect! I’m sure many of you have your own horror stories of having to unravel convoluted scripts, project settings, compiler bugs, etc. in order to get code to build, despite the project requirements seeming to be relatively simple. If you work with cross-platform software, this is probably a pain point you are particularly familiar with. This article demonstrates some more recent features of CMake which greatly reduce that pain.
Specifying target dependencies
If you are using CMake to control your builds, then versions 2.8.11 and later have added some very useful features which make specifying dependencies between build targets much simpler and more robust. Instead of carrying around compiler flags, linker options, header search paths, etc. in CMake variables, you can now attach that information directly to the targets themselves and let CMake add them as needed for anything that links to that target. This greatly simplifies projects, especially those with a large number of target dependencies spread across multiple directories.
The most relevant commands that have been added or extended to support these features are the following:
These commands all follow a similar pattern:
Things specified as
PUBLIC will apply to both
targetName and anything that links to it.
PRIVATE specifies things that only apply to
targetName, meaning anything that links to it doesn’t need to know about it.
INTERFACE applies only to targets that link to
targetName but which are not needed by
A concrete example best demonstrates how these commands and keywords are used. Let’s say we have a project with some libraries which make use of an external package, Boost:
- Static library
foowill use Boost features internally when the
useBoostpre-processor symbol is defined, but does not expose anything related to Boost in its interface. When enabled, Boost libraries need to be linked in for any target using
- Shared library
baralways uses parts of Boost which only require its headers but do not require Boost libraries to be linked in.
gumbydoes not use Boost in any way, but it uses
foointernally and it also uses parts of
barin its own public interface.
We can define these libraries as follows:
# Let CMake find Boost for us.
# Details omitted for brevity.
# Basic definition of library targets
add_library(foo STATIC foo.cpp)
add_library(bar SHARED bar.cpp)
# Dependency details for the libraries
Now if we have other targets which need to link against these libraries, even if those targets are in different directories, it is trivial to do so and CMake will take care of the dependencies for us.
In the above, we only explicitly link
appA against the static library
foo. The dependencies we set up for
foo, however, will ensure that
appA is also linked against the Boost libraries.
appB, we only specified that it should link against
gumby, but CMake will also do the following for us automatically:
barto the linked in libraries because
- Add the Boost header search paths when compiling
appB.cppbecause they are a
- Add Boost libraries to those linked in because
foois a dependency of
gumby. Even though this is a
PRIVATEdependency, CMake still honours the requirement that anything linking to
foomust also link to the Boost libraries.
Real world example: Qt
The above example was fairly contrived to show how the CMake commands are used. In practice, targets may be defined in different directories or come from entirely separate third-party projects. A particularly powerful example of how the above CMake features can be exploited to very good effect is the way they are used with recent Qt versions. Qt has a number of different modules, each one requiring its own header search paths, libraries to be linked and pre-processing symbols to be defined, with a lot of this information varying from platform to platform. In the past, these things all had to be specified via CMake variables and they were awkward to use for multiple targets which used different Qt modules. Using the new target dependency commands, it is now just a simple matter of linking to the required Qt modules and CMake takes care of the rest. For example:
# List the Qt modules required by any target
# Create some executable targets
add_executable(openGLApp WIN32 ...)
# Make each executable depend only on the Qt modules they need
target_link_libraries(openGLApp Qt5::Network Qt5::Gui)
target_link_libraries commands hide a great deal of dependency information that we mere mortals do not need to know about. We just want our targets to be able to use those Qt modules and not have to care about the details. Behind the scenes, these commands will add header search paths, compiler flags and other Qt libraries and system libraries required by the Qt module(s) that we did actually specify. In the case of
openGLApp, it even handles adding a special static library (
qtmain) which is only required for Windows GUI Qt applications. Furthermore, the above shows how two different targets can use different sets of Qt modules. Those developers who have worked with Qt prior to CMake 2.8.11 will appreciate just how much simpler this is compared to before!
For the interested reader, a more complete description of using CMake with Qt5 is now maintained as part of Qt itself.