Commit 0fe9c404 authored by Stephen Kelly's avatar Stephen Kelly Committed by Craig Scott

Unity Build: Add option for generating per-file unique id

Fixes: #21477
parent 82b25be8
Pipeline #205023 waiting for manual action with stages
in 6 minutes and 51 seconds
......@@ -353,6 +353,7 @@ Properties on Targets
/prop_tgt/UNITY_BUILD_CODE_AFTER_INCLUDE
/prop_tgt/UNITY_BUILD_CODE_BEFORE_INCLUDE
/prop_tgt/UNITY_BUILD_MODE
/prop_tgt/UNITY_BUILD_UNIQUE_ID
/prop_tgt/VERSION
/prop_tgt/VISIBILITY_INLINES_HIDDEN
/prop_tgt/VS_CONFIGURATION_TYPE
......
......@@ -470,6 +470,7 @@ Variables that Control the Build
/variable/CMAKE_TRY_COMPILE_TARGET_TYPE
/variable/CMAKE_UNITY_BUILD
/variable/CMAKE_UNITY_BUILD_BATCH_SIZE
/variable/CMAKE_UNITY_BUILD_UNIQUE_ID
/variable/CMAKE_USE_RELATIVE_PATHS
/variable/CMAKE_VISIBILITY_INLINES_HIDDEN
/variable/CMAKE_VS_GLOBALS
......
......@@ -70,6 +70,11 @@ a number of measures to help address such problems:
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
in unity builds.
* The :prop_tgt:`UNITY_BUILD_CODE_BEFORE_INCLUDE` and
:prop_tgt:`UNITY_BUILD_CODE_AFTER_INCLUDE` target properties can be used
to inject code into the unity source files before and after every
......
UNITY_BUILD_UNIQUE_ID
---------------------
The name of a valid C-identifier which is set to a unique per-file
value during unity builds.
When this property is populated and when :prop_tgt:`UNITY_BUILD`
is true, the property value is used to define a compiler definition
of the specified name. The value of the defined symbol is unspecified,
but it is unique per file path.
Given:
.. code-block:: cmake
set_target_properties(myTarget PROPERTIES
UNITY_BUILD "ON"
UNITY_BUILD_UNIQUE_ID "MY_UNITY_ID"
)
the ``MY_UNITY_ID`` symbol is defined to a unique per-file value.
One known use case for this identifier is to disambiguate the
variables in an anonymous namespace in a limited scope.
Anonymous namespaces present a problem for unity builds because
they are used to ensure that certain variables and declarations
are scoped to a translation unit which is approximated by a
single source file. When source files are combined in a unity
build file, those variables in different files are combined in
a single translation unit and the names clash. This property can
be used to avoid that with code like the following:
.. code-block:: cpp
// Needed for when unity builds are disabled
#ifndef MY_UNITY_ID
#define MY_UNITY_ID
#endif
namespace { namespace MY_UNITY_ID {
// The name 'i' clashes (or could clash) with other
// variables in other anonymous namespaces
int i = 42;
}}
int use_var()
{
return MY_UNITY_ID::i;
}
The pseudononymous namespace is used within a truly anonymous namespace.
On many platforms, this maintains the invariant that the symbols within
do not get external linkage when performing a unity build.
unity-build-anonymous-macros
----------------------------
* The :prop_tgt:`UNITY_BUILD_UNIQUE_ID` target property
was added to support generation of an identifier that is
unique per source file in unity builds. It can help to
resolve duplicate symbol problems with anonymous namespaces.
CMAKE_UNITY_BUILD_UNIQUE_ID
---------------------------
This variable is used to initialize the :prop_tgt:`UNITY_BUILD_UNIQUE_ID`
property of targets when they are created. It specifies the name of the
unique identifier generated per file in a unity build.
......@@ -2753,8 +2753,15 @@ inline void RegisterUnitySources(cmGeneratorTarget* target, cmSourceFile* sf,
inline void IncludeFileInUnitySources(cmGeneratedFileStream& unity_file,
std::string const& sf_full_path,
cmProp beforeInclude,
cmProp afterInclude)
cmProp afterInclude, cmProp uniqueIdName)
{
if (uniqueIdName && !uniqueIdName->empty()) {
unity_file << "#undef " << *uniqueIdName << "\n"
<< "#define " << *uniqueIdName << " unity_"
<< cmSystemTools::ComputeStringMD5(sf_full_path) << "\n";
}
if (beforeInclude) {
unity_file << *beforeInclude << "\n";
}
......@@ -2775,6 +2782,8 @@ std::vector<std::string> AddUnityFilesModeAuto(
batchSize = filtered_sources.size();
}
cmProp uniqueIdName = target->GetProperty("UNITY_BUILD_UNIQUE_ID");
std::vector<std::string> unity_files;
for (size_t itemsLeft = filtered_sources.size(), chunk, batch = 0;
itemsLeft > 0; itemsLeft -= chunk, ++batch) {
......@@ -2798,7 +2807,7 @@ std::vector<std::string> AddUnityFilesModeAuto(
cmSourceFile* sf = filtered_sources[begin];
RegisterUnitySources(target, sf, filename);
IncludeFileInUnitySources(file, sf->ResolveFullPath(), beforeInclude,
afterInclude);
afterInclude, uniqueIdName);
}
}
cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
......@@ -2829,6 +2838,8 @@ std::vector<std::string> AddUnityFilesModeGroup(
}
}
cmProp uniqueIdName = target->GetProperty("UNITY_BUILD_UNIQUE_ID");
for (auto const& item : explicit_mapping) {
auto const& name = item.first;
std::string filename = cmStrCat(filename_base, "unity_", name,
......@@ -2844,7 +2855,7 @@ std::vector<std::string> AddUnityFilesModeGroup(
for (cmSourceFile* sf : item.second) {
RegisterUnitySources(target, sf, filename);
IncludeFileInUnitySources(file, sf->ResolveFullPath(), beforeInclude,
afterInclude);
afterInclude, uniqueIdName);
}
}
cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
......
......@@ -379,6 +379,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
initProp("VS_JUST_MY_CODE_DEBUGGING");
initProp("DISABLE_PRECOMPILE_HEADERS");
initProp("UNITY_BUILD");
initProp("UNITY_BUILD_UNIQUE_ID");
initProp("OPTIMIZE_DEPENDENCIES");
initPropValue("UNITY_BUILD_BATCH_SIZE", "8");
initPropValue("UNITY_BUILD_MODE", "BATCH");
......
include(RunCMake)
function(run_build name)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
set(RunCMake_TEST_NO_CLEAN 1)
run_cmake(${name})
run_cmake_command(${name}-build ${CMAKE_COMMAND} --build . --config Debug)
unset(RunCMake_TEST_BINARY_DIR)
unset(RunCMake_TEST_NO_CLEAN)
endfunction()
run_cmake(unitybuild_c)
run_cmake(unitybuild_c_batch)
run_cmake(unitybuild_c_group)
......@@ -15,6 +24,9 @@ run_cmake(unitybuild_c_no_unity_build)
run_cmake(unitybuild_c_no_unity_build_group)
run_cmake(unitybuild_order)
run_cmake(unitybuild_invalid_mode)
run_build(unitybuild_anon_ns)
run_build(unitybuild_anon_ns_no_unity_build)
run_build(unitybuild_anon_ns_group_mode)
function(run_test name)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
......
project(unitybuild_anon_ns CXX)
include(${CMAKE_CURRENT_SOURCE_DIR}/unitybuild_anon_ns_test_files.cmake)
write_unity_build_anon_ns_test_files(srcs)
add_library(tgt SHARED ${srcs})
set_target_properties(tgt PROPERTIES UNITY_BUILD ON)
set_target_properties(tgt PROPERTIES UNITY_BUILD_UNIQUE_ID MY_ANON_ID)
project(unitybuild_anon_ns_group_mode CXX)
include(${CMAKE_CURRENT_SOURCE_DIR}/unitybuild_anon_ns_test_files.cmake)
write_unity_build_anon_ns_test_files(srcs)
add_library(tgt SHARED ${srcs})
set_target_properties(tgt PROPERTIES UNITY_BUILD ON
UNITY_BUILD_MODE GROUP)
set_target_properties(tgt PROPERTIES UNITY_BUILD_UNIQUE_ID MY_ANON_ID)
set_source_files_properties(s1.cpp s2.cpp s5.cpp s7.cpp s8.cpp
PROPERTIES UNITY_GROUP "a"
)
set_source_files_properties(s3.cpp s4.cpp s6.cpp
PROPERTIES UNITY_GROUP "b"
)
project(unitybuild_anon_ns_no_unity_build CXX)
include(${CMAKE_CURRENT_SOURCE_DIR}/unitybuild_anon_ns_test_files.cmake)
write_unity_build_anon_ns_test_files(srcs)
add_library(tgt SHARED ${srcs})
set_target_properties(tgt PROPERTIES UNITY_BUILD OFF)
set_target_properties(tgt PROPERTIES UNITY_BUILD_UNIQUE_ID MY_ANON_ID)
function(write_unity_build_anon_ns_test_files OUTVAR)
set(srcs)
foreach(s RANGE 1 8)
set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cpp")
file(WRITE "${src}" "
#ifndef CONFIG_H
#define CONFIG_H
#define MY_ANON_NAMESPACE MY_ANON_ID
#define MY_ANON(Name) MY_ANON_NAMESPACE::Name
#define MY_ANON_USING_NAMESPACE using namespace MY_ANON_NAMESPACE
#endif
namespace { namespace MY_ANON_NAMESPACE {
int i = ${s};
}}
int use_plain_${s}() {
return MY_ANON_NAMESPACE::i;
}
int func_like_macro_${s}() {
return MY_ANON(i);
}
int using_macro_${s}() {
MY_ANON_USING_NAMESPACE;
return i;
}
")
list(APPEND srcs "${src}")
endforeach()
set(${OUTVAR} ${srcs} PARENT_SCOPE)
endfunction()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment