| /* 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 "cmFileSetMetadata.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 "cmSourceFile.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| #include "cmTarget.h" |
| #include "cmValue.h" |
| |
| cmGeneratorFileSets::cmGeneratorFileSets(cmGeneratorTarget* target, |
| cmLocalGenerator* lg) |
| : Target(target) |
| , LocalGenerator(lg) |
| { |
| for (auto const& name : target->Target->GetAllPrivateFileSets()) { |
| auto entry = |
| this->FileSets.emplace(name, |
| cm::make_unique<cmGeneratorFileSet>( |
| target, target->Target->GetFileSet(name))); |
| |
| auto const* fileSet = entry.first->second.get(); |
| this->AllFileSets.push_back(fileSet); |
| this->SelfFileSets[fileSet->GetType()].push_back(fileSet); |
| } |
| for (auto const& name : target->Target->GetAllInterfaceFileSets()) { |
| auto it = this->FileSets.find(name); |
| cmGeneratorFileSet const* fileSet = nullptr; |
| if (it == this->FileSets.end()) { |
| auto entry = |
| this->FileSets.emplace(name, |
| cm::make_unique<cmGeneratorFileSet>( |
| target, target->Target->GetFileSet(name))); |
| fileSet = entry.first->second.get(); |
| this->AllFileSets.push_back(fileSet); |
| } else { |
| fileSet = it->second.get(); |
| } |
| this->InterfaceFileSets[fileSet->GetType()].push_back(fileSet); |
| } |
| } |
| 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; |
| } |
| |
| cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource( |
| std::string const& config, std::string const& path) const |
| { |
| using Lookup = cm::FileSetMetadata::FileSetLookup; |
| |
| 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() && |
| cm::FileSetMetadata::GetFileSetDescriptor(it2->second->GetType()) |
| .value_or(cm::FileSetMetadata::FileSetDescriptor{ Lookup::Target }) |
| .Lookup == Lookup::Dependencies) { |
| return it2->second; |
| } |
| |
| return nullptr; |
| } |
| cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource( |
| std::string const& config, cmSourceFile const* sf) const |
| { |
| return this->GetFileSetForSource(config, sf->GetFullPath()); |
| } |
| |
| 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, |
| cmGeneratorExpressionDAGChecker* dagChecker) const |
| { |
| return this->GetSources( |
| [](cmGeneratorFileSet const* fileSet) -> bool { |
| return fileSet->IsForSelf(); |
| }, |
| 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, |
| cmGeneratorExpressionDAGChecker* dagChecker) const |
| { |
| return this->GetSources( |
| [](cmGeneratorFileSet const* fileSet) -> bool { |
| return fileSet->IsForInterface(); |
| }, |
| 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, |
| cm::GenEx::Context const& context, |
| std::unordered_set<cmGeneratorTarget const*>& targets, |
| std::map<std::string, 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, target); |
| |
| for (auto const& it : files.first) { |
| for (auto const& filename : it.second) { |
| auto collapsedFile = cmSystemTools::CollapseFullPath(filename); |
| cache[collapsedFile] = fileSet; |
| } |
| } |
| } |
| } |
| } |
| |
| if (cmLinkInterfaceLibraries const* iface = |
| target->GetLinkInterfaceLibraries(context.Config, target, |
| cmGeneratorTarget::UseTo::Compile)) { |
| for (cmLinkItem const& lib : iface->Libraries) { |
| if (lib.Target && lib.Target != target && |
| targets.insert(lib.Target).second) { |
| GetInterfaceFiles(lib.Target, context, targets, cache); |
| } |
| } |
| } |
| } |
| } |
| |
| void cmGeneratorFileSets::BuildInfoCache(std::string const& config) const |
| { |
| auto& info = this->Configs[config]; |
| |
| if (info.BuiltCache) { |
| return; |
| } |
| |
| cm::GenEx::Context context(this->LocalGenerator, config); |
| |
| for (auto const& item : this->FileSets) { |
| auto const* fileSet = item.second.get(); |
| |
| 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] = fileSet; |
| } |
| } |
| } |
| |
| // retrieve all files inherited from dependent targets |
| std::unordered_set<cmGeneratorTarget const*> targets; |
| |
| if (cmLinkImplementationLibraries const* impl = |
| this->Target->GetLinkImplementationLibraries( |
| config, cmGeneratorTarget::UseTo::Compile)) { |
| for (cmLinkItem const& lib : impl->Libraries) { |
| if (lib.Target) { |
| GetInterfaceFiles(lib.Target, context, targets, |
| info.InterfaceFileSetCache); |
| } |
| } |
| } |
| |
| info.BuiltCache = true; |
| } |