These four lines are the start of every CMake project you'll write or read.
A minimal CMakeLists.txt:
cmake_minimum_required(VERSION 3.25)
project(hello VERSION 1.0 LANGUAGES CXX)
add_executable(hello main.cpp)
target_compile_features(hello PRIVATE cxx_std_20)cmake_minimum_requiredmust come first. It states the oldest CMake you support and switches on the behaviour (policies) of that version. CMake 4 refuses projects that declare a minimum below 3.5.projectnames the project, optionally gives a version, and enables languages, which triggers compiler detection.LANGUAGES CXXavoids also searching for a C compiler.add_executable(name sources...)creates a target: a thing to build, with its own sources and settings.target_compile_features(... cxx_std_20)requires C++20 for that target; CMake adds the right flag for each compiler.
Pass settings at configure time with -D: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release. They are stored in the build tree's cache, so later configures remember them.
List source files explicitly rather than using file(GLOB ...). A glob is evaluated only at configure time, so a newly added file isn't compiled until someone reruns CMake. Explicit lists also make reviews show exactly what's built.
Command names are case-insensitive (ADD_EXECUTABLE works) but lower case is the modern convention.
Example
cmake_minimum_required(VERSION 3.25)
project(hello VERSION 1.0 LANGUAGES CXX)
add_executable(hello main.cpp greet.cpp)
target_compile_features(hello PRIVATE cxx_std_20)
# main.cpp:
# #include <print> // or <iostream> before C++23
# int main() { std::puts("hello"); }
#
# $ cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
# $ cmake --build build
# [3/3] Linking CXX executable hello
# $ ./build/hello
# helloRemember
- cmake_minimum_required first, then project(), then targets
- add_executable creates a target from a list of sources
- Prefer explicit source lists over file(GLOB)
Watch out for
- Globbing sources and wondering why a new .cpp file isn't compiled until CMake is rerun.