Introduction
Native CMake projects that are intended to be used by other projects
(e.g. libraries, but also tools that could be useful as a build-utility,
such as documentation generators, wrapper generators, etc.) should
provide at a minimum a Config.cmake
or a
-config.cmake
file. This file can then be used by the
find_package()
command in config-mode to provide information about
include-directories, libraries and their dependencies, required
compile-flags or locations of executables. You are advised to carefully
read the documentation of the find_package()
command before
proceeding. This short article will show you how to a very simple
project usable for other CMake-based projects.
The full example is available as a download in the zip-file FooBar.zip.
The FooBar project
Let's assume the project contains a simple shared library, foo and a utility that uses the library, bar. The source tree could have the following layout:
FooBar/
|-- CMakeLists.txt
|-- FooBarConfig.cmake.in
|-- FooBarConfigVersion.cmake.in
|-- foo/
| |-- CMakeLists.txt
| |-- config.h.in
| |-- foo.h
| `-- foo.c
`-- bar/
|-- CMakeLists.txt
`-- bar.c
The files FooBar/foo/{config.h.in,foo.h,foo.c}
and FooBar/bar/bar.c
are of little interest here and their contents are left to the
imagination of the reader.
FooBar/CMakeLists.txt
file
The main A simple FooBar/CMakeLists.txt
could look like the following, where
the really interesting stuff starts after the respective comment.
cmake_minimum_required(VERSION 2.8)
project(FooBar C)
set(FOOBAR_MAJOR_VERSION 0)
set(FOOBAR_MINOR_VERSION 1)
set(FOOBAR_PATCH_VERSION 0)
set(FOOBAR_VERSION
${FOOBAR_MAJOR_VERSION}.${FOOBAR_MINOR_VERSION}.${FOOBAR_PATCH_VERSION})
# Offer the user the choice of overriding the installation directories
set(INSTALL_LIB_DIR lib CACHE PATH "Installation directory for libraries")
set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables")
set(INSTALL_INCLUDE_DIR include CACHE PATH
"Installation directory for header files")
if(WIN32 AND NOT CYGWIN)
set(DEF_INSTALL_CMAKE_DIR CMake)
else()
set(DEF_INSTALL_CMAKE_DIR lib/CMake/FooBar)
endif()
set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH
"Installation directory for CMake files")
# Make relative paths absolute (needed later on)
foreach(p LIB BIN INCLUDE CMAKE)
set(var INSTALL_${p}_DIR)
if(NOT IS_ABSOLUTE "${${var}}")
set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}")
endif()
endforeach()
# set up include-directories
include_directories(
"${PROJECT_SOURCE_DIR}" # to find foo/foo.h
"${PROJECT_BINARY_DIR}") # to find foo/config.h
# Add sub-directories
add_subdirectory(foo)
add_subdirectory(bar)
# The interesting stuff goes here
# ===============================
# Add all targets to the build-tree export set
export(TARGETS foo bar
FILE "${PROJECT_BINARY_DIR}/FooBarTargets.cmake")
# Export the package for use from the build-tree
# (this registers the build-tree with a global CMake-registry)
export(PACKAGE FooBar)
# Create the FooBarConfig.cmake and FooBarConfigVersion files
file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}"
"${INSTALL_INCLUDE_DIR}")
# ... for the build tree
set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
configure_file(FooBarConfig.cmake.in
"${PROJECT_BINARY_DIR}/FooBarConfig.cmake" @ONLY)
# ... for the install tree
set(CONF_INCLUDE_DIRS "\${FOOBAR_CMAKE_DIR}/${REL_INCLUDE_DIR}")
configure_file(FooBarConfig.cmake.in
"${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FooBarConfig.cmake" @ONLY)
# ... for both
configure_file(FooBarConfigVersion.cmake.in
"${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake" @ONLY)
# Install the FooBarConfig.cmake and FooBarConfigVersion.cmake
install(FILES
"${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FooBarConfig.cmake"
"${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake"
DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)
# Install the export set for use with the install-tree
install(EXPORT FooBarTargets DESTINATION
"${INSTALL_CMAKE_DIR}" COMPONENT dev)
FooBar/{foo,bar}/CMakeLists.txt
The files The file FooBar/foo/CMakeLists.txt
is pretty simple and looks like
expected:
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
add_library(foo SHARED foo.c foo.h config.h.in)
set_target_properties(foo PROPERTIES
PUBLIC_HEADER "foo.h;${CMAKE_CURRENT_BINARY_DIR}/config.h")
install(TARGETS foo
# IMPORTANT: Add the foo library to the "export-set"
EXPORT FooBarTargets
RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin
LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib
PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/foo"
COMPONENT dev)
The file FooBar/bar/CMakeLists.txt
is even shorter:
add_executable(bar bar.c)
target_link_libraries(bar foo)
install(TARGETS bar
# IMPORTANT: Add the bar executable to the "export-set"
EXPORT FooBarTargets
RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin)
FooBar/FooBarConfig.cmake.in
file
The The really interesting file is FooBar/FooBarConfig.cmake.in
. Although
it usually can be quite simple, it seems to cause considerable confusion
to new CMake-users. For the FooBar project the following is a plausible
implementation:
# - Config file for the FooBar package
# It defines the following variables
# FOOBAR_INCLUDE_DIRS - include directories for FooBar
# FOOBAR_LIBRARIES - libraries to link against
# FOOBAR_EXECUTABLE - the bar executable
# Compute paths
get_filename_component(FOOBAR_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
set(FOOBAR_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@")
# Our library dependencies (contains definitions for IMPORTED targets)
if(NOT TARGET foo AND NOT FooBar_BINARY_DIR)
include("${FOOBAR_CMAKE_DIR}/FooBarTargets.cmake")
endif()
# These are IMPORTED targets created by FooBarTargets.cmake
set(FOOBAR_LIBRARIES foo)
set(FOOBAR_EXECUTABLE bar)
If your package also provides CMake macros or functions, you might want
to put them in a file FooBarUse.cmake
(or similar), install it
alongside FooBarConfig.cmake
and define the variable FOOBAR_USE_FILE
in above code and set it to the location of the FooBarUse.cmake
file.
FooBar/FooBarConfigVersion.cmake.in
file
The The last file to discuss is the FooBar/FooBarConfigVersion.cmake.in
.
It is important because it allows client projects to determine the
version of FooBar they found using the find_package
command, but more
importantly, it allows the same command to automatically determine
whether the detected version is suitable if the client-project requested
a minimum (or even exact) version of FooBar. The file is also
straightforward and usually takes the following form:
set(PACKAGE_VERSION "@FOOBAR_VERSION@")
# Check whether the requested PACKAGE_FIND_VERSION is compatible
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
Closing Remarks
In the above approach the FooBar/FooBarConfig.cmake.in
file uses a
relative path to locate the include directory. This has the advantage of
making the installed package relocatable, i.e. the user can relocate
the whole installation tree and as long as the relative paths remain the
same, the package will continue to work with no need for manual tweaking
of the installed files. However, the approach also has a serious
drawback: On Windows it requires that the variables
INSTALL_INCLUDE_DIR
and INSTALL_CMAKE_DIR
are set to paths with the
same drive letter. Otherwise there is no valid relative path connecting
the two. If such an installation is a realistic scenario for your
project, you might need to hard-code the full INSTALL_INCLUDE_DIR
path
when configuring FooBar/FooBarConfig.cmake.in
at the cost of your
package being no longer relocatable.
This page was initially populated by conversion from its original location in another wiki.