1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmExportBuildFileGenerator.h"
12 #include <cm/string_view>
13 #include <cmext/algorithm>
14 #include <cmext/string_view>
16 #include "cmExportSet.h"
17 #include "cmFileSet.h"
18 #include "cmGeneratedFileStream.h"
19 #include "cmGeneratorExpression.h"
20 #include "cmGeneratorTarget.h"
21 #include "cmGlobalGenerator.h"
22 #include "cmLocalGenerator.h"
23 #include "cmMakefile.h"
24 #include "cmMessageType.h"
25 #include "cmOutputConverter.h"
26 #include "cmPolicies.h"
27 #include "cmStateTypes.h"
28 #include "cmStringAlgorithms.h"
29 #include "cmSystemTools.h"
31 #include "cmTargetExport.h"
37 cmExportBuildFileGenerator::cmExportBuildFileGenerator()
40 this->ExportSet = nullptr;
43 void cmExportBuildFileGenerator::Compute(cmLocalGenerator* lg)
46 if (this->ExportSet) {
47 this->ExportSet->Compute(lg);
51 bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
54 std::string expectedTargets;
56 std::vector<std::string> targets;
57 bool generatedInterfaceRequired = false;
58 this->GetTargets(targets);
59 for (std::string const& tei : targets) {
60 cmGeneratorTarget* te = this->LG->FindGeneratorTargetToUse(tei);
61 expectedTargets += sep + this->Namespace + te->GetExportName();
63 if (this->ExportedTargets.insert(te).second) {
64 this->Exports.push_back(te);
67 e << "given target \"" << te->GetName() << "\" more than once.";
68 this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
69 MessageType::FATAL_ERROR, e.str(),
70 this->LG->GetMakefile()->GetBacktrace());
73 generatedInterfaceRequired |=
74 this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY;
77 if (generatedInterfaceRequired) {
78 this->GenerateRequiredCMakeVersion(os, "3.0.0");
80 this->GenerateExpectedTargetsCode(os, expectedTargets);
83 // Create all the imported targets.
84 for (cmGeneratorTarget* gte : this->Exports) {
85 this->GenerateImportTargetCode(os, gte, this->GetExportTargetType(gte));
87 gte->Target->AppendBuildInterfaceIncludes();
89 ImportPropertyMap properties;
91 this->PopulateInterfaceProperty("INTERFACE_INCLUDE_DIRECTORIES", gte,
92 cmGeneratorExpression::BuildInterface,
94 this->PopulateInterfaceProperty("INTERFACE_SOURCES", gte,
95 cmGeneratorExpression::BuildInterface,
97 this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS", gte,
98 cmGeneratorExpression::BuildInterface,
100 this->PopulateInterfaceProperty("INTERFACE_COMPILE_OPTIONS", gte,
101 cmGeneratorExpression::BuildInterface,
103 this->PopulateInterfaceProperty("INTERFACE_PRECOMPILE_HEADERS", gte,
104 cmGeneratorExpression::BuildInterface,
106 this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gte,
107 cmGeneratorExpression::BuildInterface,
109 this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gte,
110 cmGeneratorExpression::BuildInterface,
112 this->PopulateInterfaceProperty("INTERFACE_LINK_OPTIONS", gte,
113 cmGeneratorExpression::BuildInterface,
115 this->PopulateInterfaceProperty("INTERFACE_LINK_DIRECTORIES", gte,
116 cmGeneratorExpression::BuildInterface,
118 this->PopulateInterfaceProperty("INTERFACE_LINK_DEPENDS", gte,
119 cmGeneratorExpression::BuildInterface,
121 this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE", gte,
124 std::string errorMessage;
125 if (!this->PopulateExportProperties(gte, properties, errorMessage)) {
126 this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
127 MessageType::FATAL_ERROR, errorMessage,
128 this->LG->GetMakefile()->GetBacktrace());
132 const bool newCMP0022Behavior =
133 gte->GetPolicyStatusCMP0022() != cmPolicies::WARN &&
134 gte->GetPolicyStatusCMP0022() != cmPolicies::OLD;
135 if (newCMP0022Behavior) {
136 this->PopulateInterfaceLinkLibrariesProperty(
137 gte, cmGeneratorExpression::BuildInterface, properties);
139 this->PopulateCompatibleInterfaceProperties(gte, properties);
141 this->GenerateInterfaceProperties(gte, os, properties);
143 this->GenerateTargetFileSets(gte, os);
146 this->GenerateCxxModuleInformation(os);
148 // Generate import file content for each configuration.
149 for (std::string const& c : this->Configurations) {
150 this->GenerateImportConfig(os, c);
153 // Generate import file content for each configuration.
154 for (std::string const& c : this->Configurations) {
155 this->GenerateImportCxxModuleConfigTargetInclusion(c);
158 this->GenerateMissingTargetsCheckCode(os);
163 void cmExportBuildFileGenerator::GenerateImportTargetsConfig(
164 std::ostream& os, const std::string& config, std::string const& suffix)
166 for (cmGeneratorTarget* target : this->Exports) {
167 // Collect import properties for this target.
168 ImportPropertyMap properties;
170 if (this->GetExportTargetType(target) != cmStateEnums::INTERFACE_LIBRARY) {
171 this->SetImportLocationProperty(config, suffix, target, properties);
173 if (!properties.empty()) {
174 // Get the rest of the target details.
175 if (this->GetExportTargetType(target) !=
176 cmStateEnums::INTERFACE_LIBRARY) {
177 this->SetImportDetailProperties(config, suffix, target, properties);
178 this->SetImportLinkInterface(config, suffix,
179 cmGeneratorExpression::BuildInterface,
183 // TODO: PUBLIC_HEADER_LOCATION
184 // This should wait until the build feature propagation stuff
185 // is done. Then this can be a propagated include directory.
186 // this->GenerateImportProperty(config, te->HeaderGenerator,
189 // Generate code in the export file.
190 this->GenerateImportPropertyCode(os, config, target, properties);
195 cmStateEnums::TargetType cmExportBuildFileGenerator::GetExportTargetType(
196 cmGeneratorTarget const* target) const
198 cmStateEnums::TargetType targetType = target->GetType();
199 // An object library exports as an interface library if we cannot
200 // tell clients where to find the objects. This is sufficient
201 // to support transitive usage requirements on other targets that
202 // use the object library.
203 if (targetType == cmStateEnums::OBJECT_LIBRARY &&
204 !target->Target->HasKnownObjectFileLocation(nullptr)) {
205 targetType = cmStateEnums::INTERFACE_LIBRARY;
210 void cmExportBuildFileGenerator::SetExportSet(cmExportSet* exportSet)
212 this->ExportSet = exportSet;
215 void cmExportBuildFileGenerator::SetImportLocationProperty(
216 const std::string& config, std::string const& suffix,
217 cmGeneratorTarget* target, ImportPropertyMap& properties)
219 // Get the makefile in which to lookup target information.
220 cmMakefile* mf = target->Makefile;
222 if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
223 std::string prop = cmStrCat("IMPORTED_OBJECTS", suffix);
225 // Compute all the object files inside this target and setup
226 // IMPORTED_OBJECTS as a list of object files
227 std::vector<cmSourceFile const*> objectSources;
228 target->GetObjectSources(objectSources, config);
229 std::string const obj_dir = target->GetObjectDirectory(config);
230 std::vector<std::string> objects;
231 for (cmSourceFile const* sf : objectSources) {
232 const std::string& obj = target->GetObjectName(sf);
233 objects.push_back(obj_dir + obj);
236 // Store the property.
237 properties[prop] = cmJoin(objects, ";");
239 // Add the main target file.
241 std::string prop = cmStrCat("IMPORTED_LOCATION", suffix);
243 if (target->IsAppBundleOnApple()) {
245 target->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact);
247 value = target->GetFullPath(config,
248 cmStateEnums::RuntimeBinaryArtifact, true);
250 properties[prop] = value;
253 // Add the import library for windows DLLs.
254 if (target->HasImportLibrary(config)) {
255 std::string prop = cmStrCat("IMPORTED_IMPLIB", suffix);
257 target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact);
258 if (mf->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX")) {
259 target->GetImplibGNUtoMS(config, value, value,
260 "${CMAKE_IMPORT_LIBRARY_SUFFIX}");
262 properties[prop] = value;
267 void cmExportBuildFileGenerator::HandleMissingTarget(
268 std::string& link_libs, cmGeneratorTarget const* depender,
269 cmGeneratorTarget* dependee)
271 // The target is not in the export.
272 if (!this->AppendMode) {
273 const std::string name = dependee->GetName();
274 cmGlobalGenerator* gg =
275 dependee->GetLocalGenerator()->GetGlobalGenerator();
276 auto exportInfo = this->FindBuildExportInfo(gg, name);
277 std::vector<std::string> const& exportFiles = exportInfo.first;
279 if (exportFiles.size() == 1) {
280 std::string missingTarget = exportInfo.second;
282 missingTarget += dependee->GetExportName();
283 link_libs += missingTarget;
284 this->MissingTargets.emplace_back(std::move(missingTarget));
287 // We are not appending, so all exported targets should be
288 // known here. This is probably user-error.
289 this->ComplainAboutMissingTarget(depender, dependee, exportFiles);
291 // Assume the target will be exported by another command.
292 // Append it with the export namespace.
293 link_libs += this->Namespace;
294 link_libs += dependee->GetExportName();
297 void cmExportBuildFileGenerator::GetTargets(
298 std::vector<std::string>& targets) const
300 if (this->ExportSet) {
301 for (std::unique_ptr<cmTargetExport> const& te :
302 this->ExportSet->GetTargetExports()) {
303 if (te->NamelinkOnly) {
306 targets.push_back(te->TargetName);
310 targets = this->Targets;
313 std::pair<std::vector<std::string>, std::string>
314 cmExportBuildFileGenerator::FindBuildExportInfo(cmGlobalGenerator* gg,
315 const std::string& name)
317 std::vector<std::string> exportFiles;
320 auto& exportSets = gg->GetBuildExportSets();
322 for (auto const& exp : exportSets) {
323 const auto& exportSet = exp.second;
324 std::vector<std::string> targets;
325 exportSet->GetTargets(targets);
326 if (cm::contains(targets, name)) {
327 exportFiles.push_back(exp.first);
328 ns = exportSet->GetNamespace();
332 return { exportFiles, ns };
335 void cmExportBuildFileGenerator::ComplainAboutMissingTarget(
336 cmGeneratorTarget const* depender, cmGeneratorTarget const* dependee,
337 std::vector<std::string> const& exportFiles)
339 std::ostringstream e;
340 e << "export called with target \"" << depender->GetName()
341 << "\" which requires target \"" << dependee->GetName() << "\" ";
342 if (exportFiles.empty()) {
343 e << "that is not in any export set.";
345 e << "that is not in this export set, but in multiple other export sets: "
346 << cmJoin(exportFiles, ", ") << ".\n";
347 e << "An exported target cannot depend upon another target which is "
348 "exported multiple times. Consider consolidating the exports of the "
350 << dependee->GetName() << "\" target to a single export.";
353 this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
354 MessageType::FATAL_ERROR, e.str(),
355 this->LG->GetMakefile()->GetBacktrace());
358 std::string cmExportBuildFileGenerator::InstallNameDir(
359 cmGeneratorTarget const* target, const std::string& config)
361 std::string install_name_dir;
363 cmMakefile* mf = target->Target->GetMakefile();
364 if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) {
365 install_name_dir = target->GetInstallNameDirForBuildTree(config);
368 return install_name_dir;
372 bool EntryIsContextSensitive(
373 const std::unique_ptr<cmCompiledGeneratorExpression>& cge)
375 return cge->GetHadContextSensitiveCondition();
379 std::string cmExportBuildFileGenerator::GetFileSetDirectories(
380 cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport* /*te*/)
382 std::vector<std::string> resultVector;
385 gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
386 auto directoryEntries = fileSet->CompileDirectoryEntries();
388 for (auto const& config : configs) {
389 auto directories = fileSet->EvaluateDirectoryEntries(
390 directoryEntries, gte->LocalGenerator, config, gte);
392 bool const contextSensitive =
393 std::any_of(directoryEntries.begin(), directoryEntries.end(),
394 EntryIsContextSensitive);
396 auto const& type = fileSet->GetType();
397 // C++ modules do not support interface file sets which are dependent upon
398 // the configuration.
399 if (contextSensitive &&
400 (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s)) {
401 auto* mf = this->LG->GetMakefile();
402 std::ostringstream e;
403 e << "The \"" << gte->GetName() << "\" target's interface file set \""
404 << fileSet->GetName() << "\" of type \"" << type
405 << "\" contains context-sensitive base directory entries which is not "
407 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
408 return std::string{};
411 for (auto const& directory : directories) {
412 auto dest = cmOutputConverter::EscapeForCMake(
413 directory, cmOutputConverter::WrapQuotes::NoWrap);
415 if (contextSensitive && configs.size() != 1) {
416 resultVector.push_back(
417 cmStrCat("\"$<$<CONFIG:", config, ">:", dest, ">\""));
419 resultVector.push_back(cmStrCat('"', dest, '"'));
425 return cmJoin(resultVector, " ");
428 std::string cmExportBuildFileGenerator::GetFileSetFiles(cmGeneratorTarget* gte,
430 cmTargetExport* /*te*/)
432 std::vector<std::string> resultVector;
435 gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
437 auto fileEntries = fileSet->CompileFileEntries();
438 auto directoryEntries = fileSet->CompileDirectoryEntries();
440 for (auto const& config : configs) {
441 auto directories = fileSet->EvaluateDirectoryEntries(
442 directoryEntries, gte->LocalGenerator, config, gte);
444 std::map<std::string, std::vector<std::string>> files;
445 for (auto const& entry : fileEntries) {
446 fileSet->EvaluateFileEntry(directories, files, entry,
447 gte->LocalGenerator, config, gte);
450 bool const contextSensitive =
451 std::any_of(directoryEntries.begin(), directoryEntries.end(),
452 EntryIsContextSensitive) ||
453 std::any_of(fileEntries.begin(), fileEntries.end(),
454 EntryIsContextSensitive);
456 auto const& type = fileSet->GetType();
457 // C++ modules do not support interface file sets which are dependent upon
458 // the configuration.
459 if (contextSensitive &&
460 (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s)) {
461 auto* mf = this->LG->GetMakefile();
462 std::ostringstream e;
463 e << "The \"" << gte->GetName() << "\" target's interface file set \""
464 << fileSet->GetName() << "\" of type \"" << type
465 << "\" contains context-sensitive file entries which is not "
467 mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
468 return std::string{};
471 for (auto const& it : files) {
472 for (auto const& filename : it.second) {
473 auto escapedFile = cmOutputConverter::EscapeForCMake(
474 filename, cmOutputConverter::WrapQuotes::NoWrap);
475 if (contextSensitive && configs.size() != 1) {
476 resultVector.push_back(
477 cmStrCat("\"$<$<CONFIG:", config, ">:", escapedFile, ">\""));
479 resultVector.push_back(cmStrCat('"', escapedFile, '"'));
484 if (!(contextSensitive && configs.size() != 1)) {
489 return cmJoin(resultVector, " ");
492 std::string cmExportBuildFileGenerator::GetCxxModulesDirectory() const
494 return this->CxxModulesDirectory;
497 void cmExportBuildFileGenerator::GenerateCxxModuleConfigInformation(
498 std::ostream& os) const
500 const char* opt = "";
501 if (this->Configurations.size() > 1) {
502 // With more than one configuration, each individual file is optional.
506 // Generate import file content for each configuration.
507 for (std::string c : this->Configurations) {
511 os << "include(\"${CMAKE_CURRENT_LIST_DIR}/cxx-modules-" << c << ".cmake\""
516 bool cmExportBuildFileGenerator::GenerateImportCxxModuleConfigTargetInclusion(
517 std::string config) const
519 auto cxx_modules_dirname = this->GetCxxModulesDirectory();
520 if (cxx_modules_dirname.empty()) {
524 if (config.empty()) {
528 std::string fileName = cmStrCat(this->FileDir, '/', cxx_modules_dirname,
529 "/cxx-modules-", config, ".cmake");
531 cmGeneratedFileStream os(fileName, true);
533 std::string se = cmSystemTools::GetLastSystemError();
534 std::ostringstream e;
535 e << "cannot write to file \"" << fileName << "\": " << se;
536 cmSystemTools::Error(e.str());
539 os.SetCopyIfDifferent(true);
541 for (auto const* tgt : this->ExportedTargets) {
542 os << "include(\"${CMAKE_CURRENT_LIST_DIR}/target-" << tgt->GetExportName()
543 << '-' << config << ".cmake\")\n";