What use is find_package() when you need to specify CMAKE_MODULE_PATH?

Learn what use is find_package() when you need to specify cmake_module_path? with practical examples, diagrams, and best practices. Covers cmake development techniques with visual explanations.

Demystifying find_package() and CMAKE_MODULE_PATH in CMake

Hero image for What use is find_package() when you need to specify CMAKE_MODULE_PATH?

Explore the nuances of CMake's find_package() command and understand when and why CMAKE_MODULE_PATH becomes essential for locating custom or non-standard packages.

CMake's find_package() command is a cornerstone for managing external dependencies in C++ projects. It automates the process of locating libraries, headers, and other package components, making your build system robust and portable. However, its behavior can sometimes be a source of confusion, especially when dealing with packages not installed in standard system locations or when creating your own Find<PackageName>.cmake modules. This article delves into the mechanics of find_package() and clarifies the critical role of CMAKE_MODULE_PATH in guiding CMake to the right places.

How find_package() Works: A Search Strategy Overview

At its core, find_package() attempts to locate a package by searching for specific files. It operates in two main modes: Module mode and Config mode. The search order and the files it looks for differ slightly between these modes, but the general principle involves checking a series of predefined and user-specified paths.

In Module mode, find_package(<PackageName>) looks for a file named Find<PackageName>.cmake. This file contains custom logic written by the package maintainer (or you) to locate the package's components. In Config mode, find_package(<PackageName> CONFIG) or find_package(<PackageName> REQUIRED) (which implies CONFIG mode if a module is not found) looks for <PackageName>Config.cmake or <packagename>-config.cmake files. These files are typically generated by the package's build system and provide direct information about the package's installation.

The search paths include system-wide locations, environment variables, and crucially, paths specified by CMAKE_MODULE_PATH and CMAKE_PREFIX_PATH.

flowchart TD
    A[find_package(PackageName)] --> B{Module Mode?}; 
    B -- Yes --> C[Search CMAKE_MODULE_PATH];
    C --> D[Search CMAKE_ROOT/Modules];
    D --> E{Find<PackageName>.cmake found?};
    E -- Yes --> F[Package Found (Module Mode)];
    E -- No --> G{Config Mode?};
    B -- No --> G;
    G -- Yes --> H[Search CMAKE_PREFIX_PATH];
    H --> I[Search system paths (e.g., /usr/local, /usr)];
    I --> J{<PackageName>Config.cmake found?};
    J -- Yes --> K[Package Found (Config Mode)];
    J -- No --> L[Package Not Found];

Simplified flow of CMake's find_package() search strategy.

The Role of CMAKE_MODULE_PATH

CMAKE_MODULE_PATH is a CMake list variable that holds a list of directories where CMake should look for Find<PackageName>.cmake files. It is specifically relevant when you are using or developing custom Find modules that are not part of CMake's built-in modules or when a package's Find module is installed in a non-standard location.

Consider a scenario where you've written a FindMyCustomLib.cmake module for a proprietary library, or you've downloaded a third-party Find module that isn't installed system-wide. Without CMAKE_MODULE_PATH, CMake wouldn't know where to look for this file, leading to find_package() failures.

By adding your custom module's directory to CMAKE_MODULE_PATH, you explicitly tell CMake: "Hey, when you're looking for Find<PackageName>.cmake, also check these directories." This provides a flexible way to extend CMake's package-finding capabilities without modifying system-wide CMake installations.

# In your project's CMakeLists.txt

# Define a directory where your custom Find modules reside
set(MY_FIND_MODULES_DIR "${CMAKE_SOURCE_DIR}/cmake/modules")

# Add this directory to CMAKE_MODULE_PATH
# It's generally good practice to append to avoid overwriting existing paths
list(APPEND CMAKE_MODULE_PATH "${MY_FIND_MODULES_DIR}")

# Now, find_package() will look in MY_FIND_MODULES_DIR for FindMyCustomLib.cmake
find_package(MyCustomLib REQUIRED)

Adding a custom directory to CMAKE_MODULE_PATH.

When to Use CMAKE_MODULE_PATH vs. CMAKE_PREFIX_PATH

Understanding the distinction between CMAKE_MODULE_PATH and CMAKE_PREFIX_PATH is crucial for effective dependency management:

  • CMAKE_MODULE_PATH: Use this when you have a Find<PackageName>.cmake script that CMake needs to discover. This is common for packages that don't provide their own Config files, or when you're writing your own custom finding logic.

  • CMAKE_PREFIX_PATH: Use this when a package provides a Config file (e.g., FooConfig.cmake) and is installed in a non-standard prefix. CMake will search for FooConfig.cmake within CMAKE_PREFIX_PATH/<PackageName>/(lib/cmake|share/cmake) or directly in CMAKE_PREFIX_PATH/<PackageName>. This is the more common scenario for well-behaved third-party libraries.

In many cases, if you're installing a package to a custom location, you might need to set CMAKE_PREFIX_PATH to the installation root. If that package also relies on a custom Find module that isn't in a standard location, then CMAKE_MODULE_PATH might also be necessary.

# Example: Setting both paths

# Assume a custom installation prefix for a library
set(MY_LIB_INSTALL_PREFIX "/opt/my_custom_libs")
list(APPEND CMAKE_PREFIX_PATH "${MY_LIB_INSTALL_PREFIX}")

# Assume a custom Find module for another dependency
set(MY_CUSTOM_FIND_MODULES "${CMAKE_SOURCE_DIR}/cmake/find_modules")
list(APPEND CMAKE_MODULE_PATH "${MY_CUSTOM_FIND_MODULES}")

find_package(MyConfigLib REQUIRED)
find_package(MyModuleLib REQUIRED)

Demonstrating the use of both CMAKE_PREFIX_PATH and CMAKE_MODULE_PATH.