Building a Small C++ Application with CMake

CMake Robot Artwork
CMake is Your Build Robot

Do you need to build a C++ application? CMake is the perfect tool, regardless of its size. And while you may feel hesitant to learn CMake, it’s easier to understand and does more with fewer lines of code than many other tools. Follow along as we explore one way to build a small C++ application using CMake. Let’s see how many different targets we can build for!

Here’s the source code for our simple application. It’s listed below and comes from our cmake-intro repository. The efficiency you gain using CMake for such a simple program is small compared to what you’d net from a more useful application. However, use CMake from the start to build a strong foundation. You can easily add things like linking to libraries and unit tests in the future. In addition, it’s a cinch to start this way.

#include <iostream>
#include "misc.h"

int main(int argc, char** argv)
{
    std::cout << "Hello world! This is '" PROGRAM_NAME "'." << std::endl;
    return 0;
}

Here’s the CMakeLists.txt file. CMake uses the metadata from this file to generate a build system. Let’s go through it line by line in the following section.

cmake_minimum_required(VERSION 3.14)
project(helloworld)

add_executable(helloworld main.cpp)

install(TARGETS helloworld)

CMakeLists.txt – Blow by Blow

Line one tells CMake what version(s) you need to use in this project. You should pick the version in this line by researching the features you need in your CMakeLists.txt. It’s good practice to pick a low enough version that most users can build your project with their system, but new enough to support features that make you more productive. I like using version 3.14 and newer because it supports a convenient set of command line options and includes powerful features like FetchContent. You can find more information about CMake versions at https://www.scivision.dev/cmake-changelog/ and https://cliutils.gitlab.io/modern-cmake/chapters/intro/newcmake.html. If you attempt to build a project with an incompatible version of CMake, it will stop with an error telling you what went wrong.

Line two specifies the project name and serves as a virtual anchor for the top of the build system. While not strictly essential for the most straightforward projects, I recommend adding this line. CMake will issue a warning if you don’t specify one, and you’re likely to run into build failures if you use an IDE or have a more complex solution. You leverage the virtual anchor by using the PROJECT_SOURCE_DIR and other variables. You don’t need to use this variable, but it can be advantageous in some circumstances. Additionally, you can use other optional parameters with the ‘project’ directive. See the project documentation for more information. Many projects use this simple form; you don’t need to get fancier.

We define a new target on line four called “helloworld.” Targets are a fundamental building block in Modern CMake (Version 3.0 and newer). This line inherently specifies the target type, as well as an explicit name. In this case, we’re adding an executable or application. Our example includes one file: main.cpp. Most projects will contain more files. I should have included misc.h in this list, but left it out to demonstrate a few things we’ll discuss later. You can specify the entire add_executable command on one line or spread it across multiple lines. You don’t need to escape newlines in CMake files. You should include all the files for an application in the file list. In other words, you should list both header (.h/.hpp) and implementation files (.cpp). While recording the header files is not strictly necessary, CMake gurus recommend it, and it will enable IDEs to work better for you. IDEs use this list to populate project views correctly, for example. As an interesting aside, CMake automatically generates dependencies for each build artifact, but it doesn’t need header files in the target definition to do so.

The last command is an ‘install’ command on line six. The TARGETS form of this command tells CMake to generate an ‘install’ target for the list of targets following the TARGETS keyword. This is a very succinct way to install our helloworld application. See the documentation for other parameters to ‘install.’ They are important for more complicated projects like libraries. CMake will copy the ‘helloworld’ application to ${CMAKE_INSTALL_PREFIX}/bin by default. The CMAKE_INSTALL_PREFIX variable defaults to /usr/local on Linux, for example. You can override the default by setting it with a ‘-D’ switch. I often use ‘-DCMAKE_INSTALL_PREFIX=~/.local’ to build personal binaries. In addition, the ‘install’ command will operate differently for libraries, headers, and other build artifacts.

Building Our Project With MacOS Command Line

How do we perform a build? Let’s start with a MacOS command line build. I use Homebrew to provide a GNU-like experience. You could use other commands to accomplish these results, but this is a modern way to do it. I will show some variation in following examples. In this example, I gave CMake two arguments during the config phase: a build-type of ‘Debug’ and an install prefix of ‘~/.local/bin’. The build-type ‘Debug’ tells clang to add ‘-g’ to CXX_FLAGS so the compiler generates debug symbols and turns off optimization. Turning off optimization makes source-level debugging easier.

➜  git git clone https://github.com/polishedprogrammer/cmake-intro.git
Cloning into 'cmake-intro'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 18 (delta 5), reused 14 (delta 5), pack-reused 0
Receiving objects: 100% (18/18), done.
Resolving deltas: 100% (5/5), done.
➜  git cd cmake-intro
➜  cmake-intro git:(main) cmake -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/.local
-- The C compiler identification is AppleClang 13.0.0.13000029
-- The CXX compiler identification is AppleClang 13.0.0.13000029
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/polished/git/cmake-intro/build
➜  cmake-intro git:(main) ✗ cmake --build build
[ 50%] Building CXX object CMakeFiles/helloworld.dir/main.cpp.o
[100%] Linking CXX executable helloworld
[100%] Built target helloworld
➜  cmake-intro git:(main) ✗ build/helloworld
Hello world! This is 'helloworld'.

Verbose Builds – CLI

Want to see what your build system is doing? Make it provide more verbose output. With CMake version 3.14 and newer, you can pass a ‘-v’ flag on the command line. By using the ‘cmake –build $build_dir’ pattern, you leverage CMake as a front-end for whatever build system you generated for your project. It will control the specific tool underneath, whether it be Make, Ninja, or something else.

Build Faster Using The Job Server – CLI

If you want to leverage more than one CPU core when you build, use the ‘-j’ switch. Issue a solo ‘-j’ to get an optimal number of jobs to match your CPU, or specify an explicit number of jobs for custom control. I usually use a solo ‘-j’ as it’s quicker to type and provides the quickest builds no matter what system I’m on.

cmake –build build -j    # automatically selects optimal number of jobs based on CPU cores
cmake –build build -j 4  # uses four jobs

Note I built this application on an Intel Mac, but you could also build it using identical commands for an M1/M2-based Mac.

Windows Visual Studio Build

Let’s contrast CLI builds with Visual Studio on Windows for a GUI work-flow. Many command line builds are almost identical to what we did on MacOS above. A lot of users are more comfortable building with an IDE or GUI. This example should give you an idea of how easy it is to use CMake in a GUI-based IDE.

Start off by cloning the repository using Visual Studio or your favorite method. Open the project and the select “build”. You will see something like the following text in your build output window. This is Visual Studio 2019, but it should work with many other versions.

>------ Build All started: Project: cmake-intro, Configuration: x64-Debug (default) ------
  [1/2] Building CXX object CMakeFiles\helloworld.dir\main.cpp.obj
  [2/2] Linking CXX executable helloworld.exe
  LINK : helloworld.exe not found or not built by the last incremental link; performing full link

Build All succeeded.

When you click “run” you will see the following output in a new window. You can do source level debugging and everything else you normally do in Visual Studio with this project.

Hello world! This is 'helloworld'.

C:\Users\natha\Source\Repos\cmake-intro\out\build\x64-Debug (default)\helloworld.exe (process 18040) exited with code 0.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
Visual Studio Screenshot
Visual Studio 2019 Screenshot Building Our helloworld Project

Linux CLI Build

Building ‘helloworld’ on Linux is almost identical to building on MacOS with Homebrew. I’ve included my shell session below. Note that I did a Release build here, as opposed to Debug in the MacOS CLI build. I also used a build directory called “output” instead of “build”. There’s nothing magic about the build directory names. Common choices are “debug”, “release”, “build”, etc. In addition, you can create more than one build directory for any project. You can create a build directory outside of the project directory, too.

➜  git git clone https://github.com/polishedprogrammer/cmake-intro.git
Cloning into 'cmake-intro'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 18 (delta 5), reused 14 (delta 5), pack-reused 0
Unpacking objects: 100% (18/18), done.
➜  git cd cmake-intro
➜  cmake-intro git:(main) cmake -B output -DCMAKE_BUILD_TYPE=Release
-- The C compiler identification is GNU 7.5.0
-- The CXX compiler identification is GNU 7.5.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/polished/git/cmake-intro/output
➜  cmake-intro git:(main) ✗ cmake --build output -j -v
/opt/cmake/cmake-3.23.1-linux-x86_64/bin/cmake -S/home/polished/git/cmake-intro -B/home/polished/git/cmake-intro/output --check-build-system CMakeFiles/Makefile.cmake 0
/opt/cmake/cmake-3.23.1-linux-x86_64/bin/cmake -E cmake_progress_start /home/polished/git/cmake-intro/output/CMakeFiles /home/polished/git/cmake-intro/output//CMakeFiles/progress.marks
/usr/bin/make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/polished/git/cmake-intro/output'
/usr/bin/make  -f CMakeFiles/helloworld.dir/build.make CMakeFiles/helloworld.dir/depend
make[2]: Entering directory '/home/polished/git/cmake-intro/output'
cd /home/polished/git/cmake-intro/output && /opt/cmake/cmake-3.23.1-linux-x86_64/bin/cmake -E cmake_depends "Unix Makefiles" /home/polished/git/cmake-intro /home/polished/git/cmake-intro /home/polished/git/cmake-intro/output /home/polished/git/cmake-intro/output /home/polished/git/cmake-intro/output/CMakeFiles/helloworld.dir/DependInfo.cmake --color=
make[2]: Leaving directory '/home/polished/git/cmake-intro/output'
/usr/bin/make  -f CMakeFiles/helloworld.dir/build.make CMakeFiles/helloworld.dir/build
make[2]: Entering directory '/home/polished/git/cmake-intro/output'
[ 50%] Building CXX object CMakeFiles/helloworld.dir/main.cpp.o
/usr/bin/c++   -O3 -DNDEBUG -MD -MT CMakeFiles/helloworld.dir/main.cpp.o -MF CMakeFiles/helloworld.dir/main.cpp.o.d -o CMakeFiles/helloworld.dir/main.cpp.o -c /home/polished/git/cmake-intro/main.cpp
[100%] Linking CXX executable helloworld
/opt/cmake/cmake-3.23.1-linux-x86_64/bin/cmake -E cmake_link_script CMakeFiles/helloworld.dir/link.txt --verbose=1
/usr/bin/c++ -O3 -DNDEBUG CMakeFiles/helloworld.dir/main.cpp.o -o helloworld
make[2]: Leaving directory '/home/polished/git/cmake-intro/output'
[100%] Built target helloworld
make[1]: Leaving directory '/home/polished/git/cmake-intro/output'
/opt/cmake/cmake-3.23.1-linux-x86_64/bin/cmake -E cmake_progress_start /home/polished/git/cmake-intro/output/CMakeFiles 0
➜  cmake-intro git:(main) ✗ output/helloworld
Hello world! This is 'helloworld'.

Windows Cygwin CLI Build

You have many options for Windows CLI builds. I’m using cygwin64 here. I’ve never used ming64w, but that’s another option. I did a verbose build in this example like I did with Linux above. Compare the commands to see how different they are.

polished@LAPTOP-IN9Q16T3 ~/git
$ git clone https://github.com/polishedprogrammer/cmake-intro.git
Cloning into 'cmake-intro'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 18 (delta 5), reused 14 (delta 5), pack-reused 0
Unpacking objects: 100% (18/18), 3.75 KiB | 46.00 KiB/s, done.

polished@LAPTOP-IN9Q16T3 ~/git
$ cd cmake-intro

polished@LAPTOP-IN9Q16T3 ~/git/cmake-intro
$ cmake -B debug -DCMAKE_BUILD_TYPE=Debug
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++.exe
-- Check for working CXX compiler: /usr/bin/c++.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Generating done
-- Build files have been written to: /home/polished/git/cmake-intro/debug

polished@LAPTOP-IN9Q16T3 ~/git/cmake-intro
$ cmake --build debug -v
/usr/bin/cmake.exe -S/home/polished/git/cmake-intro -B/home/polished/git/cmake-intro/d/usr/bin/cmake.exe -S/home/polished/git/cmake-intro -B/home/polished/git/cmake-intro/debug --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake.exe -E cmake_progress_start /home/polished/git/cmake-intro/debug/CMakeFiles /home/polished/git/cmake-intro/debug/CMakeFiles/progress.marks
keFiles /home/polished/git/cmake-intro/debug/CMakeFiles/progress.marks
/usr/bin/make -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/polished/git/cmake-intro/debug'
/usr/bin/make -f CMakeFiles/helloworld.dir/build.make CMakeFiles/helloworld.dir/depend
make[2]: Entering directory '/home/polished/git/cmake-intro/debug'
cd /home/polished/git/cmake-intro/debug && /usr/bin/cmake.exe -E cmake_depends "Unix Makefiles" /home/polished/git/cmake-intro /home/polished/git/cmake-intro /home/polished/git/cmake-intro/debug /home/polished/git/cmake-intro/debug /home/polished/git/cmake-intro/debug/CMakeFiles/helloworld.dir/DependInfo.cmake --color=
Dependee "/home/polished/git/cmake-intro/debug/CMakeFiles/helloworld.dir/DependInfo.cmake" is newer than depender "/home/polished/git/cmake-intro/debug/CMakeFiles/helloworld.dir/depend.internal".
Dependee "/home/polished/git/cmake-intro/debug/CMakeFiles/CMakeDirectoryInformation.cmake" is newer than depender "/home/polished/git/cmake-intro/debug/CMakeFiles/helloworld.dir/depend.internal".
Scanning dependencies of target helloworld
make[2]: Leaving directory '/home/polished/git/cmake-intro/debug'
/usr/bin/make -f CMakeFiles/helloworld.dir/build.make CMakeFiles/helloworld.dir/build
make[2]: Entering directory '/home/polished/git/cmake-intro/debug'
[ 50%] Building CXX object CMakeFiles/helloworld.dir/main.cpp.o
/usr/bin/c++.exe    -g   -o CMakeFiles/helloworld.dir/main.cpp.o -c /home/polished/git/cmake-intro/main.cpp
[100%] Linking CXX executable helloworld.exe
/usr/bin/cmake.exe -E cmake_link_script CMakeFiles/helloworld.dir/link.txt --verbose=1
/usr/bin/c++.exe -g  -Wl,--enable-auto-import CMakeFiles/helloworld.dir/main.cpp.o  -o helloworld.exe -Wl,--out-implib,libhelloworld.dll.a -Wl,--major-image-version,0,--minor-image-version,0
make[2]: Leaving directory '/home/polished/git/cmake-intro/debug'
[100%] Built target helloworld
make[1]: Leaving directory '/home/polished/git/cmake-intro/debug'
/usr/bin/cmake.exe -E cmake_progress_start /home/polished/git/cmake-intro/debug/CMakeFiles 0

polished@LAPTOP-IN9Q16T3 ~/git/cmake-intro
$ debug/helloworld.exe
Hello world! This is 'helloworld'.

Windows Native Command Prompt Build

I used the “x64 Native Tools Command Prompt” to run the following commands. This example uses a different “CMake incantation” you may be more familiar with. 1. create a directory, 2. change into the directory, 3. issue the cmake command and point back to the parent directory, 4. do the build from within that directory. Note I still use the ‘cmake –build’ command as it’s easier in Windows where we’re not using GNU Make. Specify ‘.’ for the current directory when using this model, instead of a direction name like ‘bulid’. Note how different the toolchain commands are in this example compared to other builds. CMake takes care of all those details: compiler and linker executables and the switches and arguments they need.

C:\cygwin64\home\polished\git>git clone https://github.com/polishedprogrammer/cmake-intro.git
Cloning into 'cmake-intro'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 18 (delta 5), reused 14 (delta 5), pack-reused 0
Unpacking objects: 100% (18/18), done.

C:\cygwin64\home\polished\git>cd cmake-intro

C:\cygwin64\home\polished\git\cmake-intro>md build

C:\cygwin64\home\polished\git\cmake-intro>cd build

C:\cygwin64\home\polished\git\cmake-intro\build>cmake ..
-- Building for: Visual Studio 16 2019
-- Selecting Windows SDK version 10.0.18362.0 to target Windows 10.0.22621.
-- The C compiler identification is MSVC 19.26.28806.0
-- The CXX compiler identification is MSVC 19.26.28806.0
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.26.28801/bin/Hostx64/x64/cl.exe
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.26.28801/bin/Hostx64/x64/cl.exe - works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.26.28801/bin/Hostx64/x64/cl.exe
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.26.28801/bin/Hostx64/x64/cl.exe - works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: C:/cygwin64/home/polished/git/cmake-intro/build

C:\cygwin64\home\polished\git\cmake-intro\build>cmake --build . -v
Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 2/21/2023 10:11:04 PM.
Project "C:\cygwin64\home\polished\git\cmake-intro\build\ALL_BUILD.vcxproj" on node 1 (default targets).
Project "C:\cygwin64\home\polished\git\cmake-intro\build\ALL_BUILD.vcxproj" (1) is building "C:\cygwin64\home\polished\git\cmake-intro\build\ZERO_CHECK.vcxproj" (2) on node 1 (default targets).
PrepareForBuild:
  Creating directory "x64\Debug\ZERO_CHECK\".
  Creating directory "x64\Debug\ZERO_CHECK\ZERO_CHECK.tlog\".
InitializeBuildStatus:
  Creating "x64\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Checking Build System
FinalizeBuildStatus:
  Deleting file "x64\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild".
  Touching "x64\Debug\ZERO_CHECK\ZERO_CHECK.tlog\ZERO_CHECK.lastbuildstate".
Done Building Project "C:\cygwin64\home\polished\git\cmake-intro\build\ZERO_CHECK.vcxproj" (default targets).

Project "C:\cygwin64\home\polished\git\cmake-intro\build\ALL_BUILD.vcxproj" (1) is building "C:\cygwin64\home\polished\git\cmake-intro\build\helloworld.vcxproj" (3) on node 1 (default targets).
PrepareForBuild:
  Creating directory "helloworld.dir\Debug\".
  Creating directory "C:\cygwin64\home\polished\git\cmake-intro\build\Debug\".
  Creating directory "helloworld.dir\Debug\helloworld.tlog\".
InitializeBuildStatus:
  Creating "helloworld.dir\Debug\helloworld.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Building Custom Rule C:/cygwin64/home/polished/git/cmake-intro/CMakeLists.txt
ClCompile:
  C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.26.28801\bin\HostX64\x64\CL.exe /c /Zi /nologo /W3 /WX- /diagnostics:column /Od /Ob0 /D WIN32 /D _WINDOWS /D "CMAKE_INTDIR=\"Debug\"" /D _MBCS /Gm- /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /GR /Fo"helloworld.dir\Debug\\" /Fd"helloworld.dir\Debug\vc142.pdb" /Gd /TP /errorReport:queue "C:\cygwin64\home\polished\git\cmake-intro\main.cpp"
  main.cpp
Link:
  C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.26.28801\bin\HostX64\x64\link.exe /ERRORREPORT:QUEUE /OUT:"C:\cygwin64\home\polished\git\cmake-intro\build\Debug\helloworld.exe" /INCREMENTAL /NOLOGO kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /DEBUG /PDB:"C:/cygwin64/home/polished/git/cmake-intro/build/Debug/helloworld.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"C:/cygwin64/home/polished/git/cmake-intro/build/Debug/helloworld.lib" /MACHINE:X64  /machine:x64 helloworld.dir\Debug\main.obj
  helloworld.vcxproj -> C:\cygwin64\home\polished\git\cmake-intro\build\Debug\helloworld.exe
FinalizeBuildStatus:
  Deleting file "helloworld.dir\Debug\helloworld.tlog\unsuccessfulbuild".
  Touching "helloworld.dir\Debug\helloworld.tlog\helloworld.lastbuildstate".
Done Building Project "C:\cygwin64\home\polished\git\cmake-intro\build\helloworld.vcxproj" (default targets).

PrepareForBuild:
  Creating directory "x64\Debug\ALL_BUILD\".
  Creating directory "x64\Debug\ALL_BUILD\ALL_BUILD.tlog\".
InitializeBuildStatus:
  Creating "x64\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Building Custom Rule C:/cygwin64/home/polished/git/cmake-intro/CMakeLists.txt
FinalizeBuildStatus:
  Deleting file "x64\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild".
  Touching "x64\Debug\ALL_BUILD\ALL_BUILD.tlog\ALL_BUILD.lastbuildstate".
Done Building Project "C:\cygwin64\home\polished\git\cmake-intro\build\ALL_BUILD.vcxproj" (default targets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.22

C:\cygwin64\home\polished\git\cmake-intro\build>Debug\helloworld.exe
Hello world! This is 'helloworld'.

We could also do a 32bit x86 build by starting in a “x86 Native Tools Command Prompt”. All the other steps are the same. CMake issues different commands to use the 32bit compiler and linker, and to link against 32bit libraries, for example. It’s amazing how many platforms we can build for with a single CMakeLists.txt!

MacOS Xcode Build

To build this application on MacOS using GUI tools, clone the repo and then run the config phase of CMake to generate an Xcode project. Open that in Xcode, where you can build, debug and develop the source code. Note the “open” command is an efficient way to invoke whatever tool is associated with the filetype in MacOS. You can launch Xcode and open the project by clicking through menus, too, if you want to do it the slow way. 😉

Here’s my shell session. I used a build directory called ‘xcode’ this time, but you can call it whatever you like. Note we must explicitly specify ‘Xcode’ as a build generator. Use ‘cmake –help’ for a list of Generators you can use.

➜  cmake-intro git:(main) ✗ cmake -B xcode -G Xcode
-- The C compiler identification is AppleClang 13.0.0.13000029
-- The CXX compiler identification is AppleClang 13.0.0.13000029
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/ncrapo/git/cmake-intro/xcode
➜  cmake-intro git:(main) ✗ open xcode/helloworld.xcodeproj
Xcode Screenshot
Xcode Screenshot Building Our helloworld Project

Wrapping Things Up

I hope you see the power of using CMake. We can build our small application on many operating systems and hardware flavors with one file consisting of simple high-level commands. We could have targeted many other combinations. Spending time to learn CMake will open all these possibilities and more for you. Stay tuned for more!