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 <map>
6 #include <memory>
7 #include <set>
8 #include <sstream>
9 #include <utility>
10 
11 #include <cmext/algorithm>
12 
13 #include "cmExportSet.h"
14 #include "cmGeneratorExpression.h"
15 #include "cmGeneratorTarget.h"
16 #include "cmGlobalGenerator.h"
17 #include "cmLocalGenerator.h"
18 #include "cmMakefile.h"
19 #include "cmMessageType.h"
20 #include "cmPolicies.h"
21 #include "cmStateTypes.h"
22 #include "cmStringAlgorithms.h"
23 #include "cmTarget.h"
24 #include "cmTargetExport.h"
25 #include "cmValue.h"
26 #include "cmake.h"
27 
28 class cmSourceFile;
29 
cmExportBuildFileGenerator()30 cmExportBuildFileGenerator::cmExportBuildFileGenerator()
31 {
32   this->LG = nullptr;
33   this->ExportSet = nullptr;
34 }
35 
Compute(cmLocalGenerator * lg)36 void cmExportBuildFileGenerator::Compute(cmLocalGenerator* lg)
37 {
38   this->LG = lg;
39   if (this->ExportSet) {
40     this->ExportSet->Compute(lg);
41   }
42 }
43 
GenerateMainFile(std::ostream & os)44 bool cmExportBuildFileGenerator::GenerateMainFile(std::ostream& os)
45 {
46   {
47     std::string expectedTargets;
48     std::string sep;
49     std::vector<std::string> targets;
50     bool generatedInterfaceRequired = false;
51     this->GetTargets(targets);
52     for (std::string const& tei : targets) {
53       cmGeneratorTarget* te = this->LG->FindGeneratorTargetToUse(tei);
54       expectedTargets += sep + this->Namespace + te->GetExportName();
55       sep = " ";
56       if (this->ExportedTargets.insert(te).second) {
57         this->Exports.push_back(te);
58       } else {
59         std::ostringstream e;
60         e << "given target \"" << te->GetName() << "\" more than once.";
61         this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
62           MessageType::FATAL_ERROR, e.str(),
63           this->LG->GetMakefile()->GetBacktrace());
64         return false;
65       }
66       generatedInterfaceRequired |=
67         this->GetExportTargetType(te) == cmStateEnums::INTERFACE_LIBRARY;
68     }
69 
70     if (generatedInterfaceRequired) {
71       this->GenerateRequiredCMakeVersion(os, "3.0.0");
72     }
73     this->GenerateExpectedTargetsCode(os, expectedTargets);
74   }
75 
76   std::vector<std::string> missingTargets;
77 
78   // Create all the imported targets.
79   for (cmGeneratorTarget* gte : this->Exports) {
80     this->GenerateImportTargetCode(os, gte, this->GetExportTargetType(gte));
81 
82     gte->Target->AppendBuildInterfaceIncludes();
83 
84     ImportPropertyMap properties;
85 
86     this->PopulateInterfaceProperty("INTERFACE_INCLUDE_DIRECTORIES", gte,
87                                     cmGeneratorExpression::BuildInterface,
88                                     properties, missingTargets);
89     this->PopulateInterfaceProperty("INTERFACE_SOURCES", gte,
90                                     cmGeneratorExpression::BuildInterface,
91                                     properties, missingTargets);
92     this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS", gte,
93                                     cmGeneratorExpression::BuildInterface,
94                                     properties, missingTargets);
95     this->PopulateInterfaceProperty("INTERFACE_COMPILE_OPTIONS", gte,
96                                     cmGeneratorExpression::BuildInterface,
97                                     properties, missingTargets);
98     this->PopulateInterfaceProperty("INTERFACE_PRECOMPILE_HEADERS", gte,
99                                     cmGeneratorExpression::BuildInterface,
100                                     properties, missingTargets);
101     this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gte,
102                                     cmGeneratorExpression::BuildInterface,
103                                     properties, missingTargets);
104     this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gte,
105                                     cmGeneratorExpression::BuildInterface,
106                                     properties, missingTargets);
107     this->PopulateInterfaceProperty("INTERFACE_LINK_OPTIONS", gte,
108                                     cmGeneratorExpression::BuildInterface,
109                                     properties, missingTargets);
110     this->PopulateInterfaceProperty("INTERFACE_LINK_DIRECTORIES", gte,
111                                     cmGeneratorExpression::BuildInterface,
112                                     properties, missingTargets);
113     this->PopulateInterfaceProperty("INTERFACE_LINK_DEPENDS", gte,
114                                     cmGeneratorExpression::BuildInterface,
115                                     properties, missingTargets);
116     this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE", gte,
117                                     properties);
118 
119     std::string errorMessage;
120     if (!this->PopulateExportProperties(gte, properties, errorMessage)) {
121       this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
122         MessageType::FATAL_ERROR, errorMessage,
123         this->LG->GetMakefile()->GetBacktrace());
124       return false;
125     }
126 
127     const bool newCMP0022Behavior =
128       gte->GetPolicyStatusCMP0022() != cmPolicies::WARN &&
129       gte->GetPolicyStatusCMP0022() != cmPolicies::OLD;
130     if (newCMP0022Behavior) {
131       this->PopulateInterfaceLinkLibrariesProperty(
132         gte, cmGeneratorExpression::BuildInterface, properties,
133         missingTargets);
134     }
135     this->PopulateCompatibleInterfaceProperties(gte, properties);
136 
137     this->GenerateInterfaceProperties(gte, os, properties);
138   }
139 
140   // Generate import file content for each configuration.
141   for (std::string const& c : this->Configurations) {
142     this->GenerateImportConfig(os, c, missingTargets);
143   }
144 
145   this->GenerateMissingTargetsCheckCode(os, missingTargets);
146 
147   return true;
148 }
149 
GenerateImportTargetsConfig(std::ostream & os,const std::string & config,std::string const & suffix,std::vector<std::string> & missingTargets)150 void cmExportBuildFileGenerator::GenerateImportTargetsConfig(
151   std::ostream& os, const std::string& config, std::string const& suffix,
152   std::vector<std::string>& missingTargets)
153 {
154   for (cmGeneratorTarget* target : this->Exports) {
155     // Collect import properties for this target.
156     ImportPropertyMap properties;
157 
158     if (this->GetExportTargetType(target) != cmStateEnums::INTERFACE_LIBRARY) {
159       this->SetImportLocationProperty(config, suffix, target, properties);
160     }
161     if (!properties.empty()) {
162       // Get the rest of the target details.
163       if (this->GetExportTargetType(target) !=
164           cmStateEnums::INTERFACE_LIBRARY) {
165         this->SetImportDetailProperties(config, suffix, target, properties,
166                                         missingTargets);
167         this->SetImportLinkInterface(config, suffix,
168                                      cmGeneratorExpression::BuildInterface,
169                                      target, properties, missingTargets);
170       }
171 
172       // TODO: PUBLIC_HEADER_LOCATION
173       // This should wait until the build feature propagation stuff
174       // is done.  Then this can be a propagated include directory.
175       // this->GenerateImportProperty(config, te->HeaderGenerator,
176       //                              properties);
177 
178       // Generate code in the export file.
179       this->GenerateImportPropertyCode(os, config, target, properties);
180     }
181   }
182 }
183 
GetExportTargetType(cmGeneratorTarget const * target) const184 cmStateEnums::TargetType cmExportBuildFileGenerator::GetExportTargetType(
185   cmGeneratorTarget const* target) const
186 {
187   cmStateEnums::TargetType targetType = target->GetType();
188   // An object library exports as an interface library if we cannot
189   // tell clients where to find the objects.  This is sufficient
190   // to support transitive usage requirements on other targets that
191   // use the object library.
192   if (targetType == cmStateEnums::OBJECT_LIBRARY &&
193       !this->LG->GetGlobalGenerator()->HasKnownObjectFileLocation(nullptr)) {
194     targetType = cmStateEnums::INTERFACE_LIBRARY;
195   }
196   return targetType;
197 }
198 
SetExportSet(cmExportSet * exportSet)199 void cmExportBuildFileGenerator::SetExportSet(cmExportSet* exportSet)
200 {
201   this->ExportSet = exportSet;
202 }
203 
SetImportLocationProperty(const std::string & config,std::string const & suffix,cmGeneratorTarget * target,ImportPropertyMap & properties)204 void cmExportBuildFileGenerator::SetImportLocationProperty(
205   const std::string& config, std::string const& suffix,
206   cmGeneratorTarget* target, ImportPropertyMap& properties)
207 {
208   // Get the makefile in which to lookup target information.
209   cmMakefile* mf = target->Makefile;
210 
211   if (target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
212     std::string prop = cmStrCat("IMPORTED_OBJECTS", suffix);
213 
214     // Compute all the object files inside this target and setup
215     // IMPORTED_OBJECTS as a list of object files
216     std::vector<cmSourceFile const*> objectSources;
217     target->GetObjectSources(objectSources, config);
218     std::string const obj_dir = target->GetObjectDirectory(config);
219     std::vector<std::string> objects;
220     for (cmSourceFile const* sf : objectSources) {
221       const std::string& obj = target->GetObjectName(sf);
222       objects.push_back(obj_dir + obj);
223     }
224 
225     // Store the property.
226     properties[prop] = cmJoin(objects, ";");
227   } else {
228     // Add the main target file.
229     {
230       std::string prop = cmStrCat("IMPORTED_LOCATION", suffix);
231       std::string value;
232       if (target->IsAppBundleOnApple()) {
233         value =
234           target->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact);
235       } else {
236         value = target->GetFullPath(config,
237                                     cmStateEnums::RuntimeBinaryArtifact, true);
238       }
239       properties[prop] = value;
240     }
241 
242     // Add the import library for windows DLLs.
243     if (target->HasImportLibrary(config)) {
244       std::string prop = cmStrCat("IMPORTED_IMPLIB", suffix);
245       std::string value =
246         target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact);
247       if (mf->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX")) {
248         target->GetImplibGNUtoMS(config, value, value,
249                                  "${CMAKE_IMPORT_LIBRARY_SUFFIX}");
250       }
251       properties[prop] = value;
252     }
253   }
254 }
255 
HandleMissingTarget(std::string & link_libs,std::vector<std::string> & missingTargets,cmGeneratorTarget const * depender,cmGeneratorTarget * dependee)256 void cmExportBuildFileGenerator::HandleMissingTarget(
257   std::string& link_libs, std::vector<std::string>& missingTargets,
258   cmGeneratorTarget const* depender, cmGeneratorTarget* dependee)
259 {
260   // The target is not in the export.
261   if (!this->AppendMode) {
262     const std::string name = dependee->GetName();
263     cmGlobalGenerator* gg =
264       dependee->GetLocalGenerator()->GetGlobalGenerator();
265     auto exportInfo = this->FindBuildExportInfo(gg, name);
266     std::vector<std::string> const& exportFiles = exportInfo.first;
267 
268     if (exportFiles.size() == 1) {
269       std::string missingTarget = exportInfo.second;
270 
271       missingTarget += dependee->GetExportName();
272       link_libs += missingTarget;
273       missingTargets.push_back(std::move(missingTarget));
274       return;
275     }
276     // We are not appending, so all exported targets should be
277     // known here.  This is probably user-error.
278     this->ComplainAboutMissingTarget(depender, dependee, exportFiles);
279   }
280   // Assume the target will be exported by another command.
281   // Append it with the export namespace.
282   link_libs += this->Namespace;
283   link_libs += dependee->GetExportName();
284 }
285 
GetTargets(std::vector<std::string> & targets) const286 void cmExportBuildFileGenerator::GetTargets(
287   std::vector<std::string>& targets) const
288 {
289   if (this->ExportSet) {
290     for (std::unique_ptr<cmTargetExport> const& te :
291          this->ExportSet->GetTargetExports()) {
292       if (te->NamelinkOnly) {
293         continue;
294       }
295       targets.push_back(te->TargetName);
296     }
297     return;
298   }
299   targets = this->Targets;
300 }
301 
302 std::pair<std::vector<std::string>, std::string>
FindBuildExportInfo(cmGlobalGenerator * gg,const std::string & name)303 cmExportBuildFileGenerator::FindBuildExportInfo(cmGlobalGenerator* gg,
304                                                 const std::string& name)
305 {
306   std::vector<std::string> exportFiles;
307   std::string ns;
308 
309   auto& exportSets = gg->GetBuildExportSets();
310 
311   for (auto const& exp : exportSets) {
312     const auto& exportSet = exp.second;
313     std::vector<std::string> targets;
314     exportSet->GetTargets(targets);
315     if (cm::contains(targets, name)) {
316       exportFiles.push_back(exp.first);
317       ns = exportSet->GetNamespace();
318     }
319   }
320 
321   return { exportFiles, ns };
322 }
323 
ComplainAboutMissingTarget(cmGeneratorTarget const * depender,cmGeneratorTarget const * dependee,std::vector<std::string> const & exportFiles)324 void cmExportBuildFileGenerator::ComplainAboutMissingTarget(
325   cmGeneratorTarget const* depender, cmGeneratorTarget const* dependee,
326   std::vector<std::string> const& exportFiles)
327 {
328   std::ostringstream e;
329   e << "export called with target \"" << depender->GetName()
330     << "\" which requires target \"" << dependee->GetName() << "\" ";
331   if (exportFiles.empty()) {
332     e << "that is not in any export set.";
333   } else {
334     e << "that is not in this export set, but in multiple other export sets: "
335       << cmJoin(exportFiles, ", ") << ".\n";
336     e << "An exported target cannot depend upon another target which is "
337          "exported multiple times. Consider consolidating the exports of the "
338          "\""
339       << dependee->GetName() << "\" target to a single export.";
340   }
341 
342   this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
343     MessageType::FATAL_ERROR, e.str(),
344     this->LG->GetMakefile()->GetBacktrace());
345 }
346 
InstallNameDir(cmGeneratorTarget const * target,const std::string & config)347 std::string cmExportBuildFileGenerator::InstallNameDir(
348   cmGeneratorTarget const* target, const std::string& config)
349 {
350   std::string install_name_dir;
351 
352   cmMakefile* mf = target->Target->GetMakefile();
353   if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) {
354     install_name_dir = target->GetInstallNameDirForBuildTree(config);
355   }
356 
357   return install_name_dir;
358 }
359