The install() Command
The install() command defines rules for installing files during the cmake --install step. It doesn't copy files during build — it generates install rules that execute later. This separation lets you build once and install to different prefixes without recompiling.
Installing Targets
The most common install form places executables, libraries, and their headers in the correct directories:
cmake_minimum_required(VERSION 3.21)
project(MyLib VERSION 2.1.0 LANGUAGES CXX)
# Create a shared library
add_library(mylib SHARED
src/mylib.cpp
src/utils.cpp
)
# Set public headers
target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# Install the library target
install(TARGETS mylib
EXPORT MyLibTargets
RUNTIME DESTINATION bin # DLLs on Windows
LIBRARY DESTINATION lib # .so on Linux
ARCHIVE DESTINATION lib # .a / .lib static archives
INCLUDES DESTINATION include # Sets interface include dirs
)
Each DESTINATION keyword specifies a subdirectory relative to CMAKE_INSTALL_PREFIX. The RUNTIME, LIBRARY, and ARCHIVE categories handle different artifact types automatically based on the platform (see install(TARGETS) docs).
Installing Files
For headers and other non-target files, use install(FILES):
# Install public headers preserving directory structure
install(FILES
include/mylib/mylib.h
include/mylib/utils.h
include/mylib/types.h
DESTINATION include/mylib
)
# Install a configuration file
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mylib_config.h
DESTINATION include/mylib
)
# Install with specific permissions
install(FILES scripts/setup.sh
DESTINATION bin
PERMISSIONS OWNER_EXECUTE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
)
Installing Directories
When you have an entire directory tree to install, install(DIRECTORY) is more efficient than listing individual files:
# Install all headers from include/ directory
install(DIRECTORY include/
DESTINATION include
FILES_MATCHING PATTERN "*.h"
)
# Install docs, excluding internal files
install(DIRECTORY docs/
DESTINATION share/mylib/docs
PATTERN "internal" EXCLUDE
PATTERN "*.md" EXCLUDE
)
# Install with regex filtering
install(DIRECTORY assets/
DESTINATION share/mylib/assets
FILES_MATCHING REGEX ".*\\.(png|jpg|svg)$"
)
flowchart TD
A[CMAKE_INSTALL_PREFIX] --> B[bin/]
A --> C[lib/]
A --> D[include/]
A --> E[share/]
B --> B1[myapp executable]
B --> B2[.dll files Windows]
C --> C1[libmylib.so / .dylib]
C --> C2[libmylib.a]
C --> C3[cmake/MyLib/]
C3 --> C3a[MyLibConfig.cmake]
C3 --> C3b[MyLibTargets.cmake]
C3 --> C3c[MyLibConfigVersion.cmake]
D --> D1[mylib/]
D1 --> D1a[mylib.h]
D1 --> D1b[utils.h]
E --> E1[mylib/docs/]
GNUInstallDirs Module
The GNUInstallDirs module provides standardized installation directory variables that respect platform conventions (e.g., lib64 on 64-bit Linux):
include(GNUInstallDirs)
# Now use CMAKE_INSTALL_* variables instead of hardcoded paths
install(TARGETS mylib
EXPORT MyLibTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h"
)
# Install pkg-config file
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mylib.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
Standard Directory Variables
| Variable | Default | Purpose |
|---|---|---|
CMAKE_INSTALL_BINDIR | bin | Executables, DLLs |
CMAKE_INSTALL_LIBDIR | lib (or lib64) | Libraries |
CMAKE_INSTALL_INCLUDEDIR | include | Headers |
CMAKE_INSTALL_DATADIR | share | Read-only data |
CMAKE_INSTALL_MANDIR | share/man | Man pages |
CMAKE_INSTALL_DOCDIR | share/doc/PROJECT | Documentation |
CMAKE_INSTALL_SYSCONFDIR | etc | Configuration files |
CMAKE_INSTALL_PREFIX
The CMAKE_INSTALL_PREFIX variable controls where everything gets installed. All DESTINATION paths are relative to this prefix:
# Set prefix at configure time
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/opt/myproject
# Or override at install time
cmake --install build --prefix /tmp/staging
# Default values by platform:
# Linux/macOS: /usr/local
# Windows: C:/Program Files/${PROJECT_NAME}
Exporting Targets
Installing binaries is only half the story. For libraries, you need to export CMake target information so downstream projects can use find_package() to discover and link against your library.
The EXPORT Keyword
The EXPORT keyword in install(TARGETS) associates targets with a named export set:
# Associate targets with an export set named "MyLibTargets"
install(TARGETS mylib mylib_utils
EXPORT MyLibTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
# You can add to the same export set from subdirectories
# In src/CMakeLists.txt:
install(TARGETS mylib_core
EXPORT MyLibTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(EXPORT)
The install(EXPORT) command generates and installs a CMake file that recreates the imported targets:
# Generate and install the targets file
install(EXPORT MyLibTargets
FILE MyLibTargets.cmake
NAMESPACE MyLib::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
)
# The generated MyLibTargets.cmake will contain:
# add_library(MyLib::mylib SHARED IMPORTED)
# set_target_properties(MyLib::mylib PROPERTIES
# INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
# IMPORTED_LOCATION "${_IMPORT_PREFIX}/lib/libmylib.so"
# )
Install and Verify a Library
Create a simple library, install it, then verify the install tree:
# Build and install to a staging directory
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=$PWD/install
cmake --build build
cmake --install build
# Verify the installation layout
find install/ -type f | sort
# Expected output:
# install/include/mylib/mylib.h
# install/lib/libmylib.so
# install/lib/cmake/MyLib/MyLibConfig.cmake
# install/lib/cmake/MyLib/MyLibTargets.cmake
# install/lib/cmake/MyLib/MyLibConfigVersion.cmake
Generating Config.cmake Packages
A proper CMake package consists of three files that allow find_package(MyLib) to work seamlessly:
The Config File
Use CMakePackageConfigHelpers to generate a relocatable Config.cmake:
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
# Write the config file from a template
configure_package_config_file(
cmake/MyLibConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
)
# Install the generated config file
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
)
The template file cmake/MyLibConfig.cmake.in:
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
check_required_components(MyLib)
Package Version File
Use write_basic_package_version_file() to create a version compatibility file:
# Generate version file
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
# Install the version file
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
)
The COMPATIBILITY argument controls version matching:
| Mode | find_package(MyLib 2.1) matches |
|---|---|
AnyNewerVersion | 2.1, 2.5, 3.0, 4.0 |
SameMajorVersion | 2.1, 2.5 (not 3.0) |
SameMinorVersion | 2.1, 2.1.5 (not 2.2) |
ExactVersion | 2.1 only |
flowchart TD
A["find_package(MyLib 2.0 REQUIRED)"] --> B{Search CMAKE_PREFIX_PATH}
B --> C[Find MyLibConfig.cmake]
C --> D[Check MyLibConfigVersion.cmake]
D -->|Version OK| E[Include MyLibTargets.cmake]
D -->|Version Mismatch| F[Continue Searching]
E --> G["MyLib::mylib target available"]
G --> H["target_link_libraries(app PRIVATE MyLib::mylib)"]
F --> I{More paths?}
I -->|Yes| B
I -->|No| J[FATAL_ERROR: Package not found]
GenerateExportHeader
For shared libraries, you need proper symbol visibility macros. The GenerateExportHeader module generates these automatically:
include(GenerateExportHeader)
add_library(mylib SHARED src/mylib.cpp)
# Generate mylib_export.h with visibility macros
generate_export_header(mylib
EXPORT_FILE_NAME include/mylib/mylib_export.h
EXPORT_MACRO_NAME MYLIB_API
)
# Make sure the generated header is accessible
target_include_directories(mylib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# Install the generated export header
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/include/mylib/mylib_export.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mylib
)
Then in your library headers:
#include "mylib/mylib_export.h"
class MYLIB_API MyClass {
public:
void doSomething();
static int getVersion();
};
// For functions
MYLIB_API void mylib_initialize();
MYLIB_API const char* mylib_version_string();
Full find_package()-Compatible Library
Combine all the pieces into a complete installable library that downstream projects consume with find_package():
cmake_minimum_required(VERSION 3.21)
project(MathUtils VERSION 1.2.0 LANGUAGES CXX)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
include(GenerateExportHeader)
add_library(mathutils SHARED src/mathutils.cpp)
generate_export_header(mathutils
EXPORT_FILE_NAME include/mathutils/export.h)
target_include_directories(mathutils PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_compile_features(mathutils PUBLIC cxx_std_17)
# Install targets with export set
install(TARGETS mathutils EXPORT MathUtilsTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
# Install headers
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/mathutils/export.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mathutils)
# Generate and install package config
install(EXPORT MathUtilsTargets
FILE MathUtilsTargets.cmake
NAMESPACE MathUtils::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathUtils)
configure_package_config_file(
cmake/MathUtilsConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MathUtilsConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathUtils)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/MathUtilsConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MathUtilsConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/MathUtilsConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MathUtils)
Installing Superbuilds
Superbuilds (projects using ExternalProject_Add) have a unique installation challenge: the external projects install to their own prefix during build, and the top-level project must aggregate these installations:
# In the superbuild's top-level CMakeLists.txt
set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/staged)
ExternalProject_Add(dep_zlib
URL https://zlib.net/zlib-1.3.tar.gz
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${STAGED_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
)
ExternalProject_Add(my_project
SOURCE_DIR ${CMAKE_SOURCE_DIR}/src
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DCMAKE_PREFIX_PATH=${STAGED_INSTALL_PREFIX}
DEPENDS dep_zlib
)
# Install staged dependencies alongside your project
install(DIRECTORY ${STAGED_INSTALL_PREFIX}/
DESTINATION .
USE_SOURCE_PERMISSIONS
)
Consume Your Installed Package
After installing your library, test it from a separate project:
# consumer/CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(Consumer LANGUAGES CXX)
find_package(MathUtils 1.0 REQUIRED)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE MathUtils::mathutils)
# Build the consumer, pointing to your install
cmake -S consumer -B consumer/build \
-DCMAKE_PREFIX_PATH=$PWD/install
cmake --build consumer/build
./consumer/build/app
Conclusion & Next Steps
You now understand how to create professional CMake installations that follow platform conventions and work seamlessly with find_package(). Key takeaways:
install(TARGETS)handles executables and libraries with platform-aware destination splitting- GNUInstallDirs provides portable directory variables (never hardcode "lib" or "bin")
install(EXPORT)+ Config.cmake + ConfigVersion.cmake = complete package for downstream- GenerateExportHeader manages symbol visibility for shared libraries
- Always use generator expressions (
BUILD_INTERFACE/INSTALL_INTERFACE) for include directories