CMake/Tutorials/How to create a ProjectConfig.cmake file: Difference between revisions

From KitwarePublic
Jump to navigationJump to search
(Fix bugs in CMakeLists.txt)
(Update for new conventions used for target exports)
Line 14: Line 14:
|-- FooBarConfig.cmake.in
|-- FooBarConfig.cmake.in
|-- FooBarConfigVersion.cmake.in
|-- FooBarConfigVersion.cmake.in
|-- FooBarBuildTreeSettings.cmake.in
|-- foo/
|-- foo/
|  |-- CMakeLists.txt
|  |-- CMakeLists.txt
Line 76: Line 75:
# Add all targets to the build-tree export set
# Add all targets to the build-tree export set
export(TARGETS foo bar
export(TARGETS foo bar
   FILE "${PROJECT_BINARY_DIR}/FooBarLibraryDepends.cmake")
   FILE "${PROJECT_BINARY_DIR}/FooBarTargets.cmake")


# Export the package for use from the build-tree
# Export the package for use from the build-tree
# (this registers the build-tree with a global CMake-registry)
# (this registers the build-tree with a global CMake-registry)
export(PACKAGE FooBar)
export(PACKAGE FooBar)
# Create a FooBarBuildTreeSettings.cmake file for the use from the build tree
configure_file(FooBarBuildTreeSettings.cmake.in
  "${PROJECT_BINARY_DIR}/FooBarBuildTreeSettings.cmake" @ONLY)


# Create the FooBarConfig.cmake and FooBarConfigVersion files
# Create the FooBarConfig.cmake and FooBarConfigVersion files
file(RELATIVE_PATH CONF_REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}"
file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}"
   "${INSTALL_INCLUDE_DIR}")
   "${INSTALL_INCLUDE_DIR}")
# ... for the build tree
set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
configure_file(FooBarConfig.cmake.in
configure_file(FooBarConfig.cmake.in
   "${PROJECT_BINARY_DIR}/FooBarConfig.cmake" @ONLY)
   "${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
configure_file(FooBarConfigVersion.cmake.in
   "${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake" @ONLY)
   "${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake" @ONLY)
Line 96: Line 98:
# Install the FooBarConfig.cmake and FooBarConfigVersion.cmake
# Install the FooBarConfig.cmake and FooBarConfigVersion.cmake
install(FILES
install(FILES
   "${PROJECT_BINARY_DIR}/FooBarConfig.cmake"
   "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FooBarConfig.cmake"
   "${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake"
   "${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake"
   DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)
   DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)


# Install the export set for use with the install-tree
# Install the export set for use with the install-tree
install(EXPORT FooBarLibraryDepends DESTINATION
install(EXPORT FooBarTargets DESTINATION
   "${INSTALL_CMAKE_DIR}" COMPONENT dev)
   "${INSTALL_CMAKE_DIR}" COMPONENT dev)
</source>
</source>
Line 119: Line 121:
install(TARGETS foo
install(TARGETS foo
   # IMPORTANT: Add the foo library to the "export-set"
   # IMPORTANT: Add the foo library to the "export-set"
   EXPORT FooBarLibraryDepends
   EXPORT FooBarTargets
   RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin
   RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin
   LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib
   LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib
Line 135: Line 137:
install(TARGETS bar
install(TARGETS bar
   # IMPORTANT: Add the bar executable to the "export-set"
   # IMPORTANT: Add the bar executable to the "export-set"
   EXPORT FooBarLibraryDepends
   EXPORT FooBarTargets
   RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin)
   RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin)
</source>
</source>
Line 152: Line 154:
# Compute paths
# Compute paths
get_filename_component(FOOBAR_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(FOOBAR_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
if(EXISTS "${FOOBAR_CMAKE_DIR}/CMakeCache.txt")
set(FOOBAR_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@")
  # In build tree
  include("${FOOBAR_CMAKE_DIR}/FooBarBuildTreeSettings.cmake")
else()
  set(FOOBAR_INCLUDE_DIRS "${FOOBAR_CMAKE_DIR}/@CONF_REL_INCLUDE_DIR@")
endif()


# Our library dependencies (contains definitions for IMPORTED targets)
# Our library dependencies (contains definitions for IMPORTED targets)
include("${FOOBAR_CMAKE_DIR}/FooBarLibraryDepends.cmake")
include("${FOOBAR_CMAKE_DIR}/FooBarTargets.cmake")


# These are IMPORTED targets created by FooBarLibraryDepends.cmake
# These are IMPORTED targets created by FooBarTargets.cmake
set(FOOBAR_LIBRARIES foo)
set(FOOBAR_LIBRARIES foo)
set(FOOBAR_EXECUTABLE bar)
set(FOOBAR_EXECUTABLE bar)
Line 168: Line 165:


If your package also provides CMake macros or functions, you might want to put them in a file <tt>FooBarUse.cmake</tt> (or similar), install it alongside <tt>FooBarConfig.cmake</tt> and define the variable <tt>FOOBAR_USE_FILE</tt> in above code and set it to the location of the <tt>FooBarUse.cmake</tt> file.
If your package also provides CMake macros or functions, you might want to put them in a file <tt>FooBarUse.cmake</tt> (or similar), install it alongside <tt>FooBarConfig.cmake</tt> and define the variable <tt>FOOBAR_USE_FILE</tt> in above code and set it to the location of the <tt>FooBarUse.cmake</tt> file.
=== The <tt>FooBar/FooBarBuildTreeSettings.cmake.in</tt> file ===
The file <tt>FooBar/FooBarBuildTreeSettings.cmake.in</tt> is a simple helper file that is used to contain hard-coded paths to the build and source tree we ideally wouldn't want in any of the installed files. For the FooBar project it is as simple as
<source lang="cmake">
set(FOOBAR_INCLUDE_DIRS
  "@PROJECT_SOURCE_DIR@"
  "@PROJECT_BINARY_DIR@")
</source>


=== The <tt>FooBar/FooBarConfigVersion.cmake.in</tt> file ===
=== The <tt>FooBar/FooBarConfigVersion.cmake.in</tt> file ===

Revision as of 16:57, 21 February 2013

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

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")

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

  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}/FooBarTargets.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 the FooBarConfig.cmake and FooBarConfigVersion files

file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}"

  "${INSTALL_INCLUDE_DIR}")
  1. ... 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)
  1. ... 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)
  1. ... for both

configure_file(FooBarConfigVersion.cmake.in

 "${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake" @ONLY)
  1. 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)
  1. Install the export set for use with the install-tree

install(EXPORT FooBarTargets DESTINATION

 "${INSTALL_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 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)

</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 FooBarTargets
 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_LIBRARIES - libraries to link against
  5. FOOBAR_EXECUTABLE - the bar executable
  1. Compute paths

get_filename_component(FOOBAR_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) set(FOOBAR_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@")

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

include("${FOOBAR_CMAKE_DIR}/FooBarTargets.cmake")

  1. These are IMPORTED targets created by FooBarTargets.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 location of the FooBarUse.cmake file.

The FooBar/FooBarConfigVersion.cmake.in file

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:

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

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.