find_package: Find CPS dependencies

Implement finding dependencies of CPS packages. This is done by setting
up additional `cmFindPackageCommand` instances which are used to look
for a parent package's dependencies.
This commit is contained in:
Matthew Woehlke
2024-12-17 12:50:33 -05:00
committed by Brad King
parent e6d5a518b1
commit 42de87cbae
16 changed files with 342 additions and 46 deletions

View File

@@ -23,6 +23,7 @@
#include "cmAlgorithms.h"
#include "cmDependencyProvider.h"
#include "cmExecutionStatus.h"
#include "cmExperimental.h"
#include "cmList.h"
#include "cmListFileCache.h"
@@ -55,8 +56,6 @@
# endif
#endif
class cmExecutionStatus;
namespace {
using pdt = cmFindPackageCommand::PackageDescriptionType;
@@ -560,6 +559,41 @@ void cmFindPackageCommand::AppendSearchPathGroups()
this->LabeledPaths.emplace(PathLabel::SystemRegistry, cmSearchPath{ this });
}
void cmFindPackageCommand::InheritOptions(cmFindPackageCommand* other)
{
this->RequiredCMakeVersion = other->RequiredCMakeVersion;
this->LibraryArchitecture = other->LibraryArchitecture;
this->UseLib32Paths = other->UseLib32Paths;
this->UseLib64Paths = other->UseLib64Paths;
this->UseLibx32Paths = other->UseLibx32Paths;
this->NoUserRegistry = other->NoUserRegistry;
this->NoSystemRegistry = other->NoSystemRegistry;
this->UseRealPath = other->UseRealPath;
this->SortOrder = other->SortOrder;
this->SortDirection = other->SortDirection;
this->GlobalScope = other->GlobalScope;
this->RegistryView = other->RegistryView;
this->NoDefaultPath = other->NoDefaultPath;
this->NoPackageRootPath = other->NoPackageRootPath;
this->NoCMakePath = other->NoCMakePath;
this->NoCMakeEnvironmentPath = other->NoCMakeEnvironmentPath;
this->NoSystemEnvironmentPath = other->NoSystemEnvironmentPath;
this->NoCMakeSystemPath = other->NoCMakeSystemPath;
this->NoCMakeInstallPath = other->NoCMakeInstallPath;
this->FindRootPathMode = other->FindRootPathMode;
this->SearchFrameworkLast = other->SearchFrameworkLast;
this->SearchFrameworkFirst = other->SearchFrameworkFirst;
this->SearchFrameworkOnly = other->SearchFrameworkOnly;
this->SearchAppBundleLast = other->SearchAppBundleLast;
this->SearchAppBundleFirst = other->SearchAppBundleFirst;
this->SearchAppBundleOnly = other->SearchAppBundleOnly;
this->SearchPathSuffixes = other->SearchPathSuffixes;
this->Quiet = other->Quiet;
}
bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
{
if (args.empty()) {
@@ -645,7 +679,7 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
this->SortDirection = (*sd == "ASC") ? Asc : Dec;
}
// Find what search path locations have been enabled/disable
// Find what search path locations have been enabled/disable.
this->SelectDefaultSearchModes();
// Find the current root path mode.
@@ -1510,12 +1544,7 @@ bool cmFindPackageCommand::HandlePackageMode(
if (this->CpsReader) {
// The package has been found.
found = true;
// Import targets.
cmPackageInfoReader* const reader = this->CpsReader.get();
result = reader->ImportTargets(this->Makefile, this->Status) &&
this->ImportTargetConfigurations(this->FileFound, reader) &&
this->ImportAppendices(this->FileFound);
result = this->ReadPackage();
} else if (this->ReadListFile(this->FileFound, DoPolicyScope)) {
// The package has been found.
found = true;
@@ -1816,6 +1845,36 @@ bool cmFindPackageCommand::FindEnvironmentConfig()
});
}
cmFindPackageCommand::AppendixMap cmFindPackageCommand::FindAppendices(
std::string const& base) const
{
AppendixMap appendices;
// Find package appendices.
cmsys::Glob glob;
glob.RecurseOff();
if (glob.FindFiles(cmStrCat(cmSystemTools::GetFilenamePath(base), "/"_s,
cmSystemTools::GetFilenameWithoutExtension(base),
"[-:]*.[Cc][Pp][Ss]"_s))) {
// Check glob results for valid appendices.
for (std::string const& extra : glob.GetFiles()) {
// Exclude configuration-specific files for now; we look at them later
// when we load their respective configuration-agnostic appendices.
if (extra.find('@') != std::string::npos) {
continue;
}
std::unique_ptr<cmPackageInfoReader> reader =
cmPackageInfoReader::Read(extra, this->CpsReader.get());
if (reader && reader->GetName() == this->Name) {
appendices.emplace(extra, std::move(reader));
}
}
}
return appendices;
}
bool cmFindPackageCommand::ReadListFile(const std::string& f,
const PolicyScopeRule psr)
{
@@ -1833,53 +1892,114 @@ bool cmFindPackageCommand::ReadListFile(const std::string& f,
return false;
}
bool cmFindPackageCommand::ImportTargetConfigurations(
std::string const& base, cmPackageInfoReader* parent)
bool cmFindPackageCommand::ReadPackage()
{
// Find supplemental configuration files.
cmsys::Glob glob;
glob.RecurseOff();
if (glob.FindFiles(cmStrCat(cmSystemTools::GetFilenamePath(base), "/"_s,
cmSystemTools::GetFilenameWithoutExtension(base),
"@*.[Cc][Pp][Ss]"_s))) {
// Resolve any transitive dependencies.
if (!FindPackageDependencies(this->FileFound, *this->CpsReader,
this->Required)) {
return false;
}
// Try to read supplemental data from each file found.
for (std::string const& extra : glob.GetFiles()) {
std::unique_ptr<cmPackageInfoReader> const& reader =
cmPackageInfoReader::Read(extra, parent);
if (reader && reader->GetName() == this->Name) {
if (!reader->ImportTargetConfigurations(this->Makefile,
this->Status)) {
return false;
}
cmMakefile::CallRAII scope{ this->Makefile, this->FileFound, this->Status };
// Locate appendices.
cmFindPackageCommand::AppendixMap appendices =
this->FindAppendices(this->FileFound);
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) {
return false;
}
iter = appendices.erase(iter);
} else {
++iter;
}
}
// Import targets from root file.
if (!this->ImportPackageTargets(this->FileFound, *this->CpsReader)) {
return false;
}
// Import targets from appendices.
for (auto const& appendix : appendices) {
cmMakefile::CallRAII appendixScope{ this->Makefile, appendix.first,
this->Status };
if (!this->ImportPackageTargets(appendix.first, *appendix.second)) {
return false;
}
}
return true;
}
bool cmFindPackageCommand::ImportAppendices(std::string const& base)
bool cmFindPackageCommand::FindPackageDependencies(
std::string const& fileName, cmPackageInfoReader const& reader,
bool required)
{
// Find package appendices.
// Get package requirements.
for (cmPackageRequirement const& dep : reader.GetRequirements()) {
cmExecutionStatus status{ *this->Makefile };
cmMakefile::CallRAII scope{ this->Makefile, fileName, status };
// For each requirement, set up a nested instance to find it.
cmFindPackageCommand fp{ status };
fp.InheritOptions(this);
fp.Name = dep.Name;
fp.Required = required;
fp.UseFindModules = false;
fp.UseCpsFiles = true;
fp.Version = dep.Version;
fp.VersionComplete = dep.Version;
fp.VersionCount =
parseVersion(fp.Version, fp.VersionMajor, fp.VersionMinor,
fp.VersionPatch, fp.VersionTweak);
fp.Components = cmJoin(cmMakeRange(dep.Components), ";"_s);
fp.RequiredComponents =
std::set<std::string>{ dep.Components.begin(), dep.Components.end() };
// TODO set hints
// Try to find the requirement; fail if we can't.
if (!fp.FindPackage() || fp.FileFound.empty()) {
return false;
}
}
// All requirements (if any) were found.
return true;
}
bool cmFindPackageCommand::ImportPackageTargets(std::string const& fileName,
cmPackageInfoReader& reader)
{
// Import base file.
if (!reader.ImportTargets(this->Makefile, this->Status)) {
return false;
}
// Find supplemental configuration files.
cmsys::Glob glob;
glob.RecurseOff();
if (glob.FindFiles(cmStrCat(cmSystemTools::GetFilenamePath(base), "/"_s,
cmSystemTools::GetFilenameWithoutExtension(base),
"[-:]*.[Cc][Pp][Ss]"_s))) {
if (glob.FindFiles(
cmStrCat(cmSystemTools::GetFilenamePath(fileName), "/"_s,
cmSystemTools::GetFilenameWithoutExtension(fileName),
"@*.[Cc][Pp][Ss]"_s))) {
// Try to read supplemental data from each appendix file found.
// Try to read supplemental data from each file found.
for (std::string const& extra : glob.GetFiles()) {
// This loop should not consider configuration-specific files.
if (extra.find('@') != std::string::npos) {
continue;
}
std::unique_ptr<cmPackageInfoReader> const& reader =
cmPackageInfoReader::Read(extra, this->CpsReader.get());
if (reader && reader->GetName() == this->Name) {
if (!reader->ImportTargets(this->Makefile, this->Status) ||
!this->ImportTargetConfigurations(extra, reader.get())) {
std::unique_ptr<cmPackageInfoReader> configReader =
cmPackageInfoReader::Read(extra, &reader);
if (configReader && configReader->GetName() == this->Name) {
if (!configReader->ImportTargetConfigurations(this->Makefile,
this->Status)) {
return false;
}
}

View File

@@ -92,10 +92,12 @@ private:
static PathLabel SystemRegistry;
};
void InheritOptions(cmFindPackageCommand* other);
// Try to find a package, assuming most state has already been set up. This
// is used for recursive dependency solving, particularly when importing
// packages via CPS. Bypasses providers if argsForProvider is empty.
bool FindPackage(std::vector<std::string> const& argsForProvider);
bool FindPackage(std::vector<std::string> const& argsForProvider = {});
bool FindPackageUsingModuleMode();
bool FindPackageUsingConfigMode();
@@ -137,9 +139,17 @@ private:
DoPolicyScope
};
bool ReadListFile(const std::string& f, PolicyScopeRule psr);
bool ImportTargetConfigurations(std::string const& base,
cmPackageInfoReader* parent);
bool ImportAppendices(std::string const& base);
bool ReadPackage();
using AppendixMap =
std::map<std::string, std::unique_ptr<cmPackageInfoReader>>;
AppendixMap FindAppendices(std::string const& base) const;
bool FindPackageDependencies(std::string const& fileName,
cmPackageInfoReader const& reader,
bool required);
bool ImportPackageTargets(std::string const& fileName,
cmPackageInfoReader& reader);
void StoreVersionFound();
void SetConfigDirCacheVariable(const std::string& value);

View File

@@ -464,6 +464,23 @@ std::vector<unsigned> cmPackageInfoReader::ParseVersion() const
return result;
}
std::vector<cmPackageRequirement> cmPackageInfoReader::GetRequirements() const
{
std::vector<cmPackageRequirement> requirements;
auto const& requirementObjects = this->Data["requires"];
for (auto ri = requirementObjects.begin(), re = requirementObjects.end();
ri != re; ++ri) {
cmPackageRequirement r{ ri.name(), (*ri)["version"].asString(),
ReadList(*ri, "components"),
ReadList(*ri, "hints") };
requirements.emplace_back(std::move(r));
}
return requirements;
}
std::string cmPackageInfoReader::ResolvePath(std::string path) const
{
cmSystemTools::ConvertToUnixSlashes(path);

View File

@@ -20,6 +20,14 @@ class cmExecutionStatus;
class cmMakefile;
class cmTarget;
struct cmPackageRequirement
{
std::string Name;
std::string Version;
std::vector<std::string> Components;
std::vector<std::string> Hints;
};
/** \class cmPackageInfoReader
* \brief Read and parse CPS files.
*
@@ -41,6 +49,8 @@ public:
/// version is specified.
std::vector<unsigned> ParseVersion() const;
std::vector<cmPackageRequirement> GetRequirements() const;
/// Create targets for components specified in the CPS file.
bool ImportTargets(cmMakefile* makefile, cmExecutionStatus& status);

View File

@@ -161,3 +161,27 @@ endif()
if(C_CXXONLY_H)
message(SEND_ERROR "cmincludetest/cxxonly.h unexpectedly found in C mode ?!")
endif()
###############################################################################
# Find a package that has dependencies.
find_package(Bar)
if(NOT Bar_FOUND)
message(SEND_ERROR "Bar not found !")
elseif(NOT Dep1_FOUND)
message(SEND_ERROR "Bar's Dep1 not found !")
elseif(NOT Dep2_FOUND)
message(SEND_ERROR "Bar's Dep2 not found !")
elseif(NOT Dep3_FOUND)
message(SEND_ERROR "Bar's Dep3 not found !")
elseif(NOT TARGET Dep1::Target)
message(SEND_ERROR "Dep1::Target missing !")
elseif(NOT TARGET Dep2::Target)
message(SEND_ERROR "Dep2::Target missing !")
elseif(NOT TARGET Dep3::Target)
message(SEND_ERROR "Dep3::Target missing !")
elseif(NOT TARGET Bar::Target1)
message(SEND_ERROR "Bar::Target1 missing !")
elseif(NOT TARGET Bar::Target2)
message(SEND_ERROR "Bar::Target2 missing !")
endif()

View File

@@ -0,0 +1,3 @@
set(Dep2_FOUND TRUE)
add_library(Dep2::Target INTERFACE IMPORTED)

View File

@@ -0,0 +1,13 @@
{
"cps_version": "0.13",
"name": "Bar",
"requires": {
"Dep3": null
},
"components": {
"Target2": {
"type": "interface",
"requires": [ "Dep3:Target" ]
}
}
}

View File

@@ -0,0 +1,15 @@
{
"cps_version": "0.13",
"name": "Bar",
"cps_path": "@prefix@/cps",
"requires": {
"Dep1": null,
"Dep2": null
},
"components": {
"Target1": {
"type": "interface",
"requires": [ "Dep1:Target", "Dep2:Target" ]
}
}
}

View File

@@ -0,0 +1,10 @@
{
"cps_version": "0.13",
"name": "Dep1",
"cps_path": "@prefix@/cps",
"components": {
"Target": {
"type": "interface"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"cps_version": "0.13",
"name": "Dep3",
"cps_path": "@prefix@/cps",
"components": {
"Target": {
"type": "interface"
}
}
}

View File

@@ -0,0 +1,25 @@
CMake Warning \(dev\) at MissingTransitiveDependency\.cmake:[0-9]+ \(find_package\):
CMake's support for importing package information in the Common Package
Specification format \(via find_package\) is experimental\. It is meant only
for experimentation and feedback to CMake developers.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
This warning is for project developers. Use -Wno-dev to suppress it.
+
CMake Error in cps/[Ss]till[Ii]ncomplete\.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/[Ii]ncomplete\.cps
MissingTransitiveDependency\.cmake:[0-9]+ \(find_package\)
CMakeLists\.txt:[0-9]+ \(include\)

View File

@@ -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 finding a package that is missing dependencies.
find_package(Incomplete REQUIRED)

View File

@@ -22,6 +22,7 @@ run_cmake_with_options(MissingConfigDebugPkg --debug-find-pkg=NotHere)
run_cmake(MissingConfigOneName)
run_cmake(MissingConfigRequired)
run_cmake(MissingConfigVersion)
run_cmake(MissingTransitiveDependency)
run_cmake(MixedModeOptions)
run_cmake_with_options(ModuleModeDebugPkg --debug-find-pkg=Foo,Zot)
run_cmake(PackageRoot)

View File

@@ -0,0 +1,9 @@
{
"cps_version": "0.13",
"name": "Incomplete",
"cps_path": "@prefix@/cps",
"requires": {
"StillIncomplete": null
},
"components": {}
}

View File

@@ -0,0 +1,9 @@
{
"cps_version": "0.13",
"name": "StillIncomplete",
"cps_path": "@prefix@/cps",
"requires": {
"DoesNotExist": null
},
"components": {}
}