Dependency providers: Add find_package and FetchContent support

Fixes: #22619
This commit is contained in:
Craig Scott 2022-05-18 23:29:21 +10:00
parent 8a28368feb
commit 2aa83fa15b
50 changed files with 957 additions and 38 deletions

View File

@ -13,6 +13,7 @@ Synopsis
cmake_language(`CALL`_ <command> [<arg>...])
cmake_language(`EVAL`_ CODE <code>...)
cmake_language(`DEFER`_ <options>... CALL <command> [<arg>...])
cmake_language(`SET_DEPENDENCY_PROVIDER`_ <command> SUPPORTED_METHODS <methods>...)
Introduction
^^^^^^^^^^^^
@ -225,3 +226,265 @@ also prints::
Immediate Message
Deferred Message 1
Deferred Message 2
.. _SET_DEPENDENCY_PROVIDER:
.. _dependency_providers:
Dependency Providers
^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 3.24
.. code-block:: cmake
cmake_language(SET_DEPENDENCY_PROVIDER <command>
SUPPORTED_METHODS <methods>...)
When a call is made to :command:`find_package` or
:command:`FetchContent_MakeAvailable`, the call may be forwarded to a
dependency provider which then has the opportunity to fulfill the request.
If the request is for one of the ``<methods>`` specified when the provider
was set, CMake calls the provider's ``<command>`` with a set of
method-specific arguments. If the provider does not fulfill the request,
or if the provider doesn't support the request's method, or no provider
is set, the built-in :command:`find_package` or
:command:`FetchContent_MakeAvailable` implementation is used to fulfill
the request in the usual way.
One or more of the following values can be specified for the ``<methods>``
when setting the provider:
``FIND_PACKAGE``
The provider command accepts :command:`find_package` requests.
``FETCHCONTENT_MAKEAVAILABLE_SERIAL``
The provider command accepts :command:`FetchContent_MakeAvailable`
requests. It expects each dependency to be fed to the provider command
one at a time, not the whole list in one go.
Only one provider can be set at any point in time. If a provider is already
set when ``cmake_language(SET_DEPENDENCY_PROVIDER)`` is called, the new
provider replaces the previously set one. The specified ``<command>`` must
already exist when ``cmake_language(SET_DEPENDENCY_PROVIDER)`` is called.
As a special case, providing an empty string for the ``<command>`` and no
``<methods>`` will discard any previously set provider.
The dependency provider can only be set while processing one of the files
specified by the :variable:`CMAKE_PROJECT_TOP_LEVEL_INCLUDES` variable.
Thus, dependency providers can only be set as part of the first call to
:command:`project`. Calling ``cmake_language(SET_DEPENDENCY_PROVIDER)``
outside of that context will result in an error.
.. note::
The choice of dependency provider should always be under the user's control.
As a convenience, a project may choose to provide a file that users can
list in their :variable:`CMAKE_PROJECT_TOP_LEVEL_INCLUDES` variable, but
the use of such a file should always be the user's choice.
Provider commands
"""""""""""""""""
Providers define a single ``<command>`` to accept requests. The name of
the command should be specific to that provider, not something overly
generic that another provider might also use. This enables users to compose
different providers in their own custom provider. The recommended form is
``xxx_provide_dependency()``, where ``xxx`` is the provider-specific part
(e.g. ``vcpkg_provide_dependency()``, ``conan_provide_dependency()``,
``ourcompany_provide_dependency()``, and so on).
.. code-block:: cmake
xxx_provide_dependency(<method> [<method-specific-args>...])
Because some methods expect certain variables to be set in the calling scope,
the provider command should typically be implemented as a macro rather than a
function. This ensures it does not introduce a new variable scope.
The arguments CMake passes to the dependency provider depend on the type of
request. The first argument is always the method, and it will only ever
be one of the ``<methods>`` that was specified when setting the provider.
``FIND_PACKAGE``
The ``<method-specific-args>`` will be everything passed to the
:command:`find_package` call that requested the dependency. The first of
these ``<method-specific-args>`` will therefore always be the name of the
dependency. Dependency names are case-sensitive for this method because
:command:`find_package` treats them case-sensitively too.
If the provider command fulfills the request, it must set the same variable
that :command:`find_package` expects to be set. For a dependency named
``depName``, the provider must set ``depName_FOUND`` to true if it fulfilled
the request. If the provider returns without setting this variable, CMake
will assume the request was not fulfilled and will fall back to the
built-in implementation.
If the provider needs to call the built-in :command:`find_package`
implementation as part of its processing, it can do so by including the
``BYPASS_PROVIDER`` keyword as one of the arguments.
``FETCHCONTENT_MAKEAVAILABE_SERIAL``
The ``<method-specific-args>`` will be everything passed to the
:command:`FetchContent_Declare` call that corresponds to the requested
dependency, with the following exceptions:
* If ``SOURCE_DIR`` or ``BINARY_DIR`` were not part of the original
declared arguments, they will be added with their default values.
* If :variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` is set to ``NEVER``,
any ``FIND_PACKAGE_ARGS`` will be omitted.
* The ``OVERRIDE_FIND_PACKAGE`` keyword is always omitted.
The first of the ``<method-specific-args>`` will always be the name of the
dependency. Dependency names are case-insensitive for this method because
:module:`FetchContent` also treats them case-insensitively.
If the provider fulfills the request, it should call
:command:`FetchContent_SetPopulated`, passing the name of the dependency as
the first argument. The ``SOURCE_DIR`` and ``BINARY_DIR`` arguments to that
command should only be given if the provider makes the dependency's source
and build directories available in exactly the same way as the built-in
:command:`FetchContent_MakeAvailable` command.
If the provider returns without calling :command:`FetchContent_SetPopulated`
for the named dependency, CMake will assume the request was not fulfilled
and will fall back to the built-in implementation.
Note that empty arguments may be significant for this method (e.g. an empty
string following a ``GIT_SUBMODULES`` keyword). Therefore, if forwarding
these arguments on to another command, extra care must be taken to avoid such
arguments being silently dropped.
If ``FETCHCONTENT_SOURCE_DIR_<uppercaseDepName>`` is set, then the
dependency provider will never see requests for the ``<depName>`` dependency
for this method. When the user sets such a variable, they are explicitly
overriding where to get that dependency from and are taking on the
responsibility that their overriding version meets any requirements for that
dependency and is compatible with whatever else in the project uses it.
Depending on the value of :variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE`
and whether the ``OVERRIDE_FIND_PACKAGE`` option was given to
:command:`FetchContent_Declare`, having
``FETCHCONTENT_SOURCE_DIR_<uppercaseDepName>`` set may also prevent the
dependency provider from seeing requests for a ``find_package(depName)``
call too.
Provider Examples
"""""""""""""""""
This first example only intercepts :command:`find_package` calls. The
provider command runs an external tool which copies the relevant artifacts
into a provider-specific directory, if that tool knows about the dependency.
It then relies on the built-in implementation to then find those artifacts.
:command:`FetchContent_MakeAvailable` calls would not go through the provider.
.. code-block:: cmake
:caption: mycomp_provider.cmake
# Always ensure we have the policy settings this provider expects
cmake_minimum_required(VERSION 3.24)
set(MYCOMP_PROVIDER_INSTALL_DIR ${CMAKE_BINARY_DIR}/mycomp_packages
CACHE PATH "The directory this provider installs packages to"
)
# Tell the built-in implementation to look in our area first, unless
# the find_package() call uses NO_..._PATH options to exclude it
list(APPEND CMAKE_MODULE_PATH ${MYCOMP_PROVIDER_INSTALL_DIR}/cmake)
list(APPEND CMAKE_PREFIX_PATH ${MYCOMP_PROVIDER_INSTALL_DIR})
macro(mycomp_provide_dependency method package_name)
execute_process(
COMMAND some_tool ${package_name} --installdir ${MYCOMP_PROVIDER_INSTALL_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endmacro()
cmake_language(
SET_DEPENDENCY_PROVIDER mycomp_provide_dependency
SUPPORTED_METHODS FIND_PACKAGE
)
The user would then typically use the above file like so::
cmake -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/path/to/mycomp_provider.cmake ...
The next example demonstrates a provider that accepts both methods, but
only handles one specific dependency. It enforces providing Google Test
using :module:`FetchContent`, but leaves all other dependencies to be
fulfilled by CMake's built-in implementation. It accepts a few different
names, which demonstrates one way of working around projects that hard-code
an unusual or undesirable way of adding this particular dependency to the
build. The example also demonstrates how to use the :command:`list` command
to preserve variables that may be overwritten by a call to
:command:`FetchContent_MakeAvailable`.
.. code-block:: cmake
:caption: mycomp_provider.cmake
cmake_minimum_required(VERSION 3.24)
# Because we declare this very early, it will take precedence over any
# details the project might declare later for the same thing
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG e2239ee6043f73722e7aa812a459f54a28552929 # release-1.11.0
)
# Both FIND_PACKAGE and FETCHCONTENT_MAKEAVAILABLE_SERIAL methods provide
# the package or dependency name as the first method-specific argument.
macro(mycomp_provide_dependency method dep_name)
if("${dep_name}" MATCHES "^(gtest|googletest)$")
# Save our current command arguments in case we are called recursively
list(APPEND mycomp_provider_args ${method} ${dep_name})
# This will forward to the built-in FetchContent implementation,
# which detects a recursive call for the same thing and avoids calling
# the provider again if dep_name is the same as the current call.
FetchContent_MakeAvailable(googletest)
# Restore our command arguments
list(POP_BACK mycomp_provider_args dep_name method)
# Tell the caller we fulfilled the request
if("${method}" STREQUAL "FIND_PACKAGE")
# We need to set this if we got here from a find_package() call
# since we used a different method to fulfill the request.
# This example assumes projects only use the gtest targets,
# not any of the variables the FindGTest module may define.
set(${dep_name}_FOUND TRUE)
elseif(NOT "${dep_name}" STREQUAL "googletest")
# We used the same method, but were given a different name to the
# one we populated with. Tell the caller about the name it used.
FetchContent_SetPopulated(${dep_name}
SOURCE_DIR "${googletest_SOURCE_DIR}"
BINARY_DIR "${googletest_BINARY_DIR}"
)
endif()
endif()
endmacro()
cmake_language(
SET_DEPENDENCY_PROVIDER mycomp_provide_dependency
SUPPORTED_METHODS
FIND_PACKAGE
FETCHCONTENT_MAKEAVAILABLE_SERIAL
)
The final example demonstrates how to modify arguments to a
:command:`find_package` call. It forces all such calls to have the
``QUIET`` keyword. It uses the ``BYPASS_PROVIDER`` keyword to prevent
calling the provider command recursively for the same dependency.
.. code-block:: cmake
:caption: mycomp_provider.cmake
cmake_minimum_required(VERSION 3.24)
macro(mycomp_provide_dependency method)
find_package(${ARGN} BYPASS_PROVIDER QUIET)
endmacro()
cmake_language(
SET_DEPENDENCY_PROVIDER mycomp_provide_dependency
SUPPORTED_METHODS FIND_PACKAGE
)

View File

@ -12,7 +12,8 @@ find_package
.. contents::
Find a package (usually provided by something external to the project),
and load its package-specific details.
and load its package-specific details. Calls to this command can also
be intercepted by :ref:`dependency providers <dependency_providers>`.
Search Modes
^^^^^^^^^^^^

View File

@ -0,0 +1,9 @@
dependency-providers
--------------------
* The :command:`cmake_language` command gained a new
``SET_DEPENDENCY_PROVIDER`` sub-command. When a dependency provider is set,
calls to :command:`find_package` and :command:`FetchContent_MakeAvailable`
can be redirected through a custom command, which can choose to fulfill the
request directly, modify how the request is processed, or leave it to be
fulfilled by the built-in implementation. See :ref:`dependency_providers`.

View File

@ -193,6 +193,11 @@ Commands
``OVERRIDE_FIND_PACKAGE`` cannot be used when ``FIND_PACKAGE_ARGS`` is
given.
:ref:`dependency_providers` discusses another way that
:command:`FetchContent_MakeAvailable` calls can be redirected.
``FIND_PACKAGE_ARGS`` is intended for project control, whereas
dependency providers allow users to override project behavior.
``OVERRIDE_FIND_PACKAGE``
When a ``FetchContent_Declare(<name> ...)`` call includes this option,
subsequent calls to ``find_package(<name> ...)`` will ensure that
@ -204,6 +209,13 @@ Commands
satisfy the package requirements of the latter. ``FIND_PACKAGE_ARGS``
cannot be used when ``OVERRIDE_FIND_PACKAGE`` is given.
If a :ref:`dependency provider <dependency_providers>` has been set
and the project calls :command:`find_package` for the ``<name>``
dependency, ``OVERRIDE_FIND_PACKAGE`` will not prevent the provider
from seeing that call. Dependency providers always have the opportunity
to intercept any direct call to :command:`find_package`, except if that
call contains the ``BYPASS_PROVIDER`` option.
.. command:: FetchContent_MakeAvailable
.. versionadded:: 3.14
@ -217,17 +229,35 @@ Commands
: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 ``<lowercaseName>_SOURCE_DIR`` is not set:
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:
* .. versionadded:: 3.24
If a :ref:`dependency provider <dependency_providers>` is set, call the
provider's command with ``FETCHCONTENT_MAKEAVAILABLE_SERIAL`` as the
first argument, followed by the arguments of the first call to
:command:`FetchContent_Declare` for ``<name>``. If ``SOURCE_DIR`` or
``BINARY_DIR`` were not part of the original declared arguments, they
will be added with their default values.
If :variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` was set to ``NEVER``
when the details were declared, any ``FIND_PACKAGE_ARGS`` will be
omitted. The ``OVERRIDE_FIND_PACKAGE`` keyword is also always omitted.
If the provider fulfilled the request, ``FetchContent_MakeAvailable()``
will consider that dependency handled, skip the remaining steps below
and move on to the next dependency in the list.
* .. 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 the dependency was not satisfied by a provider or a
:command:`find_package` call, ``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
@ -468,7 +498,7 @@ Commands
When using saved content details, a call to
:command:`FetchContent_MakeAvailable` or :command:`FetchContent_Populate`
records information in global properties which can be queried at any time.
This information includes the source and binary directories associated with
This information may include the source and binary directories associated with
the content and also whether or not the content population has been processed
during the current configure run.
@ -488,6 +518,8 @@ Commands
set the same variables as a call to
:command:`FetchContent_MakeAvailable(name) <FetchContent_MakeAvailable>` or
:command:`FetchContent_Populate(name) <FetchContent_Populate>`.
Note that the ``SOURCE_DIR`` and ``BINARY_DIR`` values can be empty if the
call is fulfilled by a :ref:`dependency provider <dependency_providers>`.
This command is rarely needed when using
:command:`FetchContent_MakeAvailable`. It is more commonly used as part of
@ -511,6 +543,33 @@ Commands
add_subdirectory(${depname_SOURCE_DIR} ${depname_BINARY_DIR})
endif()
.. command:: FetchContent_SetPopulated
.. versionadded:: 3.24
.. note::
This command should only be called by
:ref:`dependency providers <dependency_providers>`. Calling it in any
other context is unsupported and future CMake versions may halt with a
fatal error in such cases.
.. code-block:: cmake
FetchContent_SetPopulated(
<name>
[SOURCE_DIR <srcDir>]
[BINARY_DIR <binDir>]
)
If a provider command fulfills a ``FETCHCONTENT_MAKEAVAILABLE_SERIAL``
request, it must call this function before returning. The ``SOURCE_DIR``
and ``BINARY_DIR`` arguments can be used to specify the values that
:command:`FetchContent_GetProperties` should return for its corresponding
arguments. Only provide ``SOURCE_DIR`` and ``BINARY_DIR`` if they have
the same meaning as if they had been populated by the built-in
:command:`FetchContent_MakeAvailable` implementation.
Variables
^^^^^^^^^
@ -588,7 +647,7 @@ A number of cache variables can influence the behavior where details from a
behavior if ``FETCHCONTENT_TRY_FIND_PACKAGE_MODE`` is not set.
``ALWAYS``
:command:`find_package` will be called by
:command:`find_package` can 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
@ -1099,14 +1158,26 @@ function(FetchContent_Declare contentName)
endif()
set(options "")
set(oneValueArgs SVN_REPOSITORY)
set(oneValueArgs
BINARY_DIR
SOURCE_DIR
SVN_REPOSITORY
)
set(multiValueArgs "")
cmake_parse_arguments(PARSE_ARGV 1 ARG
"${options}" "${oneValueArgs}" "${multiValueArgs}")
unset(srcDirSuffix)
unset(svnRepoArgs)
string(TOLOWER ${contentName} contentNameLower)
if(NOT ARG_BINARY_DIR)
set(ARG_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build")
endif()
if(NOT ARG_SOURCE_DIR)
set(ARG_SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src")
endif()
if(ARG_SVN_REPOSITORY)
# Add a hash of the svn repository URL to the source dir. This works
# around the problem where if the URL changes, the download would
@ -1116,25 +1187,21 @@ function(FetchContent_Declare contentName)
# problem on windows due to path length limits).
string(SHA1 urlSHA ${ARG_SVN_REPOSITORY})
string(SUBSTRING ${urlSHA} 0 7 urlSHA)
set(srcDirSuffix "-${urlSHA}")
set(svnRepoArgs SVN_REPOSITORY ${ARG_SVN_REPOSITORY})
string(APPEND ARG_SOURCE_DIR "-${urlSHA}")
list(PREPEND ARG_UNPARSED_ARGUMENTS SVN_REPOSITORY "${ARG_SVN_REPOSITORY}")
endif()
string(TOLOWER ${contentName} contentNameLower)
list(PREPEND ARG_UNPARSED_ARGUMENTS
SOURCE_DIR "${ARG_SOURCE_DIR}"
BINARY_DIR "${ARG_BINARY_DIR}"
)
set(__argsQuoted)
foreach(__item IN LISTS ARG_UNPARSED_ARGUMENTS)
string(APPEND __argsQuoted " [==[${__item}]==]")
endforeach()
cmake_language(EVAL CODE "
__FetchContent_declareDetails(
${contentNameLower}
SOURCE_DIR \"${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src${srcDirSuffix}\"
BINARY_DIR \"${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build\"
\${svnRepoArgs}
# List these last so they can override things we set above
${__argsQuoted}
)"
cmake_language(EVAL CODE
"__FetchContent_declareDetails(${contentNameLower} ${__argsQuoted})"
)
endfunction()
@ -1145,11 +1212,11 @@ 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 things like the FetchContent_Populate() function to
# record when FetchContent_Populate() is called for a particular
# content name.
function(__FetchContent_setPopulated contentName)
# Semi-internal use. Projects must not call this directly. Dependency
# providers must call it if they satisfy a request made with the
# FETCHCONTENT_MAKEAVAILABLE_SERIAL method (that is the only permitted
# place to call it outside of the FetchContent module).
function(FetchContent_SetPopulated contentName)
cmake_parse_arguments(PARSE_ARGV 1 arg
""
@ -1488,7 +1555,8 @@ function(FetchContent_Populate contentName)
if(${contentNameLower}_POPULATED)
if("${${contentNameLower}_SOURCE_DIR}" STREQUAL "")
message(FATAL_ERROR
"Content ${contentName} already populated by find_package()"
"Content ${contentName} already populated by find_package() or a "
"dependency provider"
)
else()
message(FATAL_ERROR
@ -1592,7 +1660,7 @@ function(FetchContent_Populate contentName)
)
endif()
__FetchContent_setPopulated(
FetchContent_SetPopulated(
${contentName}
SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}"
BINARY_DIR "${${contentNameLower}_BINARY_DIR}"
@ -1662,13 +1730,85 @@ endfunction()
# calls will be available to the caller.
macro(FetchContent_MakeAvailable)
get_property(__cmake_providerCommand GLOBAL PROPERTY
__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER
)
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().
# overrides everything else and we shouldn't try to use find_package() or
# a dependency provider.
string(TOUPPER ${__cmake_contentName} __cmake_contentNameUpper)
if("${FETCHCONTENT_SOURCE_DIR_${__cmake_contentNameUpper}}" STREQUAL "")
# Dependency provider gets first opportunity, but prevent infinite
# recursion if we are called again for the same thing
if(NOT "${__cmake_providerCommand}" STREQUAL "" AND
NOT DEFINED __cmake_fcProvider_${__cmake_contentNameLower})
message(VERBOSE
"Trying FETCHCONTENT_MAKEAVAILABLE_SERIAL dependency provider for "
"${__cmake_contentName}"
)
# It's still valid if there are no saved details. The project may have
# been written to assume a dependency provider is always set and will
# provide dependencies without having any declared details for them.
__FetchContent_getSavedDetails(${__cmake_contentName} __cmake_contentDetails)
set(__cmake_providerArgs
"FETCHCONTENT_MAKEAVAILABLE_SERIAL"
"${__cmake_contentName}"
)
# Empty arguments must be preserved because of things like
# GIT_SUBMODULES (see CMP0097)
foreach(__cmake_item IN LISTS __cmake_contentDetails)
string(APPEND __cmake_providerArgs " [==[${__cmake_item}]==]")
endforeach()
# This property might be defined but empty. As long as it is defined,
# find_package() can be called.
get_property(__cmake_addfpargs GLOBAL PROPERTY
_FetchContent_${contentNameLower}_find_package_args
DEFINED
)
if(__cmake_addfpargs)
get_property(__cmake_fpargs GLOBAL PROPERTY
_FetchContent_${contentNameLower}_find_package_args
)
string(APPEND __cmake_providerArgs " FIND_PACKAGE_ARGS")
foreach(__cmake_item IN LISTS __cmake_fpargs)
string(APPEND __cmake_providerArgs " [==[${__cmake_item}]==]")
endforeach()
endif()
# Calling the provider could lead to FetchContent_MakeAvailable() being
# called for a nested dependency. That nested call may occur in the
# current variable scope. We have to save and restore the variables we
# need preserved.
list(APPEND __cmake_fcCurrentVarsStack
${__cmake_contentName}
${__cmake_contentNameLower}
)
set(__cmake_fcProvider_${__cmake_contentNameLower} YES)
cmake_language(EVAL CODE "${__cmake_providerCommand}(${__cmake_providerArgs})")
unset(__cmake_fcProvider_${__cmake_contentNameLower})
list(POP_BACK __cmake_fcCurrentVarsStack
__cmake_contentNameLower
__cmake_contentName
)
unset(__cmake_providerArgs)
unset(__cmake_addfpargs)
unset(__cmake_fpargs)
unset(__cmake_item)
unset(__cmake_contentDetails)
FetchContent_GetProperties(${__cmake_contentName})
if(${__cmake_contentNameLower}_POPULATED)
continue()
endif()
endif()
# 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
@ -1698,7 +1838,7 @@ macro(FetchContent_MakeAvailable)
unset(__cmake_fpArgs)
if(${__cmake_contentName}_FOUND)
__FetchContent_setPopulated(${__cmake_contentName})
FetchContent_SetPopulated(${__cmake_contentName})
FetchContent_GetProperties(${__cmake_contentName})
continue()
endif()
@ -1745,5 +1885,6 @@ macro(FetchContent_MakeAvailable)
unset(__cmake_contentName)
unset(__cmake_contentNameLower)
unset(__cmake_contentNameUpper)
unset(__cmake_providerCommand)
endmacro()

View File

@ -199,6 +199,7 @@ set(SRCS
cmCustomCommandTypes.h
cmDefinitions.cxx
cmDefinitions.h
cmDependencyProvider.h
cmDepends.cxx
cmDepends.h
cmDependsC.cxx

View File

@ -13,11 +13,14 @@
#include <cm/string_view>
#include <cmext/string_view>
#include "cmArgumentParser.h"
#include "cmDependencyProvider.h"
#include "cmExecutionStatus.h"
#include "cmGlobalGenerator.h"
#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmRange.h"
#include "cmState.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
@ -215,6 +218,91 @@ bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
return makefile.ReadListFileAsString(
code, cmStrCat(context.FilePath, ":", context.Line, ":EVAL"));
}
bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
std::vector<std::string> const& args, cmExecutionStatus& status)
{
cmState* state = status.GetMakefile().GetState();
if (!state->InTopLevelIncludes()) {
return FatalError(
status,
"Dependency providers can only be set as part of the first call to "
"project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) "
"can only be called while the first project() command processes files "
"listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES.");
}
struct SetProviderArgs
{
std::string Command;
std::vector<std::string> Methods;
};
auto const ArgsParser =
cmArgumentParser<SetProviderArgs>()
.Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
.Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
std::vector<std::string> unparsed;
auto parsedArgs = ArgsParser.Parse(args, &unparsed);
if (!unparsed.empty()) {
return FatalError(
status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), "\""));
}
// We store the command that FetchContent_MakeAvailable() can call in a
// global (but considered internal) property. If the provider doesn't
// support this method, we set this property to an empty string instead.
// This simplifies the logic in FetchContent_MakeAvailable() and doesn't
// require us to define a new internal command or sub-command.
std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER";
if (parsedArgs.Command.empty()) {
if (!parsedArgs.Methods.empty()) {
return FatalError(status,
"Must specify a non-empty command name when provider "
"methods are given");
}
state->ClearDependencyProvider();
state->SetGlobalProperty(fcmasProperty, "");
return true;
}
cmState::Command command = state->GetCommand(parsedArgs.Command);
if (!command) {
return FatalError(status,
cmStrCat("Command \"", parsedArgs.Command,
"\" is not a defined command"));
}
if (parsedArgs.Methods.empty()) {
return FatalError(status, "Must specify at least one provider method");
}
bool supportsFetchContentMakeAvailableSerial = false;
std::vector<cmDependencyProvider::Method> methods;
for (auto const& method : parsedArgs.Methods) {
if (method == "FIND_PACKAGE") {
methods.emplace_back(cmDependencyProvider::Method::FindPackage);
} else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") {
supportsFetchContentMakeAvailableSerial = true;
methods.emplace_back(
cmDependencyProvider::Method::FetchContentMakeAvailableSerial);
} else {
return FatalError(
status,
cmStrCat("Unknown dependency provider method \"", method, "\""));
}
}
state->SetDependencyProvider({ parsedArgs.Command, methods });
state->SetGlobalProperty(
fcmasProperty,
supportsFetchContentMakeAvailableSerial ? parsedArgs.Command.c_str() : "");
return true;
}
}
bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
@ -246,6 +334,11 @@ bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
return FatalError(status, "called with incorrect number of arguments");
}
if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
finishArgs();
return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
}
cm::optional<Defer> maybeDefer;
if (expArgs[expArg] == "DEFER"_s) {
++expArg; // Consume "DEFER".

View File

@ -0,0 +1,38 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
class cmDependencyProvider
{
public:
enum class Method
{
FindPackage,
FetchContentMakeAvailableSerial,
};
cmDependencyProvider(std::string command, std::vector<Method> methods)
: Command(std::move(command))
, Methods(std::move(methods))
{
}
std::string const& GetCommand() const { return this->Command; }
std::vector<Method> const& GetMethods() const { return this->Methods; }
bool SupportsMethod(Method method) const
{
return std::find(this->Methods.begin(), this->Methods.end(), method) !=
this->Methods.end();
}
private:
std::string Command;
std::vector<Method> Methods;
};

View File

@ -23,6 +23,7 @@
#include "cmsys/String.h"
#include "cmAlgorithms.h"
#include "cmDependencyProvider.h"
#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
@ -239,6 +240,7 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
std::set<std::string> requiredComponents;
std::set<std::string> optionalComponents;
std::vector<std::pair<std::string, const char*>> componentVarDefs;
bool bypassProvider = false;
// Always search directly in a generated path.
this->SearchPathSuffixes.emplace_back();
@ -269,6 +271,9 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
if (args[i] == "QUIET") {
this->Quiet = true;
doing = DoingNone;
} else if (args[i] == "BYPASS_PROVIDER") {
bypassProvider = true;
doing = DoingNone;
} else if (args[i] == "EXACT") {
this->VersionExact = true;
doing = DoingNone;
@ -409,7 +414,7 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
return false;
}
// Maybe choose one mode exclusively.
// Check and eliminate search modes not allowed by the args provided
this->UseFindModules = configArgs.empty();
this->UseConfigFiles = moduleArgs.empty();
if (!this->UseFindModules && !this->UseConfigFiles) {
@ -544,6 +549,48 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
return true;
}
// Now choose what method(s) we will use to satisfy the request. Note that
// we still want all the above checking of arguments, etc. regardless of the
// method used. This will ensure ill-formed arguments are caught earlier,
// before things like dependency providers need to deal with them.
// A dependency provider (if set) gets first look before other methods.
// We do this before modifying the package root path stack because a
// provider might use methods that ignore that.
cmState* state = this->Makefile->GetState();
cmState::Command providerCommand = state->GetDependencyProviderCommand(
cmDependencyProvider::Method::FindPackage);
if (bypassProvider) {
if (this->DebugMode && providerCommand) {
this->DebugMessage(
"BYPASS_PROVIDER given, skipping dependency provider");
}
} else if (providerCommand) {
if (this->DebugMode) {
this->DebugMessage(cmStrCat("Trying dependency provider command: ",
state->GetDependencyProvider()->GetCommand(),
"()"));
}
std::vector<cmListFileArgument> listFileArgs(args.size() + 1);
listFileArgs[0] =
cmListFileArgument("FIND_PACKAGE", cmListFileArgument::Unquoted, 0);
std::transform(args.begin(), args.end(), listFileArgs.begin() + 1,
[](const std::string& arg) {
return cmListFileArgument(arg,
cmListFileArgument::Bracket, 0);
});
if (!providerCommand(listFileArgs, this->Status)) {
return false;
}
if (this->Makefile->IsOn(cmStrCat(this->Name, "_FOUND"))) {
if (this->DebugMode) {
this->DebugMessage("Package was found by the dependency provider");
}
this->AppendSuccessInformation();
return true;
}
}
{
// Allocate a PACKAGE_ROOT_PATH for the current find_package call.
this->Makefile->FindPackageRootPathStack.emplace_back();

View File

@ -9,6 +9,7 @@
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <cm/string_view>

View File

@ -690,6 +690,7 @@ void cmGlobalGenerator::EnableLanguage(
}
// One-time includes of user-provided project setup files
mf->GetState()->SetInTopLevelIncludes(true);
std::string includes =
mf->GetSafeDefinition("CMAKE_PROJECT_TOP_LEVEL_INCLUDES");
std::vector<std::string> includesList = cmExpandedList(includes);
@ -700,22 +701,26 @@ void cmGlobalGenerator::EnableLanguage(
cmSystemTools::Error(
"CMAKE_PROJECT_TOP_LEVEL_INCLUDES file does not exist: " +
setupFile);
mf->GetState()->SetInTopLevelIncludes(false);
return;
}
if (cmSystemTools::FileIsDirectory(absSetupFile)) {
cmSystemTools::Error(
"CMAKE_PROJECT_TOP_LEVEL_INCLUDES file is a directory: " +
setupFile);
mf->GetState()->SetInTopLevelIncludes(false);
return;
}
if (!mf->ReadListFile(absSetupFile)) {
cmSystemTools::Error(
"Failed reading CMAKE_PROJECT_TOP_LEVEL_INCLUDES file: " +
setupFile);
mf->GetState()->SetInTopLevelIncludes(false);
return;
}
}
}
mf->GetState()->SetInTopLevelIncludes(false);
// Check that the languages are supported by the generator and its
// native build tool found above.

View File

@ -1072,3 +1072,12 @@ bool cmState::ParseCacheEntry(const std::string& entry, std::string& var,
return flag;
}
cmState::Command cmState::GetDependencyProviderCommand(
cmDependencyProvider::Method method) const
{
return (this->DependencyProvider &&
this->DependencyProvider->SupportsMethod(method))
? this->GetCommand(this->DependencyProvider->GetCommand())
: Command{};
}

View File

@ -8,11 +8,16 @@
#include <memory>
#include <set>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <cm/optional>
#include "cmDefinitions.h"
#include "cmDependencyProvider.h"
#include "cmLinkedTree.h"
#include "cmPolicies.h"
#include "cmProperty.h"
@ -227,6 +232,24 @@ public:
ProjectKind GetProjectKind() const;
void ClearDependencyProvider() { this->DependencyProvider.reset(); }
void SetDependencyProvider(cmDependencyProvider provider)
{
this->DependencyProvider = std::move(provider);
}
cm::optional<cmDependencyProvider> const& GetDependencyProvider() const
{
return this->DependencyProvider;
}
Command GetDependencyProviderCommand(
cmDependencyProvider::Method method) const;
void SetInTopLevelIncludes(bool inTopLevelIncludes)
{
this->ProcessingTopLevelIncludes = inTopLevelIncludes;
}
bool InTopLevelIncludes() const { return this->ProcessingTopLevelIncludes; }
private:
friend class cmake;
void AddCacheEntry(const std::string& key, const char* value,
@ -288,4 +311,6 @@ private:
bool NinjaMulti = false;
Mode StateMode = Unknown;
ProjectKind StateProjectKind = ProjectKind::Normal;
cm::optional<cmDependencyProvider> DependencyProvider;
bool ProcessingTopLevelIncludes = false;
};

View File

@ -461,6 +461,7 @@ add_RunCMake_test(message)
add_RunCMake_test(option)
add_RunCMake_test(project -DCMake_TEST_RESOURCES=${CMake_TEST_RESOURCES})
add_RunCMake_test(project_injected)
add_RunCMake_test(DependencyProviders)
add_RunCMake_test(return)
add_RunCMake_test(separate_arguments)
add_RunCMake_test(set_property)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
CMake Error at set_provider\.cmake:[0-9]+ \(cmake_language\):
cmake_language Dependency providers can only be set as part of the first
call to project\(\)\. More specifically,
cmake_language\(SET_DEPENDENCY_PROVIDER\) can only be called while the first
project\(\) command processes files listed in
CMAKE_PROJECT_TOP_LEVEL_INCLUDES\.

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
CMake Error at set_provider\.cmake:[0-9]+ \(cmake_language\):
cmake_language Dependency providers can only be set as part of the first
call to project\(\)\. More specifically,
cmake_language\(SET_DEPENDENCY_PROVIDER\) can only be called while the first
project\(\) command processes files listed in
CMAKE_PROJECT_TOP_LEVEL_INCLUDES\.

View File

@ -0,0 +1,7 @@
-- Before cmake_language
-- After cmake_language
-- Forwarding find_package\(SomeDep\)
-- Provider invoked for method FIND_PACKAGE with args: QUIET;REQUIRED
-- SomeDepConfig\.cmake was used
-- Leaving provider
-- Configuring done

View File

@ -0,0 +1 @@
find_package(SomeDep QUIET REQUIRED)

View File

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.23...3.24)
if(DEFINED include_before_project)
include("${include_before_project}")
endif()
project(${RunCMake_TEST} NONE)
if(DEFINED include_after_project)
include("${include_after_project}")
endif()
include(${RunCMake_TEST}.cmake OPTIONAL)

View File

@ -0,0 +1,2 @@
message(STATUS "SomeDepConfig.cmake was used")
set(SomeDep_FOUND TRUE)

View File

@ -0,0 +1,7 @@
-- Before cmake_language
-- After cmake_language
-- AThing_FOUND = 0
-- Intercepted FetchContent_MakeAvailable\(SomeDep\)
-- Provider invoked for method FETCHCONTENT_MAKEAVAILABLE_SERIAL with args: SOURCE_DIR;.*/Tests/RunCMake/DependencyProviders;BINARY_DIR;.*/Tests/RunCMake/DependencyProviders/FetchContentSerial-build/_deps/somedep-build;SOURCE_SUBDIR;DoesNotExist
-- FetchContent_MakeAvailable\(\) succeeded
-- Configuring done

View File

@ -0,0 +1 @@
include(try_methods.cmake)

View File

@ -0,0 +1,7 @@
-- Before cmake_language
-- After cmake_language
-- Intercepted find_package\(AThing\)
-- Provider invoked for method FIND_PACKAGE with args: QUIET
-- AThing_FOUND = TRUE
-- FetchContent_MakeAvailable\(\) succeeded
-- Configuring done

View File

@ -0,0 +1 @@
include(try_methods.cmake)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,3 @@
CMake Error at set_provider\.cmake:[0-9]+ \(cmake_language\):
cmake_language Must specify a non-empty command name when provider methods
are given

View File

@ -0,0 +1,3 @@
-- Before cmake_language
-- After cmake_language
-- AThing_FOUND = 0

View File

@ -0,0 +1,3 @@
# Force the provider to be invoked
find_package(AThing QUIET)
message(STATUS "AThing_FOUND = ${AThing_FOUND}")

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,2 @@
CMake Error at set_provider\.cmake:[0-9]+ \(cmake_language\):
cmake_language Must specify at least one provider method

View File

@ -0,0 +1,7 @@
-- Before cmake_language
-- After cmake_language
-- Null provider called
-- Provider invoked for method FIND_PACKAGE with args: AThing;QUIET
-- AThing_FOUND = 0
-- Null provider called
-- Provider invoked for method FETCHCONTENT_MAKEAVAILABLE_SERIAL with args: SomeDep;SOURCE_DIR;.*/Tests/RunCMake/DependencyProviders;BINARY_DIR;.*/Tests/RunCMake/DependencyProviders/PassThroughProvider-build/_deps/somedep-build;SOURCE_SUBDIR;DoesNotExist

View File

@ -0,0 +1 @@
include(try_methods.cmake)

View File

@ -0,0 +1,6 @@
CMake Error at set_provider\.cmake:[0-9]+ \(cmake_language\):
cmake_language Dependency providers can only be set as part of the first
call to project\(\)\. More specifically,
cmake_language\(SET_DEPENDENCY_PROVIDER\) can only be called while the first
project\(\) command processes files listed in
CMAKE_PROJECT_TOP_LEVEL_INCLUDES\.

View File

@ -0,0 +1,6 @@
CMake Error at set_provider\.cmake:[0-9]+ \(cmake_language\):
cmake_language Dependency providers can only be set as part of the first
call to project\(\)\. More specifically,
cmake_language\(SET_DEPENDENCY_PROVIDER\) can only be called while the first
project\(\) command processes files listed in
CMAKE_PROJECT_TOP_LEVEL_INCLUDES\.

View File

@ -0,0 +1,7 @@
-- Before cmake_language
-- After cmake_language
-- Intercepted FetchContent_MakeAvailable\(SomeDep\)
-- Provider invoked for method FETCHCONTENT_MAKEAVAILABLE_SERIAL with args: SOURCE_DIR;.*/Tests/RunCMake/DependencyProviders/Recurse-build/_deps/somedep-src;BINARY_DIR;.*/Tests/RunCMake/DependencyProviders/Recurse-build/_deps/somedep-build;DOWNLOAD_COMMAND;.*/cmake(\.exe)?;-E;echo;Download command called
.*Download command called
.*-- Should now be handled
-- Configuring done

View File

@ -0,0 +1,8 @@
include(FetchContent)
set(FETCHCONTENT_QUIET NO)
FetchContent_Declare(SomeDep
DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Download command called"
)
FetchContent_MakeAvailable(SomeDep)

View File

@ -0,0 +1,11 @@
CMake Error at set_provider\.cmake:[0-9]+ \(find_package\):
Could not find a package configuration file provided by "SomeDep" with any
of the following names:
SomeDepConfig\.cmake
somedep-config\.cmake
Add the installation prefix of "SomeDep" to CMAKE_PREFIX_PATH or set
"SomeDep_DIR" to a directory containing one of the above files\. If
"SomeDep" provides a separate development package or SDK, be sure it has
been installed\.

View File

@ -0,0 +1,5 @@
-- Before cmake_language
-- After cmake_language
-- AThing_FOUND = 0
-- Redirecting FetchContent_MakeAvailable\(SomeDep\) to find_package\(\)
-- Provider invoked for method FETCHCONTENT_MAKEAVAILABLE_SERIAL with args: SOURCE_DIR;.*/Tests/RunCMake/DependencyProviders;BINARY_DIR;.*/Tests/RunCMake/DependencyProviders/RedirectFetchContentSerial-build/_deps/somedep-build;SOURCE_SUBDIR;DoesNotExist

View File

@ -0,0 +1 @@
include(try_methods.cmake)

View File

@ -0,0 +1,7 @@
-- Before cmake_language
-- After cmake_language
-- Redirecting find_package\(AThing\) to FetchContent_MakeAvailable\(\)
-- Provider invoked for method FIND_PACKAGE with args: QUIET
-- AThing_FOUND = TRUE
-- FetchContent_MakeAvailable\(\) succeeded
-- Configuring done

View File

@ -0,0 +1 @@
include(try_methods.cmake)

View File

@ -0,0 +1,73 @@
include(RunCMake)
run_cmake_with_options(BeforeProject
-D "include_before_project=set_provider.cmake"
-D "provider_command=null_provider"
-D "provider_methods=find_package"
)
run_cmake_with_options(AfterProject
-D "include_after_project=set_provider.cmake"
-D "provider_command=null_provider"
-D "provider_methods=find_package"
)
run_cmake_with_options(ProjectIncludeBefore
-D "CMAKE_PROJECT_INCLUDE_BEFORE=set_provider.cmake"
-D "provider_command=null_provider"
-D "provider_methods=find_package"
)
run_cmake_with_options(ProjectIncludeAfter
-D "CMAKE_PROJECT_INCLUDE=set_provider.cmake"
-D "provider_command=null_provider"
-D "provider_methods=find_package"
)
run_cmake_with_options(ToolchainFile
-D "CMAKE_TOOLCHAIN_FILE=set_provider.cmake"
-D "provider_command=null_provider"
-D "provider_methods=find_package"
)
run_cmake_with_options(NoCommand
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_methods=find_package"
)
run_cmake_with_options(NoMethods
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_command=null_provider"
)
run_cmake_with_options(NoCommandOrMethods
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
)
run_cmake_with_options(PassThroughProvider
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_command=null_provider"
-D "provider_methods=FIND_PACKAGE\\;FETCHCONTENT_MAKEAVAILABLE_SERIAL"
)
run_cmake_with_options(FindPackage
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_command=find_package_provider"
-D "provider_methods=FIND_PACKAGE"
)
run_cmake_with_options(RedirectFindPackage
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_command=redirect_find_package_provider"
-D "provider_methods=FIND_PACKAGE"
)
run_cmake_with_options(FetchContentSerial
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_command=FetchContentSerial_provider"
-D "provider_methods=FETCHCONTENT_MAKEAVAILABLE_SERIAL"
)
run_cmake_with_options(RedirectFetchContentSerial
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_command=redirect_FetchContentSerial_provider"
-D "provider_methods=FETCHCONTENT_MAKEAVAILABLE_SERIAL"
)
run_cmake_with_options(Bypass
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_command=forward_find_package"
-D "provider_methods=FIND_PACKAGE"
)
run_cmake_with_options(Recurse
-D "CMAKE_PROJECT_TOP_LEVEL_INCLUDES=set_provider.cmake"
-D "provider_command=recurse_FetchContent"
-D "provider_methods=FETCHCONTENT_MAKEAVAILABLE_SERIAL"
)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
CMake Error at set_provider\.cmake:[0-9]+ \(cmake_language\):
cmake_language Dependency providers can only be set as part of the first
call to project\(\)\. More specifically,
cmake_language\(SET_DEPENDENCY_PROVIDER\) can only be called while the first
project\(\) command processes files listed in
CMAKE_PROJECT_TOP_LEVEL_INCLUDES\.

View File

@ -0,0 +1,64 @@
include(FetchContent)
macro(null_provider method)
message(STATUS "Null provider called")
message(STATUS "Provider invoked for method ${method} with args: ${ARGN}")
endmacro()
macro(find_package_provider method package_name)
message(STATUS "Intercepted find_package(${package_name})")
message(STATUS "Provider invoked for method ${method} with args: ${ARGN}")
set(${package_name}_FOUND TRUE)
endmacro()
macro(FetchContentSerial_provider method dep_name)
message(STATUS "Intercepted FetchContent_MakeAvailable(${dep_name})")
message(STATUS "Provider invoked for method ${method} with args: ${ARGN}")
FetchContent_SetPopulated(${dep_name})
endmacro()
macro(redirect_find_package_provider method package_name)
message(STATUS "Redirecting find_package(${package_name}) to FetchContent_MakeAvailable()")
message(STATUS "Provider invoked for method ${method} with args: ${ARGN}")
FetchContent_Declare(${package_name}
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}
SOURCE_SUBDIR DoesNotExist
)
FetchContent_MakeAvailable(${package_name})
set(${package_name}_FOUND TRUE)
endmacro()
macro(redirect_FetchContentSerial_provider method dep_name)
message(STATUS "Redirecting FetchContent_MakeAvailable(${dep_name}) to find_package()")
message(STATUS "Provider invoked for method ${method} with args: ${ARGN}")
find_package(${dep_name} NO_DEFAULT_PATH
PATHS ${CMAKE_CURRENT_LIST_DIR}/Finders
REQUIRED
)
FetchContent_SetPopulated(${dep_name})
endmacro()
macro(forward_find_package method package_name)
message(STATUS "Forwarding find_package(${package_name})")
message(STATUS "Provider invoked for method ${method} with args: ${ARGN}")
find_package(${package_name}
BYPASS_PROVIDER
PATHS ${CMAKE_CURRENT_LIST_DIR}/ConfigFiles
${ARGN}
)
message(STATUS "Leaving provider")
endmacro()
macro(recurse_FetchContent method dep_name)
message(STATUS "Intercepted FetchContent_MakeAvailable(${dep_name})")
message(STATUS "Provider invoked for method ${method} with args: ${ARGN}")
FetchContent_MakeAvailable(${dep_name})
message(STATUS "Should now be handled")
endmacro()
message(STATUS "Before cmake_language")
cmake_language(
SET_DEPENDENCY_PROVIDER ${provider_command}
SUPPORTED_METHODS ${provider_methods}
)
message(STATUS "After cmake_language")

View File

@ -0,0 +1,12 @@
# Force the provider to be invoked for each method
find_package(AThing QUIET)
message(STATUS "AThing_FOUND = ${AThing_FOUND}")
# These declared details should always succeed when used
include(FetchContent)
FetchContent_Declare(SomeDep
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}
SOURCE_SUBDIR DoesNotExist
)
FetchContent_MakeAvailable(SomeDep)
message(STATUS "FetchContent_MakeAvailable() succeeded")