From 20cafa2e1faff577143eb56d14e5c3998cb7d6a9 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Wed, 27 Nov 2013 13:43:52 +0100 Subject: [PATCH 1/2] Split the find_dependency macro into a separate file. This allows Config file authors to use it without having to use CONFIGURE_PACKAGE_CONFIG_FILE. --- Help/manual/cmake-modules.7.rst | 1 + Help/module/CMakeFindDependencyMacro.rst | 1 + Modules/CMakeFindDependencyMacro.cmake | 58 ++++++++++++++++++++++++ Modules/CMakePackageConfigHelpers.cmake | 35 ++------------ 4 files changed, 64 insertions(+), 31 deletions(-) create mode 100644 Help/module/CMakeFindDependencyMacro.rst create mode 100644 Modules/CMakeFindDependencyMacro.cmake diff --git a/Help/manual/cmake-modules.7.rst b/Help/manual/cmake-modules.7.rst index f148a5102..3f819d69a 100644 --- a/Help/manual/cmake-modules.7.rst +++ b/Help/manual/cmake-modules.7.rst @@ -39,6 +39,7 @@ All Modules /module/CMakeDependentOption /module/CMakeDetermineVSServicePack /module/CMakeExpandImportedTargets + /module/CMakeFindDependencyMacro /module/CMakeFindFrameworks /module/CMakeFindPackageMode /module/CMakeForceCompiler diff --git a/Help/module/CMakeFindDependencyMacro.rst b/Help/module/CMakeFindDependencyMacro.rst new file mode 100644 index 000000000..5b5b550a3 --- /dev/null +++ b/Help/module/CMakeFindDependencyMacro.rst @@ -0,0 +1 @@ +.. cmake-module:: ../../Modules/CMakeFindDependencyMacro.cmake diff --git a/Modules/CMakeFindDependencyMacro.cmake b/Modules/CMakeFindDependencyMacro.cmake new file mode 100644 index 000000000..596c6fc93 --- /dev/null +++ b/Modules/CMakeFindDependencyMacro.cmake @@ -0,0 +1,58 @@ +#.rst: +# CMakeFindDependencyMacro +# ------------------------- +# +# :: +# +# find_dependency( []) +# +# +# ``find_dependency()`` wraps a :command:`find_package` call for a package +# dependency. It is designed to be used in a Config.cmake file, and it +# forwards the correct parameters for EXACT, QUIET and REQUIRED which were +# passed to the original :command:`find_package` call. It also sets an +# informative diagnostic message if the dependency could not be found. +# + +#============================================================================= +# Copyright 2013 Stephen Kelly +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +macro(find_dependency dep) + if (NOT ${dep}_FOUND) + if (${ARGV1}) + set(version ${ARGV1}) + endif() + set(exact_arg) + if(${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION_EXACT) + set(exact_arg EXACT) + endif() + set(quiet_arg) + if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(quiet_arg QUIET) + endif() + set(required_arg) + if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) + set(required_arg REQUIRED) + endif() + + find_package(${dep} ${version} ${exact_arg} ${quiet_arg} ${required_arg}) + if (NOT ${dep}_FOUND) + set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE "${CMAKE_FIND_PACKAGE_NAME} could not be found because dependency ${dep} could not be found.") + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND False) + return() + endif() + set(required_arg) + set(quiet_arg) + set(exact_arg) + endif() +endmacro() diff --git a/Modules/CMakePackageConfigHelpers.cmake b/Modules/CMakePackageConfigHelpers.cmake index 855af9c9f..2c1c61378 100644 --- a/Modules/CMakePackageConfigHelpers.cmake +++ b/Modules/CMakePackageConfigHelpers.cmake @@ -196,11 +196,13 @@ include(CMakeParseArguments) include(WriteBasicConfigVersionFile) +include(CMakeFindDependencyMacro) macro(WRITE_BASIC_PACKAGE_VERSION_FILE) write_basic_config_version_file(${ARGN}) endmacro() +set(cfpch_dir ${CMAKE_CURRENT_LIST_DIR}) function(CONFIGURE_PACKAGE_CONFIG_FILE _inputFile _outputFile) set(options NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO NO_FIND_DEPENDENCY_MACRO) @@ -290,37 +292,8 @@ endmacro() endif() if(NOT CCF_NO_FIND_DEPENDENCY_MACRO) - set(PACKAGE_INIT "${PACKAGE_INIT} -macro(find_dependency dep) - if (NOT \${dep}_FOUND) - if (\${ARGV1}) - set(version \${ARGV1}) - endif() - set(exact_arg) - if(\${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION_EXACT) - set(exact_arg EXACT) - endif() - set(quiet_arg) - if(\${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) - set(quiet_arg QUIET) - endif() - set(required_arg) - if(\${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) - set(required_arg REQUIRED) - endif() - - find_package(\${dep} \${version} \${exact_arg} \${quiet_arg} \${required_arg}) - if (NOT \${dep}_FOUND) - set(\${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE \"\${CMAKE_FIND_PACKAGE_NAME} could not be found because dependency \${dep} could not be found.\") - set(\${CMAKE_FIND_PACKAGE_NAME}_FOUND False) - return() - endif() - set(required_arg) - set(quiet_arg) - set(exact_arg) - endif() -endmacro() -") + file(READ "${cfpch_dir}/CMakeFindDependencyMacro.cmake" find_dependency_macro) + set(PACKAGE_INIT "${PACKAGE_INIT} ${find_dependency_macro}") endif() set(PACKAGE_INIT "${PACKAGE_INIT} From 3fe4ac828d91db6ebffce6a2e47b8f4cb069a49a Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Tue, 26 Nov 2013 20:01:32 +0100 Subject: [PATCH 2/2] Help: Add a CMake manual for Packages related docs. --- Help/index.rst | 1 + Help/manual/cmake-packages.7.rst | 414 +++++++++++++++++++++++++++++++ 2 files changed, 415 insertions(+) create mode 100644 Help/manual/cmake-packages.7.rst diff --git a/Help/index.rst b/Help/index.rst index 0d3382583..a466e4b5a 100644 --- a/Help/index.rst +++ b/Help/index.rst @@ -28,6 +28,7 @@ Reference Manuals /manual/cmake-commands.7 /manual/cmake-generators.7 /manual/cmake-modules.7 + /manual/cmake-packages.7 /manual/cmake-policies.7 /manual/cmake-properties.7 /manual/cmake-variables.7 diff --git a/Help/manual/cmake-packages.7.rst b/Help/manual/cmake-packages.7.rst new file mode 100644 index 000000000..952da3caa --- /dev/null +++ b/Help/manual/cmake-packages.7.rst @@ -0,0 +1,414 @@ +.. cmake-manual-description: CMake Packages Reference + +cmake-packages(7) +***************** + +.. only:: html or latex + + .. contents:: + +Introduction +============ + +Packages provide dependency information to CMake based buildsystems. Packages +are found with the :command:`find_package` command. The result of +using ``find_package`` is either a set of :prop_tgt:`IMPORTED` targets, or +a set of variables corresponding to build-relevant information. + +Using Packages +============== + +CMake provides direct support for two forms of packages, +`Config-file Packages`_ and `Find-module Packages`_. +Indirect support for ``pkg-config`` packages is also provided via +the :module:`FindPkgConfig` module. In all cases, the basic form +of :command:`find_package` calls is the same: + +.. code-block:: cmake + + find_package(Qt4 4.7.0 REQUIRED) # CMake provides a Qt4 find-module + find_package(Qt5Core 5.1.0 REQUIRED) # Qt provides a Qt5 package config file. + find_package(LibXml2 REQUIRED) # Use pkg-config via the LibXml2 find-module + +In cases where it is known that a package configuration file is provided by +upstream, and only that should be used, the ``CONFIG`` keyword may be passed +to :command:`find_package`: + +.. code-block:: cmake + + find_package(Qt5Core 5.1.0 CONFIG REQUIRED) + find_package(Qt5Gui 5.1.0 CONFIG) + +Similarly, the ``MODULE`` keyword says to use only a find-module: + +.. code-block:: cmake + + find_package(Qt4 4.7.0 MODULE REQUIRED) + +Specifying the type of package explicitly improves the error message shown to +the user if it is not found. + +Both types of packages also support specifying components of a package, +either after the ``REQUIRED`` keyword: + +.. code-block:: cmake + + find_package(Qt5 5.1.0 CONFIG REQUIRED Widgets Xml Sql) + +or as a separate ``COMPONENTS`` list: + +.. code-block:: cmake + + find_package(Qt5 5.1.0 COMPONENTS Widgets Xml Sql) + +or as a separate ``OPTIONAL_COMPONENTS`` list: + +.. code-block:: cmake + + find_package(Qt5 5.1.0 COMPONENTS Widgets + OPTIONAL_COMPONENTS Xml Sql + ) + +Handling of ``COMPONENTS`` and ``OPTIONAL_COMPONENTS`` is defined by the +package. + +Config-file Packages +-------------------- + +A config-file package is a set of files provided by upstreams for downstreams +to use. CMake searches in a number of locations for package configuration files, as +described in the :command:`find_package` documentation. The most simple way for +a CMake user to tell :manual:`cmake(1)` to search in a non-standard prefix for +a package is to set the ``CMAKE_PREFIX_PATH`` cache variable. + +Config-file packages are provided by upstream vendors as part of development +packages, that is, they belong with the header files and any other files +provided to assist downsteams in using the package. + +A set of variables which provide package status information are also set +automatically when using a config-file package. The ``_FOUND`` +variable is set to true or false, depending on whether the package was +found. The ``_DIR`` cache variable is set to the location of the +package configuration file. + +Find-module Packages +-------------------- + +A find module is a file with a set of rules for finding the required pieces of +a dependency, primarily header files and libraries. Typically, a find module +is needed when the upstream is not built with CMake, or is not CMake-aware +enough to otherwise provide a package configuration file. Unlike a package configuration +file, it is not shipped with upstream, but is used by downstream to find the +files by guessing locations of files with platform-specific hints. + +Unlike the case of an upstream-provided package configuration file, no single point +of reference identifies the package as being found, so the ``_FOUND`` +variable is not automatically set by the :command:`find_package` command. It +can still be expected to be set by convention however and should be set by +the author of the Find-module. Similarly there is no ``_DIR`` variable, +but each of the artifacts such as library locations and header file locations +provide a separate cache variable. + +See the :manual:`cmake-developer(7)` manual for more information about creating +Find-module files. + +Package Layout +============== + +A config-file package consists of a `Package Configuration File`_ and +optionally a `Package Version File`_ provided with the project distribution. + +Package Configuration File +-------------------------- + +Consider a project ``Foo`` that installs the following files:: + + /include/foo-1.2/foo.h + /lib/foo-1.2/libfoo.a + +It may also provide a CMake package configuration file:: + + /lib/cmake/foo-1.2/FooConfig.cmake + +with content defining :prop_tgt:`IMPORTED` targets, or defining variables, such +as:: + + # ... + # (compute PREFIX relative to file location) + # ... + set(Foo_INCLUDE_DIRS ${PREFIX}/include/foo-1.2) + set(Foo_LIBRARIES ${PREFIX}/lib/foo-1.2/libfoo.a) + +If another project wishes to use ``Foo`` it need only to locate the ``FooConfig.cmake`` +file and load it to get all the information it needs about package content +locations. Since the package configuration file is provided by the package +installation it already knows all the file locations. + +The :command:`find_package` command may be used to search for the package +configuration file. This command constructs a set of installation prefixes +and searches under each prefix in several locations. Given the name ``Foo``, +it looks for a file called ``FooConfig.cmake`` or ``foo-config.cmake``. +The full set of locations is specified in the :command:`find_package` command +documentation. One place it looks is:: + + /lib/cmake/Foo*/ + +where ``Foo*`` is a case-insensitive globbing expression. In our example the +globbing expression will match ``/lib/cmake/foo-1.2`` and the package +configuration file will be found. + +Once found, a package configuration file is immediately loaded. It, together +with a package version file, contains all the information the project needs to +use the package. + +Package Version File +-------------------- + +When the :command:`find_package` command finds a candidate package configuration +file it looks next to it for a version file. The version file is loaded to test +whether the package version is an acceptable match for the version requested. +If the version file claims compatibility the configuration file is accepted. +Otherwise it is ignored. + +The name of the package version file must match that of the package configuration +file but has either ``-version`` or ``Version`` appended to the name before +the ``.cmake`` extension. For example, the files:: + + /lib/cmake/foo-1.3/foo-config.cmake + /lib/cmake/foo-1.3/foo-config-version.cmake + +and:: + + /lib/cmake/bar-4.2/BarConfig.cmake + /lib/cmake/bar-4.2/BarConfigVersion.cmake + +are each pairs of package configuration files and corresponding package version +files. + +When the :command:`find_package` command loads a version file it first sets the +following variables: + +``PACKAGE_FIND_NAME`` + The name + +``PACKAGE_FIND_VERSION`` + Full requested version string + +``PACKAGE_FIND_VERSION_MAJOR`` + Major version if requested, else 0 + +``PACKAGE_FIND_VERSION_MINOR`` + Minor version if requested, else 0 + +``PACKAGE_FIND_VERSION_PATCH`` + Patch version if requested, else 0 + +``PACKAGE_FIND_VERSION_TWEAK`` + Tweak version if requested, else 0 + +``PACKAGE_FIND_VERSION_COUNT`` + Number of version components, 0 to 4 + +The version file must use these variables to check whether it is compatible or +an exact match for the requested version and set the following variables with +results: + +``PACKAGE_VERSION`` + Full provided version string + +``PACKAGE_VERSION_EXACT`` + True if version is exact match + +``PACKAGE_VERSION_COMPATIBLE`` + True if version is compatible + +``PACKAGE_VERSION_UNSUITABLE`` + True if unsuitable as any version + +Version files are loaded in a nested scope so they are free to set any variables +they wish as part of their computation. The find_package command wipes out the +scope when the version file has completed and it has checked the output +variables. When the version file claims to be an acceptable match for the +requested version the find_package command sets the following variables for +use by the project: + +``_VERSION`` + Full provided version string + +``_VERSION_MAJOR`` + Major version if provided, else 0 + +``_VERSION_MINOR`` + Minor version if provided, else 0 + +``_VERSION_PATCH`` + Patch version if provided, else 0 + +``_VERSION_TWEAK`` + Tweak version if provided, else 0 + +``_VERSION_COUNT`` + Number of version components, 0 to 4 + +The variables report the version of the package that was actually found. +The ```` part of their name matches the argument given to the +:command:`find_package` command. + +Creating Packages +================= + +Usually, the upstream depends on CMake itself and can use some CMake facilities +for creating the package files. Consider an upstream which provides a single +shared library: + +.. code-block:: cmake + + project(UpstreamLib) + + set(CMAKE_INCLUDE_CURRENT_DIR ON) + + set(Upstream_VERSION 3.4.1) + + include(GenerateExportHeader) + + add_library(ClimbingStats SHARED climbingstats.cpp) + generate_export_header(ClimbingStats) + + install(TARGETS ClimbingStats EXPORT ClimbingStatsTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include + ) + install( + FILES + climbingstats.h + "${CMAKE_CURRENT_BINARY_DIR}/climbingstats_export.h" + DESTINATION + include + COMPONENT + Devel + ) + + include(CMakePackageConfigHelpers) + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/ClimbingStatsConfigVersion.cmake" + VERSION ${Upstream_VERSION} + COMPATIBILITY AnyNewerVersion + ) + + set(ConfigPackageLocation lib/cmake/ClimbingStats) + install(EXPORT ClimbingStatsTargets + FILE + ClimbingStatsTargets.cmake + NAMESPACE + Upstream:: + DESTINATION + ${ConfigPackageLocation} + ) + install( + FILES + cmake/ClimbingStatsConfig.cmake + "${CMAKE_CURRENT_BINARY_DIR}/ClimbingStatsConfigVersion.cmake" + DESTINATION + ${ConfigPackageLocation} + COMPONENT + Devel + ) + +The :module:`CMakePackageConfigHelpers` module provides a macro for creating +a simple ``ConfigVersion.cmake`` file. This file sets the version of the +package. It is read by CMake when :command:`find_package` is called to +determine the compatibility with the requested version, and to set some +version-specific variables ``_VERSION``, ``_VERSION_MAJOR``, +``_VERSION_MINOR`` etc. The :command:`install(EXPORT)` command is +used to export the targets in the ``ClimbingStatsTargets`` export-set, defined +previously by the :command:`install(TARGETS)` command. This command generates +the ``ClimbingStatsTargets.cmake`` file to contain :prop_tgt:`IMPORTED` +targets, suitable for use by downsteams and arranges to install it to +``lib/cmake/ClimbingStats``. The generated ``ClimbingStatsConfigVersion.cmake`` +and a ``cmake/ClimbingStatsConfig.cmake`` are installed to the same location, +completing the package. + +A ``NAMESPACE`` with double-colons is specified when exporting the targets +for installation. This convention of double-colons gives CMake a hint that +the name is an :prop_tgt:`IMPORTED` target when it is used by downstreams +with the :command:`target_link_libraries` command. This way, CMake can +issue a diagnostic if the package providing it has not yet been found. + +In this case, when using :command:`install(TARGETS)` the ``INCLUDES DESTINATION`` +was specified. This causes the ``IMPORTED`` targets to have their +:prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` populated with the ``include`` +directory in the :variable:`CMAKE_INSTALL_PREFIX`. When the ``IMPORTED`` +target is used by downsteam, it automatically consumes the entries from +that property. + +In this case, the ``ClimbingStatsConfig.cmake`` file could be as simple as: + +.. code-block:: cmake + + include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake") + +As this allows downstreams to use the ``IMPORTED`` targets. If any macros +should be provided by the ``ClimbingStats`` package, they should +be in a separate file which is installed to the same location as the +``ClimbingStatsConfig.cmake`` file, and included from there. + +This can also be extended to cover dependencies: + +.. code-block:: cmake + + # ... + add_library(ClimbingStats SHARED climbingstats.cpp) + generate_export_header(ClimbingStats) + + find_package(Stats 2.6.4 REQUIRED) + target_link_libraries(ClimbingStats PUBLIC Stats::Types) + +As the ``Stats::Types`` target is a ``PUBLIC`` dependency of ``ClimbingStats``, +downsteams must also find the ``Stats`` package and link to the ``Stats::Types`` +library. The ``Stats`` package should be found in the ``ClimbingStatsConfig.cmake`` +file to ensure this. The ``find_dependency`` macro from the +:module:`CMakeFindDependencyMacro` helps with this by propagating +whether the package is ``REQUIRED``, or ``QUIET`` etc. All ``REQUIRED`` +dependencies of a package should be found in the ``Config.cmake`` file: + +.. code-block:: cmake + + include(CMakePackageConfigHelpers) + find_dependency(Stats 2.6.4) + + include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake") + +The ``find_dependency`` macro also sets ``ClimbingStats_FOUND`` to ``False`` if +the dependency is not found, along with a diagnostic that the ``ClimbingStats`` +package can not be used without the ``Stats`` package. + +If ``COMPONENTS`` are specified when the downstream uses :command:`find_package`, +they are listed in the ``_FIND_COMPONENTS`` variable. If a particular +component is non-optional, then the ``_FIND_REQUIRED_`` will +be true. This can be tested with logic in the package configuration file: + +.. code-block:: cmake + + include(CMakePackageConfigHelpers) + find_dependency(Stats 2.6.4) + + include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake") + + set(_supported_components Plot Table) + + foreach(_comp ${ClimbingStats_FIND_COMPONENTS}) + if (NOT ";${_supported_components};" MATCHES _comp) + set(ClimbingStats_FOUND False) + set(ClimbingStats_NOTFOUND_MESSAGE "Specified unsupported component: ${_comp}") + endif() + include("${CMAKE_CURRENT_LIST_DIR}ClimbingStats${_comp}Targets.cmake") + endforeach() + +Here, the ``ClimbingStats_NOTFOUND_MESSAGE`` is set to a diagnosis that the package +could not be found because an invalid component was specified. This message +variable can be set for any case where the ``_FOUND`` variable is set to ``False``, +and will be displayed to the user.