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 extra step to development process. You
can describe your project in CMakeLists.txt file and use CMake to
generate tools you currently interested in using cross-platform CMake code:
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¶
When you work in team on your code you probably want to share and save the
history of changes, that’s what usually VCS used for. How does
storing of IDE files like *.sln works on practice? Here is the diff after
adding bar executable with bar.cpp source file to the Visual Studio
solution:
--- /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 there are a lot of magic happens while doing quite simple task like adding new target with one source file. Looking at the diffs above try to answer next questions:
- 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 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