mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-09 09:12:00 +00:00
Enable to declare in multiple sets headers files.
This complements commit 167e903c15 (FILE_SET: enforce uniqueness of files, 2026-03-06)
Issue: #27035
450 lines
15 KiB
C++
450 lines
15 KiB
C++
/* 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 "cmFileSet.h"
|
|
#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 "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 const& name : target->Target->GetAllPrivateFileSets()) {
|
|
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));
|
|
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()) {
|
|
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));
|
|
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
|
|
{
|
|
auto const& fileSets = this->GetAllFileSetsForSource(config, file);
|
|
if (fileSets.empty()) {
|
|
return nullptr;
|
|
}
|
|
return *fileSets.begin();
|
|
}
|
|
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, 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, target);
|
|
|
|
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, 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].insert(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;
|
|
}
|