| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmGeneratorFileSets.h" | |
| #include <algorithm> | |
| #include <iterator> | |
| #include <map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include <cm/memory> | |
| #include <cm/optional> | |
| #include <cmext/algorithm> | |
| #include <cmext/enum_set> | |
| #include "cmFileSet.h" | |
| #include "cmGenExContext.h" | |
| #include "cmGenExEvaluation.h" | |
| #include "cmGeneratorExpression.h" | |
| #include "cmGeneratorExpressionDAGChecker.h" | |
| #include "cmGeneratorExpressionNode.h" | |
| #include "cmGeneratorFileSet.h" | |
| #include "cmGeneratorTarget.h" | |
| #include "cmLinkItem.h" | |
| #include "cmList.h" | |
| #include "cmListFileCache.h" | |
| #include "cmLocalGenerator.h" | |
| #include "cmMakefile.h" | |
| #include "cmMessageType.h" | |
| #include "cmSourceFile.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTarget.h" | |
| #include "cmValue.h" | |
| cmGeneratorFileSets::cmGeneratorFileSets(cmGeneratorTarget* target, | |
| cmLocalGenerator* lg) | |
| : Target(target) | |
| , LocalGenerator(lg) | |
| { | |
| bool isFramework = target->IsFrameworkOnApple(); | |
| auto issueMessage = [&target](cmFileSet const* fileSet) { | |
| target->Makefile->IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat(R"(The file set ")", fileSet->GetName(), R"(", of type ")", | |
| fileSet->GetType(), | |
| R"(", is incompatible with the "FRAMEWORK" target ")", | |
| target->GetName(), R"(".)")); | |
| }; | |
| for (auto domain : cm::FileSetMetadata::AllFileSetDomains) { | |
| for (auto const& name : target->Target->GetAllPrivateFileSets(domain)) { | |
| cmFileSet const* fileSet = target->Target->GetFileSet(name); | |
| if (isFramework && | |
| !cm::FileSetMetadata::IsFrameworkSupported(fileSet->GetType())) { | |
| issueMessage(fileSet); | |
| continue; | |
| } | |
| auto entry = this->FileSets.emplace( | |
| name, cm::make_unique<cmGeneratorFileSet>(target, fileSet, domain)); | |
| auto const* genFileSet = entry.first->second.get(); | |
| this->AllFileSets.push_back(genFileSet); | |
| this->SelfFileSets[genFileSet->GetType()].push_back(genFileSet); | |
| } | |
| for (auto const& name : target->Target->GetAllInterfaceFileSets(domain)) { | |
| auto it = this->FileSets.find(name); | |
| cmGeneratorFileSet const* genFileSet = nullptr; | |
| if (it == this->FileSets.end()) { | |
| cmFileSet const* fileSet = target->Target->GetFileSet(name); | |
| if (isFramework && | |
| !cm::FileSetMetadata::IsFrameworkSupported(fileSet->GetType())) { | |
| issueMessage(fileSet); | |
| continue; | |
| } | |
| auto entry = this->FileSets.emplace( | |
| name, cm::make_unique<cmGeneratorFileSet>(target, fileSet, domain)); | |
| genFileSet = entry.first->second.get(); | |
| this->AllFileSets.push_back(genFileSet); | |
| } else { | |
| genFileSet = it->second.get(); | |
| } | |
| this->InterfaceFileSets[genFileSet->GetType()].push_back(genFileSet); | |
| } | |
| } | |
| } | |
| cmGeneratorFileSets::~cmGeneratorFileSets() = default; | |
| std::vector<cm::string_view> cmGeneratorFileSets::GetFileSetTypes() const | |
| { | |
| return cm::keys(this->SelfFileSets); | |
| } | |
| std::vector<cm::string_view> cmGeneratorFileSets::GetInterfaceFileSetTypes() | |
| const | |
| { | |
| return cm::keys(this->InterfaceFileSets); | |
| } | |
| std::vector<cmGeneratorFileSet const*> const& | |
| cmGeneratorFileSets::GetAllFileSets() const | |
| { | |
| return this->AllFileSets; | |
| } | |
| namespace { | |
| std::vector<cmGeneratorFileSet const*> NoFileSets; | |
| } | |
| std::vector<cmGeneratorFileSet const*> const& cmGeneratorFileSets::GetFileSets( | |
| cm::string_view type) const | |
| { | |
| auto it = this->SelfFileSets.find(type); | |
| if (it != this->SelfFileSets.end()) { | |
| return it->second; | |
| } | |
| return NoFileSets; | |
| } | |
| std::vector<cmGeneratorFileSet const*> const& | |
| cmGeneratorFileSets::GetInterfaceFileSets(cm::string_view type) const | |
| { | |
| auto it = this->InterfaceFileSets.find(type); | |
| if (it != this->InterfaceFileSets.end()) { | |
| return it->second; | |
| } | |
| return NoFileSets; | |
| } | |
| cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSet( | |
| std::string const& name) const | |
| { | |
| auto const it = this->FileSets.find(name); | |
| if (it != this->FileSets.end()) { | |
| return it->second.get(); | |
| } | |
| return nullptr; | |
| } | |
| std::unordered_set<cmGeneratorFileSet const*> const& | |
| cmGeneratorFileSets::GetAllFileSetsForSource(std::string const& config, | |
| std::string const& path) const | |
| { | |
| this->BuildInfoCache(config); | |
| auto const& info = this->Configs[config]; | |
| auto const it = info.FileSetCache.find(path); | |
| if (it != info.FileSetCache.end()) { | |
| return it->second; | |
| } | |
| // search in all the dependents | |
| auto const it2 = info.InterfaceFileSetCache.find(path); | |
| if (it2 != info.InterfaceFileSetCache.end()) { | |
| return it2->second; | |
| } | |
| static std::unordered_set<cmGeneratorFileSet const*> emptySet; | |
| return emptySet; | |
| } | |
| std::unordered_set<cmGeneratorFileSet const*> const& | |
| cmGeneratorFileSets::GetAllFileSetsForSource(std::string const& config, | |
| cmSourceFile const* sf) const | |
| { | |
| return this->GetAllFileSetsForSource(config, sf->GetFullPath()); | |
| } | |
| cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource( | |
| std::string const& config, std::string const& file) const | |
| { | |
| this->BuildInfoCache(config); | |
| auto const& info = this->Configs[config]; | |
| using FileSetCache = | |
| std::map<std::string, std::unordered_set<cmGeneratorFileSet const*>>; | |
| auto lookup = [&file](FileSetCache const& cache, bool nonHeaderOnly) { | |
| auto const entry = cache.find(file); | |
| if (entry == cache.end()) { | |
| return static_cast<cmGeneratorFileSet const*>(nullptr); | |
| } | |
| auto const fileSet = | |
| std::find_if(entry->second.begin(), entry->second.end(), | |
| [nonHeaderOnly](cmGeneratorFileSet const* candidate) { | |
| return !nonHeaderOnly || | |
| candidate->GetType() != cm::FileSetMetadata::HEADERS; | |
| }); | |
| return fileSet == entry->second.end() ? nullptr : *fileSet; | |
| }; | |
| if (auto const* fileSet = lookup(info.FileSetCache, true)) { | |
| return fileSet; | |
| } | |
| if (auto const* fileSet = lookup(info.InterfaceFileSetCache, true)) { | |
| return fileSet; | |
| } | |
| if (auto const* fileSet = lookup(info.FileSetCache, false)) { | |
| return fileSet; | |
| } | |
| return lookup(info.InterfaceFileSetCache, false); | |
| } | |
| cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource( | |
| std::string const& config, cmSourceFile const* sf) const | |
| { | |
| return this->GetFileSetForSource(config, sf->GetFullPath()); | |
| } | |
| cm::optional<std::string> cmGeneratorFileSets::GetLanguageForSource( | |
| std::string const& config, cmSourceFile const* sf) const | |
| { | |
| this->BuildInfoCache(config); | |
| auto const& languages = this->Configs[config].LanguageCache; | |
| auto const language = languages.find(sf->GetFullPath()); | |
| if (language == languages.end()) { | |
| return cm::nullopt; | |
| } | |
| return language->second; | |
| } | |
| bool cmGeneratorFileSets::HasSyclHeaders(std::string const& config) const | |
| { | |
| this->BuildInfoCache(config); | |
| return this->Configs[config].HasSyclHeaders; | |
| } | |
| std::vector<std::unique_ptr<cm::TargetPropertyEntry>> | |
| cmGeneratorFileSets::GetSources( | |
| std::function<bool(cmGeneratorFileSet const*)> include, | |
| cm::GenEx::Context const& context, cmGeneratorTarget const* target, | |
| cmGeneratorExpressionDAGChecker* dagChecker) const | |
| { | |
| std::vector<std::unique_ptr<TargetPropertyEntry>> entries; | |
| for (auto const& entry : this->FileSets) { | |
| auto const* fileSet = entry.second.get(); | |
| if (include(fileSet)) { | |
| auto sources = fileSet->GetSources(context, target, dagChecker); | |
| std::move(sources.begin(), sources.end(), std::back_inserter(entries)); | |
| } | |
| } | |
| return entries; | |
| } | |
| std::vector<std::unique_ptr<cm::TargetPropertyEntry>> | |
| cmGeneratorFileSets::GetSources( | |
| cm::GenEx::Context const& context, cmGeneratorTarget const* target, | |
| cm::FileSetMetadata::FileSetDomainSet domains, | |
| cmGeneratorExpressionDAGChecker* dagChecker) const | |
| { | |
| return this->GetSources( | |
| [&domains](cmGeneratorFileSet const* fileSet) -> bool { | |
| return fileSet->IsForSelf() && domains.contains(fileSet->GetDomain()); | |
| }, | |
| context, target, dagChecker); | |
| } | |
| std::vector<std::unique_ptr<cm::TargetPropertyEntry>> | |
| cmGeneratorFileSets::GetSources( | |
| std::string type, cm::GenEx::Context const& context, | |
| cmGeneratorTarget const* target, | |
| cmGeneratorExpressionDAGChecker* dagChecker) const | |
| { | |
| return this->GetSources( | |
| [&type](cmGeneratorFileSet const* fileSet) -> bool { | |
| return fileSet->IsForSelf() && fileSet->GetType() == type; | |
| }, | |
| context, target, dagChecker); | |
| } | |
| std::vector<std::unique_ptr<cm::TargetPropertyEntry>> | |
| cmGeneratorFileSets::GetInterfaceSources( | |
| cm::GenEx::Context const& context, cmGeneratorTarget const* target, | |
| cm::FileSetMetadata::FileSetDomainSet domains, | |
| cmGeneratorExpressionDAGChecker* dagChecker) const | |
| { | |
| return this->GetSources( | |
| [&domains](cmGeneratorFileSet const* fileSet) -> bool { | |
| return fileSet->IsForInterface() && | |
| domains.contains(fileSet->GetDomain()); | |
| }, | |
| context, target, dagChecker); | |
| } | |
| std::vector<std::unique_ptr<cm::TargetPropertyEntry>> | |
| cmGeneratorFileSets::GetInterfaceSources( | |
| std::string type, cm::GenEx::Context const& context, | |
| cmGeneratorTarget const* target, | |
| cmGeneratorExpressionDAGChecker* dagChecker) const | |
| { | |
| return this->GetSources( | |
| [&type](cmGeneratorFileSet const* fileSet) -> bool { | |
| return fileSet->IsForInterface() && fileSet->GetType() == type; | |
| }, | |
| context, target, dagChecker); | |
| } | |
| bool cmGeneratorFileSets::MaybeHaveInterfaceProperty( | |
| cm::string_view type, std::string const& prop, | |
| cm::GenEx::Evaluation* eval) const | |
| { | |
| std::string const key = | |
| cmStrCat(type, "::", prop, '@', eval->Context.Config); | |
| auto i = this->MaybeInterfacePropertyExists.find(key); | |
| if (i == this->MaybeInterfacePropertyExists.end()) { | |
| // Insert an entry now in case there is a cycle. | |
| i = this->MaybeInterfacePropertyExists.emplace(key, false).first; | |
| bool& maybeInterfaceProp = i->second; | |
| for (auto const* fileSet : this->GetInterfaceFileSets(type)) { | |
| // If this file set itself has a non-empty property value, we are done. | |
| maybeInterfaceProp = !fileSet->GetProperty(prop).IsEmpty(); | |
| if (maybeInterfaceProp) { | |
| break; | |
| } | |
| } | |
| // Otherwise, recurse to interface dependencies. | |
| if (!maybeInterfaceProp) { | |
| cmGeneratorTarget const* headTarget = | |
| eval->HeadTarget ? eval->HeadTarget : this->Target; | |
| if (cmLinkInterfaceLibraries const* iface = | |
| this->Target->GetLinkInterfaceLibraries( | |
| eval->Context.Config, headTarget, | |
| cmGeneratorTarget::UseTo::Compile)) { | |
| if (iface->HadHeadSensitiveCondition) { | |
| // With a different head target we may get to a library with | |
| // this interface property. | |
| maybeInterfaceProp = true; | |
| } else { | |
| // The transitive interface libraries do not depend on the | |
| // head target, so we can follow them. | |
| for (cmLinkItem const& lib : iface->Libraries) { | |
| if (lib.Target && | |
| lib.Target->GetGeneratorFileSets()->MaybeHaveInterfaceProperty( | |
| type, prop, eval)) { | |
| maybeInterfaceProp = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return i->second; | |
| } | |
| std::string cmGeneratorFileSets::EvaluateInterfaceProperty( | |
| cm::string_view type, std::string const& prop, cm::GenEx::Evaluation* eval, | |
| cmGeneratorExpressionDAGChecker* dagCheckerParent) const | |
| { | |
| // If the property does not appear transitively at all, we are done. | |
| if (!this->MaybeHaveInterfaceProperty(type, prop, eval)) { | |
| return std::string{}; | |
| } | |
| cmList result; | |
| cmGeneratorExpressionDAGChecker dagChecker{ | |
| this->Target, prop, nullptr, | |
| dagCheckerParent, eval->Context, eval->Backtrace, | |
| }; | |
| switch (dagChecker.Check()) { | |
| case cmGeneratorExpressionDAGChecker::SELF_REFERENCE: | |
| dagChecker.ReportError( | |
| eval, | |
| cmStrCat("$<FILE_SET_PROPERTY:*,TARGET:", this->Target->GetName(), ',', | |
| prop, '>')); | |
| return std::string{}; | |
| case cmGeneratorExpressionDAGChecker::CYCLIC_REFERENCE: | |
| // No error. We just skip cyclic references. | |
| case cmGeneratorExpressionDAGChecker::ALREADY_SEEN: | |
| // No error. We have already seen this transitive property. | |
| return std::string{}; | |
| case cmGeneratorExpressionDAGChecker::DAG: | |
| break; | |
| } | |
| cmGeneratorTarget const* headTarget = | |
| eval->HeadTarget ? eval->HeadTarget : this->Target; | |
| for (auto const* fileSet : this->GetInterfaceFileSets(type)) { | |
| if (cmValue p = fileSet->GetProperty(prop)) { | |
| result.append(cmGeneratorExpressionNode::EvaluateDependentExpression( | |
| *p, eval, headTarget, &dagChecker, this->Target)); | |
| } | |
| } | |
| if (cmLinkInterfaceLibraries const* iface = | |
| this->Target->GetLinkInterfaceLibraries( | |
| eval->Context.Config, headTarget, | |
| cmGeneratorTarget::UseTo::Compile)) { | |
| eval->HadContextSensitiveCondition = eval->HadContextSensitiveCondition || | |
| iface->HadContextSensitiveCondition; | |
| for (cmLinkItem const& lib : iface->Libraries) { | |
| // Broken code can have a target in its own link interface. | |
| // Don't follow such link interface entries so as not to create a | |
| // self-referencing loop. | |
| if (lib.Target && lib.Target != this->Target) { | |
| // Pretend $<FILE_SET__PROPERTY:fileSet,TARGET:lib.Target,prop> | |
| // appeared in the above property and hand-evaluate it as if it were | |
| // compiled. | |
| // Create a context as cmCompiledGeneratorExpression::Evaluate does. | |
| cm::GenEx::Evaluation libEval( | |
| eval->Context, eval->Quiet, headTarget, this->Target, | |
| eval->EvaluateForBuildsystem, eval->Backtrace); | |
| std::string libResult = cmGeneratorExpression::StripEmptyListElements( | |
| lib.Target->GetGeneratorFileSets()->EvaluateInterfaceProperty( | |
| type, prop, &libEval, &dagChecker)); | |
| if (!libResult.empty()) { | |
| result.append(libResult); | |
| } | |
| eval->HadContextSensitiveCondition = | |
| eval->HadContextSensitiveCondition || | |
| libEval.HadContextSensitiveCondition; | |
| eval->HadHeadSensitiveCondition = | |
| eval->HadHeadSensitiveCondition || libEval.HadHeadSensitiveCondition; | |
| } | |
| } | |
| } | |
| return result.to_string(); | |
| } | |
| namespace { | |
| void GetInterfaceFiles( | |
| cmGeneratorTarget const* target, cmGeneratorTarget const* headTarget, | |
| cm::GenEx::Context const& context, | |
| std::unordered_set<cmGeneratorTarget const*>& targets, | |
| std::map<std::string, std::unordered_set<cmGeneratorFileSet const*>>& cache) | |
| { | |
| namespace Metadata = cm::FileSetMetadata; | |
| for (auto const& type : Metadata::GetKnownTypes()) { | |
| auto fileSetDescriptor = Metadata::GetFileSetDescriptor(type); | |
| if (fileSetDescriptor && | |
| fileSetDescriptor->Lookup == Metadata::FileSetLookup::Dependencies) { | |
| for (auto const* fileSet : target->GetInterfaceFileSets(type)) { | |
| auto files = fileSet->GetFiles(context, headTarget); | |
| for (auto const& it : files.first) { | |
| for (auto const& filename : it.second) { | |
| auto collapsedFile = cmSystemTools::CollapseFullPath(filename); | |
| cache[collapsedFile].insert(fileSet); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (cmLinkInterfaceLibraries const* iface = | |
| target->GetLinkInterfaceLibraries(context.Config, headTarget, | |
| cmGeneratorTarget::UseTo::Compile)) { | |
| for (cmLinkItem const& lib : iface->Libraries) { | |
| if (lib.Target && lib.Target != target && | |
| targets.insert(lib.Target).second) { | |
| GetInterfaceFiles(lib.Target, headTarget, context, targets, cache); | |
| } | |
| } | |
| } | |
| } | |
| bool HasInterfaceSyclHeaders( | |
| cmGeneratorTarget const* target, cmGeneratorTarget const* headTarget, | |
| std::string const& config, | |
| std::unordered_set<cmGeneratorTarget const*>& targets) | |
| { | |
| for (auto const* fileSet : | |
| target->GetInterfaceFileSets(cm::FileSetMetadata::HEADERS)) { | |
| if (fileSet->GetProperty("LANGUAGE") == cm::FileSetMetadata::SYCL) { | |
| return true; | |
| } | |
| } | |
| if (cmLinkInterfaceLibraries const* iface = | |
| target->GetLinkInterfaceLibraries(config, headTarget, | |
| cmGeneratorTarget::UseTo::Compile)) { | |
| for (cmLinkItem const& lib : iface->Libraries) { | |
| if (lib.Target && lib.Target != target && | |
| targets.insert(lib.Target).second && | |
| HasInterfaceSyclHeaders(lib.Target, headTarget, config, targets)) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| } | |
| void cmGeneratorFileSets::BuildInfoCache(std::string const& config) const | |
| { | |
| auto& info = this->Configs[config]; | |
| if (info.BuiltCache) { | |
| return; | |
| } | |
| cm::GenEx::Context context(this->LocalGenerator, config); | |
| info.HasSyclHeaders = std::any_of( | |
| this->GetFileSets(cm::FileSetMetadata::HEADERS).begin(), | |
| this->GetFileSets(cm::FileSetMetadata::HEADERS).end(), | |
| [](cmGeneratorFileSet const* fileSet) { | |
| return fileSet->GetProperty("LANGUAGE") == cm::FileSetMetadata::SYCL; | |
| }); | |
| for (auto const& item : this->FileSets) { | |
| auto const* fileSet = item.second.get(); | |
| if (!fileSet->IsForSelf()) { | |
| continue; | |
| } | |
| auto files = fileSet->GetFiles(context, this->Target); | |
| for (auto const& it : files.first) { | |
| for (auto const& filename : it.second) { | |
| auto collapsedFile = cmSystemTools::CollapseFullPath(filename); | |
| info.FileSetCache[collapsedFile].insert(fileSet); | |
| } | |
| } | |
| } | |
| // retrieve all files inherited from dependent targets | |
| std::unordered_set<cmGeneratorTarget const*> targets; | |
| std::unordered_set<cmGeneratorTarget const*> syclHeaderTargets; | |
| if (cmLinkImplementationLibraries const* impl = | |
| this->Target->GetLinkImplementationLibraries( | |
| config, cmGeneratorTarget::UseTo::Compile)) { | |
| for (cmLinkItem const& lib : impl->Libraries) { | |
| if (!info.HasSyclHeaders && lib.Target && | |
| syclHeaderTargets.insert(lib.Target).second) { | |
| info.HasSyclHeaders = HasInterfaceSyclHeaders( | |
| lib.Target, this->Target, config, syclHeaderTargets); | |
| } | |
| if (lib.Target && targets.insert(lib.Target).second) { | |
| GetInterfaceFiles(lib.Target, this->Target, context, targets, | |
| info.InterfaceFileSetCache); | |
| } | |
| } | |
| } | |
| auto indexLanguages = | |
| [this, | |
| &info](std::map<std::string, | |
| std::unordered_set<cmGeneratorFileSet const*>> const& | |
| fileSets) { | |
| for (auto const& file : fileSets) { | |
| for (auto const* fileSet : file.second) { | |
| if (fileSet->GetType() != cm::FileSetMetadata::SOURCES) { | |
| continue; | |
| } | |
| cmValue const language = fileSet->GetProperty("LANGUAGE"); | |
| if (language.IsEmpty()) { | |
| continue; | |
| } | |
| auto const inserted = | |
| info.LanguageCache.emplace(file.first, *language); | |
| if (!inserted.second && inserted.first->second != *language) { | |
| this->LocalGenerator->IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("Source file\n ", file.first, | |
| "\nbelongs to multiple SOURCES file sets with " | |
| "conflicting LANGUAGE properties.")); | |
| } | |
| } | |
| } | |
| }; | |
| indexLanguages(info.FileSetCache); | |
| indexLanguages(info.InterfaceFileSetCache); | |
| info.BuiltCache = true; | |
| } |