From 4f120beea90080a101a7acb24eaac8d07a4ac855 Mon Sep 17 00:00:00 2001 From: Marc Chevrier Date: Sun, 10 May 2026 22:48:32 +0200 Subject: [PATCH] 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 --- Help/policy/CMP0211.rst | 11 +++-- Source/cmFileSetMetadata.cxx | 16 ++++--- Source/cmFileSetMetadata.h | 24 +++++++---- Source/cmGeneratorFileSets.cxx | 43 ++++++++++++------- Source/cmGeneratorFileSets.h | 13 +++++- Source/cmGeneratorTarget_Sources.cxx | 19 +++++++- .../target_sources/CMP0211-Common.cmake | 8 ++-- .../target_sources/CMP0211-NEW-HEADERS.cmake | 13 ++++++ .../target_sources/CMP0211-NEW-stderr.txt | 8 ++-- .../target_sources/CMP0211-WARN-stderr.txt | 8 ++-- .../target_sources/RunCMakeTest.cmake | 1 + 11 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 Tests/RunCMake/target_sources/CMP0211-NEW-HEADERS.cmake diff --git a/Help/policy/CMP0211.rst b/Help/policy/CMP0211.rst index 4ef04c8c50..376ae1e956 100644 --- a/Help/policy/CMP0211.rst +++ b/Help/policy/CMP0211.rst @@ -3,16 +3,19 @@ CMP0211 .. versionadded:: 4.4 -A file may belong to at most one :ref:`file set ` in a target. +A file may belong to at most one :ref:`file set ` 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:: diff --git a/Source/cmFileSetMetadata.cxx b/Source/cmFileSetMetadata.cxx index 821a969b15..053bf4ebb1 100644 --- a/Source/cmFileSetMetadata.cxx +++ b/Source/cmFileSetMetadata.cxx @@ -86,19 +86,19 @@ std::map 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 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 const& GetKnownTypes() diff --git a/Source/cmFileSetMetadata.h b/Source/cmFileSetMetadata.h index 9a28dd297b..c8294741fb 100644 --- a/Source/cmFileSetMetadata.h +++ b/Source/cmFileSetMetadata.h @@ -2,12 +2,14 @@ file LICENSE.rst or https://cmake.org/licensing for details. */ #pragma once +#include #include #include #include #include #include +#include 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; -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; 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 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 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) diff --git a/Source/cmGeneratorFileSets.cxx b/Source/cmGeneratorFileSets.cxx index 00917514a4..b4808800b4 100644 --- a/Source/cmGeneratorFileSets.cxx +++ b/Source/cmGeneratorFileSets.cxx @@ -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 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 emptySet; + return emptySet; +} +std::unordered_set 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& targets, - std::map& cache) +void GetInterfaceFiles( + cmGeneratorTarget const* target, cm::GenEx::Context const& context, + std::unordered_set& targets, + std::map>& 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); } } } diff --git a/Source/cmGeneratorFileSets.h b/Source/cmGeneratorFileSets.h index 3768f395a2..3d47a0d0f0 100644 --- a/Source/cmGeneratorFileSets.h +++ b/Source/cmGeneratorFileSets.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,12 @@ public: cmGeneratorFileSet const* GetFileSet(std::string const& name) const; + std::unordered_set const& GetAllFileSetsForSource( + std::string const& config, std::string const& file) const; + std::unordered_set 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 FileSetCache; - std::map InterfaceFileSetCache; + std::map> + FileSetCache; + std::map> + InterfaceFileSetCache; }; mutable std::map Configs; diff --git a/Source/cmGeneratorTarget_Sources.cxx b/Source/cmGeneratorTarget_Sources.cxx index a1b9ec9934..9db01979a0 100644 --- a/Source/cmGeneratorTarget_Sources.cxx +++ b/Source/cmGeneratorTarget_Sources.cxx @@ -6,6 +6,7 @@ #include "cmConfigure.h" +#include #include #include #include @@ -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( diff --git a/Tests/RunCMake/target_sources/CMP0211-Common.cmake b/Tests/RunCMake/target_sources/CMP0211-Common.cmake index 6a8978b516..6b5bfcd87e 100644 --- a/Tests/RunCMake/target_sources/CMP0211-Common.cmake +++ b/Tests/RunCMake/target_sources/CMP0211-Common.cmake @@ -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) diff --git a/Tests/RunCMake/target_sources/CMP0211-NEW-HEADERS.cmake b/Tests/RunCMake/target_sources/CMP0211-NEW-HEADERS.cmake new file mode 100644 index 0000000000..66ee91da31 --- /dev/null +++ b/Tests/RunCMake/target_sources/CMP0211-NEW-HEADERS.cmake @@ -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) diff --git a/Tests/RunCMake/target_sources/CMP0211-NEW-stderr.txt b/Tests/RunCMake/target_sources/CMP0211-NEW-stderr.txt index 4ba13b3def..f0ab98af74 100644 --- a/Tests/RunCMake/target_sources/CMP0211-NEW-stderr.txt +++ b/Tests/RunCMake/target_sources/CMP0211-NEW-stderr.txt @@ -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"\. diff --git a/Tests/RunCMake/target_sources/CMP0211-WARN-stderr.txt b/Tests/RunCMake/target_sources/CMP0211-WARN-stderr.txt index 4f42ee1deb..8d7639dbdb 100644 --- a/Tests/RunCMake/target_sources/CMP0211-WARN-stderr.txt +++ b/Tests/RunCMake/target_sources/CMP0211-WARN-stderr.txt @@ -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\. diff --git a/Tests/RunCMake/target_sources/RunCMakeTest.cmake b/Tests/RunCMake/target_sources/RunCMakeTest.cmake index ff89e3c552..d02fc92081 100644 --- a/Tests/RunCMake/target_sources/RunCMakeTest.cmake +++ b/Tests/RunCMake/target_sources/RunCMakeTest.cmake @@ -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")