C CMake — Cross-Platform Build System Configuration
In this tutorial, you will learn about C CMake. We cover key concepts, practical examples, and best practices to help you master this topic.
CMake is a cross-platform build system generator that uses CMakeLists.txt files to define targets (executables and libraries), dependencies, and build configuration, generating native build files (Makefiles, Ninja, Visual Studio solutions) for any platform.
What You Will Learn
- Writing CMakeLists.txt for C projects
- Defining targets with add_executable and add_library
- Managing dependencies with target_link_libraries
- Finding external packages with find_package
- Setting compiler flags and build types
- Using generator expressions
- Organizing projects with subdirectories
Why It Matters
Makefiles are platform-specific and tedious to maintain. CMake abstracts away platform differences: the same CMakeLists.txt works on Linux (Makefiles), macOS (Xcode), and Windows (Visual Studio). It handles compiler detection, library discovery, and installation rules automatically. Durga Antivirus Pro uses CMake to build on Linux (apt packages), Windows (MSI installer), and macOS (DMG), all from one source tree.
Real-World Use
A scientific computing library needs to support GCC on Linux, Clang on macOS, and MSVC on Windows. The CMakeLists.txt uses find_package(OpenMP) for parallel loops, find_package(MPI) for distributed computing, and find_package(CUDA) for GPU acceleration. Each developer runs cmake .. && make regardless of their platform.
Learning Path
flowchart LR A[Libraries] --> B[CMake\nYou are here] B --> C[File I/O] style B fill:#f90,color:#fff
Minimal CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(Hello C)
add_executable(hello main.c)
Build:
mkdir build && cd build
cmake ..
make
./hello
Library with CMake
cmake_minimum_required(VERSION 3.16)
project(Calculator VERSION 1.0.0 LANGUAGES C)
# Set C standard
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Library
add_library(calc STATIC
src/calc.c
src/math_ops.c
)
target_include_directories(calc PUBLIC include)
# Executable
add_executable(calculator
src/main.c
)
target_link_libraries(calculator PRIVATE calc)
Multiple Build Types
cmake_minimum_required(VERSION 3.16)
project(MyApp C)
# Configure build types
set(CMAKE_C_FLAGS_DEBUG "-g -O0 -DDEBUG -Wall")
set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG -march=native")
add_executable(myapp main.c)
# Platform-specific settings
if(WIN32)
target_compile_definitions(myapp PRIVATE PLATFORM_WINDOWS)
target_link_libraries(myapp PRIVATE ws2_32)
elseif(APPLE)
target_compile_definitions(myapp PRIVATE PLATFORM_MACOS)
else()
target_compile_definitions(myapp PRIVATE PLATFORM_LINUX)
endif()
Build with different configurations:
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake -DCMAKE_BUILD_TYPE=Release ..
Finding and Using Packages
cmake_minimum_required(VERSION 3.16)
project(HttpServer C)
# Find required packages
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
find_package(CURL QUIET)
add_executable(server
src/server.c
src/tls.c
src/compress.c
)
target_link_libraries(server PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
)
if(CURL_FOUND)
target_link_libraries(server PRIVATE CURL::libcurl)
target_compile_definitions(server PRIVATE HAVE_CURL)
endif()
Subdirectories and Organization
# CMakeLists.txt (root)
cmake_minimum_required(VERSION 3.16)
project(TextEditor C)
add_subdirectory(src/libeditor)
add_subdirectory(src/cli)
add_subdirectory(src/tests)
# src/libeditor/CMakeLists.txt
add_library(editor
buffer.c
cursor.c
syntax.c
)
target_include_directories(editor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# src/cli/CMakeLists.txt
add_executable(cli main.c)
target_link_libraries(cli PRIVATE editor)
Generator Expressions
Generator expressions evaluate at build time for platform-specific behavior:
add_executable(myapp main.c)
# Different source files per platform
target_sources(myapp PRIVATE
$<$<PLATFORM_ID:Linux>:linux_input.c>
$<$<PLATFORM_ID:Windows>:win_input.c>
$<$<PLATFORM_ID:Darwin>:mac_input.c>
)
# Different compile options per compiler
target_compile_options(myapp PRIVATE
$<$<C_COMPILER_ID:GNU>:-Wall -Wextra>
$<$<C_COMPILER_ID:Clang>:-Weverything>
$<$<C_COMPILER_ID:MSVC>:/W4>
)
Installing and Packaging
cmake_minimum_required(VERSION 3.16)
project(MyLib VERSION 2.1.0 LANGUAGES C)
add_library(mylib SHARED src/mylib.c)
target_include_directories(mylib PUBLIC include)
# Install rules
install(TARGETS mylib
LIBRARY DESTINATION lib
PUBLIC_HEADER DESTINATION include
)
install(FILES include/mylib.h DESTINATION include)
# CPack for packaging
include(CPack)
Common Mistakes
Not creating a separate build directory: Running cmake in the source directory creates clutter. Always use
mkdir build && cd build && cmake ...Forgetting cmake_minimum_required: CMake policy changes between versions. Without this line, CMake uses the latest policies, which may break on older CMake versions.
Mixing PUBLIC, PRIVATE, and INTERFACE incorrectly: PUBLIC: used by the target and its dependents. PRIVATE: used only by the target. INTERFACE: used only by dependents (for header-only libraries).
Hardcoding paths instead of using generator expressions:
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)is correct. Hardcoding/home/user/project/includebreaks on other machines.Not setting C++_STANDARD if using C++: CMake defaults to no standard. Set
set(CMAKE_C_STANDARD 11)for C orset(CMAKE_CXX_STANDARD 17)for C++ projects.
Practice Questions
- What is the purpose of cmake_minimum_required?
- What is the difference between add_executable and add_library?
- How does find_package locate and configure external libraries?
- What is a generator expression and when would you use one?
- Challenge: Write a CMakeLists.txt for a project that builds a shared library (libgeometry.so) for shape calculations, a unit test executable, and a main application. The library uses math functions from libm. The test executable links against libgeometry and a testing framework (find_package or FetchContent). Support both Debug and Release builds. Install the library and headers.
Mini Project
Build a complete C project with CMake:
- Project name: DataProcessor
- Structure: src/, include/, tests/, examples/, tools/
- Library target: dataproc (shared library) in src/
- Functions: csv_parse, json_parse, stats_mean, stats_stddev, filter, sort
- Test target: test_dataproc linking against dataproc and a simple test framework
- Example target: example using dataproc to parse a CSV and compute statistics
- Install target: install the library, headers, and examples
- CPack: generate a .deb package and a tarball
- Support: BUILD_SHARED_LIBS option, BUILD_TESTING option
- Use: generator expressions for platform-specific flags
FAQ
{{< faq "How do I add a compiler flag?" "Use target_compile_options(target PRIVATE -Wall) for per-target flags, or set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -Wall\") for global flags." >}}What is Next
Proceed to File I/O to learn about reading and writing files in C. Then explore Standard Library for commonly used libc functions.