Installing and exporting a package

CMake · Advanced · 15 min

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 like CMAKE_INSTALL_LIBDIR and CMAKE_INSTALL_INCLUDEDIR.
  • Headers: install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}), or the FILE_SET HEADERS feature (3.23+), which installs headers declared with target_sources.
  • The export file: install(EXPORT mylibTargets NAMESPACE mylib:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib) writes mylibTargets.cmake, which recreates mylib::mylib as an imported target with its usage requirements.
  • Config and version files: a small mylibConfig.cmake that includes the targets file (and calls find_dependency for public dependencies), plus a version file generated by write_basic_package_version_file from CMakePackageConfigHelpers.

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.

Check yourself

  1. What does install(EXPORT geoTargets NAMESPACE geo:: ...) produce?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cmake.org/cmake/help/latest/guide/importing-exporting/index.html