1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
4 #include "cmGeneratorTarget.h"
13 #include <type_traits>
14 #include <unordered_set>
18 #include <cm/string_view>
19 #include <cmext/algorithm>
20 #include <cmext/string_view>
22 #include "cmsys/RegularExpression.hxx"
24 #include "cmAlgorithms.h"
25 #include "cmEvaluatedTargetProperty.h"
26 #include "cmFileSet.h"
27 #include "cmGeneratorExpression.h"
28 #include "cmGeneratorExpressionDAGChecker.h"
29 #include "cmGlobalGenerator.h"
30 #include "cmLinkItem.h"
32 #include "cmListFileCache.h"
33 #include "cmLocalGenerator.h"
34 #include "cmMakefile.h"
35 #include "cmMessageType.h"
36 #include "cmSourceFile.h"
37 #include "cmSourceFileLocation.h"
38 #include "cmSourceGroup.h"
39 #include "cmStateTypes.h"
40 #include "cmStringAlgorithms.h"
41 #include "cmSystemTools.h"
47 using UseTo
= cmGeneratorTarget::UseTo
;
49 void AddObjectEntries(cmGeneratorTarget
const* headTarget
,
50 std::string
const& config
,
51 cmGeneratorExpressionDAGChecker
* dagChecker
,
52 EvaluatedTargetPropertyEntries
& entries
)
54 if (cmLinkImplementationLibraries
const* impl
=
55 headTarget
->GetLinkImplementationLibraries(config
, UseTo::Compile
)) {
56 entries
.HadContextSensitiveCondition
= impl
->HadContextSensitiveCondition
;
57 for (cmLinkImplItem
const& lib
: impl
->Libraries
) {
59 lib
.Target
->GetType() == cmStateEnums::OBJECT_LIBRARY
) {
60 std::string uniqueName
=
61 headTarget
->GetGlobalGenerator()->IndexGeneratorTargetUniquely(
63 std::string genex
= "$<TARGET_OBJECTS:" + std::move(uniqueName
) + ">";
64 cmGeneratorExpression
ge(*headTarget
->Makefile
->GetCMakeInstance(),
66 std::unique_ptr
<cmCompiledGeneratorExpression
> cge
= ge
.Parse(genex
);
67 cge
->SetEvaluateForBuildsystem(true);
69 EvaluatedTargetPropertyEntry
ee(lib
, lib
.Backtrace
);
70 cmExpandList(cge
->Evaluate(headTarget
->GetLocalGenerator(), config
,
71 headTarget
, dagChecker
),
73 if (cge
->GetHadContextSensitiveCondition()) {
74 ee
.ContextDependent
= true;
76 entries
.Entries
.emplace_back(std::move(ee
));
82 void addFileSetEntry(cmGeneratorTarget
const* headTarget
,
83 std::string
const& config
,
84 cmGeneratorExpressionDAGChecker
* dagChecker
,
85 cmFileSet
const* fileSet
,
86 EvaluatedTargetPropertyEntries
& entries
)
88 auto dirCges
= fileSet
->CompileDirectoryEntries();
89 auto dirs
= fileSet
->EvaluateDirectoryEntries(
90 dirCges
, headTarget
->GetLocalGenerator(), config
, headTarget
, dagChecker
);
91 bool contextSensitiveDirs
= false;
92 for (auto const& dirCge
: dirCges
) {
93 if (dirCge
->GetHadContextSensitiveCondition()) {
94 contextSensitiveDirs
= true;
98 cmake
* cm
= headTarget
->GetLocalGenerator()->GetCMakeInstance();
99 for (auto& entryCge
: fileSet
->CompileFileEntries()) {
100 auto tpe
= cmGeneratorTarget::TargetPropertyEntry::CreateFileSet(
101 dirs
, contextSensitiveDirs
, std::move(entryCge
), fileSet
);
102 entries
.Entries
.emplace_back(
103 EvaluateTargetPropertyEntry(headTarget
, config
, "", dagChecker
, *tpe
));
104 EvaluatedTargetPropertyEntry
const& entry
= entries
.Entries
.back();
105 for (auto const& file
: entry
.Values
) {
106 auto* sf
= headTarget
->Makefile
->GetOrCreateSource(file
);
107 if (fileSet
->GetType() == "HEADERS"_s
) {
108 sf
->SetProperty("HEADER_FILE_ONLY", "TRUE");
111 #ifndef CMAKE_BOOTSTRAP
114 auto path
= sf
->ResolveFullPath(&e
, &w
);
116 cm
->IssueMessage(MessageType::AUTHOR_WARNING
, w
, entry
.Backtrace
);
120 cm
->IssueMessage(MessageType::FATAL_ERROR
, e
, entry
.Backtrace
);
125 for (auto const& sg
: headTarget
->Makefile
->GetSourceGroups()) {
126 if (sg
.MatchChildrenFiles(path
)) {
132 if (fileSet
->GetType() == "HEADERS"_s
) {
133 headTarget
->Makefile
->GetOrCreateSourceGroup("Header Files")
134 ->AddGroupFile(path
);
142 void AddFileSetEntries(cmGeneratorTarget
const* headTarget
,
143 std::string
const& config
,
144 cmGeneratorExpressionDAGChecker
* dagChecker
,
145 EvaluatedTargetPropertyEntries
& entries
)
147 for (auto const& entry
: headTarget
->Target
->GetHeaderSetsEntries()) {
148 for (auto const& name
: cmList
{ entry
.Value
}) {
149 auto const* headerSet
= headTarget
->Target
->GetFileSet(name
);
150 addFileSetEntry(headTarget
, config
, dagChecker
, headerSet
, entries
);
153 for (auto const& entry
: headTarget
->Target
->GetCxxModuleSetsEntries()) {
154 for (auto const& name
: cmList
{ entry
.Value
}) {
155 auto const* cxxModuleSet
= headTarget
->Target
->GetFileSet(name
);
156 addFileSetEntry(headTarget
, config
, dagChecker
, cxxModuleSet
, entries
);
161 bool processSources(cmGeneratorTarget
const* tgt
,
162 EvaluatedTargetPropertyEntries
& entries
,
163 std::vector
<BT
<std::string
>>& srcs
,
164 std::unordered_set
<std::string
>& uniqueSrcs
,
167 cmMakefile
* mf
= tgt
->Target
->GetMakefile();
169 bool contextDependent
= entries
.HadContextSensitiveCondition
;
171 for (EvaluatedTargetPropertyEntry
& entry
: entries
.Entries
) {
172 if (entry
.ContextDependent
) {
173 contextDependent
= true;
176 cmLinkImplItem
const& item
= entry
.LinkImplItem
;
177 std::string
const& targetName
= item
.AsStr();
179 for (std::string
& src
: entry
.Values
) {
180 cmSourceFile
* sf
= mf
->GetOrCreateSource(src
);
183 std::string fullPath
= sf
->ResolveFullPath(&e
, &w
);
184 cmake
* cm
= tgt
->GetLocalGenerator()->GetCMakeInstance();
186 cm
->IssueMessage(MessageType::AUTHOR_WARNING
, w
, entry
.Backtrace
);
188 if (fullPath
.empty()) {
190 cm
->IssueMessage(MessageType::FATAL_ERROR
, e
, entry
.Backtrace
);
192 return contextDependent
;
195 if (!targetName
.empty() && !cmSystemTools::FileIsFullPath(src
)) {
196 std::ostringstream err
;
197 if (!targetName
.empty()) {
198 err
<< "Target \"" << targetName
199 << "\" contains relative path in its INTERFACE_SOURCES:\n \""
202 err
<< "Found relative path while evaluating sources of \""
203 << tgt
->GetName() << "\":\n \"" << src
<< "\"\n";
205 tgt
->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR
,
207 return contextDependent
;
211 std::string usedSources
;
212 for (std::string
const& src
: entry
.Values
) {
213 if (uniqueSrcs
.insert(src
).second
) {
214 srcs
.emplace_back(src
, entry
.Backtrace
);
216 usedSources
+= " * " + src
+ "\n";
220 if (!usedSources
.empty()) {
221 tgt
->GetLocalGenerator()->GetCMakeInstance()->IssueMessage(
223 std::string("Used sources for target ") + tgt
->GetName() + ":\n" +
228 return contextDependent
;
232 std::vector
<BT
<std::string
>> cmGeneratorTarget::GetSourceFilePaths(
233 std::string
const& config
) const
235 std::vector
<BT
<std::string
>> files
;
237 if (!this->LocalGenerator
->GetGlobalGenerator()->GetConfigureDoneCMP0026()) {
238 // At configure-time, this method can be called as part of getting the
239 // LOCATION property or to export() a file to be include()d. However
240 // there is no cmGeneratorTarget at configure-time, so search the SOURCES
241 // for TARGET_OBJECTS instead for backwards compatibility with OLD
242 // behavior of CMP0024 and CMP0026 only.
244 cmBTStringRange sourceEntries
= this->Target
->GetSourceEntries();
245 for (auto const& entry
: sourceEntries
) {
246 cmList items
{ entry
.Value
};
247 for (auto const& item
: items
) {
248 if (cmHasLiteralPrefix(item
, "$<TARGET_OBJECTS:") &&
249 item
.back() == '>') {
252 files
.emplace_back(item
);
258 cmList debugProperties
{ this->Makefile
->GetDefinition(
259 "CMAKE_DEBUG_TARGET_PROPERTIES") };
261 !this->DebugSourcesDone
&& cm::contains(debugProperties
, "SOURCES");
263 if (this->LocalGenerator
->GetGlobalGenerator()->GetConfigureDoneCMP0026()) {
264 this->DebugSourcesDone
= true;
267 cmGeneratorExpressionDAGChecker
dagChecker(this, "SOURCES", nullptr, nullptr,
268 this->LocalGenerator
, config
);
270 EvaluatedTargetPropertyEntries entries
= EvaluateTargetPropertyEntries(
271 this, config
, std::string(), &dagChecker
, this->SourceEntries
);
273 std::unordered_set
<std::string
> uniqueSrcs
;
274 bool contextDependentDirectSources
=
275 processSources(this, entries
, files
, uniqueSrcs
, debugSources
);
277 // Collect INTERFACE_SOURCES of all direct link-dependencies.
278 EvaluatedTargetPropertyEntries linkInterfaceSourcesEntries
;
279 AddInterfaceEntries(this, config
, "INTERFACE_SOURCES", std::string(),
280 &dagChecker
, linkInterfaceSourcesEntries
,
281 IncludeRuntimeInterface::No
, UseTo::Compile
);
282 bool contextDependentInterfaceSources
= processSources(
283 this, linkInterfaceSourcesEntries
, files
, uniqueSrcs
, debugSources
);
285 // Collect TARGET_OBJECTS of direct object link-dependencies.
286 bool contextDependentObjects
= false;
287 if (this->GetType() != cmStateEnums::OBJECT_LIBRARY
) {
288 EvaluatedTargetPropertyEntries linkObjectsEntries
;
289 AddObjectEntries(this, config
, &dagChecker
, linkObjectsEntries
);
290 contextDependentObjects
= processSources(this, linkObjectsEntries
, files
,
291 uniqueSrcs
, debugSources
);
292 // Note that for imported targets or multi-config generators supporting
293 // cross-config builds the paths to the object files must be per-config,
294 // so contextDependentObjects will be true here even if object libraries
295 // are specified without per-config generator expressions.
298 // Collect this target's file sets.
299 EvaluatedTargetPropertyEntries fileSetEntries
;
300 AddFileSetEntries(this, config
, &dagChecker
, fileSetEntries
);
301 bool contextDependentFileSets
=
302 processSources(this, fileSetEntries
, files
, uniqueSrcs
, debugSources
);
304 // Determine if sources are context-dependent or not.
305 if (!contextDependentDirectSources
&& !contextDependentInterfaceSources
&&
306 !contextDependentObjects
&& !contextDependentFileSets
) {
307 this->SourcesAreContextDependent
= Tribool::False
;
309 this->SourcesAreContextDependent
= Tribool::True
;
315 void cmGeneratorTarget::GetSourceFiles(std::vector
<cmSourceFile
*>& files
,
316 const std::string
& config
) const
318 std::vector
<BT
<cmSourceFile
*>> tmp
= this->GetSourceFiles(config
);
319 files
.reserve(tmp
.size());
320 for (BT
<cmSourceFile
*>& v
: tmp
) {
321 files
.push_back(v
.Value
);
325 std::vector
<BT
<cmSourceFile
*>> cmGeneratorTarget::GetSourceFiles(
326 std::string
const& config
) const
328 std::vector
<BT
<cmSourceFile
*>> files
;
329 if (!this->GlobalGenerator
->GetConfigureDoneCMP0026()) {
330 // Since we are still configuring not all sources may exist yet,
331 // so we need to avoid full source classification because that
332 // requires the absolute paths to all sources to be determined.
333 // Since this is only for compatibility with old policies that
334 // projects should not depend on anymore, just compute the files
335 // without memoizing them.
336 std::vector
<BT
<std::string
>> srcs
= this->GetSourceFilePaths(config
);
337 std::set
<cmSourceFile
*> emitted
;
338 for (BT
<std::string
> const& s
: srcs
) {
339 cmSourceFile
* sf
= this->Makefile
->GetOrCreateSource(s
.Value
);
340 if (emitted
.insert(sf
).second
) {
341 files
.emplace_back(sf
, s
.Backtrace
);
347 KindedSources
const& kinded
= this->GetKindedSources(config
);
348 files
.reserve(kinded
.Sources
.size());
349 for (SourceAndKind
const& si
: kinded
.Sources
) {
350 files
.push_back(si
.Source
);
355 void cmGeneratorTarget::GetSourceFilesWithoutObjectLibraries(
356 std::vector
<cmSourceFile
*>& files
, const std::string
& config
) const
358 std::vector
<BT
<cmSourceFile
*>> tmp
=
359 this->GetSourceFilesWithoutObjectLibraries(config
);
360 files
.reserve(tmp
.size());
361 for (BT
<cmSourceFile
*>& v
: tmp
) {
362 files
.push_back(v
.Value
);
366 std::vector
<BT
<cmSourceFile
*>>
367 cmGeneratorTarget::GetSourceFilesWithoutObjectLibraries(
368 std::string
const& config
) const
370 std::vector
<BT
<cmSourceFile
*>> files
;
371 KindedSources
const& kinded
= this->GetKindedSources(config
);
372 files
.reserve(kinded
.Sources
.size());
373 for (SourceAndKind
const& si
: kinded
.Sources
) {
374 if (si
.Source
.Value
->GetObjectLibrary().empty()) {
375 files
.push_back(si
.Source
);
381 cmGeneratorTarget::KindedSources
const& cmGeneratorTarget::GetKindedSources(
382 std::string
const& config
) const
384 // If we already processed one configuration and found no dependency
385 // on configuration then always use the one result.
386 if (this->SourcesAreContextDependent
== Tribool::False
) {
387 return this->KindedSourcesMap
.begin()->second
;
390 // Lookup any existing link implementation for this configuration.
391 std::string
const key
= cmSystemTools::UpperCase(config
);
392 auto it
= this->KindedSourcesMap
.find(key
);
393 if (it
!= this->KindedSourcesMap
.end()) {
394 if (!it
->second
.Initialized
) {
395 std::ostringstream e
;
396 e
<< "The SOURCES of \"" << this->GetName()
397 << "\" use a generator expression that depends on the "
398 "SOURCES themselves.";
399 this->GlobalGenerator
->GetCMakeInstance()->IssueMessage(
400 MessageType::FATAL_ERROR
, e
.str(), this->GetBacktrace());
401 static KindedSources empty
;
407 // Add an entry to the map for this configuration.
408 KindedSources
& files
= this->KindedSourcesMap
[key
];
409 this->ComputeKindedSources(files
, config
);
410 files
.Initialized
= true;
414 void cmGeneratorTarget::ComputeKindedSources(KindedSources
& files
,
415 std::string
const& config
) const
417 // Get the source file paths by string.
418 std::vector
<BT
<std::string
>> srcs
= this->GetSourceFilePaths(config
);
420 cmsys::RegularExpression
header_regex(CM_HEADER_REGEX
);
421 std::vector
<cmSourceFile
*> badObjLib
;
423 std::set
<cmSourceFile
*> emitted
;
424 for (BT
<std::string
> const& s
: srcs
) {
425 // Create each source at most once.
426 cmSourceFile
* sf
= this->Makefile
->GetOrCreateSource(s
.Value
);
427 if (!emitted
.insert(sf
).second
) {
431 // Compute the kind (classification) of this source file.
433 std::string ext
= cmSystemTools::LowerCase(sf
->GetExtension());
434 cmFileSet
const* fs
= this->GetFileSetForSource(config
, sf
);
435 if (sf
->GetCustomCommand()) {
436 kind
= SourceKindCustomCommand
;
437 } else if (!this->Target
->IsNormal() && !this->Target
->IsImported() &&
438 fs
&& (fs
->GetType() == "CXX_MODULES"_s
)) {
439 kind
= SourceKindCxxModuleSource
;
440 } else if (this->Target
->GetType() == cmStateEnums::UTILITY
||
441 this->Target
->GetType() == cmStateEnums::INTERFACE_LIBRARY
442 // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
443 // NOLINTNEXTLINE(bugprone-branch-clone)
445 kind
= SourceKindExtra
;
446 } else if (this->IsSourceFilePartOfUnityBatch(sf
->ResolveFullPath())) {
447 kind
= SourceKindUnityBatched
;
448 // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
449 // NOLINTNEXTLINE(bugprone-branch-clone)
450 } else if (sf
->GetPropertyAsBool("HEADER_FILE_ONLY")) {
451 kind
= SourceKindHeader
;
452 } else if (sf
->GetPropertyAsBool("EXTERNAL_OBJECT")) {
453 kind
= SourceKindExternalObject
;
454 } else if (!sf
->GetOrDetermineLanguage().empty()) {
455 kind
= SourceKindObjectSource
;
456 } else if (ext
== "def") {
457 kind
= SourceKindModuleDefinition
;
458 if (this->GetType() == cmStateEnums::OBJECT_LIBRARY
) {
459 badObjLib
.push_back(sf
);
461 } else if (ext
== "idl") {
462 kind
= SourceKindIDL
;
463 if (this->GetType() == cmStateEnums::OBJECT_LIBRARY
) {
464 badObjLib
.push_back(sf
);
466 } else if (ext
== "resx") {
467 kind
= SourceKindResx
;
468 } else if (ext
== "appxmanifest") {
469 kind
= SourceKindAppManifest
;
470 } else if (ext
== "manifest") {
471 if (sf
->GetPropertyAsBool("VS_DEPLOYMENT_CONTENT")) {
472 kind
= SourceKindExtra
;
474 kind
= SourceKindManifest
;
476 } else if (ext
== "pfx") {
477 kind
= SourceKindCertificate
;
478 } else if (ext
== "xaml") {
479 kind
= SourceKindXaml
;
480 } else if (header_regex
.find(sf
->ResolveFullPath())) {
481 kind
= SourceKindHeader
;
483 kind
= SourceKindExtra
;
486 // Save this classified source file in the result vector.
487 files
.Sources
.push_back({ BT
<cmSourceFile
*>(sf
, s
.Backtrace
), kind
});
490 if (!badObjLib
.empty()) {
491 std::ostringstream e
;
492 e
<< "OBJECT library \"" << this->GetName() << "\" contains:\n";
493 for (cmSourceFile
* i
: badObjLib
) {
494 e
<< " " << i
->GetLocation().GetName() << "\n";
496 e
<< "but may contain only sources that compile, header files, and "
497 "other files that would not affect linking of a normal library.";
498 this->GlobalGenerator
->GetCMakeInstance()->IssueMessage(
499 MessageType::FATAL_ERROR
, e
.str(), this->GetBacktrace());
503 std::vector
<cmGeneratorTarget::AllConfigSource
> const&
504 cmGeneratorTarget::GetAllConfigSources() const
506 if (this->AllConfigSources
.empty()) {
507 this->ComputeAllConfigSources();
509 return this->AllConfigSources
;
512 void cmGeneratorTarget::ComputeAllConfigSources() const
514 std::vector
<std::string
> configs
=
515 this->Makefile
->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig
);
517 std::map
<cmSourceFile
const*, size_t> index
;
519 for (size_t ci
= 0; ci
< configs
.size(); ++ci
) {
520 KindedSources
const& sources
= this->GetKindedSources(configs
[ci
]);
521 for (SourceAndKind
const& src
: sources
.Sources
) {
522 auto mi
= index
.find(src
.Source
.Value
);
523 if (mi
== index
.end()) {
525 acs
.Source
= src
.Source
.Value
;
527 this->AllConfigSources
.push_back(std::move(acs
));
528 std::map
<cmSourceFile
const*, size_t>::value_type
entry(
529 src
.Source
.Value
, this->AllConfigSources
.size() - 1);
530 mi
= index
.insert(entry
).first
;
532 this->AllConfigSources
[mi
->second
].Configs
.push_back(ci
);
537 std::vector
<cmGeneratorTarget::AllConfigSource
>
538 cmGeneratorTarget::GetAllConfigSources(SourceKind kind
) const
540 std::vector
<AllConfigSource
> result
;
541 for (AllConfigSource
const& source
: this->GetAllConfigSources()) {
542 if (source
.Kind
== kind
) {
543 result
.push_back(source
);
549 std::set
<std::string
> cmGeneratorTarget::GetAllConfigCompileLanguages() const
551 std::set
<std::string
> languages
;
552 std::vector
<AllConfigSource
> const& sources
= this->GetAllConfigSources();
553 for (AllConfigSource
const& si
: sources
) {
554 std::string
const& lang
= si
.Source
->GetOrDetermineLanguage();
556 languages
.emplace(lang
);