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
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 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
compatibility for projects that have not been updated accordingly.
may belong to at most one file set in a target except for the ``HEADERS`` file
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
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
.. |WARNS_OR_DOES_NOT_WARN| replace::

View File

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

View File

@@ -2,12 +2,14 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <cstdint>
#include <set>
#include <utility>
#include <vector>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/enum_set>
class cmMakefile;
@@ -48,27 +50,28 @@ enum class FileSetLookup
enum class DependencyMode
{
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>;
enum class FrameworkCompatible
enum class FileSetAttributes : std::uint16_t
{
No,
Yes
FrameworkCompatible, // Can be part of an Apple framework
FilesInMultipleFileSets // Files of this file set type can be part of other
// file sets
};
using AttributeSet = cm::enum_set<FileSetAttributes, 2>;
struct FileSetDescriptor
{
FileSetDescriptor(cm::string_view type, FileSetLookup lookup,
DependencySet dependencies,
DependencyMode defaultDependency,
FrameworkCompatible frameworkSupported)
DependencyMode defaultDependency, AttributeSet attributes)
: Type(type)
, Lookup(lookup)
, SupportedDependencies(std::move(dependencies))
, DefaultDependency(defaultDependency)
, FrameworkSupported(frameworkSupported)
, Attributes(attributes)
{
}
@@ -77,7 +80,6 @@ struct FileSetDescriptor
, Lookup(lookup)
, SupportedDependencies({ DependencyMode::Includables })
, DefaultDependency(DependencyMode::Includables)
, FrameworkSupported(FrameworkCompatible::No)
{
}
@@ -85,13 +87,15 @@ struct FileSetDescriptor
FileSetLookup const Lookup;
DependencySet const SupportedDependencies;
DependencyMode const DefaultDependency;
FrameworkCompatible const FrameworkSupported;
AttributeSet const Attributes;
};
cm::optional<FileSetDescriptor> GetFileSetDescriptor(cm::string_view type);
DependencyMode GetDependencyMode(cm::string_view type);
DependencyMode GetDependencyMode(cm::string_view type,
DependencyMode requestedMode);
AttributeSet GetAttributes(cm::string_view type);
bool IsFrameworkSupported(cm::string_view type);
std::vector<cm::string_view> const& GetKnownTypes();
@@ -101,3 +105,5 @@ bool IsKnownType(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;
}
cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
std::string const& config, std::string const& path) const
std::unordered_set<cmGeneratorFileSet const*> const&
cmGeneratorFileSets::GetAllFileSetsForSource(std::string const& config,
std::string const& path) const
{
using Lookup = cm::FileSetMetadata::FileSetLookup;
this->BuildInfoCache(config);
auto const& info = this->Configs[config];
@@ -149,14 +148,28 @@ cmGeneratorFileSet const* cmGeneratorFileSets::GetFileSetForSource(
// search in all the dependents
auto const it2 = info.InterfaceFileSetCache.find(path);
if (it2 != info.InterfaceFileSetCache.end() &&
cm::FileSetMetadata::GetFileSetDescriptor(it2->second->GetType())
.value_or(cm::FileSetMetadata::FileSetDescriptor{ Lookup::Target })
.Lookup == Lookup::Dependencies) {
if (it2 != info.InterfaceFileSetCache.end()) {
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(
std::string const& config, cmSourceFile const* sf) const
@@ -358,10 +371,10 @@ std::string cmGeneratorFileSets::EvaluateInterfaceProperty(
}
namespace {
void GetInterfaceFiles(cmGeneratorTarget const* target,
cm::GenEx::Context const& context,
std::unordered_set<cmGeneratorTarget const*>& targets,
std::map<std::string, cmGeneratorFileSet const*>& cache)
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;
@@ -375,7 +388,7 @@ void GetInterfaceFiles(cmGeneratorTarget const* target,
for (auto const& it : files.first) {
for (auto const& filename : it.second) {
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& filename : it.second) {
auto collapsedFile = cmSystemTools::CollapseFullPath(filename);
info.FileSetCache[collapsedFile] = fileSet;
info.FileSetCache[collapsedFile].insert(fileSet);
}
}
}

View File

@@ -9,6 +9,7 @@
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <cm/string_view>
@@ -53,6 +54,12 @@ public:
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,
std::string const& file) const;
cmGeneratorFileSet const* GetFileSetForSource(std::string const& config,
@@ -102,8 +109,10 @@ private:
struct InfoByConfig
{
bool BuiltCache = false;
std::map<std::string, cmGeneratorFileSet const*> FileSetCache;
std::map<std::string, cmGeneratorFileSet const*> InterfaceFileSetCache;
std::map<std::string, std::unordered_set<cmGeneratorFileSet const*>>
FileSetCache;
std::map<std::string, std::unordered_set<cmGeneratorFileSet const*>>
InterfaceFileSetCache;
};
mutable std::map<std::string, InfoByConfig> Configs;

View File

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

View File

@@ -2,8 +2,8 @@
enable_language(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 h2 TYPE HEADERS FILES h1.h)
target_sources(foo1 PRIVATE FILE_SET s1 TYPE SOURCES FILES lib2.c)
target_sources(foo1 PRIVATE FILE_SET s2 TYPE SOURCES FILES lib2.c)
add_library(foo2 STATIC lib1.c h1.h)
target_sources(foo2 PRIVATE FILE_SET h1 TYPE HEADERS FILES h1.h)
add_library(foo2 STATIC lib1.c lib2.c)
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:
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:
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
.+/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\.
CMake Warning \(author\) in CMakeLists\.txt:
@@ -17,7 +17,7 @@ CMake Warning \(author\) in CMakeLists\.txt:
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\.

View File

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