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
This commit is contained in:
Craig Scott
2022-04-28 23:00:19 +10:00
parent 1d82670bd4
commit 29e31e2825
51 changed files with 1010 additions and 60 deletions

View File

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

View File

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

View File

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

View File

@@ -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)>`).

View File

@@ -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()

View File

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

View File

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

View File

@@ -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);
@@ -121,6 +126,8 @@ void cmFindPackageCommand::AppendSearchPathGroups()
PathLabel::SystemRegistry);
// 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(
@@ -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];

View File

@@ -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();

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.13...3.23)
project(AddedProject LANGUAGES NONE)
message(STATUS "Confirmation project has been added")

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
file(WRITE "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/dummy_file.txt"
"This file should be deleted the next time CMake runs"
)

View File

@@ -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()

View File

@@ -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()

View File

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

View File

@@ -0,0 +1 @@
message(FATAL_ERROR "Unexpectedly added directory via FetchContent_MakeAvailable()")

View File

@@ -0,0 +1 @@
No content details recorded for t1

View File

@@ -0,0 +1,3 @@
include(FetchContent)
FetchContent_Populate(t1)

View File

@@ -0,0 +1,2 @@
set(AddedProject_FOUND TRUE)
message(STATUS "Loaded AddedProject from package config")

View File

@@ -0,0 +1,2 @@
set(FirstProject_FOUND TRUE)
message(STATUS "Loaded FirstProject from package config")

View File

@@ -0,0 +1 @@
message(FATAL_ERROR "Unexpectedly found SecondProject via find_package()")

View File

@@ -0,0 +1 @@
message(FATAL_ERROR "First project used Find module")

View File

@@ -0,0 +1 @@
message(FATAL_ERROR "Second project used Find module")

View File

@@ -0,0 +1,3 @@
Confirmation project has been added
(-- )?Lowercase extra file was read
(-- )?Uppercase extra file was read

View File

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

View File

@@ -0,0 +1,3 @@
Loaded AddedProject from package config
.*Loaded AddedProject from package config
.*Loaded AddedProject from package config

View File

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

View File

@@ -0,0 +1,4 @@
.*-- Number of arguments: 6
.*-- Argument 3: 'before'
.*-- Argument 4: ''
.*-- Argument 5: 'after'

View File

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

View File

@@ -0,0 +1,4 @@
(-- )?ConfigForm1 override successful
(-- )?ConfigForm2 override successful
(-- )?ConfigForm1_VERSION = 1.8
(-- )?ConfigForm2_VERSION = 1.9.7

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
Loaded FirstProject from package config
(-- )?Confirmation project has been added

View File

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

View File

@@ -0,0 +1,2 @@
CMake Error at .*/FetchContent.cmake:[0-9]+ \(message\):
Unsupported value for FETCHCONTENT_TRY_FIND_PACKAGE_MODE: BOGUS

View File

@@ -0,0 +1,8 @@
include(FetchContent)
set(FETCHCONTENT_TRY_FIND_PACKAGE_MODE BOGUS)
FetchContent_Declare(
AddedProject
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
)

View File

@@ -0,0 +1 @@
Confirmation project has been added

View File

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

View File

@@ -0,0 +1,2 @@
Loaded FirstProject from package config
(-- )?Confirmation project has been added

View File

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

View File

@@ -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
@@ -68,6 +73,10 @@
find_package considered the following locations for Resolved's Config
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

View File

@@ -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
@@ -68,6 +73,10 @@
find_package considered the following locations for Resolved's Config
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

View File

@@ -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\):