1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-24 11:32:44 +08:00

find_package: Actually import .cps files

Implement logic (partly adapted from the 2023 proof-of-concept) to
actually parse CPS files and generate imported targets. Implement logic
to locate and load supplemental files. Adjust prefix handling to require
that the CPS file provides sufficient information to translate the
prefix placeholder into a meaningful path. (Note that this corresponds
to a change in the specification.)
This commit is contained in:
Matthew Woehlke
2024-11-19 13:35:39 -05:00
committed by Brad King
parent 91c31ada23
commit 25cc83428e
5 changed files with 506 additions and 7 deletions

View File

@@ -17,6 +17,7 @@
#include "cmsys/Directory.hxx"
#include "cmsys/FStream.hxx"
#include "cmsys/Glob.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cmsys/String.h"
@@ -1504,10 +1505,14 @@ bool cmFindPackageCommand::HandlePackageMode(
// Parse the configuration file.
if (this->CpsReader) {
// FIXME TODO
// 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);
} else if (this->ReadListFile(this->FileFound, DoPolicyScope)) {
// The package has been found.
found = true;
@@ -1825,6 +1830,62 @@ bool cmFindPackageCommand::ReadListFile(const std::string& f,
return false;
}
bool cmFindPackageCommand::ImportTargetConfigurations(
std::string const& base, cmPackageInfoReader* parent)
{
// Find supplemental configuration files.
cmsys::Glob glob;
glob.RecurseOff();
if (glob.FindFiles(cmStrCat(cmSystemTools::GetFilenamePath(base), "/"_s,
cmSystemTools::GetFilenameWithoutExtension(base),
"@*.[Cc][Pp][Ss]"_s))) {
// 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;
}
}
}
}
return true;
}
bool cmFindPackageCommand::ImportAppendices(std::string const& base)
{
// Find package appendices.
cmsys::Glob glob;
glob.RecurseOff();
if (glob.FindFiles(cmStrCat(cmSystemTools::GetFilenamePath(base), "/"_s,
cmSystemTools::GetFilenameWithoutExtension(base),
"[-:]*.[Cc][Pp][Ss]"_s))) {
// Try to read supplemental data from each appendix 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())) {
return false;
}
}
}
}
return true;
}
void cmFindPackageCommand::AppendToFoundProperty(const bool found)
{
cmList foundContents;

View File

@@ -137,6 +137,9 @@ private:
DoPolicyScope
};
bool ReadListFile(const std::string& f, PolicyScopeRule psr);
bool ImportTargetConfigurations(std::string const& base,
cmPackageInfoReader* parent);
bool ImportAppendices(std::string const& base);
void StoreVersionFound();
void SetConfigDirCacheVariable(const std::string& value);

View File

@@ -2,7 +2,10 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmPackageInfoReader.h"
#include <initializer_list>
#include <limits>
#include <unordered_map>
#include <utility>
#include <cm/string_view>
#include <cmext/string_view>
@@ -12,12 +15,52 @@
#include <cm3p/json/version.h>
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cmExecutionStatus.h"
#include "cmListFileCache.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
namespace {
// Map of CPS language names to CMake language name. Case insensitivity is
// achieved by converting the CPS value to lower case, so keys in this map must
// be lower case.
std::unordered_map<std::string, std::string> Languages = {
// clang-format off
{ "c", "C" },
{ "c++", "CXX" },
{ "cpp", "CXX" },
{ "cxx", "CXX" },
{ "objc", "OBJC" },
{ "objc++", "OBJCXX" },
{ "objcpp", "OBJCXX" },
{ "objcxx", "OBJCXX" },
{ "swift", "swift" },
{ "hip", "HIP" },
{ "cuda", "CUDA" },
{ "ispc", "ISPC" },
{ "c#", "CSharp" },
{ "csharp", "CSharp" },
{ "fortran", "Fortran" },
// clang-format on
};
std::string GetRealPath(std::string const& path)
{
return cmSystemTools::GetRealPath(path);
}
std::string GetRealDir(std::string const& path)
{
return cmSystemTools::GetFilenamePath(cmSystemTools::GetRealPath(path));
}
Json::Value ReadJson(std::string const& fileName)
{
// Open the specified file.
@@ -56,17 +99,270 @@ bool CheckSchemaVersion(Json::Value const& data)
// Check that we understand this version.
return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL,
version, "0.12") &&
version, "0.13") &&
cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, version, "1");
// TODO Eventually this probably needs to return the version tuple, and
// should share code with cmPackageInfoReader::ParseVersion.
}
bool ComparePathSuffix(std::string const& path, std::string const& suffix)
{
std::string const& tail = path.substr(path.size() - suffix.size());
return cmSystemTools::ComparePath(tail, suffix);
}
std::string DeterminePrefix(std::string const& filepath,
Json::Value const& data)
{
// First check if an absolute prefix was supplied.
std::string prefix = data["prefix"].asString();
if (!prefix.empty()) {
// Ensure that the specified prefix is valid.
if (cmsys::SystemTools::FileIsFullPath(prefix) &&
cmsys::SystemTools::FileIsDirectory(prefix)) {
cmSystemTools::ConvertToUnixSlashes(prefix);
return prefix;
}
// The specified absolute prefix is not valid.
return {};
}
// Get and validate prefix-relative path.
std::string relPath = data["cps_path"].asString();
cmSystemTools::ConvertToUnixSlashes(relPath);
if (relPath.empty() || !cmHasLiteralPrefix(relPath, "@prefix@/")) {
// The relative prefix is not valid.
return {};
}
relPath = relPath.substr(8);
// Get directory portion of the absolute path.
std::string const& absPath = cmSystemTools::GetFilenamePath(filepath);
if (ComparePathSuffix(absPath, relPath)) {
return absPath.substr(0, absPath.size() - relPath.size());
}
for (auto* const f : { GetRealPath, GetRealDir }) {
std::string const& tmpPath = (*f)(absPath);
if (!cmSystemTools::ComparePath(tmpPath, absPath) &&
ComparePathSuffix(tmpPath, relPath)) {
return tmpPath.substr(0, tmpPath.size() - relPath.size());
}
}
return {};
}
// Extract key name from value iterator as string_view.
cm::string_view IterKey(Json::Value::const_iterator const& iter)
{
char const* end;
char const* const start = iter.memberName(&end);
return { start, static_cast<std::string::size_type>(end - start) };
}
// Get list-of-strings value from object.
std::vector<std::string> ReadList(Json::Value const& data, char const* key)
{
std::vector<std::string> result;
Json::Value const& arr = data[key];
if (arr.isArray()) {
for (Json::Value const& val : arr) {
if (val.isString()) {
result.push_back(val.asString());
}
}
}
return result;
}
std::string NormalizeTargetName(std::string const& name,
std::string const& context)
{
if (cmHasLiteralPrefix(name, ":")) {
return cmStrCat(context, name);
}
std::string::size_type const n = name.find_first_of(':');
if (n != std::string::npos) {
cm::string_view v{ name };
return cmStrCat(v.substr(0, n), ':', v.substr(n));
}
return name;
}
std::string ResolvePrefix(cm::string_view str, cm::string_view prefix)
{
if (cmHasPrefix(str, "@prefix@"_s)) {
return cmStrCat(prefix, str.substr(8));
}
return std::string{ str };
}
void AppendProperty(cmMakefile* makefile, cmTarget* target,
cm::string_view property, cm::string_view configuration,
std::string const& value)
{
std::string fullprop;
if (configuration.empty()) {
fullprop = cmStrCat("INTERFACE_"_s, property);
} else {
fullprop = cmStrCat("INTERFACE_"_s, property, '_',
cmSystemTools::UpperCase(configuration));
}
target->AppendProperty(fullprop, value, makefile->GetBacktrace());
}
void SetOptionalProperty(cmMakefile* /*makefile*/, cmTarget* target,
cm::string_view property,
cm::string_view configuration,
Json::Value const& value, std::string const& prefix)
{
if (!value.isNull()) {
std::string fullprop;
if (configuration.empty()) {
fullprop = cmStrCat("IMPORTED_"_s, property);
} else {
fullprop = cmStrCat("IMPORTED_"_s, property, '_',
cmSystemTools::UpperCase(configuration));
}
target->SetProperty(fullprop, ResolvePrefix(value.asString(), prefix));
}
}
void AddCompileFeature(cmMakefile* makefile, cmTarget* target,
cm::string_view configuration, std::string const& value)
{
auto reLanguageLevel = []() -> cmsys::RegularExpression {
static cmsys::RegularExpression re{ "^[Cc]([+][+])?([0-9][0-9])$" };
return re;
}();
if (reLanguageLevel.find(value)) {
std::string::size_type const n = reLanguageLevel.end() - 2;
cm::string_view const featurePrefix = (n == 3 ? "cxx_std_"_s : "c_std_"_s);
if (configuration.empty()) {
AppendProperty(makefile, target, "COMPILE_FEATURES"_s, {},
cmStrCat(featurePrefix, value.substr(n)));
} else {
std::string const& feature =
cmStrCat("$<$<CONFIG:"_s, configuration, ">:"_s, featurePrefix,
value.substr(n), '>');
AppendProperty(makefile, target, "COMPILE_FEATURES"_s, {}, feature);
}
} else if (cmStrCaseEq(value, "gnu"_s)) {
// Not implemented in CMake at this time
} else if (cmStrCaseEq(value, "threads"_s)) {
AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration,
"Threads::Threads");
}
}
void AddLinkFeature(cmMakefile* makefile, cmTarget* target,
cm::string_view configuration, std::string const& value)
{
if (cmStrCaseEq(value, "thread"_s)) {
AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration,
"Threads::Threads");
}
}
void SetTargetProperties(cmMakefile* makefile, cmTarget* target,
Json::Value const& data, std::string const& package,
std::string const& prefix,
cm::string_view configuration)
{
// Add compile and link features.
for (std::string const& def : ReadList(data, "compile_features")) {
AddCompileFeature(makefile, target, configuration, def);
}
for (std::string const& def : ReadList(data, "link_features")) {
AddLinkFeature(makefile, target, configuration, def);
}
// Add compile definitions.
for (std::string const& def : ReadList(data, "definitions")) {
AppendProperty(makefile, target, "COMPILE_DEFINITIONS"_s, configuration,
def);
}
// Add include directories.
for (std::string const& inc : ReadList(data, "includes")) {
AppendProperty(makefile, target, "INCLUDE_DIRECTORIES"_s, configuration,
ResolvePrefix(inc, prefix));
}
// Add link name/location(s).
SetOptionalProperty(makefile, target, "LOCATION"_s, configuration,
data["location"], prefix);
SetOptionalProperty(makefile, target, "IMPLIB"_s, configuration,
data["link_location"], prefix);
SetOptionalProperty(makefile, target, "SONAME"_s, configuration,
data["link_name"], prefix);
// Add link languages.
for (std::string const& lang : ReadList(data, "link_languages")) {
auto const li = Languages.find(cmSystemTools::LowerCase(lang));
if (li != Languages.end()) {
AppendProperty(makefile, target, "LINK_LANGUAGES"_s, configuration,
li->second);
}
}
// Add transitive dependencies.
for (std::string const& dep : ReadList(data, "requires")) {
AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration,
NormalizeTargetName(dep, package));
}
for (std::string const& dep : ReadList(data, "link_requires")) {
std::string const& lib =
cmStrCat("$<LINK_ONLY:"_s, NormalizeTargetName(dep, package), '>');
AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib);
}
}
cmTarget* AddLibraryComponent(cmMakefile* makefile,
cmStateEnums::TargetType type,
std::string const& name, Json::Value const& data,
std::vector<std::string> const& configurations,
std::string const& package,
std::string const& prefix,
cm::string_view configuration)
{
// Create the imported target.
cmTarget* const target = makefile->AddImportedTarget(name, type, false);
// Set target properties.
SetTargetProperties(makefile, target, data, package, prefix, configuration);
if (configuration.empty()) {
Json::Value const& cfgData = data["configurations"];
for (auto ci = cfgData.begin(), ce = cfgData.end(); ci != ce; ++ci) {
SetTargetProperties(makefile, target, *ci, package, prefix, IterKey(ci));
}
}
// Set default configurations.
if (!configurations.empty()) {
target->SetProperty("IMPORTED_CONFIGURATIONS",
cmJoin(configurations, ";"_s));
}
return target;
}
} // namespace
std::unique_ptr<cmPackageInfoReader> cmPackageInfoReader::Read(
std::string const& path)
std::string const& path, cmPackageInfoReader const* parent)
{
// Read file and perform some basic validation:
// - the input is valid JSON
@@ -88,9 +384,25 @@ std::unique_ptr<cmPackageInfoReader> cmPackageInfoReader::Read(
return nullptr;
}
std::string prefix = (parent ? parent->Prefix : DeterminePrefix(path, data));
if (prefix.empty()) {
return nullptr;
}
// Seems sane enough to hand back to the caller.
std::unique_ptr<cmPackageInfoReader> reader{ new cmPackageInfoReader };
reader->Data = data;
reader->Data = std::move(data);
reader->Prefix = std::move(prefix);
reader->Path = path;
// Determine other information we need to know immediately, or (if this is
// a supplemental reader) copy from the parent.
if (parent) {
reader->ComponentTargets = parent->ComponentTargets;
reader->DefaultConfigurations = parent->DefaultConfigurations;
} else {
reader->DefaultConfigurations = ReadList(reader->Data, "configurations");
}
return reader;
}
@@ -144,3 +456,110 @@ std::vector<unsigned> cmPackageInfoReader::ParseVersion() const
return result;
}
bool cmPackageInfoReader::ImportTargets(cmMakefile* makefile,
cmExecutionStatus& status)
{
std::string const& package = this->GetName();
// Read components.
Json::Value const& components = this->Data["components"];
for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) {
cm::string_view const& name = IterKey(ci);
std::string const& type =
cmSystemTools::LowerCase((*ci)["type"].asString());
// Get and validate full target name.
std::string const& fullName = cmStrCat(package, "::"_s, name);
{
std::string msg;
if (!makefile->EnforceUniqueName(fullName, msg)) {
status.SetError(msg);
return false;
}
}
cmTarget* target = nullptr;
if (type == "symbolic"_s) {
// TODO
} else if (type == "dylib"_s) {
target = AddLibraryComponent(makefile, cmStateEnums::SHARED_LIBRARY,
fullName, *ci, this->DefaultConfigurations,
package, this->Prefix, {});
} else if (type == "module"_s) {
target = AddLibraryComponent(makefile, cmStateEnums::MODULE_LIBRARY,
fullName, *ci, this->DefaultConfigurations,
package, this->Prefix, {});
} else if (type == "archive"_s) {
target = AddLibraryComponent(makefile, cmStateEnums::STATIC_LIBRARY,
fullName, *ci, this->DefaultConfigurations,
package, this->Prefix, {});
} else if (type == "interface"_s) {
target = AddLibraryComponent(makefile, cmStateEnums::INTERFACE_LIBRARY,
fullName, *ci, this->DefaultConfigurations,
package, this->Prefix, {});
} else {
makefile->IssueMessage(MessageType::WARNING,
cmStrCat("component "_s, fullName,
" has unknown type "_s, type,
" and was not imported"_s));
}
if (target) {
this->ComponentTargets.emplace(std::string{ name }, target);
}
}
// Read default components.
std::vector<std::string> const& defaultComponents =
ReadList(this->Data, "default_components");
if (!defaultComponents.empty()) {
std::string msg;
if (!makefile->EnforceUniqueName(package, msg)) {
status.SetError(msg);
return false;
}
cmTarget* const target = makefile->AddImportedTarget(
package, cmStateEnums::INTERFACE_LIBRARY, false);
for (std::string const& name : defaultComponents) {
std::string const& fullName = cmStrCat(package, "::"_s, name);
AppendProperty(makefile, target, "LINK_LIBRARIES"_s, {}, fullName);
}
}
return true;
}
bool cmPackageInfoReader::ImportTargetConfigurations(
cmMakefile* makefile, cmExecutionStatus& status) const
{
std::string const& configuration = this->Data["configuration"].asString();
if (configuration.empty()) {
makefile->IssueMessage(MessageType::WARNING,
cmStrCat("supplemental file "_s, this->Path,
" does not specify a configuration"_s));
return true;
}
std::string const& package = this->GetName();
Json::Value const& components = this->Data["components"];
for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) {
// Get component name and look up target.
cm::string_view const& name = IterKey(ci);
auto const& ti = this->ComponentTargets.find(std::string{ name });
if (ti == this->ComponentTargets.end()) {
status.SetError(cmStrCat("component "_s, name, " was not found"_s));
return false;
}
// Read supplemental data for component.
SetTargetProperties(makefile, ti->second, *ci, package, this->Prefix,
configuration);
}
return true;
}

View File

@@ -4,6 +4,7 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include <map>
#include <memory>
#include <string>
#include <vector>
@@ -12,7 +13,9 @@
#include <cm3p/json/value.h>
// class cmExecutionStatus;
class cmExecutionStatus;
class cmMakefile;
class cmTarget;
/** \class cmPackageInfoReader
* \brief Read and parse CPS files.
@@ -24,7 +27,8 @@
class cmPackageInfoReader
{
public:
static std::unique_ptr<cmPackageInfoReader> Read(std::string const& path);
static std::unique_ptr<cmPackageInfoReader> Read(
std::string const& path, cmPackageInfoReader const* parent = nullptr);
std::string GetName() const;
cm::optional<std::string> GetVersion() const;
@@ -34,9 +38,20 @@ public:
/// version is specified.
std::vector<unsigned> ParseVersion() const;
/// Create targets for components specified in the CPS file.
bool ImportTargets(cmMakefile* makefile, cmExecutionStatus& status);
/// Add configuration-specific properties for targets.
bool ImportTargetConfigurations(cmMakefile* makefile,
cmExecutionStatus& status) const;
private:
cmPackageInfoReader() = default;
std::string Path;
Json::Value Data;
std::string Prefix;
std::map<std::string, cmTarget*> ComponentTargets;
std::vector<std::string> DefaultConfigurations;
};

View File

@@ -0,0 +1 @@
/* empty header file */