| /* 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; |
| } |
| } |