Understanding Cmake

broken image


Updated June 2020

WTF is going on with all IDE's. The build system integration is terrible. Trying to get QT to work with cmake required an understanding of cmake syntax which seems baffling. There were untyped strings everywhere. QT's other build systems were also. Not very good to put it gently, either deprecated or they didn't work. CMake is the de facto industry standard for the build system generator of the C and C this days. Like C, people seem to love and hate it at the same time. However, it is definitely a better choice compare to writting Makefiles manually. In this article, we'll learn to install cmake on Linux. CMake is a cross-platform open-source meta-build system that can build, test, and package software.It can be used to support multiple native build environments including make in Linux/Unix, Apple's Xcode, and Microsoft Visual Studio. CMake is a powerful and robust build system. You specify what you want done, not how to do it. CMake then takes that information and generates the files needed to build the system.

CMake gives you the ability to detect which platform you are working on and act accordingly. This is done by inspecting CMAKESYSTEMNAME, one of the many variables that CMake defines internally. CMake also supports conditionals, that is the usual if-else combination. With this tools in place, the task is pretty easy.

With the constant evolution of C++, build systems have had to deal with the complication of selecting the relevant compiler and linker flags. If your project targets multiple platforms and compilers, this can be a headache to set up. Happily, with features added in CMake 3.1, it is trivial to handle this in a generic way.

Setting the C++ standard directly

Understanding Cmake Blood Test

The simplest way to use a particular C++ standard in your project is to add the following two variable definitions before you define any targets:

Cmake

Valid values for CMAKE_CXX_STANDARD are 98, 11 and 14, with 17 also being added in CMake 3.8 and 20 added in CMake 3.12. This variable is used as the default for the CXX_STANDARD target property, so all targets defined after the variable is set will pick up the requirement. Php last version. The CXX_STANDARD target property mostly behaves as you would expect. It results in adding the relevant compiler and linker flags to the target to make it build with the specified C++ standard. There is a minor wrinkle in that if the compiler doesn't support the specified standard, by default CMake falls back to the latest standard the compiler does support instead. It does not generate an error by default. To prevent this fallback behaviour, the CXX_STANDARD_REQUIRED target property should be set to YES. Since you will likely be wanting to disable the fallback behaviour in most situations, you will probably find it easier to just set the CMAKE_CXX_STANDARD_REQUIRED variable to YES instead, since it acts as the default for the CXX_STANDARD_REQUIRED target property. Note that CMake may still end up selecting a more recent language standard than the one specified (see the discussion of compiler features in the next section).

Projects will also probably want to set the following too:

This results in modifying the flag used to set the language standard (e.g. -std=c++11 rather than -std=gnu++11). The default behavior is for C++ extensions to be enabled, but for broadest compatibility across compilers, projects should prefer to explicitly disable them. Credit to ajneu in the comments for pointing out this particular variable and its effects.

And that's all it takes to get CMake to set the compiler and linker flags appropriately for a specific C++ standard. In most cases, you probably want to use a consistent C++ standard for all targets defined in a project, so setting the global CMAKE_CXX_STANDARD, CMAKE_CXX_STANDARD_REQUIRED and CMAKE_CXX_EXTENSIONS variables would be the most convenient. Where your CMakeLists.txt might be getting included from some higher level project or if you don't want to set the global behaviour, then setting the CXX_STANDARD, CXX_STANDARD_REQUIRED and CXX_EXTENSIONS properties on each target will achieve the same thing on a target-by-target basis. For example:

Results

Setting the C++ standard based on features

For most situations, the above method is the simplest and most convenient way to handle the compiler and linker flags for a particular C++ standard. As an alternative, CMake also offers a finer grained approach, allowing you to specify the compiler features that your code requires and letting CMake work out the C++ standard automatically. Initial support for compiler features was made available in CMake 3.1 for just a small number of compilers, expanded to a broader set of compilers in version 3.2 and from 3.6 all commonly used compilers are supported.

To specify that a particular target requires a certain feature, CMake provides the target_compile_features() command:

The PRIVATE, PUBLIC and INTERFACE keywords work just like they do for the various other target_.. commands, controlling whether the feature requirement should apply to just this target (PRIVATE), this target and anything that links to it (PUBLIC) or just things that link to it (INTERFACE). The supported feature entries depend on the compiler being used. The full set of features CMake knows about is included in the CMake documentation and can also be obtained from the CMAKE_CXX_KNOWN_FEATURES global property (so you need to use get_property() to access it). To see the subset of features supported by your current compiler, place the following line anywhere after your project() command:

The following example tells CMake that your target requires support for variadic templates and the nullptr keyword in its implementation and its public interface, plus it also uses lambda functions internally:

You can also use generator expressions with target_compile_features(). The following example adds a feature requirement for compiler attributes only when using the GNU compiler:

Understanding Cmake Standards

Using compiler features instead of unilaterally setting the C++ standard for a target has some advantages and disadvantages. On the plus side, you can specify precisely the features you want without having to know which version of the C++ standard supports them. You also get the flexibility of being able to use generator expressions and the language standard requirements are propagated to dependencies through PUBLIC and INTERFACE specifications. One obvious drawback is that the list of features your code uses might be quite long and it would be easy to miss features added to the code later. As a compromise, CMake 3.8 introduced the ability to specify the minimum language standard as a compiler feature. These meta features, as they are called, have names of the form cxx_std_YY, where YY is one of the same values supported by the CXX_STANDARD target property. When building the target, CMake will use the latest language standard required by the CXX_STANDARD target property or by any compiler feature set for that target. In the following example, foo would be built with C++14 and bar with C++17:

It should be noted that fine-grained compile features are no longer being added beyond C++14. They proved to be incomplete at best and were difficult to maintain. Only the cxx_std_YY meta-features are being added for C++17 and beyond.

Optional features

Understanding Cmake

Your code base may be flexible enough to support features of later C++ standards if available, but still build successfully without them. For such situations, the compile features approach is convenient because it allows generator expressions to control whether or not a particular feature is used based on whether or not the compiler supports that feature. The following example adapted slightly from the CMake documentation will choose between two different directories to add to the include path for anything linking against myLib:

The COMPILE_FEATURES generator expression tests whether the named feature is supported by the compiler. In the above example, if the compiler supports variadic templates, the with_variadics subdirectory will be added to the header search path. If the compiler does not support them, then the no_variadics subdirectory will be added instead. This approach could even be used with the target_sources() command to conditionally include different source files based on compiler features.

Concluding remarks

Understanding Cmake
Understanding

Valid values for CMAKE_CXX_STANDARD are 98, 11 and 14, with 17 also being added in CMake 3.8 and 20 added in CMake 3.12. This variable is used as the default for the CXX_STANDARD target property, so all targets defined after the variable is set will pick up the requirement. Php last version. The CXX_STANDARD target property mostly behaves as you would expect. It results in adding the relevant compiler and linker flags to the target to make it build with the specified C++ standard. There is a minor wrinkle in that if the compiler doesn't support the specified standard, by default CMake falls back to the latest standard the compiler does support instead. It does not generate an error by default. To prevent this fallback behaviour, the CXX_STANDARD_REQUIRED target property should be set to YES. Since you will likely be wanting to disable the fallback behaviour in most situations, you will probably find it easier to just set the CMAKE_CXX_STANDARD_REQUIRED variable to YES instead, since it acts as the default for the CXX_STANDARD_REQUIRED target property. Note that CMake may still end up selecting a more recent language standard than the one specified (see the discussion of compiler features in the next section).

Projects will also probably want to set the following too:

This results in modifying the flag used to set the language standard (e.g. -std=c++11 rather than -std=gnu++11). The default behavior is for C++ extensions to be enabled, but for broadest compatibility across compilers, projects should prefer to explicitly disable them. Credit to ajneu in the comments for pointing out this particular variable and its effects.

And that's all it takes to get CMake to set the compiler and linker flags appropriately for a specific C++ standard. In most cases, you probably want to use a consistent C++ standard for all targets defined in a project, so setting the global CMAKE_CXX_STANDARD, CMAKE_CXX_STANDARD_REQUIRED and CMAKE_CXX_EXTENSIONS variables would be the most convenient. Where your CMakeLists.txt might be getting included from some higher level project or if you don't want to set the global behaviour, then setting the CXX_STANDARD, CXX_STANDARD_REQUIRED and CXX_EXTENSIONS properties on each target will achieve the same thing on a target-by-target basis. For example:

Setting the C++ standard based on features

For most situations, the above method is the simplest and most convenient way to handle the compiler and linker flags for a particular C++ standard. As an alternative, CMake also offers a finer grained approach, allowing you to specify the compiler features that your code requires and letting CMake work out the C++ standard automatically. Initial support for compiler features was made available in CMake 3.1 for just a small number of compilers, expanded to a broader set of compilers in version 3.2 and from 3.6 all commonly used compilers are supported.

To specify that a particular target requires a certain feature, CMake provides the target_compile_features() command:

The PRIVATE, PUBLIC and INTERFACE keywords work just like they do for the various other target_.. commands, controlling whether the feature requirement should apply to just this target (PRIVATE), this target and anything that links to it (PUBLIC) or just things that link to it (INTERFACE). The supported feature entries depend on the compiler being used. The full set of features CMake knows about is included in the CMake documentation and can also be obtained from the CMAKE_CXX_KNOWN_FEATURES global property (so you need to use get_property() to access it). To see the subset of features supported by your current compiler, place the following line anywhere after your project() command:

The following example tells CMake that your target requires support for variadic templates and the nullptr keyword in its implementation and its public interface, plus it also uses lambda functions internally:

You can also use generator expressions with target_compile_features(). The following example adds a feature requirement for compiler attributes only when using the GNU compiler:

Understanding Cmake Standards

Using compiler features instead of unilaterally setting the C++ standard for a target has some advantages and disadvantages. On the plus side, you can specify precisely the features you want without having to know which version of the C++ standard supports them. You also get the flexibility of being able to use generator expressions and the language standard requirements are propagated to dependencies through PUBLIC and INTERFACE specifications. One obvious drawback is that the list of features your code uses might be quite long and it would be easy to miss features added to the code later. As a compromise, CMake 3.8 introduced the ability to specify the minimum language standard as a compiler feature. These meta features, as they are called, have names of the form cxx_std_YY, where YY is one of the same values supported by the CXX_STANDARD target property. When building the target, CMake will use the latest language standard required by the CXX_STANDARD target property or by any compiler feature set for that target. In the following example, foo would be built with C++14 and bar with C++17:

It should be noted that fine-grained compile features are no longer being added beyond C++14. They proved to be incomplete at best and were difficult to maintain. Only the cxx_std_YY meta-features are being added for C++17 and beyond.

Optional features

Your code base may be flexible enough to support features of later C++ standards if available, but still build successfully without them. For such situations, the compile features approach is convenient because it allows generator expressions to control whether or not a particular feature is used based on whether or not the compiler supports that feature. The following example adapted slightly from the CMake documentation will choose between two different directories to add to the include path for anything linking against myLib:

The COMPILE_FEATURES generator expression tests whether the named feature is supported by the compiler. In the above example, if the compiler supports variadic templates, the with_variadics subdirectory will be added to the header search path. If the compiler does not support them, then the no_variadics subdirectory will be added instead. This approach could even be used with the target_sources() command to conditionally include different source files based on compiler features.

Concluding remarks

Where possible, favour simplicity. Compile features are very flexible, but if you know your code requires a specific C++ standard, it is much cleaner to simply tell CMake to use that standard with the CXX_STANDARD target property or the default CMAKE_CXX_STANDARD global variable. This is particularly relevant when linking against third party libraries which may have been built with a specific C++ standard library. If, for example, you link against any Boost modules which have static libraries, you will need to link to the same C++ standard library as was used when Boost was built or else you will get many unresolved symbols at link time.

Also consider using the most recent CMake release your project can require. Later CMake versions have broader support for more compilers (e.g. support for Intel compilers was only added in CMake 3.6). If your project and all the dependencies it links against do not have to support CMake versions earlier than 3.8, the use of compiler features to specify the language standard directly may be considered. Note, however, that unlike target properties, there is no way to set a global default for compiler features, so they would have to be specified on each target that had a requirement for a particular minimum language standard. CMake may then upgrade some targets to later standards if their dependencies demand it through PUBLIC or INTERFACE compiler features.

It is also worth noting that language standard requirements are not restricted to just C++. The various target properties, variables and compiler features have predictably named equivalents for C as well (e.g. C_STANDARD target property, c_std_YY compiler meta feature). CMake 3.8 also introduced language standard specifications for CUDA and the try_compile() command learnt to support language standard requirements.

Have a CMake maintainer work on your project

Get the book for more CMake content





broken image