summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Help/command/find_package.rst40
-rw-r--r--Help/manual/cmake-variables.7.rst1
-rw-r--r--Help/release/dev/FetchContent_find_package_integration.rst17
-rw-r--r--Help/variable/CMAKE_FIND_PACKAGE_REDIRECTS_DIR.rst27
-rw-r--r--Modules/FetchContent.cmake519
-rw-r--r--Modules/FetchContent/package-config-version.cmake.in5
-rw-r--r--Modules/FetchContent/package-config.cmake.in11
-rw-r--r--Source/cmFindPackageCommand.cxx98
-rw-r--r--Source/cmFindPackageCommand.h3
-rw-r--r--Source/cmake.cxx15
-rw-r--r--Tests/RunCMake/CMakeLists.txt1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/AddedProject/CMakeLists.txt4
-rw-r--r--Tests/RunCMake/FetchContent_find_package/BadArgs_find_package-result.txt1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/BadArgs_find_package-stderr.txt3
-rw-r--r--Tests/RunCMake/FetchContent_find_package/BadArgs_find_package.cmake9
-rw-r--r--Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied-Setup.cmake3
-rw-r--r--Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied.cmake9
-rw-r--r--Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-Exists.cmake18
-rw-r--r--Tests/RunCMake/FetchContent_find_package/CMakeLists.txt7
-rw-r--r--Tests/RunCMake/FetchContent_find_package/FatalIfAdded/CMakeLists.txt1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/MissingDetails-result.txt1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/MissingDetails-stderr.txt1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/MissingDetails.cmake3
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PackageConfigs/AddedProjectConfig.cmake2
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PackageConfigs/FirstProjectConfig.cmake2
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PackageConfigs/SecondProjectConfig.cmake1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PackageFindModules/FindFirstProject.cmake1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PackageFindModules/FindSecondProject.cmake1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PreferFetchContent-stdout.txt3
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PreferFetchContent.cmake20
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Prefer_find_package-stdout.txt3
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Prefer_find_package.cmake15
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PreserveEmptyArgs-stdout.txt4
-rw-r--r--Tests/RunCMake/FetchContent_find_package/PreserveEmptyArgs.cmake13
-rw-r--r--Tests/RunCMake/FetchContent_find_package/ProjectProvidesPackageConfigFiles-stdout.txt4
-rw-r--r--Tests/RunCMake/FetchContent_find_package/ProjectProvidesPackageConfigFiles.cmake40
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Redirect_find_package_MODULE-stdout.txt9
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Redirect_find_package_MODULE.cmake39
-rw-r--r--Tests/RunCMake/FetchContent_find_package/RunCMakeTest.cmake22
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-ALWAYS-stdout.txt2
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-ALWAYS.cmake18
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS-result.txt1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS-stderr.txt2
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS.cmake8
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-NEVER-stdout.txt1
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-NEVER.cmake12
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-OPT_IN-stdout.txt2
-rw-r--r--Tests/RunCMake/FetchContent_find_package/Try_find_package-OPT_IN.cmake20
-rw-r--r--Tests/RunCMake/find_package/FromPATHEnv-stderr.txt9
-rw-r--r--Tests/RunCMake/find_package/FromPATHEnvDebugPkg-stderr.txt9
-rw-r--r--Tests/RunCMake/find_package/ModuleModeDebugPkg-stderr.txt10
51 files changed, 1010 insertions, 60 deletions
diff --git a/Help/command/find_package.rst b/Help/command/find_package.rst
index 86e26e9dc8..7e3f816b21 100644
--- a/Help/command/find_package.rst
+++ b/Help/command/find_package.rst
@@ -11,7 +11,7 @@ and load its package-specific details.
Search Modes
^^^^^^^^^^^^
-The command has two very distinct ways of conducting the search:
+The command has a few modes by which it searches for packages:
**Module mode**
In this mode, CMake searches for a file called ``Find<PackageName>.cmake``,
@@ -54,7 +54,17 @@ The command has two very distinct ways of conducting the search:
Config mode is supported by both the :ref:`basic <Basic Signature>` and
:ref:`full <Full Signature>` command signatures.
-The command arguments determine which of the above modes is used. When the
+**FetchContent redirection mode**
+ .. versionadded:: 3.24
+ A call to ``find_package()`` can be redirected internally to a package
+ provided by the :module:`FetchContent` module. To the caller, the behavior
+ will appear similar to Config mode, except that the search logic is
+ by-passed and the component information is not used. See
+ :command:`FetchContent_Declare` and :command:`FetchContent_MakeAvailable`
+ for further details.
+
+When not redirected to a package provided by :module:`FetchContent`, the
+command arguments determine whether Module or Config mode is used. When the
`basic signature`_ is used, the command searches in Module mode first.
If the package is not found, the search falls back to Config mode.
A user may set the :variable:`CMAKE_FIND_PACKAGE_PREFER_CONFIG` variable
@@ -64,7 +74,7 @@ forced to use only Module mode with a ``MODULE`` keyword. If the
`full signature`_ is used, the command only searches in Config mode.
Where possible, user code should generally look for packages using the
-`basic signature`_, since that allows the package to be found with either mode.
+`basic signature`_, since that allows the package to be found with any mode.
Project maintainers wishing to provide a config package should understand
the bigger picture, as explained in :ref:`Full Signature` and all subsequent
sections on this page.
@@ -189,9 +199,12 @@ proceeds at once with Config mode search.
Config mode search attempts to locate a configuration file provided by the
package to be found. A cache entry called ``<PackageName>_DIR`` is created to
-hold the directory containing the file. By default the command
-searches for a package with the name ``<PackageName>``. If the ``NAMES`` option
-is given the names following it are used instead of ``<PackageName>``.
+hold the directory containing the file. By default, the command searches for
+a package with the name ``<PackageName>``. If the ``NAMES`` option is given,
+the names following it are used instead of ``<PackageName>``. The names are
+also considered when determining whether to redirect the call to a package
+provided by :module:`FetchContent`.
+
The command searches for a file called ``<PackageName>Config.cmake`` or
``<lowercasePackageName>-config.cmake`` for each name specified.
A replacement set of possible configuration file names may be given
@@ -228,6 +241,14 @@ Config Mode Search Procedure
whether the :ref:`full <full signature>` or :ref:`basic <basic signature>`
signature was given.
+.. versionadded:: 3.24
+ All calls to ``find_package()`` (even in Module mode) first look for a config
+ package file in the :variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR` directory.
+ The :module:`FetchContent` module, or even the project itself, may write files
+ to that location to redirect ``find_package()`` calls to content already
+ provided by the project. If no config package file is found in that location,
+ the search proceeds with the logic described below.
+
CMake constructs a set of possible installation prefixes for the
package. Under each prefix several directories are searched for a
configuration file. The tables below show the directories searched.
@@ -398,7 +419,8 @@ to resolve symbolic links and store the real path to the file.
Every non-REQUIRED ``find_package`` call can be disabled or made REQUIRED:
* Setting the :variable:`CMAKE_DISABLE_FIND_PACKAGE_<PackageName>` variable
- to ``TRUE`` disables the package.
+ to ``TRUE`` disables the package. This also disables redirection to a
+ package provided by :module:`FetchContent`.
* Setting the :variable:`CMAKE_REQUIRE_FIND_PACKAGE_<PackageName>` variable
to ``TRUE`` makes the package REQUIRED.
@@ -421,8 +443,8 @@ version (see :ref:`format specification <FIND_PACKAGE_VERSION_FORMAT>`). If the
``EXACT`` option is given, only a version of the package claiming an exact match
of the requested version may be found. CMake does not establish any
convention for the meaning of version numbers. Package version
-numbers are checked by "version" files provided by the packages
-themselves. For a candidate package configuration file
+numbers are checked by "version" files provided by the packages themselves
+or by :module:`FetchContent`. For a candidate package configuration file
``<config-file>.cmake`` the corresponding version file is located next
to it and named either ``<config-file>-version.cmake`` or
``<config-file>Version.cmake``. If no such version file is available
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 70dfc35c77..98fc228308 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -59,6 +59,7 @@ Variables that Provide Information
/variable/CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES
/variable/CMAKE_FIND_DEBUG_MODE
/variable/CMAKE_FIND_PACKAGE_NAME
+ /variable/CMAKE_FIND_PACKAGE_REDIRECTS_DIR
/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION
/variable/CMAKE_FIND_PACKAGE_SORT_ORDER
/variable/CMAKE_GENERATOR
diff --git a/Help/release/dev/FetchContent_find_package_integration.rst b/Help/release/dev/FetchContent_find_package_integration.rst
new file mode 100644
index 0000000000..4ca7afc1e7
--- /dev/null
+++ b/Help/release/dev/FetchContent_find_package_integration.rst
@@ -0,0 +1,17 @@
+FetchContent_find_package_integration
+-------------------------------------
+
+* Integration has been added between the :module:`FetchContent` module and the
+ :command:`find_package` command, enabling the following new capabilities:
+
+ * :command:`FetchContent_MakeAvailable` can now try to satisfy a dependency
+ by calling :command:`find_package` first. A new
+ :variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` variable controls whether
+ this is done by default for all dependencies, is opt-in per dependency,
+ or is disabled entirely.
+
+ * :command:`find_package` can be re-routed to call
+ :command:`FetchContent_MakeAvailable` instead. A new read-only
+ :variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR` variable points to a
+ directory where config package files can be located to facilitate these
+ re-routed calls.
diff --git a/Help/variable/CMAKE_FIND_PACKAGE_REDIRECTS_DIR.rst b/Help/variable/CMAKE_FIND_PACKAGE_REDIRECTS_DIR.rst
new file mode 100644
index 0000000000..fa414e4044
--- /dev/null
+++ b/Help/variable/CMAKE_FIND_PACKAGE_REDIRECTS_DIR.rst
@@ -0,0 +1,27 @@
+CMAKE_FIND_PACKAGE_REDIRECTS_DIR
+--------------------------------
+
+.. versionadded:: 3.24
+
+This read-only variable specifies a directory that the :command:`find_package`
+command will check first before searching anywhere else for a module or config
+package file. A config package file in this directory will always be found in
+preference to any other Find module file or config package file.
+
+The primary purpose of this variable is to facilitate integration between
+:command:`find_package` and :command:`FetchContent_MakeAvailable`. The latter
+command may create files in the ``CMAKE_FIND_PACKAGE_REDIRECTS_DIR`` directory
+when it populates a dependency. This allows subsequent calls to
+:command:`find_package` for the same dependency to re-use the populated
+contents instead of trying to satisfy the dependency from somewhere external
+to the build. Projects may also want to write files into this directory in
+some situations (see :ref:`FetchContent-find_package-integration` for examples).
+
+The directory that ``CMAKE_FIND_PACKAGE_REDIRECTS_DIR`` points to will always
+be erased and recreated empty at the start of every CMake run. Any files
+written into this directory during the CMake run will be lost the next time
+CMake configures the project.
+
+``CMAKE_FIND_PACKAGE_REDIRECTS_DIR`` is only set in CMake project mode.
+It is not set when CMake is run in script mode
+(i.e. :manual:`cmake -P ... <cmake(1)>`).
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()
diff --git a/Modules/FetchContent/package-config-version.cmake.in b/Modules/FetchContent/package-config-version.cmake.in
new file mode 100644
index 0000000000..7f190944cf
--- /dev/null
+++ b/Modules/FetchContent/package-config-version.cmake.in
@@ -0,0 +1,5 @@
+# Automatically generated by CMake's FetchContent module.
+# Do not edit this file, it will be regenerated every time CMake runs.
+
+# Version not available, assuming it is compatible
+set(PACKAGE_VERSION_COMPATIBLE TRUE)
diff --git a/Modules/FetchContent/package-config.cmake.in b/Modules/FetchContent/package-config.cmake.in
new file mode 100644
index 0000000000..c3b64c907b
--- /dev/null
+++ b/Modules/FetchContent/package-config.cmake.in
@@ -0,0 +1,11 @@
+# Automatically generated by CMake's FetchContent module.
+# Do not edit this file, it will be regenerated every time CMake runs.
+
+# Projects or the dependencies themselves can provide the following files.
+# The files should define any additional commands or variables that the
+# dependency would normally provide but which won't be available globally
+# if the dependency is brought into the build via FetchContent instead.
+# For dependencies that only provide imported targets and no commands,
+# these typically won't be needed.
+include("${CMAKE_CURRENT_LIST_DIR}/@contentNameLower@-extra.cmake" OPTIONAL)
+include("${CMAKE_CURRENT_LIST_DIR}/@contentName@Extra.cmake" OPTIONAL)
diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx
index 9a89935c48..4031864983 100644
--- a/Source/cmFindPackageCommand.cxx
+++ b/Source/cmFindPackageCommand.cxx
@@ -22,6 +22,7 @@
#include "cmsys/String.h"
#include "cmAlgorithms.h"
+#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
@@ -42,6 +43,8 @@
class cmExecutionStatus;
class cmFileList;
+cmFindPackageCommand::PathLabel
+ cmFindPackageCommand::PathLabel::PackageRedirect("PACKAGE_REDIRECT");
cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::UserRegistry(
"PACKAGE_REGISTRY");
cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::Builds(
@@ -109,8 +112,10 @@ void cmFindPackageCommand::AppendSearchPathGroups()
{
std::vector<cmFindCommon::PathLabel>* labels;
- // Update the All group with new paths
+ // Update the All group with new paths. Note that package redirection must
+ // take precedence over everything else, so it has to be first in the array.
labels = &this->PathGroupLabelMap[PathGroup::All];
+ labels->insert(labels->begin(), PathLabel::PackageRedirect);
labels->insert(
std::find(labels->begin(), labels->end(), PathLabel::CMakeSystem),
PathLabel::UserRegistry);
@@ -122,6 +127,8 @@ void cmFindPackageCommand::AppendSearchPathGroups()
// Create the new path objects
this->LabeledPaths.insert(
+ std::make_pair(PathLabel::PackageRedirect, cmSearchPath(this)));
+ this->LabeledPaths.insert(
std::make_pair(PathLabel::UserRegistry, cmSearchPath(this)));
this->LabeledPaths.insert(
std::make_pair(PathLabel::Builds, cmSearchPath(this)));
@@ -552,9 +559,62 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
this->SetModuleVariables(components);
+ // See if we have been told to delegate to FetchContent or some other
+ // redirected config package first. We have to check all names that
+ // find_package() may look for, but only need to invoke the override for the
+ // first one that matches.
+ auto overrideNames = this->Names;
+ if (overrideNames.empty()) {
+ overrideNames.push_back(this->Name);
+ }
+ bool forceConfigMode = false;
+ const auto redirectsDir =
+ this->Makefile->GetSafeDefinition("CMAKE_FIND_PACKAGE_REDIRECTS_DIR");
+ for (const auto& overrideName : overrideNames) {
+ const auto nameLower = cmSystemTools::LowerCase(overrideName);
+ const auto delegatePropName =
+ cmStrCat("_FetchContent_", nameLower, "_override_find_package");
+ const cmValue delegateToFetchContentProp =
+ this->Makefile->GetState()->GetGlobalProperty(delegatePropName);
+ if (delegateToFetchContentProp.IsOn()) {
+ // When this property is set, the FetchContent module has already been
+ // included at least once, so we know the FetchContent_MakeAvailable()
+ // command will be defined. Any future find_package() calls after this
+ // one for this package will by-pass this once-only delegation.
+ // The following call will typically create a <name>-config.cmake file
+ // in the redirectsDir, which we still want to process like any other
+ // config file to ensure we follow normal find_package() processing.
+ cmListFileFunction func(
+ "FetchContent_MakeAvailable", 0, 0,
+ { cmListFileArgument(overrideName, cmListFileArgument::Unquoted, 0) });
+ if (!this->Makefile->ExecuteCommand(func, this->Status)) {
+ return false;
+ }
+ }
+
+ if (cmSystemTools::FileExists(
+ cmStrCat(redirectsDir, '/', nameLower, "-config.cmake")) ||
+ cmSystemTools::FileExists(
+ cmStrCat(redirectsDir, '/', overrideName, "Config.cmake"))) {
+ // Force the use of this redirected config package file, regardless of
+ // the type of find_package() call. Files in the redirectsDir must always
+ // take priority over everything else.
+ forceConfigMode = true;
+ this->UseConfigFiles = true;
+ this->UseFindModules = false;
+ this->Names.clear();
+ this->Names.emplace_back(overrideName); // Force finding this one
+ this->Variable = cmStrCat(this->Name, "_DIR");
+ this->SetConfigDirCacheVariable(redirectsDir);
+ break;
+ }
+ }
+
// See if there is a Find<PackageName>.cmake module.
bool loadedPackage = false;
- if (this->Makefile->IsOn("CMAKE_FIND_PACKAGE_PREFER_CONFIG")) {
+ if (forceConfigMode) {
+ loadedPackage = this->FindPackageUsingConfigMode();
+ } else if (this->Makefile->IsOn("CMAKE_FIND_PACKAGE_PREFER_CONFIG")) {
if (this->UseConfigFiles && this->FindPackageUsingConfigMode()) {
loadedPackage = true;
} else {
@@ -1164,19 +1224,24 @@ bool cmFindPackageCommand::FindConfig()
} else {
init = this->Variable + "-NOTFOUND";
}
+ // We force the value since we do not get here if it was already set.
+ this->SetConfigDirCacheVariable(init);
+
+ return found;
+}
+
+void cmFindPackageCommand::SetConfigDirCacheVariable(const std::string& value)
+{
std::string help =
cmStrCat("The directory containing a CMake configuration file for ",
this->Name, '.');
- // We force the value since we do not get here if it was already set.
- this->Makefile->AddCacheDefinition(this->Variable, init, help.c_str(),
+ this->Makefile->AddCacheDefinition(this->Variable, value, help.c_str(),
cmStateEnums::PATH, true);
if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) ==
cmPolicies::NEW &&
this->Makefile->IsNormalDefinitionSet(this->Variable)) {
- this->Makefile->AddDefinition(this->Variable, init);
+ this->Makefile->AddDefinition(this->Variable, value);
}
-
- return found;
}
bool cmFindPackageCommand::FindPrefixedConfig()
@@ -1327,6 +1392,8 @@ inline std::size_t collectPathsForDebug(std::string& buffer,
void cmFindPackageCommand::ComputePrefixes()
{
+ this->FillPrefixesPackageRedirect();
+
if (!this->NoDefaultPath) {
if (!this->NoPackageRootPath) {
this->FillPrefixesPackageRoot();
@@ -1360,6 +1427,23 @@ void cmFindPackageCommand::ComputePrefixes()
this->ComputeFinalPaths(IgnorePaths::No);
}
+void cmFindPackageCommand::FillPrefixesPackageRedirect()
+{
+ cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRedirect];
+
+ const auto redirectDir =
+ this->Makefile->GetDefinition("CMAKE_FIND_PACKAGE_REDIRECTS_DIR");
+ if (redirectDir && !redirectDir->empty()) {
+ paths.AddPath(*redirectDir);
+ }
+ if (this->DebugMode) {
+ std::string debugBuffer =
+ "The internally managed CMAKE_FIND_PACKAGE_REDIRECTS_DIR.\n";
+ collectPathsForDebug(debugBuffer, paths);
+ this->DebugBuffer = cmStrCat(this->DebugBuffer, debugBuffer);
+ }
+}
+
void cmFindPackageCommand::FillPrefixesPackageRoot()
{
cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRoot];
diff --git a/Source/cmFindPackageCommand.h b/Source/cmFindPackageCommand.h
index b9f19e4e1e..d01a886caa 100644
--- a/Source/cmFindPackageCommand.h
+++ b/Source/cmFindPackageCommand.h
@@ -76,6 +76,7 @@ private:
: cmFindCommon::PathLabel(label)
{
}
+ static PathLabel PackageRedirect;
static PathLabel UserRegistry;
static PathLabel Builds;
static PathLabel SystemRegistry;
@@ -119,8 +120,10 @@ private:
};
bool ReadListFile(const std::string& f, PolicyScopeRule psr);
void StoreVersionFound();
+ void SetConfigDirCacheVariable(const std::string& value);
void ComputePrefixes();
+ void FillPrefixesPackageRedirect();
void FillPrefixesPackageRoot();
void FillPrefixesCMakeEnvironment();
void FillPrefixesCMakeVariable();
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index 1c027adaa3..a8dc963dcb 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -2103,6 +2103,21 @@ int cmake::ActualConfigure()
cmStateEnums::INTERNAL);
}
+ // We want to create the package redirects directory as early as possible,
+ // but not before pre-configure checks have passed. This ensures we get
+ // errors about inappropriate source/binary directories first.
+ const auto redirectsDir =
+ cmStrCat(this->GetHomeOutputDirectory(), "/CMakeFiles/pkgRedirects");
+ cmSystemTools::RemoveADirectory(redirectsDir);
+ if (!cmSystemTools::MakeDirectory(redirectsDir)) {
+ cmSystemTools::Error(
+ "Unable to (re)create the private pkgRedirects directory:\n" +
+ redirectsDir);
+ return -1;
+ }
+ this->AddCacheEntry("CMAKE_FIND_PACKAGE_REDIRECTS_DIR", redirectsDir,
+ "Value Computed by CMake.", cmStateEnums::STATIC);
+
// no generator specified on the command line
if (!this->GlobalGenerator) {
cmValue genName = this->State->GetInitializedCacheValue("CMAKE_GENERATOR");
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index c2677a0214..325ec2f9b0 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -784,6 +784,7 @@ if(CMake_TEST_RunCMake_ExternalProject_DOWNLOAD_SERVER_TIMEOUT)
endif()
add_RunCMake_test(ExternalProject)
add_RunCMake_test(FetchContent)
+add_RunCMake_test(FetchContent_find_package)
set(CTestCommandLine_ARGS -DPython_EXECUTABLE=${Python_EXECUTABLE})
if(NOT CMake_TEST_EXTERNAL_CMAKE)
list(APPEND CTestCommandLine_ARGS -DTEST_AFFINITY=$<TARGET_FILE:testAffinity>)
diff --git a/Tests/RunCMake/FetchContent_find_package/AddedProject/CMakeLists.txt b/Tests/RunCMake/FetchContent_find_package/AddedProject/CMakeLists.txt
new file mode 100644
index 0000000000..8be00ed642
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/AddedProject/CMakeLists.txt
@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 3.13...3.23)
+project(AddedProject LANGUAGES NONE)
+
+message(STATUS "Confirmation project has been added")
diff --git a/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package-result.txt b/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package-stderr.txt b/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package-stderr.txt
new file mode 100644
index 0000000000..b6996b5994
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package-stderr.txt
@@ -0,0 +1,3 @@
+CMake Error at .*/FetchContent.cmake:[0-9]+ \(message\):
+ Cannot specify both OVERRIDE_FIND_PACKAGE and FIND_PACKAGE_ARGS when
+ declaring details for AddedProject
diff --git a/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package.cmake b/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package.cmake
new file mode 100644
index 0000000000..c1272b486d
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/BadArgs_find_package.cmake
@@ -0,0 +1,9 @@
+include(FetchContent)
+
+FetchContent_Declare(
+ AddedProject
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
+ # The following two args are mutually exclusive
+ OVERRIDE_FIND_PACKAGE
+ FIND_PACKAGE_ARGS
+)
diff --git a/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied-Setup.cmake b/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied-Setup.cmake
new file mode 100644
index 0000000000..ea36bf01aa
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied-Setup.cmake
@@ -0,0 +1,3 @@
+file(WRITE "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/dummy_file.txt"
+ "This file should be deleted the next time CMake runs"
+)
diff --git a/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied.cmake b/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied.cmake
new file mode 100644
index 0000000000..07c45f2b4c
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied.cmake
@@ -0,0 +1,9 @@
+file(GLOB contents LIST_DIRECTORIES true "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/*")
+
+if(NOT contents STREQUAL "")
+ list(JOIN contents "\n" fileList)
+ message(FATAL_ERROR
+ "CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not empty:\n"
+ "${fileList}"
+ )
+endif()
diff --git a/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-Exists.cmake b/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-Exists.cmake
new file mode 100644
index 0000000000..dd0133324c
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-Exists.cmake
@@ -0,0 +1,18 @@
+if(NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR)
+ message(FATAL_ERROR "CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined")
+endif()
+
+if(NOT CMAKE_FIND_PACKAGE_REDIRECTS_DIR STREQUAL "${CMAKE_BINARY_DIR}/CMakeFiles/pkgRedirects")
+ message(FATAL_ERROR
+ "CMAKE_FIND_PACKAGE_REDIRECTS_DIR has wrong value\n"
+ " Expected: ${CMAKE_BINARY_DIR}/CMakeFiles/pkgRedirects\n"
+ " Actual: ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}"
+ )
+endif()
+
+if(NOT EXISTS "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}")
+ message(FATAL_ERROR
+ "Directory CMAKE_FIND_PACKAGE_REDIRECTS_DIR points to does not exist:\n"
+ "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}"
+ )
+endif()
diff --git a/Tests/RunCMake/FetchContent_find_package/CMakeLists.txt b/Tests/RunCMake/FetchContent_find_package/CMakeLists.txt
new file mode 100644
index 0000000000..bd718c75ee
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/CMakeLists.txt
@@ -0,0 +1,7 @@
+cmake_minimum_required(VERSION 3.23)
+project(${RunCMake_TEST} NONE)
+
+# Tests assume no previous downloads in the output directory
+file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/_deps)
+
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/FetchContent_find_package/FatalIfAdded/CMakeLists.txt b/Tests/RunCMake/FetchContent_find_package/FatalIfAdded/CMakeLists.txt
new file mode 100644
index 0000000000..6a3b93104d
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/FatalIfAdded/CMakeLists.txt
@@ -0,0 +1 @@
+message(FATAL_ERROR "Unexpectedly added directory via FetchContent_MakeAvailable()")
diff --git a/Tests/RunCMake/FetchContent_find_package/MissingDetails-result.txt b/Tests/RunCMake/FetchContent_find_package/MissingDetails-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/MissingDetails-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/FetchContent_find_package/MissingDetails-stderr.txt b/Tests/RunCMake/FetchContent_find_package/MissingDetails-stderr.txt
new file mode 100644
index 0000000000..c4f1daf58d
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/MissingDetails-stderr.txt
@@ -0,0 +1 @@
+No content details recorded for t1
diff --git a/Tests/RunCMake/FetchContent_find_package/MissingDetails.cmake b/Tests/RunCMake/FetchContent_find_package/MissingDetails.cmake
new file mode 100644
index 0000000000..ba8d121679
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/MissingDetails.cmake
@@ -0,0 +1,3 @@
+include(FetchContent)
+
+FetchContent_Populate(t1)
diff --git a/Tests/RunCMake/FetchContent_find_package/PackageConfigs/AddedProjectConfig.cmake b/Tests/RunCMake/FetchContent_find_package/PackageConfigs/AddedProjectConfig.cmake
new file mode 100644
index 0000000000..a38159ff42
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PackageConfigs/AddedProjectConfig.cmake
@@ -0,0 +1,2 @@
+set(AddedProject_FOUND TRUE)
+message(STATUS "Loaded AddedProject from package config")
diff --git a/Tests/RunCMake/FetchContent_find_package/PackageConfigs/FirstProjectConfig.cmake b/Tests/RunCMake/FetchContent_find_package/PackageConfigs/FirstProjectConfig.cmake
new file mode 100644
index 0000000000..3a899692e9
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PackageConfigs/FirstProjectConfig.cmake
@@ -0,0 +1,2 @@
+set(FirstProject_FOUND TRUE)
+message(STATUS "Loaded FirstProject from package config")
diff --git a/Tests/RunCMake/FetchContent_find_package/PackageConfigs/SecondProjectConfig.cmake b/Tests/RunCMake/FetchContent_find_package/PackageConfigs/SecondProjectConfig.cmake
new file mode 100644
index 0000000000..a181ab8d9a
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PackageConfigs/SecondProjectConfig.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "Unexpectedly found SecondProject via find_package()")
diff --git a/Tests/RunCMake/FetchContent_find_package/PackageFindModules/FindFirstProject.cmake b/Tests/RunCMake/FetchContent_find_package/PackageFindModules/FindFirstProject.cmake
new file mode 100644
index 0000000000..bdd3369ffe
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PackageFindModules/FindFirstProject.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "First project used Find module")
diff --git a/Tests/RunCMake/FetchContent_find_package/PackageFindModules/FindSecondProject.cmake b/Tests/RunCMake/FetchContent_find_package/PackageFindModules/FindSecondProject.cmake
new file mode 100644
index 0000000000..09a4acffd7
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PackageFindModules/FindSecondProject.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "Second project used Find module")
diff --git a/Tests/RunCMake/FetchContent_find_package/PreferFetchContent-stdout.txt b/Tests/RunCMake/FetchContent_find_package/PreferFetchContent-stdout.txt
new file mode 100644
index 0000000000..5413a4bf80
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PreferFetchContent-stdout.txt
@@ -0,0 +1,3 @@
+Confirmation project has been added
+(-- )?Lowercase extra file was read
+(-- )?Uppercase extra file was read
diff --git a/Tests/RunCMake/FetchContent_find_package/PreferFetchContent.cmake b/Tests/RunCMake/FetchContent_find_package/PreferFetchContent.cmake
new file mode 100644
index 0000000000..c1030fbf43
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PreferFetchContent.cmake
@@ -0,0 +1,20 @@
+include(FetchContent)
+
+FetchContent_Declare(
+ AddedProject
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
+ OVERRIDE_FIND_PACKAGE
+)
+
+# The default generated config package files are expected to include these when present
+file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/AddedProjectExtra.cmake [[
+message(STATUS "Uppercase extra file was read")
+]]
+)
+file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/addedproject-extra.cmake [[
+message(STATUS "Lowercase extra file was read")
+]]
+)
+
+# This is expected to be re-routed to a FetchContent_MakeAvailable() call
+find_package(AddedProject REQUIRED)
diff --git a/Tests/RunCMake/FetchContent_find_package/Prefer_find_package-stdout.txt b/Tests/RunCMake/FetchContent_find_package/Prefer_find_package-stdout.txt
new file mode 100644
index 0000000000..dfb4238e6a
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Prefer_find_package-stdout.txt
@@ -0,0 +1,3 @@
+Loaded AddedProject from package config
+.*Loaded AddedProject from package config
+.*Loaded AddedProject from package config
diff --git a/Tests/RunCMake/FetchContent_find_package/Prefer_find_package.cmake b/Tests/RunCMake/FetchContent_find_package/Prefer_find_package.cmake
new file mode 100644
index 0000000000..f5454ab788
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Prefer_find_package.cmake
@@ -0,0 +1,15 @@
+include(FetchContent)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageConfigs)
+
+FetchContent_Declare(
+ AddedProject
+ # Ensure failure if we don't re-route to find_package()
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/FatalIfAdded
+ FIND_PACKAGE_ARGS REQUIRED
+)
+
+# Cycle through a few calls to exercise global property changes
+FetchContent_MakeAvailable(AddedProject)
+find_package(AddedProject REQUIRED)
+FetchContent_MakeAvailable(AddedProject) # Will re-route to find_package() again
diff --git a/Tests/RunCMake/FetchContent_find_package/PreserveEmptyArgs-stdout.txt b/Tests/RunCMake/FetchContent_find_package/PreserveEmptyArgs-stdout.txt
new file mode 100644
index 0000000000..a72d914c6e
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PreserveEmptyArgs-stdout.txt
@@ -0,0 +1,4 @@
+.*-- Number of arguments: 6
+.*-- Argument 3: 'before'
+.*-- Argument 4: ''
+.*-- Argument 5: 'after'
diff --git a/Tests/RunCMake/FetchContent_find_package/PreserveEmptyArgs.cmake b/Tests/RunCMake/FetchContent_find_package/PreserveEmptyArgs.cmake
new file mode 100644
index 0000000000..4f354482fe
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/PreserveEmptyArgs.cmake
@@ -0,0 +1,13 @@
+include(FetchContent)
+
+# Need to see the download command output
+set(FETCHCONTENT_QUIET OFF)
+
+FetchContent_Declare(
+ t1
+ DOWNLOAD_COMMAND ${CMAKE_COMMAND} -P
+ ${CMAKE_CURRENT_LIST_DIR}/countArgs.cmake
+ before "" after
+)
+
+FetchContent_Populate(t1)
diff --git a/Tests/RunCMake/FetchContent_find_package/ProjectProvidesPackageConfigFiles-stdout.txt b/Tests/RunCMake/FetchContent_find_package/ProjectProvidesPackageConfigFiles-stdout.txt
new file mode 100644
index 0000000000..76c6916c4c
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/ProjectProvidesPackageConfigFiles-stdout.txt
@@ -0,0 +1,4 @@
+(-- )?ConfigForm1 override successful
+(-- )?ConfigForm2 override successful
+(-- )?ConfigForm1_VERSION = 1.8
+(-- )?ConfigForm2_VERSION = 1.9.7
diff --git a/Tests/RunCMake/FetchContent_find_package/ProjectProvidesPackageConfigFiles.cmake b/Tests/RunCMake/FetchContent_find_package/ProjectProvidesPackageConfigFiles.cmake
new file mode 100644
index 0000000000..32e3f731a8
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/ProjectProvidesPackageConfigFiles.cmake
@@ -0,0 +1,40 @@
+include(FetchContent)
+
+FetchContent_Declare(
+ ConfigForm1
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/FatalIfAdded
+ FIND_PACKAGE_ARGS 1.8 EXACT REQUIRED
+)
+file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/ConfigForm1Config.cmake [[
+set(ConfigForm1_FOUND TRUE)
+message(STATUS "ConfigForm1 override successful")
+]]
+)
+file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/ConfigForm1ConfigVersion.cmake [[
+set(PACKAGE_VERSION 1.8)
+set(PACKAGE_VERSION_EXACT TRUE)
+set(PACKAGE_VERSION_COMPATIBLE TRUE)
+]]
+)
+
+FetchContent_Declare(
+ ConfigForm2
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/FatalIfAdded
+ FIND_PACKAGE_ARGS 1.8 REQUIRED
+)
+file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/configform2-config.cmake [[
+set(ConfigForm2_FOUND TRUE)
+message(STATUS "ConfigForm2 override successful")
+]]
+)
+file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/configform2-config-version.cmake [[
+set(PACKAGE_VERSION 1.9.7)
+set(PACKAGE_VERSION_EXACT FALSE)
+set(PACKAGE_VERSION_COMPATIBLE TRUE)
+]]
+)
+
+FetchContent_MakeAvailable(ConfigForm1 ConfigForm2)
+
+message(STATUS "ConfigForm1_VERSION = ${ConfigForm1_VERSION}")
+message(STATUS "ConfigForm2_VERSION = ${ConfigForm2_VERSION}")
diff --git a/Tests/RunCMake/FetchContent_find_package/Redirect_find_package_MODULE-stdout.txt b/Tests/RunCMake/FetchContent_find_package/Redirect_find_package_MODULE-stdout.txt
new file mode 100644
index 0000000000..fbe6e3870b
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Redirect_find_package_MODULE-stdout.txt
@@ -0,0 +1,9 @@
+(-- )?find_package\(FirstProject\):
+(-- )?Confirmation project has been added
+(-- )?FirstProject_FOUND = 1
+(-- )?FetchContent_MakeAvailable\(FirstProject\):
+(-- )?FetchContent_MakeAvailable\(SecondProject\):
+(-- )?Confirmation project has been added
+(-- )?find_package\(SecondProject\):
+(-- )?SecondProject_FOUND = 1
+(-- )?End of test
diff --git a/Tests/RunCMake/FetchContent_find_package/Redirect_find_package_MODULE.cmake b/Tests/RunCMake/FetchContent_find_package/Redirect_find_package_MODULE.cmake
new file mode 100644
index 0000000000..95bc2dcc4c
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Redirect_find_package_MODULE.cmake
@@ -0,0 +1,39 @@
+include(FetchContent)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageConfigs)
+
+FetchContent_Declare(
+ FirstProject
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
+ OVERRIDE_FIND_PACKAGE
+)
+FetchContent_Declare(
+ SecondProject
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
+ # Allow a call to find_package() that we know will fail.
+ # This enables redirection of calls to find_package(SecondProject)
+ # after FetchContent_MakeAvailable() populates.
+ FIND_PACKAGE_ARGS NAMES I_do_not_exist
+)
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageFindModules)
+
+# Re-directs to FetchContent_MakeAvailable()
+message(STATUS "find_package(FirstProject):")
+find_package(FirstProject REQUIRED MODULE)
+message(STATUS "FirstProject_FOUND = ${FirstProject_FOUND}")
+
+# Does nothing, already populated
+message(STATUS "FetchContent_MakeAvailable(FirstProject):")
+FetchContent_MakeAvailable(FirstProject)
+
+# Populates as normal
+message(STATUS "FetchContent_MakeAvailable(SecondProject):")
+FetchContent_MakeAvailable(SecondProject)
+
+# Redirects to config package file created by previous command
+message(STATUS "find_package(SecondProject):")
+find_package(SecondProject REQUIRED MODULE)
+message(STATUS "SecondProject_FOUND = ${FirstProject_FOUND}")
+
+message(STATUS "End of test")
diff --git a/Tests/RunCMake/FetchContent_find_package/RunCMakeTest.cmake b/Tests/RunCMake/FetchContent_find_package/RunCMakeTest.cmake
new file mode 100644
index 0000000000..c139f5780b
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/RunCMakeTest.cmake
@@ -0,0 +1,22 @@
+include(RunCMake)
+
+unset(RunCMake_TEST_NO_CLEAN)
+
+function(run_FetchContent_pkgRedirects)
+ set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied-build)
+ run_cmake(CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied-Setup)
+ set(RunCMake_TEST_NO_CLEAN 1)
+ run_cmake(CMAKE_FIND_PACKAGE_REDIRECTS_DIR-AlwaysEmptied)
+endfunction()
+
+run_cmake(CMAKE_FIND_PACKAGE_REDIRECTS_DIR-Exists)
+run_FetchContent_pkgRedirects()
+run_cmake(BadArgs_find_package)
+run_cmake(PreferFetchContent)
+run_cmake(Prefer_find_package)
+run_cmake(ProjectProvidesPackageConfigFiles)
+run_cmake(Try_find_package-ALWAYS)
+run_cmake(Try_find_package-NEVER)
+run_cmake(Try_find_package-OPT_IN)
+run_cmake(Try_find_package-BOGUS)
+run_cmake(Redirect_find_package_MODULE)
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-ALWAYS-stdout.txt b/Tests/RunCMake/FetchContent_find_package/Try_find_package-ALWAYS-stdout.txt
new file mode 100644
index 0000000000..d43b8e825f
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-ALWAYS-stdout.txt
@@ -0,0 +1,2 @@
+Loaded FirstProject from package config
+(-- )?Confirmation project has been added
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-ALWAYS.cmake b/Tests/RunCMake/FetchContent_find_package/Try_find_package-ALWAYS.cmake
new file mode 100644
index 0000000000..0d7e289f46
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-ALWAYS.cmake
@@ -0,0 +1,18 @@
+include(FetchContent)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageConfigs)
+set(FETCHCONTENT_TRY_FIND_PACKAGE_MODE ALWAYS)
+
+FetchContent_Declare(
+ FirstProject
+ # Ensure failure if we don't re-route to find_package()
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/FatalIfAdded
+)
+
+FetchContent_Declare(
+ SecondProject
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
+ OVERRIDE_FIND_PACKAGE # Takes precedence over ALWAYS mode
+)
+
+FetchContent_MakeAvailable(FirstProject SecondProject)
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS-result.txt b/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS-stderr.txt b/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS-stderr.txt
new file mode 100644
index 0000000000..4cc7347c3f
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error at .*/FetchContent.cmake:[0-9]+ \(message\):
+ Unsupported value for FETCHCONTENT_TRY_FIND_PACKAGE_MODE: BOGUS
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS.cmake b/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS.cmake
new file mode 100644
index 0000000000..f9c8ce70c8
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-BOGUS.cmake
@@ -0,0 +1,8 @@
+include(FetchContent)
+
+set(FETCHCONTENT_TRY_FIND_PACKAGE_MODE BOGUS)
+
+FetchContent_Declare(
+ AddedProject
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
+)
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-NEVER-stdout.txt b/Tests/RunCMake/FetchContent_find_package/Try_find_package-NEVER-stdout.txt
new file mode 100644
index 0000000000..52398e77de
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-NEVER-stdout.txt
@@ -0,0 +1 @@
+Confirmation project has been added
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-NEVER.cmake b/Tests/RunCMake/FetchContent_find_package/Try_find_package-NEVER.cmake
new file mode 100644
index 0000000000..92cb7d0dfa
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-NEVER.cmake
@@ -0,0 +1,12 @@
+include(FetchContent)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageConfigs)
+set(FETCHCONTENT_TRY_FIND_PACKAGE_MODE NEVER)
+
+FetchContent_Declare(
+ AddedProject
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
+ FIND_PACKAGE_ARGS REQUIRED
+)
+
+FetchContent_MakeAvailable(AddedProject)
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-OPT_IN-stdout.txt b/Tests/RunCMake/FetchContent_find_package/Try_find_package-OPT_IN-stdout.txt
new file mode 100644
index 0000000000..d43b8e825f
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-OPT_IN-stdout.txt
@@ -0,0 +1,2 @@
+Loaded FirstProject from package config
+(-- )?Confirmation project has been added
diff --git a/Tests/RunCMake/FetchContent_find_package/Try_find_package-OPT_IN.cmake b/Tests/RunCMake/FetchContent_find_package/Try_find_package-OPT_IN.cmake
new file mode 100644
index 0000000000..a54958315a
--- /dev/null
+++ b/Tests/RunCMake/FetchContent_find_package/Try_find_package-OPT_IN.cmake
@@ -0,0 +1,20 @@
+include(FetchContent)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageConfigs)
+set(FETCHCONTENT_TRY_FIND_PACKAGE_MODE OPT_IN)
+
+# With opt-in, should call find_package()
+FetchContent_Declare(
+ FirstProject
+ # Ensure failure if we don't re-route to find_package()
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/FatalIfAdded
+ FIND_PACKAGE_ARGS REQUIRED
+)
+
+# Without opt-in, shouldn't call find_package()
+FetchContent_Declare(
+ SecondProject
+ SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
+)
+
+FetchContent_MakeAvailable(FirstProject SecondProject)
diff --git a/Tests/RunCMake/find_package/FromPATHEnv-stderr.txt b/Tests/RunCMake/find_package/FromPATHEnv-stderr.txt
index 691d7f0610..fdf098df2f 100644
--- a/Tests/RunCMake/find_package/FromPATHEnv-stderr.txt
+++ b/Tests/RunCMake/find_package/FromPATHEnv-stderr.txt
@@ -6,6 +6,11 @@
The file was not found.
+ The internally managed CMAKE_FIND_PACKAGE_REDIRECTS_DIR.
+
+ [^
+]*/Tests/RunCMake/find_package/FromPATHEnv-build/CMakeFiles/pkgRedirects
+
<PackageName>_ROOT CMake variable \[CMAKE_FIND_USE_PACKAGE_ROOT_PATH\].
none
@@ -69,6 +74,10 @@
module:
[^
+]*/Tests/RunCMake/find_package/FromPATHEnv-build/CMakeFiles/pkgRedirects/ResolvedConfig.cmake
+ [^
+]*/Tests/RunCMake/find_package/FromPATHEnv-build/CMakeFiles/pkgRedirects/resolved-config.cmake
+ [^
]*/Tests/RunCMake/find_package/PackageRoot/ResolvedConfig.cmake
The file was found at
diff --git a/Tests/RunCMake/find_package/FromPATHEnvDebugPkg-stderr.txt b/Tests/RunCMake/find_package/FromPATHEnvDebugPkg-stderr.txt
index ef5ec335ff..5140616f28 100644
--- a/Tests/RunCMake/find_package/FromPATHEnvDebugPkg-stderr.txt
+++ b/Tests/RunCMake/find_package/FromPATHEnvDebugPkg-stderr.txt
@@ -6,6 +6,11 @@
The file was not found.
+ The internally managed CMAKE_FIND_PACKAGE_REDIRECTS_DIR.
+
+ [^
+]*/Tests/RunCMake/find_package/FromPATHEnvDebugPkg-build/CMakeFiles/pkgRedirects
+
<PackageName>_ROOT CMake variable \[CMAKE_FIND_USE_PACKAGE_ROOT_PATH\].
none
@@ -69,6 +74,10 @@
module:
[^
+]*/Tests/RunCMake/find_package/FromPATHEnvDebugPkg-build/CMakeFiles/pkgRedirects/ResolvedConfig.cmake
+ [^
+]*/Tests/RunCMake/find_package/FromPATHEnvDebugPkg-build/CMakeFiles/pkgRedirects/resolved-config.cmake
+ [^
]*/Tests/RunCMake/find_package/PackageRoot/ResolvedConfig.cmake
The file was found at
diff --git a/Tests/RunCMake/find_package/ModuleModeDebugPkg-stderr.txt b/Tests/RunCMake/find_package/ModuleModeDebugPkg-stderr.txt
index e4fd7c5a59..9757803950 100644
--- a/Tests/RunCMake/find_package/ModuleModeDebugPkg-stderr.txt
+++ b/Tests/RunCMake/find_package/ModuleModeDebugPkg-stderr.txt
@@ -97,6 +97,11 @@ Call Stack \(most recent call first\):
FindBar processed here.
+
CMake Debug Log at ModuleModeDebugPkg/FindFoo.cmake:[0-9]+ \(find_package\):
+ The internally managed CMAKE_FIND_PACKAGE_REDIRECTS_DIR.
+
+ [^
+]*/Tests/RunCMake/find_package/ModuleModeDebugPkg-build/CMakeFiles/pkgRedirects
+
Paths specified by the find_package HINTS option.
none
@@ -107,6 +112,11 @@ CMake Debug Log at ModuleModeDebugPkg/FindFoo.cmake:[0-9]+ \(find_package\):
find_package considered the following locations for Zot's Config module:
+ [^
+]*/Tests/RunCMake/find_package/ModuleModeDebugPkg-build/CMakeFiles/pkgRedirects/ZotConfig.cmake
+ [^
+]*/Tests/RunCMake/find_package/ModuleModeDebugPkg-build/CMakeFiles/pkgRedirects/zot-config.cmake
+
The file was not found.
Call Stack \(most recent call first\):