find_package() Basics
Real-world C and C++ projects rarely exist in isolation. They depend on external libraries — compression (ZLIB), networking (OpenSSL), testing (GTest), and many more. CMake's find_package() command is the standard mechanism for locating these dependencies, setting up include paths, and linking libraries — all in a portable way.
find_package() operates in two distinct modes: Module mode (searches for FindXXX.cmake scripts) and Config mode (searches for XXXConfig.cmake files installed by the package). Modern CMake strongly prefers Config mode because it provides imported targets with full usage requirements.
The simplest form of find_package():
# CMakeLists.txt — Basic find_package usage
cmake_minimum_required(VERSION 3.16)
project(FindDemo LANGUAGES CXX)
# Find ZLIB — REQUIRED means configure fails if not found
find_package(ZLIB REQUIRED)
add_executable(compress_demo main.cpp)
# Link using the imported target (modern approach)
target_link_libraries(compress_demo PRIVATE ZLIB::ZLIB)
REQUIRED and COMPONENTS
The REQUIRED keyword causes CMake to halt configuration with an error if the package isn't found. Without it, CMake sets <Package>_FOUND to FALSE and continues — useful for optional dependencies.
# CMakeLists.txt — COMPONENTS and version requirements
cmake_minimum_required(VERSION 3.16)
project(BoostDemo LANGUAGES CXX)
# Find Boost with specific components and minimum version
find_package(Boost 1.74 REQUIRED COMPONENTS filesystem system regex)
add_executable(fs_demo main.cpp)
target_link_libraries(fs_demo PRIVATE
Boost::filesystem
Boost::system
Boost::regex
)
The COMPONENTS keyword specifies which parts of a multi-component library you need. Only listed components are required. Use OPTIONAL_COMPONENTS for nice-to-have parts:
# CMakeLists.txt — Optional component handling
cmake_minimum_required(VERSION 3.16)
project(QtDemo LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets OPTIONAL_COMPONENTS Network)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE Qt6::Core Qt6::Widgets)
if(Qt6_Network_FOUND)
target_link_libraries(app PRIVATE Qt6::Network)
target_compile_definitions(app PRIVATE HAS_NETWORK=1)
endif()
Version Requirements
You can specify exact or minimum version requirements. CMake checks the version information provided by the package's config file:
# CMakeLists.txt — Version constraints
cmake_minimum_required(VERSION 3.16)
project(VersionDemo LANGUAGES CXX)
# Minimum version
find_package(OpenSSL 1.1 REQUIRED)
# Exact version (rarely used — too restrictive)
find_package(Protobuf 3.21.0 EXACT REQUIRED)
# Version range (CMake 3.19+)
find_package(fmt 8.0...<10.0 REQUIRED)
message(STATUS "OpenSSL version: ${OPENSSL_VERSION}")
message(STATUS "Protobuf version: ${Protobuf_VERSION}")
How find_package Searches
Understanding the search order is essential for debugging "package not found" errors. The search differs between Module mode and Config mode.
flowchart TD
A["find_package(Foo)"] --> B{Module Mode First?}
B -->|Yes| C["Search CMAKE_MODULE_PATH
for FindFoo.cmake"]
C --> D{Found?}
D -->|Yes| E["Execute FindFoo.cmake"]
D -->|No| F["Search CMake built-in
modules directory"]
F --> G{Found?}
G -->|Yes| E
G -->|No| H["Fall through to
Config Mode"]
B -->|CONFIG keyword| H
H --> I["Search CMAKE_PREFIX_PATH"]
I --> J["Search Foo_DIR"]
J --> K["Search system paths
(/usr/lib/cmake, etc.)"]
K --> L{FooConfig.cmake
found?}
L -->|Yes| M["Import targets &
set variables"]
L -->|No| N["FATAL_ERROR if REQUIRED
else set Foo_FOUND=FALSE"]
CMAKE_PREFIX_PATH
The CMAKE_PREFIX_PATH variable is the most common way to tell CMake where to find libraries installed in non-standard locations:
# Point CMake to custom install locations
cmake -B build \
-DCMAKE_PREFIX_PATH="/opt/custom-libs;/home/user/local;C:/libs" \
-S .
# Multiple paths separated by semicolons (CMake list syntax)
# Or set as environment variable:
export CMAKE_PREFIX_PATH="/opt/custom-libs:/home/user/local"
cmake -B build -S .
# CMakeLists.txt — Programmatically appending to prefix path
cmake_minimum_required(VERSION 3.16)
project(PrefixDemo LANGUAGES CXX)
# Append a project-local deps directory
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/third_party/install")
find_package(MyLib REQUIRED)
System Paths
CMake automatically searches platform-specific system paths. On Linux, this includes:
/usr/lib/cmake/and/usr/lib64/cmake//usr/local/lib/cmake//usr/share/cmake/Modules/- Paths in the
PATHenvironment variable (with/binstripped,/lib/cmakeappended)
On Windows, CMake also searches the registry and Program Files directories.
Package_DIR Variable
For Config mode, you can directly specify the directory containing the config file:
# Directly tell CMake where FooConfig.cmake lives
cmake -B build -DFoo_DIR="/path/to/foo/lib/cmake/Foo" -S .
# This skips all other search logic for this specific package
Config Mode (Modern)
Config mode is the preferred approach in modern CMake. When a library is installed properly, it provides a <Package>Config.cmake (or <package>-config.cmake) file that defines imported targets with complete usage requirements.
PackageConfig.cmake Files
A Config file is generated during a library's install step. Here's what a typical one provides:
# Example: What MyLibConfig.cmake typically does internally
# (You don't write this — the library's CMake install() generates it)
# Create an imported target
add_library(MyLib::MyLib SHARED IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
IMPORTED_LOCATION "/usr/local/lib/libmylib.so"
INTERFACE_INCLUDE_DIRECTORIES "/usr/local/include"
INTERFACE_COMPILE_DEFINITIONS "MYLIB_SHARED"
INTERFACE_LINK_LIBRARIES "Threads::Threads;ZLIB::ZLIB"
)
The version file (<Package>ConfigVersion.cmake) enables version checking:
# CMakeLists.txt — Using a library that provides Config mode
cmake_minimum_required(VERSION 3.16)
project(ConfigDemo LANGUAGES CXX)
# Config mode: CMake searches for fmtConfig.cmake
find_package(fmt 9.0 REQUIRED CONFIG)
add_executable(app main.cpp)
# Imported target carries includes, definitions, and link deps
target_link_libraries(app PRIVATE fmt::fmt)
Imported Targets
Imported targets are the key benefit of Config mode. They encapsulate everything needed to use a library:
Package::Component) over raw variables (${Package_LIBRARIES}, ${Package_INCLUDE_DIRS}). Imported targets propagate usage requirements transitively and work correctly with generator expressions.
# CMakeLists.txt — Imported targets vs legacy variables
cmake_minimum_required(VERSION 3.16)
project(TargetDemo LANGUAGES CXX)
find_package(OpenSSL REQUIRED)
add_executable(app main.cpp)
# MODERN (preferred): Use imported targets
target_link_libraries(app PRIVATE OpenSSL::SSL OpenSSL::Crypto)
# LEGACY (avoid): Manual include/link — misses transitive deps
# target_include_directories(app PRIVATE ${OPENSSL_INCLUDE_DIR})
# target_link_libraries(app PRIVATE ${OPENSSL_LIBRARIES})
flowchart LR
subgraph Module["Module Mode (Legacy)"]
direction TB
M1["FindFoo.cmake script"]
M2["Sets Foo_FOUND, Foo_LIBRARIES,
Foo_INCLUDE_DIRS variables"]
M3["May or may not create
imported targets"]
M1 --> M2 --> M3
end
subgraph Config["Config Mode (Modern)"]
direction TB
C1["FooConfig.cmake installed
by the library itself"]
C2["Creates Foo::Foo and
Foo::Component targets"]
C3["Full usage requirements:
includes, defs, link deps"]
C1 --> C2 --> C3
end
Module -.->|"Fallback"| Config
Module Mode (Legacy)
Module mode uses FindXXX.cmake scripts — either ones you write, ones in CMAKE_MODULE_PATH, or ones bundled with CMake itself. This mode predates Config mode and is still used for libraries that don't provide their own CMake configuration.
FindXXX.cmake Modules
CMake searches for Find modules in this order:
- Directories listed in
CMAKE_MODULE_PATH(your project's custom modules) - CMake's built-in module directory (ships with CMake installation)
# CMakeLists.txt — Using CMAKE_MODULE_PATH for custom Find modules
cmake_minimum_required(VERSION 3.16)
project(ModuleDemo LANGUAGES CXX)
# Tell CMake where our custom Find modules live
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
# Now CMake will search cmake/modules/FindMyCustomLib.cmake
find_package(MyCustomLib REQUIRED)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE ${MyCustomLib_LIBRARIES})
target_include_directories(app PRIVATE ${MyCustomLib_INCLUDE_DIRS})
CMake-Bundled Find Modules
CMake ships with Find modules for many common libraries. You can list them all:
# List all built-in Find modules (CMake 3.28+)
cmake --help-module-list | grep "^Find"
# Get help on a specific Find module
cmake --help-module FindOpenSSL
cmake --help-module FindThreads
cmake --help-module FindPython3
Some notable bundled Find modules: FindThreads, FindOpenSSL, FindZLIB, FindCurses, FindPython3, FindBoost, FindOpenGL, FindPkgConfig.
Writing a Find Module
When a library doesn't provide Config mode support and CMake doesn't bundle a Find module, you write your own. Here's the complete pattern:
find_path and find_library
Writing FindLibUUID.cmake
Create a Find module for libuuid — a library that's commonly available on Linux but lacks CMake config files.
# cmake/modules/FindLibUUID.cmake — Complete Find module template
#[=======================================================================[.rst:
FindLibUUID
-----------
Find the UUID library (libuuid).
Imported Targets
^^^^^^^^^^^^^^^^
``LibUUID::LibUUID``
The UUID library, if found.
Result Variables
^^^^^^^^^^^^^^^^
``LibUUID_FOUND``
True if the library was found.
``LibUUID_INCLUDE_DIRS``
Include directories for uuid.h.
``LibUUID_LIBRARIES``
Libraries to link against.
``LibUUID_VERSION``
Version string (if detectable).
#]=======================================================================]
include(FindPackageHandleStandardArgs)
# Find the header
find_path(LibUUID_INCLUDE_DIR
NAMES uuid/uuid.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
DOC "Path to uuid/uuid.h"
)
# Find the library
find_library(LibUUID_LIBRARY
NAMES uuid
PATHS
/usr/lib
/usr/lib64
/usr/local/lib
/opt/local/lib
DOC "Path to libuuid"
)
# Handle REQUIRED, QUIET, and version arguments
find_package_handle_standard_args(LibUUID
REQUIRED_VARS LibUUID_LIBRARY LibUUID_INCLUDE_DIR
)
# Create imported target if found
if(LibUUID_FOUND AND NOT TARGET LibUUID::LibUUID)
add_library(LibUUID::LibUUID UNKNOWN IMPORTED)
set_target_properties(LibUUID::LibUUID PROPERTIES
IMPORTED_LOCATION "${LibUUID_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${LibUUID_INCLUDE_DIR}"
)
endif()
# Set output variables
mark_as_advanced(LibUUID_INCLUDE_DIR LibUUID_LIBRARY)
set(LibUUID_INCLUDE_DIRS ${LibUUID_INCLUDE_DIR})
set(LibUUID_LIBRARIES ${LibUUID_LIBRARY})
find_package_handle_standard_args
The find_package_handle_standard_args() function (from the FindPackageHandleStandardArgs module) handles all the boilerplate: checking required variables, printing status messages, respecting QUIET/REQUIRED, and setting <Package>_FOUND.
# CMakeLists.txt — Using our custom Find module
cmake_minimum_required(VERSION 3.16)
project(UUIDDemo LANGUAGES C)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
find_package(LibUUID REQUIRED)
add_executable(uuid_gen main.c)
target_link_libraries(uuid_gen PRIVATE LibUUID::LibUUID)
pkg-config Integration
Many Unix libraries provide .pc files for pkg-config. CMake can leverage these through the FindPkgConfig module — useful as a fallback when no CMake config exists.
pkg_check_modules
# CMakeLists.txt — Using pkg-config through CMake
cmake_minimum_required(VERSION 3.16)
project(PkgConfigDemo LANGUAGES C)
# Load the PkgConfig module
find_package(PkgConfig REQUIRED)
# Check for libcurl using pkg-config
pkg_check_modules(CURL REQUIRED IMPORTED_TARGET libcurl)
add_executable(downloader main.c)
# IMPORTED_TARGET creates PkgConfig::CURL
target_link_libraries(downloader PRIVATE PkgConfig::CURL)
IMPORTED_TARGET keyword (CMake 3.6+) creates a proper imported target. Without it, you get only variables: CURL_LIBRARIES, CURL_INCLUDE_DIRS, CURL_CFLAGS, etc.
pkg_search_module
Use pkg_search_module when a library might be known by different names across distributions:
# CMakeLists.txt — Searching multiple pkg-config names
cmake_minimum_required(VERSION 3.16)
project(AudioDemo LANGUAGES C)
find_package(PkgConfig REQUIRED)
# Try multiple names — stops at first match
pkg_search_module(AUDIO REQUIRED IMPORTED_TARGET
libpulse # PulseAudio
alsa # ALSA
jack # JACK Audio
)
add_executable(audio_player main.c)
target_link_libraries(audio_player PRIVATE PkgConfig::AUDIO)
message(STATUS "Found audio library: ${AUDIO_MODULE_NAME}")
Finding Programs
CMake can also locate external programs needed during the build (code generators, documentation tools, etc.) using find_program():
# CMakeLists.txt — Finding and using external programs
cmake_minimum_required(VERSION 3.16)
project(GenDemo LANGUAGES CXX)
# Find protobuf compiler
find_program(PROTOC_EXECUTABLE
NAMES protoc
DOC "Protocol Buffers compiler"
)
if(NOT PROTOC_EXECUTABLE)
message(FATAL_ERROR "protoc not found — install protobuf-compiler")
endif()
message(STATUS "Found protoc: ${PROTOC_EXECUTABLE}")
# Use in custom command
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/message.pb.cc
${CMAKE_CURRENT_BINARY_DIR}/message.pb.h
COMMAND ${PROTOC_EXECUTABLE}
--cpp_out=${CMAKE_CURRENT_BINARY_DIR}
--proto_path=${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/message.proto
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/message.proto
COMMENT "Generating protobuf sources"
)
add_executable(app main.cpp ${CMAKE_CURRENT_BINARY_DIR}/message.pb.cc)
target_include_directories(app PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
Conditionally Building Documentation
Use find_package(Doxygen) to conditionally add a documentation target only when Doxygen is installed.
# CMakeLists.txt — Optional Doxygen documentation
cmake_minimum_required(VERSION 3.16)
project(DocsDemo LANGUAGES CXX)
add_executable(app main.cpp)
# Doxygen is optional — don't use REQUIRED
find_package(Doxygen OPTIONAL_COMPONENTS dot)
if(DOXYGEN_FOUND)
set(DOXYGEN_GENERATE_HTML YES)
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
doxygen_add_docs(docs
${CMAKE_SOURCE_DIR}/src
COMMENT "Generating API documentation"
)
message(STATUS "Doxygen found — 'cmake --build build --target docs' available")
else()
message(STATUS "Doxygen not found — documentation target disabled")
endif()
Finding Python
CMake 3.12+ provides the modern FindPython3 module (replacing the older FindPythonInterp and FindPythonLibs):
# CMakeLists.txt — Finding Python 3 with components
cmake_minimum_required(VERSION 3.16)
project(PyDemo LANGUAGES CXX)
# Find Python interpreter, development headers, and NumPy
find_package(Python3 REQUIRED COMPONENTS Interpreter Development NumPy)
message(STATUS "Python3 executable: ${Python3_EXECUTABLE}")
message(STATUS "Python3 version: ${Python3_VERSION}")
message(STATUS "Python3 include dirs: ${Python3_INCLUDE_DIRS}")
message(STATUS "NumPy include dirs: ${Python3_NumPy_INCLUDE_DIRS}")
# Build a Python extension module
Python3_add_library(mymodule MODULE src/mymodule.cpp)
target_link_libraries(mymodule PRIVATE Python3::NumPy)
# CMakeLists.txt — Using Python as a build-time tool
cmake_minimum_required(VERSION 3.16)
project(PyToolDemo LANGUAGES CXX)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
# Use Python in custom commands
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/generated.h
COMMAND Python3::Interpreter ${CMAKE_SOURCE_DIR}/scripts/generate.py
--output ${CMAKE_BINARY_DIR}/generated.h
DEPENDS ${CMAKE_SOURCE_DIR}/scripts/generate.py
COMMENT "Running code generator"
)
add_executable(app main.cpp ${CMAKE_BINARY_DIR}/generated.h)
target_include_directories(app PRIVATE ${CMAKE_BINARY_DIR})
Common Libraries
Threads (FindThreads)
Threading is so fundamental that CMake provides a dedicated module. It handles platform differences (pthreads on Unix, Win32 threads on Windows):
# CMakeLists.txt — Finding and using threads
cmake_minimum_required(VERSION 3.16)
project(ThreadDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
# FindThreads sets Threads::Threads imported target
find_package(Threads REQUIRED)
add_executable(worker main.cpp)
target_link_libraries(worker PRIVATE Threads::Threads)
# On Linux this adds -pthread; on Windows it's a no-op
ZLIB and OpenSSL
# CMakeLists.txt — ZLIB and OpenSSL together
cmake_minimum_required(VERSION 3.16)
project(SecureCompress LANGUAGES CXX)
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
add_executable(secure_archive main.cpp)
target_link_libraries(secure_archive PRIVATE
ZLIB::ZLIB
OpenSSL::SSL
OpenSSL::Crypto
)
message(STATUS "ZLIB version: ${ZLIB_VERSION_STRING}")
message(STATUS "OpenSSL version: ${OPENSSL_VERSION}")
Curses
# CMakeLists.txt — Finding Curses/NCurses
cmake_minimum_required(VERSION 3.16)
project(TuiApp LANGUAGES C)
find_package(Curses REQUIRED)
add_executable(tui_app main.c)
target_include_directories(tui_app PRIVATE ${CURSES_INCLUDE_DIRS})
target_link_libraries(tui_app PRIVATE ${CURSES_LIBRARIES})
Combining Multiple Dependencies
Build a project that uses Threads, ZLIB, and OpenSSL together, demonstrating how imported targets compose cleanly.
# CMakeLists.txt — Composing multiple library dependencies
cmake_minimum_required(VERSION 3.16)
project(MultiDep LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(Threads REQUIRED)
find_package(ZLIB REQUIRED)
find_package(OpenSSL 1.1 REQUIRED)
add_executable(server
src/main.cpp
src/connection.cpp
src/compression.cpp
)
# Each imported target carries its full usage requirements
target_link_libraries(server PRIVATE
Threads::Threads
ZLIB::ZLIB
OpenSSL::SSL
OpenSSL::Crypto
)
Troubleshooting find_package
When find_package() fails, CMake provides powerful debugging tools:
# Enable debug output for all find_package calls (CMake 3.17+)
cmake -B build --debug-find -S .
# Or enable for a specific package only
cmake -B build -DCMAKE_FIND_DEBUG_MODE=TRUE -S .
# Check what paths CMake is searching
cmake -B build --debug-find-pkg=OpenSSL -S .
# CMakeLists.txt — Debugging find_package in CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(DebugFind LANGUAGES CXX)
# Enable debug output programmatically
set(CMAKE_FIND_DEBUG_MODE TRUE)
find_package(SomeLib REQUIRED)
set(CMAKE_FIND_DEBUG_MODE FALSE)
- "Could not find <Package>" — Set
CMAKE_PREFIX_PATHor<Package>_DIRto the install location - "Found unsuitable version" — Install a newer version or relax the version constraint
- "Missing component" — Install the development package (e.g.,
libssl-devnot justlibssl) - Config mode skipped — Library may only support Module mode; check with
--debug-find - Wrong library found — Use
CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=FALSEand set explicit paths
# Practical troubleshooting commands
# 1. Verify the library is installed
dpkg -L libssl-dev | grep cmake # Debian/Ubuntu
rpm -ql openssl-devel | grep cmake # RHEL/Fedora
# 2. Find .cmake config files manually
find /usr -name "OpenSSLConfig.cmake" 2>/dev/null
find /usr -name "FindOpenSSL.cmake" 2>/dev/null
# 3. Check pkg-config as fallback
pkg-config --modversion openssl
pkg-config --cflags --libs openssl
# 4. Clear CMake cache and retry
rm -rf build/CMakeCache.txt build/CMakeFiles/
cmake -B build -DCMAKE_PREFIX_PATH="/custom/path" -S .
Conclusion & Next Steps
Detecting external libraries is one of the most common tasks in CMake — and also one of the most frustrating when things go wrong. The key takeaways:
- Config mode is preferred — it provides imported targets with full usage requirements
- Module mode is the fallback — for libraries that don't ship CMake configuration
- Always use imported targets (
Package::Component) over raw variables - CMAKE_PREFIX_PATH is your primary tool for pointing CMake to non-standard installs
- --debug-find is invaluable for troubleshooting search failures
- Writing a Find module follows a standard pattern:
find_path+find_library+find_package_handle_standard_args