Commit b8bc975f authored by Craig Scott's avatar Craig Scott
Browse files

Packages: Add integration between FetchContent and find_package()

parent ce874fbc
Pipeline #208343 failed with stage
......@@ -658,19 +658,83 @@ current working directory.
function(__FetchContent_declareDetails contentName)
string(TOLOWER ${contentName} contentNameLower)
set(propertyName "_FetchContent_${contentNameLower}_savedDetails")
set(prefix "_FetchContent_${contentNameLower}")
set(propertyName "${prefix}_savedDetails")
get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED)
if(NOT alreadyDefined)
if(alreadyDefined)
return()
endif()
if("${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}" STREQUAL "ALWAYS")
set(__tryFindPackage TRUE)
set(__tryFindPackageAllowed TRUE)
elseif("${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}" STREQUAL "NEVER")
set(__tryFindPackage FALSE)
set(__tryFindPackageAllowed FALSE)
elseif("${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}" STREQUAL "OPT_IN" OR
"${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}" STREQUAL "")
set(__tryFindPackage FALSE)
set(__tryFindPackageAllowed TRUE)
else()
message(FATAL_ERROR
"Unsupported value for FETCHCONTENT_TRY_FIND_PACKAGE_MODE: "
"${FETCHCONTENT_TRY_FIND_PACKAGE_MODE}"
)
endif()
set(__overrideFindPackage FALSE)
set(__cmdArgs)
set(__findPackageArgs)
set(__argVar __cmdArgs)
foreach(__item IN LISTS ARGN)
if(__item STREQUAL "FIND_PACKAGE_ARGS")
if(__tryFindPackageAllowed)
set(__tryFindPackage TRUE)
endif()
# All arguments after this keyword are for find_package().
# Note that there could be no more args, that is still a valid case.
# We automatically provide ${contentName} as the package name, the
# rest of the args from here come after that.
set(__argVar __findPackageArgs)
continue() # Don't store this
elseif(__item STREQUAL "OVERRIDE_FIND_PACKAGE")
set(__overrideFindPackage TRUE)
set(__tryFindPackageAllowed FALSE)
endif()
string(APPEND ${__argVar} " [==[${__item}]==]")
endforeach()
define_property(GLOBAL PROPERTY ${propertyName}
BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
)
cmake_language(EVAL CODE
"set_property(GLOBAL PROPERTY ${propertyName} ${__cmdArgs})")
if(__overrideFindPackage)
# Define a separate dedicated property for find_package() to check
# in its implementation. This will be a placeholder until FetchContent
# actually does the population. After that, we will have created a
# stand-in config file that find_package() will pick up instead.
set(propertyName "${prefix}_override_find_package")
define_property(GLOBAL PROPERTY ${propertyName}
BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
BRIEF_DOCS "Internal implementation detail of FetchContent_DeclareDetails()"
FULL_DOCS "Indicates whether find_package(${contentName}) should delegate to FetchContent"
)
set(__cmdArgs)
foreach(__item IN LISTS ARGN)
string(APPEND __cmdArgs " [==[${__item}]==]")
endforeach()
set_property(GLOBAL PROPERTY ${propertyName} TRUE)
elseif(__tryFindPackage AND __tryFindPackageAllowed)
set(propertyName "${prefix}_find_package_args")
define_property(GLOBAL PROPERTY ${propertyName}
BRIEF_DOCS "Internal implementation detail of FetchContent_MakeAvailable()"
FULL_DOCS "Details used by FetchContent_MakeAvailable() for ${contentName}"
)
if(NOT QUIET IN_LIST __findPackageArgs)
list(INSERT __findPackageArgs 0 QUIET)
endif()
cmake_language(EVAL CODE
"set_property(GLOBAL PROPERTY ${propertyName} ${__cmdArgs})")
"set_property(GLOBAL PROPERTY ${propertyName} ${__findPackageArgs})")
endif()
endfunction()
......@@ -699,6 +763,15 @@ endfunction()
# SOURCE_DIR and BUILD_DIR.
function(FetchContent_Declare contentName)
# Always check this even if we won't save these details.
# This helps projects catch errors earlier.
if("OVERRIDE_FIND_PACKAGE" IN_LIST ARGN AND "FIND_PACKAGE_ARGS" IN_LIST ARGN)
message(FATAL_ERROR
"Cannot specify both OVERRIDE_FIND_PACKAGE and FIND_PACKAGE_ARGS "
"when declaring details for ${contentName}"
)
endif()
set(options "")
set(oneValueArgs SVN_REPOSITORY)
set(multiValueArgs "")
......@@ -750,7 +823,16 @@ endfunction()
# intended for use by the FetchContent_Populate() function to
# record when FetchContent_Populate() is called for a particular
# content name.
function(__FetchContent_setPopulated contentName sourceDir binaryDir)
function(__FetchContent_setPopulated contentName)
cmake_parse_arguments(PARSE_ARGV 1 ARG
"REDIRECT_FIND_PACKAGE"
"SOURCE_DIR;BINARY_DIR"
""
)
if(NOT "${ARG_UNPARSED_ARGUMENTS}" STREQUAL "")
message(FATAL_ERROR "Unsupported arguments: ${ARG_UNPARSED_ARGUMENTS}")
endif()
string(TOLOWER ${contentName} contentNameLower)
set(prefix "_FetchContent_${contentNameLower}")
......@@ -760,21 +842,54 @@ function(__FetchContent_setPopulated contentName sourceDir binaryDir)
BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
)
set_property(GLOBAL PROPERTY ${propertyName} ${sourceDir})
set_property(GLOBAL PROPERTY ${propertyName} "${ARG_SOURCE_DIR}")
set(propertyName "${prefix}_binaryDir")
define_property(GLOBAL PROPERTY ${propertyName}
BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
)
set_property(GLOBAL PROPERTY ${propertyName} ${binaryDir})
set_property(GLOBAL PROPERTY ${propertyName} "${ARG_BINARY_DIR}")
set(propertyName "${prefix}_populated")
define_property(GLOBAL PROPERTY ${propertyName}
BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
)
set_property(GLOBAL PROPERTY ${propertyName} True)
set_property(GLOBAL PROPERTY ${propertyName} TRUE)
if(ARG_REDIRECT_FIND_PACKAGE)
# Note that we write out dep-config.cmake and dep-config-version.cmake
# file name forms below because they are forced to lowercase. FetchContent
# dependency names are case-insensitive, but find_package() config files
# are only case-insensitive for the -config and -config-version forms, not
# the Config and ConfigVersion forms.
set(inFileDir ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/FetchContent)
set(configFilePrefix1 "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${contentName}Config")
set(configFilePrefix2 "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${contentNameLower}-config")
if(NOT EXISTS "${configFilePrefix1}.cmake" AND
NOT EXISTS "${configFilePrefix2}.cmake")
configure_file(${inFileDir}/package-config.cmake.in
"${configFilePrefix2}.cmake" @ONLY
)
endif()
if(NOT EXISTS "${configFilePrefix1}Version.cmake" AND
NOT EXISTS "${configFilePrefix2}-version.cmake")
configure_file(${inFileDir}/package-config-version.cmake.in
"${configFilePrefix2}-version.cmake" @ONLY
)
endif()
# Now that we've created the redirected package config files, prevent
# find_package() from delegating to FetchContent and let it find these
# config files through its normal processing.
set(propertyName "${prefix}_override_find_package")
set(GLOBAL PROPERTY ${propertyName} FALSE)
set(${contentName}_DIR "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}" CACHE
PATH "Redirected by FetchContent" INTERNAL
)
endif()
endfunction()
......@@ -852,6 +967,7 @@ function(__FetchContent_directPopulate contentName)
# We need special processing if DOWNLOAD_NO_EXTRACT is true
DOWNLOAD_NO_EXTRACT
# Prevent the following from being passed through
OVERRIDE_FIND_PACKAGE
CONFIGURE_COMMAND
BUILD_COMMAND
INSTALL_COMMAND
......@@ -1042,7 +1158,13 @@ function(FetchContent_Populate contentName)
# populated this content before in case the caller forgot to check.
FetchContent_GetProperties(${contentName})
if(${contentNameLower}_POPULATED)
message(FATAL_ERROR "Content ${contentName} already populated in ${${contentNameLower}_SOURCE_DIR}")
if("${${contentNameLower}_SOURCE_DIR}" STREQUAL "")
message(FATAL_ERROR
"Content ${contentName} already populated in ${${contentNameLower}_SOURCE_DIR}")
else()
message(FATAL_ERROR
"Content ${contentName} already populated by find_package()")
endif()
endif()
__FetchContent_getSavedDetails(${contentName} contentDetails)
......@@ -1055,6 +1177,13 @@ function(FetchContent_Populate contentName)
"${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}"
CACHE PATH "When not empty, overrides where to find pre-populated content for ${contentName}")
cmake_parse_arguments(savedDetails
"OVERRIDE_FIND_PACKAGE"
"SOURCE_DIR;BINARY_DIR"
""
${contentDetails}
)
if(FETCHCONTENT_SOURCE_DIR_${contentNameUpper})
# The source directory has been explicitly provided in the cache,
# so no population is required. The build directory may still be specified
......@@ -1074,8 +1203,6 @@ function(FetchContent_Populate contentName)
set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}")
cmake_parse_arguments(savedDetails "" "BINARY_DIR" "" ${contentDetails})
if(savedDetails_BINARY_DIR)
set(${contentNameLower}_BINARY_DIR ${savedDetails_BINARY_DIR})
else()
......@@ -1086,8 +1213,6 @@ function(FetchContent_Populate contentName)
# Bypass population and assume source is already there from a previous run.
# Declared details may override the default source or build directories.
cmake_parse_arguments(savedDetails "" "SOURCE_DIR;BINARY_DIR" "" ${contentDetails})
if(savedDetails_SOURCE_DIR)
set(${contentNameLower}_SOURCE_DIR ${savedDetails_SOURCE_DIR})
else()
......@@ -1138,10 +1263,27 @@ function(FetchContent_Populate contentName)
)
endif()
if(savedDetails_OVERRIDE_FIND_PACKAGE)
set(redirectOpt REDIRECT_FIND_PACKAGE)
else()
set(propertyName "_FetchContent_${contentNameLower}_find_package_args")
get_property(haveFindPackageArgs GLOBAL PROPERTY ${propertyName} DEFINED)
if(haveFindPackageArgs)
# There was an attempt to use find_package(), but it failed and we did
# the population instead. Make future find_package() calls for this
# dependency use our populated version of it and don't try to use
# find_package() any more.
set(redirectOpt REDIRECT_FIND_PACKAGE)
else()
set(redirectOpt "")
endif()
endif()
__FetchContent_setPopulated(
${contentName}
${${contentNameLower}_SOURCE_DIR}
${${contentNameLower}_BINARY_DIR}
SOURCE_DIR ${${contentNameLower}_SOURCE_DIR}
BINARY_DIR ${${contentNameLower}_BINARY_DIR}
${redirectOpt}
)
# Pass variables back to the caller. The variables passed back here
......@@ -1149,7 +1291,7 @@ function(FetchContent_Populate contentName)
# with just the content name.
set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE)
set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE)
set(${contentNameLower}_POPULATED True PARENT_SCOPE)
set(${contentNameLower}_POPULATED TRUE PARENT_SCOPE)
endfunction()
......@@ -1161,11 +1303,36 @@ endfunction()
# calls will be available to the caller.
macro(FetchContent_MakeAvailable)
foreach(contentName IN ITEMS ${ARGV})
string(TOLOWER ${contentName} contentNameLower)
FetchContent_GetProperties(${contentName})
if(NOT ${contentNameLower}_POPULATED)
FetchContent_Populate(${contentName})
foreach(__contentName IN ITEMS ${ARGV})
string(TOLOWER ${__contentName} __contentNameLower)
string(TOUPPER ${__contentName} __contentNameUpper)
set(__prefix "_FetchContent_${__contentNameLower}")
# If user specified SOURCE_DIR, that overrides everything else
if("${FETCHCONTENT_SOURCE_DIR_${__contentNameUpper}}" STREQUAL "")
# Check if we've been asked to try find_package() first, even if we
# have already populated this dependency. If we previously tried to
# use find_package() for this and it succeeded, those things might
# no longer be in scope, so we have to do it again.
set(__fpArgsPropName "${__prefix}_find_package_args")
get_property(__haveFpArgs GLOBAL PROPERTY ${__fpArgsPropName} DEFINED)
if(__haveFpArgs)
message(VERBOSE "Trying find_package(${__contentName} ...) before FetchContent")
get_property(__fpArgs GLOBAL PROPERTY ${__fpArgsPropName})
find_package(${__contentName} ${__fpArgs})
if(${__contentName}_FOUND)
set(${__contentNameLower}_SOURCE_DIR "")
set(${__contentNameLower}_BINARY_DIR "")
set(${__contentNameLower}_POPULATED TRUE)
__FetchContent_setPopulated(${__contentName})
continue()
endif()
endif()
endif()
FetchContent_GetProperties(${__contentName})
if(NOT ${__contentNameLower}_POPULATED)
FetchContent_Populate(${__contentName})
# Only try to call add_subdirectory() if the populated content
# can be treated that way. Protecting the call with the check
......@@ -1176,10 +1343,10 @@ macro(FetchContent_MakeAvailable)
# for ExternalProject. It won't matter if it was passed through
# to the ExternalProject sub-build, since it would have been
# ignored there.
set(__fc_srcdir "${${contentNameLower}_SOURCE_DIR}")
__FetchContent_getSavedDetails(${contentName} contentDetails)
set(__fc_srcdir "${${__contentNameLower}_SOURCE_DIR}")
__FetchContent_getSavedDetails(${__contentName} contentDetails)
if("${contentDetails}" STREQUAL "")
message(FATAL_ERROR "No details have been set for content: ${contentName}")
message(FATAL_ERROR "No details have been set for content: ${__contentName}")
endif()
cmake_parse_arguments(__fc_arg "" "SOURCE_SUBDIR" "" ${contentDetails})
if(NOT "${__fc_arg_SOURCE_SUBDIR}" STREQUAL "")
......@@ -1187,11 +1354,19 @@ macro(FetchContent_MakeAvailable)
endif()
if(EXISTS ${__fc_srcdir}/CMakeLists.txt)
add_subdirectory(${__fc_srcdir} ${${contentNameLower}_BINARY_DIR})
add_subdirectory(${__fc_srcdir} ${${__contentNameLower}_BINARY_DIR})
endif()
unset(__fc_srcdir)
unset(__fc_arg_SOURCE_SUBDIR)
endif()
endforeach()
unset(__fpArgsPropName)
unset(__haveFpArgs)
unset(__fpArgs)
unset(__contentName)
unset(__contentNameLower)
unset(__prefix)
endmacro()
# 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("@contentNameLower@-extra.cmake" OPTIONAL)
include("@contentName@Extra.cmake" OPTIONAL)
......@@ -42,6 +42,8 @@
class cmExecutionStatus;
class cmFileList;
cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::PackageRedirect(
"PACKAGE_REDIRECT");
cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::UserRegistry(
"PACKAGE_REGISTRY");
cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::Builds(
......@@ -111,6 +113,7 @@ void cmFindPackageCommand::AppendSearchPathGroups()
// Update the All group with new paths
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 +124,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(
......@@ -524,6 +529,39 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
this->SetModuleVariables(components);
// See if we have been told to delegate to FetchContent for population first.
// We have to check all names that find_package() may look for, but only need
// to invoke the override for the first one it will find.
auto overrideNames = this->Names;
if (overrideNames.empty()) {
overrideNames.push_back(this->Name);
}
for (const auto& overrideName : overrideNames) {
const auto nameLower = cmSystemTools::LowerCase(overrideName);
const auto delegatePropName =
cmStrCat("_FetchContent_", nameLower, "_override_find_package");
cmProp delegateToFetchContentProp =
this->Makefile->GetState()->GetGlobalProperty(delegatePropName);
if (cmIsOn(delegateToFetchContentProp)) {
// If this property is set, then the FetchContent module has already been
// included at least once and we know the FetchContent_MakeAvailable()
// command will be defined
cmListFileFunction func(
"FetchContent_MakeAvailable", 0,
{ cmListFileArgument(overrideName, cmListFileArgument::Unquoted, 0) });
if (!this->Makefile->ExecuteCommand(func, this->Status)) {
return false;
}
// Any future find_package() calls for this package will by-pass this
// once-only delegation. Both this case now and all future find_package()
// calls for this package should load the redirected package config files
// that FetchContent will have just created, so we still need to fall
// through to the next block for that processing.
// TODO: Need some way to indicate config mode must be used
break;
}
}
// See if there is a Find<PackageName>.cmake module.
bool loadedPackage = false;
if (this->Makefile->IsOn("CMAKE_FIND_PACKAGE_PREFER_CONFIG")) {
......@@ -1292,6 +1330,8 @@ inline std::size_t collectPathsForDebug(std::string& buffer,
void cmFindPackageCommand::ComputePrefixes()
{
this->FillPrefixesPackageRedirect();
if (!this->NoDefaultPath) {
if (!this->NoPackageRootPath) {
this->FillPrefixesPackageRoot();
......@@ -1325,6 +1365,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;
......@@ -121,6 +122,7 @@ private:
void StoreVersionFound();
void ComputePrefixes();
void FillPrefixesPackageRedirect();
void FillPrefixesPackageRoot();
void FillPrefixesCMakeEnvironment();
void FillPrefixesCMakeVariable();
......
......@@ -1417,6 +1417,19 @@ int cmake::AddCMakePaths()
this->AddCacheEntry("CMAKE_ROOT", cmSystemTools::GetCMakeRoot().c_str(),
"Path to CMake installation.", cmStateEnums::INTERNAL);
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 0;
}
this->AddCacheEntry("CMAKE_FIND_PACKAGE_REDIRECTS_DIR", redirectsDir.c_str(),
"Value Computed by CMake.", cmStateEnums::STATIC);
return 1;
}
......
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