Commit 7642cc98 authored by Craig Scott's avatar Craig Scott
Browse files

Packages: Integrate FetchContent and find_package()

Allow FetchContent_MakeAvailable() to try a call to
find_package() first, or redirect a find_package() call to
FetchContent_MakeAvailable(). The user can set variables
to control which of these are allowed or tried by default.

Fixes: #21687
parent 32d4a8ee
......@@ -11,7 +11,7 @@ and load its package-specific details.
Search Modes
^^^^^^^^^^^^
The command has two modes by which it searches for packages:
The command has a few modes by which it searches for packages:
**Module mode**
CMake searches for a file called ``Find<PackageName>.cmake``.
......@@ -34,7 +34,17 @@ The command has two modes by which it searches for packages:
Config mode is supported by both the :ref:`basic <Basic Signature>` and
:ref:`full <Full Signature>` command signatures.
The command arguments determine which of the above modes is used. When the
**FetchContent redirection mode**
.. versionadded:: 3.22
A call to ``find_package()`` can be redirected internally to a package
provided by the :module:`FetchContent` module. To the caller, the behavior
will appear similar to Config mode, except that the search logic is
by-passed and the component information is not used. See
:command:`FetchContent_Declare` and :command:`FetchContent_MakeAvailable`
for further details.
When not redirected to a package provided by :module:`FetchContent`, the
command arguments determine whether Module or Config mode is used. When the
`basic signature`_ is used, the command searches in Module mode first.
If the package is not found, the search falls back to Config mode.
A user may set the :variable:`CMAKE_FIND_PACKAGE_PREFER_CONFIG` variable
......@@ -44,7 +54,7 @@ forced to use only Module mode with a ``MODULE`` keyword. If the
`full signature`_ is used, the command only searches in Config mode.
Where possible, user code should generally look for packages using the
`basic signature`_, since that allows the package to be found with either mode.
`basic signature`_, since that allows the package to be found with any mode.
Project maintainers wishing to provide a config package should understand
the bigger picture, as explained in :ref:`Full Signature` and all subsequent
sections on this page.
......@@ -160,9 +170,12 @@ proceeds at once with Config mode search.
Config mode search attempts to locate a configuration file provided by the
package to be found. A cache entry called ``<PackageName>_DIR`` is created to
hold the directory containing the file. By default the command
searches for a package with the name ``<PackageName>``. If the ``NAMES`` option
is given the names following it are used instead of ``<PackageName>``.
hold the directory containing the file. By default, the command searches for
a package with the name ``<PackageName>``. If the ``NAMES`` option is given,
the names following it are used instead of ``<PackageName>``. The names are
also considered when determining whether to redirect the call to a package
provided by :module:`FetchContent`.
The command searches for a file called ``<PackageName>Config.cmake`` or
``<lowercasePackageName>-config.cmake`` for each name specified.
A replacement set of possible configuration file names may be given
......@@ -199,6 +212,14 @@ Config Mode Search Procedure
whether the :ref:`full <full signature>` or :ref:`basic <basic signature>`
signature was given.
.. versionadded:: 3.22
All calls to ``find_package()`` (even in Module mode) first look for a config
package file in the :variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR` directory.
The :module:`FetchContent` module, or even the project itself, may write files
to that location to redirect ``find_package()`` calls to content already
provided by the project. If no config package file is found in that location,
the search proceeds with the logic described below.
CMake constructs a set of possible installation prefixes for the
package. Under each prefix several directories are searched for a
configuration file. The tables below show the directories searched.
......@@ -362,7 +383,8 @@ to resolve symbolic links and store the real path to the file.
Every non-REQUIRED ``find_package`` call can be disabled or made REQUIRED:
* Setting the :variable:`CMAKE_DISABLE_FIND_PACKAGE_<PackageName>` variable
to ``TRUE`` disables the package.
to ``TRUE`` disables the package. This also disables redirection to a
package provided by :module:`FetchContent`.
* Setting the :variable:`CMAKE_REQUIRE_FIND_PACKAGE_<PackageName>` variable
to ``TRUE`` makes the package REQUIRED.
......@@ -385,8 +407,8 @@ version (see :ref:`format specification <FIND_PACKAGE_VERSION_FORMAT>`). If the
``EXACT`` option is given, only a version of the package claiming an exact match
of the requested version may be found. CMake does not establish any
convention for the meaning of version numbers. Package version
numbers are checked by "version" files provided by the packages
themselves. For a candidate package configuration file
numbers are checked by "version" files provided by the packages themselves
or by :module:`FetchContent`. For a candidate package configuration file
``<config-file>.cmake`` the corresponding version file is located next
to it and named either ``<config-file>-version.cmake`` or
``<config-file>Version.cmake``. If no such version file is available
......
......@@ -58,6 +58,7 @@ Variables that Provide Information
/variable/CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES
/variable/CMAKE_FIND_DEBUG_MODE
/variable/CMAKE_FIND_PACKAGE_NAME
/variable/CMAKE_FIND_PACKAGE_REDIRECTS_DIR
/variable/CMAKE_FIND_PACKAGE_SORT_DIRECTION
/variable/CMAKE_FIND_PACKAGE_SORT_ORDER
/variable/CMAKE_GENERATOR
......
FetchContent_find_package_integration
-------------------------------------
* Integration has been added between the :module:`FetchContent` module and the
:command:`find_package` command, enabling the following new capabilities:
* :command:`FetchContent_MakeAvailable` can now try to satisfy a dependency
by calling :command:`find_package` first. A new
:variable:`FETCHCONTENT_TRY_FIND_PACKAGE_MODE` variable controls whether
this is done by default for all dependencies, is opt-in per dependency,
or is disabled entirely.
* :command:`find_package` can be re-routed to call
:command:`FetchContent_MakeAvailable` instead. A new read-only
:variable:`CMAKE_FIND_PACKAGE_REDIRECTS_DIR` variable points to a
directory where config package files can be located to facilitate these
re-routed calls.
CMAKE_FIND_PACKAGE_REDIRECTS_DIR
--------------------------------
.. versionadded:: 3.22
This read-only variable specifies a directory that the :command:`find_package`
command will check first before searching anywhere else for a module or config
package file. A config package file in this directory will always be found in
preference to any other Find module file or config package file.
The primary purpose of this variable is to facilitate integration between
:command:`find_package` and :command:`FetchContent_MakeAvailable`. The latter
command may create files in the ``CMAKE_FIND_PACKAGE_REDIRECTS_DIR`` directory
when it populates a dependency. This allows subsequent calls to
:command:`find_package` for the same dependency to re-use the populated
contents instead of trying to satisfy the dependency from somewhere external
to the build. Projects may also want to write files into this directory in
some situations (see :ref:`FetchContent-find_package-integration` for examples).
The directory that ``CMAKE_FIND_PACKAGE_REDIRECTS_DIR`` points to will always
be erased and recreated empty at the start of every CMake run. Any files
written into this directory during the CMake run will be lost the next time
CMake configures the project.
``CMAKE_FIND_PACKAGE_REDIRECTS_DIR`` is only set in CMake project mode.
It is not set when CMake is run in script mode
(i.e. :manual:`cmake -P ... <cmake(1)>`).
This diff is collapsed.
# Automatically generated by CMake's FetchContent module.
# Do not edit this file, it will be regenerated every time CMake runs.
# Version not available, assuming it is compatible
set(PACKAGE_VERSION_COMPATIBLE TRUE)
# Automatically generated by CMake's FetchContent module.
# Do not edit this file, it will be regenerated every time CMake runs.
# Projects or the dependencies themselves can provide the following files.
# The files should define any additional commands or variables that the
# dependency would normally provide but which won't be available globally
# if the dependency is brought into the build via FetchContent instead.
# For dependencies that only provide imported targets and no commands,
# these typically won't be needed.
include("${CMAKE_CURRENT_LIST_DIR}/@contentNameLower@-extra.cmake" OPTIONAL)
include("${CMAKE_CURRENT_LIST_DIR}/@contentName@Extra.cmake" OPTIONAL)
......@@ -22,6 +22,7 @@
#include "cmsys/String.h"
#include "cmAlgorithms.h"
#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
......@@ -42,6 +43,8 @@
class cmExecutionStatus;
class cmFileList;
cmFindPackageCommand::PathLabel
cmFindPackageCommand::PathLabel::PackageRedirect("PACKAGE_REDIRECT");
cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::UserRegistry(
"PACKAGE_REGISTRY");
cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::Builds(
......@@ -109,8 +112,10 @@ void cmFindPackageCommand::AppendSearchPathGroups()
{
std::vector<cmFindCommon::PathLabel>* labels;
// Update the All group with new paths
// Update the All group with new paths. Note that package redirection must
// take precedence over everything else, so it has to be first in the array.
labels = &this->PathGroupLabelMap[PathGroup::All];
labels->insert(labels->begin(), PathLabel::PackageRedirect);
labels->insert(
std::find(labels->begin(), labels->end(), PathLabel::CMakeSystem),
PathLabel::UserRegistry);
......@@ -121,6 +126,8 @@ void cmFindPackageCommand::AppendSearchPathGroups()
PathLabel::SystemRegistry);
// Create the new path objects
this->LabeledPaths.insert(
std::make_pair(PathLabel::PackageRedirect, cmSearchPath(this)));
this->LabeledPaths.insert(
std::make_pair(PathLabel::UserRegistry, cmSearchPath(this)));
this->LabeledPaths.insert(
......@@ -542,9 +549,62 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
this->SetModuleVariables(components);
// See if we have been told to delegate to FetchContent or some other
// redirected config package first. We have to check all names that
// find_package() may look for, but only need to invoke the override for the
// first one that matches.
auto overrideNames = this->Names;
if (overrideNames.empty()) {
overrideNames.push_back(this->Name);
}
bool forceConfigMode = false;
const auto redirectsDir =
this->Makefile->GetSafeDefinition("CMAKE_FIND_PACKAGE_REDIRECTS_DIR");
for (const auto& overrideName : overrideNames) {
const auto nameLower = cmSystemTools::LowerCase(overrideName);
const auto delegatePropName =
cmStrCat("_FetchContent_", nameLower, "_override_find_package");
const cmProp delegateToFetchContentProp =
this->Makefile->GetState()->GetGlobalProperty(delegatePropName);
if (delegateToFetchContentProp.IsOn()) {
// When this property is set, the FetchContent module has already been
// included at least once, so we know the FetchContent_MakeAvailable()
// command will be defined. Any future find_package() calls after this
// one for this package will by-pass this once-only delegation.
// The following call will typically create a <name>-config.cmake file
// in the redirectsDir, which we still want to process like any other
// config file to ensure we follow normal find_package() processing.
cmListFileFunction func(
"FetchContent_MakeAvailable", 0,
{ cmListFileArgument(overrideName, cmListFileArgument::Unquoted, 0) });
if (!this->Makefile->ExecuteCommand(func, this->Status)) {
return false;
}
}
if (cmSystemTools::FileExists(
cmStrCat(redirectsDir, '/', nameLower, "-config.cmake")) ||
cmSystemTools::FileExists(
cmStrCat(redirectsDir, '/', overrideName, "Config.cmake"))) {
// Force the use of this redirected config package file, regardless of
// the type of find_package() call. Files in the redirectsDir must always
// take priority over everything else.
forceConfigMode = true;
this->UseConfigFiles = true;
this->UseFindModules = false;
this->Names.clear();
this->Names.emplace_back(overrideName); // Force finding this one
this->Variable = cmStrCat(this->Name, "_DIR");
this->SetConfigDirCacheVariable(redirectsDir);
break;
}
}
// See if there is a Find<PackageName>.cmake module.
bool loadedPackage = false;
if (this->Makefile->IsOn("CMAKE_FIND_PACKAGE_PREFER_CONFIG")) {
if (forceConfigMode) {
loadedPackage = this->FindPackageUsingConfigMode();
} else if (this->Makefile->IsOn("CMAKE_FIND_PACKAGE_PREFER_CONFIG")) {
if (this->UseConfigFiles && this->FindPackageUsingConfigMode()) {
loadedPackage = true;
} else {
......@@ -1150,19 +1210,24 @@ bool cmFindPackageCommand::FindConfig()
} else {
init = this->Variable + "-NOTFOUND";
}
// We force the value since we do not get here if it was already set.
this->SetConfigDirCacheVariable(init);
return found;
}
void cmFindPackageCommand::SetConfigDirCacheVariable(const std::string& value)
{
std::string help =
cmStrCat("The directory containing a CMake configuration file for ",
this->Name, '.');
// We force the value since we do not get here if it was already set.
this->Makefile->AddCacheDefinition(this->Variable, init, help.c_str(),
this->Makefile->AddCacheDefinition(this->Variable, value, help.c_str(),
cmStateEnums::PATH, true);
if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) ==
cmPolicies::NEW &&
this->Makefile->IsNormalDefinitionSet(this->Variable)) {
this->Makefile->AddDefinition(this->Variable, init);
this->Makefile->AddDefinition(this->Variable, value);
}
return found;
}
bool cmFindPackageCommand::FindPrefixedConfig()
......@@ -1308,6 +1373,8 @@ inline std::size_t collectPathsForDebug(std::string& buffer,
void cmFindPackageCommand::ComputePrefixes()
{
this->FillPrefixesPackageRedirect();
if (!this->NoDefaultPath) {
if (!this->NoPackageRootPath) {
this->FillPrefixesPackageRoot();
......@@ -1341,6 +1408,23 @@ void cmFindPackageCommand::ComputePrefixes()
this->ComputeFinalPaths();
}
void cmFindPackageCommand::FillPrefixesPackageRedirect()
{
cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRedirect];
const auto redirectDir =
this->Makefile->GetDefinition("CMAKE_FIND_PACKAGE_REDIRECTS_DIR");
if (redirectDir && !redirectDir->empty()) {
paths.AddPath(*redirectDir);
}
if (this->DebugMode) {
std::string debugBuffer =
"The internally managed CMAKE_FIND_PACKAGE_REDIRECTS_DIR.\n";
collectPathsForDebug(debugBuffer, paths);
this->DebugBuffer = cmStrCat(this->DebugBuffer, debugBuffer, "\n");
}
}
void cmFindPackageCommand::FillPrefixesPackageRoot()
{
cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRoot];
......
......@@ -76,6 +76,7 @@ private:
: cmFindCommon::PathLabel(label)
{
}
static PathLabel PackageRedirect;
static PathLabel UserRegistry;
static PathLabel Builds;
static PathLabel SystemRegistry;
......@@ -119,8 +120,10 @@ private:
};
bool ReadListFile(const std::string& f, PolicyScopeRule psr);
void StoreVersionFound();
void SetConfigDirCacheVariable(const std::string& value);
void ComputePrefixes();
void FillPrefixesPackageRedirect();
void FillPrefixesPackageRoot();
void FillPrefixesCMakeEnvironment();
void FillPrefixesCMakeVariable();
......
......@@ -1999,6 +1999,21 @@ int cmake::ActualConfigure()
cmStateEnums::INTERNAL);
}
// We want to create the package redirects directory as early as possible,
// but not before pre-configure checks have passed. This ensures we get
// errors about inappropriate source/binary directories first.
const auto redirectsDir =
cmStrCat(this->GetHomeOutputDirectory(), "/CMakeFiles/pkgRedirects");
cmSystemTools::RemoveADirectory(redirectsDir);
if (!cmSystemTools::MakeDirectory(redirectsDir)) {
cmSystemTools::Error(
"Unable to (re)create the private pkgRedirects directory:\n" +
redirectsDir);
return -1;
}
this->AddCacheEntry("CMAKE_FIND_PACKAGE_REDIRECTS_DIR", redirectsDir.c_str(),
"Value Computed by CMake.", cmStateEnums::STATIC);
// no generator specified on the command line
if (!this->GlobalGenerator) {
cmProp genName = this->State->GetInitializedCacheValue("CMAKE_GENERATOR");
......
......@@ -684,6 +684,7 @@ if(XCODE_VERSION)
endif()
add_RunCMake_test(ExternalProject)
add_RunCMake_test(FetchContent)
add_RunCMake_test(FetchContent_find_package)
set(CTestCommandLine_ARGS -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE})
if(NOT CMake_TEST_EXTERNAL_CMAKE)
list(APPEND CTestCommandLine_ARGS -DTEST_AFFINITY=$<TARGET_FILE:testAffinity>)
......
cmake_minimum_required(VERSION 3.13)
project(AddedProject LANGUAGES NONE)
message(STATUS "Confirmation project has been added")
CMake Error at .*/FetchContent.cmake:[0-9]+ \(message\):
Cannot specify both OVERRIDE_FIND_PACKAGE and FIND_PACKAGE_ARGS when
declaring details for AddedProject
include(FetchContent)
FetchContent_Declare(
AddedProject
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/AddedProject
# The following two args are mutually exclusive
OVERRIDE_FIND_PACKAGE
FIND_PACKAGE_ARGS
)
file(WRITE "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/dummy_file.txt"
"This file should be deleted the next time CMake runs"
)
file(GLOB contents LIST_DIRECTORIES true "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/*")
if(NOT contents STREQUAL "")
list(JOIN contents "\n" fileList)
message(FATAL_ERROR
"CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not empty:\n"
"${fileList}"
)
endif()
if(NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR)
message(FATAL_ERROR "CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined")
endif()
if(NOT CMAKE_FIND_PACKAGE_REDIRECTS_DIR STREQUAL "${CMAKE_BINARY_DIR}/CMakeFiles/pkgRedirects")
message(FATAL_ERROR
"CMAKE_FIND_PACKAGE_REDIRECTS_DIR has wrong value\n"
" Expected: ${CMAKE_BINARY_DIR}/CMakeFiles/pkgRedirects\n"
" Actual: ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}"
)
endif()
if(NOT EXISTS "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}")
message(FATAL_ERROR
"Directory CMAKE_FIND_PACKAGE_REDIRECTS_DIR points to does not exist:\n"
"${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}"
)
endif()
cmake_minimum_required(VERSION 3.21)
project(${RunCMake_TEST} NONE)
# Tests assume no previous downloads in the output directory
file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/_deps)
include(${RunCMake_TEST}.cmake)
message(FATAL_ERROR "Unexpectedly added directory via FetchContent_MakeAvailable()")
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment