summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Maynard <robert.maynard@kitware.com>2020-05-06 15:45:42 -0400
committerRobert Maynard <robert.maynard@kitware.com>2020-05-07 11:13:34 -0400
commit9f4eb352fe1383f740876e3924205be1d4bf897c (patch)
tree6911d23d9c5f950860a9ba1d0048b4d7f1417f1c
parentb00585adcc11afcf4d02d9d9927a929f33b19546 (diff)
downloadcmake-9f4eb352fe1383f740876e3924205be1d4bf897c.tar.gz
Unity Builds: Support explicit specification of sources to groups
Instead of having CMake determine which files should go into each unity file, the user can now use explicitly state the mapping.
-rw-r--r--Help/manual/cmake-properties.7.rst2
-rw-r--r--Help/prop_sf/UNITY_GROUP.rst5
-rw-r--r--Help/prop_tgt/UNITY_BUILD.rst24
-rw-r--r--Help/prop_tgt/UNITY_BUILD_MODE.rst22
-rw-r--r--Help/release/dev/grouped-unity-build-mode.rst6
-rw-r--r--Source/cmLocalGenerator.cxx67
-rw-r--r--Source/cmTarget.cxx1
-rw-r--r--Tests/RunCMake/UnityBuild/RunCMakeTest.cmake6
-rw-r--r--Tests/RunCMake/UnityBuild/f.c5
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group-check.cmake42
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group.cmake39
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_c_batch-check.cmake5
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_c_batch.cmake15
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_c_group-check.cmake11
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_c_group.cmake17
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group-check.cmake5
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group.cmake16
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_cxx_group-check.cmake27
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_cxx_group.cmake27
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-result.txt1
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-stderr.txt5
-rw-r--r--Tests/RunCMake/UnityBuild/unitybuild_invalid_mode.cmake12
-rw-r--r--Tests/RunCMake/VS10Project/RunCMakeTest.cmake1
-rw-r--r--Tests/RunCMake/VS10Project/UnityBuildNativeGrouped-check.cmake56
-rw-r--r--Tests/RunCMake/VS10Project/UnityBuildNativeGrouped.cmake20
-rw-r--r--Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped-check.cmake59
-rw-r--r--Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped.cmake20
27 files changed, 511 insertions, 5 deletions
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 9031e9c69d..4103806cbf 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -346,6 +346,7 @@ Properties on Targets
/prop_tgt/UNITY_BUILD_BATCH_SIZE
/prop_tgt/UNITY_BUILD_CODE_AFTER_INCLUDE
/prop_tgt/UNITY_BUILD_CODE_BEFORE_INCLUDE
+ /prop_tgt/UNITY_BUILD_MODE
/prop_tgt/VERSION
/prop_tgt/VISIBILITY_INLINES_HIDDEN
/prop_tgt/VS_CONFIGURATION_TYPE
@@ -484,6 +485,7 @@ Properties on Source Files
/prop_sf/Swift_DEPENDENCIES_FILE
/prop_sf/Swift_DIAGNOSTICS_FILE
/prop_sf/SYMBOLIC
+ /prop_sf/UNITY_GROUP
/prop_sf/VS_COPY_TO_OUT_DIR
/prop_sf/VS_CSHARP_tagname
/prop_sf/VS_DEPLOYMENT_CONTENT
diff --git a/Help/prop_sf/UNITY_GROUP.rst b/Help/prop_sf/UNITY_GROUP.rst
new file mode 100644
index 0000000000..ec6b0f6520
--- /dev/null
+++ b/Help/prop_sf/UNITY_GROUP.rst
@@ -0,0 +1,5 @@
+UNITY_GROUP
+-----------
+
+This property controls which *bucket* the source will be part of when
+the :prop_tgt:`UNITY_BUILD_MODE` is set to ``GROUP``.
diff --git a/Help/prop_tgt/UNITY_BUILD.rst b/Help/prop_tgt/UNITY_BUILD.rst
index 479802e595..e140952110 100644
--- a/Help/prop_tgt/UNITY_BUILD.rst
+++ b/Help/prop_tgt/UNITY_BUILD.rst
@@ -5,8 +5,28 @@ When this property is set to true, the target source files will be combined
into batches for faster compilation. This is done by creating a (set of)
unity sources which ``#include`` the original sources, then compiling these
unity sources instead of the originals. This is known as a *Unity* or *Jumbo*
-build. The :prop_tgt:`UNITY_BUILD_BATCH_SIZE` property controls the upper
-limit on how many sources can be combined per unity source file.
+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:
+
+* ``BATCH``
+ When in this mode CMake determines which files are grouped together.
+ The :prop_tgt:`UNITY_BUILD_BATCH_SIZE` property controls the upper limit on
+ how many sources can be combined per unity source file.
+
+* ``GROUP``
+ When in this mode each target explicitly specifies how to group
+ source files. Each source file that has the same
+ :prop_sf:`UNITY_GROUP` value will be grouped together. Any sources
+ that don't have this property will be compiled individually. The
+ :prop_tgt:`UNITY_BUILD_BATCH_SIZE` property is ignored when using
+ this mode.
+
+If no explicit :prop_tgt:`UNITY_BUILD_MODE` has been specified, CMake will
+default to ``BATCH``.
Unity builds are not currently supported for all languages. CMake version
|release| supports combining ``C`` and ``CXX`` source files. For targets that
diff --git a/Help/prop_tgt/UNITY_BUILD_MODE.rst b/Help/prop_tgt/UNITY_BUILD_MODE.rst
new file mode 100644
index 0000000000..cbc49891cf
--- /dev/null
+++ b/Help/prop_tgt/UNITY_BUILD_MODE.rst
@@ -0,0 +1,22 @@
+UNITY_BUILD_MODE
+----------------
+
+CMake provides different algorithms for selecting which sources are grouped
+together into a *bucket*. Selection is decided by this property,
+which has the following acceptable values:
+
+* ``BATCH``
+ When in this mode CMake determines which files are grouped together.
+ The :prop_tgt:`UNITY_BUILD_BATCH_SIZE` property controls the upper limit on
+ how many sources can be combined per unity source file.
+
+* ``GROUP``
+ When in this mode each target explicitly specifies how to group
+ source files. Each source file that has the same
+ :prop_sf:`UNITY_GROUP` value will be grouped together. Any sources
+ that don't have this property will be compiled individually. The
+ :prop_tgt:`UNITY_BUILD_BATCH_SIZE` property is ignored when using
+ this mode.
+
+If no explicit :prop_tgt:`UNITY_BUILD_MODE` has been specified, CMake will
+default to ``BATCH``.
diff --git a/Help/release/dev/grouped-unity-build-mode.rst b/Help/release/dev/grouped-unity-build-mode.rst
new file mode 100644
index 0000000000..802de4af89
--- /dev/null
+++ b/Help/release/dev/grouped-unity-build-mode.rst
@@ -0,0 +1,6 @@
+grouped-unity-build-mode
+------------------------
+
+* The :prop_tgt:`UNITY_BUILD_MODE` target property was added to tell
+ generators which algorithm to use for grouping included source
+ files.
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index dee5cb4dbd..63b757800a 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -2821,6 +2821,53 @@ std::vector<std::string> AddUnityFilesModeAuto(
}
return unity_files;
}
+
+std::vector<std::string> AddUnityFilesModeGroup(
+ cmGeneratorTarget* target, std::string const& lang,
+ std::vector<cmSourceFile*> const& filtered_sources, cmProp beforeInclude,
+ cmProp afterInclude, std::string const& filename_base)
+{
+ std::vector<std::string> unity_files;
+
+ // sources organized by group name. Drop any source
+ // without a group
+ std::unordered_map<std::string, std::vector<cmSourceFile*>> explicit_mapping;
+ for (cmSourceFile* sf : filtered_sources) {
+ if (cmProp value = sf->GetProperty("UNITY_GROUP")) {
+ auto i = explicit_mapping.find(*value);
+ if (i == explicit_mapping.end()) {
+ std::vector<cmSourceFile*> sources{ sf };
+ explicit_mapping.emplace(*value, sources);
+ } else {
+ i->second.emplace_back(sf);
+ }
+ }
+ }
+
+ for (auto const& item : explicit_mapping) {
+ auto const& name = item.first;
+ std::string filename = cmStrCat(filename_base, "unity_", name,
+ (lang == "C") ? "_c.c" : "_cxx.cxx");
+
+ const std::string filename_tmp = cmStrCat(filename, ".tmp");
+ {
+ cmGeneratedFileStream file(
+ filename_tmp, false,
+ target->GetGlobalGenerator()->GetMakefileEncoding());
+ file << "/* generated by CMake */\n\n";
+
+ for (cmSourceFile* sf : item.second) {
+ RegisterUnitySources(target, sf, filename);
+ IncludeFileInUnitySources(file, sf->ResolveFullPath(), beforeInclude,
+ afterInclude);
+ }
+ }
+ cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
+ unity_files.emplace_back(std::move(filename));
+ }
+
+ return unity_files;
+}
}
void cmLocalGenerator::AddUnityBuild(cmGeneratorTarget* target)
@@ -2851,6 +2898,7 @@ void cmLocalGenerator::AddUnityBuild(cmGeneratorTarget* target)
cmProp beforeInclude =
target->GetProperty("UNITY_BUILD_CODE_BEFORE_INCLUDE");
cmProp afterInclude = target->GetProperty("UNITY_BUILD_CODE_AFTER_INCLUDE");
+ cmProp unityMode = target->GetProperty("UNITY_BUILD_MODE");
for (std::string lang : { "C", "CXX" }) {
std::vector<cmSourceFile*> filtered_sources;
@@ -2865,9 +2913,22 @@ void cmLocalGenerator::AddUnityBuild(cmGeneratorTarget* target)
!sf->GetProperty("INCLUDE_DIRECTORIES");
});
- std::vector<std::string> unity_files =
- AddUnityFilesModeAuto(target, lang, filtered_sources, beforeInclude,
- afterInclude, filename_base, unityBatchSize);
+ std::vector<std::string> unity_files;
+ if (!unityMode || *unityMode == "BATCH") {
+ unity_files =
+ AddUnityFilesModeAuto(target, lang, filtered_sources, beforeInclude,
+ afterInclude, filename_base, unityBatchSize);
+ } else if (unityMode && *unityMode == "GROUP") {
+ unity_files =
+ AddUnityFilesModeGroup(target, lang, filtered_sources, beforeInclude,
+ afterInclude, filename_base);
+ } else {
+ // unity mode is set to an unsupported value
+ std::string e("Invalid UNITY_BUILD_MODE value of " + *unityMode +
+ " assigned to target " + target->GetName() +
+ ". Acceptable values are BATCH and GROUP.");
+ this->IssueMessage(MessageType::FATAL_ERROR, e);
+ }
for (auto const& file : unity_files) {
auto unity = this->GetMakefile()->GetOrCreateSource(file);
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index a776398857..d43fbe5c03 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -372,6 +372,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
initProp("DISABLE_PRECOMPILE_HEADERS");
initProp("UNITY_BUILD");
initPropValue("UNITY_BUILD_BATCH_SIZE", "8");
+ initPropValue("UNITY_BUILD_MODE", "BATCH");
initPropValue("PCH_WARN_INVALID", "ON");
#ifdef __APPLE__
if (this->GetGlobalGenerator()->IsXcode()) {
diff --git a/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake b/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
index 24daa64ff0..9ba3c856db 100644
--- a/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
+++ b/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
@@ -1,14 +1,20 @@
include(RunCMake)
run_cmake(unitybuild_c)
+run_cmake(unitybuild_c_batch)
+run_cmake(unitybuild_c_group)
run_cmake(unitybuild_cxx)
+run_cmake(unitybuild_cxx_group)
run_cmake(unitybuild_c_and_cxx)
+run_cmake(unitybuild_c_and_cxx_group)
run_cmake(unitybuild_batchsize)
run_cmake(unitybuild_default_batchsize)
run_cmake(unitybuild_skip)
run_cmake(unitybuild_code_before_and_after_include)
run_cmake(unitybuild_c_no_unity_build)
+run_cmake(unitybuild_c_no_unity_build_group)
run_cmake(unitybuild_order)
+run_cmake(unitybuild_invalid_mode)
function(run_test name)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
diff --git a/Tests/RunCMake/UnityBuild/f.c b/Tests/RunCMake/UnityBuild/f.c
new file mode 100644
index 0000000000..d5813c67ca
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/f.c
@@ -0,0 +1,5 @@
+int f(int x)
+{
+ (void)x;
+ return 0;
+}
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group-check.cmake
new file mode 100644
index 0000000000..b027682b18
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group-check.cmake
@@ -0,0 +1,42 @@
+set(unitybuild_a_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_a_c.c")
+if(NOT EXISTS "${unitybuild_a_c}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_a_c} does not exist.")
+ return()
+else()
+ #verify that the 4 c file is part of this grouping and therefore UNITY_BUILD_BATCH_SIZE
+ #was ignored
+ file(STRINGS ${unitybuild_a_c} unitybuild_a_c_strings)
+ string(REGEX MATCH ".*#include.*s1.c.*#include.*s2.c.*#include.*s3.c.*#include.*s4.c.*" matched_code ${unitybuild_a_c_strings})
+ if(NOT matched_code)
+ set(RunCMake_TEST_FAILED "Generated unity file doesn't include expected source files")
+ return()
+ endif()
+endif()
+
+set(unitybuild_b_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_b_c.c")
+if(NOT EXISTS "${unitybuild_b_c}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_b_c} does not exist.")
+ return()
+endif()
+
+
+set(unitybuild_a_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_a_cxx.cxx")
+if(NOT EXISTS "${unitybuild_a_cxx}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_a_cxx} does not exist.")
+ return()
+else()
+ #verify that the 4 cxx file are part of this grouping and therefore UNITY_BUILD_BATCH_SIZE
+ #was ignored
+ file(STRINGS ${unitybuild_a_cxx} unitybuild_a_cxx_strings)
+ string(REGEX MATCH ".*#include.*s1.cxx.*#include.*s2.cxx.*#include.*s3.cxx.*#include.*s4.cxx.*" matched_code ${unitybuild_a_cxx_strings})
+ if(NOT matched_code)
+ set(RunCMake_TEST_FAILED "Generated unity file doesn't include expected source files")
+ return()
+ endif()
+endif()
+
+set(unitybuild_b_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_b_cxx.cxx")
+if(NOT EXISTS "${unitybuild_b_cxx}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_b_cxx} does not exist.")
+ return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group.cmake
new file mode 100644
index 0000000000..7bf3f4b3af
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group.cmake
@@ -0,0 +1,39 @@
+project(unitybuild_c_and_cxx C CXX)
+
+set(srcs f.c)
+foreach(s RANGE 1 8)
+ set(src_c "${CMAKE_CURRENT_BINARY_DIR}/s${s}.c")
+ file(WRITE "${src_c}" "
+int f(int);\n
+int s${s}(void) { return f(${s}); }\n"
+ )
+
+ set(src_cxx "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cxx")
+ file(WRITE "${src_cxx}" "
+extern \"C\" { \n
+ int f(int); \n
+}\n
+ int s${s}(void) { return f(${s}); }\n"
+ )
+
+ list(APPEND srcs "${src_c}")
+ list(APPEND srcs "${src_cxx}")
+endforeach()
+
+
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+ UNITY_BUILD_MODE GROUP
+ #UNITY_BUILD_BATCH_SIZE will be ignored
+ UNITY_BUILD_BATCH_SIZE 2)
+
+set_source_files_properties(s1.c s2.c s3.c s4.c
+ s1.cxx s2.cxx s3.cxx s4.cxx
+ PROPERTIES UNITY_GROUP "a"
+ )
+set_source_files_properties(s5.c s6.c s7.c s8.c
+ s5.cxx s6.cxx s7.cxx s8.cxx
+ PROPERTIES UNITY_GROUP "b"
+ )
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_batch-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_batch-check.cmake
new file mode 100644
index 0000000000..024286d5a0
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_batch-check.cmake
@@ -0,0 +1,5 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_c.c")
+if(NOT EXISTS "${unitybuild_c}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c} does not exist.")
+ return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_batch.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_batch.cmake
new file mode 100644
index 0000000000..33873b658d
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_batch.cmake
@@ -0,0 +1,15 @@
+project(unitybuild_c C)
+
+set(srcs "")
+foreach(s RANGE 1 8)
+ 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(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES
+ UNITY_BUILD ON
+ UNITY_BUILD_MODE BATCH
+ )
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_group-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_group-check.cmake
new file mode 100644
index 0000000000..0b9ea15de7
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_group-check.cmake
@@ -0,0 +1,11 @@
+set(unitybuild_a_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_a_c.c")
+if(NOT EXISTS "${unitybuild_a_c}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_a_c} does not exist.")
+ return()
+endif()
+
+set(unitybuild_b_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_b_c.c")
+if(NOT EXISTS "${unitybuild_b_c}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_b_c} does not exist.")
+ return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_group.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_group.cmake
new file mode 100644
index 0000000000..1fa17f3232
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_group.cmake
@@ -0,0 +1,17 @@
+project(unitybuild_c C)
+
+set(srcs "")
+foreach(s RANGE 1 8)
+ 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(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+ UNITY_BUILD_MODE GROUP)
+
+set_source_files_properties(s1.c PROPERTIES UNITY_GROUP "a")
+set_source_files_properties(s2.c PROPERTIES UNITY_GROUP "a")
+set_source_files_properties(s3.c s4.c PROPERTIES UNITY_GROUP "b")
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group-check.cmake
new file mode 100644
index 0000000000..e344627455
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group-check.cmake
@@ -0,0 +1,5 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_a_c.c")
+if(EXISTS "${unitybuild_c}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c} should not exist.")
+ return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group.cmake
new file mode 100644
index 0000000000..7b26dc166e
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group.cmake
@@ -0,0 +1,16 @@
+project(unitybuild_c_no_unity_build C)
+
+set(srcs "")
+foreach(s RANGE 1 8)
+ 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(tgt SHARED ${srcs})
+
+#These should be ignored as UNITY_BUILD is off
+set_target_properties(tgt PROPERTIES UNITY_BUILD_MODE GROUP)
+set_source_files_properties(s1.c s2.c s3.c s4.c s5.c s6.c s7.c s8.c
+ PROPERTIES UNITY_GROUP "a"
+ )
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_cxx_group-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_cxx_group-check.cmake
new file mode 100644
index 0000000000..ecef3a3a7b
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_cxx_group-check.cmake
@@ -0,0 +1,27 @@
+set(unitybuild_a_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_a_cxx.cxx")
+if(NOT EXISTS "${unitybuild_a_cxx}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_a_cxx} does not exist.")
+ return()
+else()
+ #verify that odr2 is not part of this source set
+ file(STRINGS ${unitybuild_a_cxx} unitybuild_a_cxx_strings)
+ string(REGEX MATCH ".*#include.*odr2.cxx" matched_code ${unitybuild_a_cxx_strings})
+ if(matched_code)
+ set(RunCMake_TEST_FAILED "Generated unity file includes un-expected ord2.cxx source file")
+ return()
+ endif()
+endif()
+
+set(unitybuild_b_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_b_cxx.cxx")
+if(NOT EXISTS "${unitybuild_b_cxx}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_b_cxx} does not exist.")
+ return()
+else()
+ #verify that odr1 is not part of this source set
+ file(STRINGS ${unitybuild_b_cxx} unitybuild_b_cxx_strings)
+ string(REGEX MATCH ".*#include.*odr1.cxx" matched_code ${unitybuild_b_cxx_strings})
+ if(matched_code)
+ set(RunCMake_TEST_FAILED "Generated unity file includes un-expected ord1.cxx source file")
+ return()
+ endif()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_cxx_group.cmake b/Tests/RunCMake/UnityBuild/unitybuild_cxx_group.cmake
new file mode 100644
index 0000000000..98042896e2
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_cxx_group.cmake
@@ -0,0 +1,27 @@
+project(unitybuild_cxx CXX)
+
+set(srcs "")
+foreach(s RANGE 1 4)
+ set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cxx")
+ file(WRITE "${src}" "int s${s}(void) { return 0; }\n")
+ list(APPEND srcs "${src}")
+endforeach()
+
+foreach(s RANGE 1 2)
+ set(src "${CMAKE_CURRENT_BINARY_DIR}/odr${s}.cxx")
+ file(WRITE "${src}" "namespace odr { int s${s}(void) { return 0; } }\n")
+ list(APPEND srcs "${src}")
+endforeach()
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+ UNITY_BUILD_MODE GROUP
+ )
+
+set_source_files_properties(s1.cxx s2.cxx odr1.cxx
+ PROPERTIES UNITY_GROUP "a"
+ )
+set_source_files_properties(s3.cxx s4.cxx odr2.cxx
+ PROPERTIES UNITY_GROUP "b"
+ )
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-result.txt b/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-stderr.txt b/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-stderr.txt
new file mode 100644
index 0000000000..b1b17a03c5
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Error in CMakeLists.txt:
+ Invalid UNITY_BUILD_MODE value of INVALID assigned to target tgt\.
+ Acceptable values are BATCH and GROUP\.
+.*
+CMake Generate step failed\. Build files cannot be regenerated correctly\.$
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode.cmake b/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode.cmake
new file mode 100644
index 0000000000..0212200d72
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_invalid_mode.cmake
@@ -0,0 +1,12 @@
+project(unitybuild_c C)
+
+set(srcs "")
+foreach(s RANGE 1 8)
+ 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(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON UNITY_BUILD_MODE INVALID)
diff --git a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
index 06ccaaea31..93ef60343b 100644
--- a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
+++ b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
@@ -56,6 +56,7 @@ if (RunCMake_GENERATOR MATCHES "Visual Studio 1[0-4] 201[0-5]" OR
run_cmake(UnityBuildPre2017)
else()
run_cmake(UnityBuildNative)
+ run_cmake(UnityBuildNativeGrouped)
endif()
run_cmake(VsDotnetTargetFramework)
diff --git a/Tests/RunCMake/VS10Project/UnityBuildNativeGrouped-check.cmake b/Tests/RunCMake/VS10Project/UnityBuildNativeGrouped-check.cmake
new file mode 100644
index 0000000000..d6380da469
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/UnityBuildNativeGrouped-check.cmake
@@ -0,0 +1,56 @@
+set(unitybuild_c0 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_poolA_c.c")
+if(NOT EXISTS "${unitybuild_c0}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c0} does not exist.")
+ return()
+endif()
+
+set(unitybuild_c1 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_poolB_c.c")
+if(NOT EXISTS "${unitybuild_c1}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c1} does not exist.")
+ return()
+endif()
+
+set(tgt_project "${RunCMake_TEST_BINARY_DIR}/tgt.vcxproj")
+if (NOT EXISTS "${tgt_project}")
+ set(RunCMake_TEST_FAILED "Generated project file ${tgt_project} doesn't exist.")
+ return()
+endif()
+
+file(STRINGS ${tgt_project} tgt_projects_strings)
+
+foreach(line IN LISTS tgt_projects_strings)
+ if (line MATCHES "<EnableUnitySupport>true</EnableUnitySupport>")
+ set(have_unity_support ON)
+ endif()
+
+ if (line MATCHES "<ClCompile Include=.*IncludeInUnityFile=\"false\" CustomUnityFile=\"true\"")
+ list(APPEND unity_source_lines ${line})
+ endif()
+
+ if (line MATCHES "<ClCompile Include=.*IncludeInUnityFile=\"true\" CustomUnityFile=\"true\"")
+ list(APPEND sources_list ${line})
+ endif()
+endforeach()
+
+if (NOT have_unity_support)
+ set(RunCMake_TEST_FAILED "Generated project should include the <EnableUnitySupport> block.")
+ return()
+endif()
+
+string(REPLACE "\\" "/" unity_source_lines "${unity_source_lines}")
+string(FIND "${unity_source_lines}" "CMakeFiles/tgt.dir/Unity/unity_poolA_c.c" unity_source_file_position)
+if (unity_source_file_position EQUAL "-1")
+ set(RunCMake_TEST_FAILED "Generated project should include the generated unity source file 'poolA'.")
+ return()
+endif()
+string(FIND "${unity_source_lines}" "CMakeFiles/tgt.dir/Unity/unity_poolB_c.c" unity_source_file_position)
+if (unity_source_file_position EQUAL "-1")
+ set(RunCMake_TEST_FAILED "Generated project should include the generated unity source file 'poolB'.")
+ return()
+endif()
+
+list(LENGTH sources_list number_of_sources)
+if(NOT number_of_sources EQUAL 7)
+ set(RunCMake_TEST_FAILED "Generated project doesn't include the expect number of files.")
+ return()
+endif()
diff --git a/Tests/RunCMake/VS10Project/UnityBuildNativeGrouped.cmake b/Tests/RunCMake/VS10Project/UnityBuildNativeGrouped.cmake
new file mode 100644
index 0000000000..b740e3b4e0
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/UnityBuildNativeGrouped.cmake
@@ -0,0 +1,20 @@
+project(unitybuild_c C)
+
+set(srcs "")
+foreach(s RANGE 1 8)
+ 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(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON UNITY_BUILD_MODE GROUP)
+
+set_source_files_properties(s1.c s2.c s3.c s4.c
+ PROPERTIES UNITY_GROUP "poolA"
+ )
+
+set_source_files_properties(s5.c s6.c s7.c
+ PROPERTIES UNITY_GROUP "poolB"
+ )
diff --git a/Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped-check.cmake b/Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped-check.cmake
new file mode 100644
index 0000000000..d9b8c01a0a
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped-check.cmake
@@ -0,0 +1,59 @@
+set(unitybuild_c0 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_poolA.c")
+if(NOT EXISTS "${unitybuild_c0}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c0} does not exist.")
+ return()
+endif()
+
+set(unitybuild_c1 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_poolB.c")
+if(NOT EXISTS "${unitybuild_c1}")
+ set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c1} does not exist.")
+ return()
+endif()
+
+set(tgt_project "${RunCMake_TEST_BINARY_DIR}/tgt.vcxproj")
+if (NOT EXISTS "${tgt_project}")
+ set(RunCMake_TEST_FAILED "Generated project file ${tgt_project} doesn't exist.")
+ return()
+endif()
+
+file(STRINGS ${tgt_project} tgt_projects_strings)
+
+foreach(line IN LISTS tgt_projects_strings)
+ if (line MATCHES "<ClCompile Include=.*/>")
+ set(unity_source_line ${line})
+ endif()
+
+ if (line MATCHES "<ClCompile Include=\"[^\"]*\">")
+ string(REGEX MATCH "<ClCompile Include=\"([^\"]*)\">" source_file ${line})
+ list(APPEND sources_list ${source_file})
+ endif()
+
+ if (line MATCHES "<ExcludedFromBuild.*</ExcludedFromBuild>")
+ list(APPEND excluded_sources_list ${source_file})
+ endif()
+endforeach()
+
+string(REPLACE "\\" "/" unity_source_line ${unity_source_line})
+string(FIND "${unity_source_line}" "CMakeFiles/tgt.dir/Unity/unity_poolA.c" unity_source_file_position)
+if (unity_source_file_position EQUAL "-1")
+ set(RunCMake_TEST_FAILED "Generated project should include the generated unity source file.")
+ return()
+endif()
+string(FIND "${unity_source_line}" "CMakeFiles/tgt.dir/Unity/unity_poolB.c" unity_source_file_position)
+if (unity_source_file_position EQUAL "-1")
+ set(RunCMake_TEST_FAILED "Generated project should include the generated unity source file.")
+ return()
+endif()
+
+list(LENGTH sources_list number_of_sources)
+if(NOT number_of_sources EQUAL 7)
+ set(RunCMake_TEST_FAILED "Generated project doesn't include the expect number of files.")
+ return()
+endif()
+
+# Exclusions for Debug|Release|MinSizeRel|RelWithDebInfo
+list(LENGTH excluded_sources_list number_of_excluded_sources)
+if(NOT number_of_excluded_sources EQUAL 28)
+ set(RunCMake_TEST_FAILED "Generated project doesn't exclude the source files for all configurations.")
+ return()
+endif()
diff --git a/Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped.cmake b/Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped.cmake
new file mode 100644
index 0000000000..b740e3b4e0
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped.cmake
@@ -0,0 +1,20 @@
+project(unitybuild_c C)
+
+set(srcs "")
+foreach(s RANGE 1 8)
+ 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(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON UNITY_BUILD_MODE GROUP)
+
+set_source_files_properties(s1.c s2.c s3.c s4.c
+ PROPERTIES UNITY_GROUP "poolA"
+ )
+
+set_source_files_properties(s5.c s6.c s7.c
+ PROPERTIES UNITY_GROUP "poolB"
+ )