FILE_SET: relax file uniqueness for HEADERS type

Enable to declare in multiple sets headers files.

This complements commit 167e903c15 (FILE_SET: enforce uniqueness of files, 2026-03-06)

Issue: #27035
This commit is contained in:
Marc Chevrier
2026-05-10 22:48:32 +02:00
parent 6a742d9af3
commit 4f120beea9
11 changed files with 114 additions and 50 deletions

View File

@@ -3,16 +3,19 @@ CMP0211
.. versionadded:: 4.4 .. versionadded:: 4.4
A file may belong to at most one :ref:`file set <file sets>` in a target. A file may belong to at most one :ref:`file set <file sets>` in a target except
for the ``HEADERS`` file set type.
In CMake 4.3 and below, it was possible to specify the same file In CMake 4.3 and below, it was possible to specify the same file
in multiple file sets in a target. In CMake 4.4 and above, a file in multiple file sets in a target. In CMake 4.4 and above, a file
may belong to at most one file set in a target. This policy provides may belong to at most one file set in a target except for the ``HEADERS`` file
compatibility for projects that have not been updated accordingly. set type. This policy provides compatibility for projects that have not been
updated accordingly.
The ``OLD`` behavior for this policy is to accept the same file in The ``OLD`` behavior for this policy is to accept the same file in
multiple file sets in a target. The ``NEW`` behavior for this policy multiple file sets in a target. The ``NEW`` behavior for this policy
is to accept a given file in at most one file set in a target. is to accept a given file in at most one file set in a target except for the
``HEADERS`` file set type.
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.4 .. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.4
.. |WARNS_OR_DOES_NOT_WARN| replace:: .. |WARNS_OR_DOES_NOT_WARN| replace::

View File

@@ -86,19 +86,19 @@ std::map<cm::string_view, FileSetDescriptor> const FileSetDescriptors{
cm::FileSetMetadata::FileSetLookup::Target, cm::FileSetMetadata::FileSetLookup::Target,
{ DependencyMode ::Includables }, { DependencyMode ::Includables },
DependencyMode ::Includables, DependencyMode ::Includables,
FrameworkCompatible::No } }, { cm::FileSetMetadata::FileSetAttributes::FilesInMultipleFileSets } } },
{ cm::FileSetMetadata::SOURCES, { cm::FileSetMetadata::SOURCES,
{ cm::FileSetMetadata::SOURCES, { cm::FileSetMetadata::SOURCES,
cm::FileSetMetadata::FileSetLookup::Dependencies, cm::FileSetMetadata::FileSetLookup::Dependencies,
{ DependencyMode ::IndependentFiles, DependencyMode ::Includables }, { DependencyMode ::IndependentFiles, DependencyMode ::Includables },
DependencyMode ::Includables, DependencyMode ::Includables,
FrameworkCompatible::Yes } }, { cm::FileSetMetadata::FileSetAttributes::FrameworkCompatible } } },
{ cm::FileSetMetadata::CXX_MODULES, { cm::FileSetMetadata::CXX_MODULES,
{ cm::FileSetMetadata::CXX_MODULES, { cm::FileSetMetadata::CXX_MODULES,
cm::FileSetMetadata::FileSetLookup::Target, cm::FileSetMetadata::FileSetLookup::Target,
{ DependencyMode ::IndependentFiles }, { DependencyMode ::IndependentFiles },
DependencyMode ::IndependentFiles, DependencyMode ::IndependentFiles,
FrameworkCompatible::No } }, {} } },
}; };
std::vector<cm::string_view> KnownTypes{ HEADERS, SOURCES, CXX_MODULES }; std::vector<cm::string_view> KnownTypes{ HEADERS, SOURCES, CXX_MODULES };
@@ -138,13 +138,17 @@ DependencyMode GetDependencyMode(cm::string_view type,
return DependencyMode::Includables; return DependencyMode::Includables;
} }
bool IsFrameworkSupported(cm::string_view type) AttributeSet GetAttributes(cm::string_view type)
{ {
auto descriptor = GetFileSetDescriptor(type); auto descriptor = GetFileSetDescriptor(type);
if (descriptor) { if (descriptor) {
return descriptor->FrameworkSupported == FrameworkCompatible::Yes; return descriptor->Attributes;
} }
return false; return {};
}
bool IsFrameworkSupported(cm::string_view type)
{
return GetAttributes(type).contains(FileSetAttributes::FrameworkCompatible);
} }
std::vector<cm::string_view> const& GetKnownTypes() std::vector<cm::string_view> const& GetKnownTypes()

View File

@@ -2,12 +2,14 @@
file LICENSE.rst or https://cmake.org/licensing for details. */ file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once #pragma once
#include <cstdint>
#include <set> #include <set>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <cm/optional> #include <cm/optional>
#include <cm/string_view> #include <cm/string_view>
#include <cmext/enum_set>
class cmMakefile; class cmMakefile;
@@ -48,27 +50,28 @@ enum class FileSetLookup
enum class DependencyMode enum class DependencyMode
{ {
IndependentFiles, // files in the file set are independent from each other IndependentFiles, // files in the file set are independent from each other
Includables, // files can be used by another source during compilation Includables // files can be used by another source during compilation
}; };
using DependencySet = std::set<DependencyMode>; using DependencySet = std::set<DependencyMode>;
enum class FrameworkCompatible enum class FileSetAttributes : std::uint16_t
{ {
No, FrameworkCompatible, // Can be part of an Apple framework
Yes FilesInMultipleFileSets // Files of this file set type can be part of other
// file sets
}; };
using AttributeSet = cm::enum_set<FileSetAttributes, 2>;
struct FileSetDescriptor struct FileSetDescriptor
{ {
FileSetDescriptor(cm::string_view type, FileSetLookup lookup, FileSetDescriptor(cm::string_view type, FileSetLookup lookup,
DependencySet dependencies, DependencySet dependencies,
DependencyMode defaultDependency, DependencyMode defaultDependency, AttributeSet attributes)
FrameworkCompatible frameworkSupported)
: Type(type) : Type(type)
, Lookup(lookup) , Lookup(lookup)
, SupportedDependencies(std::move(dependencies)) , SupportedDependencies(std::move(dependencies))
, DefaultDependency(defaultDependency) , DefaultDependency(defaultDependency)
, FrameworkSupported(frameworkSupported) , Attributes(attributes)
{ {
} }
@@ -77,7 +80,6 @@ struct FileSetDescriptor
, Lookup(lookup) , Lookup(lookup)
, SupportedDependencies({ DependencyMode::Includables }) , SupportedDependencies({ DependencyMode::Includables })
, DefaultDependency(DependencyMode::Includables) , DefaultDependency(DependencyMode::Includables)
, FrameworkSupported(FrameworkCompatible::No)
{ {
} }
@@ -85,13 +87,15 @@ struct FileSetDescriptor
FileSetLookup const Lookup; FileSetLookup const Lookup;
DependencySet const SupportedDependencies; DependencySet const SupportedDependencies;
DependencyMode const DefaultDependency; DependencyMode const DefaultDependency;
FrameworkCompatible const FrameworkSupported; AttributeSet const Attributes;
}; };
cm::optional<FileSetDescriptor> GetFileSetDescriptor(cm::string_view type); cm::optional<FileSetDescriptor> GetFileSetDescriptor(cm::string_view type);
DependencyMode GetDependencyMode(cm::string_view type); DependencyMode GetDependencyMode(cm::string_view type);
DependencyMode GetDependencyMode(cm::string_view type, DependencyMode GetDependencyMode(cm::string_view type,
DependencyMode requestedMode); DependencyMode requestedMode);
AttributeSet GetAttributes(cm::string_view type);
bool IsFrameworkSupported(cm::string_view type); bool IsFrameworkSupported(cm::string_view type);
std::vector<cm::string_view> const& GetKnownTypes(); std::vector<cm::string_view> const& GetKnownTypes();
@@ -101,3 +105,5 @@ bool IsKnownType(cm::string_view type);
bool IsValidName(cm::string_view type); bool IsValidName(cm::string_view type);
} }
} }
CM_ENUM_SET_TRAITS(cm::FileSetMetadata::AttributeSet)

View File

@@ -133,11 +133,10 @@ cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSet(
return nullptr; return nullptr;
} }
cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource( std::unordered_set<cmGeneratorFileSet const*> const&
std::string const& config, std::string const& path) const cmGeneratorFileSets::GetAllFileSetsForSource(std::string const& config,
std::string const& path) const
{ {
using Lookup = cm::FileSetMetadata::FileSetLookup;
this->BuildInfoCache(config); this->BuildInfoCache(config);
auto const& info = this->Configs[config]; auto const& info = this->Configs[config];
@@ -149,14 +148,28 @@ cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
// search in all the dependents // search in all the dependents
auto const it2 = info.InterfaceFileSetCache.find(path); auto const it2 = info.InterfaceFileSetCache.find(path);
if (it2 != info.InterfaceFileSetCache.end() && 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 it2->second;
} }
return nullptr; 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( cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
std::string const& config, cmSourceFile const* sf) const std::string const& config, cmSourceFile const* sf) const
@@ -358,10 +371,10 @@ std::string cmGeneratorFileSets::EvaluateInterfaceProperty(
} }
namespace { namespace {
void GetInterfaceFiles(cmGeneratorTarget const* target, void GetInterfaceFiles(
cm::GenEx::Context const& context, cmGeneratorTarget const* target, cm::GenEx::Context const& context,
std::unordered_set<cmGeneratorTarget const*>& targets, std::unordered_set<cmGeneratorTarget const*>& targets,
std::map<std::string, cmGeneratorFileSet const*>& cache) std::map<std::string, std::unordered_set<cmGeneratorFileSet const*>>& cache)
{ {
namespace Metadata = cm::FileSetMetadata; namespace Metadata = cm::FileSetMetadata;
@@ -375,7 +388,7 @@ void GetInterfaceFiles(cmGeneratorTarget const* target,
for (auto const& it : files.first) { for (auto const& it : files.first) {
for (auto const& filename : it.second) { for (auto const& filename : it.second) {
auto collapsedFile = cmSystemTools::CollapseFullPath(filename); auto collapsedFile = cmSystemTools::CollapseFullPath(filename);
cache[collapsedFile] = fileSet; cache[collapsedFile].insert(fileSet);
} }
} }
} }
@@ -413,7 +426,7 @@ void cmGeneratorFileSets::BuildInfoCache(std::string const& config) const
for (auto const& it : files.first) { for (auto const& it : files.first) {
for (auto const& filename : it.second) { for (auto const& filename : it.second) {
auto collapsedFile = cmSystemTools::CollapseFullPath(filename); auto collapsedFile = cmSystemTools::CollapseFullPath(filename);
info.FileSetCache[collapsedFile] = fileSet; info.FileSetCache[collapsedFile].insert(fileSet);
} }
} }
} }

View File

@@ -9,6 +9,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <vector> #include <vector>
#include <cm/string_view> #include <cm/string_view>
@@ -53,6 +54,12 @@ public:
cmGeneratorFileSet const* GetFileSet(std::string const& name) const; cmGeneratorFileSet const* GetFileSet(std::string const& name) const;
std::unordered_set<cmGeneratorFileSet const*> const& GetAllFileSetsForSource(
std::string const& config, std::string const& file) const;
std::unordered_set<cmGeneratorFileSet const*> const& GetAllFileSetsForSource(
std::string const& config, cmSourceFile const* sf) const;
// returns the first FileSet of the set
cmGeneratorFileSet const* GetFileSetForSource(std::string const& config, cmGeneratorFileSet const* GetFileSetForSource(std::string const& config,
std::string const& file) const; std::string const& file) const;
cmGeneratorFileSet const* GetFileSetForSource(std::string const& config, cmGeneratorFileSet const* GetFileSetForSource(std::string const& config,
@@ -102,8 +109,10 @@ private:
struct InfoByConfig struct InfoByConfig
{ {
bool BuiltCache = false; bool BuiltCache = false;
std::map<std::string, cmGeneratorFileSet const*> FileSetCache; std::map<std::string, std::unordered_set<cmGeneratorFileSet const*>>
std::map<std::string, cmGeneratorFileSet const*> InterfaceFileSetCache; FileSetCache;
std::map<std::string, std::unordered_set<cmGeneratorFileSet const*>>
InterfaceFileSetCache;
}; };
mutable std::map<std::string, InfoByConfig> Configs; mutable std::map<std::string, InfoByConfig> Configs;

View File

@@ -6,6 +6,7 @@
#include "cmConfigure.h" #include "cmConfigure.h"
#include <algorithm>
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <map> #include <map>
@@ -160,8 +161,22 @@ bool processSources(cmGeneratorTarget const* tgt, std::string const& config,
usedSources += cmStrCat(" * ", src, '\n'); usedSources += cmStrCat(" * ", src, '\n');
} }
} else { } else {
if (auto const* fileSet = auto const& fileSets =
tgt->GetGeneratorFileSets()->GetFileSetForSource(config, src)) { 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()) { switch (tgt->GetPolicyStatusCMP0211()) {
case cmPolicies::WARN: case cmPolicies::WARN:
tgt->GetLocalGenerator()->IssueDiagnostic( tgt->GetLocalGenerator()->IssueDiagnostic(

View File

@@ -2,8 +2,8 @@
enable_language(C) enable_language(C)
add_library(foo1 STATIC lib1.c) add_library(foo1 STATIC lib1.c)
target_sources(foo1 PRIVATE FILE_SET h1 TYPE HEADERS FILES h1.h) target_sources(foo1 PRIVATE FILE_SET s1 TYPE SOURCES FILES lib2.c)
target_sources(foo1 PRIVATE FILE_SET h2 TYPE HEADERS FILES h1.h) target_sources(foo1 PRIVATE FILE_SET s2 TYPE SOURCES FILES lib2.c)
add_library(foo2 STATIC lib1.c h1.h) add_library(foo2 STATIC lib1.c lib2.c)
target_sources(foo2 PRIVATE FILE_SET h1 TYPE HEADERS FILES h1.h) target_sources(foo2 PRIVATE FILE_SET s1 TYPE SOURCES FILES lib2.c)

View File

@@ -0,0 +1,13 @@
cmake_policy(SET CMP0211 NEW)
enable_language(C)
# files in HEADERS file sets can also be specified in other file sets
add_library(foo1 STATIC lib1.c)
target_sources(foo1 PRIVATE FILE_SET s1 TYPE SOURCES FILES lib2.c h1.h)
target_sources(foo1 PRIVATE FILE_SET h1 TYPE HEADERS FILES h1.h)
add_library(foo2 STATIC lib1.c h1.h)
target_sources(foo2 PRIVATE FILE_SET h1 TYPE HEADERS FILES h1.h)

View File

@@ -1,14 +1,14 @@
CMake Error in CMakeLists\.txt: CMake Error in CMakeLists\.txt:
In target "foo1" the file In target "foo1" the file
.+/Tests/RunCMake/target_sources/h1\.h .+/Tests/RunCMake/target_sources/lib2\.c
already belongs to file set "h2"\. already belongs to file set "s[12]"\.
CMake Error in CMakeLists\.txt: CMake Error in CMakeLists\.txt:
In target "foo2" the file In target "foo2" the file
.+/Tests/RunCMake/target_sources/h1\.h .+/Tests/RunCMake/target_sources/lib2\.c
already belongs to file set "h1"\. already belongs to file set "s1"\.

View File

@@ -5,9 +5,9 @@ CMake Warning \(author\) in CMakeLists\.txt:
In target "foo1" the file In target "foo1" the file
.+/Tests/RunCMake/target_sources/h1\.h .+/Tests/RunCMake/target_sources/lib2\.c
already belongs to file set "h2"\. already belongs to file set "s[12]"\.
This warning is for project developers\. Use -Wno-author to suppress it\. This warning is for project developers\. Use -Wno-author to suppress it\.
CMake Warning \(author\) in CMakeLists\.txt: CMake Warning \(author\) in CMakeLists\.txt:
@@ -17,7 +17,7 @@ CMake Warning \(author\) in CMakeLists\.txt:
In target "foo2" the file In target "foo2" the file
.+/Tests/RunCMake/target_sources/h1\.h .+/Tests/RunCMake/target_sources/lib2\.c
already belongs to file set "h1"\. already belongs to file set "s1"\.
This warning is for project developers\. Use -Wno-author to suppress it\. This warning is for project developers\. Use -Wno-author to suppress it\.

View File

@@ -52,6 +52,7 @@ endif()
run_cmake(CMP0211-OLD) run_cmake(CMP0211-OLD)
run_cmake(CMP0211-NEW) run_cmake(CMP0211-NEW)
run_cmake(CMP0211-WARN) run_cmake(CMP0211-WARN)
run_cmake(CMP0211-NEW-HEADERS)
set(RunCMake_TEST_NO_CLEAN 1) set(RunCMake_TEST_NO_CLEAN 1)
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/FileSetGeneratedDependency-build") set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/FileSetGeneratedDependency-build")