CMake Fundamentals for C++ Projects

CMake is a cross-platform build system generator that simplifies the compilation of complex software projects written in multiple languages. It uses configuration files named CMakeLists.txt to generate native build environments such as Makefiles or Visual Studio projects.

A minimal example starts with a source file main.cpp:

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

Accompanied by a CMakeLists.txt:

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

To build using internal (in-source) compilation:

cmake .
make
./hello

However, out-of-source builds are strongly recommended to keep generated files separate from source code:

mkdir build
cd build
cmake ..
make

Core CMake Concepts

  • project(): Declares the project name and supported languages. It implicitly defines variables like <PROJECT_NAME>_SOURCE_DIR and <PROJECT_NAME>_BINARY_DIR. Use PROJECT_SOURCE_DIR and PROJECT_BINARY_DIR for name-agnostic references.
  • set(): Assigns values to variables. Example: set(SRC_FILES main.cpp helper.cpp).
  • message(): Outputs messages during configuration. Types include STATUS, SEND_ERROR, and FATAL_ERROR.
  • add_executable(): Creates an executable target from source files.

CMake syntax rules:

  • Variables are referenced with ${VAR_NAME}.
  • Commands are case-insensitive, but uppercase is conventional.
  • Arguments are space- or semicolon-separated.

Multi-directory Projects

For structrued projects:

project_root/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   └── main.cpp
└── build/

Top-level CMakeLists.txt:

project(HelloApp)
add_subdirectory(src bin)

src/CMakeLists.txt:

add_executable(hello main.cpp)

The add_subdirectory(source_dir [binary_dir]) command includes subdirectories and optionally specifies where to place compiled outputs.

To control output locations globally:

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

Installation Rules

Use install() to define what gets deployed:

# Install documentation
install(FILES COPYRIGHT README DESTINATION share/doc/hello)

# Install scripts
install(PROGRAMS runhello.sh DESTINATION bin)

# Install entire directory
install(DIRECTORY doc/ DESTINATION share/doc/hello)

Run installation after building:

make install              # installs to default prefix (/usr/local)
make install DESTDIR=/tmp/test  # installs to /tmp/test/usr/local

The install prefix is controlled by CMAKE_INSTALL_PREFIX.

Building Libraries

Create both static and shared libraries from the same source:

Directory structure:

lib/
├── hello.h
└── hello.cpp

lib/CMakeLists.txt:

set(LIB_SOURCES hello.cpp)

add_library(hello_static STATIC ${LIB_SOURCES})
set_target_properties(hello_static PROPERTIES
    OUTPUT_NAME "hello"
    CLEAN_DIRECT_OUTPUT 1
)

add_library(hello_shared SHARED ${LIB_SOURCES})
set_target_properties(hello_shared PROPERTIES
    OUTPUT_NAME "hello"
    VERSION 1.0
    SOVERSION 1
    CLEAN_DIRECT_OUTPUT 1
)

Install headers and libraries:

install(FILES hello.h DESTINATION include/hello)
install(TARGETS hello_static hello_shared
    ARCHIVE DESTINATION lib  # static libs (.a)
    LIBRARY DESTINATION lib  # shared libs (.so)
)

Using External Libraries

When consuming installed libraries:

// main.cpp
#include <hello.h>
int main() {
    HelloFunc();
    return 0;
}

In CMakeLists.txt:

include_directories(/usr/include/hello)
add_executable(app main.cpp)
target_link_libraries(app hello)

Alternatively, use environment variables like CMAKE_INCLUDE_PATH and CMAKE_LIBRARY_PATH to hint CMake about non-standard search paths.

Tags: C++ CMake Build Systems compilation Software Engineering

Posted on Tue, 26 May 2026 22:23:10 +0000 by TRI0N