blob: 87aeb3cbb90f084308ba2ee7b40c750e2537c848 [file] [log] [blame]
/* 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 <algorithm>
#include <cstddef>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <utility>
#include <vector>
#include <cm/string_view>
#include <cmext/string_view>
#include "cmCryptoHash.h"
#include "cmExportSet.h"
#include "cmFileSet.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.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"
cmExportBuildCMakeConfigGenerator::cmExportBuildCMakeConfigGenerator()
{
this->LG = nullptr;
this->ExportSet = nullptr;
}
bool cmExportBuildCMakeConfigGenerator::GenerateMainFile(std::ostream& os)
{
{
std::string expectedTargets;
std::string sep;
bool generatedInterfaceRequired = false;
auto visitor = [&](cmGeneratorTarget const* te) {
expectedTargets += sep + this->Namespace + te->GetExportName();
sep = " ";
generatedInterfaceRequired |=
this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY;
};
if (!this->CollectExports(visitor)) {
return false;
}
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;
if (!this->PopulateInterfaceProperties(gte, properties)) {
return false;
}
bool const newCMP0022Behavior =
gte->GetPolicyStatusCMP0022() != cmPolicies::WARN &&
gte->GetPolicyStatusCMP0022() != cmPolicies::OLD;
if (newCMP0022Behavior) {
this->PopulateInterfaceLinkLibrariesProperty(
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, std::string const& 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);
}
}
}
namespace {
bool EntryIsContextSensitive(
std::unique_ptr<cmCompiledGeneratorExpression> const& cge)
{
return cge->GetHadContextSensitiveCondition();
}
}
std::string cmExportBuildCMakeConfigGenerator::GetFileSetDirectories(
cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport const* /*te*/)
{
std::vector<std::string> 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("\"$<$<CONFIG:", config, ">:", dest, ">\""));
} else {
resultVector.emplace_back(cmStrCat('"', dest, '"'));
break;
}
}
}
return cmJoin(resultVector, " ");
}
std::string cmExportBuildCMakeConfigGenerator::GetFileSetFiles(
cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport const* /*te*/)
{
std::vector<std::string> 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<std::string, std::vector<std::string>> 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("\"$<$<CONFIG:", config, ">:", escapedFile, ">\""));
} else {
resultVector.emplace_back(cmStrCat('"', escapedFile, '"'));
}
}
}
if (!(contextSensitive && configs.size() != 1)) {
break;
}
}
return cmJoin(resultVector, " ");
}
void cmExportBuildCMakeConfigGenerator::GenerateCxxModuleConfigInformation(
std::string const& name, std::ostream& os) const
{
char const* 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;
}