A library that installs a proper CMake package is trivially consumable by other projects and package managers.
install() rules describe what cmake --install build --prefix /usr/local copies. For a library to be usable by others via find_package(mylib) you need four pieces:
- Targets with an export set:
install(TARGETS mylib EXPORT mylibTargets ...).include(GNUInstallDirs)provides standard destinations likeCMAKE_INSTALL_LIBDIRandCMAKE_INSTALL_INCLUDEDIR. - Headers:
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}), or theFILE_SET HEADERSfeature (3.23+), which installs headers declared withtarget_sources. - The export file:
install(EXPORT mylibTargets NAMESPACE mylib:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib)writesmylibTargets.cmake, which recreatesmylib::mylibas an imported target with its usage requirements. - Config and version files: a small
mylibConfig.cmakethat includes the targets file (and callsfind_dependencyfor public dependencies), plus a version file generated bywrite_basic_package_version_filefromCMakePackageConfigHelpers.
Include directories must differ between the build tree and the installed tree, hence $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> and $<INSTALL_INTERFACE:include>. Never put absolute source paths in an installed interface: CMake rejects them.
Test the result by installing to a temporary prefix and building a tiny consumer project with -DCMAKE_PREFIX_PATH pointing at it.
Example
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
add_library(geo src/geo.cpp)
add_library(geo::geo ALIAS geo)
target_include_directories(geo PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
install(TARGETS geo EXPORT geoTargets)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(EXPORT geoTargets NAMESPACE geo::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/geo)
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/geoConfig.cmake
"include(\"\${CMAKE_CURRENT_LIST_DIR}/geoTargets.cmake\")\n")
write_basic_package_version_file(geoConfigVersion.cmake
VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/geoConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/geoConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/geo)
# $ cmake --install build --prefix /tmp/geo-install
# consumer: find_package(geo 1.0 REQUIRED) + target_link_libraries(app PRIVATE geo::geo)Remember
- install(TARGETS ... EXPORT) + install(EXPORT ... NAMESPACE) create importable targets
- Use BUILD_INTERFACE / INSTALL_INTERFACE for include paths
- Ship a Config and ConfigVersion file so find_package works
Watch out for
- Hard-coding absolute include paths into the exported target, which breaks as soon as the package is relocated.