FILE_SET: add SKIP_UNITY_BUILD_INCLUSION property support

This commit is contained in:
Marc Chevrier
2026-05-14 10:55:35 -04:00
committed by Brad King
parent 05c103d92a
commit 5f7a7aa263
17 changed files with 296 additions and 20 deletions
+1
View File
@@ -554,6 +554,7 @@ Properties on File Sets
/prop_fs/INTERFACE_SOURCES
/prop_fs/SCOPE
/prop_fs/SKIP_LINTING
/prop_fs/SKIP_UNITY_BUILD_INCLUSION
/prop_fs/SOURCES
/prop_fs/TYPE
@@ -0,0 +1,23 @@
SKIP_UNITY_BUILD_INCLUSION
--------------------------
.. versionadded:: 4.4
Setting this property to true ensures the source files of the file set will be
skipped by unity builds when its associated target has its
:prop_tgt:`UNITY_BUILD` property set to true. The source files of the file set
will instead be compiled on their own in the same way as it would with unity
builds disabled.
This property helps with "ODR (One definition rule)" problems where combining
a particular source file with others might lead to build errors or other
unintended side effects.
Note that sources which are scanned for C++ modules (see
:manual:`cmake-cxxmodules(7)`) are not eligible for unity build inclusion and
will automatically be excluded.
See Also
^^^^^^^^
* :prop_sf:`SKIP_UNITY_BUILD_INCLUSION` source file property
@@ -15,3 +15,8 @@ unintended side effects.
Note that sources which are scanned for C++ modules (see
:manual:`cmake-cxxmodules(7)`) are not eligible for unity build inclusion and
will automatically be excluded.
See Also
^^^^^^^^
* :prop_fs:`SKIP_UNITY_BUILD_INCLUSION` file set property
+14 -2
View File
@@ -11,8 +11,8 @@ build.
CMake provides different algorithms for selecting which sources are grouped
together into a *bucket*. Algorithm selection is decided by the
:prop_tgt:`UNITY_BUILD_MODE` target property, which has the following acceptable
values:
:prop_tgt:`UNITY_BUILD_MODE` target property, which has the following
acceptable values:
* ``BATCH``
When in this mode CMake determines which files are grouped together.
@@ -79,6 +79,11 @@ a number of measures to help address such problems:
:prop_sf:`INCLUDE_DIRECTORIES` source property will not be combined
into a unity source.
* .. versionadded:: 4.4
Any file set that has a non-empty :prop_fs:`COMPILE_OPTIONS`,
:prop_fs:`COMPILE_DEFINITIONS`, or :prop_fs:`INCLUDE_DIRECTORIES`
file set property will not be combined into a unity source.
* Any source file which is scanned for C++ module sources via
:prop_tgt:`CXX_SCAN_FOR_MODULES`, :prop_sf:`CXX_SCAN_FOR_MODULES`, or
membership of a ``CXX_MODULES`` file set will not be combined into a unity
@@ -90,6 +95,13 @@ a number of measures to help address such problems:
problems with specific files than disabling unity builds for an entire
target.
* .. versionadded:: 4.4
Projects can prevent all the source files of a file set from being combined
into a unity source by setting its :prop_fs:`SKIP_UNITY_BUILD_INCLUSION`
file set property to true. This can be a more effective way to prevent
problems with specific files than disabling unity builds for an entire
target.
* Projects can set :prop_tgt:`UNITY_BUILD_UNIQUE_ID` to cause a valid
C-identifier to be generated which is unique per file in a unity
build. This can be used to avoid problems with anonymous namespaces
@@ -0,0 +1,5 @@
FILE_SET-SKIP_UNITY_BUILD_INCLUSION
-----------------------------------
* File sets gained the support of the :prop_fs:`SKIP_UNITY_BUILD_INCLUSION`
file set property.
+21 -2
View File
@@ -25,6 +25,7 @@
#include "cmCommonTargetGenerator.h"
#include "cmCryptoHash.h"
#include "cmFastbuildTargetGenerator.h"
#include "cmFileSetMetadata.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorFileSet.h"
@@ -76,8 +77,24 @@ char const kPATH_SLASH = '\\';
char const kPATH_SLASH = '/';
#endif
bool IsExcludedFromUnity(cmSourceFile const& srcFile)
bool IsExcludedFromUnity(cmGeneratorTarget const* target,
cmGeneratorFileSet const* fileSet,
cmSourceFile const& srcFile)
{
if (fileSet &&
(fileSet->GetType() == cm::FileSetMetadata::HEADERS ||
fileSet->GetProperty("SKIP_UNITY_BUILD_INCLUSION").IsOn() ||
fileSet->GetProperty(fileSet->BelongsTo(target)
? "COMPILE_OPTIONS"
: "INTERFACE_COMPILE_OPTIONS") ||
fileSet->GetProperty(fileSet->BelongsTo(target)
? "COMPILE_DEFINITIONS"
: "INTERFACE_COMPILE_DEFINITIONS") ||
fileSet->GetProperty(fileSet->BelongsTo(target)
? "INCLUDE_DIRECTORIES"
: "INTERFACE_INCLUDE_DIRECTORIES"))) {
return true;
}
return srcFile.GetPropertyAsBool(SKIP_UNITY_BUILD_INCLUSION) ||
srcFile.GetProperty(COMPILE_OPTIONS) ||
srcFile.GetProperty(COMPILE_DEFINITIONS) ||
@@ -1440,8 +1457,10 @@ void cmFastbuildNormalTargetGenerator::GenerateObjects(FastbuildTarget& target)
std::string const pathToFile = srcFile.GetFullPath();
bool fileUsesUnity = useUnity;
if (useUnity) {
cmGeneratorFileSet const* fileSet =
GeneratorTarget->GetFileSetForSource(Config, source);
// Check if the source should be added to "UnityInputExcludedFiles".
if (IsExcludedFromUnity(srcFile)) {
if (IsExcludedFromUnity(GeneratorTarget, fileSet, srcFile)) {
fileUsesUnity = false;
excludedFromUnity.emplace(pathToFile);
}
+35 -12
View File
@@ -32,9 +32,11 @@
#include "cmCustomCommandGenerator.h"
#include "cmCustomCommandLines.h"
#include "cmCustomCommandTypes.h"
#include "cmFileSetMetadata.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorExpressionEvaluationFile.h"
#include "cmGeneratorFileSet.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
#include "cmInstallGenerator.h"
@@ -3327,18 +3329,39 @@ void cmLocalGenerator::AddUnityBuild(cmGeneratorTarget* target)
for (std::string lang : { "C", "CXX", "OBJC", "OBJCXX", "CUDA" }) {
std::vector<UnityBatchedSource> filtered_sources;
std::copy_if(unitySources.begin(), unitySources.end(),
std::back_inserter(filtered_sources),
[&](UnityBatchedSource const& ubs) -> bool {
cmSourceFile* sf = ubs.Source;
return sf->GetLanguage() == lang &&
!sf->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION") &&
!sf->GetPropertyAsBool("HEADER_FILE_ONLY") &&
!sf->GetProperty("COMPILE_OPTIONS") &&
!sf->GetProperty("COMPILE_DEFINITIONS") &&
!sf->GetProperty("COMPILE_FLAGS") &&
!sf->GetProperty("INCLUDE_DIRECTORIES");
});
std::copy_if(
unitySources.begin(), unitySources.end(),
std::back_inserter(filtered_sources),
[&](UnityBatchedSource const& ubs) -> bool {
cmSourceFile* sf = ubs.Source;
if (sf->GetLanguage() != lang) {
return false;
}
for (auto idx : ubs.Configs) {
cmGeneratorFileSet const* fileSet =
target->GetFileSetForSource(configs[idx], sf);
if (fileSet &&
(fileSet->GetType() == cm::FileSetMetadata::HEADERS ||
fileSet->GetProperty("SKIP_UNITY_BUILD_INCLUSION").IsOn() ||
fileSet->GetProperty(fileSet->BelongsTo(target)
? "COMPILE_OPTIONS"
: "INTERFACE_COMPILE_OPTIONS") ||
fileSet->GetProperty(fileSet->BelongsTo(target)
? "COMPILE_DEFINITIONS"
: "INTERFACE_COMPILE_DEFINITIONS") ||
fileSet->GetProperty(fileSet->BelongsTo(target)
? "INCLUDE_DIRECTORIES"
: "INTERFACE_INCLUDE_DIRECTORIES"))) {
return false;
}
}
return !sf->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION") &&
!sf->GetPropertyAsBool("HEADER_FILE_ONLY") &&
!sf->GetProperty("COMPILE_OPTIONS") &&
!sf->GetProperty("COMPILE_DEFINITIONS") &&
!sf->GetProperty("COMPILE_FLAGS") &&
!sf->GetProperty("INCLUDE_DIRECTORIES");
});
std::vector<UnitySource> unity_files;
if (!unityMode || *unityMode == "BATCH") {
+3 -1
View File
@@ -1637,7 +1637,9 @@ cmLocalVisualStudio7GeneratorFCInfo::cmLocalVisualStudio7GeneratorFCInfo(
!cm::contains(acs.Configs, ci) ||
(gt->GetPropertyAsBool("UNITY_BUILD") &&
sf.GetProperty("UNITY_SOURCE_FILE") &&
!sf.GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION"));
!((fileSet &&
fileSet->GetProperty("SKIP_UNITY_BUILD_INCLUSION").IsOn()) ||
sf.GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION")));
if (fc.ExcludedFromBuild) {
needfc = true;
}
+6 -3
View File
@@ -2694,7 +2694,8 @@ void cmVisualStudio10TargetGenerator::WriteAllSources(Elem& e0)
if (useNativeUnityBuild) {
e2.Attribute(
"IncludeInUnityFile",
si.Source->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION")
((fs && fs->GetProperty("SKIP_UNITY_BUILD_INCLUSION").IsOn()) ||
si.Source->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION"))
? "false"
: "true");
e2.Attribute("CustomUnityFile", "true");
@@ -2705,14 +2706,16 @@ void cmVisualStudio10TargetGenerator::WriteAllSources(Elem& e0)
} else {
// Visual Studio versions prior to 2017 15.8 do not know about unity
// builds, thus we exclude the files already part of unity sources.
if (!si.Source->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION")) {
if (!((fs && fs->GetProperty("SKIP_UNITY_BUILD_INCLUSION").IsOn()) ||
si.Source->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION"))) {
exclude_configs = all_configs;
}
}
}
if (haveUnityBuild && strcmp(tool, "CudaCompile") == 0 &&
si.Source->GetProperty("UNITY_SOURCE_FILE")) {
if (!si.Source->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION")) {
if (!((fs && fs->GetProperty("SKIP_UNITY_BUILD_INCLUSION").IsOn()) ||
si.Source->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION"))) {
exclude_configs = all_configs;
}
}
@@ -6,7 +6,9 @@ run_cmake(Unity2)
run_cmake(UnityBatchSize)
run_cmake(UnityGroup)
run_cmake(UnityIsolate)
run_cmake(UnityIsolateFileSet)
run_cmake(UnitySourceProperties)
run_cmake(UnityFileSetProperties)
run_cmake(UnitySubdirs)
run_cmake(UnitySubdirsIsolate)
run_cmake(DisableCaching)
@@ -0,0 +1,35 @@
set(fbuild_bff "${RunCMake_TEST_BINARY_DIR}/fbuild.bff")
if(NOT EXISTS "${fbuild_bff}")
set(RunCMake_TEST_FAILED "Generator output file is missing:\n ${fbuild_bff}")
return()
endif()
file(STRINGS "${fbuild_bff}" fbuild_bff_lines
REGEX "\\.UnityInputExcludedFiles =|unity_.*\\.cpp'|^ \\}")
set(in_excluded_files 0)
set(excluded_sources)
foreach(line IN LISTS fbuild_bff_lines)
if(line MATCHES "\\.UnityInputExcludedFiles =")
set(in_excluded_files 1)
elseif(in_excluded_files AND line MATCHES "^ \\}")
set(in_excluded_files 0)
elseif(in_excluded_files AND line MATCHES "unity_([^']+)\\.cpp'")
list(APPEND excluded_sources "${CMAKE_MATCH_1}")
endif()
endforeach()
foreach(prop IN ITEMS
compile_options
compile_definitions
include_directories)
foreach(index IN ITEMS 1 2)
set(expected_source "${prop}_${index}")
if(NOT expected_source IN_LIST excluded_sources)
set(RunCMake_TEST_FAILED
"Source unity_${prop}_${index}.cpp was not excluded from generated FASTBuild unity files in ${fbuild_bff}")
return()
endif()
endforeach()
endforeach()
@@ -0,0 +1,16 @@
set(CMAKE_UNITY_BUILD ON)
add_executable(main main.cpp)
target_sources(main PRIVATE FILE_SET s1 TYPE SOURCES FILES unity_compile_options_1.cpp
unity_compile_options_2.cpp)
set_property(FILE_SET s1 TARGET main PROPERTY COMPILE_OPTIONS -DUNITY_COMPILE_OPTIONS)
target_sources(main PRIVATE FILE_SET s2 TYPE SOURCES FILES unity_compile_definitions_1.cpp
unity_compile_definitions_2.cpp)
set_property(FILE_SET s2 TARGET main PROPERTY COMPILE_DEFINITIONS UNITY_COMPILE_DEFINITIONS)
target_sources(main PRIVATE FILE_SET s3 TYPE SOURCES FILES unity_include_directories_1.cpp
unity_include_directories_2.cpp)
set_property(FILE_SET s3 TARGET main PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}")
@@ -0,0 +1,27 @@
set(REGEX_TO_MATCH "
Unity\\('main_Unity_1'\\)
{
.*
.UnityInputFiles =
{
'.*main.cpp',
'.*some_source_file_1.cpp',
'.*some_source_file_4.cpp',
'.*some_source_file_2.cpp',
'.*some_source_file_3.cpp'
}
.UnityInputExcludedFiles =
{
'.*some_source_file_1.cpp',
'.*some_source_file_4.cpp'
}
}
.*ObjectList.*
.*
.CompilerInputUnity =
{
'main_Unity_1'
}
")
include(${RunCMake_SOURCE_DIR}/check.cmake)
@@ -0,0 +1,10 @@
set(CMAKE_UNITY_BUILD ON)
add_executable(main
main.cpp)
target_sources(main PRIVATE FILE_SET s1 TYPE SOURCES FILES some_source_file_1.cpp
some_source_file_4.cpp)
set_property(FILE_SET s1 TARGET main PROPERTY SKIP_UNITY_BUILD_INCLUSION ON)
target_sources(main PRIVATE FILE_SET s2 TYPE SOURCES FILES some_source_file_2.cpp
some_source_file_3.cpp)
@@ -42,6 +42,7 @@ endif()
run_cmake(unitybuild_batchsize)
run_cmake(unitybuild_default_batchsize)
run_cmake(unitybuild_skip)
run_cmake(unitybuild_fileset_skip)
run_cmake(unitybuild_code_before_and_after_include)
run_cmake(unitybuild_c_no_unity_build)
run_cmake(unitybuild_c_no_unity_build_group)
@@ -0,0 +1,30 @@
set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/fileset.dir/Unity/unity_0_c.c")
file(STRINGS ${unitybuild_c} unitybuild_c_strings)
string(REGEX MATCH "\\/s[0-8].c" matched_files_0_8 ${unitybuild_c_strings})
if(matched_files_0_8)
set(RunCMake_TEST_FAILED "Generated unity for fileset contains s0.c -> s8.c which should have been skipped")
return()
endif()
string(REGEX MATCH "\\/s9.c" matched_files_9 ${unitybuild_c_strings})
if(NOT matched_files_9)
set(RunCMake_TEST_FAILED "Generated unity for fileset should have contained s9.c!")
return()
endif()
set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/fileset2.dir/Unity/unity_0_c.c")
file(STRINGS ${unitybuild_c} unitybuild_c_strings)
string(REGEX MATCH "\\/s[0-8].c" matched_files_0_8 ${unitybuild_c_strings})
if(matched_files_0_8)
set(RunCMake_TEST_FAILED "Generated unity for fileset2 contains s0.c -> s8.c which should have been skipped")
return()
endif()
string(REGEX MATCH "\\/s9.c" matched_files_9 ${unitybuild_c_strings})
if(NOT matched_files_9)
set(RunCMake_TEST_FAILED "Generated unity for fileset2 should have contained s9.c!")
return()
endif()
@@ -0,0 +1,62 @@
set(CMAKE_INTERMEDIATE_DIR_STRATEGY FULL CACHE STRING "" FORCE)
project(unitybuild_skip C)
set(srcs "")
foreach(s RANGE 0 9)
set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.c")
file(WRITE "${src}" "int s${s}(void) { return 0; }\n")
list(APPEND srcs "${src}")
endforeach()
add_library(fileset SHARED)
set_target_properties(fileset PROPERTIES UNITY_BUILD ON)
target_sources(fileset PUBLIC FILE_SET s0 TYPE HEADERS BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s0.c)
target_sources(fileset PUBLIC FILE_SET s1 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s1.c)
set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/s1.c PROPERTY HEADER_FILE_ONLY ON)
target_sources(fileset PUBLIC FILE_SET s2 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s2.c)
set_property(FILE_SET s2 TARGET fileset PROPERTY SKIP_UNITY_BUILD_INCLUSION ON)
target_sources(fileset PUBLIC FILE_SET s3 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s3.c)
set_property(FILE_SET s3 TARGET fileset PROPERTY COMPILE_OPTIONS "opt")
set_property(FILE_SET s3 TARGET fileset PROPERTY INTERFACE_COMPILE_OPTIONS "opt")
target_sources(fileset PUBLIC FILE_SET s4 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s4.c)
set_property(FILE_SET s4 TARGET fileset PROPERTY COMPILE_DEFINITIONS "def")
set_property(FILE_SET s4 TARGET fileset PROPERTY INTERFACE_COMPILE_DEFINITIONS "def")
target_sources(fileset PUBLIC FILE_SET s5 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s5.c)
set_property(FILE_SET s5 TARGET fileset PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}")
set_property(FILE_SET s5 TARGET fileset PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}")
target_sources(fileset PUBLIC FILE_SET s6 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s6.c)
set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/s6.c PROPERTY COMPILE_OPTIONS "opt")
target_sources(fileset PUBLIC FILE_SET s7 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s7.c)
set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/s7.c PROPERTY COMPILE_DEFINITIONS "def")
target_sources(fileset PUBLIC FILE_SET s8 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s8.c)
set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/s8.c PROPERTY INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}")
target_sources(fileset PUBLIC FILE_SET s9 TYPE SOURCES BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}"
FILES ${CMAKE_CURRENT_BINARY_DIR}/s9.c)
add_library(fileset2 SHARED)
set_target_properties(fileset2 PROPERTIES UNITY_BUILD ON)
target_link_libraries(fileset2 PRIVATE fileset)