Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / cmExportBuildFileGenerator.cxx
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"
4
5 #include <algorithm>
6 #include <map>
7 #include <memory>
8 #include <set>
9 #include <sstream>
10 #include <utility>
11
12 #include <cm/string_view>
13 #include <cmext/algorithm>
14 #include <cmext/string_view>
15
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"
30 #include "cmTarget.h"
31 #include "cmTargetExport.h"
32 #include "cmValue.h"
33 #include "cmake.h"
34
35 class cmSourceFile;
36
37 cmExportBuildFileGenerator::cmExportBuildFileGenerator()
38 {
39   this->LG = nullptr;
40   this->ExportSet = nullptr;
41 }
42
43 void cmExportBuildFileGenerator::Compute(cmLocalGenerator* lg)
44 {
45   this->LG = lg;
46   if (this->ExportSet) {
47     this->ExportSet->Compute(lg);
48   }
49 }
50
51 bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
52 {
53   {
54     std::string expectedTargets;
55     std::string sep;
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();
62       sep = " ";
63       if (this->ExportedTargets.insert(te).second) {
64         this->Exports.push_back(te);
65       } else {
66         std::ostringstream e;
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());
71         return false;
72       }
73       generatedInterfaceRequired |=
74         this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY;
75     }
76
77     if (generatedInterfaceRequired) {
78       this->GenerateRequiredCMakeVersion(os, "3.0.0");
79     }
80     this->GenerateExpectedTargetsCode(os, expectedTargets);
81   }
82
83   // Create all the imported targets.
84   for (cmGeneratorTarget* gte : this->Exports) {
85     this->GenerateImportTargetCode(os, gte, this->GetExportTargetType(gte));
86
87     gte->Target->AppendBuildInterfaceIncludes();
88
89     ImportPropertyMap properties;
90
91     this->PopulateInterfaceProperty("INTERFACE_INCLUDE_DIRECTORIES", gte,
92                                     cmGeneratorExpression::BuildInterface,
93                                     properties);
94     this->PopulateInterfaceProperty("INTERFACE_SOURCES", gte,
95                                     cmGeneratorExpression::BuildInterface,
96                                     properties);
97     this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS", gte,
98                                     cmGeneratorExpression::BuildInterface,
99                                     properties);
100     this->PopulateInterfaceProperty("INTERFACE_COMPILE_OPTIONS", gte,
101                                     cmGeneratorExpression::BuildInterface,
102                                     properties);
103     this->PopulateInterfaceProperty("INTERFACE_PRECOMPILE_HEADERS", gte,
104                                     cmGeneratorExpression::BuildInterface,
105                                     properties);
106     this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gte,
107                                     cmGeneratorExpression::BuildInterface,
108                                     properties);
109     this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gte,
110                                     cmGeneratorExpression::BuildInterface,
111                                     properties);
112     this->PopulateInterfaceProperty("INTERFACE_LINK_OPTIONS", gte,
113                                     cmGeneratorExpression::BuildInterface,
114                                     properties);
115     this->PopulateInterfaceProperty("INTERFACE_LINK_DIRECTORIES", gte,
116                                     cmGeneratorExpression::BuildInterface,
117                                     properties);
118     this->PopulateInterfaceProperty("INTERFACE_LINK_DEPENDS", gte,
119                                     cmGeneratorExpression::BuildInterface,
120                                     properties);
121     this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE", gte,
122                                     properties);
123
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());
129       return false;
130     }
131
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);
138     }
139     this->PopulateCompatibleInterfaceProperties(gte, properties);
140
141     this->GenerateInterfaceProperties(gte, os, properties);
142
143     this->GenerateTargetFileSets(gte, os);
144   }
145
146   this->GenerateCxxModuleInformation(os);
147
148   // Generate import file content for each configuration.
149   for (std::string const& c : this->Configurations) {
150     this->GenerateImportConfig(os, c);
151   }
152
153   // Generate import file content for each configuration.
154   for (std::string const& c : this->Configurations) {
155     this->GenerateImportCxxModuleConfigTargetInclusion(c);
156   }
157
158   this->GenerateMissingTargetsCheckCode(os);
159
160   return true;
161 }
162
163 void cmExportBuildFileGenerator::GenerateImportTargetsConfig(
164   std::ostream& os, const std::string& config, std::string const& suffix)
165 {
166   for (cmGeneratorTarget* target : this->Exports) {
167     // Collect import properties for this target.
168     ImportPropertyMap properties;
169
170     if (this->GetExportTargetType(target) != cmStateEnums::INTERFACE_LIBRARY) {
171       this->SetImportLocationProperty(config, suffix, target, properties);
172     }
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,
180                                      target, properties);
181       }
182
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,
187       //                              properties);
188
189       // Generate code in the export file.
190       this->GenerateImportPropertyCode(os, config, target, properties);
191     }
192   }
193 }
194
195 cmStateEnums::TargetType cmExportBuildFileGenerator::GetExportTargetType(
196   cmGeneratorTarget const* target) const
197 {
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;
206   }
207   return targetType;
208 }
209
210 void cmExportBuildFileGenerator::SetExportSet(cmExportSet* exportSet)
211 {
212   this->ExportSet = exportSet;
213 }
214
215 void cmExportBuildFileGenerator::SetImportLocationProperty(
216   const std::string& config, std::string const& suffix,
217   cmGeneratorTarget* target, ImportPropertyMap& properties)
218 {
219   // Get the makefile in which to lookup target information.
220   cmMakefile* mf = target->Makefile;
221
222   if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
223     std::string prop = cmStrCat("IMPORTED_OBJECTS", suffix);
224
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);
234     }
235
236     // Store the property.
237     properties[prop] = cmJoin(objects, ";");
238   } else {
239     // Add the main target file.
240     {
241       std::string prop = cmStrCat("IMPORTED_LOCATION", suffix);
242       std::string value;
243       if (target->IsAppBundleOnApple()) {
244         value =
245           target->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact);
246       } else {
247         value = target->GetFullPath(config,
248                                     cmStateEnums::RuntimeBinaryArtifact, true);
249       }
250       properties[prop] = value;
251     }
252
253     // Add the import library for windows DLLs.
254     if (target->HasImportLibrary(config)) {
255       std::string prop = cmStrCat("IMPORTED_IMPLIB", suffix);
256       std::string value =
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}");
261       }
262       properties[prop] = value;
263     }
264   }
265 }
266
267 void cmExportBuildFileGenerator::HandleMissingTarget(
268   std::string& link_libs, cmGeneratorTarget const* depender,
269   cmGeneratorTarget* dependee)
270 {
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;
278
279     if (exportFiles.size() == 1) {
280       std::string missingTarget = exportInfo.second;
281
282       missingTarget += dependee->GetExportName();
283       link_libs += missingTarget;
284       this->MissingTargets.emplace_back(std::move(missingTarget));
285       return;
286     }
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);
290   }
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();
295 }
296
297 void cmExportBuildFileGenerator::GetTargets(
298   std::vector<std::string>& targets) const
299 {
300   if (this->ExportSet) {
301     for (std::unique_ptr<cmTargetExport> const& te :
302          this->ExportSet->GetTargetExports()) {
303       if (te->NamelinkOnly) {
304         continue;
305       }
306       targets.push_back(te->TargetName);
307     }
308     return;
309   }
310   targets = this->Targets;
311 }
312
313 std::pair<std::vector<std::string>, std::string>
314 cmExportBuildFileGenerator::FindBuildExportInfo(cmGlobalGenerator* gg,
315                                                 const std::string& name)
316 {
317   std::vector<std::string> exportFiles;
318   std::string ns;
319
320   auto& exportSets = gg->GetBuildExportSets();
321
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();
329     }
330   }
331
332   return { exportFiles, ns };
333 }
334
335 void cmExportBuildFileGenerator::ComplainAboutMissingTarget(
336   cmGeneratorTarget const* depender, cmGeneratorTarget const* dependee,
337   std::vector<std::string> const& exportFiles)
338 {
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.";
344   } else {
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 "
349          "\""
350       << dependee->GetName() << "\" target to a single export.";
351   }
352
353   this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
354     MessageType::FATAL_ERROR, e.str(),
355     this->LG->GetMakefile()->GetBacktrace());
356 }
357
358 std::string cmExportBuildFileGenerator::InstallNameDir(
359   cmGeneratorTarget const* target, const std::string& config)
360 {
361   std::string install_name_dir;
362
363   cmMakefile* mf = target->Target->GetMakefile();
364   if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) {
365     install_name_dir = target->GetInstallNameDirForBuildTree(config);
366   }
367
368   return install_name_dir;
369 }
370
371 namespace {
372 bool EntryIsContextSensitive(
373   const std::unique_ptr<cmCompiledGeneratorExpression>& cge)
374 {
375   return cge->GetHadContextSensitiveCondition();
376 }
377 }
378
379 std::string cmExportBuildFileGenerator::GetFileSetDirectories(
380   cmGeneratorTarget* gte, cmFileSet* fileSet, cmTargetExport* /*te*/)
381 {
382   std::vector<std::string> resultVector;
383
384   auto configs =
385     gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
386   auto directoryEntries = fileSet->CompileDirectoryEntries();
387
388   for (auto const& config : configs) {
389     auto directories = fileSet->EvaluateDirectoryEntries(
390       directoryEntries, gte->LocalGenerator, config, gte);
391
392     bool const contextSensitive =
393       std::any_of(directoryEntries.begin(), directoryEntries.end(),
394                   EntryIsContextSensitive);
395
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 "
406            "supported.";
407       mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
408       return std::string{};
409     }
410
411     for (auto const& directory : directories) {
412       auto dest = cmOutputConverter::EscapeForCMake(
413         directory, cmOutputConverter::WrapQuotes::NoWrap);
414
415       if (contextSensitive && configs.size() != 1) {
416         resultVector.push_back(
417           cmStrCat("\"$<$<CONFIG:", config, ">:", dest, ">\""));
418       } else {
419         resultVector.push_back(cmStrCat('"', dest, '"'));
420         break;
421       }
422     }
423   }
424
425   return cmJoin(resultVector, " ");
426 }
427
428 std::string cmExportBuildFileGenerator::GetFileSetFiles(cmGeneratorTarget* gte,
429                                                         cmFileSet* fileSet,
430                                                         cmTargetExport* /*te*/)
431 {
432   std::vector<std::string> resultVector;
433
434   auto configs =
435     gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
436
437   auto fileEntries = fileSet->CompileFileEntries();
438   auto directoryEntries = fileSet->CompileDirectoryEntries();
439
440   for (auto const& config : configs) {
441     auto directories = fileSet->EvaluateDirectoryEntries(
442       directoryEntries, gte->LocalGenerator, config, gte);
443
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);
448     }
449
450     bool const contextSensitive =
451       std::any_of(directoryEntries.begin(), directoryEntries.end(),
452                   EntryIsContextSensitive) ||
453       std::any_of(fileEntries.begin(), fileEntries.end(),
454                   EntryIsContextSensitive);
455
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 "
466            "supported.";
467       mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
468       return std::string{};
469     }
470
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, ">\""));
478         } else {
479           resultVector.push_back(cmStrCat('"', escapedFile, '"'));
480         }
481       }
482     }
483
484     if (!(contextSensitive && configs.size() != 1)) {
485       break;
486     }
487   }
488
489   return cmJoin(resultVector, " ");
490 }
491
492 std::string cmExportBuildFileGenerator::GetCxxModulesDirectory() const
493 {
494   return this->CxxModulesDirectory;
495 }
496
497 void cmExportBuildFileGenerator::GenerateCxxModuleConfigInformation(
498   std::ostream& os) const
499 {
500   const char* opt = "";
501   if (this->Configurations.size() > 1) {
502     // With more than one configuration, each individual file is optional.
503     opt = " OPTIONAL";
504   }
505
506   // Generate import file content for each configuration.
507   for (std::string c : this->Configurations) {
508     if (c.empty()) {
509       c = "noconfig";
510     }
511     os << "include(\"${CMAKE_CURRENT_LIST_DIR}/cxx-modules-" << c << ".cmake\""
512        << opt << ")\n";
513   }
514 }
515
516 bool cmExportBuildFileGenerator::GenerateImportCxxModuleConfigTargetInclusion(
517   std::string config) const
518 {
519   auto cxx_modules_dirname = this->GetCxxModulesDirectory();
520   if (cxx_modules_dirname.empty()) {
521     return true;
522   }
523
524   if (config.empty()) {
525     config = "noconfig";
526   }
527
528   std::string fileName = cmStrCat(this->FileDir, '/', cxx_modules_dirname,
529                                   "/cxx-modules-", config, ".cmake");
530
531   cmGeneratedFileStream os(fileName, true);
532   if (!os) {
533     std::string se = cmSystemTools::GetLastSystemError();
534     std::ostringstream e;
535     e << "cannot write to file \"" << fileName << "\": " << se;
536     cmSystemTools::Error(e.str());
537     return false;
538   }
539   os.SetCopyIfDifferent(true);
540
541   for (auto const* tgt : this->ExportedTargets) {
542     os << "include(\"${CMAKE_CURRENT_LIST_DIR}/target-" << tgt->GetExportName()
543        << '-' << config << ".cmake\")\n";
544   }
545
546   return true;
547 }