🧱 CMake Build System (C Project Guide)

Professionally styled HTML version of the original README, with all content preserved and presented in a polished, readable format.

🧱 CMake Build System (C Project Guide)

A complete beginner β†’ intermediate guide to understanding and using CMake with Make & Ninja on Windows for building C projects.


πŸ“Œ Table of Contents

  1. What is CMake?
  2. Build System Overview (How everything works)
  3. Installing Tools (Windows)
  4. First CMake Project (Hello World)
  5. Using Different Generators (Make vs Ninja)
  6. Multi-file Project Structure
  7. Libraries in CMake
  8. Common Commands Cheat Sheet
  9. Debug vs Release Builds
  10. Common Mistakes
  11. Recommended Workflow
  12. Complete CMake Commands Reference

1️⃣ What is CMake?

CMake is a build system generator.

πŸ‘‰ It does NOT compile your code directly.

Instead:

  • It generates build files for tools like:

    • Make
    • Ninja
    • Visual Studio

2️⃣ 🧠 How the Build System Works

πŸ” Full Flow

Your Code β†’ CMake β†’ Build System (Ninja/Make) β†’ Compiler (gcc/clang) β†’ Executable

πŸ” Breakdown

Component Role
CMake Generates build instructions
Ninja / Make Executes build
gcc / clang Compiles code

3️⃣ βš™οΈ Install Everything (Windows)

Step 1: Install CMake

Download from: πŸ‘‰ https://cmake.org/download/

βœ” Choose:

  • Windows x64 Installer (.msi)

βœ” During install:

  • βœ… Add CMake to PATH

Step 2: Install Compiler

Download from: πŸ‘‰ https://www.mingw-w64.org/

Or use MSYS2: πŸ‘‰ https://www.msys2.org/

Install:

pacman -S mingw-w64-x86_64-gcc

Download: πŸ‘‰ https://github.com/ninja-build/ninja/releases

Or via Chocolatey:

choco install ninja

Step 4: Verify Installation

cmake --version
gcc --version
ninja --version

4️⃣ πŸš€ First CMake Project

Folder Structure

hello_project/
β”œβ”€β”€ CMakeLists.txt
└── main.c

main.c

#include <stdio.h>

int main() {
    printf("Hello from CMake!\n");
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

project(HelloProject C)

add_executable(hello main.c)

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

Run:

build\hello.exe

5️⃣ βš”οΈ Using Different Generators

πŸ”Ή Using Make (Unix Makefiles)

cmake -S . -B build -G "Unix Makefiles"
cmake --build build

cmake -S . -B build -G Ninja
cmake --build build

πŸ“Š Comparison

Feature Make Ninja
Speed Medium Fast ⚑
Complexity Higher Simpler
Recommended ❌ βœ…

6️⃣ πŸ“ Multi-file C Project (Real Example)

Project Structure

my_project/
β”œβ”€β”€ CMakeLists.txt
β”œβ”€β”€ include/
β”‚   └── math_utils.h
└── src/
    β”œβ”€β”€ main.c
    └── math_utils.c

include/math_utils.h

#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);

#endif

src/math_utils.c

#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

src/main.c

#include <stdio.h>
#include "math_utils.h"

int main() {
    int result = add(5, 3);
    printf("Result: %d\n", result);
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

project(MyProject C)

add_executable(my_app
    src/main.c
    src/math_utils.c
)

target_include_directories(my_app PRIVATE include)

Build

cmake -S . -B build -G Ninja
cmake --build build

Run:

build\my_app.exe

7️⃣ πŸ“¦ Using Libraries in CMake

CMakeLists.txt (Library Version)

cmake_minimum_required(VERSION 3.10)

project(MyProject C)

add_library(math_utils src/math_utils.c)

target_include_directories(math_utils PUBLIC include)

add_executable(my_app src/main.c)

target_link_libraries(my_app PRIVATE math_utils)

8️⃣ 🧾 CMake Command Cheat Sheet

Configure

cmake -S . -B build

Build

cmake --build build

Clean (manual)

rmdir /s /q build

Specify Generator

cmake -S . -B build -G Ninja
cmake -S . -B build -G "Unix Makefiles"

Debug Build

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug

Release Build

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

9️⃣ πŸ§ͺ Debug vs Release

Mode Use
Debug Debugging (slow, more info)
Release Optimized (fast)

πŸ”Ÿ ⚠️ Common Mistakes

❌ Building inside source folder

βœ” Always use build/


❌ Missing source files

add_executable(app main.c math_utils.c)

❌ Headers not found

target_include_directories(app PRIVATE include)

❌ Wrong generator confusion

Always specify:

cmake -S . -B build -G Ninja

1️⃣1️⃣ 🧠 Best Workflow (Recommended)

cmake -S . -B build -G Ninja
cmake --build build
build\my_app.exe

🎯 Final Summary

  • CMake β†’ generates build system
  • Ninja / Make β†’ builds code
  • gcc / clang β†’ compiles code

πŸ“Œ Tip

πŸ‘‰ Always think:

β€œCMake prepares β†’ Ninja builds β†’ Compiler compiles”


1️⃣2️⃣ Complete CMake Commands Reference

This section is a practical notebook of the most useful CMake commands, from the basics to more advanced project setup.


A. Basic CMake File Commands

cmake_minimum_required

Defines the minimum CMake version required.

cmake_minimum_required(VERSION 3.10)

Example:

cmake_minimum_required(VERSION 3.10)
project(MyProject C)

project

Defines the project name and languages used.

project(MyProject C)

You can also specify version and description:

project(MyProject VERSION 1.0 DESCRIPTION "My C project" LANGUAGES C)

add_executable

Creates an executable program.

add_executable(my_app main.c)

With multiple source files:

add_executable(my_app
    src/main.c
    src/math_utils.c
    src/file_utils.c
)

add_library

Creates a library.

Static library

add_library(math_utils STATIC src/math_utils.c)

Shared library

add_library(math_utils SHARED src/math_utils.c)

Default type

add_library(math_utils src/math_utils.c)

B. Include and Linking Commands

target_include_directories

Tells the compiler where header files are located.

target_include_directories(my_app PRIVATE include)

Scopes:

  • PRIVATE β†’ used only by this target
  • PUBLIC β†’ used by this target and targets linking to it
  • INTERFACE β†’ only for dependents

Example:

target_include_directories(math_utils PUBLIC include)

target_link_libraries

Links a target with a library.

target_link_libraries(my_app PRIVATE math_utils)

Example with system library:

target_link_libraries(my_app PRIVATE m)

link_directories

Adds library search paths. Older style; use carefully.

link_directories(path/to/libs)

Usually better to use full target-based linking instead.


C. Source File and Target Control

target_sources

Adds source files to an existing target.

target_sources(my_app PRIVATE src/extra.c)

Example:

add_executable(my_app src/main.c)
target_sources(my_app PRIVATE src/math_utils.c src/file_utils.c)

set

Creates or modifies a variable.

set(MY_SOURCES src/main.c src/math_utils.c)
add_executable(my_app ${MY_SOURCES})

Example for version numbers:

set(PROJECT_VERSION_MAJOR 1)
set(PROJECT_VERSION_MINOR 0)

unset

Removes a variable.

unset(MY_SOURCES)

D. Compiler Options and Definitions

target_compile_definitions

Adds preprocessor macros.

target_compile_definitions(my_app PRIVATE DEBUG_MODE=1)

Example in C code:

#ifdef DEBUG_MODE
printf("Debug mode enabled\n");
#endif

add_definitions

Older global style for definitions.

add_definitions(-DDEBUG_MODE)

Target-based commands are preferred.


target_compile_options

Adds compiler flags for one target.

target_compile_options(my_app PRIVATE -Wall -Wextra)

Example:

target_compile_options(my_app PRIVATE -Wall -Wextra -Wpedantic)

add_compile_options

Adds compiler flags globally.

add_compile_options(-Wall -Wextra)

set(CMAKE_C_STANDARD ...)

Sets the C standard.

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)

Example:

cmake_minimum_required(VERSION 3.10)
project(MyProject C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

E. Build Type and Output Control

CMAKE_BUILD_TYPE

Used to choose Debug, Release, etc.

Command line:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

Common values:

  • Debug
  • Release
  • RelWithDebInfo
  • MinSizeRel

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ...)

Sets where executables go.

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ...)

Sets where shared libraries go.

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ...)

Sets where static libraries go.

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

Example:

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

F. Project Organization Commands

add_subdirectory

Adds another folder containing its own CMakeLists.txt.

Project structure:

my_project/
β”œβ”€β”€ CMakeLists.txt
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ CMakeLists.txt
β”‚   └── main.c
└── lib/
    β”œβ”€β”€ CMakeLists.txt
    β”œβ”€β”€ math_utils.c
    └── math_utils.h

Top-level:

add_subdirectory(lib)
add_subdirectory(app)

include

Includes another CMake script file.

include(cmake/common_settings.cmake)

Useful for shared settings.


option

Creates a user-configurable ON/OFF option.

option(ENABLE_TESTS "Build tests" ON)

Example:

option(ENABLE_DEBUG_LOGS "Enable debug logs" OFF)

if(ENABLE_DEBUG_LOGS)
    target_compile_definitions(my_app PRIVATE DEBUG_LOGS=1)
endif()

Command line:

cmake -S . -B build -DENABLE_DEBUG_LOGS=ON

G. Conditional Logic Commands

if / elseif / else / endif

if(WIN32)
    message(STATUS "Building on Windows")
elseif(UNIX)
    message(STATUS "Building on Unix")
else()
    message(STATUS "Unknown platform")
endif()

message

Prints text during configuration.

message(STATUS "Configuring project...")
message(WARNING "This is a warning")
message(FATAL_ERROR "This stops configuration")

Types:

  • STATUS
  • WARNING
  • AUTHOR_WARNING
  • FATAL_ERROR

foreach

Loops through a list.

set(FILES main.c math_utils.c file_utils.c)

foreach(file ${FILES})
    message(STATUS "Source file: ${file}")
endforeach()

while

Loop while a condition is true.

set(COUNT 1)
while(COUNT LESS 4)
    message(STATUS "Count = ${COUNT}")
    math(EXPR COUNT "${COUNT} + 1")
endwhile()

H. File and Path Commands

file

Works with files and directories.

Read file

file(READ myfile.txt CONTENT)

Write file

file(WRITE output.txt "Hello")

Append file

file(APPEND output.txt "\nMore text")

Copy files

file(COPY assets DESTINATION ${CMAKE_BINARY_DIR})

Create directory

file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated)

configure_file

Copies a file and optionally replaces variables.

Example template config.h.in:

#define PROJECT_NAME "@PROJECT_NAME@"
#define PROJECT_VERSION "@PROJECT_VERSION@"

In CMake:

configure_file(config.h.in config.h @ONLY)

This generates config.h.


I. Finding Packages and External Libraries

find_package

Finds installed packages/libraries.

find_package(OpenSSL REQUIRED)

Example use:

find_package(OpenSSL REQUIRED)
target_link_libraries(my_app PRIVATE OpenSSL::SSL)

find_library

Finds a library file.

find_library(MATH_LIB m)

find_path

Finds header file paths.

find_path(MYLIB_INCLUDE_DIR mylib.h)

find_program

Finds an executable tool.

find_program(GIT_EXECUTABLE git)

J. Testing Commands

enable_testing

Turns on testing support.

enable_testing()

add_test

Adds a test.

add_test(NAME MyTest COMMAND my_app)

Example:

enable_testing()
add_executable(my_app src/main.c)
add_test(NAME RunApp COMMAND my_app)

Run tests:

ctest --test-dir build

K. Install Commands

install

Specifies what gets installed.

Install executable:

install(TARGETS my_app DESTINATION bin)

Install headers:

install(FILES include/math_utils.h DESTINATION include)

Install a directory:

install(DIRECTORY include/ DESTINATION include)

Install command:

cmake --install build

Set custom install path:

cmake --install build --prefix install_dir

L. Advanced Target Commands

target_link_directories

Adds link search directories to a specific target.

target_link_directories(my_app PRIVATE path/to/libs)

target_precompile_headers

Used more in C++, but available for advanced setups.

target_precompile_headers(my_app PRIVATE stdio.h)

Not very common for simple C projects.


set_target_properties

Sets advanced properties on a target.

set_target_properties(my_app PROPERTIES
    OUTPUT_NAME "myprogram"
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)

get_target_property

Reads a target property.

get_target_property(APP_TYPE my_app TYPE)
message(STATUS "Target type: ${APP_TYPE}")

M. Custom Build Steps

add_custom_command

Adds a custom step to generate files or run commands.

Example:

add_custom_command(
    OUTPUT generated.txt
    COMMAND ${CMAKE_COMMAND} -E echo "Generated file" > generated.txt
)

add_custom_target

Creates a target for custom actions.

add_custom_target(print_message ALL
    COMMAND ${CMAKE_COMMAND} -E echo "Building project..."
)

N. Useful Built-in Variables

Common built-in variables

${CMAKE_SOURCE_DIR}
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${PROJECT_NAME}
${PROJECT_VERSION}
${CMAKE_C_COMPILER}
${CMAKE_GENERATOR}
${WIN32}
${UNIX}

Example:

message(STATUS "Source dir: ${CMAKE_SOURCE_DIR}")
message(STATUS "Build dir: ${CMAKE_BINARY_DIR}")
message(STATUS "Compiler: ${CMAKE_C_COMPILER}")
message(STATUS "Generator: ${CMAKE_GENERATOR}")

O. Beginner-to-Advanced Command Line CMake Commands

Configure a project

cmake -S . -B build

Configure with Ninja

cmake -S . -B build -G Ninja

Configure with Unix Makefiles

cmake -S . -B build -G "Unix Makefiles"

Configure Debug build

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug

Configure Release build

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

Build project

cmake --build build

Build specific configuration

cmake --build build --config Release

Build with parallel jobs

cmake --build build --parallel

or

cmake --build build --parallel 4

Install project

cmake --install build

Run tests

ctest --test-dir build

Clean build manually

rmdir /s /q build

Open CMake GUI project folder

cmake-gui

List available generators

cmake --help

Show command help

cmake --help-command add_executable

Show manual

cmake --help-manual cmake-commands

P. Complete Practical Example

cmake_minimum_required(VERSION 3.15)

project(MyProject VERSION 1.0 LANGUAGES C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

option(ENABLE_WARNINGS "Enable extra warnings" ON)

add_library(math_utils src/math_utils.c)
target_include_directories(math_utils PUBLIC include)

add_executable(my_app src/main.c)
target_link_libraries(my_app PRIVATE math_utils)

if(ENABLE_WARNINGS)
    target_compile_options(my_app PRIVATE -Wall -Wextra -Wpedantic)
endif()

set_target_properties(my_app PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)

enable_testing()
add_test(NAME RunApp COMMAND my_app)

install(TARGETS my_app DESTINATION bin)
install(DIRECTORY include/ DESTINATION include)

Q. Final Command Memory Trick

Remember CMake in 3 parts:

1. Configure

cmake -S . -B build

2. Build

cmake --build build

3. Install or Run

cmake --install build

or run the executable:

build\my_app.exe

R. Best Commands to Learn First

Start with these:

cmake_minimum_required()
project()
add_executable()
add_library()
target_include_directories()
target_link_libraries()
set()
if()
message()
add_subdirectory()
option()
install()
enable_testing()
add_test()

These are enough to build most real beginner and intermediate C projects.