Files
cmake/Source/cmGeneratorTarget_Sources.cxx
T
Marc Chevrier dee8799d16 FileSet management: Introduce dedicated classes for generation
To manage the generation step, introduce cmGeneratorFileSet and
cmGeneratorFileSets classes.
These reorganizations of the code are done in preparation of the
implementation of SOURCES file set type as well as user's file set types.

Issues: #27550, #27383
2026-02-19 17:35:56 +01:00

496 lines
17 KiB
C++

/* 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 <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#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 "cmSourceFile.h"
#include "cmSourceFileLocation.h"
#include "cmSourceGroup.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"
#include "cmake.h"
namespace {
using UseTo = cmGeneratorTarget::UseTo;
void AddObjectEntries(cmGeneratorTarget const* headTarget,
cm::GenEx::Context const& context,
cmGeneratorExpressionDAGChecker* dagChecker,
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() == cmStateEnums::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);
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,
EvaluatedTargetPropertyEntries& entries)
{
auto sources = fileSets->GetSources(context, headTarget, dagChecker);
entries =
EvaluateTargetPropertyEntries(headTarget, context, dagChecker, sources);
}
bool processSources(cmGeneratorTarget const* tgt,
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 (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);
cmake* cm = tgt->GetLocalGenerator()->GetCMakeInstance();
if (!w.empty()) {
cm->IssueMessage(MessageType::AUTHOR_WARNING, w, entry.Backtrace);
}
if (fullPath.empty()) {
if (!e.empty()) {
cm->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');
}
}
}
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,
};
EvaluatedTargetPropertyEntries entries = EvaluateTargetPropertyEntries(
this, context, &dagChecker, this->SourceEntries);
std::unordered_set<std::string> uniqueSrcs;
bool contextDependentDirectSources =
processSources(this, entries, files, uniqueSrcs, debugSources);
// Collect INTERFACE_SOURCES of all direct link-dependencies.
EvaluatedTargetPropertyEntries linkInterfaceSourcesEntries;
AddInterfaceEntries(this, "INTERFACE_SOURCES", context, &dagChecker,
linkInterfaceSourcesEntries, IncludeRuntimeInterface::No,
UseTo::Compile);
bool contextDependentInterfaceSources = processSources(
this, linkInterfaceSourcesEntries, files, uniqueSrcs, debugSources);
// Collect TARGET_OBJECTS of direct object link-dependencies.
bool contextDependentObjects = false;
if (this->GetType() != cmStateEnums::OBJECT_LIBRARY) {
EvaluatedTargetPropertyEntries linkObjectsEntries;
AddObjectEntries(this, context, &dagChecker, linkObjectsEntries);
contextDependentObjects = processSources(this, 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.
EvaluatedTargetPropertyEntries fileSetEntries;
AddFileSetEntries(this, this->FileSets.get(), context, &dagChecker,
fileSetEntries);
auto processFileSetEntry = [this, &config](cmSourceFile* sf) {
auto const* fileSet = this->GetFileSetForSource(config, sf);
if (fileSet->GetType() == cm::FileSetMetadata::HEADERS) {
sf->SetProperty("HEADER_FILE_ONLY", "TRUE");
}
#if !defined(CMAKE_BOOTSTRAP)
cmMakefile* mf = this->Target->GetMakefile();
auto const& path = sf->GetFullPath();
bool found = false;
for (auto const& sg : mf->GetSourceGroups()) {
if (sg->MatchChildrenFiles(path)) {
found = true;
break;
}
}
if (!found) {
if (fileSet->GetType() == cm::FileSetMetadata::HEADERS) {
mf->GetOrCreateSourceGroup("Header Files")->AddGroupFile(path);
}
}
#endif
};
bool contextDependentFileSets =
processSources(this, fileSetEntries, files, uniqueSrcs, debugSources,
processFileSetEntry);
// Determine if sources are context-dependent or not.
if (!contextDependentDirectSources && !contextDependentInterfaceSources &&
!contextDependentObjects && !contextDependentFileSets) {
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) {
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<BT<std::string>> srcs = this->GetSourceFilePaths(config);
cmsys::RegularExpression header_regex(CM_HEADER_REGEX);
std::vector<cmSourceFile*> badObjLib;
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);
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() == cmStateEnums::UTILITY ||
this->Target->GetType() == cmStateEnums::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()) {
kind = SourceKindObjectSource;
} else if (ext == "def") {
kind = SourceKindModuleDefinition;
if (this->GetType() == cmStateEnums::OBJECT_LIBRARY) {
badObjLib.push_back(sf);
}
} else if (ext == "idl") {
kind = SourceKindIDL;
if (this->GetType() == cmStateEnums::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::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<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<AllConfigSource> 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<std::string> cmGeneratorTarget::GetAllConfigCompileLanguages() const
{
if (this->AllConfigCompileLanguages.empty()) {
this->ComputeAllConfigCompileLanguages();
}
return this->AllConfigCompileLanguages;
}