Merge topic 'find-cps-components'

894f256a12 find_package: Find CPS components
ac20cd43c0 find_package: Fix reporting of transitive dependency errors

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !10262
This commit is contained in:
Brad King
2025-02-03 15:06:42 +00:00
committed by Kitware Robot
34 changed files with 494 additions and 44 deletions
+3 -3
View File
@@ -88,9 +88,9 @@ The command has a few modes by which it searches for packages:
mode", "package configuration files", and so forth refer equally to both
CPS and CMake-script files. However, some features of ``find_package``
are not supported at this time when a CPS file is found. In particular,
no attempt to validate whether a candidate ``.cps`` file matches
``VERSION`` or ``COMPONENTS`` requirements is performed at this time.
(We expect to implement these features in the near future.)
if a ``VERSION`` requirement is specified, only ``.cps`` files which do not
provide version information will be rejected. (We expect to implement
proper version validation in the near future.)
Search is implemented in a manner that will tend to prefer |CPS| files
over CMake-script config files in most cases. Specifying ``CONFIGS``
+105 -38
View File
@@ -1846,7 +1846,7 @@ bool cmFindPackageCommand::FindEnvironmentConfig()
}
cmFindPackageCommand::AppendixMap cmFindPackageCommand::FindAppendices(
std::string const& base) const
std::string const& base, cmPackageInfoReader const& baseReader) const
{
AppendixMap appendices;
@@ -1865,9 +1865,11 @@ cmFindPackageCommand::AppendixMap cmFindPackageCommand::FindAppendices(
}
std::unique_ptr<cmPackageInfoReader> reader =
cmPackageInfoReader::Read(extra, this->CpsReader.get());
cmPackageInfoReader::Read(extra, &baseReader);
if (reader && reader->GetName() == this->Name) {
appendices.emplace(extra, std::move(reader));
std::vector<std::string> components = reader->GetComponentNames();
Appendix appendix{ std::move(reader), std::move(components) };
appendices.emplace(extra, std::move(appendix));
}
}
}
@@ -1894,27 +1896,55 @@ bool cmFindPackageCommand::ReadListFile(std::string const& f,
bool cmFindPackageCommand::ReadPackage()
{
// Resolve any transitive dependencies.
// Resolve any transitive dependencies for the root file.
if (!FindPackageDependencies(this->FileFound, *this->CpsReader,
this->Required)) {
return false;
}
auto const hasComponentsRequested =
!this->RequiredComponents.empty() || !this->OptionalComponents.empty();
cmMakefile::CallRAII scope{ this->Makefile, this->FileFound, this->Status };
// Locate appendices.
cmFindPackageCommand::AppendixMap appendices =
this->FindAppendices(this->FileFound);
// Loop over appendices.
auto iter = this->CpsAppendices.begin();
while (iter != this->CpsAppendices.end()) {
bool required = false;
bool important = false;
auto iter = appendices.begin();
while (iter != appendices.end()) {
bool providesRequiredComponents = false; // TODO
bool required = providesRequiredComponents && this->Required;
if (!this->FindPackageDependencies(iter->first, *iter->second, required)) {
if (providesRequiredComponents) {
// Check if this appendix provides any requested components.
if (hasComponentsRequested) {
auto providesAny = [&iter](
std::set<std::string> const& desiredComponents) {
return std::any_of(iter->second.Components.begin(),
iter->second.Components.end(),
[&desiredComponents](std::string const& component) {
return cm::contains(desiredComponents, component);
});
};
if (providesAny(this->RequiredComponents)) {
important = true;
required = this->Required;
} else if (!providesAny(this->OptionalComponents)) {
// This appendix doesn't provide any requested components; remove it
// from the set to be imported.
iter = this->CpsAppendices.erase(iter);
continue;
}
}
// Resolve any transitive dependencies for the appendix.
if (!this->FindPackageDependencies(iter->first, iter->second, required)) {
if (important) {
// Some dependencies are missing, and we need(ed) this appendix; fail.
return false;
}
iter = appendices.erase(iter);
// Some dependencies are missing, but we don't need this appendix; remove
// it from the set to be imported.
iter = this->CpsAppendices.erase(iter);
} else {
++iter;
}
@@ -1926,10 +1956,11 @@ bool cmFindPackageCommand::ReadPackage()
}
// Import targets from appendices.
for (auto const& appendix : appendices) {
// NOLINTNEXTLINE(readability-use-anyofallof)
for (auto const& appendix : this->CpsAppendices) {
cmMakefile::CallRAII appendixScope{ this->Makefile, appendix.first,
this->Status };
if (!this->ImportPackageTargets(appendix.first, *appendix.second)) {
if (!this->ImportPackageTargets(appendix.first, appendix.second)) {
return false;
}
}
@@ -1969,6 +2000,8 @@ bool cmFindPackageCommand::FindPackageDependencies(
// Try to find the requirement; fail if we can't.
if (!fp.FindPackage() || fp.FileFound.empty()) {
this->SetError(cmStrCat("could not find "_s, dep.Name,
", required by "_s, this->Name, '.'));
return false;
}
}
@@ -2687,29 +2720,63 @@ bool cmFindPackageCommand::CheckVersion(std::string const& config_file)
cm::optional<std::string> cpsVersion = reader->GetVersion();
if (cpsVersion) {
// TODO: Implement version check for CPS
this->VersionFound = (version = std::move(*cpsVersion));
std::vector<unsigned> const& versionParts = reader->ParseVersion();
this->VersionFoundCount = static_cast<unsigned>(versionParts.size());
switch (this->VersionFoundCount) {
case 4:
this->VersionFoundTweak = versionParts[3];
CM_FALLTHROUGH;
case 3:
this->VersionFoundPatch = versionParts[2];
CM_FALLTHROUGH;
case 2:
this->VersionFoundMinor = versionParts[1];
CM_FALLTHROUGH;
case 1:
this->VersionFoundMajor = versionParts[0];
CM_FALLTHROUGH;
default:
break;
}
result = true;
} else {
result = this->Version.empty();
}
if (result) {
// Locate appendices.
cmFindPackageCommand::AppendixMap appendices =
this->FindAppendices(config_file, *reader);
// Collect available components.
std::set<std::string> allComponents;
std::vector<std::string> const& rootComponents =
reader->GetComponentNames();
allComponents.insert(rootComponents.begin(), rootComponents.end());
for (auto const& appendix : appendices) {
allComponents.insert(appendix.second.Components.begin(),
appendix.second.Components.end());
}
// Verify that all required components are available.
std::vector<std::string> missingComponents;
std::set_difference(this->RequiredComponents.begin(),
this->RequiredComponents.end(),
allComponents.begin(), allComponents.end(),
std::back_inserter(missingComponents));
if (!missingComponents.empty()) {
result = false;
}
if (result && cpsVersion) {
this->VersionFound = (version = std::move(*cpsVersion));
std::vector<unsigned> const& versionParts = reader->ParseVersion();
this->VersionFoundCount = static_cast<unsigned>(versionParts.size());
switch (this->VersionFoundCount) {
case 4:
this->VersionFoundTweak = versionParts[3];
CM_FALLTHROUGH;
case 3:
this->VersionFoundPatch = versionParts[2];
CM_FALLTHROUGH;
case 2:
this->VersionFoundMinor = versionParts[1];
CM_FALLTHROUGH;
case 1:
this->VersionFoundMajor = versionParts[0];
CM_FALLTHROUGH;
default:
break;
}
}
this->CpsReader = std::move(reader);
this->CpsAppendices = std::move(appendices);
}
this->CpsReader = std::move(reader);
result = true;
}
} else {
// Get the filename without the .cmake extension.
+11 -3
View File
@@ -141,9 +141,16 @@ private:
bool ReadListFile(std::string const& f, PolicyScopeRule psr);
bool ReadPackage();
using AppendixMap =
std::map<std::string, std::unique_ptr<cmPackageInfoReader>>;
AppendixMap FindAppendices(std::string const& base) const;
struct Appendix
{
std::unique_ptr<cmPackageInfoReader> Reader;
std::vector<std::string> Components;
operator cmPackageInfoReader&() const { return *this->Reader; }
};
using AppendixMap = std::map<std::string, Appendix>;
AppendixMap FindAppendices(std::string const& base,
cmPackageInfoReader const& baseReader) const;
bool FindPackageDependencies(std::string const& fileName,
cmPackageInfoReader const& reader,
bool required);
@@ -299,6 +306,7 @@ private:
std::vector<ConfigFileInfo> ConsideredConfigs;
std::unique_ptr<cmPackageInfoReader> CpsReader;
AppendixMap CpsAppendices;
friend struct std::hash<ConfigFileInfo>;
};
+12
View File
@@ -481,6 +481,18 @@ std::vector<cmPackageRequirement> cmPackageInfoReader::GetRequirements() const
return requirements;
}
std::vector<std::string> cmPackageInfoReader::GetComponentNames() const
{
std::vector<std::string> componentNames;
Json::Value const& components = this->Data["components"];
for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) {
componentNames.emplace_back(ci.name());
}
return componentNames;
}
std::string cmPackageInfoReader::ResolvePath(std::string path) const
{
cmSystemTools::ConvertToUnixSlashes(path);
+1
View File
@@ -50,6 +50,7 @@ public:
std::vector<unsigned> ParseVersion() const;
std::vector<cmPackageRequirement> GetRequirements() const;
std::vector<std::string> GetComponentNames() const;
/// Create targets for components specified in the CPS file.
bool ImportTargets(cmMakefile* makefile, cmExecutionStatus& status);
+46
View File
@@ -185,3 +185,49 @@ elseif(NOT TARGET Bar::Target1)
elseif(NOT TARGET Bar::Target2)
message(SEND_ERROR "Bar::Target2 missing !")
endif()
###############################################################################
# Test requesting components from a package.
find_package(ComponentTest
COMPONENTS Target1 Target2
OPTIONAL_COMPONENTS Target4 Target6)
if(NOT ComponentTest_FOUND)
message(SEND_ERROR "ComponentTest not found !")
elseif(NOT TARGET ComponentTest::Target1)
message(SEND_ERROR "ComponentTest::Target1 missing !")
elseif(NOT TARGET ComponentTest::Target2)
message(SEND_ERROR "ComponentTest::Target2 missing !")
elseif(NOT TARGET ComponentTest::Target3)
message(SEND_ERROR "ComponentTest::Target3 missing !")
elseif(NOT TARGET ComponentTest::Target4)
message(SEND_ERROR "ComponentTest::Target4 missing !")
elseif(NOT TARGET ComponentTest::Target5)
message(SEND_ERROR "ComponentTest::Target5 missing !")
elseif(TARGET ComponentTest::Target6)
message(SEND_ERROR "ComponentTest::Target6 exists ?!")
elseif(TARGET ComponentTest::Target7)
message(SEND_ERROR "ComponentTest::Target7 exists ?!")
elseif(TARGET ComponentTest::Target8)
message(SEND_ERROR "ComponentTest::Target8 exists ?!")
endif()
###############################################################################
# Test requesting components from a dependency.
find_package(TransitiveTest)
if(NOT TransitiveTest_FOUND)
message(SEND_ERROR "TransitiveTest not found !")
elseif(NOT TransitiveDep_FOUND)
message(SEND_ERROR "TransitiveTest's TransitiveDep not found !")
elseif(NOT TARGET TransitiveDep::Target1)
message(SEND_ERROR "TransitiveDep::Target1 missing !")
elseif(NOT TARGET TransitiveDep::Target2)
message(SEND_ERROR "TransitiveDep::Target2 missing !")
elseif(NOT TARGET TransitiveDep::Target3)
message(SEND_ERROR "TransitiveDep::Target3 missing !")
elseif(TARGET TransitiveDep::Target4)
message(SEND_ERROR "TransitiveDep::Target4 exists ?!")
elseif(TARGET TransitiveDep::Target5)
message(SEND_ERROR "TransitiveDep::Target5 exists ?!")
endif()
@@ -0,0 +1,12 @@
{
"cps_version": "0.13",
"name": "ComponentTest",
"components": {
"Target2": {
"type": "interface"
},
"Target3": {
"type": "interface"
}
}
}
@@ -0,0 +1,12 @@
{
"cps_version": "0.13",
"name": "ComponentTest",
"components": {
"Target4": {
"type": "interface"
},
"Target5": {
"type": "interface"
}
}
}
@@ -0,0 +1,15 @@
{
"cps_version": "0.13",
"name": "ComponentTest",
"requires": {
"DoesNotExist": null
},
"components": {
"Target6": {
"type": "interface"
},
"Target7": {
"type": "interface"
}
}
}
@@ -0,0 +1,9 @@
{
"cps_version": "0.13",
"name": "ComponentTest",
"components": {
"Target8": {
"type": "interface"
}
}
}
@@ -0,0 +1,10 @@
{
"cps_version": "0.13",
"name": "ComponentTest",
"cps_path": "@prefix@/cps",
"components": {
"Target1": {
"type": "interface"
}
}
}
@@ -0,0 +1,12 @@
{
"cps_version": "0.13",
"name": "TransitiveDep",
"components": {
"Target2": {
"type": "interface"
},
"Target3": {
"type": "interface"
}
}
}
@@ -0,0 +1,9 @@
{
"cps_version": "0.13",
"name": "TransitiveDep",
"components": {
"Target4": {
"type": "interface"
}
}
}
@@ -0,0 +1,12 @@
{
"cps_version": "0.13",
"name": "ComponentTest",
"requires": {
"DoesNotExist": null
},
"components": {
"Target5": {
"type": "interface"
}
}
}
@@ -0,0 +1,10 @@
{
"cps_version": "0.13",
"name": "TransitiveDep",
"cps_path": "@prefix@/cps",
"components": {
"Target1": {
"type": "interface"
}
}
}
@@ -0,0 +1,11 @@
{
"cps_version": "0.13",
"name": "TransitiveTest",
"cps_path": "@prefix@/cps",
"requires": {
"TransitiveDep": {
"components": [ "Target2" ]
}
},
"components": {}
}
@@ -0,0 +1 @@
1
@@ -0,0 +1,11 @@
CMake Error at MissingComponent.cmake:[0-9]+ \(find_package\):
Could not find a configuration file for package "ComponentTest" that is
compatible with requested version ""\.
The following configuration files were considered but not accepted:
(
[^
]*/Tests/RunCMake/find_package/cps/[Cc]omponent[Tt]est\.cps, version: unknown)+
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.31)
set(CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES "e82e467b-f997-4464-8ace-b00808fff261")
# Protect tests from running inside the default install prefix.
set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/NotDefaultPrefix")
# Disable built-in search paths.
set(CMAKE_FIND_USE_PACKAGE_ROOT_PATH OFF)
set(CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH OFF)
set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH OFF)
set(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH OFF)
set(CMAKE_FIND_USE_INSTALL_PREFIX OFF)
set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
###############################################################################
# Test requesting unavailable components from a package.
find_package(ComponentTest REQUIRED COMPONENTS DoesNotExist)
@@ -0,0 +1,22 @@
CMake Error in cps/componenttest-extra\.cps:
Could not find a package configuration file provided by "DoesNotExist" with
any of the following names:
DoesNotExist\.cps
doesnotexist\.cps
DoesNotExistConfig\.cmake
doesnotexist-config\.cmake
Add the installation prefix of "DoesNotExist" to CMAKE_PREFIX_PATH or set
"DoesNotExist_DIR" to a directory containing one of the above files\. If
"DoesNotExist" provides a separate development package or SDK, be sure it
has been installed\.
Call Stack \(most recent call first\):
cps/[Cc]omponent[Tt]est\.cps
MissingComponentDependency\.cmake:[0-9]+ \(find_package\)
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at MissingComponentDependency.cmake:[0-9]+ \(find_package\):
find_package could not find DoesNotExist, required by ComponentTest\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.31)
set(CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES "e82e467b-f997-4464-8ace-b00808fff261")
# Protect tests from running inside the default install prefix.
set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/NotDefaultPrefix")
# Disable built-in search paths.
set(CMAKE_FIND_USE_PACKAGE_ROOT_PATH OFF)
set(CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH OFF)
set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH OFF)
set(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH OFF)
set(CMAKE_FIND_USE_INSTALL_PREFIX OFF)
set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
###############################################################################
# Test requesting components with missing dependencies from a package.
find_package(ComponentTest REQUIRED COMPONENTS Incomplete)
@@ -0,0 +1,17 @@
CMake Error in cps/[Tt]ransitive[Mm]issing\.cps:
Could not find a configuration file for package "ComponentTest" that is
compatible with requested version ""\.
The following configuration files were considered but not accepted:
(
[^
]*/Tests/RunCMake/find_package/cps/[Cc]omponent[Tt]est\.cps, version: unknown)+
Call Stack \(most recent call first\):
MissingTransitiveComponent\.cmake:[0-9]+ \(find_package\)
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at MissingTransitiveComponent\.cmake:[0-9]+ \(find_package\):
find_package could not find ComponentTest, required by TransitiveMissing\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.31)
set(CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES "e82e467b-f997-4464-8ace-b00808fff261")
# Protect tests from running inside the default install prefix.
set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/NotDefaultPrefix")
# Disable built-in search paths.
set(CMAKE_FIND_USE_PACKAGE_ROOT_PATH OFF)
set(CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH OFF)
set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH OFF)
set(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH OFF)
set(CMAKE_FIND_USE_INSTALL_PREFIX OFF)
set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
###############################################################################
# Test depending on components of another package which are unavailable.
find_package(TransitiveMissing REQUIRED)
@@ -0,0 +1,24 @@
CMake Error in cps/componenttest-extra\.cps:
Could not find a package configuration file provided by "DoesNotExist" with
any of the following names:
DoesNotExist\.cps
doesnotexist\.cps
DoesNotExistConfig\.cmake
doesnotexist-config\.cmake
Add the installation prefix of "DoesNotExist" to CMAKE_PREFIX_PATH or set
"DoesNotExist_DIR" to a directory containing one of the above files\. If
"DoesNotExist" provides a separate development package or SDK, be sure it
has been installed\.
Call Stack \(most recent call first\):
cps/[Cc]omponent[Tt]est\.cps
cps/[Tt]ransitive[Ii]ncomplete\.cps
MissingTransitiveComponentDependency\.cmake:[0-9]+ \(find_package\)
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at MissingTransitiveComponentDependency\.cmake:[0-9]+ \(find_package\):
find_package could not find ComponentTest, required by
TransitiveIncomplete\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.31)
set(CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES "e82e467b-f997-4464-8ace-b00808fff261")
# Protect tests from running inside the default install prefix.
set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/NotDefaultPrefix")
# Disable built-in search paths.
set(CMAKE_FIND_USE_PACKAGE_ROOT_PATH OFF)
set(CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH OFF)
set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH OFF)
set(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH OFF)
set(CMAKE_FIND_USE_INSTALL_PREFIX OFF)
set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
###############################################################################
# Test depending on components of another package which are missing
# dependencies.
find_package(TransitiveIncomplete REQUIRED)
@@ -23,3 +23,8 @@ Call Stack \(most recent call first\):
cps/[Ii]ncomplete\.cps
MissingTransitiveDependency\.cmake:[0-9]+ \(find_package\)
CMakeLists\.txt:[0-9]+ \(include\)
+
CMake Error at MissingTransitiveDependency\.cmake:[0-9]+ \(find_package\):
find_package could not find StillIncomplete, required by Incomplete\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -23,6 +23,10 @@ run_cmake(MissingConfigOneName)
run_cmake(MissingConfigRequired)
run_cmake(MissingConfigVersion)
run_cmake(MissingTransitiveDependency)
run_cmake(MissingComponent)
run_cmake(MissingComponentDependency)
run_cmake(MissingTransitiveComponent)
run_cmake(MissingTransitiveComponentDependency)
run_cmake(MixedModeOptions)
run_cmake_with_options(ModuleModeDebugPkg --debug-find-pkg=Foo,Zot)
run_cmake(PackageRoot)
@@ -0,0 +1,12 @@
{
"cps_version": "0.13",
"name": "ComponentTest",
"requires": {
"DoesNotExist": null
},
"components": {
"Incomplete": {
"type": "interface"
}
}
}
@@ -0,0 +1,6 @@
{
"cps_version": "0.13",
"name": "ComponentTest",
"cps_path": "@prefix@/cps",
"components": {}
}
@@ -0,0 +1,11 @@
{
"cps_version": "0.13",
"name": "TransitiveIncomplete",
"cps_path": "@prefix@/cps",
"requires": {
"ComponentTest": {
"components": [ "Incomplete" ]
}
},
"components": {}
}
@@ -0,0 +1,11 @@
{
"cps_version": "0.13",
"name": "TransitiveMissing",
"cps_path": "@prefix@/cps",
"requires": {
"ComponentTest": {
"components": [ "DoesNotExist" ]
}
},
"components": {}
}