find_package: CPS component requirements != CMake components

Modify how CMake handles required components of a CPS transitive
dependency to not pass them as COMPONENTS if a CMake-script package is
found as the resolved dependency. This is necessary as many CMake-script
package description files do not treat component requests as target
requests (which, in CPS-land, they effectively are), but do implement
logic to mark themselves 'not found' if requested components are
missing. As a result, passing in the required targets as required
components is likely to cause the dependency to be spuriously not found
if it is only available via a CMake-script package configuration file.

Fix this by introducing a new 'required targets' concept, and by passing
CPS component requirements as both required targets and optional
components. The latter serves as a hint for packages that might provide
only a subset of themselves. The former is used to post-validate a
CMake-script package, or is folded on-the-fly into required components
when considering CPS packages.

Note that this functionality is not exposed to the user at this time,
and is only used when resolving transitive dependencies for a CPS
package.
This commit is contained in:
Matthew Woehlke 2025-03-05 14:47:49 -05:00
parent bc51d06814
commit c3d279841b
15 changed files with 93 additions and 18 deletions

View File

@ -1525,6 +1525,7 @@ bool cmFindPackageCommand::HandlePackageMode(
bool result = true; bool result = true;
bool found = false; bool found = false;
bool configFileSetFOUNDFalse = false; bool configFileSetFOUNDFalse = false;
std::vector<std::string> missingTargets;
if (fileFound) { if (fileFound) {
if (this->Makefile->IsDefinitionSet(foundVar) && if (this->Makefile->IsDefinitionSet(foundVar) &&
@ -1559,6 +1560,17 @@ bool cmFindPackageCommand::HandlePackageMode(
notFoundMessage = notFoundMessage =
this->Makefile->GetSafeDefinition(notFoundMessageVar); this->Makefile->GetSafeDefinition(notFoundMessageVar);
} }
// Check whether the required targets are defined.
if (found && !this->RequiredTargets.empty()) {
for (std::string const& t : this->RequiredTargets) {
std::string qualifiedTarget = cmStrCat(this->Name, "::"_s, t);
if (!this->Makefile->FindImportedTarget(qualifiedTarget)) {
missingTargets.emplace_back(std::move(qualifiedTarget));
found = false;
}
}
}
} else { } else {
// The configuration file is invalid. // The configuration file is invalid.
result = false; result = false;
@ -1593,10 +1605,18 @@ bool cmFindPackageCommand::HandlePackageMode(
if (!notFoundMessage.empty()) { if (!notFoundMessage.empty()) {
e << " Reason given by package: \n" << notFoundMessage << "\n"; e << " Reason given by package: \n" << notFoundMessage << "\n";
} }
} } else if (!missingTargets.empty()) {
// If there are files in ConsideredConfigs, it means that FooConfig.cmake e << "Found package configuration file:\n"
// have been found, but they didn't have appropriate versions. " "
else if (!this->ConsideredConfigs.empty()) { << this->FileFound
<< "\n"
"but the following required targets were not found:\n"
" "
<< cmJoin(cmMakeRange(missingTargets), ", "_s);
} else if (!this->ConsideredConfigs.empty()) {
// If there are files in ConsideredConfigs, it means that
// FooConfig.cmake have been found, but they didn't have appropriate
// versions.
auto duplicate_end = cmRemoveDuplicates(this->ConsideredConfigs); auto duplicate_end = cmRemoveDuplicates(this->ConsideredConfigs);
e << "Could not find a configuration file for package \"" << this->Name e << "Could not find a configuration file for package \"" << this->Name
<< "\" that " << "\" that "
@ -1903,7 +1923,7 @@ bool cmFindPackageCommand::ReadPackage()
return false; return false;
} }
auto const hasComponentsRequested = bool const hasComponentsRequested =
!this->RequiredComponents.empty() || !this->OptionalComponents.empty(); !this->RequiredComponents.empty() || !this->OptionalComponents.empty();
cmMakefile::CallRAII scope{ this->Makefile, this->FileFound, this->Status }; cmMakefile::CallRAII scope{ this->Makefile, this->FileFound, this->Status };
@ -1994,8 +2014,9 @@ bool cmFindPackageCommand::FindPackageDependencies(
fp.VersionPatch, fp.VersionTweak); fp.VersionPatch, fp.VersionTweak);
fp.Components = cmJoin(cmMakeRange(dep.Components), ";"_s); fp.Components = cmJoin(cmMakeRange(dep.Components), ";"_s);
fp.RequiredComponents = fp.OptionalComponents =
std::set<std::string>{ dep.Components.begin(), dep.Components.end() }; std::set<std::string>{ dep.Components.begin(), dep.Components.end() };
fp.RequiredTargets = fp.OptionalComponents;
// TODO set hints // TODO set hints
@ -2793,10 +2814,14 @@ bool cmFindPackageCommand::CheckVersion(std::string const& config_file)
} }
// Verify that all required components are available. // Verify that all required components are available.
std::set<std::string> requiredComponents = this->RequiredComponents;
requiredComponents.insert(this->RequiredTargets.begin(),
this->RequiredTargets.end());
std::vector<std::string> missingComponents; std::vector<std::string> missingComponents;
std::set_difference(this->RequiredComponents.begin(), std::set_difference(requiredComponents.begin(),
this->RequiredComponents.end(), requiredComponents.end(), allComponents.begin(),
allComponents.begin(), allComponents.end(), allComponents.end(),
std::back_inserter(missingComponents)); std::back_inserter(missingComponents));
if (!missingComponents.empty()) { if (!missingComponents.empty()) {
result = false; result = false;
@ -2833,6 +2858,7 @@ bool cmFindPackageCommand::CheckVersion(std::string const& config_file)
} }
this->CpsReader = std::move(reader); this->CpsReader = std::move(reader);
this->CpsAppendices = std::move(appendices); this->CpsAppendices = std::move(appendices);
this->RequiredComponents = std::move(requiredComponents);
} }
} }
} else { } else {

View File

@ -254,6 +254,7 @@ private:
std::string Components; std::string Components;
std::set<std::string> RequiredComponents; std::set<std::string> RequiredComponents;
std::set<std::string> OptionalComponents; std::set<std::string> OptionalComponents;
std::set<std::string> RequiredTargets;
std::string DebugBuffer; std::string DebugBuffer;
struct ConfigName struct ConfigName

View File

@ -1728,6 +1728,15 @@ std::string const& cmMakefile::GetCurrentBinaryDirectory() const
return this->StateSnapshot.GetDirectory().GetCurrentBinary(); return this->StateSnapshot.GetDirectory().GetCurrentBinary();
} }
cmTarget* cmMakefile::FindImportedTarget(std::string const& name) const
{
auto const i = this->ImportedTargets.find(name);
if (i != this->ImportedTargets.end()) {
return i->second;
}
return nullptr;
}
std::vector<cmTarget*> cmMakefile::GetImportedTargets() const std::vector<cmTarget*> cmMakefile::GetImportedTargets() const
{ {
std::vector<cmTarget*> tgts; std::vector<cmTarget*> tgts;

View File

@ -477,6 +477,8 @@ public:
} }
std::vector<cmTarget*> GetImportedTargets() const; std::vector<cmTarget*> GetImportedTargets() const;
cmTarget* FindImportedTarget(std::string const& name) const;
cmTarget* FindLocalNonAliasTarget(std::string const& name) const; cmTarget* FindLocalNonAliasTarget(std::string const& name) const;
/** Find a target to use in place of the given name. The target /** Find a target to use in place of the given name. The target

View File

@ -3,8 +3,12 @@
"name": "Bar", "name": "Bar",
"cps_path": "@prefix@/cps", "cps_path": "@prefix@/cps",
"requires": { "requires": {
"Dep1": null, "Dep1": {
"Dep2": null "components": [ "Target" ]
},
"Dep2": {
"components": [ "Target" ]
}
}, },
"components": { "components": {
"Target1": { "Target1": {

View File

@ -0,0 +1,11 @@
CMake Error in cps/[Tt]ransitive[Mm]issing[Cc][Mm]ake\.cps:
Found package configuration file:
(
[^
]*/Tests/RunCMake/find_package-CPS/cmake/cmaketestpackage-config.cmake)+
but the following required targets were not found:[
]+CMakeTestPackage::DoesNotExist
Call Stack \(most recent call first\):
MissingTransitiveComponentCMake\.cmake:[0-9]+ \(find_package\)
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 4.0)
include(Setup.cmake)
###############################################################################
# Test depending on components of another package which are unavailable.
find_package(TransitiveMissingCMake REQUIRED)

View File

@ -1,4 +1,4 @@
CMake Error in cps/[Tt]ransitive[Mm]issing\.cps: CMake Error in cps/[Tt]ransitive[Mm]issing[Cc][Pp][Ss]\.cps:
Could not find a configuration file for package "ComponentTest" that is Could not find a configuration file for package "ComponentTest" that is
compatible with requested version ""\. compatible with requested version ""\.
@ -8,10 +8,11 @@ CMake Error in cps/[Tt]ransitive[Mm]issing\.cps:
]*/Tests/RunCMake/find_package-CPS/cps/[Cc]omponent[Tt]est\.cps, version: 1\.0)+ ]*/Tests/RunCMake/find_package-CPS/cps/[Cc]omponent[Tt]est\.cps, version: 1\.0)+
Call Stack \(most recent call first\): Call Stack \(most recent call first\):
MissingTransitiveComponent\.cmake:[0-9]+ \(find_package\) MissingTransitiveComponentCPS\.cmake:[0-9]+ \(find_package\)
CMakeLists\.txt:[0-9]+ \(include\) CMakeLists\.txt:[0-9]+ \(include\)
+ +
CMake Error at MissingTransitiveComponent\.cmake:[0-9]+ \(find_package\): CMake Error at MissingTransitiveComponentCPS\.cmake:[0-9]+ \(find_package\):
find_package could not find ComponentTest, required by TransitiveMissing\. find_package could not find ComponentTest, required by
TransitiveMissingCPS\.
Call Stack \(most recent call first\): Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\) CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -4,4 +4,4 @@ include(Setup.cmake)
############################################################################### ###############################################################################
# Test depending on components of another package which are unavailable. # Test depending on components of another package which are unavailable.
find_package(TransitiveMissing REQUIRED) find_package(TransitiveMissingCPS REQUIRED)

View File

@ -27,5 +27,6 @@ run_cmake(VersionLimit4)
run_cmake(MissingTransitiveDependency) run_cmake(MissingTransitiveDependency)
run_cmake(MissingComponent) run_cmake(MissingComponent)
run_cmake(MissingComponentDependency) run_cmake(MissingComponentDependency)
run_cmake(MissingTransitiveComponent) run_cmake(MissingTransitiveComponentCPS)
run_cmake(MissingTransitiveComponentCMake)
run_cmake(MissingTransitiveComponentDependency) run_cmake(MissingTransitiveComponentDependency)

View File

@ -0,0 +1 @@
# Test config file.

View File

@ -0,0 +1,11 @@
{
"cps_version": "0.13",
"name": "TransitiveMissingCMake",
"cps_path": "@prefix@/cps",
"requires": {
"CMakeTestPackage": {
"components": [ "DoesNotExist" ]
}
},
"components": {}
}

View File

@ -1,6 +1,6 @@
{ {
"cps_version": "0.13", "cps_version": "0.13",
"name": "TransitiveMissing", "name": "TransitiveMissingCPS",
"cps_path": "@prefix@/cps", "cps_path": "@prefix@/cps",
"requires": { "requires": {
"ComponentTest": { "ComponentTest": {