π§± 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
- What is CMake?
- Build System Overview (How everything works)
- Installing Tools (Windows)
- First CMake Project (Hello World)
- Using Different Generators (Make vs Ninja)
- Multi-file Project Structure
- Libraries in CMake
- Common Commands Cheat Sheet
- Debug vs Release Builds
- Common Mistakes
- Recommended Workflow
- 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
Option A: MinGW (recommended)
Download from: π https://www.mingw-w64.org/
Or use MSYS2: π https://www.msys2.org/
Install:
pacman -S mingw-w64-x86_64-gcc
Step 3: Install Ninja (optional but recommended)
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)
Build (Recommended Method)
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
πΉ Using Ninja (Recommended)
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 targetPUBLICβ used by this target and targets linking to itINTERFACEβ 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:
DebugReleaseRelWithDebInfoMinSizeRel
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:
STATUSWARNINGAUTHOR_WARNINGFATAL_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.