Notes on CMake (part 1)
Table of contents
Open Table of contents
Introduction
CMake is a cross-platform build system generator that follows a two-step process:
- Configure: Reads
CMakeLists.txtand builds an internal project representation - Generate: Creates platform-specific build files (Makefiles, Visual Studio projects, etc.)
Project Setup
A fundamental part of CMake is the concept of separate source and build directories:
- Source Directory: Where the
CMakeLists.txtfile is located - Build Directory: Where build artifacts are created (also called the “binary directory”)
- Contains
CMakeCache.txtwhich stores information for reuse on subsequent runs
- Contains
Out-of-Source Build (Recommended)
mkdir build
cd build
cmake -G "Unix Makefiles" ..
You can also set the CMAKE_GENERATOR environment variable to specify your preferred generator.
Typical output after configuration:
-- Configuring done
-- Generating done
-- Build files have been written to: /some/path/build
Running the Build
cmake --build /path/to/build --config Debug --target MyApp
Basic Project Structure
A minimal CMake project:
cmake_minimum_required(VERSION 3.12)
project(MyApp)
# Define an executable target
add_executable(MyExe main.cpp)
Building Targets
Creating Libraries
add_library(targetName [STATIC | SHARED | MODULE] source1 [source2...])
- STATIC: Creates a static library (
.aon Unix,.libon Windows) - SHARED: Creates a shared/dynamic library (
.soon Unix,.dllon Windows) - MODULE: Creates a dynamically loadable module (typically for plugins)
Linking & Dependencies
Libraries can have different types of dependency relationships:
- PRIVATE: Target uses dependency internally only
- PUBLIC: Target uses dependency both internally and in its public interface
- INTERFACE: Target doesn’t use dependency itself but its users need it (e.g., header-only)
target_link_libraries(targetName <PRIVATE|PUBLIC|INTERFACE> item1 [item2] [...])
Example
# MyLib uses Boost internally and exposes Eigen in its public API
target_link_libraries(MyLib
PRIVATE Boost::boost
PUBLIC Eigen3::Eigen
)
Working with Include Directories
The target_include_directories command specifies include directories for a target:
Syntax
target_include_directories(<target>
[SYSTEM] [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items...]
)
Dependency Propagation
| Scope | Applies To |
|---|---|
| PRIVATE | Only the target itself |
| PUBLIC | Target and any target linking to it |
| INTERFACE | Only targets that link to this target |
Examples
Basic Usage:
# Library with public headers
add_library(MyMath STATIC src/math.cpp)
target_include_directories(MyMath
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
)
Header-Only Library:
add_library(HeaderOnly INTERFACE)
target_include_directories(HeaderOnly
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
)
Advanced Features
-
System Headers: Use
SYSTEMflag to suppress compiler warningstarget_include_directories(MyLib SYSTEM PUBLIC /path/to/third_party) -
Generator Expressions: For conditional paths
target_include_directories(MyLib PUBLIC $<$<CONFIG:Debug>:${CMAKE_SOURCE_DIR}/debug_includes> )
Best Practices
- Prefer
target_include_directories()over globalinclude_directories() - Use absolute paths with
${CMAKE_CURRENT_SOURCE_DIR} - Only mark truly public headers as
PUBLIC - For libraries that will be installed:
target_include_directories(MyLib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> )
Variables & Directories
CMake provides predefined variables for directory paths:
# Set a variable
set(varName value)
# Set an environment variable
set(ENV{PATH} "$ENV{PATH}:/opt/myDir")
Directory Variables
CMAKE_SOURCE_DIR: Top-level source directoryCMAKE_BINARY_DIR: Top-level binary directoryCMAKE_CURRENT_SOURCE_DIR: Currently processed source directoryCMAKE_CURRENT_BINARY_DIR: Currently processed binary directory
Adding Subdirectories
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])