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:

../_images/native-build.png

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

../_images/native-build-add.png

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:

../_images/generate-native-files.png

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

../_images/generate-native-files-add.png

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:

--- /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/overview/snippets/foo-old.sln
+++ /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/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:

--- /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/overview/snippets/project-old.pbxproj
+++ /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/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 :)

--- /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/overview/snippets/CMakeLists-old.txt
+++ /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/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:

../_images/cmake-environment.png

Note

  • All stages will be described fully in Tutorials.

See also

1.1.5. Summary

  • Human-readable configuration

  • Single configuration for all tools

  • Cross-platform/cross-tools friendly development

  • Doesn’t force you to change your favorite build tool/IDE

  • VCS friendly development

  • Easy experimenting

  • Easy development of new IDEs