1.1. What CMake can do¶
CMake is a meta build system. It can generate real
native build tool files from abstracted text configuration.
Usually such code lives in CMakeLists.txt
files.
What does it mean and how it can be useful?
1.1.1. Cross-platform development¶
Let’s assume you have some cross-platform project with C++ code shared along
different platforms/IDEs. Say you use Visual Studio
on Windows, Xcode
on OSX and Makefile
for Linux:

What you will do if you want to add new bar.cpp
source file? You have to add
it to every tool you use:

To keep the environment consistent you have to do the similar update several times. And the most important thing is that you have to do it manually (arrow marked with a red color on the diagram in this case). Of course such approach is error prone and not flexible.
CMake solve this design flaw by adding an extra step to the development process. You
can describe your project in a CMakeLists.txt
file and use CMake to
generate the cross-platform build tools:

Same action - adding new bar.cpp
file, will be done in one step now:

Note that the bottom part of the diagram was not changed. I.e. you still can
keep using your favorite tools like Visual Studio/msbuild
,
Xcode/xcodebuild
and Makefile/make
!
1.1.2. VCS friendly¶
Version Control (VCS) is used to share and save your code’s
history of changes when you work in a team. However, different IDEs use unique
files to track project files (*.sln
, *.pbxproj
, *.vscode
, etc)
For example, here is the diff after adding bar.cpp
source file to the bar
executable in Visual Studio
:
--- /overview/snippets/foo-old.sln
+++ /overview/snippets/foo-new.sln
@@ -4,6 +4,8 @@
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foo", "foo.vcxproj", "{C8F8C325-ACF3-460E-81DF-8515C72B334A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bar", "..\bar\bar.vcxproj", "{D14B78EA-1ADA-487F-B1ED-42C2B919C000}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -21,6 +23,14 @@
{C8F8C325-ACF3-460E-81DF-8515C72B334A}.Release|x64.Build.0 = Release|x64
{C8F8C325-ACF3-460E-81DF-8515C72B334A}.Release|x86.ActiveCfg = Release|Win32
{C8F8C325-ACF3-460E-81DF-8515C72B334A}.Release|x86.Build.0 = Release|Win32
+ {D14B78EA-1ADA-487F-B1ED-42C2B919C000}.Debug|x64.ActiveCfg = Debug|x64
+ {D14B78EA-1ADA-487F-B1ED-42C2B919C000}.Debug|x64.Build.0 = Debug|x64
+ {D14B78EA-1ADA-487F-B1ED-42C2B919C000}.Debug|x86.ActiveCfg = Debug|Win32
+ {D14B78EA-1ADA-487F-B1ED-42C2B919C000}.Debug|x86.Build.0 = Debug|Win32
+ {D14B78EA-1ADA-487F-B1ED-42C2B919C000}.Release|x64.ActiveCfg = Release|x64
+ {D14B78EA-1ADA-487F-B1ED-42C2B919C000}.Release|x64.Build.0 = Release|x64
+ {D14B78EA-1ADA-487F-B1ED-42C2B919C000}.Release|x86.ActiveCfg = Release|Win32
+ {D14B78EA-1ADA-487F-B1ED-42C2B919C000}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
And new bar.vcxproj
of 150 lines of code. Here are some parts of it:
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<ItemGroup>
<ClCompile Include="bar.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
When using Xcode
:
--- /overview/snippets/project-old.pbxproj
+++ /overview/snippets/project-new.pbxproj
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
0FE79B881D22BAE400E38C27 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FE79B871D22BAE400E38C27 /* main.cpp */; };
+ 0FE79B951D22BB5E00E38C27 /* bar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FE79B941D22BB5E00E38C27 /* bar.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -20,15 +21,33 @@
);
runOnlyForDeploymentPostprocessing = 1;
};
+ 0FE79B901D22BB5E00E38C27 /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = /usr/share/man/man1/;
+ dstSubfolderSpec = 0;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 1;
+ };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0FE79B841D22BAE400E38C27 /* foo */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = foo; sourceTree = BUILT_PRODUCTS_DIR; };
0FE79B871D22BAE400E38C27 /* main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
+ 0FE79B921D22BB5E00E38C27 /* bar */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = bar; sourceTree = BUILT_PRODUCTS_DIR; };
+ 0FE79B941D22BB5E00E38C27 /* bar.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = bar.cpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
0FE79B811D22BAE400E38C27 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 0FE79B8F1D22BB5E00E38C27 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -42,6 +61,7 @@
isa = PBXGroup;
children = (
0FE79B861D22BAE400E38C27 /* foo */,
+ 0FE79B931D22BB5E00E38C27 /* bar */,
0FE79B851D22BAE400E38C27 /* Products */,
);
sourceTree = "<group>";
@@ -50,6 +70,7 @@
isa = PBXGroup;
children = (
0FE79B841D22BAE400E38C27 /* foo */,
+ 0FE79B921D22BB5E00E38C27 /* bar */,
);
name = Products;
sourceTree = "<group>";
@@ -60,6 +81,14 @@
0FE79B871D22BAE400E38C27 /* main.cpp */,
);
path = foo;
+ sourceTree = "<group>";
+ };
+ 0FE79B931D22BB5E00E38C27 /* bar */ = {
+ isa = PBXGroup;
+ children = (
+ 0FE79B941D22BB5E00E38C27 /* bar.cpp */,
+ );
+ path = bar;
sourceTree = "<group>";
};
/* End PBXGroup section */
@@ -80,6 +109,23 @@
name = foo;
productName = foo;
productReference = 0FE79B841D22BAE400E38C27 /* foo */;
+ productType = "com.apple.product-type.tool";
+ };
+ 0FE79B911D22BB5E00E38C27 /* bar */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 0FE79B981D22BB5E00E38C27 /* Build configuration list for PBXNativeTarget "bar" */;
+ buildPhases = (
+ 0FE79B8E1D22BB5E00E38C27 /* Sources */,
+ 0FE79B8F1D22BB5E00E38C27 /* Frameworks */,
+ 0FE79B901D22BB5E00E38C27 /* CopyFiles */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = bar;
+ productName = bar;
+ productReference = 0FE79B921D22BB5E00E38C27 /* bar */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
@@ -94,6 +140,9 @@
0FE79B831D22BAE400E38C27 = {
CreatedOnToolsVersion = 7.3.1;
};
+ 0FE79B911D22BB5E00E38C27 = {
+ CreatedOnToolsVersion = 7.3.1;
+ };
};
};
buildConfigurationList = 0FE79B7F1D22BAE400E38C27 /* Build configuration list for PBXProject "foo" */;
@@ -109,6 +158,7 @@
projectRoot = "";
targets = (
0FE79B831D22BAE400E38C27 /* foo */,
+ 0FE79B911D22BB5E00E38C27 /* bar */,
);
};
/* End PBXProject section */
@@ -119,6 +169,14 @@
buildActionMask = 2147483647;
files = (
0FE79B881D22BAE400E38C27 /* main.cpp in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 0FE79B8E1D22BB5E00E38C27 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0FE79B951D22BB5E00E38C27 /* bar.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -220,6 +278,20 @@
};
name = Release;
};
+ 0FE79B961D22BB5E00E38C27 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ 0FE79B971D22BB5E00E38C27 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -239,6 +311,15 @@
0FE79B8D1D22BAE400E38C27 /* Release */,
);
defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 0FE79B981D22BB5E00E38C27 /* Build configuration list for PBXNativeTarget "bar" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 0FE79B961D22BB5E00E38C27 /* Debug */,
+ 0FE79B971D22BB5E00E38C27 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
};
/* End XCConfigurationList section */
};
As you can see, a lot of magic happens while doing a simple task like adding one new source file to a target. Additionally,
- Are you sure that all XML sections added on purpose and was not the result of accidental clicking?
- Are you sure all this x86/x64/Win32, Debug/Release configurations connected together in right order and you haven’t break something while debugging?
- Are you sure all that magic numbers was not read from your environment while you have done non-trivial scripting and is in fact some private key, token or password?
- Do you think it will be easy to resolve conflict in this file?
Luckily we have CMake which helps us in a neat way. We haven’t touched any CMake syntax yet but I’m pretty sure it’s quite obvious what’s happening here :)
--- /overview/snippets/CMakeLists-old.txt
+++ /overview/snippets/CMakeLists-new.txt
@@ -2,3 +2,4 @@
project(foo)
add_executable(foo foo.cpp)
+add_executable(bar bar.cpp)
What a relief! Having such human-readable form of build system commands actually making CMake a convenient tool for development even if you’re using only one platform.
1.1.3. Experimenting¶
Even if your team has no plans to work with some native tools
originally, this may change in the future. E.g. you have worked with Makefile
and
want to try Ninja
. What you will do? Convert manually? Find the converter?
Write converter from scratch? Write new Ninja
configuration from scratch?
With CMake you can change cmake -G 'Unix Makefiles'
to
cmake -G Ninja
- done!
This helps developers of new IDEs also. Instead of putting your IDE users into
situations when they have to decide should they use your SuperDuperIDE
instead of their favorite one and probably writing endless number of
SuperDuperIDE
<-> Xcode
, SuperDuperIDE
<-> Visual Studio
, etc.
converters, all you have to do is to add new generator -G SuperDuperIDE
to
CMake.
1.1.4. Family of tools¶
CMake is a family of tools that can help you during all stages of
sources for developers
-> quality control
-> installers for users
stack. Next activity diagram shows CMake, CTest and CPack connections:

Note
- All stages will be described fully in Tutorials.
See also