1
0
Эх сурвалжийг харах

Merge topic 'ninja-depfile-transformation'

146e1e6ba1 Ninja: Transform DEPFILEs with policy CMP0116
596439b1bb cmCustomCommandGenerator: Add option to transform depfile
b2c14bc774 cmake -E: Add cmake_transform_depfile internal command
946adadd40 cmGccDepfileReader: Rework helper code

Acked-by: Kitware Robot <[email protected]>
Merge-request: !5325
Brad King 5 жил өмнө
parent
commit
c555959717
61 өөрчлөгдсөн 669 нэмэгдсэн , 68 устгасан
  1. 5 0
      Help/command/add_custom_command.rst
  2. 1 0
      Help/manual/cmake-policies.7.rst
  3. 38 0
      Help/policy/CMP0116.rst
  4. 5 0
      Help/release/dev/ninja-depfile-transformation.rst
  5. 2 0
      Help/variable/CMAKE_POLICY_WARNING_CMPNNNN.rst
  6. 2 0
      Source/CMakeLists.txt
  7. 1 1
      Source/LexerParser/cmGccDepfileLexer.cxx
  8. 1 1
      Source/LexerParser/cmGccDepfileLexer.in.l
  9. 71 2
      Source/cmCustomCommandGenerator.cxx
  10. 3 1
      Source/cmCustomCommandGenerator.h
  11. 22 11
      Source/cmGccDepfileLexerHelper.cxx
  12. 2 1
      Source/cmGccDepfileLexerHelper.h
  13. 5 4
      Source/cmGccDepfileReader.cxx
  14. 3 1
      Source/cmGccDepfileReader.h
  15. 6 0
      Source/cmGlobalGenerator.h
  16. 5 0
      Source/cmGlobalNinjaGenerator.h
  17. 43 2
      Source/cmLocalNinjaGenerator.cxx
  18. 4 1
      Source/cmPolicies.h
  19. 4 4
      Source/cmQtAutoMocUic.cxx
  20. 114 0
      Source/cmTransformDepfile.cxx
  21. 14 0
      Source/cmTransformDepfile.h
  22. 18 0
      Source/cmcmd.cxx
  23. 16 5
      Tests/CMakeLib/testGccDepfileReader.cxx
  24. 1 0
      Tests/CMakeLib/testGccDepfileReader_data/deps4.d
  25. 0 0
      Tests/CMakeLib/testGccDepfileReader_data/deps5.d
  26. 2 0
      Tests/CMakeLib/testGccDepfileReader_data/deps5.txt
  27. 6 0
      Tests/CMakeLib/testGccDepfileReader_data/deps7.d
  28. 10 0
      Tests/CMakeLib/testGccDepfileReader_data/deps7.txt
  29. 3 0
      Tests/RunCMake/CMP0116/CMP0116-NEW-NOWARN.cmake
  30. 3 0
      Tests/RunCMake/CMP0116/CMP0116-NEW-WARN.cmake
  31. 3 0
      Tests/RunCMake/CMP0116/CMP0116-OLD-NOWARN.cmake
  32. 3 0
      Tests/RunCMake/CMP0116/CMP0116-OLD-WARN.cmake
  33. 7 0
      Tests/RunCMake/CMP0116/CMP0116-WARN-NOWARN-stderr.txt
  34. 3 0
      Tests/RunCMake/CMP0116/CMP0116-WARN-NOWARN.cmake
  35. 16 0
      Tests/RunCMake/CMP0116/CMP0116-WARN-WARN-stderr.txt
  36. 3 0
      Tests/RunCMake/CMP0116/CMP0116-WARN-WARN.cmake
  37. 3 0
      Tests/RunCMake/CMP0116/CMakeLists.txt
  38. 8 0
      Tests/RunCMake/CMP0116/Common.cmake
  39. 49 0
      Tests/RunCMake/CMP0116/RunCMakeTest.cmake
  40. 6 0
      Tests/RunCMake/CMP0116/Subdirectory/CMakeLists.txt
  41. 3 0
      Tests/RunCMake/CMP0116/WriteDepfile.cmake
  42. 18 0
      Tests/RunCMake/CMP0116/check.cmake
  43. 4 0
      Tests/RunCMake/CMakeLists.txt
  44. 21 0
      Tests/RunCMake/TransformDepfile/RunCMakeTest.cmake
  45. 6 0
      Tests/RunCMake/TransformDepfile/deps-unix.d
  46. 8 0
      Tests/RunCMake/TransformDepfile/deps-unix.d.txt
  47. 6 0
      Tests/RunCMake/TransformDepfile/deps-unix.tlog.txt
  48. 6 0
      Tests/RunCMake/TransformDepfile/deps-windows.d
  49. 8 0
      Tests/RunCMake/TransformDepfile/deps-windows.d.txt
  50. 6 0
      Tests/RunCMake/TransformDepfile/deps-windows.tlog.txt
  51. 0 0
      Tests/RunCMake/TransformDepfile/empty.d
  52. 0 0
      Tests/RunCMake/TransformDepfile/empty.d.txt
  53. 0 0
      Tests/RunCMake/TransformDepfile/empty.tlog.txt
  54. 16 0
      Tests/RunCMake/TransformDepfile/gccdepfile.cmake
  55. 1 0
      Tests/RunCMake/TransformDepfile/invalid-gcc-result.txt
  56. 1 0
      Tests/RunCMake/TransformDepfile/invalid-tlog-result.txt
  57. 1 0
      Tests/RunCMake/TransformDepfile/invalid.d
  58. 0 0
      Tests/RunCMake/TransformDepfile/noexist.d.txt
  59. 0 0
      Tests/RunCMake/TransformDepfile/noexist.tlog.txt
  60. 16 0
      Tests/RunCMake/TransformDepfile/vstlog.cmake
  61. 37 34
      bootstrap

+ 5 - 0
Help/command/add_custom_command.rst

@@ -239,6 +239,11 @@ The options are:
   command itself.
   Using ``DEPFILE`` with other generators than Ninja is an error.
 
+  If the ``DEPFILE`` argument is relative, it should be relative to
+  :variable:`CMAKE_CURRENT_BINARY_DIR`, and any relative paths inside the
+  ``DEPFILE`` should also be relative to :variable:`CMAKE_CURRENT_BINARY_DIR`
+  (see policy :policy:`CMP0116`.)
+
 Build Events
 ^^^^^^^^^^^^
 

+ 1 - 0
Help/manual/cmake-policies.7.rst

@@ -57,6 +57,7 @@ Policies Introduced by CMake 3.20
 .. toctree::
    :maxdepth: 1
 
+   CMP0116: Ninja generators transform DEPFILEs from add_custom_command(). </policy/CMP0116>
    CMP0115: Source file extensions must be explicit. </policy/CMP0115>
 
 Policies Introduced by CMake 3.19

+ 38 - 0
Help/policy/CMP0116.rst

@@ -0,0 +1,38 @@
+CMP0116
+-------
+
+.. versionadded:: 3.20
+
+Ninja generators transform ``DEPFILE`` s from :command:`add_custom_command`.
+
+In CMake 3.19 and below, files given to the ``DEPFILE`` argument of
+:command:`add_custom_command` were passed directly to Ninja's ``depfile``
+variable without any path resolution. This meant that if
+:command:`add_custom_command` was called from a subdirectory (created by
+:command:`add_subdirectory`), the ``DEPFILE`` argument would have to be either
+an absolute path or a path relative to :variable:`CMAKE_BINARY_DIR`, rather
+than :variable:`CMAKE_CURRENT_BINARY_DIR`. In addition, no transformation was
+done on the file listed in ``DEPFILE``, which meant that the paths within the
+``DEPFILE`` had the same restrictions.
+
+Starting with CMake 3.20, the ``DEPFILE`` argument is relative to
+:variable:`CMAKE_CURRENT_BINARY_DIR` (unless it is absolute), and the paths in
+the ``DEPFILE`` are also relative to :variable:`CMAKE_CURRENT_BINARY_DIR`.
+CMake automatically transforms the paths in the ``DEPFILE`` (unless they are
+absolute) after the custom command is run. The file listed in ``DEPFILE`` is
+not modified in any way. Instead, CMake writes the transformation to its own
+internal file, and passes this internal file to Ninja's ``depfile`` variable.
+This transformation happens regardless of whether or not ``DEPFILE`` is
+relative, and regardless of whether or not :command:`add_custom_command` is
+called from a subdirectory.
+
+The ``OLD`` behavior for this policy is to pass the ``DEPFILE`` to Ninja
+unaltered. The ``NEW`` behavior for this policy is to transform the ``DEPFILE``
+after running the custom command.
+
+This policy was introduced in CMake version 3.20.  Unlike most policies,
+CMake version |release| does *not* warn by default when this policy is not set
+(unless ``DEPFILE`` is used in a subdirectory) and simply uses ``OLD``
+behavior.  See documentation of the
+:variable:`CMAKE_POLICY_WARNING_CMP0116 <CMAKE_POLICY_WARNING_CMP<NNNN>>`
+variable to control the warning.

+ 5 - 0
Help/release/dev/ninja-depfile-transformation.rst

@@ -0,0 +1,5 @@
+ninja-depfile-transformation
+----------------------------
+
+* Ninja generators now transform ``DEPFILE`` s from
+  :command:`add_custom_command`. See policy :policy:`CMP0116` for details.

+ 2 - 0
Help/variable/CMAKE_POLICY_WARNING_CMPNNNN.rst

@@ -27,6 +27,8 @@ warn by default:
   policy :policy:`CMP0102`.
 * ``CMAKE_POLICY_WARNING_CMP0112`` controls the warning for
   policy :policy:`CMP0112`.
+* ``CMAKE_POLICY_WARNING_CMP0116`` controls the warning for
+  policy :policy:`CMP0116`.
 
 This variable should not be set by a project in CMake code.  Project
 developers running CMake may set this variable in their cache to

+ 2 - 0
Source/CMakeLists.txt

@@ -439,6 +439,8 @@ set(SRCS
   cmTest.h
   cmTestGenerator.cxx
   cmTestGenerator.h
+  cmTransformDepfile.cxx
+  cmTransformDepfile.h
   cmUuid.cxx
   cmUVHandlePtr.cxx
   cmUVHandlePtr.h

+ 1 - 1
Source/LexerParser/cmGccDepfileLexer.cxx

@@ -994,7 +994,7 @@ case 5:
 YY_RULE_SETUP
 {
                          // A line continuation ends the current file name.
-                         yyextra->newDependency();
+                         yyextra->newRuleOrDependency();
                        }
 	YY_BREAK
 case 6:

+ 1 - 1
Source/LexerParser/cmGccDepfileLexer.in.l

@@ -42,7 +42,7 @@ NEWLINE \r?\n
                        }
 {WSPACE}*\\{NEWLINE}   {
                          // A line continuation ends the current file name.
-                         yyextra->newDependency();
+                         yyextra->newRuleOrDependency();
                        }
 {NEWLINE}              {
                          // A newline ends the current file name and the current rule.

+ 71 - 2
Source/cmCustomCommandGenerator.cxx

@@ -6,18 +6,22 @@
 #include <memory>
 #include <utility>
 
+#include <cm/optional>
 #include <cmext/algorithm>
 
+#include "cmCryptoHash.h"
 #include "cmCustomCommand.h"
 #include "cmCustomCommandLines.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmProperty.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cmTransformDepfile.h"
 
 namespace {
 void AppendPaths(const std::vector<std::string>& inputs,
@@ -42,7 +46,8 @@ void AppendPaths(const std::vector<std::string>& inputs,
 
 cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
                                                    std::string config,
-                                                   cmLocalGenerator* lg)
+                                                   cmLocalGenerator* lg,
+                                                   bool transformDepfile)
   : CC(cc)
   , Config(std::move(config))
   , LG(lg)
@@ -75,6 +80,36 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
     this->CommandLines.push_back(std::move(argv));
   }
 
+  if (transformDepfile && !this->CommandLines.empty() &&
+      !cc.GetDepfile().empty() &&
+      this->LG->GetGlobalGenerator()->DepfileFormat()) {
+    cmCustomCommandLine argv;
+    argv.push_back(cmSystemTools::GetCMakeCommand());
+    argv.emplace_back("-E");
+    argv.emplace_back("cmake_transform_depfile");
+    switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) {
+      case cmDepfileFormat::GccDepfile:
+        argv.emplace_back("gccdepfile");
+        break;
+      case cmDepfileFormat::VsTlog:
+        argv.emplace_back("vstlog");
+        break;
+    }
+    if (this->LG->GetCurrentBinaryDirectory() ==
+        this->LG->GetBinaryDirectory()) {
+      argv.emplace_back("./");
+    } else {
+      argv.push_back(cmStrCat(this->LG->MaybeConvertToRelativePath(
+                                this->LG->GetBinaryDirectory(),
+                                this->LG->GetCurrentBinaryDirectory()),
+                              '/'));
+    }
+    argv.push_back(this->GetFullDepfile());
+    argv.push_back(this->GetInternalDepfile());
+
+    this->CommandLines.push_back(std::move(argv));
+  }
+
   AppendPaths(cc.GetByproducts(), ge, this->LG, this->Config,
               this->Byproducts);
   AppendPaths(cc.GetDepends(), ge, this->LG, this->Config, this->Depends);
@@ -97,7 +132,7 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(cmCustomCommand const& cc,
 
 unsigned int cmCustomCommandGenerator::GetNumberOfCommands() const
 {
-  return static_cast<unsigned int>(this->CC.GetCommandLines().size());
+  return static_cast<unsigned int>(this->CommandLines.size());
 }
 
 void cmCustomCommandGenerator::FillEmulatorsWithArguments()
@@ -234,6 +269,40 @@ void cmCustomCommandGenerator::AppendArguments(unsigned int c,
   }
 }
 
+std::string cmCustomCommandGenerator::GetFullDepfile() const
+{
+  std::string depfile = this->CC.GetDepfile();
+  if (depfile.empty()) {
+    return "";
+  }
+
+  if (!cmSystemTools::FileIsFullPath(depfile)) {
+    depfile = cmStrCat(this->LG->GetCurrentBinaryDirectory(), '/', depfile);
+  }
+  return cmSystemTools::CollapseFullPath(depfile);
+}
+
+std::string cmCustomCommandGenerator::GetInternalDepfile() const
+{
+  std::string depfile = this->GetFullDepfile();
+  if (depfile.empty()) {
+    return "";
+  }
+
+  cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
+  std::string extension;
+  switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) {
+    case cmDepfileFormat::GccDepfile:
+      extension = ".d";
+      break;
+    case cmDepfileFormat::VsTlog:
+      extension = ".tlog";
+      break;
+  }
+  return cmStrCat(this->LG->GetBinaryDirectory(), "/CMakeFiles/d/",
+                  hash.HashString(depfile), extension);
+}
+
 const char* cmCustomCommandGenerator::GetComment() const
 {
   return this->CC.GetComment();

+ 3 - 1
Source/cmCustomCommandGenerator.h

@@ -31,7 +31,7 @@ class cmCustomCommandGenerator
 
 public:
   cmCustomCommandGenerator(cmCustomCommand const& cc, std::string config,
-                           cmLocalGenerator* lg);
+                           cmLocalGenerator* lg, bool transformDepfile = true);
   cmCustomCommandGenerator(const cmCustomCommandGenerator&) = delete;
   cmCustomCommandGenerator& operator=(const cmCustomCommandGenerator&) =
     delete;
@@ -45,4 +45,6 @@ public:
   std::vector<std::string> const& GetByproducts() const;
   std::vector<std::string> const& GetDepends() const;
   bool HasOnlyEmptyCommandLines() const;
+  std::string GetFullDepfile() const;
+  std::string GetInternalDepfile() const;
 };

+ 22 - 11
Source/cmGccDepfileLexerHelper.cxx

@@ -27,23 +27,30 @@ bool cmGccDepfileLexerHelper::readFile(const char* filePath)
   if (!file) {
     return false;
   }
-  newEntry();
+  this->newEntry();
   yyscan_t scanner;
   cmGccDepfile_yylex_init(&scanner);
   cmGccDepfile_yyset_extra(this, scanner);
   cmGccDepfile_yyrestart(file, scanner);
   cmGccDepfile_yylex(scanner);
   cmGccDepfile_yylex_destroy(scanner);
-  sanitizeContent();
+  this->sanitizeContent();
   fclose(file);
-  return true;
+  return this->HelperState != State::Failed;
 }
 
 void cmGccDepfileLexerHelper::newEntry()
 {
+  if (this->HelperState == State::Rule && !this->Content.empty()) {
+    if (!this->Content.back().rules.empty() &&
+        !this->Content.back().rules.back().empty()) {
+      this->HelperState = State::Failed;
+    }
+    return;
+  }
   this->HelperState = State::Rule;
   this->Content.emplace_back();
-  newRule();
+  this->newRule();
 }
 
 void cmGccDepfileLexerHelper::newRule()
@@ -56,20 +63,22 @@ void cmGccDepfileLexerHelper::newRule()
 
 void cmGccDepfileLexerHelper::newDependency()
 {
-  // printf("NEW DEP\n");
+  if (this->HelperState == State::Failed) {
+    return;
+  }
   this->HelperState = State::Dependency;
-  if (this->Content.back().paths.empty() ||
-      !this->Content.back().paths.back().empty()) {
-    this->Content.back().paths.emplace_back();
+  auto& entry = this->Content.back();
+  if (entry.paths.empty() || !entry.paths.back().empty()) {
+    entry.paths.emplace_back();
   }
 }
 
 void cmGccDepfileLexerHelper::newRuleOrDependency()
 {
   if (this->HelperState == State::Rule) {
-    newRule();
-  } else {
-    newDependency();
+    this->newRule();
+  } else if (this->HelperState == State::Dependency) {
+    this->newDependency();
   }
 }
 
@@ -93,6 +102,8 @@ void cmGccDepfileLexerHelper::addToCurrentPath(const char* s)
       }
       dst = &dep->paths.back();
     } break;
+    case State::Failed:
+      return;
   }
   dst->append(s);
 }

+ 2 - 1
Source/cmGccDepfileLexerHelper.h

@@ -29,7 +29,8 @@ private:
   enum class State
   {
     Rule,
-    Dependency
+    Dependency,
+    Failed,
   };
   State HelperState = State::Rule;
 };

+ 5 - 4
Source/cmGccDepfileReader.cxx

@@ -5,14 +5,15 @@
 #include <type_traits>
 #include <utility>
 
+#include <cm/optional>
+
 #include "cmGccDepfileLexerHelper.h"
 
-cmGccDepfileContent cmReadGccDepfile(const char* filePath)
+cm::optional<cmGccDepfileContent> cmReadGccDepfile(const char* filePath)
 {
-  cmGccDepfileContent result;
   cmGccDepfileLexerHelper helper;
   if (helper.readFile(filePath)) {
-    result = std::move(helper).extractContent();
+    return cm::make_optional(std::move(helper).extractContent());
   }
-  return result;
+  return cm::nullopt;
 }

+ 3 - 1
Source/cmGccDepfileReader.h

@@ -2,6 +2,8 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #pragma once
 
+#include <cm/optional>
+
 #include "cmGccDepfileReaderTypes.h"
 
-cmGccDepfileContent cmReadGccDepfile(const char* filePath);
+cm::optional<cmGccDepfileContent> cmReadGccDepfile(const char* filePath);

+ 6 - 0
Source/cmGlobalGenerator.h

@@ -14,6 +14,7 @@
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
 #include <cmext/algorithm>
 
 #include "cm_codecvt.hxx"
@@ -26,6 +27,7 @@
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmTargetDepend.h"
+#include "cmTransformDepfile.h"
 
 #if !defined(CMAKE_BOOTSTRAP)
 #  include <cm3p/json/value.h>
@@ -452,6 +454,10 @@ public:
   virtual bool ShouldStripResourcePath(cmMakefile*) const;
 
   virtual bool SupportsCustomCommandDepfile() const { return false; }
+  virtual cm::optional<cmDepfileFormat> DepfileFormat() const
+  {
+    return cm::nullopt;
+  }
 
   std::string GetSharedLibFlagsForLanguage(std::string const& lang) const;
 

+ 5 - 0
Source/cmGlobalNinjaGenerator.h

@@ -24,6 +24,7 @@
 #include "cmNinjaTypes.h"
 #include "cmPolicies.h"
 #include "cmStringAlgorithms.h"
+#include "cmTransformDepfile.h"
 
 class cmCustomCommand;
 class cmGeneratorTarget;
@@ -210,6 +211,10 @@ public:
   const char* GetCleanTargetName() const override { return "clean"; }
 
   bool SupportsCustomCommandDepfile() const override { return true; }
+  cm::optional<cmDepfileFormat> DepfileFormat() const override
+  {
+    return cmDepfileFormat::GccDepfile;
+  }
 
   virtual cmGeneratedFileStream* GetImplFileStream(
     const std::string& /*config*/) const

+ 43 - 2
Source/cmLocalNinjaGenerator.cxx

@@ -22,7 +22,9 @@
 #include "cmGlobalNinjaGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
+#include "cmMessageType.h"
 #include "cmNinjaTargetGenerator.h"
+#include "cmPolicies.h"
 #include "cmProperty.h"
 #include "cmRulePlaceholderExpander.h"
 #include "cmSourceFile.h"
@@ -573,7 +575,20 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
     return;
   }
 
-  cmCustomCommandGenerator ccg(*cc, config, this);
+  bool transformDepfile = false;
+  auto cmp0116 = this->GetPolicyStatus(cmPolicies::CMP0116);
+  switch (cmp0116) {
+    case cmPolicies::OLD:
+    case cmPolicies::WARN:
+      break;
+    case cmPolicies::REQUIRED_IF_USED:
+    case cmPolicies::REQUIRED_ALWAYS:
+    case cmPolicies::NEW:
+      transformDepfile = true;
+      break;
+  }
+
+  cmCustomCommandGenerator ccg(*cc, config, this, transformDepfile);
 
   const std::vector<std::string>& outputs = ccg.GetOutputs();
   const std::vector<std::string>& byproducts = ccg.GetByproducts();
@@ -623,10 +638,36 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
     cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
     customStep += hash.HashString(ninjaOutputs[0]).substr(0, 7);
 
+    std::string depfile = cc->GetDepfile();
+    if (!depfile.empty()) {
+      switch (cmp0116) {
+        case cmPolicies::WARN:
+          if (this->GetCurrentBinaryDirectory() !=
+                this->GetBinaryDirectory() ||
+              this->Makefile->PolicyOptionalWarningEnabled(
+                "CMAKE_POLICY_WARNING_CMP0116")) {
+            this->GetCMakeInstance()->IssueMessage(
+              MessageType::AUTHOR_WARNING,
+              cmPolicies::GetPolicyWarning(cmPolicies::CMP0116),
+              cc->GetBacktrace());
+          }
+          CM_FALLTHROUGH;
+        case cmPolicies::OLD:
+          break;
+        case cmPolicies::REQUIRED_IF_USED:
+        case cmPolicies::REQUIRED_ALWAYS:
+        case cmPolicies::NEW:
+          cmSystemTools::MakeDirectory(
+            cmStrCat(this->GetBinaryDirectory(), "/CMakeFiles/d"));
+          depfile = ccg.GetInternalDepfile();
+          break;
+      }
+    }
+
     gg->WriteCustomCommandBuild(
       this->BuildCommandLine(cmdLines, customStep),
       this->ConstructComment(ccg), "Custom command for " + ninjaOutputs[0],
-      cc->GetDepfile(), cc->GetJobPool(), cc->GetUsesTerminal(),
+      depfile, cc->GetJobPool(), cc->GetUsesTerminal(),
       /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, config,
       ninjaDeps, orderOnlyDeps);
   }

+ 4 - 1
Source/cmPolicies.h

@@ -342,7 +342,10 @@ class cmMakefile;
          "ExternalProject step targets fully adopt their steps.", 3, 19, 0,   \
          cmPolicies::WARN)                                                    \
   SELECT(POLICY, CMP0115, "Source file extensions must be explicit.", 3, 20,  \
-         0, cmPolicies::WARN)
+         0, cmPolicies::WARN)                                                 \
+  SELECT(POLICY, CMP0116,                                                     \
+         "Ninja generators transform DEPFILEs from add_custom_command().", 3, \
+         20, 0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \

+ 4 - 4
Source/cmQtAutoMocUic.cxx

@@ -15,6 +15,7 @@
 #include <vector>
 
 #include <cm/memory>
+#include <cm/optional>
 #include <cm/string_view>
 #include <cmext/algorithm>
 
@@ -26,7 +27,6 @@
 #include "cmCryptoHash.h"
 #include "cmFileTime.h"
 #include "cmGccDepfileReader.h"
-#include "cmGccDepfileReaderTypes.h"
 #include "cmGeneratedFileStream.h"
 #include "cmQtAutoGen.h"
 #include "cmQtAutoGenerator.h"
@@ -2841,14 +2841,14 @@ bool cmQtAutoMocUicT::CreateDirectories()
 std::vector<std::string> cmQtAutoMocUicT::dependenciesFromDepFile(
   const char* filePath)
 {
-  cmGccDepfileContent content = cmReadGccDepfile(filePath);
-  if (content.empty()) {
+  auto const content = cmReadGccDepfile(filePath);
+  if (!content || content->empty()) {
     return {};
   }
 
   // Moc outputs a depfile with exactly one rule.
   // Discard the rule and return the dependencies.
-  return content.front().paths;
+  return content->front().paths;
 }
 
 void cmQtAutoMocUicT::Abort(bool error)

+ 114 - 0
Source/cmTransformDepfile.cxx

@@ -0,0 +1,114 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmTransformDepfile.h"
+
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <cm/optional>
+
+#include "cmsys/FStream.hxx"
+
+#include "cmGccDepfileReader.h"
+#include "cmGccDepfileReaderTypes.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+namespace {
+void WriteFilenameGcc(cmsys::ofstream& fout, const std::string& filename)
+{
+  for (auto c : filename) {
+    switch (c) {
+      case ' ':
+        fout << "\\ ";
+        break;
+      case '\\':
+        fout << "\\\\";
+        break;
+      default:
+        fout << c;
+        break;
+    }
+  }
+}
+
+void WriteGccDepfile(cmsys::ofstream& fout, const cmGccDepfileContent& content)
+{
+  for (auto const& dep : content) {
+    bool first = true;
+    for (auto const& rule : dep.rules) {
+      if (!first) {
+        fout << " \\\n  ";
+      }
+      first = false;
+      WriteFilenameGcc(fout, rule);
+    }
+    fout << ':';
+    for (auto const& path : dep.paths) {
+      fout << " \\\n  " << path;
+    }
+    fout << '\n';
+  }
+}
+
+void WriteVsTlog(cmsys::ofstream& fout, const cmGccDepfileContent& content)
+{
+  for (auto const& dep : content) {
+    fout << '^';
+    bool first = true;
+    for (auto const& rule : dep.rules) {
+      if (!first) {
+        fout << '|';
+      }
+      first = false;
+      fout << cmSystemTools::ConvertToOutputPath(rule);
+    }
+    fout << "\r\n";
+    for (auto const& path : dep.paths) {
+      fout << cmSystemTools::ConvertToOutputPath(path) << "\r\n";
+    }
+  }
+}
+}
+
+bool cmTransformDepfile(cmDepfileFormat format, const std::string& prefix,
+                        const std::string& infile, const std::string& outfile)
+{
+  cmGccDepfileContent content;
+  if (cmSystemTools::FileExists(infile)) {
+    auto result = cmReadGccDepfile(infile.c_str());
+    if (!result) {
+      return false;
+    }
+    content = *std::move(result);
+  }
+
+  for (auto& dep : content) {
+    for (auto& rule : dep.rules) {
+      if (!cmSystemTools::FileIsFullPath(rule)) {
+        rule = cmStrCat(prefix, rule);
+      }
+    }
+    for (auto& path : dep.paths) {
+      if (!cmSystemTools::FileIsFullPath(path)) {
+        path = cmStrCat(prefix, path);
+      }
+    }
+  }
+
+  cmsys::ofstream fout(outfile.c_str());
+  if (!fout) {
+    return false;
+  }
+  switch (format) {
+    case cmDepfileFormat::GccDepfile:
+      WriteGccDepfile(fout, content);
+      break;
+    case cmDepfileFormat::VsTlog:
+      WriteVsTlog(fout, content);
+      break;
+  }
+  return true;
+}

+ 14 - 0
Source/cmTransformDepfile.h

@@ -0,0 +1,14 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <string>
+
+enum class cmDepfileFormat
+{
+  GccDepfile,
+  VsTlog,
+};
+
+bool cmTransformDepfile(cmDepfileFormat format, const std::string& prefix,
+                        const std::string& infile, const std::string& outfile);

+ 18 - 0
Source/cmcmd.cxx

@@ -19,6 +19,7 @@
 #include "cmStateSnapshot.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cmTransformDepfile.h"
 #include "cmUVProcessChain.h"
 #include "cmUtils.hxx"
 #include "cmVersion.h"
@@ -1426,6 +1427,23 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args)
       return cmcmd::WindowsCEEnvironment("9.0", args[2]);
     }
 #endif
+
+    // Internal depfile transformation
+    if (args[1] == "cmake_transform_depfile" && args.size() == 6) {
+      auto format = cmDepfileFormat::GccDepfile;
+      if (args[2] == "gccdepfile") {
+        format = cmDepfileFormat::GccDepfile;
+      } else if (args[2] == "vstlog") {
+        format = cmDepfileFormat::VsTlog;
+      } else {
+        return 1;
+      }
+      std::string prefix = args[3];
+      if (prefix == "./") {
+        prefix.clear();
+      }
+      return cmTransformDepfile(format, prefix, args[4], args[5]) ? 0 : 1;
+    }
   }
 
   ::CMakeCommandUsage(args[0].c_str());

+ 16 - 5
Tests/CMakeLib/testGccDepfileReader.cxx

@@ -5,6 +5,8 @@
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
+
 #include "cmsys/FStream.hxx"
 
 #include "cmGccDepfileReader.h"
@@ -112,17 +114,26 @@ int testGccDepfileReader(int argc, char* argv[])
 
   std::string dataDirPath = argv[1];
   dataDirPath += "/testGccDepfileReader_data";
-  const int numberOfTestFiles = 3;
+  const int numberOfTestFiles = 7; // 6th file doesn't exist
   for (int i = 1; i <= numberOfTestFiles; ++i) {
     const std::string base = dataDirPath + "/deps" + std::to_string(i);
     const std::string depfile = base + ".d";
     const std::string plainDepfile = base + ".txt";
     std::cout << "Comparing " << base << " with " << plainDepfile << std::endl;
     const auto actual = cmReadGccDepfile(depfile.c_str());
-    const auto expected = readPlainDepfile(plainDepfile.c_str());
-    if (!compare(actual, expected)) {
-      dump("actual", actual);
-      dump("expected", expected);
+    if (cmSystemTools::FileExists(plainDepfile)) {
+      if (!actual) {
+        std::cerr << "Reading " << depfile << " should have succeeded\n";
+        return 1;
+      }
+      const auto expected = readPlainDepfile(plainDepfile.c_str());
+      if (!compare(*actual, expected)) {
+        dump("actual", *actual);
+        dump("expected", expected);
+        return 1;
+      }
+    } else if (actual) {
+      std::cerr << "Reading " << depfile << " should have failed\n";
       return 1;
     }
   }

+ 1 - 0
Tests/CMakeLib/testGccDepfileReader_data/deps4.d

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

+ 0 - 0
Tests/CMakeLib/testGccDepfileReader_data/deps5.d


+ 2 - 0
Tests/CMakeLib/testGccDepfileReader_data/deps5.txt

@@ -0,0 +1,2 @@
+--RULES--
+--DEPENDENCIES--

+ 6 - 0
Tests/CMakeLib/testGccDepfileReader_data/deps7.d

@@ -0,0 +1,6 @@
+out1 \
+  out2: \
+  in1 \
+  in2
+
+out3: in3

+ 10 - 0
Tests/CMakeLib/testGccDepfileReader_data/deps7.txt

@@ -0,0 +1,10 @@
+--RULES--
+out1
+out2
+--DEPENDENCIES--
+in1
+in2
+--RULES--
+out3
+--DEPENDENCIES--
+in3

+ 3 - 0
Tests/RunCMake/CMP0116/CMP0116-NEW-NOWARN.cmake

@@ -0,0 +1,3 @@
+set(depdir)
+
+include(Common.cmake)

+ 3 - 0
Tests/RunCMake/CMP0116/CMP0116-NEW-WARN.cmake

@@ -0,0 +1,3 @@
+set(depdir)
+
+include(Common.cmake)

+ 3 - 0
Tests/RunCMake/CMP0116/CMP0116-OLD-NOWARN.cmake

@@ -0,0 +1,3 @@
+set(depdir Subdirectory/)
+
+include(Common.cmake)

+ 3 - 0
Tests/RunCMake/CMP0116/CMP0116-OLD-WARN.cmake

@@ -0,0 +1,3 @@
+set(depdir Subdirectory/)
+
+include(Common.cmake)

+ 7 - 0
Tests/RunCMake/CMP0116/CMP0116-WARN-NOWARN-stderr.txt

@@ -0,0 +1,7 @@
+^(CMake Warning \(dev\) at Subdirectory/CMakeLists\.txt:[0-9]+ \(add_custom_command\):
+  Policy CMP0116 is not set: Ninja generators transform DEPFILEs from
+  add_custom_command\(\)\.  Run "cmake --help-policy CMP0116" for policy
+  details\.  Use the cmake_policy command to set the policy and suppress this
+  warning\.
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
+*)+$

+ 3 - 0
Tests/RunCMake/CMP0116/CMP0116-WARN-NOWARN.cmake

@@ -0,0 +1,3 @@
+set(depdir Subdirectory/)
+
+include(Common.cmake)

+ 16 - 0
Tests/RunCMake/CMP0116/CMP0116-WARN-WARN-stderr.txt

@@ -0,0 +1,16 @@
+^(CMake Warning \(dev\) at Common\.cmake:[0-9]+ \(add_custom_command\):
+  Policy CMP0116 is not set: Ninja generators transform DEPFILEs from
+  add_custom_command\(\)\.  Run "cmake --help-policy CMP0116" for policy
+  details\.  Use the cmake_policy command to set the policy and suppress this
+  warning\.
+Call Stack \(most recent call first\):
+  CMP0116-WARN-WARN\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
++)+(CMake Warning \(dev\) at Subdirectory/CMakeLists\.txt:[0-9]+ \(add_custom_command\):
+  Policy CMP0116 is not set: Ninja generators transform DEPFILEs from
+  add_custom_command\(\)\.  Run "cmake --help-policy CMP0116" for policy
+  details\.  Use the cmake_policy command to set the policy and suppress this
+  warning\.
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
+*)+$

+ 3 - 0
Tests/RunCMake/CMP0116/CMP0116-WARN-WARN.cmake

@@ -0,0 +1,3 @@
+set(depdir Subdirectory/)
+
+include(Common.cmake)

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

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

+ 8 - 0
Tests/RunCMake/CMP0116/Common.cmake

@@ -0,0 +1,8 @@
+add_custom_command(
+  OUTPUT top.txt
+  COMMAND ${CMAKE_COMMAND} -DOUTFILE=top.txt -DINFILE=topdep.txt -DDEPFILE=top.txt.d -DSTAMPFILE=topstamp.txt -DDEPDIR= -P ${CMAKE_SOURCE_DIR}/WriteDepfile.cmake
+  DEPFILE top.txt.d
+  )
+add_custom_target(top ALL DEPENDS top.txt)
+
+add_subdirectory(Subdirectory)

+ 49 - 0
Tests/RunCMake/CMP0116/RunCMakeTest.cmake

@@ -0,0 +1,49 @@
+include(RunCMake)
+
+function(run_cmp0116 status warn)
+  if(warn)
+    set(name CMP0116-${status}-WARN)
+  else()
+    set(name CMP0116-${status}-NOWARN)
+  endif()
+  set(RunCMake_TEST_OPTIONS
+    -DCMAKE_POLICY_WARNING_CMP0116:BOOL=${warn}
+    )
+  if(NOT status STREQUAL "WARN")
+    list(APPEND RunCMake_TEST_OPTIONS
+      -DCMAKE_POLICY_DEFAULT_CMP0116:STRING=${status}
+      )
+  endif()
+
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
+  run_cmake(${name})
+  unset(RunCMake_TEST_OPTIONS)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake-check-file check.cmake)
+
+  file(TOUCH "${RunCMake_TEST_BINARY_DIR}/topdep.txt")
+  file(TOUCH "${RunCMake_TEST_BINARY_DIR}/Subdirectory/subdep.txt")
+  set(cmp0116_step 1)
+  run_cmake_command(${name}-build1 ${CMAKE_COMMAND} --build . --config Debug)
+  file(REMOVE "${RunCMake_TEST_BINARY_DIR}/topstamp.txt")
+  file(REMOVE "${RunCMake_TEST_BINARY_DIR}/Subdirectory/substamp.txt")
+  execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 1.25)
+
+  file(TOUCH "${RunCMake_TEST_BINARY_DIR}/topdep.txt")
+  file(TOUCH "${RunCMake_TEST_BINARY_DIR}/Subdirectory/subdep.txt")
+  set(cmp0116_step 2)
+  run_cmake_command(${name}-build2 ${CMAKE_COMMAND} --build . --config Debug)
+  file(REMOVE "${RunCMake_TEST_BINARY_DIR}/topstamp.txt")
+  file(REMOVE "${RunCMake_TEST_BINARY_DIR}/Subdirectory/substamp.txt")
+  execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 1.25)
+
+  set(cmp0116_step 3)
+  run_cmake_command(${name}-build3 ${CMAKE_COMMAND} --build . --config Debug)
+endfunction()
+
+run_cmp0116(WARN OFF)
+run_cmp0116(OLD OFF)
+run_cmp0116(NEW OFF)
+run_cmp0116(WARN ON)
+run_cmp0116(OLD ON)
+run_cmp0116(NEW ON)

+ 6 - 0
Tests/RunCMake/CMP0116/Subdirectory/CMakeLists.txt

@@ -0,0 +1,6 @@
+add_custom_command(
+  OUTPUT sub.txt
+  COMMAND ${CMAKE_COMMAND} -DOUTFILE=sub.txt -DINFILE=subdep.txt -DDEPFILE=sub.txt.d -DSTAMPFILE=substamp.txt -DDEPDIR=${depdir} -P ${CMAKE_SOURCE_DIR}/WriteDepfile.cmake
+  DEPFILE ${depdir}sub.txt.d
+  )
+add_custom_target(sub ALL DEPENDS sub.txt)

+ 3 - 0
Tests/RunCMake/CMP0116/WriteDepfile.cmake

@@ -0,0 +1,3 @@
+file(TOUCH "${OUTFILE}")
+file(TOUCH "${STAMPFILE}")
+file(WRITE "${DEPFILE}" "${DEPDIR}${OUTFILE}: ${DEPDIR}${INFILE}\n")

+ 18 - 0
Tests/RunCMake/CMP0116/check.cmake

@@ -0,0 +1,18 @@
+function(check_exists file)
+  if(NOT EXISTS "${file}")
+    string(APPEND RunCMake_TEST_FAILED "${file} does not exist\n")
+  endif()
+  set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+function(check_not_exists file)
+  if(EXISTS "${file}")
+    string(APPEND RunCMake_TEST_FAILED "${file} exists\n")
+  endif()
+  set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+if(cmp0116_step EQUAL 3)
+  check_not_exists("${RunCMake_TEST_BINARY_DIR}/topstamp.txt")
+  check_not_exists("${RunCMake_TEST_BINARY_DIR}/Subdirectory/substamp.txt")
+endif()

+ 4 - 0
Tests/RunCMake/CMakeLists.txt

@@ -126,6 +126,9 @@ endif()
 add_RunCMake_test(CMP0106)
 add_RunCMake_test(CMP0111)
 add_RunCMake_test(CMP0115)
+if(CMAKE_GENERATOR MATCHES "Ninja")
+  add_RunCMake_test(CMP0116)
+endif()
 
 # The test for Policy 65 requires the use of the
 # CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode
@@ -771,6 +774,7 @@ add_RunCMake_test(PrecompileHeaders -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
 
 add_RunCMake_test("UnityBuild")
 add_RunCMake_test(CMakePresets)
+add_RunCMake_test(TransformDepfile)
 
 if(WIN32)
   add_RunCMake_test(Win32GenEx)

+ 21 - 0
Tests/RunCMake/TransformDepfile/RunCMakeTest.cmake

@@ -0,0 +1,21 @@
+include(RunCMake)
+
+function(run_transform_depfile name)
+  set(RunCMake-check-file gccdepfile.cmake)
+  run_cmake_command(${name}-gcc
+    ${CMAKE_COMMAND} -E cmake_transform_depfile gccdepfile ../ ${CMAKE_CURRENT_LIST_DIR}/${name}.d out.d
+    )
+  set(RunCMake-check-file vstlog.cmake)
+  run_cmake_command(${name}-tlog
+    ${CMAKE_COMMAND} -E cmake_transform_depfile vstlog ../ ${CMAKE_CURRENT_LIST_DIR}/${name}.d out.tlog
+    )
+endfunction()
+
+if(WIN32)
+  run_transform_depfile(deps-windows)
+else()
+  run_transform_depfile(deps-unix)
+endif()
+run_transform_depfile(noexist)
+run_transform_depfile(empty)
+run_transform_depfile(invalid)

+ 6 - 0
Tests/RunCMake/TransformDepfile/deps-unix.d

@@ -0,0 +1,6 @@
+out1 /home/build/out2: in1 /home/build/in2
+
+out3 \
+  /home/build/out4: \
+  in3 \
+  /home/build/in4

+ 8 - 0
Tests/RunCMake/TransformDepfile/deps-unix.d.txt

@@ -0,0 +1,8 @@
+../out1 \
+  /home/build/out2: \
+  ../in1 \
+  /home/build/in2
+../out3 \
+  /home/build/out4: \
+  ../in3 \
+  /home/build/in4

+ 6 - 0
Tests/RunCMake/TransformDepfile/deps-unix.tlog.txt

@@ -0,0 +1,6 @@
+^../out1|/home/build/out2
+../in1
+/home/build/in2
+^../out3|/home/build/out4
+../in3
+/home/build/in4

+ 6 - 0
Tests/RunCMake/TransformDepfile/deps-windows.d

@@ -0,0 +1,6 @@
+out1 C:/build/out2: in1 C:/build/in2
+
+out3 \
+  C:/build/out4: \
+  in3 \
+  C:/build/in4

+ 8 - 0
Tests/RunCMake/TransformDepfile/deps-windows.d.txt

@@ -0,0 +1,8 @@
+../out1 \
+  C:/build/out2: \
+  ../in1 \
+  C:/build/in2
+../out3 \
+  C:/build/out4: \
+  ../in3 \
+  C:/build/in4

+ 6 - 0
Tests/RunCMake/TransformDepfile/deps-windows.tlog.txt

@@ -0,0 +1,6 @@
+^..\out1|C:\build\out2
+..\in1
+C:\build\in2
+^..\out3|C:\build\out4
+..\in3
+C:\build\in4

+ 0 - 0
Tests/RunCMake/TransformDepfile/empty.d


+ 0 - 0
Tests/RunCMake/TransformDepfile/empty.d.txt


+ 0 - 0
Tests/RunCMake/TransformDepfile/empty.tlog.txt


+ 16 - 0
Tests/RunCMake/TransformDepfile/gccdepfile.cmake

@@ -0,0 +1,16 @@
+if(EXISTS "${RunCMake_SOURCE_DIR}/${name}.d.txt")
+  file(READ "${RunCMake_SOURCE_DIR}/${name}.d.txt" expected_contents)
+
+  if(EXISTS "${RunCMake_TEST_BINARY_DIR}/out.d")
+    file(READ "${RunCMake_TEST_BINARY_DIR}/out.d" actual_contents)
+    if(NOT actual_contents STREQUAL expected_contents)
+      string(REPLACE "\n" "\n  " p_expected_contents "${expected_contents}")
+      string(REPLACE "\n" "\n  " p_actual_contents "${actual_contents}")
+      string(APPEND RunCMake_TEST_FAILED "Expected contents of ${RunCMake_TEST_BINARY_DIR}/out.d:\n  ${p_expected_contents}\nActual contents:\n  ${p_actual_contents}")
+    endif()
+  else()
+    string(APPEND RunCMake_TEST_FAILED "${RunCMake_TEST_BINARY_DIR}/out.d should exist\n")
+  endif()
+elseif(EXISTS "${RunCMake_TEST_BINARY_DIR}/out.d")
+  string(APPEND RunCMake_TEST_FAILED "${RunCMake_TEST_BINARY_DIR}/out.d should not exist\n")
+endif()

+ 1 - 0
Tests/RunCMake/TransformDepfile/invalid-gcc-result.txt

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

+ 1 - 0
Tests/RunCMake/TransformDepfile/invalid-tlog-result.txt

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

+ 1 - 0
Tests/RunCMake/TransformDepfile/invalid.d

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

+ 0 - 0
Tests/RunCMake/TransformDepfile/noexist.d.txt


+ 0 - 0
Tests/RunCMake/TransformDepfile/noexist.tlog.txt


+ 16 - 0
Tests/RunCMake/TransformDepfile/vstlog.cmake

@@ -0,0 +1,16 @@
+if(EXISTS "${RunCMake_SOURCE_DIR}/${name}.tlog.txt")
+  file(READ "${RunCMake_SOURCE_DIR}/${name}.tlog.txt" expected_contents)
+
+  if(EXISTS "${RunCMake_TEST_BINARY_DIR}/out.tlog")
+    file(READ "${RunCMake_TEST_BINARY_DIR}/out.tlog" actual_contents)
+    if(NOT actual_contents STREQUAL expected_contents)
+      string(REPLACE "\n" "\n  " p_expected_contents "${expected_contents}")
+      string(REPLACE "\n" "\n  " p_actual_contents "${actual_contents}")
+      string(APPEND RunCMake_TEST_FAILED "Expected contents of ${RunCMake_TEST_BINARY_DIR}/out.tlog:\n  ${p_expected_contents}\nActual contents:\n  ${p_actual_contents}")
+    endif()
+  else()
+    string(APPEND RunCMake_TEST_FAILED "${RunCMake_TEST_BINARY_DIR}/out.tlog should exist\n")
+  endif()
+elseif(EXISTS "${RunCMake_TEST_BINARY_DIR}/out.tlog")
+  string(APPEND RunCMake_TEST_FAILED "${RunCMake_TEST_BINARY_DIR}/out.tlog should not exist\n")
+endif()

+ 37 - 34
bootstrap

@@ -307,6 +307,7 @@ CMAKE_CXX_SOURCES="\
   cmContinueCommand \
   cmCoreTryCompile \
   cmCreateTestSourceList \
+  cmCryptoHash \
   cmCustomCommand \
   cmCustomCommandGenerator \
   cmCustomCommandLines \
@@ -412,6 +413,8 @@ CMAKE_CXX_SOURCES="\
   cmProjectCommand \
   cmPropertyDefinition \
   cmPropertyMap \
+  cmGccDepfileLexerHelper \
+  cmGccDepfileReader \
   cmReturnCommand \
   cmRulePlaceholderExpander \
   cmRuntimeDependencyArchive \
@@ -452,6 +455,7 @@ CMAKE_CXX_SOURCES="\
   cmTest \
   cmTestGenerator \
   cmTimestamp \
+  cmTransformDepfile \
   cmTryCompileCommand \
   cmTryRunCommand \
   cmUnsetCommand \
@@ -491,6 +495,7 @@ LexerParser_CXX_SOURCES="\
   cmCommandArgumentParser \
   cmExprLexer \
   cmExprParser \
+  cmGccDepfileLexer \
 "
 
 LexerParser_C_SOURCES="\
@@ -535,6 +540,18 @@ KWSYS_FILES="\
   SystemTools.hxx \
   Terminal.h"
 
+LIBRHASH_C_SOURCES="\
+  librhash/algorithms.c \
+  librhash/byte_order.c \
+  librhash/hex.c \
+  librhash/md5.c \
+  librhash/rhash.c \
+  librhash/sha1.c \
+  librhash/sha256.c \
+  librhash/sha3.c \
+  librhash/sha512.c \
+  "
+
 if ${cmake_system_mingw}; then
   LIBUV_C_SOURCES="\
     src/fs-poll.c \
@@ -1012,7 +1029,6 @@ cmake_ld_flags=${LDFLAGS}
 # Add generator-specific files
 if test "${cmake_bootstrap_generator}" = "Ninja"; then
   CMAKE_CXX_SOURCES="${CMAKE_CXX_SOURCES} \
-    cmCryptoHash \
     cmFortranParserImpl \
     cmGlobalNinjaGenerator \
     cmLocalNinjaGenerator \
@@ -1033,18 +1049,6 @@ if test "${cmake_bootstrap_generator}" = "Ninja"; then
     src/lib_json/json_value.cpp \
     src/lib_json/json_writer.cpp \
     "
-
-  LIBRHASH_C_SOURCES="\
-    librhash/algorithms.c \
-    librhash/byte_order.c \
-    librhash/hex.c \
-    librhash/md5.c \
-    librhash/rhash.c \
-    librhash/sha1.c \
-    librhash/sha256.c \
-    librhash/sha3.c \
-    librhash/sha512.c \
-    "
 else
   CMAKE_CXX_SOURCES="${CMAKE_CXX_SOURCES} \
     cmDepends \
@@ -1058,7 +1062,6 @@ else
     "
 
   JSONCPP_CXX_SOURCES=
-  LIBRHASH_C_SOURCES=
 fi
 
 # Add Cygwin-specific flags
@@ -1628,17 +1631,17 @@ if test "x${bootstrap_system_libuv}" = "x"; then
     objs="${objs} uv-`cmake_obj ${a}`"
   done
 fi
+if test "x${bootstrap_system_librhash}" = "x"; then
+  for a in ${LIBRHASH_C_SOURCES}; do
+    objs="${objs} rhash-`cmake_obj ${a}`"
+  done
+fi
 if test "${cmake_bootstrap_generator}" = "Ninja"; then
   if test "x${bootstrap_system_jsoncpp}" = "x"; then
     for a in ${JSONCPP_CXX_SOURCES}; do
       objs="${objs} jsoncpp-`cmake_obj ${a}`"
     done
   fi
-  if test "x${bootstrap_system_librhash}" = "x"; then
-    for a in ${LIBRHASH_C_SOURCES}; do
-      objs="${objs} rhash-`cmake_obj ${a}`"
-    done
-  fi
 fi
 
 libs=""
@@ -1698,6 +1701,15 @@ else
   libs="${libs} -luv"
 fi
 
+if test "x${bootstrap_system_librhash}" != "x"; then
+  if test `which pkg-config`; then
+    use_librhash_flags="`pkg-config --cflags librhash`"
+    cmake_c_flags="${cmake_c_flags} ${use_librhash_flags}"
+    cmake_cxx_flags="${cmake_cxx_flags} ${use_librhash_flags}"
+  fi
+  libs="${libs} -lrhash"
+fi
+
 if test "${cmake_bootstrap_generator}" = "Ninja"; then
   jsoncpp_cxx_flags=
   if test "x${bootstrap_system_jsoncpp}" = "x"; then
@@ -1709,15 +1721,6 @@ if test "${cmake_bootstrap_generator}" = "Ninja"; then
     fi
     libs="${libs} -ljsoncpp"
   fi
-
-  if test "x${bootstrap_system_librhash}" != "x"; then
-    if test `which pkg-config`; then
-      use_librhash_flags="`pkg-config --cflags librhash`"
-      cmake_c_flags="${cmake_c_flags} ${use_librhash_flags}"
-      cmake_cxx_flags="${cmake_cxx_flags} ${use_librhash_flags}"
-    fi
-    libs="${libs} -lrhash"
-  fi
 fi
 
 if test "x${cmake_ansi_cxx_flags}" != "x"; then
@@ -1841,6 +1844,12 @@ if test "x${bootstrap_system_libuv}" = "x"; then
     write_source_rule "c" "uv-`cmake_obj ${a}`" "${src}" "${uv_c_flags}"
   done
 fi
+if test "x${bootstrap_system_librhash}" = "x"; then
+  for a in ${LIBRHASH_C_SOURCES}; do
+    src=`cmake_escape_artifact "${cmake_source_dir}/Utilities/cmlibrhash/${a}"`
+    write_source_rule "c" "rhash-`cmake_obj ${a}`" "${src}" ""
+  done
+fi
 if test "${cmake_bootstrap_generator}" = "Ninja"; then
   if test "x${bootstrap_system_jsoncpp}" = "x"; then
     for a in ${JSONCPP_CXX_SOURCES}; do
@@ -1848,12 +1857,6 @@ if test "${cmake_bootstrap_generator}" = "Ninja"; then
       write_source_rule "cxx" "jsoncpp-`cmake_obj ${a}`" "${src}" "${jsoncpp_cxx_flags}"
     done
   fi
-  if test "x${bootstrap_system_librhash}" = "x"; then
-    for a in ${LIBRHASH_C_SOURCES}; do
-      src=`cmake_escape_artifact "${cmake_source_dir}/Utilities/cmlibrhash/${a}"`
-      write_source_rule "c" "rhash-`cmake_obj ${a}`" "${src}" ""
-    done
-  fi
 fi
 if test "${cmake_bootstrap_generator}" = "Ninja"; then
   echo "