| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| /* clang-format off */ | |
| #include "cmGeneratorTarget.h" | |
| /* clang-format on */ | |
| #include "cmConfigure.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <functional> | |
| #include <map> | |
| #include <memory> | |
| #include <set> | |
| #include <string> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include <cm/string_view> | |
| #include <cmext/algorithm> | |
| #include "cmsys/RegularExpression.hxx" | |
| #include "cmEvaluatedTargetProperty.h" | |
| #include "cmFileSetMetadata.h" | |
| #include "cmGenExContext.h" | |
| #include "cmGeneratorExpression.h" | |
| #include "cmGeneratorExpressionDAGChecker.h" | |
| #include "cmGeneratorFileSet.h" | |
| #include "cmGeneratorFileSets.h" | |
| #include "cmGlobalGenerator.h" | |
| #include "cmLinkItem.h" | |
| #include "cmList.h" | |
| #include "cmListFileCache.h" | |
| #include "cmLocalGenerator.h" | |
| #include "cmMakefile.h" | |
| #include "cmMessageType.h" | |
| #include "cmPolicies.h" | |
| #include "cmSourceFile.h" | |
| #include "cmSourceFileLocation.h" | |
| #include "cmSourceGroup.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTarget.h" | |
| #include "cmTargetTypes.h" | |
| #include "cmValue.h" | |
| #include "cmake.h" | |
| namespace { | |
| using UseTo = cmGeneratorTarget::UseTo; | |
| void AddObjectEntries(cmGeneratorTarget const* headTarget, | |
| cm::GenEx::Context const& context, | |
| cmGeneratorExpressionDAGChecker* dagChecker, | |
| cm::EvaluatedTargetPropertyEntries& entries) | |
| { | |
| if (cmLinkImplementationLibraries const* impl = | |
| headTarget->GetLinkImplementationLibraries(context.Config, | |
| UseTo::Link)) { | |
| entries.HadContextSensitiveCondition = impl->HadContextSensitiveCondition; | |
| for (cmLinkItem const& lib : impl->Libraries) { | |
| if (lib.Target && | |
| lib.Target->GetType() == cm::TargetType::OBJECT_LIBRARY) { | |
| std::string uniqueName = | |
| headTarget->GetGlobalGenerator()->IndexGeneratorTargetUniquely( | |
| lib.Target); | |
| std::string genex = | |
| cmStrCat("$<TARGET_OBJECTS:", std::move(uniqueName), '>'); | |
| cmGeneratorExpression ge(*headTarget->Makefile->GetCMakeInstance(), | |
| lib.Backtrace); | |
| std::unique_ptr<cmCompiledGeneratorExpression> cge = | |
| ge.Parse(std::move(genex)); | |
| cge->SetEvaluateForBuildsystem(true); | |
| cm::EvaluatedTargetPropertyEntry ee(lib, lib.Backtrace); | |
| cmExpandList(cge->Evaluate(context, dagChecker, headTarget), | |
| ee.Values); | |
| if (cge->GetHadContextSensitiveCondition()) { | |
| ee.ContextDependent = true; | |
| } | |
| entries.Entries.emplace_back(std::move(ee)); | |
| } | |
| } | |
| } | |
| } | |
| void AddFileSetEntries(cmGeneratorTarget const* headTarget, | |
| cmGeneratorFileSets const* fileSets, | |
| cm::GenEx::Context const& context, | |
| cmGeneratorExpressionDAGChecker* dagChecker, | |
| cm::EvaluatedTargetPropertyEntries& entries) | |
| { | |
| auto sources = | |
| fileSets->GetSources(context, headTarget, | |
| cm::FileSetMetadata::FileSetDomainSet{ | |
| cm::FileSetMetadata::FileSetDomain::NATIVE }, | |
| dagChecker); | |
| entries = | |
| EvaluateTargetPropertyEntries(headTarget, context, dagChecker, sources); | |
| } | |
| bool processSources(cmGeneratorTarget const* tgt, std::string const& config, | |
| cm::EvaluatedTargetPropertyEntries& entries, | |
| std::vector<BT<std::string>>& srcs, | |
| std::unordered_set<std::string>& uniqueSrcs, | |
| bool debugSources, | |
| std::function<void(cmSourceFile*)> postProcess = {}) | |
| { | |
| cmMakefile* mf = tgt->Target->GetMakefile(); | |
| bool contextDependent = entries.HadContextSensitiveCondition; | |
| for (cm::EvaluatedTargetPropertyEntry& entry : entries.Entries) { | |
| if (entry.ContextDependent) { | |
| contextDependent = true; | |
| } | |
| cmLinkItem const& item = entry.LinkItem; | |
| std::string const& targetName = item.AsStr(); | |
| for (std::string& src : entry.Values) { | |
| cmSourceFile* sf = mf->GetOrCreateSource(src); | |
| std::string e; | |
| std::string w; | |
| std::string fullPath = sf->ResolveFullPath(&e, &w); | |
| cmLocalGenerator const* const lg = tgt->GetLocalGenerator(); | |
| if (!w.empty()) { | |
| lg->IssuePolicyWarning(cmPolicies::CMP0115, {}, w, entry.Backtrace); | |
| } | |
| if (fullPath.empty()) { | |
| if (!e.empty()) { | |
| lg->IssueMessage(MessageType::FATAL_ERROR, e, entry.Backtrace); | |
| } | |
| return contextDependent; | |
| } | |
| if (!targetName.empty() && !cmSystemTools::FileIsFullPath(src)) { | |
| if (!targetName.empty()) { | |
| tgt->GetLocalGenerator()->IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("Target \"", targetName, | |
| "\" contains relative path in its INTERFACE_SOURCES:\n " | |
| "\"", | |
| src, '"')); | |
| } else { | |
| tgt->GetLocalGenerator()->IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("Found relative path while evaluating sources of \"", | |
| tgt->GetName(), "\":\n \"", src, "\"\n")); | |
| } | |
| return contextDependent; | |
| } | |
| src = fullPath; | |
| if (postProcess) { | |
| postProcess(sf); | |
| } | |
| } | |
| std::string usedSources; | |
| for (std::string const& src : entry.Values) { | |
| if (uniqueSrcs.insert(src).second) { | |
| srcs.emplace_back(src, entry.Backtrace); | |
| if (debugSources) { | |
| usedSources = cmStrCat(std::move(usedSources), " * ", src, '\n'); | |
| } | |
| } else { | |
| auto const& fileSets = | |
| tgt->GetGeneratorFileSets()->GetAllFileSetsForSource(config, src); | |
| if (fileSets.empty()) { | |
| continue; | |
| } | |
| auto fileMustBeUnique = [&fileSets]() -> bool { | |
| return std::none_of( | |
| fileSets.begin(), fileSets.end(), | |
| [](cmGeneratorFileSet const* fileSet) { | |
| return cm::FileSetMetadata::GetAttributes(fileSet->GetType()) | |
| .contains(cm::FileSetMetadata::FileSetAttributes:: | |
| FilesInMultipleFileSets); | |
| }); | |
| }; | |
| if (fileMustBeUnique()) { | |
| auto const* fileSet = *fileSets.begin(); | |
| switch (tgt->GetPolicyStatusCMP0211()) { | |
| case cmPolicies::WARN: | |
| tgt->GetLocalGenerator()->IssuePolicyWarning( | |
| cmPolicies::CMP0211, {}, | |
| cmStrCat("In target \"", tgt->GetName(), "\" the file\n ", | |
| src, "\nalready belongs to file set \"", | |
| fileSet->GetName(), "\".")); | |
| CM_FALLTHROUGH; | |
| case cmPolicies::OLD: | |
| break; | |
| default: | |
| tgt->GetLocalGenerator()->IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("In target \"", tgt->GetName(), "\" the file\n ", | |
| src, "\nalready belongs to file set \"", | |
| fileSet->GetName(), "\".")); | |
| } | |
| } | |
| } | |
| } | |
| if (!usedSources.empty()) { | |
| tgt->GetLocalGenerator()->GetCMakeInstance()->IssueMessage( | |
| MessageType::LOG, | |
| cmStrCat("Used sources for target ", tgt->GetName(), ":\n", | |
| usedSources), | |
| entry.Backtrace); | |
| } | |
| } | |
| return contextDependent; | |
| } | |
| } | |
| std::vector<BT<std::string>> cmGeneratorTarget::GetSourceFilePaths( | |
| std::string const& config) const | |
| { | |
| std::vector<BT<std::string>> files; | |
| cmList debugProperties{ this->Makefile->GetDefinition( | |
| "CMAKE_DEBUG_TARGET_PROPERTIES") }; | |
| bool debugSources = | |
| !this->DebugSourcesDone && cm::contains(debugProperties, "SOURCES"); | |
| this->DebugSourcesDone = true; | |
| cm::GenEx::Context context(this->LocalGenerator, config, | |
| /*language=*/std::string()); | |
| cmGeneratorExpressionDAGChecker dagChecker{ | |
| this, "SOURCES", nullptr, nullptr, context, | |
| }; | |
| cm::EvaluatedTargetPropertyEntries entries = | |
| cm::EvaluateTargetPropertyEntries(this, context, &dagChecker, | |
| this->SourceEntries); | |
| std::unordered_set<std::string> uniqueSrcs; | |
| bool contextDependentDirectSources = | |
| processSources(this, config, entries, files, uniqueSrcs, debugSources); | |
| // Collect INTERFACE_SOURCES of all direct link-dependencies. | |
| cm::EvaluatedTargetPropertyEntries linkInterfaceSourcesEntries; | |
| cm::AddInterfaceEntries(this, "INTERFACE_SOURCES", context, &dagChecker, | |
| linkInterfaceSourcesEntries, | |
| cm::IncludeRuntimeInterface::No, UseTo::Compile); | |
| bool contextDependentInterfaceSources = | |
| processSources(this, config, linkInterfaceSourcesEntries, files, | |
| uniqueSrcs, debugSources); | |
| // Collect TARGET_OBJECTS of direct object link-dependencies. | |
| bool contextDependentObjects = false; | |
| if (this->GetType() != cm::TargetType::OBJECT_LIBRARY) { | |
| cm::EvaluatedTargetPropertyEntries linkObjectsEntries; | |
| AddObjectEntries(this, context, &dagChecker, linkObjectsEntries); | |
| contextDependentObjects = processSources(this, config, linkObjectsEntries, | |
| files, uniqueSrcs, debugSources); | |
| // Note that for imported targets or multi-config generators supporting | |
| // cross-config builds the paths to the object files must be per-config, | |
| // so contextDependentObjects will be true here even if object libraries | |
| // are specified without per-config generator expressions. | |
| } | |
| // Collect this target's file sets. | |
| cmGeneratorExpressionDAGChecker fsDagChecker{ | |
| this, "SOURCES", nullptr, nullptr, context, | |
| }; | |
| cm::EvaluatedTargetPropertyEntries fileSetEntries; | |
| AddFileSetEntries(this, this->FileSets.get(), context, &fsDagChecker, | |
| fileSetEntries); | |
| auto processFileSetEntry = [this, &config](cmSourceFile* sf) { | |
| cmGeneratorFileSet const* fileSet = this->GetFileSetForSource(config, sf); | |
| if (fileSet && fileSet->GetType() == cm::FileSetMetadata::HEADERS) { | |
| sf->SetProperty("HEADER_FILE_ONLY", "TRUE"); | |
| #if !defined(CMAKE_BOOTSTRAP) | |
| cmMakefile* mf = this->Target->GetMakefile(); | |
| std::string const& path = sf->GetFullPath(); | |
| bool found = false; | |
| for (auto const& sg : mf->GetSourceGroups()) { | |
| if (sg->MatchChildrenFiles(path)) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| mf->GetOrCreateSourceGroup("Header Files")->AddGroupFile(path); | |
| } | |
| #endif | |
| } | |
| }; | |
| bool contextDependentFileSets = | |
| processSources(this, config, fileSetEntries, files, uniqueSrcs, | |
| debugSources, processFileSetEntry); | |
| // Collect file sets INTERFACE_SOURCES of all direct link-dependencies. | |
| cm::EvaluatedTargetPropertyEntries linkInterfaceFileSetsEntries; | |
| cm::AddInterfaceFileSetsEntries(this, cm::FileSetMetadata::SOURCES, | |
| "INTERFACE_SOURCES", context, &fsDagChecker, | |
| linkInterfaceFileSetsEntries); | |
| bool contextDependentInterfaceFileSets = | |
| processSources(this, config, linkInterfaceFileSetsEntries, files, | |
| uniqueSrcs, debugSources); | |
| // Determine if sources are context-dependent or not. | |
| if (!contextDependentDirectSources && !contextDependentInterfaceSources && | |
| !contextDependentObjects && !contextDependentFileSets && | |
| !contextDependentInterfaceFileSets) { | |
| this->SourcesAreContextDependent = Tribool::False; | |
| } else { | |
| this->SourcesAreContextDependent = Tribool::True; | |
| } | |
| return files; | |
| } | |
| void cmGeneratorTarget::GetSourceFiles(std::vector<cmSourceFile*>& files, | |
| std::string const& config) const | |
| { | |
| std::vector<BT<cmSourceFile*>> tmp = this->GetSourceFiles(config); | |
| files.reserve(tmp.size()); | |
| for (BT<cmSourceFile*>& v : tmp) { | |
| files.push_back(v.Value); | |
| } | |
| } | |
| std::vector<BT<cmSourceFile*>> cmGeneratorTarget::GetSourceFiles( | |
| std::string const& config) const | |
| { | |
| std::vector<BT<cmSourceFile*>> files; | |
| KindedSources const& kinded = this->GetKindedSources(config); | |
| files.reserve(kinded.Sources.size()); | |
| for (SourceAndKind const& si : kinded.Sources) { | |
| files.push_back(si.Source); | |
| } | |
| return files; | |
| } | |
| void cmGeneratorTarget::GetSourceFilesWithoutObjectLibraries( | |
| std::vector<cmSourceFile*>& files, std::string const& config) const | |
| { | |
| std::vector<BT<cmSourceFile*>> tmp = | |
| this->GetSourceFilesWithoutObjectLibraries(config); | |
| files.reserve(tmp.size()); | |
| for (BT<cmSourceFile*>& v : tmp) { | |
| files.push_back(v.Value); | |
| } | |
| } | |
| std::vector<BT<cmSourceFile*>> | |
| cmGeneratorTarget::GetSourceFilesWithoutObjectLibraries( | |
| std::string const& config) const | |
| { | |
| std::vector<BT<cmSourceFile*>> files; | |
| KindedSources const& kinded = this->GetKindedSources(config); | |
| files.reserve(kinded.Sources.size()); | |
| for (SourceAndKind const& si : kinded.Sources) { | |
| if (si.Source.Value->GetObjectLibrary().empty()) { | |
| files.push_back(si.Source); | |
| } | |
| } | |
| return files; | |
| } | |
| cmGeneratorTarget::KindedSources const& cmGeneratorTarget::GetKindedSources( | |
| std::string const& config) const | |
| { | |
| // If we already processed one configuration and found no dependency | |
| // on configuration then always use the one result. | |
| if (this->SourcesAreContextDependent == Tribool::False) { | |
| return this->KindedSourcesMap.begin()->second; | |
| } | |
| // Lookup any existing link implementation for this configuration. | |
| std::string const key = cmSystemTools::UpperCase(config); | |
| auto it = this->KindedSourcesMap.find(key); | |
| if (it != this->KindedSourcesMap.end()) { | |
| if (!it->second.Initialized) { | |
| this->GlobalGenerator->GetCMakeInstance()->IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| cmStrCat("The SOURCES of \"", this->GetName(), | |
| "\" use a generator expression that depends on the " | |
| "SOURCES themselves."), | |
| this->GetBacktrace()); | |
| static KindedSources empty; | |
| return empty; | |
| } | |
| return it->second; | |
| } | |
| // Add an entry to the map for this configuration. | |
| KindedSources& files = this->KindedSourcesMap[key]; | |
| this->ComputeKindedSources(files, config); | |
| files.Initialized = true; | |
| return files; | |
| } | |
| void cmGeneratorTarget::ComputeKindedSources(KindedSources& files, | |
| std::string const& config) const | |
| { | |
| // Get the source file paths by string. | |
| std::vector<BT<std::string>> srcs = this->GetSourceFilePaths(config); | |
| cmsys::RegularExpression header_regex(CM_HEADER_REGEX); | |
| std::vector<cmSourceFile*> badObjLib; | |
| cmValue const rustMainCrateRootProp = | |
| this->GetProperty("Rust_MAIN_CRATE_ROOT"); | |
| cmSourceFile const* rustMainCrateRootSf = rustMainCrateRootProp | |
| ? this->Makefile->GetOrCreateSource(rustMainCrateRootProp) | |
| : nullptr; | |
| std::set<cmSourceFile*> emitted; | |
| for (BT<std::string> const& s : srcs) { | |
| // Create each source at most once. | |
| cmSourceFile* sf = this->Makefile->GetOrCreateSource(s.Value); | |
| if (!emitted.insert(sf).second) { | |
| continue; | |
| } | |
| // Compute the kind (classification) of this source file. | |
| SourceKind kind; | |
| std::string ext = cmSystemTools::LowerCase(sf->GetExtension()); | |
| cmGeneratorFileSet const* fs = this->GetFileSetForSource(config, sf); | |
| sf->GetOrDetermineLanguage(); | |
| std::string const language = this->GetSourceFileLanguage(sf, config); | |
| if (sf->GetCustomCommand()) { | |
| kind = SourceKindCustomCommand; | |
| } else if (!this->Target->IsNormal() && !this->Target->IsImported() && | |
| fs && (fs->GetType() == cm::FileSetMetadata::CXX_MODULES)) { | |
| kind = this->Target->CxxModuleNeedsInterfaceObjects() | |
| ? SourceKindObjectSource | |
| : SourceKindCxxModuleSource; | |
| } else if (this->Target->GetType() == cm::TargetType::UTILITY || | |
| this->Target->GetType() == cm::TargetType::INTERFACE_LIBRARY | |
| // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165 | |
| // NOLINTNEXTLINE(bugprone-branch-clone) | |
| ) { | |
| kind = SourceKindExtra; | |
| } else if (this->IsSourceFilePartOfUnityBatch(sf->ResolveFullPath())) { | |
| kind = SourceKindUnityBatched; | |
| // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165 | |
| // NOLINTNEXTLINE(bugprone-branch-clone) | |
| } else if (sf->GetPropertyAsBool("HEADER_FILE_ONLY")) { | |
| kind = SourceKindHeader; | |
| } else if (sf->GetPropertyAsBool("EXTERNAL_OBJECT")) { | |
| kind = SourceKindExternalObject; | |
| } else if (!language.empty()) { | |
| if (language == "Rust") { | |
| // NOLINTNEXTLINE(bugprone-branch-clone) | |
| if (this->Target->GetType() == cm::TargetType::OBJECT_LIBRARY) { | |
| // There is no main crate root for object libraries. | |
| kind = SourceKindObjectSource; | |
| } else if (!rustMainCrateRootSf) { | |
| // We do not have a main crate root source file, we use the first | |
| // Rust source file for it. | |
| rustMainCrateRootSf = sf; | |
| kind = SourceKindRustMainCrateRoot; | |
| } else if (rustMainCrateRootSf == sf) { | |
| // Current source file is the main crate root defined in the target | |
| // Rust_MAIN_CRATE_ROOT property. | |
| kind = SourceKindRustMainCrateRoot; | |
| } else { | |
| // Any other Rust source file is treated as a normal object, but will | |
| // be built into a .rlib. Maybe in the future this could be changed? | |
| kind = SourceKindObjectSource; | |
| } | |
| } else { | |
| kind = SourceKindObjectSource; | |
| } | |
| } else if (ext == "def") { | |
| kind = SourceKindModuleDefinition; | |
| if (this->GetType() == cm::TargetType::OBJECT_LIBRARY) { | |
| badObjLib.push_back(sf); | |
| } | |
| } else if (ext == "idl") { | |
| kind = SourceKindIDL; | |
| if (this->GetType() == cm::TargetType::OBJECT_LIBRARY) { | |
| badObjLib.push_back(sf); | |
| } | |
| } else if (ext == "resx") { | |
| kind = SourceKindResx; | |
| } else if (ext == "appxmanifest") { | |
| kind = SourceKindAppManifest; | |
| } else if (ext == "manifest") { | |
| if (sf->GetPropertyAsBool("VS_DEPLOYMENT_CONTENT")) { | |
| kind = SourceKindExtra; | |
| } else { | |
| kind = SourceKindManifest; | |
| } | |
| } else if (ext == "pfx") { | |
| kind = SourceKindCertificate; | |
| } else if (ext == "xaml") { | |
| kind = SourceKindXaml; | |
| } else if (header_regex.find(sf->ResolveFullPath())) { | |
| kind = SourceKindHeader; | |
| } else { | |
| kind = SourceKindExtra; | |
| } | |
| // Save this classified source file in the result vector. | |
| files.Sources.push_back({ BT<cmSourceFile*>(sf, s.Backtrace), kind }); | |
| } | |
| if (!badObjLib.empty()) { | |
| std::string e = | |
| cmStrCat("OBJECT library \"", this->GetName(), "\" contains:\n"); | |
| for (cmSourceFile* i : badObjLib) { | |
| e = cmStrCat(std::move(e), " ", i->GetLocation().GetName(), '\n'); | |
| } | |
| e = | |
| cmStrCat(std::move(e), | |
| "but may contain only sources that compile, header files, and " | |
| "other files that would not affect linking of a normal " | |
| "library."); | |
| this->GlobalGenerator->GetCMakeInstance()->IssueMessage( | |
| MessageType::FATAL_ERROR, e, this->GetBacktrace()); | |
| } | |
| } | |
| std::vector<cmGeneratorTarget::AllConfigSource> const& | |
| cmGeneratorTarget::GetAllConfigSources() const | |
| { | |
| if (this->AllConfigSources.empty()) { | |
| this->ComputeAllConfigSources(); | |
| } | |
| return this->AllConfigSources; | |
| } | |
| void cmGeneratorTarget::ComputeAllConfigSources() const | |
| { | |
| std::vector<std::string> configs = | |
| this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); | |
| std::map<cmSourceFile const*, size_t> index; | |
| for (size_t ci = 0; ci < configs.size(); ++ci) { | |
| KindedSources const& sources = this->GetKindedSources(configs[ci]); | |
| for (SourceAndKind const& src : sources.Sources) { | |
| auto mi = index.find(src.Source.Value); | |
| if (mi == index.end()) { | |
| AllConfigSource acs; | |
| acs.Source = src.Source.Value; | |
| acs.Kind = src.Kind; | |
| this->AllConfigSources.push_back(std::move(acs)); | |
| std::map<cmSourceFile const*, size_t>::value_type entry( | |
| src.Source.Value, this->AllConfigSources.size() - 1); | |
| mi = index.insert(entry).first; | |
| } | |
| this->AllConfigSources[mi->second].Configs.push_back(ci); | |
| } | |
| } | |
| } | |
| std::vector<cmGeneratorTarget::AllConfigSource> | |
| cmGeneratorTarget::GetAllConfigSources(SourceKind kind) const | |
| { | |
| std::vector<AllConfigSource> result; | |
| for (AllConfigSource const& source : this->GetAllConfigSources()) { | |
| if (source.Kind == kind) { | |
| result.push_back(source); | |
| } | |
| } | |
| return result; | |
| } | |
| void cmGeneratorTarget::ComputeAllConfigCompileLanguages() const | |
| { | |
| std::set<std::string> languages; | |
| std::vector<std::string> const configs = | |
| this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); | |
| std::vector<AllConfigSource> const& sources = this->GetAllConfigSources(); | |
| for (AllConfigSource const& si : sources) { | |
| for (size_t const configIndex : si.Configs) { | |
| std::string const lang = | |
| this->GetSourceFileLanguage(si.Source, configs[configIndex]); | |
| if (!lang.empty()) { | |
| languages.emplace(lang); | |
| } | |
| } | |
| } | |
| this->AllConfigCompileLanguages = languages; | |
| } | |
| std::set<std::string> cmGeneratorTarget::GetAllConfigCompileLanguages() const | |
| { | |
| if (this->AllConfigCompileLanguages.empty()) { | |
| this->ComputeAllConfigCompileLanguages(); | |
| } | |
| return this->AllConfigCompileLanguages; | |
| } |