summaryrefslogtreecommitdiff
path: root/Modules/FetchContent.cmake
diff options
context:
space:
mode:
authorCraig Scott <craig.scott@crascit.com>2022-04-28 23:00:19 +1000
committerCraig Scott <craig.scott@crascit.com>2022-05-03 16:48:11 +1000
commit29e31e2825a2cd5099b8abe66f4816919cec934a (patch)
treea1590254fba9ed2a4e5e80b6725fc3f9795ac269 /Modules/FetchContent.cmake
parent1d82670bd4daff26d0d0169820b289bc401f4943 (diff)
downloadcmake-29e31e2825a2cd5099b8abe66f4816919cec934a.tar.gz
Packages: Integrate FetchContent and find_package()
Allow FetchContent_MakeAvailable() to try a call to find_package() first, or redirect a find_package() call to FetchContent_MakeAvailable(). The user can set variables to control which of these are allowed or tried by default. Fixes: #21687
Diffstat (limited to 'Modules/FetchContent.cmake')
-rw-r--r--Modules/FetchContent.cmake519
1 files changed, 475 insertions, 44 deletions
diff --git a/Modules/FetchContent.cmake b/Modules/FetchContent.cmake
index 7e14756010..1b81778cc2 100644
--- a/Modules/FetchContent.cmake
+++ b/Modules/FetchContent.cmake
@@ -102,7 +102,12 @@ Commands
.. code-block:: cmake
- FetchContent_Declare(<name> <contentOptions>...)
+ FetchContent_Declare(
+ <name>
+ <contentOptions>...
+ [OVERRIDE_FIND_PACKAGE |
+ FIND_PACKAGE_ARGS args...]
+ )
The ``FetchContent_Declare()`` function records the options that describe
how to populate the specified content. If such details have already
@@ -169,6 +174,36 @@ Commands
they do for :command:`ExternalProject_Add`. Previously, these variables
were ignored by the ``FetchContent`` module.
+ .. versionadded:: 3.24
+
+ ``FIND_PACKAGE_ARGS``
+ This option is for scenarios where the
+ :command:`FetchContent_MakeAvailable` command may first try a call to
+ :command:`find_package` to satisfy the dependency for ``<name>``.
+ By default, such a call would be simply ``find_package(<name>)``, but
+ ``FIND_PACKAGE_ARGS`` can be used to provide additional arguments to be
+ appended after the ``<name>``. ``FIND_PACKAGE_ARGS`` can also be given
+ with nothing after it, which indicates that :command:`find_package` can
+ still be called if :variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` is
+ set to ``OPT_IN`` or is not set.
+
+ Everything after the ``FIND_PACKAGE_ARGS`` keyword is appended to the
+ :command:`find_package` call, so all other ``<contentOptions>`` must
+ come before the ``FIND_PACKAGE_ARGS`` keyword.
+ ``OVERRIDE_FIND_PACKAGE`` cannot be used when ``FIND_PACKAGE_ARGS`` is
+ given.
+
+ ``OVERRIDE_FIND_PACKAGE``
+ When a ``FetchContent_Declare(<name> ...)`` call includes this option,
+ subsequent calls to ``find_package(<name> ...)`` will ensure that
+ ``FetchContent_MakeAvailable(<name>)`` has been called, then use the
+ config package files in the :variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR`
+ directory (which are usually created by ``FetchContent_MakeAvailable()``).
+ This effectively makes :command:`FetchContent_MakeAvailable` override
+ :command:`find_package` for the named dependency, allowing the former to
+ satisfy the package requirements of the latter. ``FIND_PACKAGE_ARGS``
+ cannot be used when ``OVERRIDE_FIND_PACKAGE`` is given.
+
.. command:: FetchContent_MakeAvailable
.. versionadded:: 3.14
@@ -177,9 +212,22 @@ Commands
FetchContent_MakeAvailable(<name1> [<name2>...])
- This command ensures that each of the named dependencies are populated and
- potentially added to the build by the time it returns. It iterates over
- the list, and for each dependency, the following logic is applied:
+ This command ensures that each of the named dependencies are made available
+ to the project by the time it returns. There must have been a call to
+ :command:`FetchContent_Declare` for each dependency, and the first such call
+ will control how that dependency will be made available, as described below.
+
+ .. versionadded:: 3.24
+ If permitted, :command:`find_package(<name> [<args>...]) <find_package>`
+ will be called, where ``<args>...`` may be provided by the
+ ``FIND_PACKAGE_ARGS`` option in :command:`FetchContent_Declare`.
+ The value of the :variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` variable
+ at the time :command:`FetchContent_Declare` was called determines whether
+ ``FetchContent_MakeAvailable()`` can call :command:`find_package`.
+
+ If :command:`find_package` was unsuccessful or was not allowed to be called,
+ ``FetchContent_MakeAvailable()`` then uses the following logic to make the
+ dependency available:
* If the dependency has already been populated earlier in this run, set
the ``<lowercaseName>_POPULATED``, ``<lowercaseName>_SOURCE_DIR`` and
@@ -194,6 +242,37 @@ Commands
the declared details and use content provided at the specified location
instead.
+ * .. versionadded:: 3.24
+
+ Ensure the :variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR` directory
+ contains a ``<lowercaseName>-config.cmake`` and a
+ ``<lowercaseName>-config-version.cmake`` file (or equivalently
+ ``<name>Config.cmake`` and ``<name>ConfigVersion.cmake``).
+ The directory that the :variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR`
+ variable points to is cleared at the start of every CMake run.
+ If no config file exists when :command:`FetchContent_Populate` returns,
+ a minimal one will be written which :command:`includes <include>` any
+ ``<lowercaseName>-extra.cmake`` or ``<name>Extra.cmake`` file with the
+ ``OPTIONAL`` flag (so the files can be missing and won't generate a
+ warning). Similarly, if no config version file exists, a very simple
+ one will be written which sets ``PACKAGE_VERSION_COMPATIBLE`` to true.
+ CMake cannot automatically determine an arbitrary dependency's version,
+ so it cannot set ``PACKAGE_VERSION`` or ``PACKAGE_VERSION_EXACT``.
+ When a dependency is pulled in via :command:`add_subdirectory` in the
+ next step, it may choose to overwrite the generated config version file
+ in :variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR` with one that also sets
+ ``PACKAGE_VERSION``, and if appropriate, ``PACKAGE_VERSION_EXACT``.
+ The dependency may also write a ``<lowercaseName>-extra.cmake`` or
+ ``<name>Extra.cmake`` file to perform custom processing or define any
+ variables that their normal (installed) package config file would
+ otherwise usually define (many projects don't do any custom processing
+ or set any variables and therefore have no need to do this).
+ If required, the main project can write these files instead if the
+ dependency project doesn't do so. This allows the main project to
+ add missing details from older dependencies that haven't or can't be
+ updated to support this functionality.
+ See `Integrating With find_package()`_ for examples.
+
* If the top directory of the populated content contains a ``CMakeLists.txt``
file, call :command:`add_subdirectory` to add it to the main build.
It is not an error for there to be no ``CMakeLists.txt`` file, which
@@ -437,8 +516,10 @@ Variables
A number of cache variables can influence the behavior where details from a
:command:`FetchContent_Declare` call are used to populate content.
-The variables are all intended for the developer to customize behavior and
-should not normally be set by the project.
+
+.. note::
+ All of these variables are intended for the developer to customize behavior.
+ They should not normally be set by the project.
.. variable:: FETCHCONTENT_BASE_DIR
@@ -481,8 +562,53 @@ should not normally be set by the project.
This can speed up the configure stage, but not as much as
:variable:`FETCHCONTENT_FULLY_DISCONNECTED`. It is ``OFF`` by default.
-In addition to the above cache variables, the following cache variables are
-also defined for each content name:
+.. variable:: FETCHCONTENT_TRY_FIND_PACKAGE_MODE
+
+ .. versionadded:: 3.24
+
+ This variable modifies the details that :command:`FetchContent_Declare`
+ records for a given dependency. While it ultimately controls the behavior
+ of :command:`FetchContent_MakeAvailable`, it is the variable's value when
+ :command:`FetchContent_Declare` is called that gets used. It makes no
+ difference what the variable is set to when
+ :command:`FetchContent_MakeAvailable` is called. Since the variable should
+ only be set by the user and not by projects directly, it will typically have
+ the same value throughout anyway, so this distinction is not usually
+ noticeable.
+
+ ``FETCHCONTENT_TRY_FIND_PACKAGE_MODE`` ultimately controls whether
+ :command:`FetchContent_MakeAvailable` is allowed to call
+ :command:`find_package` to satisfy a dependency. The variable can be set
+ to one of the following values:
+
+ ``OPT_IN``
+ :command:`FetchContent_MakeAvailable` will only call
+ :command:`find_package` if the :command:`FetchContent_Declare` call
+ included a ``FIND_PACKAGE_ARGS`` keyword. This is also the default
+ behavior if ``FETCHCONTENT_TRY_FIND_PACKAGE_MODE`` is not set.
+
+ ``ALWAYS``
+ :command:`find_package` will be called by
+ :command:`FetchContent_MakeAvailable` regardless of whether the
+ :command:`FetchContent_Declare` call included a ``FIND_PACKAGE_ARGS``
+ keyword or not. If no ``FIND_PACKAGE_ARGS`` keyword was given, the
+ behavior will be as though ``FIND_PACKAGE_ARGS`` had been provided,
+ with no additional arguments after it.
+
+ ``NEVER``
+ :command:`FetchContent_MakeAvailable` will not call
+ :command:`find_package`. Any ``FIND_PACKAGE_ARGS`` given to the
+ :command:`FetchContent_Declare` call will be ignored.
+
+ As a special case, if the :variable:`FETCHCONTENT_SOURCE_DIR_<uppercaseName>`
+ variable has a non-empty value for a dependency, it is assumed that the
+ user is overriding all other methods of making that dependency available.
+ ``FETCHCONTENT_TRY_FIND_PACKAGE_MODE`` will have no effect on that
+ dependency and :command:`FetchContent_MakeAvailable` will not try to call
+ :command:`find_package` for it.
+
+In addition to the above, the following variables are also defined for each
+content name:
.. variable:: FETCHCONTENT_SOURCE_DIR_<uppercaseName>
@@ -511,6 +637,9 @@ also defined for each content name:
Examples
^^^^^^^^
+Typical Case
+""""""""""""
+
This first fairly straightforward example ensures that some popular testing
frameworks are available to the main build:
@@ -532,6 +661,135 @@ frameworks are available to the main build:
# Catch2 will be available to the rest of the build
FetchContent_MakeAvailable(googletest Catch2)
+.. _FetchContent-find_package-integration:
+
+Integrating With find_package()
+"""""""""""""""""""""""""""""""
+
+For the previous example, if the user wanted to try to find ``googletest``
+and ``Catch2`` via :command:`find_package` first before trying to download
+and build them from source, they could set the
+:variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` variable to ``ALWAYS``.
+This would also affect any other calls to :command:`FetchContent_Declare`
+throughout the project, which might not be acceptable. The behavior can be
+enabled for just these two dependencies instead by adding ``FIND_PACKAGE_ARGS``
+to the declared details and leaving
+:variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` unset, or set to ``OPT_IN``:
+
+.. code-block:: cmake
+
+ include(FetchContent)
+ FetchContent_Declare(
+ googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
+ FIND_PACKAGE_ARGS NAMES gtest
+ )
+ FetchContent_Declare(
+ Catch2
+ GIT_REPOSITORY https://github.com/catchorg/Catch2.git
+ GIT_TAG de6fe184a9ac1a06895cdd1c9b437f0a0bdf14ad # v2.13.4
+ FIND_PACKAGE_ARGS
+ )
+
+ # This will try calling find_package() first for both dependencies
+ FetchContent_MakeAvailable(googletest Catch2)
+
+For ``Catch2``, no additional arguments to :command:`find_package` are needed,
+so no additional arguments are provided after the ``FIND_PACKAGE_ARGS``
+keyword. For ``googletest``, its package is more commonly called ``gtest``,
+so arguments are added to support it being found by that name.
+
+If the user wanted to disable :command:`FetchContent_MakeAvailable` from
+calling :command:`find_package` for any dependency, even if it provided
+``FIND_PACKAGE_ARGS`` in its declared details, they could set
+:variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` to ``NEVER``.
+
+If the project wanted to indicate that these two dependencies should be
+downloaded and built from source and that :command:`find_package` calls
+should be redirected to use the built dependencies, the
+``OVERRIDE_FIND_PACKAGE`` option should be used when declaring the content
+details:
+
+.. code-block:: cmake
+
+ include(FetchContent)
+ FetchContent_Declare(
+ googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
+ OVERRIDE_FIND_PACKAGE
+ )
+ FetchContent_Declare(
+ Catch2
+ GIT_REPOSITORY https://github.com/catchorg/Catch2.git
+ GIT_TAG de6fe184a9ac1a06895cdd1c9b437f0a0bdf14ad # v2.13.4
+ OVERRIDE_FIND_PACKAGE
+ )
+
+ # The following will automatically forward through to FetchContent_MakeAvailable()
+ find_package(googletest)
+ find_package(Catch2)
+
+CMake provides a FindGTest module which defines some variables that older
+projects may use instead of linking to the imported targets. To support
+those cases, we can provide an extras file. In keeping with the
+"first to define, wins" philosophy of ``FetchContent``, we only write out
+that file if something else hasn't already done so.
+
+.. code-block:: cmake
+
+ FetchContent_MakeAvailable(googletest)
+
+ if(NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletest-extras.cmake AND
+ NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletestExtras.cmake)
+ file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletest-extras.cmake
+ [=[
+ if("${GTEST_LIBRARIES}" STREQUAL "" AND TARGET GTest::gtest)
+ set(GTEST_LIBRARIES GTest::gtest)
+ endif()
+ if("${GTEST_MAIN_LIBRARIES}" STREQUAL "" AND TARGET GTest::gtest_main)
+ set(GTEST_MAIN_LIBRARIES GTest::gtest_main)
+ endif()
+ if("${GTEST_BOTH_LIBRARIES}" STREQUAL "")
+ set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES})
+ endif()
+ ]=]
+ endif()
+
+Projects will also likely be using ``find_package(GTest)`` rather than
+``find_package(googletest)``, but it is possible to make use of the
+:variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR` area to pull in the latter as
+a dependency of the former. This is likely to be sufficient to satisfy
+a typical ``find_package(GTest)`` call.
+
+.. code-block:: cmake
+
+ FetchContent_MakeAvailable(googletest)
+
+ if(NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-config.cmake AND
+ NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/GTestConfig.cmake)
+ file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-config.cmake
+ [=[
+ include(CMakeFindDependencyMacro)
+ find_dependency(googletest)
+ ]=]
+ endif()
+
+ if(NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-config-version.cmake AND
+ NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/GTestConfigVersion.cmake)
+ file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-config-version.cmake
+ [=[
+ include(${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletest-config-version.cmake OPTIONAL)
+ if(NOT PACKAGE_VERSION_COMPATIBLE)
+ include(${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletestConfigVersion.cmake OPTIONAL)
+ endif()
+ ]=]
+ endif()
+
+Overriding Where To Find CMakeLists.txt
+"""""""""""""""""""""""""""""""""""""""
+
If the sub-project's ``CMakeLists.txt`` file is not at the top level of its
source tree, the ``SOURCE_SUBDIR`` option can be used to tell ``FetchContent``
where to find it. The following example shows how to use that option and
@@ -550,6 +808,9 @@ it into the main build:
set(protobuf_BUILD_TESTS OFF)
FetchContent_MakeAvailable(protobuf)
+Complex Dependency Hierarchies
+""""""""""""""""""""""""""""""
+
In more complex project hierarchies, the dependency relationships can be more
complicated. Consider a hierarchy where ``projA`` is the top level project and
it depends directly on projects ``projB`` and ``projC``. Both ``projB`` and
@@ -647,6 +908,8 @@ A few key points should be noted in the above:
child projects. This saves repeating the same thing at each level of the
project hierarchy unnecessarily.
+Populating Content Without Adding It To The Build
+"""""""""""""""""""""""""""""""""""""""""""""""""
Projects don't always need to add the populated content to the build.
Sometimes the project just wants to make the downloaded content available at
@@ -682,7 +945,10 @@ named toolchain file relative to the build directory. Because the tarball has
already been downloaded and unpacked by then, the toolchain file will be in
place, even the very first time that ``cmake`` is run in the build directory.
-Lastly, the following example demonstrates how one might download and unpack a
+Populating Content In CMake Script Mode
+"""""""""""""""""""""""""""""""""""""""
+
+This last example demonstrates how one might download and unpack a
firmware tarball using CMake's :manual:`script mode <cmake(1)>`. The call to
:command:`FetchContent_Populate` specifies all the content details and the
unpacked firmware will be placed in a ``firmware`` directory below the
@@ -722,19 +988,79 @@ current working directory.
function(__FetchContent_declareDetails contentName)
string(TOLOWER ${contentName} contentNameLower)
- set(propertyName "_FetchContent_${contentNameLower}_savedDetails")
- get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED)
- if(NOT alreadyDefined)
- define_property(GLOBAL PROPERTY ${propertyName}
- BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
- FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
+ set(savedDetailsPropertyName "_FetchContent_${contentNameLower}_savedDetails")
+ get_property(alreadyDefined GLOBAL PROPERTY ${savedDetailsPropertyName} DEFINED)
+ if(alreadyDefined)
+ return()
+ endif()
+
+ if("${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}" STREQUAL "ALWAYS")
+ set(__tryFindPackage TRUE)
+ set(__tryFindPackageAllowed TRUE)
+ elseif("${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}" STREQUAL "NEVER")
+ set(__tryFindPackage FALSE)
+ set(__tryFindPackageAllowed FALSE)
+ elseif("${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}" STREQUAL "OPT_IN" OR
+ NOT DEFINED FETCHCONTENT_TRY_FIND_PACKAGE_MODE)
+ set(__tryFindPackage FALSE)
+ set(__tryFindPackageAllowed TRUE)
+ else()
+ message(FATAL_ERROR
+ "Unsupported value for FETCHCONTENT_TRY_FIND_PACKAGE_MODE: "
+ "${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}"
)
- set(__cmdArgs)
- foreach(__item IN LISTS ARGN)
- string(APPEND __cmdArgs " [==[${__item}]==]")
- endforeach()
+ endif()
+
+ set(__cmdArgs)
+ set(__findPackageArgs)
+ foreach(__item IN LISTS ARGN)
+ if(DEFINED __findPackageArgs)
+ # All remaining args are for find_package()
+ string(APPEND __findPackageArgs " [==[${__item}]==]")
+ continue()
+ endif()
+
+ # Still processing non-find_package() args
+ if(__item STREQUAL "FIND_PACKAGE_ARGS")
+ if(__tryFindPackageAllowed)
+ set(__tryFindPackage TRUE)
+ endif()
+ # All arguments after this keyword are for find_package(). Define the
+ # variable but with an empty value initially. This allows us to check
+ # at the start of the loop whether to store remaining items in this
+ # variable or not. Note that there could be no more args, which is still
+ # a valid case because we automatically provide ${contentName} as the
+ # package name and there may not need to be any further arguments.
+ set(__findPackageArgs "")
+ continue() # Don't store this item
+ elseif(__item STREQUAL "OVERRIDE_FIND_PACKAGE")
+ set(__tryFindPackageAllowed FALSE)
+ # Define a separate dedicated property for find_package() to check
+ # in its implementation. This will be a placeholder until FetchContent
+ # actually does the population. After that, we will have created a
+ # stand-in config file that find_package() will pick up instead.
+ set(propertyName "_FetchContent_${contentNameLower}_override_find_package")
+ define_property(GLOBAL PROPERTY ${propertyName})
+ set_property(GLOBAL PROPERTY ${propertyName} TRUE)
+ endif()
+
+ string(APPEND __cmdArgs " [==[${__item}]==]")
+ endforeach()
+
+ define_property(GLOBAL PROPERTY ${savedDetailsPropertyName})
+ cmake_language(EVAL CODE
+ "set_property(GLOBAL PROPERTY ${savedDetailsPropertyName} ${__cmdArgs})"
+ )
+
+ if(__tryFindPackage AND __tryFindPackageAllowed)
+ set(propertyName "_FetchContent_${contentNameLower}_find_package_args")
+ define_property(GLOBAL PROPERTY ${propertyName})
+ if(NOT QUIET IN_LIST __findPackageArgs)
+ list(INSERT __findPackageArgs 0 QUIET)
+ endif()
cmake_language(EVAL CODE
- "set_property(GLOBAL PROPERTY ${propertyName} ${__cmdArgs})")
+ "set_property(GLOBAL PROPERTY ${propertyName} ${__findPackageArgs})"
+ )
endif()
endfunction()
@@ -763,6 +1089,15 @@ endfunction()
# SOURCE_DIR and BUILD_DIR.
function(FetchContent_Declare contentName)
+ # Always check this even if we won't save these details.
+ # This helps projects catch errors earlier.
+ if("OVERRIDE_FIND_PACKAGE" IN_LIST ARGN AND "FIND_PACKAGE_ARGS" IN_LIST ARGN)
+ message(FATAL_ERROR
+ "Cannot specify both OVERRIDE_FIND_PACKAGE and FIND_PACKAGE_ARGS "
+ "when declaring details for ${contentName}"
+ )
+ endif()
+
set(options "")
set(oneValueArgs SVN_REPOSITORY)
set(multiValueArgs "")
@@ -810,35 +1145,35 @@ endfunction()
# The setter also records the source and binary dirs used.
#=======================================================================
-# Internal use, projects must not call this directly. It is
-# intended for use by the FetchContent_Populate() function to
+# Internal use, projects must not call this directly. It is intended
+# for use by things like the FetchContent_Populate() function to
# record when FetchContent_Populate() is called for a particular
# content name.
-function(__FetchContent_setPopulated contentName sourceDir binaryDir)
+function(__FetchContent_setPopulated contentName)
+
+ cmake_parse_arguments(PARSE_ARGV 1 arg
+ ""
+ "SOURCE_DIR;BINARY_DIR"
+ ""
+ )
+ if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "")
+ message(FATAL_ERROR "Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}")
+ endif()
string(TOLOWER ${contentName} contentNameLower)
set(prefix "_FetchContent_${contentNameLower}")
set(propertyName "${prefix}_sourceDir")
- define_property(GLOBAL PROPERTY ${propertyName}
- BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
- FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
- )
- set_property(GLOBAL PROPERTY ${propertyName} ${sourceDir})
+ define_property(GLOBAL PROPERTY ${propertyName})
+ set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}")
set(propertyName "${prefix}_binaryDir")
- define_property(GLOBAL PROPERTY ${propertyName}
- BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
- FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
- )
- set_property(GLOBAL PROPERTY ${propertyName} ${binaryDir})
+ define_property(GLOBAL PROPERTY ${propertyName})
+ set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}")
set(propertyName "${prefix}_populated")
- define_property(GLOBAL PROPERTY ${propertyName}
- BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
- FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
- )
- set_property(GLOBAL PROPERTY ${propertyName} True)
+ define_property(GLOBAL PROPERTY ${propertyName})
+ set_property(GLOBAL PROPERTY ${propertyName} TRUE)
endfunction()
@@ -1134,7 +1469,15 @@ function(FetchContent_Populate contentName)
# populated this content before in case the caller forgot to check.
FetchContent_GetProperties(${contentName})
if(${contentNameLower}_POPULATED)
- message(FATAL_ERROR "Content ${contentName} already populated in ${${contentNameLower}_SOURCE_DIR}")
+ if("${${contentNameLower}_SOURCE_DIR}" STREQUAL "")
+ message(FATAL_ERROR
+ "Content ${contentName} already populated by find_package()"
+ )
+ else()
+ message(FATAL_ERROR
+ "Content ${contentName} already populated in ${${contentNameLower}_SOURCE_DIR}"
+ )
+ endif()
endif()
__FetchContent_getSavedDetails(${contentName} contentDetails)
@@ -1212,7 +1555,9 @@ function(FetchContent_Populate contentName)
set(__detailsQuoted)
foreach(__item IN LISTS contentDetails)
- string(APPEND __detailsQuoted " [==[${__item}]==]")
+ if(NOT __item STREQUAL "OVERRIDE_FIND_PACKAGE")
+ string(APPEND __detailsQuoted " [==[${__item}]==]")
+ endif()
endforeach()
cmake_language(EVAL CODE "
__FetchContent_directPopulate(
@@ -1232,8 +1577,8 @@ function(FetchContent_Populate contentName)
__FetchContent_setPopulated(
${contentName}
- ${${contentNameLower}_SOURCE_DIR}
- ${${contentNameLower}_BINARY_DIR}
+ SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}"
+ BINARY_DIR "${${contentNameLower}_BINARY_DIR}"
)
# Pass variables back to the caller. The variables passed back here
@@ -1245,6 +1590,53 @@ function(FetchContent_Populate contentName)
endfunction()
+function(__FetchContent_setupFindPackageRedirection contentName)
+
+ __FetchContent_getSavedDetails(${contentName} contentDetails)
+
+ string(TOLOWER ${contentName} contentNameLower)
+ get_property(wantFindPackage GLOBAL PROPERTY
+ _FetchContent_${contentNameLower}_find_package_args
+ DEFINED
+ )
+
+ if(NOT wantFindPackage AND NOT OVERRIDE_FIND_PACKAGE IN_LIST contentDetails)
+ # No find_package() redirection allowed
+ return()
+ endif()
+
+ # We write out dep-config.cmake and dep-config-version.cmake file name
+ # forms here because they are forced to lowercase. FetchContent
+ # dependency names are case-insensitive, but find_package() config files
+ # are only case-insensitive for the -config and -config-version forms,
+ # not the Config and ConfigVersion forms.
+ set(inFileDir ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/FetchContent)
+ set(configFilePrefix1 "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${contentName}Config")
+ set(configFilePrefix2 "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${contentNameLower}-config")
+ if(NOT EXISTS "${configFilePrefix1}.cmake" AND
+ NOT EXISTS "${configFilePrefix2}.cmake")
+ configure_file(${inFileDir}/package-config.cmake.in
+ "${configFilePrefix2}.cmake" @ONLY
+ )
+ endif()
+ if(NOT EXISTS "${configFilePrefix1}Version.cmake" AND
+ NOT EXISTS "${configFilePrefix2}-version.cmake")
+ configure_file(${inFileDir}/package-config-version.cmake.in
+ "${configFilePrefix2}-version.cmake" @ONLY
+ )
+ endif()
+
+ # Now that we've created the redirected package config files, prevent
+ # find_package() from delegating to FetchContent and let it find these
+ # config files through its normal processing.
+ set(propertyName "${prefix}_override_find_package")
+ set(GLOBAL PROPERTY ${propertyName} FALSE)
+ set(${contentName}_DIR "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}"
+ CACHE INTERNAL "Redirected by FetchContent"
+ )
+
+endfunction()
+
# Arguments are assumed to be the names of dependencies that have been
# declared previously and should be populated. It is not an error if
# any of them have already been populated (they will just be skipped in
@@ -1255,9 +1647,48 @@ macro(FetchContent_MakeAvailable)
foreach(__cmake_contentName IN ITEMS ${ARGV})
string(TOLOWER ${__cmake_contentName} __cmake_contentNameLower)
+
+ # If user specified FETCHCONTENT_SOURCE_DIR_... for this dependency, that
+ # overrides everything else and we shouldn't try to use find_package().
+ string(TOUPPER ${__cmake_contentName} __cmake_contentNameUpper)
+ if("${FETCHCONTENT_SOURCE_DIR_${__cmake_contentNameUpper}}" STREQUAL "")
+ # Check if we've been asked to try find_package() first, even if we
+ # have already populated this dependency. If we previously tried to
+ # use find_package() for this and it succeeded, those things might
+ # no longer be in scope, so we have to do it again.
+ set(__cmake_fpArgsPropName "_FetchContent_${__cmake_contentNameLower}_find_package_args")
+ get_property(__cmake_haveFpArgs GLOBAL PROPERTY ${__cmake_fpArgsPropName} DEFINED)
+ if(__cmake_haveFpArgs)
+ message(VERBOSE "Trying find_package(${__cmake_contentName} ...) before FetchContent")
+ get_property(__cmake_fpArgs GLOBAL PROPERTY ${__cmake_fpArgsPropName})
+
+ # This call could lead to FetchContent_MakeAvailable() being called for
+ # a nested dependency and it may occur in the current variable scope.
+ # We have to save/restore the variables we need to preserve.
+ list(APPEND __cmake_fcCurrentNameStack
+ ${__cmake_contentName}
+ ${__cmake_contentNameLower}
+ )
+ find_package(${__cmake_contentName} ${__cmake_fpArgs})
+ list(POP_BACK __cmake_fcCurrentNameStack
+ __cmake_contentNameLower
+ __cmake_contentName
+ )
+
+ if(${__cmake_contentName}_FOUND)
+ set(${__cmake_contentNameLower}_SOURCE_DIR "")
+ set(${__cmake_contentNameLower}_BINARY_DIR "")
+ set(${__cmake_contentNameLower}_POPULATED TRUE)
+ __FetchContent_setPopulated(${__cmake_contentName})
+ continue()
+ endif()
+ endif()
+ endif()
+
FetchContent_GetProperties(${__cmake_contentName})
if(NOT ${__cmake_contentNameLower}_POPULATED)
FetchContent_Populate(${__cmake_contentName})
+ __FetchContent_setupFindPackageRedirection(${__cmake_contentName})
# Only try to call add_subdirectory() if the populated content
# can be treated that way. Protecting the call with the check
@@ -1283,13 +1714,13 @@ macro(FetchContent_MakeAvailable)
endif()
unset(__cmake_srcdir)
+ unset(__cmake_contentDetails)
+ unset(__cmake_arg_SOURCE_SUBDIR)
endif()
endforeach()
# clear local variables to prevent leaking into the caller's scope
unset(__cmake_contentName)
unset(__cmake_contentNameLower)
- unset(__cmake_contentDetails)
- unset(__cmake_arg_SOURCE_SUBDIR)
endmacro()