CMake is the industry standard build tool for multi-platform C++ codebases used by the oneAPI Construction Kit. This document covers best practices for CMake development and conventions within the project, but is not intended as a CMake tutorial.
oneAPI Construction Kit uses modern CMake, regarded as version 3.0 and later,
with the minimum required version enforced in the root
For general tips on writing CMake see the internal Codeplay knowledge sharing talk by Morris Hafner “Improve Your CMake With These 17 Weird Tricks”.
Modules are files containing CMake code named
<modulename>.cmake, which get
loaded and run by
CMakeLists.txt files using the include command.
The oneAPI Construction Kit provides the following modules to aid build system
- AddCA Module
- Bin2H Module
- CAOption Module
- CAPlatform Module
- ConfigureFileScript Module
- Coverage Module
- DetectLLVMMSVCCRT Module
- Find Modules
- Format Module
- ImportLLVM Module
- ReleaseAssert Module
- Sanitizers Module
- OpenCL CMake
- Vulkan CMake
- ComputeMux CMake
For non-native builds CMake supports a toolchain file mechanism which
defines the path to the cross-compilation toolchain and location of non-native
system libraries. Without using a toolchain file setting flags directly in
CMake modules can be error prone. For example, setting the
modifies CPU architecture after CMake has detected a 64-bit system, leading to
inconsistencies such as
8 rather than
and CMake looking for 64-bit libraries in the native path. Using a
toolchain file will also implicitly set the CMAKE_CROSSCOMPILING flag
if the module sets CMAKE_SYSTEM_NAME as ours do, pruning the need for an
extra user passed commandline option.
The oneAPI Construction Kit stores toolchain files in the root
directory for all of the cross-compilation platforms the project supports. Our
Arm Linux platform makes use of the CMAKE_CROSSCOMPILING_EMULATOR CMake
feature with QEMU to emulate 64-bit and 32-bit Arm architectures as part of
platform/arm-linux/arm-toolchain.cmake. Utilizing an emulator allows us to
run our check targets natively to verify cross-compiled builds, which although
slower and more memory constrained than native, is valuable option when
hardware is unavailable.
Generator Expression Usage
The deferred evaluation of generator expressions from configuration time
until the point of build system generation provides benefits in terms of
expressibility. For example, the multi-configuration nature of MSVC generators
we support means that we don’t know what the build type is at configure time,
unlike single-configuration generators which can rely on CMAKE_BUILD_TYPE.
By using generator expressions we can check the
CONFIG variable query
on MSVC to discover the build type and change our settings accordingly.
The variable query expressions also provides a concise syntax for
conditionally including items in a list, particularly compared to appending
if()/else() control flow. We often use this paradigm for
setting compiler flags, see AddCA Module, using conditional expressions
to set the appropriate flags for the various combinations of build
However, the exception to this is using generator expressions with a list of source files. This is not supported by multi-configuration MSVC generators where files must be known by CMake at configure time, and can’t be deferred for later optional inclusion. A possible workaround for this is defining a separate library which is only linked into the target when the condition expression evaluates to True.
Using generator expressions for source files will result in the MSVC error message “Target <target name> has source files which vary by configuration.”