CMAKE-PACKAGES(7)

✨ NAME ✨

cmake-packages β€” CMake Packages Reference

ok bestie, buckle up πŸ’– this is the cmake-packages(7) manual page, and it's literally the whole lore on how CMake packages work. it's a lot, but we're getting through it together, i promise 🫢


🌸 INTRODUCTION 🌸

So. Packages. What are they even? Packages provide dependency information to CMake based buildsystems. That's the whole vibe. You go find a package with the find_package() command, and what you get back is either:

  • a set of IMPORTED targets, or
  • a set of variables that correspond to build-relevant info.

That's it. That's the payoff. no cap ✨


🫢 USING PACKAGES 🫢

CMake has direct support for two flavors of packages, and you're gonna hear about both:

  • Config-file Packages
  • Find-module Packages

There's also indirect support for pkg-config packages, and that comes via the FindPkgConfig module. cute, right? πŸŽ€

And here's the thing β€” in all of these cases, the basic form of a find_package() call is literally the same:

find_package(Qt4 4.7.0 REQUIRED) # CMake provides a Qt4 find-module
find_package(Qt5Core 5.1.0 REQUIRED) # Qt provides a Qt5 package config file.
find_package(LibXml2 REQUIRED) # Use pkg-config via the LibXml2 find-module

See? Same shape. Slay πŸ’…

Now, sometimes you know that the package configuration file is provided by upstream, and you want only that one used. In that case you pass the CONFIG keyword to find_package():

find_package(Qt5Core 5.1.0 CONFIG REQUIRED)
find_package(Qt5Gui 5.1.0 CONFIG)

And like, same energy in reverse β€” the MODULE keyword says "use only a find-module":

find_package(Qt4 4.7.0 MODULE REQUIRED)

Specifying the type of package explicitly improves the error message shown to the user if it is not found. Which β€” bestie β€” you want. trust me on this one 😭

Both types of packages also support specifying components of a package. You can drop them right after the REQUIRED keyword:

find_package(Qt5 5.1.0 CONFIG REQUIRED Widgets Xml Sql)

or as a separate COMPONENTS list:

find_package(Qt5 5.1.0 COMPONENTS Widgets Xml Sql)

or as a separate OPTIONAL_COMPONENTS list:

find_package(Qt5 5.1.0 COMPONENTS Widgets
                       OPTIONAL_COMPONENTS Xml Sql
)
ngl, how COMPONENTS and OPTIONAL_COMPONENTS actually get handled is defined by the package itself. not by CMake. so like, check with the package πŸ‘€

One more move: set the CMAKE_DISABLE_FIND_PACKAGE_<PackageName> variable to TRUE, and the <PackageName> package will not be searched at all, and will always be NOTFOUND. Iconic. And likewise, setting CMAKE_REQUIRE_FIND_PACKAGE_<PackageName> to TRUE will make that package REQUIRED. 🫑

πŸŽ€ Config-file Packages πŸŽ€

A config-file package is a set of files provided by upstreams for downstreams to use. periodt.

CMake searches a whole bunch of locations for package configuration files β€” the full list lives in the find_package() documentation, so go read that when you're ready. But the most simple way for a CMake user to tell cmake(1) to search in a non-standard prefix for a package? Just set the CMAKE_PREFIX_PATH cache variable. that's the move πŸ’…

Config-file packages are provided by upstream vendors as part of development packages β€” meaning they belong with the header files and any other files provided to assist downstreams in using the package. they travel together, it's giving found family πŸ₯Ή

A set of variables that provide package status info also gets set automatically when you use a config-file package:

  • <PackageName>_FOUND β€” set to true or false, depending on whether the package was found
  • <PackageName>_DIR β€” a cache variable set to the location of the package configuration file

πŸ¦‹ Find-module Packages πŸ¦‹

A find module is a file with a set of rules for finding the required pieces of a dependency β€” primarily header files and libraries.

Typically you need a find module when the upstream is not built with CMake, or isn't CMake-aware enough to otherwise provide a package configuration file. Unlike a package configuration file, a find module is not shipped with upstream β€” instead it's used by downstream to find the files by guessing locations of files with platform-specific hints. it's giving detective energy πŸ”

Now, unlike the case of an upstream-provided package configuration file, no single point of reference identifies the package as being found, so the <PackageName>_FOUND variable is not automatically set by the find_package() command. It can still be expected to be set by convention though, and it should be set by the author of the Find-module. Similarly there's no <PackageName>_DIR variable β€” but each of the artifacts, like library locations and header file locations, provides a separate cache variable instead.

wanna know more about creating Find-module files? go read the cmake-developer(7) manual πŸ’–

πŸ‘‘ PACKAGE LAYOUT πŸ‘‘

A config-file package consists of a Package Configuration File and, optionally, a Package Version File, both provided with the project distribution. two besties, one package 🫢

πŸ“ Package Configuration File πŸ“

Consider a project Foo that installs the following files:

<prefix>/include/foo-1.2/foo.h
<prefix>/lib/foo-1.2/libfoo.a

It may also provide a CMake package configuration file:

<prefix>/lib/cmake/foo-1.2/FooConfig.cmake

with content defining IMPORTED targets, or defining variables, such as:

# ...
# (compute PREFIX relative to file location)
# ...
set(Foo_INCLUDE_DIRS ${PREFIX}/include/foo-1.2)
set(Foo_LIBRARIES ${PREFIX}/lib/foo-1.2/libfoo.a)

Now if another project wishes to use Foo, it need only locate the FooConfig.cmake file and load it to get all the information it needs about package content locations. And since the package configuration file is provided by the package installation, it already knows all the file locations. main character energy, honestly πŸ‘‘

The find_package() command may be used to search for the package configuration file. This command constructs a set of installation prefixes and searches under each prefix in several locations. Given the name Foo, it looks for a file called FooConfig.cmake or foo-config.cmake. The full set of locations is specified in the find_package() command documentation. One place it looks is:

<prefix>/lib/cmake/Foo*/

where Foo* is a case-insensitive globbing expression. In our example the globbing expression will match <prefix>/lib/cmake/foo-1.2 and the package configuration file will be found. ate and left no crumbs ✨

Once found, a package configuration file is immediately loaded. It, together with a package version file, contains all the information the project needs to use the package.

🌈 Package Version File 🌈

When the find_package() command finds a candidate package configuration file, it looks next to it for a version file. The version file is loaded to test whether the package version is an acceptable match for the version requested. If the version file claims compatibility, the configuration file is accepted. Otherwise it is ignored. bye πŸ‘‹

The name of the package version file must match that of the package configuration file, but has either -version or Version appended to the name before the .cmake extension. For example, the files:

<prefix>/lib/cmake/foo-1.3/foo-config.cmake
<prefix>/lib/cmake/foo-1.3/foo-config-version.cmake

and:

<prefix>/lib/cmake/bar-4.2/BarConfig.cmake
<prefix>/lib/cmake/bar-4.2/BarConfigVersion.cmake

are each pairs of package configuration files and corresponding package version files. matched set, we love to see it πŸŽ€

When the find_package() command loads a version file it first sets the following variables:

  • PACKAGE_FIND_NAME β€” the <PackageName>
  • PACKAGE_FIND_VERSION β€” full requested version string
  • PACKAGE_FIND_VERSION_MAJOR β€” major version if requested, else 0
  • PACKAGE_FIND_VERSION_MINOR β€” minor version if requested, else 0
  • PACKAGE_FIND_VERSION_PATCH β€” patch version if requested, else 0
  • PACKAGE_FIND_VERSION_TWEAK β€” tweak version if requested, else 0
  • PACKAGE_FIND_VERSION_COUNT β€” number of version components, 0 to 4

The version file must use these variables to check whether it is compatible or an exact match for the requested version, and set the following variables with results:

  • PACKAGE_VERSION β€” full provided version string
  • PACKAGE_VERSION_EXACT β€” true if version is exact match
  • PACKAGE_VERSION_COMPATIBLE β€” true if version is compatible
  • PACKAGE_VERSION_UNSUITABLE β€” true if unsuitable as any version

Version files are loaded in a nested scope, so they're free to set any variables they wish as part of their computation. The find_package command wipes out the scope when the version file has completed and it has checked the output variables. no leftovers, we love a clean exit 🧚

When the version file claims to be an acceptable match for the requested version, the find_package command sets the following variables for use by the project:

  • <PackageName>_VERSION β€” full provided version string
  • <PackageName>_VERSION_MAJOR β€” major version if provided, else 0
  • <PackageName>_VERSION_MINOR β€” minor version if provided, else 0
  • <PackageName>_VERSION_PATCH β€” patch version if provided, else 0
  • <PackageName>_VERSION_TWEAK β€” tweak version if provided, else 0
  • <PackageName>_VERSION_COUNT β€” number of version components, 0 to 4

These variables report the version of the package that was actually found. And the <PackageName> part of their name matches the argument given to the find_package() command. consistency is key πŸ’…


πŸ”₯ CREATING PACKAGES πŸ”₯

Usually, the upstream depends on CMake itself and can use some CMake facilities for creating the package files. Consider an upstream which provides a single shared library:

project(UpstreamLib)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)

set(Upstream_VERSION 3.4.1)

include(GenerateExportHeader)

add_library(ClimbingStats SHARED climbingstats.cpp)
generate_export_header(ClimbingStats)
set_property(TARGET ClimbingStats PROPERTY VERSION ${Upstream_VERSION})
set_property(TARGET ClimbingStats PROPERTY SOVERSION 3)
set_property(TARGET ClimbingStats PROPERTY
  INTERFACE_ClimbingStats_MAJOR_VERSION 3)
set_property(TARGET ClimbingStats APPEND PROPERTY
  COMPATIBLE_INTERFACE_STRING ClimbingStats_MAJOR_VERSION
)

install(TARGETS ClimbingStats EXPORT ClimbingStatsTargets
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
  RUNTIME DESTINATION bin
  INCLUDES DESTINATION include
)
install(
  FILES
    climbingstats.h
    "${CMAKE_CURRENT_BINARY_DIR}/climbingstats_export.h"
  DESTINATION
    include
  COMPONENT
    Devel
)

include(CMakePackageConfigHelpers)
write_basic_package_version_file(
  "${CMAKE_CURRENT_BINARY_DIR}/ClimbingStats/ClimbingStatsConfigVersion.cmake"
  VERSION ${Upstream_VERSION}
  COMPATIBILITY AnyNewerVersion
)

okay so here's that exact export/install snippet doing its thing, bestie:

export(EXPORT ClimbingStatsTargets
  FILE "${CMAKE_CURRENT_BINARY_DIR}/ClimbingStats/ClimbingStatsTargets.cmake"
  NAMESPACE Upstream::
)
configure_file(cmake/ClimbingStatsConfig.cmake
  "${CMAKE_CURRENT_BINARY_DIR}/ClimbingStats/ClimbingStatsConfig.cmake"
  COPYONLY
)

set(ConfigPackageLocation lib/cmake/ClimbingStats)
install(EXPORT ClimbingStatsTargets
  FILE
    ClimbingStatsTargets.cmake
  NAMESPACE
    Upstream::
  DESTINATION
    ${ConfigPackageLocation}
)
install(
  FILES
    cmake/ClimbingStatsConfig.cmake
    "${CMAKE_CURRENT_BINARY_DIR}/ClimbingStats/ClimbingStatsConfigVersion.cmake"
  DESTINATION
    ${ConfigPackageLocation}
  COMPONENT
    Devel
)

The module CMakePackageConfigHelpers provides a macro for creating a simple ConfigVersion.cmake file. This file sets the version of the package. It is read by CMake when find_package() is called to determine the compatibility with the requested version, and to set some version-specific variables <PackageName>_VERSION, <PackageName>_VERSION_MAJOR, <PackageName>_VERSION_MINOR etc. The install(EXPORT) command is used to export the targets in the ClimbingStatsTargets export-set, defined previously by the install(TARGETS) command. This command generates the ClimbingStatsTargets.cmake file to contain IMPORTED targets, suitable for use by downstreams and arranges to install it to lib/cmake/ClimbingStats. The generated ClimbingStatsConfigVersion.cmake and a cmake/ClimbingStatsConfig.cmake are installed to the same location, completing the package.

The generated IMPORTED targets have appropriate properties set to define their usage requirements, such as INTERFACE_INCLUDE_DIRECTORIES, INTERFACE_COMPILE_DEFINITIONS and other relevant built-in INTERFACE_ properties. The INTERFACE variant of user-defined properties listed in COMPATIBLE_INTERFACE_STRING and other Compatible Interface Properties are also propagated to the generated IMPORTED targets. In the above case, ClimbingStats_MAJOR_VERSION is defined as a string which must be compatible among the dependencies of any depender. By setting this custom defined user property in this version and in the next version of ClimbingStats, cmake(1) will issue a diagnostic if there is an attempt to use version 3 together with version 4. Packages can choose to employ such a pattern if different major versions of the package are designed to be incompatible.

A NAMESPACE with double-colons is specified when exporting the targets for installation. This convention of double-colons gives CMake a hint that the name is an IMPORTED target when it is used by downstreams with the target_link_libraries() command. This way, CMake can issue a diagnostic if the package providing it has not yet been found.

In this case, when using install(TARGETS) the INCLUDES DESTINATION was specified. This causes the IMPORTED targets to have their INTERFACE_INCLUDE_DIRECTORIES populated with the include directory in the CMAKE_INSTALL_PREFIX. When the IMPORTED target is used by downstream, it automatically consumes the entries from that property.

πŸŽ€ Creating a Package Configuration File πŸŽ€

In this case, the ClimbingStatsConfig.cmake file could be as simple as:

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

As this allows downstreams to use the IMPORTED targets. If any macros should be provided by the ClimbingStats package, they should be in a separate file which is installed to the same location as the ClimbingStatsConfig.cmake file, and included from there.

This can also be extended to cover dependencies:

# ...
add_library(ClimbingStats SHARED climbingstats.cpp)
generate_export_header(ClimbingStats)

find_package(Stats 2.6.4 REQUIRED)
target_link_libraries(ClimbingStats PUBLIC Stats::Types)

As the Stats::Types target is a PUBLIC dependency of ClimbingStats, downstreams must also find the Stats package and link to the Stats::Types library. The Stats package should be found in the ClimbingStatsConfig.cmake file to ensure this. The find_dependency macro from the CMakeFindDependencyMacro helps with this by propagating whether the package is REQUIRED, or QUIET etc. All REQUIRED dependencies of a package should be found in the Config.cmake file:

include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)

include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake")

The find_dependency macro also sets ClimbingStats_FOUND to False if the dependency is not found, along with a diagnostic that the ClimbingStats package can not be used without the Stats package.

If COMPONENTS are specified when the downstream uses find_package(), they are listed in the <PackageName>_FIND_COMPONENTS variable. If a particular component is non-optional, then the <PackageName>_FIND_REQUIRED_<comp> will be true. This can be tested with logic in the package configuration file:

include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)

include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake")

set(_ClimbingStats_supported_components Plot Table)

foreach(_comp ${ClimbingStats_FIND_COMPONENTS})
  if (NOT ";${_ClimbingStats_supported_components};" MATCHES ";${_comp};")
    set(ClimbingStats_FOUND False)
    set(ClimbingStats_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
  endif()
  include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStats${_comp}Targets.cmake")
endforeach()

Here, the ClimbingStats_NOT_FOUND_MESSAGE is set to a diagnosis that the package could not be found because an invalid component was specified. This message variable can be set for any case where the _FOUND variable is set to False, and will be displayed to the user.

🌈 Creating a Package Configuration File for the Build Tree 🌈

The export(EXPORT) command creates an IMPORTED targets definition file which is specific to the build-tree, and is not relocatable. This can similarly be used with a suitable package configuration file and package version file to define a package for the build tree which may be used without installation. Consumers of the build tree can simply ensure that the CMAKE_PREFIX_PATH contains the build directory, or set the ClimbingStats_DIR to <build_dir>/ClimbingStats in the cache.

πŸ¦‹ Creating Relocatable Packages πŸ¦‹

A relocatable package must not reference absolute paths of files on the machine where the package is built that will not exist on the machines where the package may be installed.

Packages created by install(EXPORT) are designed to be relocatable, using paths relative to the location of the package itself. When defining the interface of a target for EXPORT, keep in mind that the include directories should be specified as relative paths which are relative to the CMAKE_INSTALL_PREFIX:

target_include_directories(tgt INTERFACE
  # Wrong, not relocatable:
  $<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include/TgtName>
)

target_include_directories(tgt INTERFACE
  # Ok, relocatable:
  $<INSTALL_INTERFACE:include/TgtName>
)

The $<INSTALL_PREFIX> generator expression may be used as a placeholder for the install prefix without resulting in a non-relocatable package. This is necessary if complex generator expressions are used:

target_include_directories(tgt INTERFACE
  # Ok, relocatable:
  $<INSTALL_INTERFACE:$<$<CONFIG:Debug>:$<INSTALL_PREFIX>/include/TgtName>>
)

This also applies to paths referencing external dependencies. It is not advisable to populate any properties which may contain paths, such as INTERFACE_INCLUDE_DIRECTORIES and INTERFACE_LINK_LIBRARIES, with paths relevant to dependencies. For example, this code may not work well for a relocatable package:

target_link_libraries(ClimbingStats INTERFACE
  ${Foo_LIBRARIES} ${Bar_LIBRARIES}
  )
target_include_directories(ClimbingStats INTERFACE
  "$<INSTALL_INTERFACE:${Foo_INCLUDE_DIRS};${Bar_INCLUDE_DIRS}>"
  )

The referenced variables may contain the absolute paths to libraries and include directories as found on the machine the package was made on. This would create a package with hard-coded paths to dependencies and not suitable for relocation.

Ideally such dependencies should be used through their own IMPORTED targets that have their own IMPORTED_LOCATION and usage requirement properties such as INTERFACE_INCLUDE_DIRECTORIES populated appropriately. Those imported targets may then be used with the target_link_libraries() command for ClimbingStats:

target_link_libraries(ClimbingStats INTERFACE Foo::Foo Bar::Bar)

With this approach the package references its external dependencies only through the names of IMPORTED targets. When a consumer uses the installed package, the consumer will run the appropriate find_package() commands (via the find_dependency macro described above) to find the dependencies and populate the imported targets with appropriate paths on their own machine.

Unfortunately many modules shipped with CMake do not yet provide IMPORTED targets because their development pre-dated this approach. This may improve incrementally over time. Workarounds to create relocatable packages using such modules include:

  • When building the package, specify each Foo_LIBRARY cache entry as just a library name, e.g. -DFoo_LIBRARY=foo. This tells the corresponding find module to populate the Foo_LIBRARIES with just foo to ask the linker to search for the library instead of hard-coding a path.
  • Or, after installing the package content but before creating the package installation binary for redistribution, manually replace the absolute paths with placeholders, so the installation tool can substitute them when the package actually gets installed. slay πŸ’…

πŸ’Ύ PACKAGE REGISTRY πŸ’Ύ

okay bestie, so CMake lowkey runs two central spots where packages that were built or installed anywhere on your system can get registered:

  • the User Package Registry
  • the System Package Registry

These registries are literally the moment when your project's package lives in some unhinged non-standard install location or straight up inside its own build tree. 🫢 A project can populate either the user or the system registry (using its own means β€” see below) to point at its location. Either way, the package should store a Package Configuration File (<PackageName>Config.cmake) at the registered location, and optionally a Package Version File (<PackageName>ConfigVersion.cmake) too.

The find_package() command searches these two registries as two of the search steps spelled out in its docs. And ngl, if it has sufficient permissions, it also goes through and removes stale registry entries β€” the ones pointing at directories that don't exist or that don't contain a matching package configuration file. it's giving housekeeping icon πŸ’–

πŸ‘€ User Package Registry πŸ‘€

The User Package Registry lives in a per-user location. You can use the export(PACKAGE) command to register a project build tree in the user package registry. CMake currently provides no interface to add install trees to the user package registry β€” so installers have to be manually taught to register their packages if you want that, fr fr. πŸ™ˆ

On Windows, the user package registry is stored in the Windows registry under a key in HKEY_CURRENT_USER.

A <PackageName> may appear under registry key:

HKEY_CURRENT_USER\Software\Kitware\CMake\Packages\<PackageName>

as a REG_SZ value, with arbitrary name, that specifies the directory containing the package configuration file.

On UNIX platforms, the user package registry is stored in the user home directory under ~/.cmake/packages. A <PackageName> may appear under the directory:

~/.cmake/packages/<PackageName>

as a file, with arbitrary name, whose content specifies the directory containing the package configuration file. ✨

🏒 System Package Registry 🏒

The System Package Registry is stored in a system-wide location. And yeah, CMake currently provides no interface to add to the system package registry either β€” installers must be manually taught to register their packages if desired. same energy, different scope 😭

On Windows, the system package registry is stored in the Windows registry under a key in HKEY_LOCAL_MACHINE. A <PackageName> may appear under registry key:

HKEY_LOCAL_MACHINE\Software\Kitware\CMake\Packages\<PackageName>

as a REG_SZ value, with arbitrary name, that specifies the directory containing the package configuration file.

There is no system package registry on non-Windows platforms. like, zero. none. don't go looking πŸ’€

🚫 Disabling the Package Registry 🚫

Sometimes using the Package Registries is just not the vibe. CMake lets you disable them with these variables:

  • The export(PACKAGE) command does not populate the user package registry when CMP0090 is set to NEW β€” unless the CMAKE_EXPORT_PACKAGE_REGISTRY variable explicitly enables it. But when CMP0090 is not set to NEW, then export(PACKAGE) does populate the user package registry, unless CMAKE_EXPORT_NO_PACKAGE_REGISTRY explicitly disables it. yes it's conditional on conditional, stay with me 🫠
  • CMAKE_FIND_USE_PACKAGE_REGISTRY disables the User Package Registry in all the find_package() calls when set to FALSE.
  • The deprecated CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY disables the User Package Registry in all the find_package() calls when set to TRUE. This variable is ignored when CMAKE_FIND_USE_PACKAGE_REGISTRY has been set. πŸ’€
  • CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY disables the System Package Registry in all the find_package() calls.

πŸ§ͺ Package Registry Example πŸ§ͺ

A simple convention for naming package registry entries is to use content hashes β€” they're deterministic and unlikely to collide (and yeah, export(PACKAGE) uses exactly this approach πŸ’…). The name of an entry referencing a specific directory is simply the content hash of the directory path itself.

So if a project arranges for package registry entries to exist, like:

> reg query HKCU\Software\Kitware\CMake\Packages\MyPackage
HKEY_CURRENT_USER\Software\Kitware\CMake\Packages\MyPackage
 45e7d55f13b87179bb12f907c8de6fc4 REG_SZ c:/Users/Me/Work/lib/cmake/MyPackage
 7b4a9844f681c80ce93190d4e3185db9 REG_SZ c:/Users/Me/Work/MyPackage-build

or

$ cat ~/.cmake/packages/MyPackage/7d1fb77e07ce59a81bed093bbee945bd
/home/me/work/lib/cmake/MyPackage
$ cat ~/.cmake/packages/MyPackage/f92c1db873a1937f3100706657c63e07
/home/me/work/MyPackage-build

then this CMakeLists.txt code:

find_package(MyPackage)

will search the registered locations for package configuration files (MyPackageConfig.cmake). πŸŽ€

Now the details, because they matter: the search order among package registry entries for a single package is unspecified, and the entry names (the hashes in this example) have no meaning. Registered locations may contain package version files (MyPackageConfigVersion.cmake) to tell find_package() whether a specific location is suitable for the version requested. iykyk 🌸

πŸ”‘ Package Registry Ownership πŸ”‘

Package registry entries are individually owned by the project installations that they reference. A package installer is responsible for adding its own entry, and the corresponding uninstaller is responsible for removing it. that's the whole deal, no cap.

The export(PACKAGE) command populates the user package registry with the location of a project build tree. And bestie, build trees tend to get deleted by developers, and they have no "uninstall" event that could trigger removal of their entries. 😭 So to keep the registries clean, the find_package() command automatically removes stale entries it encounters β€” if it has sufficient permissions.

CMake provides no interface to remove an entry referencing an existing build tree once export(PACKAGE) has been invoked. However β€” and this is the loophole 🧚 β€” if the project removes its package configuration file from the build tree, then the entry referencing that location will be considered stale.


2000-2026 Kitware, Inc. and Contributors


okay that's the whole page, you made it to the end, I'm so proud of you πŸ₯ΉπŸ’– this was CMAKE-PACKAGES(7) from CMake 4.4.5, dated Oct 28, 2026 β€” go forth and find those packages, icon behavior only πŸ‘‘βœ¨πŸ¦‹

πŸ“¦ where this page comes from