Fundamentals 13 min read

Master CMake Basics: A Simple, Step‑by‑Step Guide to Build Automation

This article walks you through why CMake is a more elegant, cross‑platform alternative to hand‑written Makefiles, explains its core benefits, and provides a complete, hands‑on tutorial that covers the basic workflow, a minimal project example, multi‑source handling, and multi‑directory organization with clear code snippets.

Linux Kernel Journey
Linux Kernel Journey
Linux Kernel Journey
Master CMake Basics: A Simple, Step‑by‑Step Guide to Build Automation

Why Choose CMake Over Hand‑Written Makefiles

Makefiles are the traditional way to compile C/C++ projects, but they become cumbersome as the number of source files grows. CMake acts as a project manager that abstracts away low‑level build details, generates native build files (Makefile, Visual Studio projects, Xcode projects) and supports cross‑platform development.

What Is CMake and Its Core Benefits

No manual Makefile writing : Write a simple CMakeLists.txt and let CMake generate the appropriate build files.

Cross‑platform support : Works on Linux, Windows, and macOS.

Automatic dependency handling : Changes in source files trigger dependency updates automatically.

Scales to large projects : Handles hundreds of source files reliably.

In short, CMake = convenience + efficiency .

Basic CMake Workflow

Write the menu : Create a CMakeLists.txt that lists the source files to compile.

Configure the kitchen : Run cmake to generate the native build system (e.g., a Makefile).

Cook the meal : Invoke the generated build tool (e.g., make) to compile the program.

The following diagram illustrates the flow from source code to final executable:

项目源码
   │
   └──► CMakeLists.txt   # Write CMake build file
          │
          ▼
   cmake (command)        # Generate Makefile
          │
          ▼
   [Makefile]              # Auto‑generated
          │
          ▼
   make (command)          # Build target
          │
          ▼
   目标文件                # Executable or library

First Simple CMake Project

File Structure

my_project/
├── main.cpp
├── CMakeLists.txt

CMakeLists.txt Content

# Minimum required CMake version
cmake_minimum_required(VERSION 3.10)

# Project name
project(HelloCMake)

# Create executable
add_executable(hello main.cpp)

Explanation: cmake_minimum_required() locks the CMake version to avoid compatibility issues. project() sets the project name. add_executable() tells CMake to build an executable called hello from main.cpp.

Build Steps

mkdir build          # Create out‑of‑source build directory
cd build
cmake ..             # Generate Makefile in the build folder
make                 # Compile
./hello              # Run the program (outputs "Hello, CMake!")

Handling Multiple Source Files and Directories

Adding Multiple Sources Directly

add_executable(hello main.cpp utils.cpp)

All listed files are compiled into the hello executable.

Using Variables for Source Management

set(SOURCES main.cpp utils.cpp)
add_executable(hello ${SOURCES})

Changing the variable updates the build without editing the command list.

Accessing Environment Variables

set(MY_PATH $ENV{HOME})

CMake can read system environment variables via the $ENV{} syntax.

Multi‑Directory Project Layout

A typical large‑scale layout separates source, libraries, and headers:

my_project/
├── CMakeLists.txt          # Root configuration
├── src/
│   ├── CMakeLists.txt      # src-specific configuration
│   ├── main.cpp
│   ├── utils.cpp
│   └── helper.cpp
├── libs/
│   ├── CMakeLists.txt      # Library configuration
│   ├── lib.cpp
│   └── lib.h
├── include/
│   ├── utils.h
│   └── helper.h
└── build/                  # Out‑of‑source build directory

Root CMakeLists.txt

# Root CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject)

add_subdirectory(src)
add_subdirectory(libs)

It sets the global project name, enforces the minimum CMake version, and includes the src and libs sub‑directories.

src/CMakeLists.txt

# src/CMakeLists.txt
set(SOURCES
    main.cpp
    utils.cpp
    helper.cpp
)
add_executable(hello ${SOURCES})

target_include_directories(hello PRIVATE ${PROJECT_SOURCE_DIR}/include)

target_link_libraries(hello PRIVATE mylib)

Key actions: set(SOURCES ...) stores source file names in a variable. add_executable() creates the hello target. target_include_directories() adds the include/ folder to the include path. target_link_libraries() links the external static library mylib.

libs/CMakeLists.txt

# libs/CMakeLists.txt
set(LIB_SOURCES
    lib.cpp
)
add_library(mylib STATIC ${LIB_SOURCES})   # Use SHARED for a shared library

target_include_directories(mylib PUBLIC ${PROJECT_SOURCE_DIR}/include)

This creates a static library mylib and makes its headers visible to dependants.

Include Directory

Header files live in include/. They do not need to be listed in the CMakeLists.txt files; CMake discovers them via the target_include_directories() calls.

Build Directory

All compilation output (object files, executables, etc.) is placed in the separate build/ folder, keeping the source tree clean.

mkdir build
cd build
cmake ..
make

Conclusion

By structuring a project with a root CMakeLists.txt that includes sub‑directories, using variables to manage source lists, and keeping build artifacts in an out‑of‑source build/ folder, CMake provides a modular, scalable, and cross‑platform build system suitable for both small examples and large codebases.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

cross-platformBuild AutomationCCMakeMakefileVariablesProject Structure
Linux Kernel Journey
Written by

Linux Kernel Journey

Linux Kernel Journey

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.