CMake/Tutorials/How to create a ProjectConfig.cmake file

From KitwarePublic
Jump to navigationJump to search

How to create a ProjectConfig.cmake file

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 <name>Config.cmake or a <lower-name>-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 do so for a very simple project.

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
|-- FooBarVersion.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.

The main FooBar/CMakeLists.txt file

A simple FooBar/CMakeLists.txt could look like the following, where the really interesting stuff starts after the respective comment.

<source lang="cmake"> 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})
  1. 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")

set(INSTALL_DATA_DIR share CACHE PATH

 "Installation directory for data files")
  1. Make relative paths absolute (needed later on)

foreach(p LIB BIN INCLUDE DATA)

 set(var INSTALL_${p}_DIR)
 if(NOT IS_ABSOLUTE "${${var}}")
   set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}")
 endif()

endforeach()

  1. set up include-directories

include_directories(

 "${PROJECT_SOURCE_DIR}"   # to find foo/foo.h
 "${PROJECT_BINARY_DIR}")  # to find foo/config.h
  1. Add sub-directories

add_subdirectory(foo) add_subdirectory(bar)

  1. The interesting stuff goes here
  2. ===============================
  1. Add all targets to the build-tree export set

export(TARGETS foo bar

 FILE "${PROJECT_BINARY_DIR}/FooBarLibraryDepends.cmake")
  1. Export the package for use from the build-tree
  2. (this registers the build-tree with a global CMake-registry)

export(PACKAGE FooBar)

  1. Create a FooBarConfig.cmake file for the use from the build tree

set(FOOBAR_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${FOOBAR_BINARY_DIR}") set(FOOBAR_LIB_DIR "${PROJECT_BINARY_DIR}/foo") set(FOOBAR_CMAKE_DIR "${PROJECT_BINARY_DIR}") configure_file(FooBarConfig.cmake.in

 "${PROJECT_BINARY_DIR}/FooBarConfig.cmake" @ONLY)
  1. Install the export set for use with the install-tree

install(EXPORT FooBarLibraryDepends DESTINATION

 "${INSTALL_DATA_DIR}/FooBar/CMake"
 COMPONENT dev)
  1. Create a FooBarConfig.cmake file for the use from the install tree
  2. and install it

set(FOOBAR_INCLUDE_DIRS "${INSTALL_INCLUDE_DIR}") set(FOOBAR_LIB_DIR ${INSTALL_LIB_DIR}") set(FOOBAR_CMAKE_DIR "${INSTALL_DATA_DIR}/FooBar/CMake") configure_file(FooBarConfig.cmake.in

 "${PROJECT_BINARY_DIR}/InstallFiles/FooBarConfig.cmake" @ONLY)

install(FILES

 "${PROJECT_BINARY_DIR}/InstallFiles/FooBarConfig.cmake"
 DESTINATION "${FOOBAR_CMAKE_DIR} COMPONENT dev)

</source>

The files FooBar/{foo,bar}/CMakeLists.txt

The file FooBar/foo/CMakeLists.txt is pretty simple and looks like expected:

<source lang="cmake"> configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)

add_library(foo SHARED foo.c foo.h)

set_target_properties(foo PROPERTIES

 PUBLIC_HEADER "foo.h")

install(TARGETS foo

 # IMPORTANT: Add the foo library to the "export-set"
 EXPORT FooBarLibraryDependencies
 RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin
 LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib
 PUBLIC_HEADER_DESTINATION "${INSTALL_INCLUDE_DIR}
   COMPONENT dev)

</source>

The file FooBar/bar/CMakeLists.txt is even shorter:

<source lang="cmake"> add_executable(bar bar.c)

target_link_libraries(bar foo)

install(TARGETS bar

 # IMPORTANT: Add the bar executable to the "export-set"
 EXPORT FooBarLibraryDependencies
 RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin)

</source>

The FooBar/FooBarConfig.cmake.in file

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:

<source lang="cmake">

  1. - Config file for the FooBar package
  2. It defines the following variables
  3. FOOBAR_INCLUDE_DIRS - include directories for FooBar
  4. FOOBAR_LIBRARY_DIRS - library directories for FooBar (normally not used!)
  5. FOOBAR_LIBRARIES - libraries to link against
  6. FOOBAR_EXECUTABLE - the bar executable
  1. Tell the user project where to find our headers and libraries

set(FOOBAR_INCLUDE_DIRS "@FOOBAR_INCLUDE_DIRS@") set(FOOBAR_LIBRARY_DIRS "@FOOBAR_LIB_DIR@")

  1. Our library dependencies (contains definitions for IMPORTED targets)

include("@FOOBAR_CMAKE_DIR@/FooBarLibraryDepends.cmake")

  1. These are IMPORTED targets created by FooBarLibraryDepends.cmake

set(FOOBAR_LIBRARIES foo) set(FOOBAR_EXECUTABLE bar) </source>

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 install-tree location of the FooBarUse.cmake file.

The FooBar/FooBarVersion.cmake.in file

The last file to discuss is the FooBar/FooBarVersion.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:

<source lang="cmake"> set(PACKAGE_VERSION "@FOOBAR_VERSION@")

  1. 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() </source>