Lesson 23 · Build & ship

Create a CMake package

Turn your install into a real package. The Config.cmake file, the version file, and the find_package story from the consumer's side.

In lesson 17 you used find_package(fmt), and it worked because fmt’s authors shipped a fmtConfig.cmake. This lesson shows you how to ship the same for your library. When you are done, your users write exactly two lines.

The goal

user's CMakeLists.txt
find_package(calc 1.0 REQUIRED) target_link_libraries(myapp PRIVATE calc::calc)

For that to work, the installed library must include, next to itself:

lib/cmake/calc/
├── calcConfig.cmake           # "here is the calc::calc target"
└── calcConfigVersion.cmake    # "which versions of calc am I?"

The commands

Building on lesson 22, the full packaging block in your CMakeLists.txt:

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

# 1. Install targets and export set (from lesson 22).
install(TARGETS calc
    EXPORT calcTargets
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES include/calc/calc.h
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/calc
)

# 2. One export call. The generated file doubles as the package Config file.
install(EXPORT calcTargets
    FILE calcConfig.cmake          # exactly this name
    NAMESPACE calc::
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/calc
)

# 3. The version file next to it, so find_package(calc 1.0) can check versions.
write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/calcConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY AnyNewerVersion
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/calcConfigVersion.cmake"
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/calc
)

find_package(calc) searches for calcConfig.cmake. If a version is requested, it first consults the sibling version file, which claims compatibility or not. Name the file exactly <Name>Config.cmake (or <name>-config.cmake) and everything just works.

COMPATIBILITY, which to pick

  • AnyNewerVersion. The default choice. Any newer version is compatible. Perfect for libraries with a stable API.
  • SameMajorVersion. Only same-major versions (semantic versioning).
  • SameMinorVersion. Only same-minor.
  • ExactVersion. Deprecated in CMake 4.4. It is almost never what you want. If your library can only ever work with an exact version, that is a packaging smell.

Why

The version file exists so consumers can state requirements, find_package(calc 2.0), and CMake can refuse mismatches before loading your Config file. A clear error instead of a broken build.

The consumer side, what you just enabled

cmake --install build --prefix /opt/calc
cmake -S . -B build -DCMAKE_PREFIX_PATH=/opt/calc
find_package(calc 1.0 REQUIRED)
target_link_libraries(myapp PRIVATE calc::calc)

If the header include dirs were declared PUBLIC on the calc target, the consumer gets them for free (lesson 9). That is the whole chain closing.

Pitfall

A consumer error like “find_package(calc) could not find calcConfig.cmake” after a correct install usually means CMAKE_PREFIX_PATH points at the wrong prefix. CMake looks for lib/cmake/calc/ under the prefixes you give it. Install to /opt/calc, then -DCMAKE_PREFIX_PATH=/opt/calc, not /opt/calc/lib/cmake/calc.

Tip

CMakePackageConfigHelpers is bundled with CMake. No downloads. And keep project(calc VERSION 1.2.3 ...) (lesson 5) as the single source of the version. write_basic_package_version_file reads ${PROJECT_VERSION} from it, so you bump one number and everything follows.

Read the docs