Browse Source

Merge topic 'unity-build'

7786a05c70 Unity build: Add XCode support
1353802af3 Unity build: Add unit tests
8dfeb5d278 Unity build: Add support for Visual Studio generator
7114c141e2 Unity build: Add support for Ninja and Makefile generators

Acked-by: Kitware Robot <[email protected]>
Acked-by: Stanislav Ershov <[email protected]>
Acked-by: Evgeniy Dushistov <[email protected]>
Acked-by: Viktor Kirilov <[email protected]>
Merge-request: !3611
Brad King 6 years ago
parent
commit
ac4d6d4a9d
50 changed files with 665 additions and 2 deletions
  1. 5 0
      Help/manual/cmake-properties.7.rst
  2. 2 0
      Help/manual/cmake-variables.7.rst
  3. 7 0
      Help/prop_sf/SKIP_UNITY_BUILD_INCLUSION.rst
  4. 55 0
      Help/prop_tgt/UNITY_BUILD.rst
  5. 13 0
      Help/prop_tgt/UNITY_BUILD_BATCH_SIZE.rst
  6. 8 0
      Help/prop_tgt/UNITY_BUILD_CODE_AFTER_INCLUDE.rst
  7. 8 0
      Help/prop_tgt/UNITY_BUILD_CODE_BEFORE_INCLUDE.rst
  8. 6 0
      Help/release/dev/unity-build.rst
  9. 6 0
      Help/variable/CMAKE_UNITY_BUILD.rst
  10. 6 0
      Help/variable/CMAKE_UNITY_BUILD_BATCH_SIZE.rst
  11. 1 0
      Source/cmGlobalXCodeGenerator.cxx
  12. 96 0
      Source/cmLocalGenerator.cxx
  13. 1 0
      Source/cmLocalGenerator.h
  14. 6 2
      Source/cmLocalVisualStudio7Generator.cxx
  15. 1 0
      Source/cmMakefileExecutableTargetGenerator.cxx
  16. 1 0
      Source/cmMakefileLibraryTargetGenerator.cxx
  17. 1 0
      Source/cmMakefileUtilityTargetGenerator.cxx
  18. 1 0
      Source/cmNinjaNormalTargetGenerator.cxx
  19. 2 0
      Source/cmTarget.cxx
  20. 51 0
      Source/cmVisualStudio10TargetGenerator.cxx
  21. 1 0
      Tests/RunCMake/CMakeLists.txt
  22. 3 0
      Tests/RunCMake/UnityBuild/CMakeLists.txt
  23. 23 0
      Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
  24. 6 0
      Tests/RunCMake/UnityBuild/func.c
  25. 6 0
      Tests/RunCMake/UnityBuild/func.h
  26. 6 0
      Tests/RunCMake/UnityBuild/main.c
  27. 11 0
      Tests/RunCMake/UnityBuild/unitybuild_batchsize-check.cmake
  28. 16 0
      Tests/RunCMake/UnityBuild/unitybuild_batchsize.cmake
  29. 5 0
      Tests/RunCMake/UnityBuild/unitybuild_c-check.cmake
  30. 12 0
      Tests/RunCMake/UnityBuild/unitybuild_c.cmake
  31. 11 0
      Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx-check.cmake
  32. 17 0
      Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx.cmake
  33. 5 0
      Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build-check.cmake
  34. 10 0
      Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build.cmake
  35. 7 0
      Tests/RunCMake/UnityBuild/unitybuild_code_before_and_after_include-check.cmake
  36. 13 0
      Tests/RunCMake/UnityBuild/unitybuild_code_before_and_after_include.cmake
  37. 5 0
      Tests/RunCMake/UnityBuild/unitybuild_cxx-check.cmake
  38. 12 0
      Tests/RunCMake/UnityBuild/unitybuild_cxx.cmake
  39. 7 0
      Tests/RunCMake/UnityBuild/unitybuild_default_batchsize-check.cmake
  40. 15 0
      Tests/RunCMake/UnityBuild/unitybuild_default_batchsize.cmake
  41. 7 0
      Tests/RunCMake/UnityBuild/unitybuild_order-check.cmake
  42. 12 0
      Tests/RunCMake/UnityBuild/unitybuild_order.cmake
  43. 8 0
      Tests/RunCMake/UnityBuild/unitybuild_runtest.cmake
  44. 14 0
      Tests/RunCMake/UnityBuild/unitybuild_skip-check.cmake
  45. 30 0
      Tests/RunCMake/UnityBuild/unitybuild_skip.cmake
  46. 9 0
      Tests/RunCMake/VS10Project/RunCMakeTest.cmake
  47. 45 0
      Tests/RunCMake/VS10Project/UnityBuildNative-check.cmake
  48. 12 0
      Tests/RunCMake/VS10Project/UnityBuildNative.cmake
  49. 48 0
      Tests/RunCMake/VS10Project/UnityBuildPre2017-check.cmake
  50. 12 0
      Tests/RunCMake/VS10Project/UnityBuildPre2017.cmake

+ 5 - 0
Help/manual/cmake-properties.7.rst

@@ -323,6 +323,10 @@ Properties on Targets
    /prop_tgt/Swift_MODULE_DIRECTORY
    /prop_tgt/Swift_MODULE_NAME
    /prop_tgt/TYPE
+   /prop_tgt/UNITY_BUILD
+   /prop_tgt/UNITY_BUILD_BATCH_SIZE
+   /prop_tgt/UNITY_BUILD_CODE_AFTER_INCLUDE
+   /prop_tgt/UNITY_BUILD_CODE_BEFORE_INCLUDE
    /prop_tgt/VERSION
    /prop_tgt/VISIBILITY_INLINES_HIDDEN
    /prop_tgt/VS_CONFIGURATION_TYPE
@@ -450,6 +454,7 @@ Properties on Source Files
    /prop_sf/SKIP_AUTORCC
    /prop_sf/SKIP_AUTOUIC
    /prop_sf/SKIP_PRECOMPILE_HEADERS
+   /prop_sf/SKIP_UNITY_BUILD_INCLUSION
    /prop_sf/Swift_DEPENDENCIES_FILE
    /prop_sf/Swift_DIAGNOSTICS_FILE
    /prop_sf/SYMBOLIC

+ 2 - 0
Help/manual/cmake-variables.7.rst

@@ -432,6 +432,8 @@ Variables that Control the Build
    /variable/CMAKE_TRY_COMPILE_CONFIGURATION
    /variable/CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
    /variable/CMAKE_TRY_COMPILE_TARGET_TYPE
+   /variable/CMAKE_UNITY_BUILD
+   /variable/CMAKE_UNITY_BUILD_BATCH_SIZE
    /variable/CMAKE_USE_RELATIVE_PATHS
    /variable/CMAKE_VISIBILITY_INLINES_HIDDEN
    /variable/CMAKE_VS_GLOBALS

+ 7 - 0
Help/prop_sf/SKIP_UNITY_BUILD_INCLUSION.rst

@@ -0,0 +1,7 @@
+SKIP_UNITY_BUILD_INCLUSION
+--------------------------
+
+Is this source file skipped by :prop_tgt:`UNITY_BUILD` feature.
+
+This property helps with "ODR (One definition rule)" problems
+that one would run into when using an :prop_tgt:`UNITY_BUILD`.

+ 55 - 0
Help/prop_tgt/UNITY_BUILD.rst

@@ -0,0 +1,55 @@
+UNITY_BUILD
+-----------
+
+Should the target source files be processed into batches for
+faster compilation. This feature is known as "Unity build",
+or "Jumbo build".
+
+The `C` and `CXX` source files are grouped separately.
+
+This property is initialized by the value of the
+:variable:`CMAKE_UNITY_BUILD` variable if it is set when
+a target is created.
+
+.. note ::
+
+   It's not recommended to directly set :prop_tgt:`UNITY_BUILD`
+   to `ON`, but to instead set :variable:`CMAKE_UNITY_BUILD` from
+   the command line. However, it IS recommended to set
+   :prop_tgt:`UNITY_BUILD` to `OFF` if you need to ensure that a
+   target doesn't get a unity build.
+
+The batch size can be specified by setting
+:prop_tgt:`UNITY_BUILD_BATCH_SIZE`.
+
+The batching of source files is done by adding new sources files
+wich will `#include` the source files, and exclude them from
+building by setting :prop_sf:`HEADER_FILE_ONLY` to `ON`.
+
+
+ODR (One definition rule) errors
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Since multiple source files are included into one source file,
+it can lead to ODR errors. This section contains properites
+which help fixing these errors.
+
+The source files marked by :prop_sf:`GENERATED` will be skipped
+from unity build. This applies also for the source files marked
+with :prop_sf:`SKIP_UNITY_BUILD_INCLUSION`.
+
+The source files that have :prop_sf:`COMPILE_OPTIONS`,
+:prop_sf:`COMPILE_DEFINITIONS`, :prop_sf:`COMPILE_FLAGS`, or
+:prop_sf:`INCLUDE_DIRECTORIES` will also be skipped.
+
+With the :prop_tgt:`UNITY_BUILD_CODE_BEFORE_INCLUDE` and
+:prop_tgt:`UNITY_BUILD_CODE_AFTER_INCLUDE` one can specify code
+to be injected in the unity source file before and after every
+`#include` statement.
+
+.. note ::
+
+   The order of source files defined in the `CMakeLists.txt` will
+   be preserved into the generated unity source files. This can
+   be used to manually enforce a specific grouping based on the
+   :prop_tgt:`UNITY_BUILD_BATCH_SIZE`.

+ 13 - 0
Help/prop_tgt/UNITY_BUILD_BATCH_SIZE.rst

@@ -0,0 +1,13 @@
+UNITY_BUILD_BATCH_SIZE
+----------------------
+
+Specifies how many source code files will be included into a
+:prop_tgt:`UNITY_BUILD` source file.
+
+If the property is not set, CMake will use the value provided
+by :variable:`CMAKE_UNITY_BUILD_BATCH_SIZE`.
+
+By setting it to value `0` the generated unity source file will
+contain all the source files that would be otherwise be split
+into multiple batches. It is not recommended to do so, since it
+would affect performance.

+ 8 - 0
Help/prop_tgt/UNITY_BUILD_CODE_AFTER_INCLUDE.rst

@@ -0,0 +1,8 @@
+UNITY_BUILD_CODE_AFTER_INCLUDE
+------------------------------
+
+Code snippet which is included verbatim by the :prop_tgt:`UNITY_BUILD`
+feature just after the `#include` statement of the targeted source
+files.
+
+This could be something like `#undef NOMINMAX`.

+ 8 - 0
Help/prop_tgt/UNITY_BUILD_CODE_BEFORE_INCLUDE.rst

@@ -0,0 +1,8 @@
+UNITY_BUILD_CODE_BEFORE_INCLUDE
+-------------------------------
+
+Code snippet which is included verbatim by the :prop_tgt:`UNITY_BUILD`
+feature just before the `#include` statement of the targeted source
+files.
+
+This could be something like `#define NOMINMAX`.

+ 6 - 0
Help/release/dev/unity-build.rst

@@ -0,0 +1,6 @@
+Unity build
+-----------
+
+* The :prop_tgt:`UNITY_BUILD` target property was added to tell
+  generators to batch include source files for faster compilation
+  times.

+ 6 - 0
Help/variable/CMAKE_UNITY_BUILD.rst

@@ -0,0 +1,6 @@
+CMAKE_UNITY_BUILD
+-----------------
+
+Default value for :prop_tgt:`UNITY_BUILD` of targets.
+
+By default ``CMAKE_UNITY_BUILD`` is ``OFF``.

+ 6 - 0
Help/variable/CMAKE_UNITY_BUILD_BATCH_SIZE.rst

@@ -0,0 +1,6 @@
+CMAKE_UNITY_BUILD_BATCH_SIZE
+----------------------------
+
+Default value for :prop_tgt:`UNITY_BUILD_BATCH_SIZE` of targets.
+
+By default ``CMAKE_UNITY_BUILD_BATCH_SIZE`` is set to ``8``.

+ 1 - 0
Source/cmGlobalXCodeGenerator.cxx

@@ -2839,6 +2839,7 @@ bool cmGlobalXCodeGenerator::CreateGroups(
         continue;
       }
 
+      generator->AddUnityBuild(gtgt, "");
       generator->AddPchDependencies(gtgt, "");
 
       auto addSourceToGroup = [this, mf, gtgt,

+ 96 - 0
Source/cmLocalGenerator.cxx

@@ -38,6 +38,7 @@
 
 #include <algorithm>
 #include <assert.h>
+#include <cstdlib>
 #include <functional>
 #include <initializer_list>
 #include <iterator>
@@ -2199,6 +2200,101 @@ void cmLocalGenerator::AddPchDependencies(cmGeneratorTarget* target,
   }
 }
 
+void cmLocalGenerator::AddUnityBuild(cmGeneratorTarget* target,
+                                     const std::string& config)
+{
+  if (!target->GetPropertyAsBool("UNITY_BUILD")) {
+    return;
+  }
+
+  const std::string buildType = cmSystemTools::UpperCase(config);
+
+  std::string filename_base =
+    cmStrCat(this->GetCurrentBinaryDirectory(), "/CMakeFiles/",
+             target->GetName(), ".dir/Unity/");
+
+  std::vector<cmSourceFile*> sources;
+  target->GetSourceFiles(sources, buildType);
+
+  auto batchSizeString = target->GetProperty("UNITY_BUILD_BATCH_SIZE");
+  const size_t unityBatchSize =
+    static_cast<size_t>(std::atoi(batchSizeString));
+
+  auto beforeInclude = target->GetProperty("UNITY_BUILD_CODE_BEFORE_INCLUDE");
+  auto afterInclude = target->GetProperty("UNITY_BUILD_CODE_AFTER_INCLUDE");
+
+  for (std::string lang : { "C", "CXX" }) {
+    std::vector<cmSourceFile*> filtered_sources;
+    std::copy_if(sources.begin(), sources.end(),
+                 std::back_inserter(filtered_sources), [&](cmSourceFile* sf) {
+                   return sf->GetLanguage() == lang &&
+                     !sf->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION") &&
+                     !sf->GetPropertyAsBool("GENERATED") &&
+                     !sf->GetProperty("COMPILE_OPTIONS") &&
+                     !sf->GetProperty("COMPILE_DEFINITIONS") &&
+                     !sf->GetProperty("COMPILE_FLAGS") &&
+                     !sf->GetProperty("INCLUDE_DIRECTORIES");
+                 });
+
+    size_t batchSize = unityBatchSize;
+    if (unityBatchSize == 0) {
+      batchSize = filtered_sources.size();
+    }
+
+    for (size_t itemsLeft = filtered_sources.size(), chunk = batchSize,
+                batch = 0;
+         itemsLeft > 0; itemsLeft -= chunk, ++batch) {
+
+      chunk = std::min(itemsLeft, batchSize);
+
+      std::string filename = cmStrCat(filename_base, "unity_", batch,
+                                      (lang == "C") ? ".c" : ".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];
+
+          // Only in Visual Studio generator we keep the source files
+          // for explicit processing. For the rest the source files will
+          // not be included in the project.
+          if (!this->GetGlobalGenerator()->IsMultiConfig() ||
+              this->GetGlobalGenerator()->IsXcode()) {
+            sf->SetProperty("HEADER_FILE_ONLY", "ON");
+          }
+          sf->SetProperty("UNITY_SOURCE_FILE", filename.c_str());
+
+          if (beforeInclude) {
+            file << beforeInclude << "\n";
+          }
+
+          file << "#include \"" << sf->GetFullPath() << "\"\n";
+
+          if (afterInclude) {
+            file << afterInclude << "\n";
+          }
+        }
+      }
+      cmSystemTools::CopyFileIfDifferent(filename_tmp, filename);
+      cmSystemTools::RemoveFile(filename_tmp);
+
+      target->AddSource(filename, true);
+
+      auto unity = this->Makefile->GetOrCreateSource(filename);
+      unity->SetProperty("SKIP_UNITY_BUILD_INCLUSION", "ON");
+      unity->SetProperty("UNITY_SOURCE_FILE", filename.c_str());
+    }
+  }
+}
+
 void cmLocalGenerator::AppendIPOLinkerFlags(std::string& flags,
                                             cmGeneratorTarget* target,
                                             const std::string& config,

+ 1 - 0
Source/cmLocalGenerator.h

@@ -126,6 +126,7 @@ public:
                                 const std::string& rawFlag) const;
   void AddPchDependencies(cmGeneratorTarget* target,
                           const std::string& config);
+  void AddUnityBuild(cmGeneratorTarget* target, const std::string& config);
   void AppendIPOLinkerFlags(std::string& flags, cmGeneratorTarget* target,
                             const std::string& config,
                             const std::string& lang);

+ 6 - 2
Source/cmLocalVisualStudio7Generator.cxx

@@ -1321,6 +1321,7 @@ void cmLocalVisualStudio7Generator::WriteVCProjFile(std::ostream& fout,
                                                     const std::string& libName,
                                                     cmGeneratorTarget* target)
 {
+  this->AddUnityBuild(target, "");
   this->AddPchDependencies(target, "");
 
   std::vector<std::string> configs;
@@ -1509,8 +1510,11 @@ cmLocalVisualStudio7GeneratorFCInfo::cmLocalVisualStudio7GeneratorFCInfo(
     const std::string& linkLanguage = gt->GetLinkerLanguage(config.c_str());
     // If HEADER_FILE_ONLY is set, we must suppress this generation in
     // the project file
-    fc.ExcludedFromBuild =
-      sf.GetPropertyAsBool("HEADER_FILE_ONLY") || !cmContains(acs.Configs, ci);
+    fc.ExcludedFromBuild = sf.GetPropertyAsBool("HEADER_FILE_ONLY") ||
+      !cmContains(acs.Configs, ci) ||
+      (gt->GetPropertyAsBool("UNITY_BUILD") &&
+       sf.GetProperty("UNITY_SOURCE_FILE") &&
+       !sf.GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION"));
     if (fc.ExcludedFromBuild) {
       needfc = true;
     }

+ 1 - 0
Source/cmMakefileExecutableTargetGenerator.cxx

@@ -41,6 +41,7 @@ cmMakefileExecutableTargetGenerator::cmMakefileExecutableTargetGenerator(
     cm::make_unique<cmOSXBundleGenerator>(target, this->ConfigName);
   this->OSXBundleGenerator->SetMacContentFolders(&this->MacContentFolders);
 
+  this->LocalGenerator->AddUnityBuild(target, this->ConfigName);
   this->LocalGenerator->AddPchDependencies(target, this->ConfigName);
 }
 

+ 1 - 0
Source/cmMakefileLibraryTargetGenerator.cxx

@@ -43,6 +43,7 @@ cmMakefileLibraryTargetGenerator::cmMakefileLibraryTargetGenerator(
     cm::make_unique<cmOSXBundleGenerator>(target, this->ConfigName);
   this->OSXBundleGenerator->SetMacContentFolders(&this->MacContentFolders);
 
+  this->LocalGenerator->AddUnityBuild(target, this->ConfigName);
   this->LocalGenerator->AddPchDependencies(target, this->ConfigName);
 }
 

+ 1 - 0
Source/cmMakefileUtilityTargetGenerator.cxx

@@ -26,6 +26,7 @@ cmMakefileUtilityTargetGenerator::cmMakefileUtilityTargetGenerator(
     cm::make_unique<cmOSXBundleGenerator>(target, this->ConfigName);
   this->OSXBundleGenerator->SetMacContentFolders(&this->MacContentFolders);
 
+  this->LocalGenerator->AddUnityBuild(target, this->ConfigName);
   this->LocalGenerator->AddPchDependencies(target, this->ConfigName);
 }
 

+ 1 - 0
Source/cmNinjaNormalTargetGenerator.cxx

@@ -61,6 +61,7 @@ cmNinjaNormalTargetGenerator::cmNinjaNormalTargetGenerator(
     cm::make_unique<cmOSXBundleGenerator>(target, this->GetConfigName());
   this->OSXBundleGenerator->SetMacContentFolders(&this->MacContentFolders);
 
+  GetLocalGenerator()->AddUnityBuild(target, this->GetConfigName());
   GetLocalGenerator()->AddPchDependencies(target, this->GetConfigName());
 }
 

+ 2 - 0
Source/cmTarget.cxx

@@ -352,6 +352,8 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     initProp("Swift_MODULE_DIRECTORY");
     initProp("VS_JUST_MY_CODE_DEBUGGING");
     initProp("DISABLE_PRECOMPILE_HEADERS");
+    initProp("UNITY_BUILD");
+    initPropValue("UNITY_BUILD_BATCH_SIZE", "8");
 #ifdef __APPLE__
     if (this->GetGlobalGenerator()->IsXcode()) {
       initProp("XCODE_GENERATE_SCHEME");

+ 51 - 0
Source/cmVisualStudio10TargetGenerator.cxx

@@ -250,6 +250,7 @@ cmVisualStudio10TargetGenerator::cmVisualStudio10TargetGenerator(
   this->InSourceBuild = (this->Makefile->GetCurrentSourceDirectory() ==
                          this->Makefile->GetCurrentBinaryDirectory());
 
+  this->LocalGenerator->AddUnityBuild(target, "");
   this->LocalGenerator->AddPchDependencies(target, "");
 }
 
@@ -2070,6 +2071,17 @@ void cmVisualStudio10TargetGenerator::WriteAllSources(Elem& e0)
   if (this->GeneratorTarget->GetType() > cmStateEnums::UTILITY) {
     return;
   }
+
+  const bool haveUnityBuild =
+    this->GeneratorTarget->GetPropertyAsBool("UNITY_BUILD");
+
+  if (haveUnityBuild &&
+      this->GlobalGenerator->GetVersion() >=
+        cmGlobalVisualStudioGenerator::VS15) {
+    Elem e1(e0, "PropertyGroup");
+    e1.Element("EnableUnitySupport", "true");
+  }
+
   Elem e1(e0, "ItemGroup");
   e1.SetHasElements();
 
@@ -2168,6 +2180,45 @@ void cmVisualStudio10TargetGenerator::WriteAllSources(Elem& e0)
 
       Elem e2(e1, tool);
       this->WriteSource(e2, si.Source);
+
+      bool useNativeUnityBuild = false;
+      if (haveUnityBuild &&
+          this->GlobalGenerator->GetVersion() >=
+            cmGlobalVisualStudioGenerator::VS15) {
+        // Magic value taken from cmGlobalVisualStudioVersionedGenerator.cxx
+        static const std::string vs15 = "141";
+        std::string toolset =
+          this->GlobalGenerator->GetPlatformToolsetString();
+        cmSystemTools::ReplaceString(toolset, "v", "");
+
+        if (toolset.empty() ||
+            cmSystemTools::VersionCompareGreaterEq(toolset, vs15)) {
+          useNativeUnityBuild = true;
+        }
+      }
+
+      if (haveUnityBuild && strcmp(tool, "ClCompile") == 0 &&
+          si.Source->GetProperty("UNITY_SOURCE_FILE")) {
+        if (useNativeUnityBuild) {
+          e2.Attribute(
+            "IncludeInUnityFile",
+            si.Source->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION")
+              ? "false"
+              : "true");
+          e2.Attribute("CustomUnityFile", "true");
+
+          std::string unityDir = cmSystemTools::GetFilenamePath(
+            si.Source->GetProperty("UNITY_SOURCE_FILE"));
+          e2.Attribute("UnityFilesDirectory", unityDir);
+        } else {
+          // Visual Studio versions prior to 2017 do not know about unity
+          // builds, thus we exclude the files alredy part of unity sources.
+          if (!si.Source->GetPropertyAsBool("SKIP_UNITY_BUILD_INCLUSION")) {
+            exclude_configs = si.Configs;
+          }
+        }
+      }
+
       if (si.Kind == cmGeneratorTarget::SourceKindObjectSource) {
         this->OutputSourceSpecificFlags(e2, si.Source);
       }

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -576,3 +576,4 @@ endif()
 add_RunCMake_test("CTestCommandExpandLists")
 
 add_RunCMake_test(PrecompileHeaders)
+add_RunCMake_test("UnityBuild")

+ 3 - 0
Tests/RunCMake/UnityBuild/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.15)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 23 - 0
Tests/RunCMake/UnityBuild/RunCMakeTest.cmake

@@ -0,0 +1,23 @@
+include(RunCMake)
+
+run_cmake(unitybuild_c)
+run_cmake(unitybuild_cxx)
+run_cmake(unitybuild_c_and_cxx)
+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_order)
+
+function(run_test 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)
+  run_cmake_command(${name}-test ${CMAKE_CTEST_COMMAND} -C Debug)
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+endfunction()
+
+run_test(unitybuild_runtest)

+ 6 - 0
Tests/RunCMake/UnityBuild/func.c

@@ -0,0 +1,6 @@
+#include "func.h"
+
+int func(void)
+{
+  return 0;
+}

+ 6 - 0
Tests/RunCMake/UnityBuild/func.h

@@ -0,0 +1,6 @@
+#ifndef func_h
+#define func_h
+
+extern int func(void);
+
+#endif

+ 6 - 0
Tests/RunCMake/UnityBuild/main.c

@@ -0,0 +1,6 @@
+#include "func.h"
+
+int main(void)
+{
+  return func();
+}

+ 11 - 0
Tests/RunCMake/UnityBuild/unitybuild_batchsize-check.cmake

@@ -0,0 +1,11 @@
+set(unitybuild_c0 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+set(unitybuild_c1 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_1.c")
+if(NOT EXISTS "${unitybuild_c0}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c0} does not exist.")
+  return()
+endif()
+
+if(NOT EXISTS "${unitybuild_c1}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c1} does not exist.")
+  return()
+endif()

+ 16 - 0
Tests/RunCMake/UnityBuild/unitybuild_batchsize.cmake

@@ -0,0 +1,16 @@
+project(unitybuild_batchsize 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_BATCH_SIZE 4
+)

+ 5 - 0
Tests/RunCMake/UnityBuild/unitybuild_c-check.cmake

@@ -0,0 +1,5 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+if(NOT EXISTS "${unitybuild_c}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c} does not exist.")
+  return()
+endif()

+ 12 - 0
Tests/RunCMake/UnityBuild/unitybuild_c.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)

+ 11 - 0
Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx-check.cmake

@@ -0,0 +1,11 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+if(NOT EXISTS "${unitybuild_c}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c} does not exist.")
+  return()
+endif()
+
+set(unitybuild_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.cxx")
+if(NOT EXISTS "${unitybuild_cxx}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_cxx} does not exist.")
+  return()
+endif()

+ 17 - 0
Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx.cmake

@@ -0,0 +1,17 @@
+project(unitybuild_c_and_cxx C CXX)
+
+set(srcs "")
+foreach(s RANGE 1 8)
+  set(src_c "${CMAKE_CURRENT_BINARY_DIR}/s${s}.c")
+  file(WRITE "${src_c}" "int s${s}(void) { return 0; }\n")
+
+  set(src_cxx "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cxx")
+  file(WRITE "${src_cxx}" "int s${s}(void) { return 0; }\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)

+ 5 - 0
Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build-check.cmake

@@ -0,0 +1,5 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+if(EXISTS "${unitybuild_c}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c} should not exist.")
+  return()
+endif()

+ 10 - 0
Tests/RunCMake/UnityBuild/unitybuild_c_no_unity_build.cmake

@@ -0,0 +1,10 @@
+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})

+ 7 - 0
Tests/RunCMake/UnityBuild/unitybuild_code_before_and_after_include-check.cmake

@@ -0,0 +1,7 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+file(STRINGS ${unitybuild_c} unitybuild_c_strings)
+string(REGEX MATCH "#define NOMINMAX.*#include.*s1.c.*#undef NOMINMAX" matched_code ${unitybuild_c_strings})
+if(NOT matched_code)
+  set(RunCMake_TEST_FAILED "Generated unity file doesn't include expected code before and after include")
+  return()
+endif()

+ 13 - 0
Tests/RunCMake/UnityBuild/unitybuild_code_before_and_after_include.cmake

@@ -0,0 +1,13 @@
+project(unitybuild_code_before_and_after_include C)
+
+set(src "${CMAKE_CURRENT_BINARY_DIR}/s1.c")
+file(WRITE "${src}" "int s1(void) { return 0; }\n")
+
+add_library(tgt SHARED ${src})
+
+set_target_properties(tgt
+  PROPERTIES
+    UNITY_BUILD ON
+    UNITY_BUILD_CODE_BEFORE_INCLUDE "#define NOMINMAX"
+    UNITY_BUILD_CODE_AFTER_INCLUDE "#undef NOMINMAX"
+)

+ 5 - 0
Tests/RunCMake/UnityBuild/unitybuild_cxx-check.cmake

@@ -0,0 +1,5 @@
+set(unitybuild_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.cxx")
+if(NOT EXISTS "${unitybuild_cxx}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_cxx} does not exist.")
+  return()
+endif()

+ 12 - 0
Tests/RunCMake/UnityBuild/unitybuild_cxx.cmake

@@ -0,0 +1,12 @@
+project(unitybuild_cxx CXX)
+
+set(srcs "")
+foreach(s RANGE 1 8)
+  set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cxx")
+  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)

+ 7 - 0
Tests/RunCMake/UnityBuild/unitybuild_default_batchsize-check.cmake

@@ -0,0 +1,7 @@
+set(unitybuild_c0 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+file(STRINGS ${unitybuild_c0} unitybuild_c_strings REGEX "/s[0-9]+.c\"$" )
+list(LENGTH unitybuild_c_strings number_of_includes)
+if(NOT number_of_includes EQUAL 8)
+  set(RunCMake_TEST_FAILED "Generated unity doesn't include the expect number of files")
+  return()
+endif()

+ 15 - 0
Tests/RunCMake/UnityBuild/unitybuild_default_batchsize.cmake

@@ -0,0 +1,15 @@
+project(unitybuild_default_batchsize C)
+
+set(srcs "")
+foreach(s RANGE 1 10)
+  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
+)

+ 7 - 0
Tests/RunCMake/UnityBuild/unitybuild_order-check.cmake

@@ -0,0 +1,7 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+file(STRINGS ${unitybuild_c} unitybuild_c_strings)
+string(REGEX MATCH ".*#include.*s3.c.*#include.*s1.c.*#include.*s2.c.*" matched_code ${unitybuild_c_strings})
+if(NOT matched_code)
+  set(RunCMake_TEST_FAILED "Generated unity file doesn't include expected oder of source files")
+  return()
+endif()

+ 12 - 0
Tests/RunCMake/UnityBuild/unitybuild_order.cmake

@@ -0,0 +1,12 @@
+project(unitybuild_order C)
+
+set(srcs "")
+foreach(s 3 1 2)
+  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)

+ 8 - 0
Tests/RunCMake/UnityBuild/unitybuild_runtest.cmake

@@ -0,0 +1,8 @@
+project(unitybuild_runtest C)
+
+set(CMAKE_UNITY_BUILD ON) # This tests that the variable works in addition to the property
+
+add_executable(main main.c func.c)
+
+enable_testing()
+add_test(NAME main COMMAND main)

+ 14 - 0
Tests/RunCMake/UnityBuild/unitybuild_skip-check.cmake

@@ -0,0 +1,14 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+file(STRINGS ${unitybuild_c} unitybuild_c_strings)
+
+string(REGEX MATCH "\\/s[1-6].c" matched_files_1_6 ${unitybuild_c_strings})
+if(matched_files_1_6)
+  set(RunCMake_TEST_FAILED "Generated unity contains s1.c -> s6.c which should have been skipped")
+  return()
+endif()
+
+string(REGEX MATCH "\\/s[7-8].c" matched_files_7_8 ${unitybuild_c_strings})
+if(NOT matched_files_7_8)
+  set(RunCMake_TEST_FAILED "Generated unity should have contained s7.c, s8.c!")
+  return()
+endif()

+ 30 - 0
Tests/RunCMake/UnityBuild/unitybuild_skip.cmake

@@ -0,0 +1,30 @@
+project(unitybuild_skip 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)
+
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/s1.c
+  PROPERTIES GENERATED ON)
+
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/s2.c
+  PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON)
+
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/s3.c
+  PROPERTIES COMPILE_OPTIONS "val")
+
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/s4.c
+  PROPERTIES COMPILE_DEFINITIONS "val")
+
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/s5.c
+  PROPERTIES COMPILE_FLAGS "val")
+
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/s6.c
+  PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}")

+ 9 - 0
Tests/RunCMake/VS10Project/RunCMakeTest.cmake

@@ -34,3 +34,12 @@ endif()
 if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 19.20)
   run_cmake(VsSpectreMitigation)
 endif()
+
+# Visual Studio 2017 has toolset version 141
+string(REPLACE "v" "" generator_toolset "${RunCMake_GENERATOR_TOOLSET}")
+if (RunCMake_GENERATOR MATCHES "Visual Studio 1[0-4] 201[0-5]" OR
+   (RunCMake_GENERATOR_TOOLSET AND generator_toolset VERSION_LESS "141"))
+  run_cmake(UnityBuildPre2017)
+else()
+  run_cmake(UnityBuildNative)
+endif()

+ 45 - 0
Tests/RunCMake/VS10Project/UnityBuildNative-check.cmake

@@ -0,0 +1,45 @@
+set(unitybuild_c0 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+if(NOT EXISTS "${unitybuild_c0}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c0} 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\"")
+    set(unity_source_line ${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_line "${unity_source_line}")
+string(FIND "${unity_source_line}" "CMakeFiles/tgt.dir/Unity/unity_0.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 8)
+  set(RunCMake_TEST_FAILED "Generated project doesn't include the expect number of files.")
+  return()
+endif()

+ 12 - 0
Tests/RunCMake/VS10Project/UnityBuildNative.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)

+ 48 - 0
Tests/RunCMake/VS10Project/UnityBuildPre2017-check.cmake

@@ -0,0 +1,48 @@
+set(unitybuild_c0 "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0.c")
+if(NOT EXISTS "${unitybuild_c0}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c0} 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_0.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 8)
+  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 32)
+  set(RunCMake_TEST_FAILED "Generated project doesn't exclude the source files for all configurations.")
+  return()
+endif()

+ 12 - 0
Tests/RunCMake/VS10Project/UnityBuildPre2017.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)