diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 1e16e17860caf0009cba5a568d5ec72a70e7041b..2946022850dd8b084a2866d8503c80603096137a 100644
--- a/Source/CMakeLists.txt
+++ b/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
diff --git a/Source/cmTransformDepfile.cxx b/Source/cmTransformDepfile.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..e1f875399587a2357de355836d14896ebd3379af
--- /dev/null
+++ b/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;
+}
diff --git a/Source/cmTransformDepfile.h b/Source/cmTransformDepfile.h
new file mode 100644
index 0000000000000000000000000000000000000000..792c1aa24ef5972b34f4fcabe9be4dd622cfefa7
--- /dev/null
+++ b/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);
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index 15d2fd12e9c5e944da788328f9103e3273f272a4..0bdc6fabfea8691fe2c2360ffc4cc688ee0048c4 100644
--- a/Source/cmcmd.cxx
+++ b/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());
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index a8261c68d30f05d22d23c2ab46032327c6f5ad50..59ca3b1fd012adbcf4a148d39af49b22045317f0 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -771,6 +771,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)
diff --git a/Tests/RunCMake/TransformDepfile/RunCMakeTest.cmake b/Tests/RunCMake/TransformDepfile/RunCMakeTest.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..cb75eb0630ea8add2fd01c205b46a42c934cf6cb
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/TransformDepfile/deps-unix.d b/Tests/RunCMake/TransformDepfile/deps-unix.d
new file mode 100644
index 0000000000000000000000000000000000000000..5da5be8f7b007c26b9aa8b996482c2fa45502c73
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/TransformDepfile/deps-unix.d.txt b/Tests/RunCMake/TransformDepfile/deps-unix.d.txt
new file mode 100644
index 0000000000000000000000000000000000000000..58770f2022d04400b0c479e3b3ae7d885faed6da
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/TransformDepfile/deps-unix.tlog.txt b/Tests/RunCMake/TransformDepfile/deps-unix.tlog.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2a26edfa11d59a03cd2b2a7920e6774409fe7b6d
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/TransformDepfile/deps-windows.d b/Tests/RunCMake/TransformDepfile/deps-windows.d
new file mode 100644
index 0000000000000000000000000000000000000000..c926670906049702ffa6078c8f61819b28f00997
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/TransformDepfile/deps-windows.d.txt b/Tests/RunCMake/TransformDepfile/deps-windows.d.txt
new file mode 100644
index 0000000000000000000000000000000000000000..47b3ebfe69e8410ef4a304555bad9c5418548da5
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/TransformDepfile/deps-windows.tlog.txt b/Tests/RunCMake/TransformDepfile/deps-windows.tlog.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1e6024dce127c47a05038aea1527143b39c49d1d
--- /dev/null
+++ b/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
diff --git a/Tests/RunCMake/TransformDepfile/empty.d b/Tests/RunCMake/TransformDepfile/empty.d
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tests/RunCMake/TransformDepfile/empty.d.txt b/Tests/RunCMake/TransformDepfile/empty.d.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tests/RunCMake/TransformDepfile/empty.tlog.txt b/Tests/RunCMake/TransformDepfile/empty.tlog.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tests/RunCMake/TransformDepfile/gccdepfile.cmake b/Tests/RunCMake/TransformDepfile/gccdepfile.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..be1e21016e201e8a60c85fbc7bb842d5b83a2b95
--- /dev/null
+++ b/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()
diff --git a/Tests/RunCMake/TransformDepfile/invalid-gcc-result.txt b/Tests/RunCMake/TransformDepfile/invalid-gcc-result.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d00491fd7e5bb6fa28c517a0bb32b8b506539d4d
--- /dev/null
+++ b/Tests/RunCMake/TransformDepfile/invalid-gcc-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/TransformDepfile/invalid-tlog-result.txt b/Tests/RunCMake/TransformDepfile/invalid-tlog-result.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d00491fd7e5bb6fa28c517a0bb32b8b506539d4d
--- /dev/null
+++ b/Tests/RunCMake/TransformDepfile/invalid-tlog-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/TransformDepfile/invalid.d b/Tests/RunCMake/TransformDepfile/invalid.d
new file mode 100644
index 0000000000000000000000000000000000000000..9977a2836c1a0f7fbe7fae516d18d1da4ff4770d
--- /dev/null
+++ b/Tests/RunCMake/TransformDepfile/invalid.d
@@ -0,0 +1 @@
+invalid
diff --git a/Tests/RunCMake/TransformDepfile/noexist.d.txt b/Tests/RunCMake/TransformDepfile/noexist.d.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tests/RunCMake/TransformDepfile/noexist.tlog.txt b/Tests/RunCMake/TransformDepfile/noexist.tlog.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/Tests/RunCMake/TransformDepfile/vstlog.cmake b/Tests/RunCMake/TransformDepfile/vstlog.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..afa78d05ccb031c2fcf14d8ed5b6deadf5b7631d
--- /dev/null
+++ b/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()
diff --git a/bootstrap b/bootstrap
index feccde98da6b430032eb2cc1ce249e1fec222194..9126ae646ff9df1999a1ca922ee707032393a89e 100755
--- a/bootstrap
+++ b/bootstrap
@@ -412,6 +412,8 @@ CMAKE_CXX_SOURCES="\
   cmProjectCommand \
   cmPropertyDefinition \
   cmPropertyMap \
+  cmGccDepfileLexerHelper \
+  cmGccDepfileReader \
   cmReturnCommand \
   cmRulePlaceholderExpander \
   cmRuntimeDependencyArchive \
@@ -452,6 +454,7 @@ CMAKE_CXX_SOURCES="\
   cmTest \
   cmTestGenerator \
   cmTimestamp \
+  cmTransformDepfile \
   cmTryCompileCommand \
   cmTryRunCommand \
   cmUnsetCommand \
@@ -491,6 +494,7 @@ LexerParser_CXX_SOURCES="\
   cmCommandArgumentParser \
   cmExprLexer \
   cmExprParser \
+  cmGccDepfileLexer \
 "
 
 LexerParser_C_SOURCES="\