CMake/Source/cmExportBuildFileGenerator.cxx
Ben Boeckel 249cd3efad cmExportFileGenerator: export private compile info for C++ modules
When consuming exported targets which contain C++ modules, the consuming
project must be able to recompile BMI files using the original target's
flags. This is because a module source may use some private target usage
requirement but not want to propagate it to consumers. To facilitate
this, export the private information as necessary for consumers to be
able to perform the BMI compilations.
2023-08-17 14:42:53 -04:00

565 lines
19 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmExportBuildFileGenerator.h"
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <utility>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cmext/string_view>
#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;
cmExportBuildFileGenerator::cmExportBuildFileGenerator()
{
this->LG = nullptr;
this->ExportSet = nullptr;
}
void cmExportBuildFileGenerator::Compute(cmLocalGenerator* lg)
{
this->LG = lg;
if (this->ExportSet) {
this->ExportSet->Compute(lg);
}
}
bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
{
{
std::string expectedTargets;
std::string sep;
std::vector<std::string> targets;
bool generatedInterfaceRequired = false;
this->GetTargets(targets);
for (std::string const& tei : targets) {
cmGeneratorTarget* te = this->LG->FindGeneratorTargetToUse(tei);
expectedTargets += sep + this->Namespace + te->GetExportName();
sep = " ";
if (this->ExportedTargets.insert(te).second) {
this->Exports.push_back(te);
} 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->GenerateRequiredCMakeVersion(os, "3.0.0");
}
this->GenerateExpectedTargetsCode(os, expectedTargets);
}
// Create all the imported targets.
for (cmGeneratorTarget* gte : this->Exports) {
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->GenerateInterfaceProperties(gte, os, properties);
this->GenerateTargetFileSets(gte, os);
}
this->GenerateCxxModuleInformation(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(c);
}
this->GenerateMissingTargetsCheckCode(os);
return true;
}
void cmExportBuildFileGenerator::GenerateImportTargetsConfig(
std::ostream& os, const std::string& config, std::string const& suffix)
{
for (cmGeneratorTarget* target : this->Exports) {
// 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.
this->GenerateImportPropertyCode(os, config, target, properties);
}
}
}
cmStateEnums::TargetType cmExportBuildFileGenerator::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 cmExportBuildFileGenerator::SetExportSet(cmExportSet* exportSet)
{
this->ExportSet = exportSet;
}
void cmExportBuildFileGenerator::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<cmSourceFile const*> objectSources;
target->GetObjectSources(objectSources, config);
std::string const obj_dir = target->GetObjectDirectory(config);
std::vector<std::string> 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 cmExportBuildFileGenerator::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<std::string> 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 cmExportBuildFileGenerator::GetTargets(
std::vector<std::string>& targets) const
{
if (this->ExportSet) {
for (std::unique_ptr<cmTargetExport> const& te :
this->ExportSet->GetTargetExports()) {
if (te->NamelinkOnly) {
continue;
}
targets.push_back(te->TargetName);
}
return;
}
targets = this->Targets;
}
std::pair<std::vector<std::string>, std::string>
cmExportBuildFileGenerator::FindBuildExportInfo(cmGlobalGenerator* gg,
const std::string& name)
{
std::vector<std::string> exportFiles;
std::string ns;
auto& exportSets = gg->GetBuildExportSets();
for (auto const& exp : exportSets) {
const auto& exportSet = exp.second;
std::vector<std::string> targets;
exportSet->GetTargets(targets);
if (cm::contains(targets, name)) {
exportFiles.push_back(exp.first);
ns = exportSet->GetNamespace();
}
}
return { exportFiles, ns };
}
void cmExportBuildFileGenerator::ComplainAboutMissingTarget(
cmGeneratorTarget const* depender, cmGeneratorTarget const* dependee,
std::vector<std::string> 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 cmExportBuildFileGenerator::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<cmCompiledGeneratorExpression>& cge)
{
return cge->GetHadContextSensitiveCondition();
}
}
std::string cmExportBuildFileGenerator::GetFileSetDirectories(
cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport* /*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 cmExportBuildFileGenerator::GetFileSetFiles(cmGeneratorTarget* gte,
cmFileSet* fileSet,
cmTargetExport* /*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, " ");
}
std::string cmExportBuildFileGenerator::GetCxxModulesDirectory() const
{
return this->CxxModulesDirectory;
}
void cmExportBuildFileGenerator::GenerateCxxModuleConfigInformation(
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-" << c << ".cmake\""
<< opt << ")\n";
}
}
bool cmExportBuildFileGenerator::GenerateImportCxxModuleConfigTargetInclusion(
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-", 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->GetExportName()
<< '-' << config << ".cmake\")\n";
}
return true;
}