You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cmake/Source/cmExportPackageInfoGenerato...

453 lines
14 KiB

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmExportPackageInfoGenerator.h"
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cmext/string_view>
#include <cm3p/json/value.h>
#include <cm3p/json/writer.h>
#include "cmExportSet.h"
#include "cmFindPackageStack.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.h"
#include "cmList.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"
static const std::string kCPS_VERSION_STR = "0.12.0";
cmExportPackageInfoGenerator::cmExportPackageInfoGenerator(
std::string packageName, std::string version, std::string versionCompat,
std::string versionSchema, std::vector<std::string> defaultTargets,
std::vector<std::string> defaultConfigurations)
: PackageName(std::move(packageName))
, PackageVersion(std::move(version))
, PackageVersionCompat(std::move(versionCompat))
, PackageVersionSchema(std::move(versionSchema))
, DefaultTargets(std::move(defaultTargets))
, DefaultConfigurations(std::move(defaultConfigurations))
{
}
cm::string_view cmExportPackageInfoGenerator::GetImportPrefixWithSlash() const
{
return "@prefix@/"_s;
}
bool cmExportPackageInfoGenerator::GenerateImportFile(std::ostream& os)
{
return this->GenerateMainFile(os);
}
void cmExportPackageInfoGenerator::WritePackageInfo(
Json::Value const& packageInfo, std::ostream& os) const
{
Json::StreamWriterBuilder builder;
builder["indentation"] = " ";
builder["commentStyle"] = "None";
std::unique_ptr<Json::StreamWriter> const writer(builder.newStreamWriter());
writer->write(packageInfo, &os);
}
namespace {
template <typename T>
void buildArray(Json::Value& object, std::string const& property,
T const& values)
{
if (!values.empty()) {
Json::Value& array = object[property];
for (auto const& item : values) {
array.append(item);
}
}
}
}
bool cmExportPackageInfoGenerator::CheckDefaultTargets() const
{
bool result = true;
std::set<std::string> exportedTargetNames;
for (auto const* te : this->ExportedTargets) {
exportedTargetNames.emplace(te->GetExportName());
}
for (auto const& name : this->DefaultTargets) {
if (!cm::contains(exportedTargetNames, name)) {
this->ReportError(
cmStrCat("Package \"", this->GetPackageName(),
"\" specifies DEFAULT_TARGETS \"", name,
"\", which is not a target in the export set \"",
this->GetExportSet()->GetName(), "\"."));
result = false;
}
}
return result;
}
Json::Value cmExportPackageInfoGenerator::GeneratePackageInfo() const
{
Json::Value package;
package["name"] = this->GetPackageName();
package["cps_version"] = std::string(kCPS_VERSION_STR);
if (!this->PackageVersion.empty()) {
package["version"] = this->PackageVersion;
if (!this->PackageVersionCompat.empty()) {
package["compat_version"] = this->PackageVersionCompat;
}
if (!this->PackageVersionSchema.empty()) {
package["version_schema"] = this->PackageVersionSchema;
}
}
buildArray(package, "default_components", this->DefaultTargets);
buildArray(package, "configurations", this->DefaultConfigurations);
// TODO: description, website, license
return package;
}
void cmExportPackageInfoGenerator::GeneratePackageRequires(
Json::Value& package) const
{
if (!this->Requirements.empty()) {
Json::Value& requirements = package["requires"];
for (auto const& requirement : this->Requirements) {
// TODO: version, hint
requirements[requirement] = Json::Value{};
}
}
}
Json::Value* cmExportPackageInfoGenerator::GenerateImportTarget(
Json::Value& components, cmGeneratorTarget const* target,
cmStateEnums::TargetType targetType) const
{
auto const& name = target->GetExportName();
if (name.empty()) {
return nullptr;
}
Json::Value& component = components[name];
Json::Value& type = component["type"];
switch (targetType) {
case cmStateEnums::EXECUTABLE:
type = "executable";
break;
case cmStateEnums::STATIC_LIBRARY:
type = "archive";
break;
case cmStateEnums::SHARED_LIBRARY:
type = "dylib";
break;
case cmStateEnums::MODULE_LIBRARY:
type = "module";
break;
case cmStateEnums::INTERFACE_LIBRARY:
type = "interface";
break;
default:
type = "unknown";
break;
}
return &component;
}
bool cmExportPackageInfoGenerator::GenerateInterfaceProperties(
Json::Value& component, cmGeneratorTarget const* target,
ImportPropertyMap const& properties) const
{
bool result = true;
this->GenerateInterfaceLinkProperties(result, component, target, properties);
this->GenerateInterfaceCompileFeatures(result, component, target,
properties);
this->GenerateInterfaceCompileDefines(result, component, target, properties);
this->GenerateInterfaceListProperty(result, component, target,
"compile_flags", "COMPILE_OPTIONS"_s,
properties);
this->GenerateInterfaceListProperty(result, component, target, "link_flags",
"LINK_OPTIONS"_s, properties);
this->GenerateInterfaceListProperty(result, component, target,
"link_directories", "LINK_DIRECTORIES"_s,
properties);
this->GenerateInterfaceListProperty(result, component, target, "includes",
"INCLUDE_DIRECTORIES"_s, properties);
// TODO: description, license
return result;
}
namespace {
bool forbidGeneratorExpressions(std::string const& propertyName,
std::string const& propertyValue,
cmGeneratorTarget const* target)
{
std::string const& evaluatedValue = cmGeneratorExpression::Preprocess(
propertyValue, cmGeneratorExpression::StripAllGeneratorExpressions);
if (evaluatedValue != propertyValue) {
target->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Property \"", propertyName, "\" of target \"",
target->GetName(),
"\" contains a generator expression. This is not allowed."));
return false;
}
return true;
}
}
bool cmExportPackageInfoGenerator::NoteLinkedTarget(
cmGeneratorTarget const* target, std::string const& linkedName,
cmGeneratorTarget const* linkedTarget)
{
if (cm::contains(this->ExportedTargets, linkedTarget)) {
// Target is internal to this package.
this->LinkTargets.emplace(linkedName,
cmStrCat(':', linkedTarget->GetExportName()));
return true;
}
if (linkedTarget->IsImported()) {
// Target is imported from a found package.
auto pkgName = [linkedTarget]() -> std::string {
auto const& pkgStack = linkedTarget->Target->GetFindPackageStack();
if (!pkgStack.Empty()) {
return pkgStack.Top().Name;
}
return linkedTarget->Target->GetProperty("EXPORT_FIND_PACKAGE_NAME");
}();
if (pkgName.empty()) {
target->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Target \"", target->GetName(),
"\" references imported target \"", linkedName,
"\" which does not come from any known package."));
return false;
}
auto const& prefix = cmStrCat(pkgName, "::");
if (!cmHasPrefix(linkedName, prefix)) {
target->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Target \"", target->GetName(), "\" references target \"",
linkedName, "\", which comes from the \"", pkgName,
"\" package, but does not belong to the package's "
"canonical namespace. This is not allowed."));
return false;
}
// TODO: Record package version, hint.
this->Requirements.emplace(pkgName);
this->LinkTargets.emplace(
linkedName, cmStrCat(pkgName, ':', linkedName.substr(prefix.length())));
return true;
}
// Target belongs to another export from this build.
auto const& exportInfo = this->FindExportInfo(linkedTarget);
if (exportInfo.first.size() == 1) {
auto const& linkNamespace = exportInfo.second;
if (!cmHasSuffix(linkNamespace, "::")) {
target->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Target \"", target->GetName(), "\" references target \"",
linkedName,
"\", which does not use the standard namespace separator. "
"This is not allowed."));
return false;
}
auto pkgName =
cm::string_view{ linkNamespace.data(), linkNamespace.size() - 2 };
if (pkgName == this->GetPackageName()) {
this->LinkTargets.emplace(linkedName,
cmStrCat(':', linkedTarget->GetExportName()));
} else {
this->Requirements.emplace(pkgName);
this->LinkTargets.emplace(
linkedName, cmStrCat(pkgName, ':', linkedTarget->GetExportName()));
}
return true;
}
// cmExportFileGenerator::HandleMissingTarget should have complained about
// this already. (In fact, we probably shouldn't ever get here.)
return false;
}
void cmExportPackageInfoGenerator::GenerateInterfaceLinkProperties(
bool& result, Json::Value& component, cmGeneratorTarget const* target,
ImportPropertyMap const& properties) const
{
auto const& iter = properties.find("INTERFACE_LINK_LIBRARIES");
if (iter == properties.end()) {
return;
}
// TODO: Support $<LINK_ONLY>.
if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
result = false;
return;
}
std::vector<std::string> buildRequires;
// std::vector<std::string> linkRequires; TODO
std::vector<std::string> linkLibraries;
for (auto const& name : cmList{ iter->second }) {
auto const& ti = this->LinkTargets.find(name);
if (ti != this->LinkTargets.end()) {
if (ti->second.empty()) {
result = false;
} else {
buildRequires.emplace_back(ti->second);
}
} else {
linkLibraries.emplace_back(name);
}
}
buildArray(component, "requires", buildRequires);
// buildArray(component, "link_requires", linkRequires); TODO
buildArray(component, "link_libraries", linkLibraries);
}
void cmExportPackageInfoGenerator::GenerateInterfaceCompileFeatures(
bool& result, Json::Value& component, cmGeneratorTarget const* target,
ImportPropertyMap const& properties) const
{
auto const& iter = properties.find("INTERFACE_COMPILE_FEATURES");
if (iter == properties.end()) {
return;
}
if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
result = false;
return;
}
std::set<std::string> features;
for (auto const& value : cmList{ iter->second }) {
if (cmHasLiteralPrefix(value, "c_std_")) {
auto suffix = cm::string_view{ value }.substr(6, 2);
features.emplace(cmStrCat("cxx", suffix));
} else if (cmHasLiteralPrefix(value, "cxx_std_")) {
auto suffix = cm::string_view{ value }.substr(8, 2);
features.emplace(cmStrCat("c++", suffix));
}
}
buildArray(component, "compile_features", features);
}
void cmExportPackageInfoGenerator::GenerateInterfaceCompileDefines(
bool& result, Json::Value& component, cmGeneratorTarget const* target,
ImportPropertyMap const& properties) const
{
auto const& iter = properties.find("INTERFACE_COMPILE_DEFINITIONS");
if (iter == properties.end()) {
return;
}
// TODO: Support language-specific defines.
if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
result = false;
return;
}
Json::Value defines;
for (auto const& def : cmList{ iter->second }) {
auto const n = def.find('=');
if (n == std::string::npos) {
defines[def] = Json::Value{};
} else {
defines[def.substr(0, n)] = def.substr(n + 1);
}
}
if (!defines.empty()) {
component["compile_definitions"]["*"] = std::move(defines);
}
}
void cmExportPackageInfoGenerator::GenerateInterfaceListProperty(
bool& result, Json::Value& component, cmGeneratorTarget const* target,
std::string const& outName, cm::string_view inName,
ImportPropertyMap const& properties) const
{
auto const& prop = cmStrCat("INTERFACE_", inName);
auto const& iter = properties.find(prop);
if (iter == properties.end()) {
return;
}
if (!forbidGeneratorExpressions(prop, iter->second, target)) {
result = false;
return;
}
Json::Value& array = component[outName];
for (auto const& value : cmList{ iter->second }) {
array.append(value);
}
}
void cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties(
Json::Value& components, cmGeneratorTarget const* target,
std::string const& suffix, ImportPropertyMap const& properties) const
{
Json::Value component;
auto const suffixLength = suffix.length();
for (auto const& p : properties) {
if (!cmHasSuffix(p.first, suffix)) {
continue;
}
auto const n = p.first.length() - suffixLength - 9;
auto const prop = cm::string_view{ p.first }.substr(9, n);
if (prop == "LOCATION") {
component["location"] = p.second;
} else if (prop == "IMPLIB") {
component["link_location"] = p.second;
} else if (prop == "LINK_INTERFACE_LANGUAGES") {
std::vector<std::string> languages;
for (auto const& lang : cmList{ p.second }) {
auto ll = cmSystemTools::LowerCase(lang);
if (ll == "cxx") {
languages.emplace_back("cpp");
} else {
languages.emplace_back(std::move(ll));
}
}
buildArray(component, "link_languages", languages);
}
}
if (!component.empty()) {
components[target->GetExportName()] = component;
}
}