From 1bceab352023123b4a3e11c7910bc86b949f8e86 Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Mon, 8 Jul 2024 13:19:27 -0400 Subject: export: Factor out CMake-specific export generation (*/2) In order to support generation of Common Package Specifications, the mechanisms CMake uses to export package information need to be made more abstract. This will involve substantial refactoring of the classes used to generate the actual export files. In order to help git track what's happening, create the new files as copies of the files that will serve as their source material. The class names have been updated and formatting repaired, but no other refactoring has been done, which should still allow git to detect the copies. This commit is a purely intermediate step that exists for no other reason than to improve history tracking. --- Source/cmExportAndroidMKGenerator.cxx | 196 +++ Source/cmExportAndroidMKGenerator.h | 62 + Source/cmExportBuildCMakeConfigGenerator.cxx | 602 +++++++++ Source/cmExportBuildCMakeConfigGenerator.h | 135 ++ Source/cmExportCMakeConfigGenerator.cxx | 1683 ++++++++++++++++++++++++ Source/cmExportCMakeConfigGenerator.h | 264 ++++ Source/cmExportInstallCMakeConfigGenerator.cxx | 800 +++++++++++ Source/cmExportInstallCMakeConfigGenerator.h | 145 ++ 8 files changed, 3887 insertions(+) create mode 100644 Source/cmExportAndroidMKGenerator.cxx create mode 100644 Source/cmExportAndroidMKGenerator.h create mode 100644 Source/cmExportBuildCMakeConfigGenerator.cxx create mode 100644 Source/cmExportBuildCMakeConfigGenerator.h create mode 100644 Source/cmExportCMakeConfigGenerator.cxx create mode 100644 Source/cmExportCMakeConfigGenerator.h create mode 100644 Source/cmExportInstallCMakeConfigGenerator.cxx create mode 100644 Source/cmExportInstallCMakeConfigGenerator.h diff --git a/Source/cmExportAndroidMKGenerator.cxx b/Source/cmExportAndroidMKGenerator.cxx new file mode 100644 index 0000000..52a76f7 --- /dev/null +++ b/Source/cmExportAndroidMKGenerator.cxx @@ -0,0 +1,196 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmExportAndroidMKGenerator.h" + +#include +#include +#include +#include + +#include + +#include "cmGeneratorTarget.h" +#include "cmLinkItem.h" +#include "cmList.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmPolicies.h" +#include "cmStateTypes.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmTarget.h" + +cmExportAndroidMKGenerator::cmExportAndroidMKGenerator() +{ + this->LG = nullptr; + this->ExportSet = nullptr; +} + +void cmExportAndroidMKGenerator::GenerateImportHeaderCode(std::ostream& os, + const std::string&) +{ + os << "LOCAL_PATH := $(call my-dir)\n\n"; +} + +void cmExportAndroidMKGenerator::GenerateImportFooterCode(std::ostream&) +{ +} + +void cmExportAndroidMKGenerator::GenerateExpectedTargetsCode( + std::ostream&, const std::string&) +{ +} + +void cmExportAndroidMKGenerator::GenerateImportTargetCode( + std::ostream& os, cmGeneratorTarget const* target, + cmStateEnums::TargetType /*targetType*/) +{ + std::string targetName = cmStrCat(this->Namespace, target->GetExportName()); + os << "include $(CLEAR_VARS)\n"; + os << "LOCAL_MODULE := "; + os << targetName << "\n"; + os << "LOCAL_SRC_FILES := "; + std::string const noConfig; // FIXME: What config to use here? + std::string path = + cmSystemTools::ConvertToOutputPath(target->GetFullPath(noConfig)); + os << path << "\n"; +} + +void cmExportAndroidMKGenerator::GenerateImportPropertyCode( + std::ostream&, const std::string&, const std::string&, + cmGeneratorTarget const*, ImportPropertyMap const&, const std::string&) +{ +} + +void cmExportAndroidMKGenerator::GenerateMissingTargetsCheckCode(std::ostream&) +{ +} + +void cmExportAndroidMKGenerator::GenerateInterfaceProperties( + const cmGeneratorTarget* target, std::ostream& os, + const ImportPropertyMap& properties) +{ + std::string config; + if (!this->Configurations.empty()) { + config = this->Configurations[0]; + } + cmExportAndroidMKGenerator::GenerateInterfaceProperties( + target, os, properties, cmExportAndroidMKGenerator::BUILD, config); +} + +void cmExportAndroidMKGenerator::GenerateInterfaceProperties( + const cmGeneratorTarget* target, std::ostream& os, + const ImportPropertyMap& properties, GenerateType type, + std::string const& config) +{ + const bool newCMP0022Behavior = + target->GetPolicyStatusCMP0022() != cmPolicies::WARN && + target->GetPolicyStatusCMP0022() != cmPolicies::OLD; + if (!newCMP0022Behavior) { + std::ostringstream w; + if (type == cmExportAndroidMKGenerator::BUILD) { + w << "export(TARGETS ... ANDROID_MK) called with policy CMP0022"; + } else { + w << "install( EXPORT_ANDROID_MK ...) called with policy CMP0022"; + } + w << " set to OLD for target " << target->Target->GetName() << ". " + << "The export will only work with CMP0022 set to NEW."; + target->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, w.str()); + } + if (!properties.empty()) { + os << "LOCAL_CPP_FEATURES := rtti exceptions\n"; + for (auto const& property : properties) { + if (property.first == "INTERFACE_COMPILE_OPTIONS") { + os << "LOCAL_CPP_FEATURES += "; + os << (property.second) << "\n"; + } else if (property.first == "INTERFACE_LINK_LIBRARIES") { + std::string staticLibs; + std::string sharedLibs; + std::string ldlibs; + cmLinkInterfaceLibraries const* linkIFace = + target->GetLinkInterfaceLibraries(config, target, + cmGeneratorTarget::UseTo::Link); + for (cmLinkItem const& item : linkIFace->Libraries) { + cmGeneratorTarget const* gt = item.Target; + std::string const& lib = item.AsStr(); + if (gt) { + + if (gt->GetType() == cmStateEnums::SHARED_LIBRARY || + gt->GetType() == cmStateEnums::MODULE_LIBRARY) { + sharedLibs += " " + lib; + } else { + staticLibs += " " + lib; + } + } else { + bool relpath = false; + if (type == cmExportAndroidMKGenerator::INSTALL) { + relpath = cmHasLiteralPrefix(lib, "../"); + } + // check for full path or if it already has a -l, or + // in the case of an install check for relative paths + // if it is full or a link library then use string directly + if (cmSystemTools::FileIsFullPath(lib) || + cmHasLiteralPrefix(lib, "-l") || relpath) { + ldlibs += " " + lib; + // if it is not a path and does not have a -l then add -l + } else if (!lib.empty()) { + ldlibs += " -l" + lib; + } + } + } + if (!sharedLibs.empty()) { + os << "LOCAL_SHARED_LIBRARIES :=" << sharedLibs << "\n"; + } + if (!staticLibs.empty()) { + os << "LOCAL_STATIC_LIBRARIES :=" << staticLibs << "\n"; + } + if (!ldlibs.empty()) { + os << "LOCAL_EXPORT_LDLIBS :=" << ldlibs << "\n"; + } + } else if (property.first == "INTERFACE_INCLUDE_DIRECTORIES") { + std::string includes = property.second; + cmList includeList{ includes }; + os << "LOCAL_EXPORT_C_INCLUDES := "; + std::string end; + for (std::string const& i : includeList) { + os << end << i; + end = "\\\n"; + } + os << "\n"; + } else if (property.first == "INTERFACE_LINK_OPTIONS") { + os << "LOCAL_EXPORT_LDFLAGS := "; + cmList linkFlagsList{ property.second }; + os << linkFlagsList.join(" ") << "\n"; + } else { + os << "# " << property.first << " " << (property.second) << "\n"; + } + } + } + + // Tell the NDK build system if prebuilt static libraries use C++. + if (target->GetType() == cmStateEnums::STATIC_LIBRARY) { + cmLinkImplementation const* li = + target->GetLinkImplementation(config, cmGeneratorTarget::UseTo::Link); + if (cm::contains(li->Languages, "CXX")) { + os << "LOCAL_HAS_CPP := true\n"; + } + } + + switch (target->GetType()) { + case cmStateEnums::SHARED_LIBRARY: + case cmStateEnums::MODULE_LIBRARY: + os << "include $(PREBUILT_SHARED_LIBRARY)\n"; + break; + case cmStateEnums::STATIC_LIBRARY: + os << "include $(PREBUILT_STATIC_LIBRARY)\n"; + break; + case cmStateEnums::EXECUTABLE: + case cmStateEnums::UTILITY: + case cmStateEnums::OBJECT_LIBRARY: + case cmStateEnums::GLOBAL_TARGET: + case cmStateEnums::INTERFACE_LIBRARY: + case cmStateEnums::UNKNOWN_LIBRARY: + break; + } + os << "\n"; +} diff --git a/Source/cmExportAndroidMKGenerator.h b/Source/cmExportAndroidMKGenerator.h new file mode 100644 index 0000000..065297b --- /dev/null +++ b/Source/cmExportAndroidMKGenerator.h @@ -0,0 +1,62 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include + +#include "cmExportBuildFileGenerator.h" +#include "cmStateTypes.h" + +class cmGeneratorTarget; + +/** \class cmExportAndroidMKGenerator + * \brief Generate a file exporting targets from a build tree. + * + * cmExportAndroidMKGenerator generates a file exporting targets from + * a build tree. This exports the targets to the Android ndk build tool + * makefile format for prebuilt libraries. + * + * This is used to implement the export() command. + */ +class cmExportAndroidMKGenerator : public cmExportBuildFileGenerator +{ +public: + cmExportAndroidMKGenerator(); + // this is so cmExportInstallAndroidMKGenerator can share this + // function as they are almost the same + enum GenerateType + { + BUILD, + INSTALL + }; + static void GenerateInterfaceProperties(cmGeneratorTarget const* target, + std::ostream& os, + const ImportPropertyMap& properties, + GenerateType type, + std::string const& config); + +protected: + // Implement virtual methods from the superclass. + void GeneratePolicyHeaderCode(std::ostream&) override {} + void GeneratePolicyFooterCode(std::ostream&) override {} + void GenerateImportHeaderCode(std::ostream& os, + const std::string& config = "") override; + void GenerateImportFooterCode(std::ostream& os) override; + void GenerateImportTargetCode( + std::ostream& os, cmGeneratorTarget const* target, + cmStateEnums::TargetType /*targetType*/) override; + void GenerateExpectedTargetsCode( + std::ostream& os, const std::string& expectedTargets) override; + void GenerateImportPropertyCode( + std::ostream& os, const std::string& config, const std::string& suffix, + cmGeneratorTarget const* target, ImportPropertyMap const& properties, + const std::string& importedXcFrameworkLocation) override; + void GenerateMissingTargetsCheckCode(std::ostream& os) override; + void GenerateFindDependencyCalls(std::ostream&) override {} + void GenerateInterfaceProperties( + cmGeneratorTarget const* target, std::ostream& os, + const ImportPropertyMap& properties) override; +}; diff --git a/Source/cmExportBuildCMakeConfigGenerator.cxx b/Source/cmExportBuildCMakeConfigGenerator.cxx new file mode 100644 index 0000000..918e18c --- /dev/null +++ b/Source/cmExportBuildCMakeConfigGenerator.cxx @@ -0,0 +1,602 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmExportBuildCMakeConfigGenerator.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cmCryptoHash.h" +#include "cmExportSet.h" +#include "cmFileSet.h" +#include "cmGeneratedFileStream.h" +#include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" +#include "cmList.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmOutputConverter.h" +#include "cmPolicies.h" +#include "cmStateTypes.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmTarget.h" +#include "cmTargetExport.h" +#include "cmValue.h" +#include "cmake.h" + +class cmSourceFile; + +cmExportBuildCMakeConfigGenerator::cmExportBuildCMakeConfigGenerator() +{ + this->LG = nullptr; + this->ExportSet = nullptr; +} + +void cmExportBuildCMakeConfigGenerator::Compute(cmLocalGenerator* lg) +{ + this->LG = lg; + if (this->ExportSet) { + this->ExportSet->Compute(lg); + } +} + +bool cmExportBuildCMakeConfigGenerator::GenerateMainFile(std::ostream& os) +{ + { + std::string expectedTargets; + std::string sep; + std::vector targets; + bool generatedInterfaceRequired = false; + this->GetTargets(targets); + for (auto const& tei : targets) { + cmGeneratorTarget* te = this->LG->FindGeneratorTargetToUse(tei.Name); + expectedTargets += sep + this->Namespace + te->GetExportName(); + sep = " "; + if (this->ExportedTargets.insert(te).second) { + this->Exports.emplace_back(te, tei.XcFrameworkLocation); + } else { + std::ostringstream e; + e << "given target \"" << te->GetName() << "\" more than once."; + this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, e.str(), + this->LG->GetMakefile()->GetBacktrace()); + return false; + } + generatedInterfaceRequired |= + this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY; + } + + if (generatedInterfaceRequired) { + this->SetRequiredCMakeVersion(3, 0, 0); + } + this->GenerateExpectedTargetsCode(os, expectedTargets); + } + + // Create all the imported targets. + for (auto const& exp : this->Exports) { + cmGeneratorTarget* gte = exp.Target; + this->GenerateImportTargetCode(os, gte, this->GetExportTargetType(gte)); + + gte->Target->AppendBuildInterfaceIncludes(); + + ImportPropertyMap properties; + + this->PopulateInterfaceProperty("INTERFACE_INCLUDE_DIRECTORIES", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_SOURCES", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_COMPILE_OPTIONS", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_PRECOMPILE_HEADERS", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_AUTOMOC_MACRO_NAMES", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_LINK_OPTIONS", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_LINK_DIRECTORIES", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_LINK_DEPENDS", gte, + cmGeneratorExpression::BuildInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE", gte, + properties); + + std::string errorMessage; + if (!this->PopulateCxxModuleExportProperties( + gte, properties, cmGeneratorExpression::BuildInterface, {}, + errorMessage)) { + this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, errorMessage, + this->LG->GetMakefile()->GetBacktrace()); + return false; + } + + if (!this->PopulateExportProperties(gte, properties, errorMessage)) { + this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, errorMessage, + this->LG->GetMakefile()->GetBacktrace()); + return false; + } + + const bool newCMP0022Behavior = + gte->GetPolicyStatusCMP0022() != cmPolicies::WARN && + gte->GetPolicyStatusCMP0022() != cmPolicies::OLD; + if (newCMP0022Behavior) { + this->PopulateInterfaceLinkLibrariesProperty( + gte, cmGeneratorExpression::BuildInterface, properties); + } + this->PopulateCompatibleInterfaceProperties(gte, properties); + this->PopulateCustomTransitiveInterfaceProperties( + gte, cmGeneratorExpression::BuildInterface, properties); + + this->GenerateInterfaceProperties(gte, os, properties); + + this->GenerateTargetFileSets(gte, os); + } + + std::string cxx_modules_name; + if (this->ExportSet) { + cxx_modules_name = this->ExportSet->GetName(); + } else { + cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_512); + constexpr std::size_t HASH_TRUNCATION = 12; + for (auto const& target : this->Targets) { + hasher.Append(target.Name); + } + cxx_modules_name = hasher.FinalizeHex().substr(0, HASH_TRUNCATION); + } + + this->GenerateCxxModuleInformation(cxx_modules_name, os); + + // Generate import file content for each configuration. + for (std::string const& c : this->Configurations) { + this->GenerateImportConfig(os, c); + } + + // Generate import file content for each configuration. + for (std::string const& c : this->Configurations) { + this->GenerateImportCxxModuleConfigTargetInclusion(cxx_modules_name, c); + } + + this->GenerateMissingTargetsCheckCode(os); + + return true; +} + +void cmExportBuildCMakeConfigGenerator::GenerateImportTargetsConfig( + std::ostream& os, const std::string& config, std::string const& suffix) +{ + for (auto const& exp : this->Exports) { + cmGeneratorTarget* target = exp.Target; + + // Collect import properties for this target. + ImportPropertyMap properties; + + if (this->GetExportTargetType(target) != cmStateEnums::INTERFACE_LIBRARY) { + this->SetImportLocationProperty(config, suffix, target, properties); + } + if (!properties.empty()) { + // Get the rest of the target details. + if (this->GetExportTargetType(target) != + cmStateEnums::INTERFACE_LIBRARY) { + this->SetImportDetailProperties(config, suffix, target, properties); + this->SetImportLinkInterface(config, suffix, + cmGeneratorExpression::BuildInterface, + target, properties); + } + + // TODO: PUBLIC_HEADER_LOCATION + // This should wait until the build feature propagation stuff + // is done. Then this can be a propagated include directory. + // this->GenerateImportProperty(config, te->HeaderGenerator, + // properties); + + // Generate code in the export file. + std::string importedXcFrameworkLocation = exp.XcFrameworkLocation; + if (!importedXcFrameworkLocation.empty()) { + importedXcFrameworkLocation = cmGeneratorExpression::Preprocess( + importedXcFrameworkLocation, + cmGeneratorExpression::PreprocessContext::BuildInterface); + importedXcFrameworkLocation = cmGeneratorExpression::Evaluate( + importedXcFrameworkLocation, exp.Target->GetLocalGenerator(), config, + exp.Target, nullptr, exp.Target); + if (!importedXcFrameworkLocation.empty() && + !cmSystemTools::FileIsFullPath(importedXcFrameworkLocation)) { + importedXcFrameworkLocation = + cmStrCat(this->LG->GetCurrentBinaryDirectory(), '/', + importedXcFrameworkLocation); + } + } + this->GenerateImportPropertyCode(os, config, suffix, target, properties, + importedXcFrameworkLocation); + } + } +} + +cmStateEnums::TargetType +cmExportBuildCMakeConfigGenerator::GetExportTargetType( + cmGeneratorTarget const* target) const +{ + cmStateEnums::TargetType targetType = target->GetType(); + // An object library exports as an interface library if we cannot + // tell clients where to find the objects. This is sufficient + // to support transitive usage requirements on other targets that + // use the object library. + if (targetType == cmStateEnums::OBJECT_LIBRARY && + !target->Target->HasKnownObjectFileLocation(nullptr)) { + targetType = cmStateEnums::INTERFACE_LIBRARY; + } + return targetType; +} + +void cmExportBuildCMakeConfigGenerator::SetExportSet(cmExportSet* exportSet) +{ + this->ExportSet = exportSet; +} + +void cmExportBuildCMakeConfigGenerator::SetImportLocationProperty( + const std::string& config, std::string const& suffix, + cmGeneratorTarget* target, ImportPropertyMap& properties) +{ + // Get the makefile in which to lookup target information. + cmMakefile* mf = target->Makefile; + + if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) { + std::string prop = cmStrCat("IMPORTED_OBJECTS", suffix); + + // Compute all the object files inside this target and setup + // IMPORTED_OBJECTS as a list of object files + std::vector objectSources; + target->GetObjectSources(objectSources, config); + std::string const obj_dir = target->GetObjectDirectory(config); + std::vector objects; + for (cmSourceFile const* sf : objectSources) { + const std::string& obj = target->GetObjectName(sf); + objects.push_back(obj_dir + obj); + } + + // Store the property. + properties[prop] = cmList::to_string(objects); + } else { + // Add the main target file. + { + std::string prop = cmStrCat("IMPORTED_LOCATION", suffix); + std::string value; + if (target->IsAppBundleOnApple()) { + value = + target->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact); + } else { + value = target->GetFullPath(config, + cmStateEnums::RuntimeBinaryArtifact, true); + } + properties[prop] = value; + } + + // Add the import library for windows DLLs. + if (target->HasImportLibrary(config)) { + std::string prop = cmStrCat("IMPORTED_IMPLIB", suffix); + std::string value = + target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact, true); + if (mf->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX")) { + target->GetImplibGNUtoMS(config, value, value, + "${CMAKE_IMPORT_LIBRARY_SUFFIX}"); + } + properties[prop] = value; + } + } +} + +void cmExportBuildCMakeConfigGenerator::HandleMissingTarget( + std::string& link_libs, cmGeneratorTarget const* depender, + cmGeneratorTarget* dependee) +{ + // The target is not in the export. + if (!this->AppendMode) { + const std::string name = dependee->GetName(); + cmGlobalGenerator* gg = + dependee->GetLocalGenerator()->GetGlobalGenerator(); + auto exportInfo = this->FindBuildExportInfo(gg, name); + std::vector const& exportFiles = exportInfo.first; + + if (exportFiles.size() == 1) { + std::string missingTarget = exportInfo.second; + + missingTarget += dependee->GetExportName(); + link_libs += missingTarget; + this->MissingTargets.emplace_back(std::move(missingTarget)); + return; + } + // We are not appending, so all exported targets should be + // known here. This is probably user-error. + this->ComplainAboutMissingTarget(depender, dependee, exportFiles); + } + // Assume the target will be exported by another command. + // Append it with the export namespace. + link_libs += this->Namespace; + link_libs += dependee->GetExportName(); +} + +void cmExportBuildCMakeConfigGenerator::GetTargets( + std::vector& targets) const +{ + if (this->ExportSet) { + for (std::unique_ptr const& te : + this->ExportSet->GetTargetExports()) { + if (te->NamelinkOnly) { + continue; + } + targets.emplace_back(te->TargetName, te->XcFrameworkLocation); + } + return; + } + targets = this->Targets; +} + +std::pair, std::string> +cmExportBuildCMakeConfigGenerator::FindBuildExportInfo(cmGlobalGenerator* gg, + const std::string& name) +{ + std::vector exportFiles; + std::string ns; + + auto& exportSets = gg->GetBuildExportSets(); + + for (auto const& exp : exportSets) { + const auto& exportSet = exp.second; + std::vector targets; + exportSet->GetTargets(targets); + if (std::any_of( + targets.begin(), targets.end(), + [&name](const TargetExport& te) { return te.Name == name; })) { + exportFiles.push_back(exp.first); + ns = exportSet->GetNamespace(); + } + } + + return { exportFiles, ns }; +} + +void cmExportBuildCMakeConfigGenerator::ComplainAboutMissingTarget( + cmGeneratorTarget const* depender, cmGeneratorTarget const* dependee, + std::vector const& exportFiles) +{ + std::ostringstream e; + e << "export called with target \"" << depender->GetName() + << "\" which requires target \"" << dependee->GetName() << "\" "; + if (exportFiles.empty()) { + e << "that is not in any export set."; + } else { + e << "that is not in this export set, but in multiple other export sets: " + << cmJoin(exportFiles, ", ") << ".\n"; + e << "An exported target cannot depend upon another target which is " + "exported multiple times. Consider consolidating the exports of the " + "\"" + << dependee->GetName() << "\" target to a single export."; + } + + this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage( + MessageType::FATAL_ERROR, e.str(), + this->LG->GetMakefile()->GetBacktrace()); +} + +std::string cmExportBuildCMakeConfigGenerator::InstallNameDir( + cmGeneratorTarget const* target, const std::string& config) +{ + std::string install_name_dir; + + cmMakefile* mf = target->Target->GetMakefile(); + if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { + install_name_dir = target->GetInstallNameDirForBuildTree(config); + } + + return install_name_dir; +} + +namespace { +bool EntryIsContextSensitive( + const std::unique_ptr& cge) +{ + return cge->GetHadContextSensitiveCondition(); +} +} + +std::string cmExportBuildCMakeConfigGenerator::GetFileSetDirectories( + cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport* /*te*/) +{ + std::vector resultVector; + + auto configs = + gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); + auto directoryEntries = fileSet->CompileDirectoryEntries(); + + for (auto const& config : configs) { + auto directories = fileSet->EvaluateDirectoryEntries( + directoryEntries, gte->LocalGenerator, config, gte); + + bool const contextSensitive = + std::any_of(directoryEntries.begin(), directoryEntries.end(), + EntryIsContextSensitive); + + auto const& type = fileSet->GetType(); + // C++ modules do not support interface file sets which are dependent upon + // the configuration. + if (contextSensitive && type == "CXX_MODULES"_s) { + auto* mf = this->LG->GetMakefile(); + std::ostringstream e; + e << "The \"" << gte->GetName() << "\" target's interface file set \"" + << fileSet->GetName() << "\" of type \"" << type + << "\" contains context-sensitive base directory entries which is not " + "supported."; + mf->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return std::string{}; + } + + for (auto const& directory : directories) { + auto dest = cmOutputConverter::EscapeForCMake( + directory, cmOutputConverter::WrapQuotes::NoWrap); + + if (contextSensitive && configs.size() != 1) { + resultVector.push_back( + cmStrCat("\"$<$:", dest, ">\"")); + } else { + resultVector.emplace_back(cmStrCat('"', dest, '"')); + break; + } + } + } + + return cmJoin(resultVector, " "); +} + +std::string cmExportBuildCMakeConfigGenerator::GetFileSetFiles( + cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport* /*te*/) +{ + std::vector resultVector; + + auto configs = + gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); + + auto fileEntries = fileSet->CompileFileEntries(); + auto directoryEntries = fileSet->CompileDirectoryEntries(); + + for (auto const& config : configs) { + auto directories = fileSet->EvaluateDirectoryEntries( + directoryEntries, gte->LocalGenerator, config, gte); + + std::map> files; + for (auto const& entry : fileEntries) { + fileSet->EvaluateFileEntry(directories, files, entry, + gte->LocalGenerator, config, gte); + } + + bool const contextSensitive = + std::any_of(directoryEntries.begin(), directoryEntries.end(), + EntryIsContextSensitive) || + std::any_of(fileEntries.begin(), fileEntries.end(), + EntryIsContextSensitive); + + auto const& type = fileSet->GetType(); + // C++ modules do not support interface file sets which are dependent upon + // the configuration. + if (contextSensitive && type == "CXX_MODULES"_s) { + auto* mf = this->LG->GetMakefile(); + std::ostringstream e; + e << "The \"" << gte->GetName() << "\" target's interface file set \"" + << fileSet->GetName() << "\" of type \"" << type + << "\" contains context-sensitive file entries which is not " + "supported."; + mf->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return std::string{}; + } + + for (auto const& it : files) { + for (auto const& filename : it.second) { + auto escapedFile = cmOutputConverter::EscapeForCMake( + filename, cmOutputConverter::WrapQuotes::NoWrap); + if (contextSensitive && configs.size() != 1) { + resultVector.push_back( + cmStrCat("\"$<$:", escapedFile, ">\"")); + } else { + resultVector.emplace_back(cmStrCat('"', escapedFile, '"')); + } + } + } + + if (!(contextSensitive && configs.size() != 1)) { + break; + } + } + + return cmJoin(resultVector, " "); +} + +std::string cmExportBuildCMakeConfigGenerator::GetCxxModulesDirectory() const +{ + return this->CxxModulesDirectory; +} + +void cmExportBuildCMakeConfigGenerator::GenerateCxxModuleConfigInformation( + std::string const& name, std::ostream& os) const +{ + const char* opt = ""; + if (this->Configurations.size() > 1) { + // With more than one configuration, each individual file is optional. + opt = " OPTIONAL"; + } + + // Generate import file content for each configuration. + for (std::string c : this->Configurations) { + if (c.empty()) { + c = "noconfig"; + } + os << "include(\"${CMAKE_CURRENT_LIST_DIR}/cxx-modules-" << name << '-' + << c << ".cmake\"" << opt << ")\n"; + } +} + +bool cmExportBuildCMakeConfigGenerator:: + GenerateImportCxxModuleConfigTargetInclusion(std::string const& name, + std::string config) const +{ + auto cxx_modules_dirname = this->GetCxxModulesDirectory(); + if (cxx_modules_dirname.empty()) { + return true; + } + + if (config.empty()) { + config = "noconfig"; + } + + std::string fileName = + cmStrCat(this->FileDir, '/', cxx_modules_dirname, "/cxx-modules-", name, + '-', config, ".cmake"); + + cmGeneratedFileStream os(fileName, true); + if (!os) { + std::string se = cmSystemTools::GetLastSystemError(); + std::ostringstream e; + e << "cannot write to file \"" << fileName << "\": " << se; + cmSystemTools::Error(e.str()); + return false; + } + os.SetCopyIfDifferent(true); + + for (auto const* tgt : this->ExportedTargets) { + // Only targets with C++ module sources will have a + // collator-generated install script. + if (!tgt->HaveCxx20ModuleSources()) { + continue; + } + + os << "include(\"${CMAKE_CURRENT_LIST_DIR}/target-" + << tgt->GetFilesystemExportName() << '-' << config << ".cmake\")\n"; + } + + return true; +} diff --git a/Source/cmExportBuildCMakeConfigGenerator.h b/Source/cmExportBuildCMakeConfigGenerator.h new file mode 100644 index 0000000..5c8f403 --- /dev/null +++ b/Source/cmExportBuildCMakeConfigGenerator.h @@ -0,0 +1,135 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include +#include + +#include + +#include "cmExportFileGenerator.h" +#include "cmStateTypes.h" + +class cmExportSet; +class cmFileSet; +class cmGeneratorTarget; +class cmGlobalGenerator; +class cmLocalGenerator; +class cmTargetExport; + +/** \class cmExportBuildCMakeConfigGenerator + * \brief Generate a file exporting targets from a build tree. + * + * cmExportBuildCMakeConfigGenerator generates a file exporting targets from + * a build tree. A single file exports information for all + * configurations built. + * + * This is used to implement the export() command. + */ +class cmExportBuildCMakeConfigGenerator : public cmExportFileGenerator +{ +public: + struct TargetExport + { + TargetExport(std::string name, std::string xcFrameworkLocation) + : Name(std::move(name)) + , XcFrameworkLocation(std::move(xcFrameworkLocation)) + { + } + + std::string Name; + std::string XcFrameworkLocation; + }; + + cmExportBuildCMakeConfigGenerator(); + + /** Set the list of targets to export. */ + void SetTargets(std::vector const& targets) + { + this->Targets = targets; + } + void GetTargets(std::vector& targets) const; + void AppendTargets(std::vector const& targets) + { + cm::append(this->Targets, targets); + } + void SetExportSet(cmExportSet*); + + /** Set the name of the C++ module directory. */ + void SetCxxModuleDirectory(std::string cxx_module_dir) + { + this->CxxModulesDirectory = std::move(cxx_module_dir); + } + const std::string& GetCxxModuleDirectory() const + { + return this->CxxModulesDirectory; + } + + /** Set whether to append generated code to the output file. */ + void SetAppendMode(bool append) { this->AppendMode = append; } + + void Compute(cmLocalGenerator* lg); + +protected: + // Implement virtual methods from the superclass. + bool GenerateMainFile(std::ostream& os) override; + void GenerateImportTargetsConfig(std::ostream& os, const std::string& config, + std::string const& suffix) override; + cmStateEnums::TargetType GetExportTargetType( + cmGeneratorTarget const* target) const; + void HandleMissingTarget(std::string& link_libs, + cmGeneratorTarget const* depender, + cmGeneratorTarget* dependee) override; + + void ComplainAboutMissingTarget(cmGeneratorTarget const* depender, + cmGeneratorTarget const* dependee, + std::vector const& namespaces); + + /** Fill in properties indicating built file locations. */ + void SetImportLocationProperty(const std::string& config, + std::string const& suffix, + cmGeneratorTarget* target, + ImportPropertyMap& properties); + + std::string InstallNameDir(cmGeneratorTarget const* target, + const std::string& config) override; + + std::string GetFileSetDirectories(cmGeneratorTarget* gte, cmFileSet* fileSet, + cmTargetExport* te) override; + std::string GetFileSetFiles(cmGeneratorTarget* gte, cmFileSet* fileSet, + cmTargetExport* te) override; + cmExportSet* GetExportSet() const override { return this->ExportSet; } + + std::string GetCxxModulesDirectory() const override; + void GenerateCxxModuleConfigInformation(std::string const&, + std::ostream&) const override; + bool GenerateImportCxxModuleConfigTargetInclusion(std::string const&, + std::string) const; + + std::pair, std::string> FindBuildExportInfo( + cmGlobalGenerator* gg, const std::string& name); + + struct TargetExportPrivate + { + TargetExportPrivate(cmGeneratorTarget* target, + std::string xcFrameworkLocation) + : Target(target) + , XcFrameworkLocation(std::move(xcFrameworkLocation)) + { + } + + cmGeneratorTarget* Target; + std::string XcFrameworkLocation; + }; + + std::vector Targets; + cmExportSet* ExportSet; + std::vector Exports; + cmLocalGenerator* LG; + // The directory for C++ module information. + std::string CxxModulesDirectory; +}; diff --git a/Source/cmExportCMakeConfigGenerator.cxx b/Source/cmExportCMakeConfigGenerator.cxx new file mode 100644 index 0000000..3ca1876 --- /dev/null +++ b/Source/cmExportCMakeConfigGenerator.cxx @@ -0,0 +1,1683 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmExportCMakeConfigGenerator.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "cmsys/FStream.hxx" + +#include "cmComputeLinkInformation.h" +#include "cmExportSet.h" +#include "cmFileSet.h" +#include "cmFindPackageStack.h" +#include "cmGeneratedFileStream.h" +#include "cmGeneratorTarget.h" +#include "cmLinkItem.h" +#include "cmList.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmOutputConverter.h" +#include "cmPolicies.h" +#include "cmPropertyMap.h" +#include "cmStateTypes.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmTarget.h" +#include "cmValue.h" +#include "cmVersion.h" + +static std::string cmExportCMakeConfigGeneratorEscape(std::string const& str) +{ + // Escape a property value for writing into a .cmake file. + std::string result = cmOutputConverter::EscapeForCMake(str); + // Un-escape variable references generated by our own export code. + cmSystemTools::ReplaceString(result, "\\${_IMPORT_PREFIX}", + "${_IMPORT_PREFIX}"); + cmSystemTools::ReplaceString(result, "\\${CMAKE_IMPORT_LIBRARY_SUFFIX}", + "${CMAKE_IMPORT_LIBRARY_SUFFIX}"); + return result; +} + +cmExportCMakeConfigGenerator::cmExportCMakeConfigGenerator() +{ + this->AppendMode = false; + this->ExportOld = false; +} + +void cmExportCMakeConfigGenerator::AddConfiguration(const std::string& config) +{ + this->Configurations.push_back(config); +} + +void cmExportCMakeConfigGenerator::SetExportFile(const char* mainFile) +{ + this->MainImportFile = mainFile; + this->FileDir = cmSystemTools::GetFilenamePath(this->MainImportFile); + this->FileBase = + cmSystemTools::GetFilenameWithoutLastExtension(this->MainImportFile); + this->FileExt = + cmSystemTools::GetFilenameLastExtension(this->MainImportFile); +} + +const std::string& cmExportCMakeConfigGenerator::GetMainExportFileName() const +{ + return this->MainImportFile; +} + +bool cmExportCMakeConfigGenerator::GenerateImportFile() +{ + // Open the output file to generate it. + std::unique_ptr foutPtr; + if (this->AppendMode) { + // Open for append. + auto openmodeApp = std::ios::app; + foutPtr = cm::make_unique(this->MainImportFile.c_str(), + openmodeApp); + } else { + // Generate atomically and with copy-if-different. + std::unique_ptr ap( + new cmGeneratedFileStream(this->MainImportFile, true)); + ap->SetCopyIfDifferent(true); + foutPtr = std::move(ap); + } + if (!foutPtr || !*foutPtr) { + std::string se = cmSystemTools::GetLastSystemError(); + std::ostringstream e; + e << "cannot write to file \"" << this->MainImportFile << "\": " << se; + cmSystemTools::Error(e.str()); + return false; + } + std::ostream& os = *foutPtr; + std::stringstream mainFileWithHeadersAndFootersBuffer; + + // Start with the import file header. + this->GenerateImportHeaderCode(mainFileWithHeadersAndFootersBuffer); + + // Create all the imported targets. + std::stringstream mainFileBuffer; + bool result = this->GenerateMainFile(mainFileBuffer); + + // Export find_dependency() calls. Must be done after GenerateMainFile(), + // because that's when target dependencies are gathered, which we need for + // the find_dependency() calls. + if (!this->AppendMode && this->GetExportSet() && + this->ExportPackageDependencies) { + this->SetRequiredCMakeVersion(3, 9, 0); + this->GenerateFindDependencyCalls(mainFileWithHeadersAndFootersBuffer); + } + + // Write cached import code. + mainFileWithHeadersAndFootersBuffer << mainFileBuffer.rdbuf(); + + // End with the import file footer. + this->GenerateImportFooterCode(mainFileWithHeadersAndFootersBuffer); + this->GeneratePolicyFooterCode(mainFileWithHeadersAndFootersBuffer); + + // This has to be done last, after the minimum CMake version has been + // determined. + this->GeneratePolicyHeaderCode(os); + os << mainFileWithHeadersAndFootersBuffer.rdbuf(); + + return result; +} + +void cmExportCMakeConfigGenerator::GenerateImportConfig( + std::ostream& os, const std::string& config) +{ + // Construct the property configuration suffix. + std::string suffix = "_"; + if (!config.empty()) { + suffix += cmSystemTools::UpperCase(config); + } else { + suffix += "NOCONFIG"; + } + + // Generate the per-config target information. + this->GenerateImportTargetsConfig(os, config, suffix); +} + +void cmExportCMakeConfigGenerator::PopulateInterfaceProperty( + const std::string& propName, cmGeneratorTarget const* target, + ImportPropertyMap& properties) +{ + cmValue input = target->GetProperty(propName); + if (input) { + properties[propName] = *input; + } +} + +void cmExportCMakeConfigGenerator::PopulateInterfaceProperty( + const std::string& propName, const std::string& outputName, + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties) +{ + cmValue input = target->GetProperty(propName); + if (input) { + if (input->empty()) { + // Set to empty + properties[outputName].clear(); + return; + } + + std::string prepro = + cmGeneratorExpression::Preprocess(*input, preprocessRule); + if (!prepro.empty()) { + this->ResolveTargetsInGeneratorExpressions(prepro, target); + properties[outputName] = prepro; + } + } +} + +bool cmExportCMakeConfigGenerator::PopulateInterfaceLinkLibrariesProperty( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties) +{ + if (!target->IsLinkable()) { + return false; + } + static const std::array linkIfaceProps = { + { "INTERFACE_LINK_LIBRARIES", "INTERFACE_LINK_LIBRARIES_DIRECT", + "INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE" } + }; + bool hadINTERFACE_LINK_LIBRARIES = false; + for (std::string const& linkIfaceProp : linkIfaceProps) { + if (cmValue input = target->GetProperty(linkIfaceProp)) { + std::string prepro = + cmGeneratorExpression::Preprocess(*input, preprocessRule); + if (!prepro.empty()) { + this->ResolveTargetsInGeneratorExpressions(prepro, target, + ReplaceFreeTargets); + properties[linkIfaceProp] = prepro; + hadINTERFACE_LINK_LIBRARIES = true; + } + } + } + return hadINTERFACE_LINK_LIBRARIES; +} + +static bool isSubDirectory(std::string const& a, std::string const& b) +{ + return (cmSystemTools::ComparePath(a, b) || + cmSystemTools::IsSubDirectory(a, b)); +} + +static bool checkInterfaceDirs(const std::string& prepro, + cmGeneratorTarget const* target, + const std::string& prop) +{ + std::string const& installDir = + target->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX"); + std::string const& topSourceDir = + target->GetLocalGenerator()->GetSourceDirectory(); + std::string const& topBinaryDir = + target->GetLocalGenerator()->GetBinaryDirectory(); + + std::vector parts; + cmGeneratorExpression::Split(prepro, parts); + + const bool inSourceBuild = topSourceDir == topBinaryDir; + + bool hadFatalError = false; + + for (std::string const& li : parts) { + size_t genexPos = cmGeneratorExpression::Find(li); + if (genexPos == 0) { + continue; + } + if (cmHasLiteralPrefix(li, "${_IMPORT_PREFIX}")) { + continue; + } + MessageType messageType = MessageType::FATAL_ERROR; + std::ostringstream e; + if (genexPos != std::string::npos) { + if (prop == "INTERFACE_INCLUDE_DIRECTORIES") { + switch (target->GetPolicyStatusCMP0041()) { + case cmPolicies::WARN: + messageType = MessageType::WARNING; + e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0041) << "\n"; + break; + case cmPolicies::OLD: + continue; + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::NEW: + hadFatalError = true; + break; // Issue fatal message. + } + } else { + hadFatalError = true; + } + } + if (!cmSystemTools::FileIsFullPath(li)) { + /* clang-format off */ + e << "Target \"" << target->GetName() << "\" " << prop << + " property contains relative path:\n" + " \"" << li << "\""; + /* clang-format on */ + target->GetLocalGenerator()->IssueMessage(messageType, e.str()); + } + bool inBinary = isSubDirectory(li, topBinaryDir); + bool inSource = isSubDirectory(li, topSourceDir); + if (isSubDirectory(li, installDir)) { + // The include directory is inside the install tree. If the + // install tree is not inside the source tree or build tree then + // fall through to the checks below that the include directory is not + // also inside the source tree or build tree. + bool shouldContinue = + (!inBinary || isSubDirectory(installDir, topBinaryDir)) && + (!inSource || isSubDirectory(installDir, topSourceDir)); + + if (prop == "INTERFACE_INCLUDE_DIRECTORIES") { + if (!shouldContinue) { + switch (target->GetPolicyStatusCMP0052()) { + case cmPolicies::WARN: { + std::ostringstream s; + s << cmPolicies::GetPolicyWarning(cmPolicies::CMP0052) << "\n"; + s << "Directory:\n \"" << li + << "\"\nin " + "INTERFACE_INCLUDE_DIRECTORIES of target \"" + << target->GetName() + << "\" is a subdirectory of the install " + "directory:\n \"" + << installDir + << "\"\nhowever it is also " + "a subdirectory of the " + << (inBinary ? "build" : "source") << " tree:\n \"" + << (inBinary ? topBinaryDir : topSourceDir) << "\"\n"; + target->GetLocalGenerator()->IssueMessage( + MessageType::AUTHOR_WARNING, s.str()); + CM_FALLTHROUGH; + } + case cmPolicies::OLD: + shouldContinue = true; + break; + case cmPolicies::REQUIRED_ALWAYS: + case cmPolicies::REQUIRED_IF_USED: + case cmPolicies::NEW: + break; + } + } + } + if (shouldContinue) { + continue; + } + } + if (inBinary) { + /* clang-format off */ + e << "Target \"" << target->GetName() << "\" " << prop << + " property contains path:\n" + " \"" << li << "\"\nwhich is prefixed in the build directory."; + /* clang-format on */ + target->GetLocalGenerator()->IssueMessage(messageType, e.str()); + } + if (!inSourceBuild) { + if (inSource) { + e << "Target \"" << target->GetName() << "\" " << prop + << " property contains path:\n" + " \"" + << li << "\"\nwhich is prefixed in the source directory."; + target->GetLocalGenerator()->IssueMessage(messageType, e.str()); + } + } + } + return !hadFatalError; +} + +static void prefixItems(std::string& exportDirs) +{ + std::vector entries; + cmGeneratorExpression::Split(exportDirs, entries); + exportDirs.clear(); + const char* sep = ""; + for (std::string const& e : entries) { + exportDirs += sep; + sep = ";"; + if (!cmSystemTools::FileIsFullPath(e) && + e.find("${_IMPORT_PREFIX}") == std::string::npos) { + exportDirs += "${_IMPORT_PREFIX}/"; + } + exportDirs += e; + } +} + +void cmExportCMakeConfigGenerator::PopulateSourcesInterface( + cmGeneratorTarget const* gt, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties) +{ + assert(preprocessRule == cmGeneratorExpression::InstallInterface); + + const char* propName = "INTERFACE_SOURCES"; + cmValue input = gt->GetProperty(propName); + + if (!input) { + return; + } + + if (input->empty()) { + properties[propName].clear(); + return; + } + + std::string prepro = + cmGeneratorExpression::Preprocess(*input, preprocessRule, true); + if (!prepro.empty()) { + this->ResolveTargetsInGeneratorExpressions(prepro, gt); + + if (!checkInterfaceDirs(prepro, gt, propName)) { + return; + } + properties[propName] = prepro; + } +} + +void cmExportCMakeConfigGenerator::PopulateIncludeDirectoriesInterface( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties, cmTargetExport const& te, + std::string& includesDestinationDirs) +{ + assert(preprocessRule == cmGeneratorExpression::InstallInterface); + + includesDestinationDirs.clear(); + + const char* propName = "INTERFACE_INCLUDE_DIRECTORIES"; + cmValue input = target->GetProperty(propName); + + cmGeneratorExpression ge(*target->Makefile->GetCMakeInstance()); + + std::string dirs = cmGeneratorExpression::Preprocess( + cmList::to_string(target->Target->GetInstallIncludeDirectoriesEntries(te)), + preprocessRule, true); + this->ReplaceInstallPrefix(dirs); + std::unique_ptr cge = ge.Parse(dirs); + std::string exportDirs = + cge->Evaluate(target->GetLocalGenerator(), "", target); + + if (cge->GetHadContextSensitiveCondition()) { + cmLocalGenerator* lg = target->GetLocalGenerator(); + std::ostringstream e; + e << "Target \"" << target->GetName() + << "\" is installed with " + "INCLUDES DESTINATION set to a context sensitive path. Paths which " + "depend on the configuration, policy values or the link interface " + "are " + "not supported. Consider using target_include_directories instead."; + lg->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return; + } + + if (!input && exportDirs.empty()) { + return; + } + if ((input && input->empty()) && exportDirs.empty()) { + // Set to empty + properties[propName].clear(); + return; + } + + prefixItems(exportDirs); + includesDestinationDirs = exportDirs; + + std::string includes = (input ? *input : ""); + const char* sep = input ? ";" : ""; + includes += sep + exportDirs; + std::string prepro = + cmGeneratorExpression::Preprocess(includes, preprocessRule, true); + if (!prepro.empty()) { + this->ResolveTargetsInGeneratorExpressions(prepro, target); + + if (!checkInterfaceDirs(prepro, target, propName)) { + return; + } + properties[propName] = prepro; + } +} + +void cmExportCMakeConfigGenerator::PopulateLinkDependsInterface( + cmGeneratorTarget const* gt, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties) +{ + assert(preprocessRule == cmGeneratorExpression::InstallInterface); + + const char* propName = "INTERFACE_LINK_DEPENDS"; + cmValue input = gt->GetProperty(propName); + + if (!input) { + return; + } + + if (input->empty()) { + properties[propName].clear(); + return; + } + + std::string prepro = + cmGeneratorExpression::Preprocess(*input, preprocessRule, true); + if (!prepro.empty()) { + this->ResolveTargetsInGeneratorExpressions(prepro, gt); + + if (!checkInterfaceDirs(prepro, gt, propName)) { + return; + } + properties[propName] = prepro; + } +} + +void cmExportCMakeConfigGenerator::PopulateLinkDirectoriesInterface( + cmGeneratorTarget const* gt, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties) +{ + assert(preprocessRule == cmGeneratorExpression::InstallInterface); + + const char* propName = "INTERFACE_LINK_DIRECTORIES"; + cmValue input = gt->GetProperty(propName); + + if (!input) { + return; + } + + if (input->empty()) { + properties[propName].clear(); + return; + } + + std::string prepro = + cmGeneratorExpression::Preprocess(*input, preprocessRule, true); + if (!prepro.empty()) { + this->ResolveTargetsInGeneratorExpressions(prepro, gt); + + if (!checkInterfaceDirs(prepro, gt, propName)) { + return; + } + properties[propName] = prepro; + } +} + +void cmExportCMakeConfigGenerator::PopulateInterfaceProperty( + const std::string& propName, cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties) +{ + this->PopulateInterfaceProperty(propName, propName, target, preprocessRule, + properties); +} + +static void getPropertyContents(cmGeneratorTarget const* tgt, + const std::string& prop, + std::set& ifaceProperties) +{ + cmValue p = tgt->GetProperty(prop); + if (!p) { + return; + } + cmList content{ *p }; + ifaceProperties.insert(content.begin(), content.end()); +} + +static void getCompatibleInterfaceProperties( + cmGeneratorTarget const* target, std::set& ifaceProperties, + const std::string& config) +{ + if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) { + // object libraries have no link information, so nothing to compute + return; + } + + cmComputeLinkInformation* info = target->GetLinkInformation(config); + + if (!info) { + cmLocalGenerator* lg = target->GetLocalGenerator(); + std::ostringstream e; + e << "Exporting the target \"" << target->GetName() + << "\" is not " + "allowed since its linker language cannot be determined"; + lg->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return; + } + + const cmComputeLinkInformation::ItemVector& deps = info->GetItems(); + + for (auto const& dep : deps) { + if (!dep.Target || dep.Target->GetType() == cmStateEnums::OBJECT_LIBRARY) { + continue; + } + getPropertyContents(dep.Target, "COMPATIBLE_INTERFACE_BOOL", + ifaceProperties); + getPropertyContents(dep.Target, "COMPATIBLE_INTERFACE_STRING", + ifaceProperties); + getPropertyContents(dep.Target, "COMPATIBLE_INTERFACE_NUMBER_MIN", + ifaceProperties); + getPropertyContents(dep.Target, "COMPATIBLE_INTERFACE_NUMBER_MAX", + ifaceProperties); + } +} + +void cmExportCMakeConfigGenerator::PopulateCompatibleInterfaceProperties( + cmGeneratorTarget const* gtarget, ImportPropertyMap& properties) +{ + this->PopulateInterfaceProperty("COMPATIBLE_INTERFACE_BOOL", gtarget, + properties); + this->PopulateInterfaceProperty("COMPATIBLE_INTERFACE_STRING", gtarget, + properties); + this->PopulateInterfaceProperty("COMPATIBLE_INTERFACE_NUMBER_MIN", gtarget, + properties); + this->PopulateInterfaceProperty("COMPATIBLE_INTERFACE_NUMBER_MAX", gtarget, + properties); + + std::set ifaceProperties; + + getPropertyContents(gtarget, "COMPATIBLE_INTERFACE_BOOL", ifaceProperties); + getPropertyContents(gtarget, "COMPATIBLE_INTERFACE_STRING", ifaceProperties); + getPropertyContents(gtarget, "COMPATIBLE_INTERFACE_NUMBER_MIN", + ifaceProperties); + getPropertyContents(gtarget, "COMPATIBLE_INTERFACE_NUMBER_MAX", + ifaceProperties); + + if (gtarget->GetType() != cmStateEnums::INTERFACE_LIBRARY) { + std::vector configNames = + gtarget->Target->GetMakefile()->GetGeneratorConfigs( + cmMakefile::IncludeEmptyConfig); + + for (std::string const& cn : configNames) { + getCompatibleInterfaceProperties(gtarget, ifaceProperties, cn); + } + } + + for (std::string const& ip : ifaceProperties) { + this->PopulateInterfaceProperty("INTERFACE_" + ip, gtarget, properties); + } +} + +void cmExportCMakeConfigGenerator::PopulateCustomTransitiveInterfaceProperties( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties) +{ + this->PopulateInterfaceProperty("TRANSITIVE_COMPILE_PROPERTIES", target, + properties); + this->PopulateInterfaceProperty("TRANSITIVE_LINK_PROPERTIES", target, + properties); + std::set ifaceProperties; + for (std::string const& config : this->Configurations) { + for (auto const& i : target->GetCustomTransitiveProperties( + config, cmGeneratorTarget::PropertyFor::Interface)) { + ifaceProperties.emplace(i.second.InterfaceName); + } + } + for (std::string const& ip : ifaceProperties) { + this->PopulateInterfaceProperty(ip, target, preprocessRule, properties); + } +} + +void cmExportCMakeConfigGenerator::GenerateInterfaceProperties( + const cmGeneratorTarget* target, std::ostream& os, + const ImportPropertyMap& properties) +{ + if (!properties.empty()) { + std::string targetName = + cmStrCat(this->Namespace, target->GetExportName()); + os << "set_target_properties(" << targetName << " PROPERTIES\n"; + for (auto const& property : properties) { + os << " " << property.first << " " + << cmExportCMakeConfigGeneratorEscape(property.second) << "\n"; + } + os << ")\n\n"; + } +} + +bool cmExportCMakeConfigGenerator::AddTargetNamespace( + std::string& input, cmGeneratorTarget const* target, + cmLocalGenerator const* lg) +{ + cmGeneratorTarget::TargetOrString resolved = + target->ResolveTargetReference(input, lg); + + cmGeneratorTarget* tgt = resolved.Target; + if (!tgt) { + input = resolved.String; + return false; + } + + cmFindPackageStack const& pkgStack = tgt->Target->GetFindPackageStack(); + if (!pkgStack.Empty() || + tgt->Target->GetProperty("EXPORT_FIND_PACKAGE_NAME")) { + this->ExternalTargets.emplace(tgt); + } + + if (tgt->IsImported()) { + input = tgt->GetName(); + return true; + } + if (this->ExportedTargets.find(tgt) != this->ExportedTargets.end()) { + input = this->Namespace + tgt->GetExportName(); + } else { + std::string namespacedTarget; + this->HandleMissingTarget(namespacedTarget, target, tgt); + if (!namespacedTarget.empty()) { + input = namespacedTarget; + } else { + input = tgt->GetName(); + } + } + return true; +} + +void cmExportCMakeConfigGenerator::ResolveTargetsInGeneratorExpressions( + std::string& input, cmGeneratorTarget const* target, + FreeTargetsReplace replace) +{ + cmLocalGenerator const* lg = target->GetLocalGenerator(); + if (replace == NoReplaceFreeTargets) { + this->ResolveTargetsInGeneratorExpression(input, target, lg); + return; + } + std::vector parts; + cmGeneratorExpression::Split(input, parts); + + std::string sep; + input.clear(); + for (std::string& li : parts) { + if (target->IsLinkLookupScope(li, lg)) { + continue; + } + if (cmGeneratorExpression::Find(li) == std::string::npos) { + this->AddTargetNamespace(li, target, lg); + } else { + this->ResolveTargetsInGeneratorExpression(li, target, lg); + } + input += sep + li; + sep = ";"; + } +} + +void cmExportCMakeConfigGenerator::ResolveTargetsInGeneratorExpression( + std::string& input, cmGeneratorTarget const* target, + cmLocalGenerator const* lg) +{ + std::string::size_type pos = 0; + std::string::size_type lastPos = pos; + + while ((pos = input.find("$', nameStartPos); + std::string::size_type commaPos = input.find(',', nameStartPos); + std::string::size_type nextOpenPos = input.find("$<", nameStartPos); + if (commaPos == std::string::npos // Implied 'this' target + || closePos == std::string::npos // Incomplete expression. + || closePos < commaPos // Implied 'this' target + || nextOpenPos < commaPos) // Non-literal + { + lastPos = nameStartPos; + continue; + } + + std::string targetName = + input.substr(nameStartPos, commaPos - nameStartPos); + + if (this->AddTargetNamespace(targetName, target, lg)) { + input.replace(nameStartPos, commaPos - nameStartPos, targetName); + } + lastPos = nameStartPos + targetName.size() + 1; + } + + std::string errorString; + pos = 0; + lastPos = pos; + while ((pos = input.find("$', nameStartPos); + if (endPos == std::string::npos) { + errorString = "$ expression incomplete"; + break; + } + std::string targetName = input.substr(nameStartPos, endPos - nameStartPos); + if (targetName.find("$<") != std::string::npos) { + errorString = "$ requires its parameter to be a " + "literal."; + break; + } + if (!this->AddTargetNamespace(targetName, target, lg)) { + errorString = "$ requires its parameter to be a " + "reachable target."; + break; + } + input.replace(pos, endPos - pos + 1, targetName); + lastPos = pos + targetName.size(); + } + + pos = 0; + lastPos = pos; + while (errorString.empty() && + (pos = input.find("$', nameStartPos); + if (endPos == std::string::npos) { + errorString = "$ expression incomplete"; + break; + } + std::string libName = input.substr(nameStartPos, endPos - nameStartPos); + if (cmGeneratorExpression::IsValidTargetName(libName) && + this->AddTargetNamespace(libName, target, lg)) { + input.replace(nameStartPos, endPos - nameStartPos, libName); + } + lastPos = nameStartPos + libName.size() + 1; + } + + while (errorString.empty() && + (pos = input.find("$', nameStartPos); + if (endPos == std::string::npos) { + errorString = "$ expression incomplete"; + break; + } + std::string libName = input.substr(nameStartPos, endPos - nameStartPos); + if (cmGeneratorExpression::IsValidTargetName(libName) && + this->AddTargetNamespace(libName, target, lg)) { + input.replace(nameStartPos, endPos - nameStartPos, libName); + } + lastPos = nameStartPos + libName.size() + 1; + } + + this->ReplaceInstallPrefix(input); + + if (!errorString.empty()) { + target->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, + errorString); + } +} + +void cmExportCMakeConfigGenerator::ReplaceInstallPrefix( + std::string& /*unused*/) +{ + // Do nothing +} + +void cmExportCMakeConfigGenerator::SetImportLinkInterface( + const std::string& config, std::string const& suffix, + cmGeneratorExpression::PreprocessContext preprocessRule, + cmGeneratorTarget const* target, ImportPropertyMap& properties) +{ + // Add the transitive link dependencies for this configuration. + cmLinkInterface const* iface = target->GetLinkInterface(config, target); + if (!iface) { + return; + } + + if (iface->ImplementationIsInterface) { + // Policy CMP0022 must not be NEW. + this->SetImportLinkProperty( + suffix, target, "IMPORTED_LINK_INTERFACE_LIBRARIES", iface->Libraries, + properties, ImportLinkPropertyTargetNames::Yes); + return; + } + + cmValue propContent; + + if (cmValue prop_suffixed = + target->GetProperty("LINK_INTERFACE_LIBRARIES" + suffix)) { + propContent = prop_suffixed; + } else if (cmValue prop = target->GetProperty("LINK_INTERFACE_LIBRARIES")) { + propContent = prop; + } else { + return; + } + + const bool newCMP0022Behavior = + target->GetPolicyStatusCMP0022() != cmPolicies::WARN && + target->GetPolicyStatusCMP0022() != cmPolicies::OLD; + + if (newCMP0022Behavior && !this->ExportOld) { + cmLocalGenerator* lg = target->GetLocalGenerator(); + std::ostringstream e; + e << "Target \"" << target->GetName() + << "\" has policy CMP0022 enabled, " + "but also has old-style LINK_INTERFACE_LIBRARIES properties " + "populated, but it was exported without the " + "EXPORT_LINK_INTERFACE_LIBRARIES to export the old-style properties"; + lg->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return; + } + + if (propContent->empty()) { + properties["IMPORTED_LINK_INTERFACE_LIBRARIES" + suffix].clear(); + return; + } + + std::string prepro = + cmGeneratorExpression::Preprocess(*propContent, preprocessRule); + if (!prepro.empty()) { + this->ResolveTargetsInGeneratorExpressions(prepro, target, + ReplaceFreeTargets); + properties["IMPORTED_LINK_INTERFACE_LIBRARIES" + suffix] = prepro; + } +} + +void cmExportCMakeConfigGenerator::SetImportDetailProperties( + const std::string& config, std::string const& suffix, + cmGeneratorTarget* target, ImportPropertyMap& properties) +{ + // Get the makefile in which to lookup target information. + cmMakefile* mf = target->Makefile; + + // Add the soname for unix shared libraries. + if (target->GetType() == cmStateEnums::SHARED_LIBRARY || + target->GetType() == cmStateEnums::MODULE_LIBRARY) { + if (!target->IsDLLPlatform()) { + std::string prop; + std::string value; + if (target->HasSOName(config)) { + if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { + value = this->InstallNameDir(target, config); + } + prop = "IMPORTED_SONAME"; + value += target->GetSOName(config); + } else { + prop = "IMPORTED_NO_SONAME"; + value = "TRUE"; + } + prop += suffix; + properties[prop] = value; + } + } + + // Add the transitive link dependencies for this configuration. + if (cmLinkInterface const* iface = + target->GetLinkInterface(config, target)) { + this->SetImportLinkProperty( + suffix, target, "IMPORTED_LINK_INTERFACE_LANGUAGES", iface->Languages, + properties, ImportLinkPropertyTargetNames::No); + + // Export IMPORTED_LINK_DEPENDENT_LIBRARIES to help consuming linkers + // find private dependencies of shared libraries. + std::size_t oldMissingTargetsSize = this->MissingTargets.size(); + auto oldExternalTargets = this->ExternalTargets; + this->SetImportLinkProperty( + suffix, target, "IMPORTED_LINK_DEPENDENT_LIBRARIES", iface->SharedDeps, + properties, ImportLinkPropertyTargetNames::Yes); + // Avoid enforcing shared library private dependencies as public package + // dependencies by ignoring missing targets added for them. + this->MissingTargets.resize(oldMissingTargetsSize); + this->ExternalTargets = std::move(oldExternalTargets); + + if (iface->Multiplicity > 0) { + std::string prop = + cmStrCat("IMPORTED_LINK_INTERFACE_MULTIPLICITY", suffix); + properties[prop] = std::to_string(iface->Multiplicity); + } + } + + // Add information if this target is a managed target + if (target->GetManagedType(config) != + cmGeneratorTarget::ManagedType::Native) { + std::string prop = cmStrCat("IMPORTED_COMMON_LANGUAGE_RUNTIME", suffix); + std::string propval; + if (cmValue p = target->GetProperty("COMMON_LANGUAGE_RUNTIME")) { + propval = *p; + } else if (target->IsCSharpOnly()) { + // C# projects do not have the /clr flag, so we set the property + // here to mark the target as (only) managed (i.e. no .lib file + // to link to). Otherwise the COMMON_LANGUAGE_RUNTIME target + // property would have to be set manually for C# targets to make + // exporting/importing work. + propval = "CSharp"; + } + properties[prop] = propval; + } +} + +static std::string const& asString(std::string const& l) +{ + return l; +} + +static std::string const& asString(cmLinkItem const& l) +{ + return l.AsStr(); +} + +template +void cmExportCMakeConfigGenerator::SetImportLinkProperty( + std::string const& suffix, cmGeneratorTarget const* target, + const std::string& propName, std::vector const& entries, + ImportPropertyMap& properties, ImportLinkPropertyTargetNames targetNames) +{ + // Skip the property if there are no entries. + if (entries.empty()) { + return; + } + + cmLocalGenerator const* lg = target->GetLocalGenerator(); + + // Construct the property value. + std::string link_entries; + const char* sep = ""; + for (T const& l : entries) { + // Separate this from the previous entry. + link_entries += sep; + sep = ";"; + + if (targetNames == ImportLinkPropertyTargetNames::Yes) { + std::string temp = asString(l); + this->AddTargetNamespace(temp, target, lg); + link_entries += temp; + } else { + link_entries += asString(l); + } + } + + // Store the property. + std::string prop = cmStrCat(propName, suffix); + properties[prop] = link_entries; +} + +void cmExportCMakeConfigGenerator::GeneratePolicyHeaderCode(std::ostream& os) +{ + // Protect that file against use with older CMake versions. + /* clang-format off */ + os << "# Generated by CMake\n\n"; + os << "if(\"${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}\" LESS 2.8)\n" + << " message(FATAL_ERROR \"CMake >= 2.8.0 required\")\n" + << "endif()\n" + << "if(CMAKE_VERSION VERSION_LESS \"" + << this->RequiredCMakeVersionMajor << '.' + << this->RequiredCMakeVersionMinor << '.' + << this->RequiredCMakeVersionPatch << "\")\n" + << " message(FATAL_ERROR \"CMake >= " + << this->RequiredCMakeVersionMajor << '.' + << this->RequiredCMakeVersionMinor << '.' + << this->RequiredCMakeVersionPatch << " required\")\n" + << "endif()\n"; + /* clang-format on */ + + // Isolate the file policy level. + // Support CMake versions as far back as the + // RequiredCMakeVersion{Major,Minor,Patch}, but also support using NEW + // policy settings for up to CMake 3.28 (this upper limit may be reviewed + // and increased from time to time). This reduces the opportunity for CMake + // warnings when an older export file is later used with newer CMake + // versions. + /* clang-format off */ + os << "cmake_policy(PUSH)\n" + << "cmake_policy(VERSION " + << this->RequiredCMakeVersionMajor << '.' + << this->RequiredCMakeVersionMinor << '.' + << this->RequiredCMakeVersionPatch << "...3.28)\n"; + /* clang-format on */ +} + +void cmExportCMakeConfigGenerator::GeneratePolicyFooterCode(std::ostream& os) +{ + os << "cmake_policy(POP)\n"; +} + +void cmExportCMakeConfigGenerator::GenerateImportHeaderCode( + std::ostream& os, const std::string& config) +{ + os << "#----------------------------------------------------------------\n" + << "# Generated CMake target import file"; + if (!config.empty()) { + os << " for configuration \"" << config << "\".\n"; + } else { + os << ".\n"; + } + os << "#----------------------------------------------------------------\n" + << "\n"; + this->GenerateImportVersionCode(os); +} + +void cmExportCMakeConfigGenerator::GenerateImportFooterCode(std::ostream& os) +{ + os << "# Commands beyond this point should not need to know the version.\n" + << "set(CMAKE_IMPORT_FILE_VERSION)\n"; +} + +void cmExportCMakeConfigGenerator::GenerateImportVersionCode(std::ostream& os) +{ + // Store an import file format version. This will let us change the + // format later while still allowing old import files to work. + /* clang-format off */ + os << "# Commands may need to know the format version.\n" + << "set(CMAKE_IMPORT_FILE_VERSION 1)\n" + << "\n"; + /* clang-format on */ +} + +void cmExportCMakeConfigGenerator::GenerateExpectedTargetsCode( + std::ostream& os, const std::string& expectedTargets) +{ + /* clang-format off */ + os << "# Protect against multiple inclusion, which would fail when already " + "imported targets are added once more.\n" + "set(_cmake_targets_defined \"\")\n" + "set(_cmake_targets_not_defined \"\")\n" + "set(_cmake_expected_targets \"\")\n" + "foreach(_cmake_expected_target IN ITEMS " << expectedTargets << ")\n" + " list(APPEND _cmake_expected_targets \"${_cmake_expected_target}\")\n" + " if(TARGET \"${_cmake_expected_target}\")\n" + " list(APPEND _cmake_targets_defined \"${_cmake_expected_target}\")\n" + " else()\n" + " list(APPEND _cmake_targets_not_defined \"${_cmake_expected_target}\")\n" + " endif()\n" + "endforeach()\n" + "unset(_cmake_expected_target)\n" + "if(_cmake_targets_defined STREQUAL _cmake_expected_targets)\n" + " unset(_cmake_targets_defined)\n" + " unset(_cmake_targets_not_defined)\n" + " unset(_cmake_expected_targets)\n" + " unset(CMAKE_IMPORT_FILE_VERSION)\n" + " cmake_policy(POP)\n" + " return()\n" + "endif()\n" + "if(NOT _cmake_targets_defined STREQUAL \"\")\n" + " string(REPLACE \";\" \", \" _cmake_targets_defined_text \"${_cmake_targets_defined}\")\n" + " string(REPLACE \";\" \", \" _cmake_targets_not_defined_text \"${_cmake_targets_not_defined}\")\n" + " message(FATAL_ERROR \"Some (but not all) targets in this export " + "set were already defined.\\nTargets Defined: ${_cmake_targets_defined_text}\\n" + "Targets not yet defined: ${_cmake_targets_not_defined_text}\\n\")\n" + "endif()\n" + "unset(_cmake_targets_defined)\n" + "unset(_cmake_targets_not_defined)\n" + "unset(_cmake_expected_targets)\n" + "\n\n"; + /* clang-format on */ +} + +void cmExportCMakeConfigGenerator::GenerateImportTargetCode( + std::ostream& os, cmGeneratorTarget const* target, + cmStateEnums::TargetType targetType) +{ + // Construct the imported target name. + std::string targetName = this->Namespace; + + targetName += target->GetExportName(); + + // Create the imported target. + os << "# Create imported target " << targetName << "\n"; + switch (targetType) { + case cmStateEnums::EXECUTABLE: + os << "add_executable(" << targetName << " IMPORTED)\n"; + break; + case cmStateEnums::STATIC_LIBRARY: + os << "add_library(" << targetName << " STATIC IMPORTED)\n"; + break; + case cmStateEnums::SHARED_LIBRARY: + os << "add_library(" << targetName << " SHARED IMPORTED)\n"; + break; + case cmStateEnums::MODULE_LIBRARY: + os << "add_library(" << targetName << " MODULE IMPORTED)\n"; + break; + case cmStateEnums::UNKNOWN_LIBRARY: + os << "add_library(" << targetName << " UNKNOWN IMPORTED)\n"; + break; + case cmStateEnums::OBJECT_LIBRARY: + os << "add_library(" << targetName << " OBJECT IMPORTED)\n"; + break; + case cmStateEnums::INTERFACE_LIBRARY: + os << "add_library(" << targetName << " INTERFACE IMPORTED)\n"; + break; + default: // should never happen + break; + } + + // Mark the imported executable if it has exports. + if (target->IsExecutableWithExports() || + (target->IsSharedLibraryWithExports() && target->HasImportLibrary(""))) { + os << "set_property(TARGET " << targetName + << " PROPERTY ENABLE_EXPORTS 1)\n"; + } + + // Mark the imported library if it is a framework. + if (target->IsFrameworkOnApple()) { + os << "set_property(TARGET " << targetName << " PROPERTY FRAMEWORK 1)\n"; + } + + // Mark the imported executable if it is an application bundle. + if (target->IsAppBundleOnApple()) { + os << "set_property(TARGET " << targetName + << " PROPERTY MACOSX_BUNDLE 1)\n"; + } + + if (target->IsCFBundleOnApple()) { + os << "set_property(TARGET " << targetName << " PROPERTY BUNDLE 1)\n"; + } + + // generate DEPRECATION + if (target->IsDeprecated()) { + os << "set_property(TARGET " << targetName << " PROPERTY DEPRECATION " + << cmExportCMakeConfigGeneratorEscape(target->GetDeprecation()) + << ")\n"; + } + + if (target->GetPropertyAsBool("IMPORTED_NO_SYSTEM")) { + os << "set_property(TARGET " << targetName + << " PROPERTY IMPORTED_NO_SYSTEM 1)\n"; + } + + if (target->GetPropertyAsBool("EXPORT_NO_SYSTEM")) { + os << "set_property(TARGET " << targetName << " PROPERTY SYSTEM 0)\n"; + } + + os << "\n"; +} + +void cmExportCMakeConfigGenerator::GenerateImportPropertyCode( + std::ostream& os, const std::string& config, const std::string& suffix, + cmGeneratorTarget const* target, ImportPropertyMap const& properties, + const std::string& importedXcFrameworkLocation) +{ + // Construct the imported target name. + std::string targetName = this->Namespace; + + targetName += target->GetExportName(); + + // Set the import properties. + os << "# Import target \"" << targetName << "\" for configuration \"" + << config << "\"\n"; + os << "set_property(TARGET " << targetName + << " APPEND PROPERTY IMPORTED_CONFIGURATIONS "; + if (!config.empty()) { + os << cmSystemTools::UpperCase(config); + } else { + os << "NOCONFIG"; + } + os << ")\n"; + os << "set_target_properties(" << targetName << " PROPERTIES\n"; + std::string importedLocationProp = cmStrCat("IMPORTED_LOCATION", suffix); + for (auto const& property : properties) { + if (importedXcFrameworkLocation.empty() || + property.first != importedLocationProp) { + os << " " << property.first << " " + << cmExportCMakeConfigGeneratorEscape(property.second) << "\n"; + } + } + os << " )\n"; + if (!importedXcFrameworkLocation.empty()) { + auto importedLocationIt = properties.find(importedLocationProp); + if (importedLocationIt != properties.end()) { + os << "if(NOT CMAKE_VERSION VERSION_LESS \"3.28\" AND IS_DIRECTORY " + << cmExportCMakeConfigGeneratorEscape(importedXcFrameworkLocation) + << ")\n" + " set_property(TARGET " + << targetName << " PROPERTY " << importedLocationProp << " " + << cmExportCMakeConfigGeneratorEscape(importedXcFrameworkLocation) + << ")\nelse()\n set_property(TARGET " << targetName << " PROPERTY " + << importedLocationProp << " " + << cmExportCMakeConfigGeneratorEscape(importedLocationIt->second) + << ")\nendif()\n"; + } + } + os << "\n"; +} + +void cmExportCMakeConfigGenerator::GenerateFindDependencyCalls( + std::ostream& os) +{ + os << "include(CMakeFindDependencyMacro)\n"; + std::map packageDependencies; + auto* exportSet = this->GetExportSet(); + if (exportSet) { + packageDependencies = exportSet->GetPackageDependencies(); + } + + for (cmGeneratorTarget const* gt : this->ExternalTargets) { + std::string findPackageName; + auto exportFindPackageName = gt->GetProperty("EXPORT_FIND_PACKAGE_NAME"); + cmFindPackageStack pkgStack = gt->Target->GetFindPackageStack(); + if (!exportFindPackageName.IsEmpty()) { + findPackageName = *exportFindPackageName; + } else { + if (!pkgStack.Empty()) { + cmFindPackageCall const& fpc = pkgStack.Top(); + findPackageName = fpc.Name; + } + } + if (!findPackageName.empty()) { + auto& dep = packageDependencies[findPackageName]; + if (!pkgStack.Empty()) { + dep.FindPackageIndex = pkgStack.Top().Index; + } + if (dep.Enabled == cmExportSet::PackageDependencyExportEnabled::Auto) { + dep.Enabled = cmExportSet::PackageDependencyExportEnabled::On; + } + } + } + + std::vector> + packageDependenciesSorted(packageDependencies.begin(), + packageDependencies.end()); + std::sort( + packageDependenciesSorted.begin(), packageDependenciesSorted.end(), + [](const std::pair& lhs, + const std::pair& rhs) + -> bool { + if (lhs.second.SpecifiedIndex) { + if (rhs.second.SpecifiedIndex) { + return lhs.second.SpecifiedIndex < rhs.second.SpecifiedIndex; + } + assert(rhs.second.FindPackageIndex); + return true; + } + assert(lhs.second.FindPackageIndex); + if (rhs.second.SpecifiedIndex) { + return false; + } + assert(rhs.second.FindPackageIndex); + return lhs.second.FindPackageIndex < rhs.second.FindPackageIndex; + }); + + for (auto const& it : packageDependenciesSorted) { + if (it.second.Enabled == cmExportSet::PackageDependencyExportEnabled::On) { + os << "find_dependency(" << it.first; + for (auto const& arg : it.second.ExtraArguments) { + os << " " << cmOutputConverter::EscapeForCMake(arg); + } + os << ")\n"; + } + } + os << "\n\n"; +} + +void cmExportCMakeConfigGenerator::GenerateMissingTargetsCheckCode( + std::ostream& os) +{ + if (this->MissingTargets.empty()) { + /* clang-format off */ + os << "# This file does not depend on other imported targets which have\n" + "# been exported from the same project but in a separate " + "export set.\n\n"; + /* clang-format on */ + return; + } + /* clang-format off */ + os << "# Make sure the targets which have been exported in some other\n" + "# export set exist.\n" + "unset(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)\n" + "foreach(_target "; + /* clang-format on */ + std::set emitted; + for (std::string const& missingTarget : this->MissingTargets) { + if (emitted.insert(missingTarget).second) { + os << "\"" << missingTarget << "\" "; + } + } + /* clang-format off */ + os << ")\n" + " if(NOT TARGET \"${_target}\" )\n" + " set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets \"" + "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets} ${_target}\")" + "\n" + " endif()\n" + "endforeach()\n" + "\n" + "if(DEFINED ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)\n" + " if(CMAKE_FIND_PACKAGE_NAME)\n" + " set( ${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)\n" + " set( ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE " + "\"The following imported targets are " + "referenced, but are missing: " + "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets}\")\n" + " else()\n" + " message(FATAL_ERROR \"The following imported targets are " + "referenced, but are missing: " + "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets}\")\n" + " endif()\n" + "endif()\n" + "unset(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)\n" + "\n"; + /* clang-format on */ +} + +void cmExportCMakeConfigGenerator::GenerateImportedFileCheckLoop( + std::ostream& os) +{ + // Add code which verifies at cmake time that the file which is being + // imported actually exists on disk. This should in theory always be theory + // case, but still when packages are split into normal and development + // packages this might get broken (e.g. the Config.cmake could be part of + // the non-development package, something similar happened to me without + // on SUSE with a mysql pkg-config file, which claimed everything is fine, + // but the development package was not installed.). + /* clang-format off */ + os << "# Loop over all imported files and verify that they actually exist\n" + "foreach(_cmake_target IN LISTS _cmake_import_check_targets)\n" + " if(CMAKE_VERSION VERSION_LESS \"3.28\"\n" + " OR NOT DEFINED " + "_cmake_import_check_xcframework_for_${_cmake_target}\n" + " OR NOT IS_DIRECTORY " + "\"${_cmake_import_check_xcframework_for_${_cmake_target}}\")\n" + " foreach(_cmake_file IN LISTS " + "\"_cmake_import_check_files_for_${_cmake_target}\")\n" + " if(NOT EXISTS \"${_cmake_file}\")\n" + " message(FATAL_ERROR \"The imported target " + "\\\"${_cmake_target}\\\" references the file\n" + " \\\"${_cmake_file}\\\"\n" + "but this file does not exist. Possible reasons include:\n" + "* The file was deleted, renamed, or moved to another location.\n" + "* An install or uninstall procedure did not complete successfully.\n" + "* The installation package was faulty and contained\n" + " \\\"${CMAKE_CURRENT_LIST_FILE}\\\"\n" + "but not all the files it references.\n" + "\")\n" + " endif()\n" + " endforeach()\n" + " endif()\n" + " unset(_cmake_file)\n" + " unset(\"_cmake_import_check_files_for_${_cmake_target}\")\n" + "endforeach()\n" + "unset(_cmake_target)\n" + "unset(_cmake_import_check_targets)\n" + "\n"; + /* clang-format on */ +} + +void cmExportCMakeConfigGenerator::GenerateImportedFileChecksCode( + std::ostream& os, cmGeneratorTarget* target, + ImportPropertyMap const& properties, + const std::set& importedLocations, + const std::string& importedXcFrameworkLocation) +{ + // Construct the imported target name. + std::string targetName = cmStrCat(this->Namespace, target->GetExportName()); + + os << "list(APPEND _cmake_import_check_targets " << targetName << " )\n"; + if (!importedXcFrameworkLocation.empty()) { + os << "set(_cmake_import_check_xcframework_for_" << targetName << ' ' + << cmExportCMakeConfigGeneratorEscape(importedXcFrameworkLocation) + << ")\n"; + } + os << "list(APPEND _cmake_import_check_files_for_" << targetName << " "; + + for (std::string const& li : importedLocations) { + auto pi = properties.find(li); + if (pi != properties.end()) { + os << cmExportCMakeConfigGeneratorEscape(pi->second) << " "; + } + } + + os << ")\n\n"; +} + +enum class ExportWhen +{ + Defined, + Always, +}; + +enum class PropertyType +{ + Strings, + Paths, + IncludePaths, +}; + +namespace { +bool PropertyTypeIsForPaths(PropertyType pt) +{ + switch (pt) { + case PropertyType::Strings: + return false; + case PropertyType::Paths: + case PropertyType::IncludePaths: + return true; + } + return false; +} +} + +struct ModuleTargetPropertyTable +{ + cm::static_string_view Name; + ExportWhen Cond; +}; + +struct ModulePropertyTable +{ + cm::static_string_view Name; + PropertyType Type; +}; + +bool cmExportCMakeConfigGenerator::PopulateCxxModuleExportProperties( + cmGeneratorTarget const* gte, ImportPropertyMap& properties, + cmGeneratorExpression::PreprocessContext ctx, + std::string const& includesDestinationDirs, std::string& errorMessage) +{ + if (!gte->HaveCxx20ModuleSources(&errorMessage)) { + return true; + } + + const ModuleTargetPropertyTable exportedDirectModuleProperties[] = { + { "CXX_EXTENSIONS"_s, ExportWhen::Defined }, + // Always define this property as it is an intrinsic property of the target + // and should not be inherited from the in-scope `CMAKE_CXX_MODULE_STD` + // variable. + // + // TODO(cxxmodules): A future policy may make this "ON" based on the target + // policies if unset. Add a new `ExportWhen` condition to handle it when + // this happens. + { "CXX_MODULE_STD"_s, ExportWhen::Always }, + }; + for (auto const& prop : exportedDirectModuleProperties) { + auto const propNameStr = std::string(prop.Name); + cmValue propValue = gte->Target->GetComputedProperty( + propNameStr, *gte->Target->GetMakefile()); + if (!propValue) { + propValue = gte->Target->GetProperty(propNameStr); + } + if (propValue) { + properties[propNameStr] = + cmGeneratorExpression::Preprocess(*propValue, ctx); + } else if (prop.Cond == ExportWhen::Always) { + properties[propNameStr] = ""; + } + } + + const ModulePropertyTable exportedModuleProperties[] = { + { "INCLUDE_DIRECTORIES"_s, PropertyType::IncludePaths }, + { "COMPILE_DEFINITIONS"_s, PropertyType::Strings }, + { "COMPILE_OPTIONS"_s, PropertyType::Strings }, + { "COMPILE_FEATURES"_s, PropertyType::Strings }, + }; + for (auto const& propEntry : exportedModuleProperties) { + auto const propNameStr = std::string(propEntry.Name); + cmValue prop = gte->Target->GetComputedProperty( + propNameStr, *gte->Target->GetMakefile()); + if (!prop) { + prop = gte->Target->GetProperty(propNameStr); + } + if (prop) { + auto const exportedPropName = + cmStrCat("IMPORTED_CXX_MODULES_", propEntry.Name); + properties[exportedPropName] = + cmGeneratorExpression::Preprocess(*prop, ctx); + if (ctx == cmGeneratorExpression::InstallInterface && + PropertyTypeIsForPaths(propEntry.Type)) { + this->ReplaceInstallPrefix(properties[exportedPropName]); + prefixItems(properties[exportedPropName]); + if (propEntry.Type == PropertyType::IncludePaths && + !includesDestinationDirs.empty()) { + if (!properties[exportedPropName].empty()) { + properties[exportedPropName] += ';'; + } + properties[exportedPropName] += includesDestinationDirs; + } + } + } + } + + const cm::static_string_view exportedLinkModuleProperties[] = { + "LINK_LIBRARIES"_s, + }; + for (auto const& propName : exportedLinkModuleProperties) { + auto const propNameStr = std::string(propName); + cmValue prop = gte->Target->GetComputedProperty( + propNameStr, *gte->Target->GetMakefile()); + if (!prop) { + prop = gte->Target->GetProperty(propNameStr); + } + if (prop) { + auto const exportedPropName = + cmStrCat("IMPORTED_CXX_MODULES_", propName); + auto value = cmGeneratorExpression::Preprocess(*prop, ctx); + this->ResolveTargetsInGeneratorExpressions( + value, gte, cmExportCMakeConfigGenerator::ReplaceFreeTargets); + properties[exportedPropName] = value; + } + } + + return true; +} + +bool cmExportCMakeConfigGenerator::PopulateExportProperties( + cmGeneratorTarget const* gte, ImportPropertyMap& properties, + std::string& errorMessage) +{ + const auto& targetProperties = gte->Target->GetProperties(); + if (cmValue exportProperties = + targetProperties.GetPropertyValue("EXPORT_PROPERTIES")) { + for (auto& prop : cmList{ *exportProperties }) { + /* Black list reserved properties */ + if (cmHasLiteralPrefix(prop, "IMPORTED_") || + cmHasLiteralPrefix(prop, "INTERFACE_")) { + std::ostringstream e; + e << "Target \"" << gte->Target->GetName() << "\" contains property \"" + << prop << "\" in EXPORT_PROPERTIES but IMPORTED_* and INTERFACE_* " + << "properties are reserved."; + errorMessage = e.str(); + return false; + } + cmValue propertyValue = targetProperties.GetPropertyValue(prop); + if (!propertyValue) { + // Asked to export a property that isn't defined on the target. Do not + // consider this an error, there's just nothing to export. + continue; + } + std::string evaluatedValue = cmGeneratorExpression::Preprocess( + *propertyValue, cmGeneratorExpression::StripAllGeneratorExpressions); + if (evaluatedValue != *propertyValue) { + std::ostringstream e; + e << "Target \"" << gte->Target->GetName() << "\" contains property \"" + << prop << "\" in EXPORT_PROPERTIES but this property contains a " + << "generator expression. This is not allowed."; + errorMessage = e.str(); + return false; + } + properties[prop] = *propertyValue; + } + } + return true; +} + +void cmExportCMakeConfigGenerator::GenerateTargetFileSets( + cmGeneratorTarget* gte, std::ostream& os, cmTargetExport* te) +{ + auto interfaceFileSets = gte->Target->GetAllInterfaceFileSets(); + if (!interfaceFileSets.empty()) { + std::string targetName = cmStrCat(this->Namespace, gte->GetExportName()); + os << "if(NOT CMAKE_VERSION VERSION_LESS \"3.23.0\")\n" + " target_sources(" + << targetName << "\n"; + + for (auto const& name : interfaceFileSets) { + auto* fileSet = gte->Target->GetFileSet(name); + if (!fileSet) { + gte->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("File set \"", name, + "\" is listed in interface file sets of ", gte->GetName(), + " but has not been created")); + return; + } + + os << " INTERFACE" + << "\n FILE_SET " << cmOutputConverter::EscapeForCMake(name) + << "\n TYPE " + << cmOutputConverter::EscapeForCMake(fileSet->GetType()) + << "\n BASE_DIRS " + << this->GetFileSetDirectories(gte, fileSet, te) << "\n FILES " + << this->GetFileSetFiles(gte, fileSet, te) << "\n"; + } + + os << " )\nelse()\n set_property(TARGET " << targetName + << "\n APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES"; + for (auto const& name : interfaceFileSets) { + auto* fileSet = gte->Target->GetFileSet(name); + if (!fileSet) { + gte->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("File set \"", name, + "\" is listed in interface file sets of ", gte->GetName(), + " but has not been created")); + return; + } + + if (fileSet->GetType() == "HEADERS"_s) { + os << "\n " << this->GetFileSetDirectories(gte, fileSet, te); + } + } + os << "\n )\nendif()\n\n"; + } +} + +std::string cmExportCMakeConfigGenerator::GetCxxModuleFile( + std::string const& name) const +{ + auto const& cxxModuleDirname = this->GetCxxModulesDirectory(); + if (cxxModuleDirname.empty()) { + return {}; + } + + return cmStrCat(cmSystemTools::GetFilenamePath(this->MainImportFile), '/', + cxxModuleDirname, "/cxx-modules-", name, ".cmake"); +} + +void cmExportCMakeConfigGenerator::GenerateCxxModuleInformation( + std::string const& name, std::ostream& os) +{ + auto const cxx_module_dirname = this->GetCxxModulesDirectory(); + if (cxx_module_dirname.empty()) { + return; + } + + // Write the include. + os << "# Include C++ module properties\n" + << "include(\"${CMAKE_CURRENT_LIST_DIR}/" << cxx_module_dirname + << "/cxx-modules-" << name << ".cmake\")\n\n"; + + // Include all configuration-specific include files. + cmGeneratedFileStream ap(this->GetCxxModuleFile(name), true); + ap.SetCopyIfDifferent(true); + + this->GenerateCxxModuleConfigInformation(name, ap); +} + +void cmExportCMakeConfigGenerator::SetRequiredCMakeVersion(unsigned int major, + unsigned int minor, + unsigned int patch) +{ + if (CMake_VERSION_ENCODE(major, minor, patch) > + CMake_VERSION_ENCODE(this->RequiredCMakeVersionMajor, + this->RequiredCMakeVersionMinor, + this->RequiredCMakeVersionPatch)) { + this->RequiredCMakeVersionMajor = major; + this->RequiredCMakeVersionMinor = minor; + this->RequiredCMakeVersionPatch = patch; + } +} diff --git a/Source/cmExportCMakeConfigGenerator.h b/Source/cmExportCMakeConfigGenerator.h new file mode 100644 index 0000000..f1ab9b4 --- /dev/null +++ b/Source/cmExportCMakeConfigGenerator.h @@ -0,0 +1,264 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include +#include +#include + +#include "cmGeneratorExpression.h" +#include "cmStateTypes.h" +#include "cmVersion.h" +#include "cmVersionConfig.h" + +class cmExportSet; +class cmFileSet; +class cmGeneratorTarget; +class cmLocalGenerator; +class cmTargetExport; + +#define STRINGIFY_HELPER(X) #X +#define STRINGIFY(X) STRINGIFY_HELPER(X) + +#define DEVEL_CMAKE_VERSION(major, minor) \ + (CMake_VERSION_ENCODE(major, minor, 0) > \ + CMake_VERSION_ENCODE(CMake_VERSION_MAJOR, CMake_VERSION_MINOR, 0) \ + ? STRINGIFY(CMake_VERSION_MAJOR) "." STRINGIFY( \ + CMake_VERSION_MINOR) "." STRINGIFY(CMake_VERSION_PATCH) \ + : #major "." #minor ".0") + +/** \class cmExportCMakeConfigGenerator + * \brief Generate a file exporting targets from a build or install tree. + * + * cmExportCMakeConfigGenerator is the superclass for + * cmExportBuildFileGenerator and cmExportInstallFileGenerator. It + * contains common code generation routines for the two kinds of + * export implementations. + */ +class cmExportCMakeConfigGenerator +{ +public: + cmExportCMakeConfigGenerator(); + virtual ~cmExportCMakeConfigGenerator() = default; + + /** Set the full path to the export file to generate. */ + void SetExportFile(const char* mainFile); + const std::string& GetMainExportFileName() const; + + /** Set the namespace in which to place exported target names. */ + void SetNamespace(const std::string& ns) { this->Namespace = ns; } + std::string GetNamespace() const { return this->Namespace; } + + void SetExportOld(bool exportOld) { this->ExportOld = exportOld; } + + /** Add a configuration to be exported. */ + void AddConfiguration(const std::string& config); + + /** Actually generate the export file. Returns whether there was an + error. */ + bool GenerateImportFile(); + + void SetExportPackageDependencies(bool exportPackageDependencies) + { + this->ExportPackageDependencies = exportPackageDependencies; + } + +protected: + using ImportPropertyMap = std::map; + + // Generate per-configuration target information to the given output + // stream. + void GenerateImportConfig(std::ostream& os, const std::string& config); + + // Methods to implement export file code generation. + virtual void GeneratePolicyHeaderCode(std::ostream& os); + virtual void GeneratePolicyFooterCode(std::ostream& os); + virtual void GenerateImportHeaderCode(std::ostream& os, + const std::string& config = ""); + virtual void GenerateImportFooterCode(std::ostream& os); + void GenerateImportVersionCode(std::ostream& os); + virtual void GenerateImportTargetCode(std::ostream& os, + cmGeneratorTarget const* target, + cmStateEnums::TargetType targetType); + virtual void GenerateImportPropertyCode( + std::ostream& os, const std::string& config, const std::string& suffix, + cmGeneratorTarget const* target, ImportPropertyMap const& properties, + const std::string& importedXcFrameworkLocation); + virtual void GenerateImportedFileChecksCode( + std::ostream& os, cmGeneratorTarget* target, + ImportPropertyMap const& properties, + const std::set& importedLocations, + const std::string& importedXcFrameworkLocation); + virtual void GenerateImportedFileCheckLoop(std::ostream& os); + virtual void GenerateMissingTargetsCheckCode(std::ostream& os); + virtual void GenerateFindDependencyCalls(std::ostream& os); + + virtual void GenerateExpectedTargetsCode(std::ostream& os, + const std::string& expectedTargets); + + // Collect properties with detailed information about targets beyond + // their location on disk. + void SetImportDetailProperties(const std::string& config, + std::string const& suffix, + cmGeneratorTarget* target, + ImportPropertyMap& properties); + + enum class ImportLinkPropertyTargetNames + { + Yes, + No, + }; + template + void SetImportLinkProperty(std::string const& suffix, + cmGeneratorTarget const* target, + const std::string& propName, + std::vector const& entries, + ImportPropertyMap& properties, + ImportLinkPropertyTargetNames targetNames); + + /** Each subclass knows how to generate its kind of export file. */ + virtual bool GenerateMainFile(std::ostream& os) = 0; + + /** Each subclass knows where the target files are located. */ + virtual void GenerateImportTargetsConfig(std::ostream& os, + const std::string& config, + std::string const& suffix) = 0; + + /** Each subclass knows how to deal with a target that is missing from an + * export set. */ + virtual void HandleMissingTarget(std::string& link_libs, + cmGeneratorTarget const* depender, + cmGeneratorTarget* dependee) = 0; + void PopulateInterfaceProperty(const std::string&, + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext, + ImportPropertyMap& properties); + bool PopulateInterfaceLinkLibrariesProperty( + cmGeneratorTarget const* target, cmGeneratorExpression::PreprocessContext, + ImportPropertyMap& properties); + void PopulateInterfaceProperty(const std::string& propName, + cmGeneratorTarget const* target, + ImportPropertyMap& properties); + void PopulateCompatibleInterfaceProperties(cmGeneratorTarget const* target, + ImportPropertyMap& properties); + void PopulateCustomTransitiveInterfaceProperties( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties); + virtual void GenerateInterfaceProperties( + cmGeneratorTarget const* target, std::ostream& os, + const ImportPropertyMap& properties); + void PopulateIncludeDirectoriesInterface( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties, cmTargetExport const& te, + std::string& includesDestinationDirs); + void PopulateSourcesInterface( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties); + void PopulateLinkDirectoriesInterface( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties); + void PopulateLinkDependsInterface( + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext preprocessRule, + ImportPropertyMap& properties); + + void SetImportLinkInterface( + const std::string& config, std::string const& suffix, + cmGeneratorExpression::PreprocessContext preprocessRule, + cmGeneratorTarget const* target, ImportPropertyMap& properties); + + enum FreeTargetsReplace + { + ReplaceFreeTargets, + NoReplaceFreeTargets + }; + + void ResolveTargetsInGeneratorExpressions( + std::string& input, cmGeneratorTarget const* target, + FreeTargetsReplace replace = NoReplaceFreeTargets); + + bool PopulateCxxModuleExportProperties( + cmGeneratorTarget const* gte, ImportPropertyMap& properties, + cmGeneratorExpression::PreprocessContext ctx, + std::string const& includesDestinationDirs, std::string& errorMessage); + bool PopulateExportProperties(cmGeneratorTarget const* gte, + ImportPropertyMap& properties, + std::string& errorMessage); + + void GenerateTargetFileSets(cmGeneratorTarget* gte, std::ostream& os, + cmTargetExport* te = nullptr); + + void GenerateCxxModuleInformation(std::string const& name, std::ostream& os); + + virtual std::string GetFileSetDirectories(cmGeneratorTarget* gte, + cmFileSet* fileSet, + cmTargetExport* te) = 0; + virtual std::string GetFileSetFiles(cmGeneratorTarget* gte, + cmFileSet* fileSet, + cmTargetExport* te) = 0; + + virtual cmExportSet* GetExportSet() const { return nullptr; } + + std::string GetCxxModuleFile(std::string const& name) const; + + void SetRequiredCMakeVersion(unsigned int major, unsigned int minor, + unsigned int patch); + + // The namespace in which the exports are placed in the generated file. + std::string Namespace; + + bool ExportOld; + + // The set of configurations to export. + std::vector Configurations; + + // The file to generate. + std::string MainImportFile; + std::string FileDir; + std::string FileBase; + std::string FileExt; + bool AppendMode; + + // The set of targets included in the export. + std::set ExportedTargets; + + std::vector MissingTargets; + + std::set ExternalTargets; + + unsigned int RequiredCMakeVersionMajor = 2; + unsigned int RequiredCMakeVersionMinor = 8; + unsigned int RequiredCMakeVersionPatch = 3; + + bool ExportPackageDependencies = false; + +private: + void PopulateInterfaceProperty(const std::string&, const std::string&, + cmGeneratorTarget const* target, + cmGeneratorExpression::PreprocessContext, + ImportPropertyMap& properties); + + bool AddTargetNamespace(std::string& input, cmGeneratorTarget const* target, + cmLocalGenerator const* lg); + + void ResolveTargetsInGeneratorExpression(std::string& input, + cmGeneratorTarget const* target, + cmLocalGenerator const* lg); + + virtual void ReplaceInstallPrefix(std::string& input); + + virtual std::string InstallNameDir(cmGeneratorTarget const* target, + const std::string& config) = 0; + + virtual std::string GetCxxModulesDirectory() const = 0; + virtual void GenerateCxxModuleConfigInformation(std::string const&, + std::ostream& os) const = 0; +}; diff --git a/Source/cmExportInstallCMakeConfigGenerator.cxx b/Source/cmExportInstallCMakeConfigGenerator.cxx new file mode 100644 index 0000000..a84f1f2 --- /dev/null +++ b/Source/cmExportInstallCMakeConfigGenerator.cxx @@ -0,0 +1,800 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmExportInstallCMakeConfigGenerator.h" + +#include +#include +#include +#include + +#include +#include + +#include "cmExportSet.h" +#include "cmFileSet.h" +#include "cmGeneratedFileStream.h" +#include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" +#include "cmInstallExportGenerator.h" +#include "cmInstallFileSetGenerator.h" +#include "cmInstallTargetGenerator.h" +#include "cmList.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmOutputConverter.h" +#include "cmPolicies.h" +#include "cmStateTypes.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmTarget.h" +#include "cmTargetExport.h" +#include "cmValue.h" + +cmExportInstallCMakeConfigGenerator::cmExportInstallCMakeConfigGenerator( + cmInstallExportGenerator* iegen) + : IEGen(iegen) +{ +} + +std::string cmExportInstallCMakeConfigGenerator::GetConfigImportFileGlob() +{ + std::string glob = cmStrCat(this->FileBase, "-*", this->FileExt); + return glob; +} + +bool cmExportInstallCMakeConfigGenerator::GenerateMainFile(std::ostream& os) +{ + std::vector allTargets; + { + std::string expectedTargets; + std::string sep; + for (std::unique_ptr const& te : + this->GetExportSet()->GetTargetExports()) { + if (te->NamelinkOnly) { + continue; + } + expectedTargets += sep + this->Namespace + te->Target->GetExportName(); + sep = " "; + if (this->ExportedTargets.insert(te->Target).second) { + allTargets.push_back(te.get()); + } else { + std::ostringstream e; + e << "install(EXPORT \"" << this->GetExportSet()->GetName() + << "\" ...) " + << "includes target \"" << te->Target->GetName() + << "\" more than once in the export set."; + cmSystemTools::Error(e.str()); + return false; + } + } + + this->GenerateExpectedTargetsCode(os, expectedTargets); + } + + // Compute the relative import prefix for the file + this->GenerateImportPrefix(os); + + bool requiresConfigFiles = false; + // Create all the imported targets. + for (cmTargetExport* te : allTargets) { + cmGeneratorTarget* gt = te->Target; + cmStateEnums::TargetType targetType = this->GetExportTargetType(te); + + requiresConfigFiles = + requiresConfigFiles || targetType != cmStateEnums::INTERFACE_LIBRARY; + + this->GenerateImportTargetCode(os, gt, targetType); + + ImportPropertyMap properties; + + std::string includesDestinationDirs; + this->PopulateIncludeDirectoriesInterface( + gt, cmGeneratorExpression::InstallInterface, properties, *te, + includesDestinationDirs); + this->PopulateSourcesInterface(gt, cmGeneratorExpression::InstallInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_SYSTEM_INCLUDE_DIRECTORIES", gt, + cmGeneratorExpression::InstallInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS", gt, + cmGeneratorExpression::InstallInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_COMPILE_OPTIONS", gt, + cmGeneratorExpression::InstallInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_PRECOMPILE_HEADERS", gt, + cmGeneratorExpression::InstallInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gt, + cmGeneratorExpression::InstallInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_AUTOMOC_MACRO_NAMES", gt, + cmGeneratorExpression::InstallInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gt, + cmGeneratorExpression::InstallInterface, + properties); + this->PopulateInterfaceProperty("INTERFACE_LINK_OPTIONS", gt, + cmGeneratorExpression::InstallInterface, + properties); + this->PopulateLinkDirectoriesInterface( + gt, cmGeneratorExpression::InstallInterface, properties); + this->PopulateLinkDependsInterface( + gt, cmGeneratorExpression::InstallInterface, properties); + + std::string errorMessage; + if (!this->PopulateCxxModuleExportProperties( + gt, properties, cmGeneratorExpression::InstallInterface, + includesDestinationDirs, errorMessage)) { + cmSystemTools::Error(errorMessage); + return false; + } + + if (!this->PopulateExportProperties(gt, properties, errorMessage)) { + cmSystemTools::Error(errorMessage); + return false; + } + + const bool newCMP0022Behavior = + gt->GetPolicyStatusCMP0022() != cmPolicies::WARN && + gt->GetPolicyStatusCMP0022() != cmPolicies::OLD; + if (newCMP0022Behavior) { + if (this->PopulateInterfaceLinkLibrariesProperty( + gt, cmGeneratorExpression::InstallInterface, properties) && + !this->ExportOld) { + this->SetRequiredCMakeVersion(2, 8, 12); + } + } + if (targetType == cmStateEnums::INTERFACE_LIBRARY) { + this->SetRequiredCMakeVersion(3, 0, 0); + } + if (gt->GetProperty("INTERFACE_SOURCES")) { + // We can only generate INTERFACE_SOURCES in CMake 3.3, but CMake 3.1 + // can consume them. + this->SetRequiredCMakeVersion(3, 1, 0); + } + + this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE", gt, + properties); + + this->PopulateCompatibleInterfaceProperties(gt, properties); + this->PopulateCustomTransitiveInterfaceProperties( + gt, cmGeneratorExpression::InstallInterface, properties); + + this->GenerateInterfaceProperties(gt, os, properties); + + this->GenerateTargetFileSets(gt, os, te); + } + + this->LoadConfigFiles(os); + + bool result = true; + + std::string cxx_modules_name = this->GetExportSet()->GetName(); + this->GenerateCxxModuleInformation(cxx_modules_name, os); + if (requiresConfigFiles) { + for (std::string const& c : this->Configurations) { + if (!this->GenerateImportCxxModuleConfigTargetInclusion(cxx_modules_name, + c)) { + result = false; + } + } + } + + this->CleanupTemporaryVariables(os); + this->GenerateImportedFileCheckLoop(os); + + // Generate an import file for each configuration. + // Don't do this if we only export INTERFACE_LIBRARY targets. + if (requiresConfigFiles) { + for (std::string const& c : this->Configurations) { + if (!this->GenerateImportFileConfig(c)) { + result = false; + } + } + } + + this->GenerateMissingTargetsCheckCode(os); + + return result; +} + +void cmExportInstallCMakeConfigGenerator::GenerateImportPrefix( + std::ostream& os) +{ + // Set an _IMPORT_PREFIX variable for import location properties + // to reference if they are relative to the install prefix. + std::string installPrefix = + this->IEGen->GetLocalGenerator()->GetMakefile()->GetSafeDefinition( + "CMAKE_INSTALL_PREFIX"); + std::string const& expDest = this->IEGen->GetDestination(); + if (cmSystemTools::FileIsFullPath(expDest)) { + // The export file is being installed to an absolute path so the + // package is not relocatable. Use the configured install prefix. + /* clang-format off */ + os << + "# The installation prefix configured by this project.\n" + "set(_IMPORT_PREFIX \"" << installPrefix << "\")\n" + "\n"; + /* clang-format on */ + } else { + // Add code to compute the installation prefix relative to the + // import file location. + std::string absDest = installPrefix + "/" + expDest; + std::string absDestS = absDest + "/"; + os << "# Compute the installation prefix relative to this file.\n" + << "get_filename_component(_IMPORT_PREFIX" + << " \"${CMAKE_CURRENT_LIST_FILE}\" PATH)\n"; + if (cmHasLiteralPrefix(absDestS, "/lib/") || + cmHasLiteralPrefix(absDestS, "/lib64/") || + cmHasLiteralPrefix(absDestS, "/libx32/") || + cmHasLiteralPrefix(absDestS, "/usr/lib/") || + cmHasLiteralPrefix(absDestS, "/usr/lib64/") || + cmHasLiteralPrefix(absDestS, "/usr/libx32/")) { + // Handle "/usr move" symlinks created by some Linux distros. + /* clang-format off */ + os << + "# Use original install prefix when loaded through a\n" + "# cross-prefix symbolic link such as /lib -> /usr/lib.\n" + "get_filename_component(_realCurr \"${_IMPORT_PREFIX}\" REALPATH)\n" + "get_filename_component(_realOrig \"" << absDest << "\" REALPATH)\n" + "if(_realCurr STREQUAL _realOrig)\n" + " set(_IMPORT_PREFIX \"" << absDest << "\")\n" + "endif()\n" + "unset(_realOrig)\n" + "unset(_realCurr)\n"; + /* clang-format on */ + } + std::string dest = expDest; + while (!dest.empty()) { + os << "get_filename_component(_IMPORT_PREFIX \"${_IMPORT_PREFIX}\" " + "PATH)\n"; + dest = cmSystemTools::GetFilenamePath(dest); + } + os << "if(_IMPORT_PREFIX STREQUAL \"/\")\n" + << " set(_IMPORT_PREFIX \"\")\n" + << "endif()\n" + << "\n"; + } +} + +void cmExportInstallCMakeConfigGenerator::CleanupTemporaryVariables( + std::ostream& os) +{ + /* clang-format off */ + os << "# Cleanup temporary variables.\n" + << "set(_IMPORT_PREFIX)\n" + << "\n"; + /* clang-format on */ +} + +void cmExportInstallCMakeConfigGenerator::LoadConfigFiles(std::ostream& os) +{ + // Now load per-configuration properties for them. + /* clang-format off */ + os << "# Load information for each installed configuration.\n" + << "file(GLOB _cmake_config_files \"${CMAKE_CURRENT_LIST_DIR}/" + << this->GetConfigImportFileGlob() << "\")\n" + << "foreach(_cmake_config_file IN LISTS _cmake_config_files)\n" + << " include(\"${_cmake_config_file}\")\n" + << "endforeach()\n" + << "unset(_cmake_config_file)\n" + << "unset(_cmake_config_files)\n" + << "\n"; + /* clang-format on */ +} + +void cmExportInstallCMakeConfigGenerator::ReplaceInstallPrefix( + std::string& input) +{ + cmGeneratorExpression::ReplaceInstallPrefix(input, "${_IMPORT_PREFIX}"); +} + +bool cmExportInstallCMakeConfigGenerator::GenerateImportFileConfig( + const std::string& config) +{ + // Skip configurations not enabled for this export. + if (!this->IEGen->InstallsForConfig(config)) { + return true; + } + + // Construct the name of the file to generate. + std::string fileName = cmStrCat(this->FileDir, '/', this->FileBase, '-'); + if (!config.empty()) { + fileName += cmSystemTools::LowerCase(config); + } else { + fileName += "noconfig"; + } + fileName += this->FileExt; + + // Open the output file to generate it. + cmGeneratedFileStream exportFileStream(fileName, true); + if (!exportFileStream) { + std::string se = cmSystemTools::GetLastSystemError(); + std::ostringstream e; + e << "cannot write to file \"" << fileName << "\": " << se; + cmSystemTools::Error(e.str()); + return false; + } + exportFileStream.SetCopyIfDifferent(true); + std::ostream& os = exportFileStream; + + // Start with the import file header. + this->GenerateImportHeaderCode(os, config); + + // Generate the per-config target information. + this->GenerateImportConfig(os, config); + + // End with the import file footer. + this->GenerateImportFooterCode(os); + + // Record this per-config import file. + this->ConfigImportFiles[config] = fileName; + + return true; +} + +void cmExportInstallCMakeConfigGenerator::GenerateImportTargetsConfig( + std::ostream& os, const std::string& config, std::string const& suffix) +{ + // Add each target in the set to the export. + for (std::unique_ptr const& te : + this->GetExportSet()->GetTargetExports()) { + // Collect import properties for this target. + if (this->GetExportTargetType(te.get()) == + cmStateEnums::INTERFACE_LIBRARY) { + continue; + } + + ImportPropertyMap properties; + std::set importedLocations; + + this->SetImportLocationProperty(config, suffix, te->ArchiveGenerator, + properties, importedLocations); + this->SetImportLocationProperty(config, suffix, te->LibraryGenerator, + properties, importedLocations); + this->SetImportLocationProperty(config, suffix, te->RuntimeGenerator, + properties, importedLocations); + this->SetImportLocationProperty(config, suffix, te->ObjectsGenerator, + properties, importedLocations); + this->SetImportLocationProperty(config, suffix, te->FrameworkGenerator, + properties, importedLocations); + this->SetImportLocationProperty(config, suffix, te->BundleGenerator, + properties, importedLocations); + + // If any file location was set for the target add it to the + // import file. + if (!properties.empty()) { + // Get the rest of the target details. + cmGeneratorTarget* gtgt = te->Target; + this->SetImportDetailProperties(config, suffix, gtgt, properties); + + this->SetImportLinkInterface(config, suffix, + cmGeneratorExpression::InstallInterface, + gtgt, properties); + + // TODO: PUBLIC_HEADER_LOCATION + // This should wait until the build feature propagation stuff + // is done. Then this can be a propagated include directory. + // this->GenerateImportProperty(config, te->HeaderGenerator, + // properties); + + // Generate code in the export file. + std::string importedXcFrameworkLocation = te->XcFrameworkLocation; + if (!importedXcFrameworkLocation.empty()) { + importedXcFrameworkLocation = cmGeneratorExpression::Preprocess( + importedXcFrameworkLocation, + cmGeneratorExpression::PreprocessContext::InstallInterface, true); + importedXcFrameworkLocation = cmGeneratorExpression::Evaluate( + importedXcFrameworkLocation, te->Target->GetLocalGenerator(), config, + te->Target, nullptr, te->Target); + if (!importedXcFrameworkLocation.empty() && + !cmSystemTools::FileIsFullPath(importedXcFrameworkLocation) && + !cmHasLiteralPrefix(importedXcFrameworkLocation, + "${_IMPORT_PREFIX}/")) { + importedXcFrameworkLocation = + cmStrCat("${_IMPORT_PREFIX}/", importedXcFrameworkLocation); + } + } + this->GenerateImportPropertyCode(os, config, suffix, gtgt, properties, + importedXcFrameworkLocation); + this->GenerateImportedFileChecksCode( + os, gtgt, properties, importedLocations, importedXcFrameworkLocation); + } + } +} + +void cmExportInstallCMakeConfigGenerator::SetImportLocationProperty( + const std::string& config, std::string const& suffix, + cmInstallTargetGenerator* itgen, ImportPropertyMap& properties, + std::set& importedLocations) +{ + // Skip rules that do not match this configuration. + if (!(itgen && itgen->InstallsForConfig(config))) { + return; + } + + // Get the target to be installed. + cmGeneratorTarget* target = itgen->GetTarget(); + + // Construct the installed location of the target. + std::string dest = itgen->GetDestination(config); + std::string value; + if (!cmSystemTools::FileIsFullPath(dest)) { + // The target is installed relative to the installation prefix. + value = "${_IMPORT_PREFIX}/"; + } + value += dest; + value += "/"; + + if (itgen->IsImportLibrary()) { + // Construct the property name. + std::string prop = cmStrCat("IMPORTED_IMPLIB", suffix); + + // Append the installed file name. + value += cmInstallTargetGenerator::GetInstallFilename( + target, config, cmInstallTargetGenerator::NameImplibReal); + + // Store the property. + properties[prop] = value; + importedLocations.insert(prop); + } else if (itgen->GetTarget()->GetType() == cmStateEnums::OBJECT_LIBRARY) { + // Construct the property name. + std::string prop = cmStrCat("IMPORTED_OBJECTS", suffix); + + // Compute all the object files inside this target and setup + // IMPORTED_OBJECTS as a list of object files + std::vector objects; + itgen->GetInstallObjectNames(config, objects); + for (std::string& obj : objects) { + obj = cmStrCat(value, obj); + } + + // Store the property. + properties[prop] = cmList::to_string(objects); + importedLocations.insert(prop); + } else { + if (target->IsFrameworkOnApple() && target->HasImportLibrary(config)) { + // store as well IMPLIB value + auto importProp = cmStrCat("IMPORTED_IMPLIB", suffix); + auto importValue = + cmStrCat(value, + cmInstallTargetGenerator::GetInstallFilename( + target, config, cmInstallTargetGenerator::NameImplibReal)); + + // Store the property. + properties[importProp] = importValue; + importedLocations.insert(importProp); + } + + // Construct the property name. + std::string prop = cmStrCat("IMPORTED_LOCATION", suffix); + + // Append the installed file name. + if (target->IsAppBundleOnApple()) { + value += cmInstallTargetGenerator::GetInstallFilename(target, config); + value += ".app/"; + if (!target->Makefile->PlatformIsAppleEmbedded()) { + value += "Contents/MacOS/"; + } + value += cmInstallTargetGenerator::GetInstallFilename(target, config); + } else { + value += cmInstallTargetGenerator::GetInstallFilename( + target, config, cmInstallTargetGenerator::NameReal); + } + + // Store the property. + properties[prop] = value; + importedLocations.insert(prop); + } +} + +cmStateEnums::TargetType +cmExportInstallCMakeConfigGenerator::GetExportTargetType( + cmTargetExport const* targetExport) const +{ + cmStateEnums::TargetType targetType = targetExport->Target->GetType(); + // An OBJECT library installed with no OBJECTS DESTINATION + // is transformed to an INTERFACE library. + if (targetType == cmStateEnums::OBJECT_LIBRARY && + targetExport->ObjectsGenerator == nullptr) { + targetType = cmStateEnums::INTERFACE_LIBRARY; + } + return targetType; +} + +void cmExportInstallCMakeConfigGenerator::HandleMissingTarget( + std::string& link_libs, cmGeneratorTarget const* depender, + cmGeneratorTarget* dependee) +{ + const std::string name = dependee->GetName(); + cmGlobalGenerator* gg = dependee->GetLocalGenerator()->GetGlobalGenerator(); + auto exportInfo = this->FindNamespaces(gg, name); + std::vector const& exportFiles = exportInfo.first; + if (exportFiles.size() == 1) { + std::string missingTarget = exportInfo.second; + + missingTarget += dependee->GetExportName(); + link_libs += missingTarget; + this->MissingTargets.emplace_back(std::move(missingTarget)); + } else { + // All exported targets should be known here and should be unique. + // This is probably user-error. + this->ComplainAboutMissingTarget(depender, dependee, exportFiles); + } +} + +std::pair, std::string> +cmExportInstallCMakeConfigGenerator::FindNamespaces(cmGlobalGenerator* gg, + const std::string& name) +{ + std::vector exportFiles; + std::string ns; + const cmExportSetMap& exportSets = gg->GetExportSets(); + + for (auto const& expIt : exportSets) { + const cmExportSet& exportSet = expIt.second; + + bool containsTarget = false; + for (auto const& target : exportSet.GetTargetExports()) { + if (name == target->TargetName) { + containsTarget = true; + break; + } + } + + if (containsTarget) { + std::vector const* installs = + exportSet.GetInstallations(); + for (cmInstallExportGenerator const* install : *installs) { + exportFiles.push_back(install->GetDestinationFile()); + ns = install->GetNamespace(); + } + } + } + + return { exportFiles, ns }; +} + +void cmExportInstallCMakeConfigGenerator::ComplainAboutMissingTarget( + cmGeneratorTarget const* depender, cmGeneratorTarget const* dependee, + std::vector const& exportFiles) +{ + std::ostringstream e; + e << "install(EXPORT \"" << this->GetExportSet()->GetName() << "\" ...) " + << "includes target \"" << depender->GetName() + << "\" which requires target \"" << dependee->GetName() << "\" "; + if (exportFiles.empty()) { + e << "that is not in any export set."; + } else { + e << "that is not in this export set, but in multiple other export sets: " + << cmJoin(exportFiles, ", ") << ".\n"; + e << "An exported target cannot depend upon another target which is " + "exported multiple times. Consider consolidating the exports of the " + "\"" + << dependee->GetName() << "\" target to a single export."; + } + cmSystemTools::Error(e.str()); +} + +std::string cmExportInstallCMakeConfigGenerator::InstallNameDir( + cmGeneratorTarget const* target, const std::string& config) +{ + std::string install_name_dir; + + cmMakefile* mf = target->Target->GetMakefile(); + if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { + install_name_dir = + target->GetInstallNameDirForInstallTree(config, "${_IMPORT_PREFIX}"); + } + + return install_name_dir; +} + +namespace { +bool EntryIsContextSensitive( + const std::unique_ptr& cge) +{ + return cge->GetHadContextSensitiveCondition(); +} +} + +std::string cmExportInstallCMakeConfigGenerator::GetFileSetDirectories( + cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport* te) +{ + std::vector resultVector; + + auto configs = + gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); + + cmGeneratorExpression ge(*gte->Makefile->GetCMakeInstance()); + auto cge = ge.Parse(te->FileSetGenerators.at(fileSet)->GetDestination()); + + for (auto const& config : configs) { + auto unescapedDest = cge->Evaluate(gte->LocalGenerator, config, gte); + auto dest = cmOutputConverter::EscapeForCMake( + unescapedDest, cmOutputConverter::WrapQuotes::NoWrap); + if (!cmSystemTools::FileIsFullPath(unescapedDest)) { + dest = cmStrCat("${_IMPORT_PREFIX}/", dest); + } + + auto const& type = fileSet->GetType(); + // C++ modules do not support interface file sets which are dependent upon + // the configuration. + if (cge->GetHadContextSensitiveCondition() && type == "CXX_MODULES"_s) { + auto* mf = this->IEGen->GetLocalGenerator()->GetMakefile(); + std::ostringstream e; + e << "The \"" << gte->GetName() << "\" target's interface file set \"" + << fileSet->GetName() << "\" of type \"" << type + << "\" contains context-sensitive base file entries which is not " + "supported."; + mf->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return std::string{}; + } + + if (cge->GetHadContextSensitiveCondition() && configs.size() != 1) { + resultVector.push_back( + cmStrCat("\"$<$:", dest, ">\"")); + } else { + resultVector.emplace_back(cmStrCat('"', dest, '"')); + break; + } + } + + return cmJoin(resultVector, " "); +} + +std::string cmExportInstallCMakeConfigGenerator::GetFileSetFiles( + cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport* te) +{ + std::vector resultVector; + + auto configs = + gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); + + auto fileEntries = fileSet->CompileFileEntries(); + auto directoryEntries = fileSet->CompileDirectoryEntries(); + + cmGeneratorExpression destGe(*gte->Makefile->GetCMakeInstance()); + auto destCge = + destGe.Parse(te->FileSetGenerators.at(fileSet)->GetDestination()); + + for (auto const& config : configs) { + auto directories = fileSet->EvaluateDirectoryEntries( + directoryEntries, gte->LocalGenerator, config, gte); + + std::map> files; + for (auto const& entry : fileEntries) { + fileSet->EvaluateFileEntry(directories, files, entry, + gte->LocalGenerator, config, gte); + } + auto unescapedDest = destCge->Evaluate(gte->LocalGenerator, config, gte); + auto dest = + cmStrCat(cmOutputConverter::EscapeForCMake( + unescapedDest, cmOutputConverter::WrapQuotes::NoWrap), + '/'); + if (!cmSystemTools::FileIsFullPath(unescapedDest)) { + dest = cmStrCat("${_IMPORT_PREFIX}/", dest); + } + + bool const contextSensitive = destCge->GetHadContextSensitiveCondition() || + std::any_of(directoryEntries.begin(), directoryEntries.end(), + EntryIsContextSensitive) || + std::any_of(fileEntries.begin(), fileEntries.end(), + EntryIsContextSensitive); + + auto const& type = fileSet->GetType(); + // C++ modules do not support interface file sets which are dependent upon + // the configuration. + if (contextSensitive && type == "CXX_MODULES"_s) { + auto* mf = this->IEGen->GetLocalGenerator()->GetMakefile(); + std::ostringstream e; + e << "The \"" << gte->GetName() << "\" target's interface file set \"" + << fileSet->GetName() << "\" of type \"" << type + << "\" contains context-sensitive base file entries which is not " + "supported."; + mf->IssueMessage(MessageType::FATAL_ERROR, e.str()); + return std::string{}; + } + + for (auto const& it : files) { + auto prefix = it.first.empty() ? "" : cmStrCat(it.first, '/'); + for (auto const& filename : it.second) { + auto relFile = + cmStrCat(prefix, cmSystemTools::GetFilenameName(filename)); + auto escapedFile = + cmStrCat(dest, + cmOutputConverter::EscapeForCMake( + relFile, cmOutputConverter::WrapQuotes::NoWrap)); + if (contextSensitive && configs.size() != 1) { + resultVector.push_back( + cmStrCat("\"$<$:", escapedFile, ">\"")); + } else { + resultVector.emplace_back(cmStrCat('"', escapedFile, '"')); + } + } + } + + if (!(contextSensitive && configs.size() != 1)) { + break; + } + } + + return cmJoin(resultVector, " "); +} + +std::string cmExportInstallCMakeConfigGenerator::GetCxxModulesDirectory() const +{ + return IEGen->GetCxxModuleDirectory(); +} + +void cmExportInstallCMakeConfigGenerator::GenerateCxxModuleConfigInformation( + std::string const& name, std::ostream& os) const +{ + // Now load per-configuration properties for them. + /* clang-format off */ + os << "# Load information for each installed configuration.\n" + "file(GLOB _cmake_cxx_module_includes \"${CMAKE_CURRENT_LIST_DIR}/cxx-modules-" << name << "-*.cmake\")\n" + "foreach(_cmake_cxx_module_include IN LISTS _cmake_cxx_module_includes)\n" + " include(\"${_cmake_cxx_module_include}\")\n" + "endforeach()\n" + "unset(_cmake_cxx_module_include)\n" + "unset(_cmake_cxx_module_includes)\n"; + /* clang-format on */ +} + +std::string cmExportInstallCMakeConfigGenerator::GetCxxModuleFile() const +{ + return this->GetCxxModuleFile(this->GetExportSet()->GetName()); +} + +bool cmExportInstallCMakeConfigGenerator:: + GenerateImportCxxModuleConfigTargetInclusion(std::string const& name, + std::string const& config) +{ + auto cxx_modules_dirname = this->GetCxxModulesDirectory(); + if (cxx_modules_dirname.empty()) { + return true; + } + + std::string filename_config = config; + if (filename_config.empty()) { + filename_config = "noconfig"; + } + + std::string const dest = + cmStrCat(this->FileDir, '/', cxx_modules_dirname, '/'); + std::string fileName = + cmStrCat(dest, "cxx-modules-", name, '-', filename_config, ".cmake"); + + cmGeneratedFileStream os(fileName, true); + if (!os) { + std::string se = cmSystemTools::GetLastSystemError(); + std::ostringstream e; + e << "cannot write to file \"" << fileName << "\": " << se; + cmSystemTools::Error(e.str()); + return false; + } + os.SetCopyIfDifferent(true); + + // Record this per-config import file. + this->ConfigCxxModuleFiles[config] = fileName; + + auto& prop_files = this->ConfigCxxModuleTargetFiles[config]; + for (auto const* tgt : this->ExportedTargets) { + // Only targets with C++ module sources will have a + // collator-generated install script. + if (!tgt->HaveCxx20ModuleSources()) { + continue; + } + + auto prop_filename = cmStrCat("target-", tgt->GetFilesystemExportName(), + '-', filename_config, ".cmake"); + prop_files.emplace_back(cmStrCat(dest, prop_filename)); + os << "include(\"${CMAKE_CURRENT_LIST_DIR}/" << prop_filename << "\")\n"; + } + + return true; +} diff --git a/Source/cmExportInstallCMakeConfigGenerator.h b/Source/cmExportInstallCMakeConfigGenerator.h new file mode 100644 index 0000000..0411477 --- /dev/null +++ b/Source/cmExportInstallCMakeConfigGenerator.h @@ -0,0 +1,145 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include +#include +#include +#include + +#include "cmExportFileGenerator.h" +#include "cmInstallExportGenerator.h" +#include "cmStateTypes.h" + +class cmExportSet; +class cmFileSet; +class cmGeneratorTarget; +class cmGlobalGenerator; +class cmInstallTargetGenerator; +class cmTargetExport; + +/** \class cmExportInstallCMakeConfigGenerator + * \brief Generate a file exporting targets from an install tree. + * + * cmExportInstallCMakeConfigGenerator generates files exporting targets from + * install an installation tree. The files are placed in a temporary + * location for installation by cmInstallExportGenerator. One main + * file is generated that creates the imported targets and loads + * per-configuration files. Target locations and settings for each + * configuration are written to these per-configuration files. After + * installation the main file loads the configurations that have been + * installed. + * + * This is used to implement the INSTALL(EXPORT) command. + */ +class cmExportInstallCMakeConfigGenerator : public cmExportFileGenerator +{ +public: + /** Construct with the export installer that will install the + files. */ + cmExportInstallCMakeConfigGenerator(cmInstallExportGenerator* iegen); + + /** Get the per-config file generated for each configuration. This + maps from the configuration name to the file temporary location + for installation. */ + std::map const& GetConfigImportFiles() + { + return this->ConfigImportFiles; + } + + /** Get the temporary location of the config-agnostic C++ module file. */ + std::string GetCxxModuleFile() const; + + /** Get the per-config C++ module file generated for each configuration. + This maps from the configuration name to the file temporary location + for installation. */ + std::map const& GetConfigCxxModuleFiles() + { + return this->ConfigCxxModuleFiles; + } + + /** Get the per-config C++ module file generated for each configuration. + This maps from the configuration name to the file temporary location + for installation for each target in the export set. */ + std::map> const& + GetConfigCxxModuleTargetFiles() + { + return this->ConfigCxxModuleTargetFiles; + } + + /** Compute the globbing expression used to load per-config import + files from the main file. */ + std::string GetConfigImportFileGlob(); + +protected: + // Implement virtual methods from the superclass. + bool GenerateMainFile(std::ostream& os) override; + void GenerateImportTargetsConfig(std::ostream& os, const std::string& config, + std::string const& suffix) override; + cmStateEnums::TargetType GetExportTargetType( + cmTargetExport const* targetExport) const; + void HandleMissingTarget(std::string& link_libs, + cmGeneratorTarget const* depender, + cmGeneratorTarget* dependee) override; + + void ReplaceInstallPrefix(std::string& input) override; + + void ComplainAboutMissingTarget(cmGeneratorTarget const* depender, + cmGeneratorTarget const* dependee, + std::vector const& exportFiles); + + std::pair, std::string> FindNamespaces( + cmGlobalGenerator* gg, const std::string& name); + + /** Generate the relative import prefix. */ + virtual void GenerateImportPrefix(std::ostream&); + + /** Generate the relative import prefix. */ + virtual void LoadConfigFiles(std::ostream&); + + virtual void CleanupTemporaryVariables(std::ostream&); + + /** Generate a per-configuration file for the targets. */ + virtual bool GenerateImportFileConfig(const std::string& config); + + /** Fill in properties indicating installed file locations. */ + void SetImportLocationProperty(const std::string& config, + std::string const& suffix, + cmInstallTargetGenerator* itgen, + ImportPropertyMap& properties, + std::set& importedLocations); + + std::string InstallNameDir(cmGeneratorTarget const* target, + const std::string& config) override; + + std::string GetFileSetDirectories(cmGeneratorTarget* gte, cmFileSet* fileSet, + cmTargetExport* te) override; + std::string GetFileSetFiles(cmGeneratorTarget* gte, cmFileSet* fileSet, + cmTargetExport* te) override; + + using cmExportFileGenerator::GetCxxModuleFile; + + std::string GetCxxModulesDirectory() const override; + void GenerateCxxModuleConfigInformation(std::string const&, + std::ostream&) const override; + bool GenerateImportCxxModuleConfigTargetInclusion(std::string const&, + std::string const&); + + cmExportSet* GetExportSet() const override + { + return this->IEGen->GetExportSet(); + } + + cmInstallExportGenerator* IEGen; + + // The import file generated for each configuration. + std::map ConfigImportFiles; + // The C++ module property file generated for each configuration. + std::map ConfigCxxModuleFiles; + // The C++ module property target files generated for each configuration. + std::map> ConfigCxxModuleTargetFiles; +}; -- cgit v0.12