Dependency providers: Add find_package and FetchContent support
Fixes: #22619
This commit is contained in:
parent
8a28368feb
commit
2aa83fa15b
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
^^^^^^^^^^^^
|
||||
|
9
Help/release/dev/dependency-providers.rst
Normal file
9
Help/release/dev/dependency-providers.rst
Normal 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`.
|
@ -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()
|
||||
|
@ -199,6 +199,7 @@ set(SRCS
|
||||
cmCustomCommandTypes.h
|
||||
cmDefinitions.cxx
|
||||
cmDefinitions.h
|
||||
cmDependencyProvider.h
|
||||
cmDepends.cxx
|
||||
cmDepends.h
|
||||
cmDependsC.cxx
|
||||
|
@ -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".
|
||||
|
38
Source/cmDependencyProvider.h
Normal file
38
Source/cmDependencyProvider.h
Normal 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;
|
||||
};
|
@ -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();
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/string_view>
|
||||
|
@ -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.
|
||||
|
@ -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{};
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1 @@
|
||||
1
|
@ -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\.
|
@ -0,0 +1 @@
|
||||
1
|
@ -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\.
|
7
Tests/RunCMake/DependencyProviders/Bypass-stdout.txt
Normal file
7
Tests/RunCMake/DependencyProviders/Bypass-stdout.txt
Normal 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
|
1
Tests/RunCMake/DependencyProviders/Bypass.cmake
Normal file
1
Tests/RunCMake/DependencyProviders/Bypass.cmake
Normal file
@ -0,0 +1 @@
|
||||
find_package(SomeDep QUIET REQUIRED)
|
13
Tests/RunCMake/DependencyProviders/CMakeLists.txt
Normal file
13
Tests/RunCMake/DependencyProviders/CMakeLists.txt
Normal 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)
|
@ -0,0 +1,2 @@
|
||||
message(STATUS "SomeDepConfig.cmake was used")
|
||||
set(SomeDep_FOUND TRUE)
|
@ -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
|
@ -0,0 +1 @@
|
||||
include(try_methods.cmake)
|
@ -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
|
1
Tests/RunCMake/DependencyProviders/FindPackage.cmake
Normal file
1
Tests/RunCMake/DependencyProviders/FindPackage.cmake
Normal file
@ -0,0 +1 @@
|
||||
include(try_methods.cmake)
|
1
Tests/RunCMake/DependencyProviders/NoCommand-result.txt
Normal file
1
Tests/RunCMake/DependencyProviders/NoCommand-result.txt
Normal file
@ -0,0 +1 @@
|
||||
1
|
3
Tests/RunCMake/DependencyProviders/NoCommand-stderr.txt
Normal file
3
Tests/RunCMake/DependencyProviders/NoCommand-stderr.txt
Normal 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
|
@ -0,0 +1,3 @@
|
||||
-- Before cmake_language
|
||||
-- After cmake_language
|
||||
-- AThing_FOUND = 0
|
@ -0,0 +1,3 @@
|
||||
# Force the provider to be invoked
|
||||
find_package(AThing QUIET)
|
||||
message(STATUS "AThing_FOUND = ${AThing_FOUND}")
|
1
Tests/RunCMake/DependencyProviders/NoMethods-result.txt
Normal file
1
Tests/RunCMake/DependencyProviders/NoMethods-result.txt
Normal file
@ -0,0 +1 @@
|
||||
1
|
2
Tests/RunCMake/DependencyProviders/NoMethods-stderr.txt
Normal file
2
Tests/RunCMake/DependencyProviders/NoMethods-stderr.txt
Normal file
@ -0,0 +1,2 @@
|
||||
CMake Error at set_provider\.cmake:[0-9]+ \(cmake_language\):
|
||||
cmake_language Must specify at least one provider method
|
@ -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
|
@ -0,0 +1 @@
|
||||
include(try_methods.cmake)
|
@ -0,0 +1 @@
|
||||
1
|
@ -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\.
|
@ -0,0 +1 @@
|
||||
1
|
@ -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\.
|
7
Tests/RunCMake/DependencyProviders/Recurse-stdout.txt
Normal file
7
Tests/RunCMake/DependencyProviders/Recurse-stdout.txt
Normal 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
|
8
Tests/RunCMake/DependencyProviders/Recurse.cmake
Normal file
8
Tests/RunCMake/DependencyProviders/Recurse.cmake
Normal 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)
|
@ -0,0 +1 @@
|
||||
1
|
@ -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\.
|
@ -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
|
@ -0,0 +1 @@
|
||||
include(try_methods.cmake)
|
@ -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
|
@ -0,0 +1 @@
|
||||
include(try_methods.cmake)
|
73
Tests/RunCMake/DependencyProviders/RunCMakeTest.cmake
Normal file
73
Tests/RunCMake/DependencyProviders/RunCMakeTest.cmake
Normal 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"
|
||||
)
|
@ -0,0 +1 @@
|
||||
1
|
@ -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\.
|
64
Tests/RunCMake/DependencyProviders/set_provider.cmake
Normal file
64
Tests/RunCMake/DependencyProviders/set_provider.cmake
Normal 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")
|
12
Tests/RunCMake/DependencyProviders/try_methods.cmake
Normal file
12
Tests/RunCMake/DependencyProviders/try_methods.cmake
Normal 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")
|
Loading…
Reference in New Issue
Block a user