CMake

CMake does not compile anything itself: it reads a CMakeLists.txt and writes the build files of a generator, make by default on Linux. The compiler runs later, from those files.

The reason to use it is portability and scale: the same file configures a build on Linux, Windows and macOS, and the target model carries include directories, compile features and linked libraries. This lesson starts with the smallest project and the three commands that build it.

The build directory also holds the cache: CMakeCache.txt remembers the compiler, the generator and every option, which is why a second configure is fast and why deleting the directory is the reliable reset.

The generator is chosen at configure time, so the same project builds with make, Ninja or an IDE project file. The commands of this lesson do not change with the generator, which is the point of the two-step interface.

A build directory can be deleted at any time and regenerated from CMakeLists.txt, which is the recovery step for a build that behaves strangely. The sources never live there.

The two commands are also what a script in the repository runs, so the build on a server is the build on a laptop.

A configure that fails leaves the build directory in a half-written state; the next configure overwrites it, and deleting the directory is the reliable reset.

CMakeLists.txt

The file names the minimum CMake version, the project, the C++ standard and one executable. project(toolbox CXX) tells CMake to look for a C++ compiler, and add_executable builds the target from main.cpp.

The program is a hello world with a version macro in the output. The standard is set for the target, which is the modern form and the subject of the next lesson.

CMakeLists.txt lives in the source directory and describes the project, not the build. The build directory is a separate tree that CMake fills with generated files.

CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(toolbox CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(toolbox main.cpp)

main.cpp

#include <iostream> int main() { std::cout << "built by CMake\n"; }

cmake -S . -B build

-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/cppcourse/work/L099/s0/build

Configure and build

cmake -S . -B build reads the source directory and writes a build tree into build. The output names the compiler it found and ends with the line that says where the files went.

cmake --build build runs the generator on that tree and compiles only what is out of date. The program then runs the binary from the build directory.

The two steps are separate on purpose: configure once, build many times. A build script or an IDE runs the same two commands, which is why a project can be built the same way from a terminal and from an editor.

CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(toolbox CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(toolbox main.cpp)

main.cpp

#include <iostream> int main() { std::cout << "built by CMake\n"; }

cmake -S . -B build
cmake --build build
./build/toolbox

-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/cppcourse/work/L099/s1/build
[ 50%] Building CXX object CMakeFiles/toolbox.dir/main.cpp.o
[100%] Linking CXX executable toolbox
[100%] Built target toolbox
built by CMake

Rebuild after a change

A second build finds the target up to date and prints nothing but the summary. That is the timestamp check of lesson 097, generated by CMake rather than written by hand.

The program builds once, then builds again. The second run prints the built target line and stops, which is the whole output of an up-to-date build.

CMake tracks the headers a source includes, so a header change does rebuild the object; that is the dependency problem of lesson 098 solved by the generator.

CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(toolbox CXX) add_executable(toolbox main.cpp)

main.cpp

#include <iostream> int main() { std::cout << "built by CMake\n"; }

cmake -S . -B build
cmake --build build
cmake --build build

-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/cppcourse/work/L099/s2/build
[ 50%] Building CXX object CMakeFiles/toolbox.dir/main.cpp.o
[100%] Linking CXX executable toolbox
[100%] Built target toolbox
[100%] Built target toolbox

A source that is not there

CMake checks the sources of a target at configure time. A file that does not exist is a configure error, and the message names the file and the target that wanted it.

The file below lists missing.cpp, which is not in the directory. CMake stops with Cannot find source file and a second error about the target having no sources, before the compiler is started.

The fix is the file name. A rename in the tree without a rename in CMakeLists.txt produces exactly this message, and it is the first thing to check when a configure that used to work stops.

cmake_minimum_required(VERSION 3.20) project(toolbox CXX) add_executable(toolbox missing.cpp)

cmake -S . -B build

-- The CXX compiler identification is GNU 15.2.0
-- 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 (0.4s)
CMake Error at CMakeLists.txt:4 (add_executable):
Cannot find source file:

missing.cpp


CMake Error at CMakeLists.txt:4 (add_executable):
No SOURCES given to target: toolbox


CMake Generate step failed. Build files cannot be regenerated correctly.

Exercises

  1. Add a second source file to the executable and rebuild.
  2. Delete the build directory and configure again, and compare the output with the first configure.
  3. Run `cmake --build build --verbose` and find the g++ command CMake generated.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does cmake -S . -B build do?

    Answer

    Reads CMakeLists.txt and writes build files into build

    Configure reads the source tree and generates the build tree; no compiler runs at this step.

  2. What does cmake --build build do?

    Answer

    Runs the generator in build and compiles what is out of date

    It invokes the underlying build tool, make here, on the generated files.

  3. Which command declares a C++ executable target called toolbox?

    Answer

    add_executable(toolbox main.cpp)

    add_executable creates an executable target from the listed sources.

  4. What does project(toolbox CXX) tell CMake?

    Answer

    That the project is written in C++ and needs a C++ compiler

    The language argument makes CMake look for a C++ compiler and enable the C++ rules.

  5. Where does the executable appear after a default configure and build?

    Answer

    In the build directory

    An out-of-source build keeps every generated file, including the binary, under the build tree.

  6. What does the second cmake --build build print when nothing changed?

    Answer

    Only the built target summary

    make finds the target current and prints the summary line, with no compiler output.

  7. What happens when CMakeLists.txt has no project() call?

    Answer

    CMake stops: no project has been declared

    The project call enables the languages; without it the target commands have no compiler.

  8. What does set(CMAKE_CXX_STANDARD 23) do?

    Answer

    Sets the default C++ standard for targets in this directory

    It sets the standard property that targets inherit, unless a target overrides it.

  9. Which files does the build directory contain?

    Answer

    Generated build files, object files and the executable

    Everything generated, from the makefiles to the objects and the binary, lives under build.

  10. Why is the build directory kept out of the source tree?

    Answer

    So a clean build is one directory removal and several builds can coexist

    An out-of-source build keeps the source clean and allows Debug and Release trees side by side.

The code on this page was built with g++ 15.2.0 and CMake 4.2.3 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.

Article author: Arthur Isaev