CMake Basics: Build, Install, and Package a Small Library

This is a basic end-to-end guide for modern CMake packaging.

We will create a tiny library called SimpleMath that:

  • links a lightweight dependency (spdlog),
  • exposes public headers with correct include directories,
  • installs binaries + headers,
  • exports SimpleMathConfig.cmake and SimpleMathConfigVersion.cmake,
  • is found by other projects with find_package(SimpleMath CONFIG REQUIRED),
  • uses find_dependency(spdlog) in its package config.

Useful references:

Table of contents

Minimal project structure

SimpleMath/
├─ CMakeLists.txt
├─ cmake/
│  └─ SimpleMathConfig.cmake.in
├─ include/
│  └─ simplemath/simplemath.hpp
└─ src/
   └─ simplemath.cpp

Library source

include/simplemath/simplemath.hpp

#pragma once

namespace simplemath {

double add(double a, double b);
double divide(double numerator, double denominator);

} // namespace simplemath

src/simplemath.cpp

#include <simplemath/simplemath.hpp>

#include <spdlog/spdlog.h>
#include <stdexcept>

namespace simplemath {

double add(double a, double b) {
    const auto result = a + b;
    spdlog::debug("add({}, {}) = {}", a, b, result);
    return result;
}

double divide(double numerator, double denominator) {
    if (denominator == 0.0) {
        spdlog::error("divide({}, {}): division by zero", numerator, denominator);
        throw std::invalid_argument("division by zero");
    }

    const auto result = numerator / denominator;
    spdlog::debug("divide({}, {}) = {}", numerator, denominator, result);
    return result;
}

} // namespace simplemath

Full CMakeLists.txt

This file handles building, linking, include directories, install rules, exported targets, and package config generation.

cmake_minimum_required(VERSION 3.20)
project(SimpleMath VERSION 1.0.0 LANGUAGES CXX)

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

add_library(SimpleMath src/simplemath.cpp)
add_library(SimpleMath::SimpleMath ALIAS SimpleMath)

target_compile_features(SimpleMath PUBLIC cxx_std_17)

find_package(spdlog CONFIG REQUIRED)

target_link_libraries(SimpleMath
    PUBLIC
        spdlog::spdlog
)

target_include_directories(SimpleMath
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)

install(TARGETS SimpleMath
    EXPORT SimpleMathTargets
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

install(DIRECTORY include/
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

set(SIMPLEMATH_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/SimpleMath")

install(EXPORT SimpleMathTargets
    FILE SimpleMathTargets.cmake
    NAMESPACE SimpleMath::
    DESTINATION ${SIMPLEMATH_INSTALL_CMAKEDIR}
)

write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/SimpleMathConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
)

configure_package_config_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/SimpleMathConfig.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/SimpleMathConfig.cmake"
    INSTALL_DESTINATION ${SIMPLEMATH_INSTALL_CMAKEDIR}
)

install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/SimpleMathConfig.cmake"
    "${CMAKE_CURRENT_BINARY_DIR}/SimpleMathConfigVersion.cmake"
    DESTINATION ${SIMPLEMATH_INSTALL_CMAKEDIR}
)

SimpleMathConfig.cmake.in and find_dependency

Create cmake/SimpleMathConfig.cmake.in:

@PACKAGE_INIT@

include(CMakeFindDependencyMacro)
find_dependency(spdlog CONFIG REQUIRED)

include("${CMAKE_CURRENT_LIST_DIR}/SimpleMathTargets.cmake")

Why this matters:

  • Downstream projects call find_package(SimpleMath CONFIG REQUIRED).
  • CMake loads SimpleMathConfig.cmake.
  • find_dependency(spdlog) ensures spdlog::spdlog is also available to the consumer.

Build + install

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$PWD/install"
cmake --build build
cmake --install build

Expected install output (simplified):

install/
├─ include/simplemath/simplemath.hpp
└─ lib/cmake/SimpleMath/
   ├─ SimpleMathConfig.cmake
   ├─ SimpleMathConfigVersion.cmake
   └─ SimpleMathTargets.cmake

Consume it with find_package

Consumer CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(Consumer LANGUAGES CXX)

find_package(SimpleMath CONFIG REQUIRED)

add_executable(consumer_app main.cpp)
target_link_libraries(consumer_app PRIVATE SimpleMath::SimpleMath)

Consumer main.cpp:

#include <simplemath/simplemath.hpp>
#include <iostream>

int main() {
    std::cout << simplemath::add(2.0, 3.0) << "\n";
    std::cout << simplemath::divide(10.0, 2.0) << "\n";
    return 0;
}

If CMake cannot locate SimpleMath, point it at your install prefix:

cmake -S . -B build -DCMAKE_PREFIX_PATH="/path/to/SimpleMath/install"
cmake --build build

Quick checklist

  • add_library for your project target.
  • find_package(spdlog CONFIG REQUIRED) + target_link_libraries(... PUBLIC spdlog::spdlog).
  • BUILD_INTERFACE and INSTALL_INTERFACE include directories.
  • install(TARGETS ...) + install(DIRECTORY include/ ...).
  • install(EXPORT ...) for generated targets.
  • configure_package_config_file + write_basic_package_version_file.
  • find_dependency(spdlog) in SimpleMathConfig.cmake.in.

This pattern is the standard baseline for a small, reusable CMake library.