/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #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("$'); cmGeneratorExpression ge(*headTarget->Makefile->GetCMakeInstance(), lib.Backtrace); std::unique_ptr 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, dagChecker); entries = EvaluateTargetPropertyEntries(headTarget, context, dagChecker, sources); } bool processSources(cmGeneratorTarget const* tgt, std::string const& config, cm::EvaluatedTargetPropertyEntries& entries, std::vector>& srcs, std::unordered_set& uniqueSrcs, bool debugSources, std::function 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)) { std::ostringstream err; if (!targetName.empty()) { err << "Target \"" << targetName << "\" contains relative path in its INTERFACE_SOURCES:\n \"" << src << "\""; } else { err << "Found relative path while evaluating sources of \"" << tgt->GetName() << "\":\n \"" << src << "\"\n"; } tgt->GetLocalGenerator()->IssueMessage(MessageType::FATAL_ERROR, err.str()); 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(" * ", 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> cmGeneratorTarget::GetSourceFilePaths( std::string const& config) const { std::vector> 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 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& files, std::string const& config) const { std::vector> tmp = this->GetSourceFiles(config); files.reserve(tmp.size()); for (BT& v : tmp) { files.push_back(v.Value); } } std::vector> cmGeneratorTarget::GetSourceFiles( std::string const& config) const { std::vector> 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& files, std::string const& config) const { std::vector> tmp = this->GetSourceFilesWithoutObjectLibraries(config); files.reserve(tmp.size()); for (BT& v : tmp) { files.push_back(v.Value); } } std::vector> cmGeneratorTarget::GetSourceFilesWithoutObjectLibraries( std::string const& config) const { std::vector> 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) { std::ostringstream e; e << "The SOURCES of \"" << this->GetName() << "\" use a generator expression that depends on the " "SOURCES themselves."; this->GlobalGenerator->GetCMakeInstance()->IssueMessage( MessageType::FATAL_ERROR, e.str(), 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> srcs = this->GetSourceFilePaths(config); cmsys::RegularExpression header_regex(CM_HEADER_REGEX); std::vector badObjLib; cmValue const rustMainCrateRootProp = this->GetProperty("Rust_MAIN_CRATE_ROOT"); cmSourceFile const* rustMainCrateRootSf = rustMainCrateRootProp ? this->Makefile->GetOrCreateSource(rustMainCrateRootProp) : nullptr; std::set emitted; for (BT 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); if (sf->GetCustomCommand()) { kind = SourceKindCustomCommand; } else if (!this->Target->IsNormal() && !this->Target->IsImported() && fs && (fs->GetType() == cm::FileSetMetadata::CXX_MODULES)) { kind = 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 (!sf->GetOrDetermineLanguage().empty()) { if (sf->GetOrDetermineLanguage() == "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(sf, s.Backtrace), kind }); } if (!badObjLib.empty()) { std::ostringstream e; e << "OBJECT library \"" << this->GetName() << "\" contains:\n"; for (cmSourceFile* i : badObjLib) { e << " " << i->GetLocation().GetName() << "\n"; } 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.str(), this->GetBacktrace()); } } std::vector const& cmGeneratorTarget::GetAllConfigSources() const { if (this->AllConfigSources.empty()) { this->ComputeAllConfigSources(); } return this->AllConfigSources; } void cmGeneratorTarget::ComputeAllConfigSources() const { std::vector configs = this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig); std::map 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::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::GetAllConfigSources(SourceKind kind) const { std::vector result; for (AllConfigSource const& source : this->GetAllConfigSources()) { if (source.Kind == kind) { result.push_back(source); } } return result; } void cmGeneratorTarget::ComputeAllConfigCompileLanguages() const { std::set languages; std::vector const& sources = this->GetAllConfigSources(); for (AllConfigSource const& si : sources) { std::string const& lang = si.Source->GetOrDetermineLanguage(); if (!lang.empty()) { languages.emplace(lang); } } this->AllConfigCompileLanguages = languages; } std::set cmGeneratorTarget::GetAllConfigCompileLanguages() const { if (this->AllConfigCompileLanguages.empty()) { this->ComputeAllConfigCompileLanguages(); } return this->AllConfigCompileLanguages; }