Browse Source

Merge topic 'unity_explicit_groups'

9f4eb352fe Unity Builds: Support explicit specification of sources to groups
b00585adcc Unity: Refactor implementation to make it easier to extend

Acked-by: Kitware Robot <[email protected]>
Merge-request: !4716
Brad King 5 năm trước cách đây
mục cha
commit
cee7eb8b3a
27 tập tin đã thay đổi với 581 bổ sung46 xóa
  1. 2 0
      Help/manual/cmake-properties.7.rst
  2. 5 0
      Help/prop_sf/UNITY_GROUP.rst
  3. 22 2
      Help/prop_tgt/UNITY_BUILD.rst
  4. 22 0
      Help/prop_tgt/UNITY_BUILD_MODE.rst
  5. 6 0
      Help/release/dev/grouped-unity-build-mode.rst
  6. 134 44
      Source/cmLocalGenerator.cxx
  7. 1 0
      Source/cmTarget.cxx
  8. 6 0
      Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
  9. 5 0
      Tests/RunCMake/UnityBuild/f.c
  10. 42 0
      Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group-check.cmake
  11. 39 0
      Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_group.cmake
  12. 5 0
      Tests/RunCMake/UnityBuild/unitybuild_c_batch-check.cmake
  13. 15 0
      Tests/RunCMake/UnityBuild/unitybuild_c_batch.cmake
  14. 11 0
      Tests/RunCMake/UnityBuild/unitybuild_c_group-check.cmake
  15. 17 0
      Tests/RunCMake/UnityBuild/unitybuild_c_group.cmake
  16. 5 0
      Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group-check.cmake
  17. 16 0
      Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build_group.cmake
  18. 27 0
      Tests/RunCMake/UnityBuild/unitybuild_cxx_group-check.cmake
  19. 27 0
      Tests/RunCMake/UnityBuild/unitybuild_cxx_group.cmake
  20. 1 0
      Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-result.txt
  21. 5 0
      Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-stderr.txt
  22. 12 0
      Tests/RunCMake/UnityBuild/unitybuild_invalid_mode.cmake
  23. 1 0
      Tests/RunCMake/VS10Project/RunCMakeTest.cmake
  24. 56 0
      Tests/RunCMake/VS10Project/UnityBuildNativeGrouped-check.cmake
  25. 20 0
      Tests/RunCMake/VS10Project/UnityBuildNativeGrouped.cmake
  26. 59 0
      Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped-check.cmake
  27. 20 0
      Tests/RunCMake/VS10Project/UnityBuildPre2017Grouped.cmake

+ 2 - 0
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

+ 5 - 0
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``.

+ 22 - 2
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

+ 22 - 0
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``.

+ 6 - 0
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.

+ 134 - 44
Source/cmLocalGenerator.cxx

@@ -2758,6 +2758,120 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target)
   }
 }
 
+namespace {
+
+inline void RegisterUnitySources(cmGeneratorTarget* target, cmSourceFile* sf,
+                                 std::string const& filename)
+{
+  target->AddSourceFileToUnityBatch(sf->ResolveFullPath());
+  sf->SetProperty("UNITY_SOURCE_FILE", filename.c_str());
+}
+
+inline void IncludeFileInUnitySources(cmGeneratedFileStream& unity_file,
+                                      std::string const& sf_full_path,
+                                      cmProp beforeInclude,
+                                      cmProp afterInclude)
+{
+  if (beforeInclude) {
+    unity_file << *beforeInclude << "\n";
+  }
+
+  unity_file << "#include \"" << sf_full_path << "\"\n";
+
+  if (afterInclude) {
+    unity_file << *afterInclude << "\n";
+  }
+}
+
+std::vector<std::string> AddUnityFilesModeAuto(
+  cmGeneratorTarget* target, std::string const& lang,
+  std::vector<cmSourceFile*> const& filtered_sources, cmProp beforeInclude,
+  cmProp afterInclude, std::string const& filename_base, size_t batchSize)
+{
+  if (batchSize == 0) {
+    batchSize = filtered_sources.size();
+  }
+
+  std::vector<std::string> unity_files;
+  for (size_t itemsLeft = filtered_sources.size(), chunk, batch = 0;
+       itemsLeft > 0; itemsLeft -= chunk, ++batch) {
+
+    chunk = std::min(itemsLeft, batchSize);
+
+    std::string filename = cmStrCat(filename_base, "unity_", batch,
+                                    (lang == "C") ? "_c.c" : "_cxx.cxx");
+
+    const std::string filename_tmp = cmStrCat(filename, ".tmp");
+    {
+      size_t begin = batch * batchSize;
+      size_t end = begin + chunk;
+
+      cmGeneratedFileStream file(
+        filename_tmp, false,
+        target->GetGlobalGenerator()->GetMakefileEncoding());
+      file << "/* generated by CMake */\n\n";
+
+      for (; begin != end; ++begin) {
+        cmSourceFile* sf = filtered_sources[begin];
+        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;
+}
+
+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)
 {
   if (!target->GetPropertyAsBool("UNITY_BUILD")) {
@@ -2786,6 +2900,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;
@@ -2800,53 +2915,28 @@ void cmLocalGenerator::AddUnityBuild(cmGeneratorTarget* target)
                      !sf->GetProperty("INCLUDE_DIRECTORIES");
                  });
 
-    size_t batchSize = unityBatchSize;
-    if (unityBatchSize == 0) {
-      batchSize = filtered_sources.size();
+    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 (size_t itemsLeft = filtered_sources.size(), chunk, batch = 0;
-         itemsLeft > 0; itemsLeft -= chunk, ++batch) {
-
-      chunk = std::min(itemsLeft, batchSize);
-
-      std::string filename = cmStrCat(filename_base, "unity_", batch,
-                                      (lang == "C") ? "_c.c" : "_cxx.cxx");
-
-      const std::string filename_tmp = cmStrCat(filename, ".tmp");
-      {
-        size_t begin = batch * batchSize;
-        size_t end = begin + chunk;
-
-        cmGeneratedFileStream file(
-          filename_tmp, false,
-          this->GetGlobalGenerator()->GetMakefileEncoding());
-        file << "/* generated by CMake */\n\n";
-
-        for (; begin != end; ++begin) {
-          cmSourceFile* sf = filtered_sources[begin];
-
-          target->AddSourceFileToUnityBatch(sf->ResolveFullPath());
-          sf->SetProperty("UNITY_SOURCE_FILE", filename.c_str());
-
-          if (beforeInclude) {
-            file << *beforeInclude << "\n";
-          }
-
-          file << "#include \"" << sf->ResolveFullPath() << "\"\n";
-
-          if (afterInclude) {
-            file << *afterInclude << "\n";
-          }
-        }
-      }
-      cmSystemTools::MoveFileIfDifferent(filename_tmp, filename);
-
-      target->AddSource(filename, true);
-
-      auto unity = this->Makefile->GetOrCreateSource(filename);
+    for (auto const& file : unity_files) {
+      auto unity = this->GetMakefile()->GetOrCreateSource(file);
+      target->AddSource(file, true);
       unity->SetProperty("SKIP_UNITY_BUILD_INCLUSION", "ON");
-      unity->SetProperty("UNITY_SOURCE_FILE", filename.c_str());
+      unity->SetProperty("UNITY_SOURCE_FILE", file.c_str());
     }
   }
 }

+ 1 - 0
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()) {

+ 6 - 0
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)

+ 5 - 0
Tests/RunCMake/UnityBuild/f.c

@@ -0,0 +1,5 @@
+int f(int x)
+{
+  (void)x;
+  return 0;
+}

+ 42 - 0
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()

+ 39 - 0
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"
+                            )

+ 5 - 0
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()

+ 15 - 0
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
+                          )

+ 11 - 0
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()

+ 17 - 0
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")

+ 5 - 0
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()

+ 16 - 0
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"
+                            )

+ 27 - 0
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()

+ 27 - 0
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"
+                            )

+ 1 - 0
Tests/RunCMake/UnityBuild/unitybuild_invalid_mode-result.txt

@@ -0,0 +1 @@
+1

+ 5 - 0
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\.$

+ 12 - 0
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)

+ 1 - 0
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)

+ 56 - 0
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()

+ 20 - 0
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"
+                            )

+ 59 - 0
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()

+ 20 - 0
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"
+                            )