diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst
index b6ff0ea35bc7f72d347d1636b56fd66230b4b21c..c0ff81ae83959cca9db6d7c4a34ffc6a720ec8da 100644
--- a/Help/command/add_custom_command.rst
+++ b/Help/command/add_custom_command.rst
@@ -271,32 +271,42 @@ The options are:
 ``DEPFILE``
   .. versionadded:: 3.7
 
-  Specify a ``.d`` depfile for the :generator:`Ninja` generator and
-  :ref:`Makefile Generators`. The depfile may use "generator expressions" with
-  the syntax ``$<...>``. See the :manual:`generator-expressions(7)
-  <cmake-generator-expressions(7)>` manual for available expressions.
-  A ``.d`` file holds dependencies usually emitted by the custom
-  command itself.
-  Using ``DEPFILE`` with other generators than :generator:`Ninja` or
-  :ref:`Makefile Generators` is an error.
+  Specify a ``.d`` depfile for the :generator:`Ninja`, :generator:`Xcode` and
+  :ref:`Makefile <Makefile Generators>` generators. The depfile may use
+  "generator expressions" with the syntax ``$<...>``. See the
+  :manual:`generator-expressions(7) <cmake-generator-expressions(7)>` manual
+  for available expressions. A ``.d`` file holds dependencies usually emitted
+  by the custom command itself.
+
+  Using ``DEPFILE`` with other generators than :generator:`Ninja`,
+  :generator:`Xcode` or :ref:`Makefile <Makefile Generators>` is an error.
 
   .. versionadded:: 3.20
     Added the support of :ref:`Makefile Generators`.
 
   .. versionadded:: 3.21
-    Added the support of :manual:`generator expressions <cmake-generator-expressions(7)>`.
+    Added the support of  :generator:`Xcode` generator and
+    :manual:`generator expressions <cmake-generator-expressions(7)>`.
 
   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`. This policy is always ``NEW`` for
-  :ref:`Makefile Generators`).
+  :ref:`Makefile <Makefile Generators>` and :generator:`Xcode` generators).
 
   .. note::
 
     For :ref:`Makefile Generators`, this option cannot be specified at the
     same time as ``IMPLICIT_DEPENDS`` option.
 
+  .. note::
+
+    For the :generator:`Xcode` generator, this option requires that the
+    :ref:`Xcode Build System Selection` uses the ``buildsystem=12`` variant
+    or higher.  This is the default when using Xcode 12 or above.
+    The :variable:`CMAKE_XCODE_BUILD_SYSTEM` variable indicates which variant
+    of the Xcode build system is used.
+
 Examples: Generating Files
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/Help/release/dev/Xcode-add_custom_command-DEPFILE.rst b/Help/release/dev/Xcode-add_custom_command-DEPFILE.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4c4d48c7690c5665c321b3120587628d3b00ad28
--- /dev/null
+++ b/Help/release/dev/Xcode-add_custom_command-DEPFILE.rst
@@ -0,0 +1,5 @@
+Xcode-add_custom_command-DEPFILE
+--------------------------------
+
+* The :command:`add_custom_command` command gained ``DEPFILE`` support on
+  :generator:`Xcode` generator.
diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx
index 4705443190e2ab65f1a9763a212518c58750fd2b..7659792dddc8de7bbd663cb1f25f80e0c3450cc9 100644
--- a/Source/cmCustomCommandGenerator.cxx
+++ b/Source/cmCustomCommandGenerator.cxx
@@ -151,7 +151,9 @@ std::string EvaluateDepfile(std::string const& path,
 
 cmCustomCommandGenerator::cmCustomCommandGenerator(
   cmCustomCommand const& cc, std::string config, cmLocalGenerator* lg,
-  bool transformDepfile, cm::optional<std::string> crossConfig)
+  bool transformDepfile, cm::optional<std::string> crossConfig,
+  std::function<std::string(const std::string&, const std::string&)>
+    computeInternalDepfile)
   : CC(&cc)
   , OutputConfig(crossConfig ? *crossConfig : config)
   , CommandConfig(std::move(config))
@@ -159,7 +161,15 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(
   , OldStyle(cc.GetEscapeOldStyle())
   , MakeVars(cc.GetEscapeAllowMakeVars())
   , EmulatorsWithArguments(cc.GetCommandLines().size())
+  , ComputeInternalDepfile(std::move(computeInternalDepfile))
 {
+  if (!this->ComputeInternalDepfile) {
+    this->ComputeInternalDepfile =
+      [this](const std::string& cfg, const std::string& file) -> std::string {
+      return this->GetInternalDepfileName(cfg, file);
+    };
+  }
+
   cmGeneratorExpression ge(cc.GetBacktrace());
 
   const cmCustomCommandLines& cmdlines = this->CC->GetCommandLines();
@@ -413,13 +423,9 @@ std::string cmCustomCommandGenerator::GetFullDepfile() const
   return cmSystemTools::CollapseFullPath(depfile);
 }
 
-std::string cmCustomCommandGenerator::GetInternalDepfile() const
+std::string cmCustomCommandGenerator::GetInternalDepfileName(
+  const std::string& /*config*/, const std::string& depfile)
 {
-  std::string depfile = this->GetFullDepfile();
-  if (depfile.empty()) {
-    return "";
-  }
-
   cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
   std::string extension;
   switch (*this->LG->GetGlobalGenerator()->DepfileFormat()) {
@@ -434,6 +440,16 @@ std::string cmCustomCommandGenerator::GetInternalDepfile() const
                   hash.HashString(depfile), extension);
 }
 
+std::string cmCustomCommandGenerator::GetInternalDepfile() const
+{
+  std::string depfile = this->GetFullDepfile();
+  if (depfile.empty()) {
+    return "";
+  }
+
+  return this->ComputeInternalDepfile(this->OutputConfig, depfile);
+}
+
 const char* cmCustomCommandGenerator::GetComment() const
 {
   return this->CC->GetComment();
diff --git a/Source/cmCustomCommandGenerator.h b/Source/cmCustomCommandGenerator.h
index 53e55737da85936f9f63533f1f6d9dc89df86f73..e70909a74a0f7190db37797794df480ee3f84eba 100644
--- a/Source/cmCustomCommandGenerator.h
+++ b/Source/cmCustomCommandGenerator.h
@@ -4,6 +4,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <functional>
 #include <set>
 #include <string>
 #include <utility>
@@ -19,6 +20,8 @@ class cmLocalGenerator;
 
 class cmCustomCommandGenerator
 {
+  std::string GetInternalDepfileName(const std::string&, const std::string&);
+
   cmCustomCommand const* CC;
   std::string OutputConfig;
   std::string CommandConfig;
@@ -32,15 +35,19 @@ class cmCustomCommandGenerator
   std::vector<std::string> Depends;
   std::string WorkingDirectory;
   std::set<BT<std::pair<std::string, bool>>> Utilities;
+  std::function<std::string(const std::string&, const std::string&)>
+    ComputeInternalDepfile;
 
   void FillEmulatorsWithArguments();
   std::vector<std::string> GetCrossCompilingEmulator(unsigned int c) const;
   const char* GetArgv0Location(unsigned int c) const;
 
 public:
-  cmCustomCommandGenerator(cmCustomCommand const& cc, std::string config,
-                           cmLocalGenerator* lg, bool transformDepfile = true,
-                           cm::optional<std::string> crossConfig = {});
+  cmCustomCommandGenerator(
+    cmCustomCommand const& cc, std::string config, cmLocalGenerator* lg,
+    bool transformDepfile = true, cm::optional<std::string> crossConfig = {},
+    std::function<std::string(const std::string&, const std::string&)>
+      computeInternalDepfile = {});
   cmCustomCommandGenerator(const cmCustomCommandGenerator&) = delete;
   cmCustomCommandGenerator(cmCustomCommandGenerator&&) = default;
   cmCustomCommandGenerator& operator=(const cmCustomCommandGenerator&) =
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 7dd17045ee1495ff7fcdd4edaf97ea4128cfc7be..dc3d3f253e125df576d324313459d79e2b922381 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -17,6 +17,7 @@
 
 #include "cmsys/RegularExpression.hxx"
 
+#include "cmCMakePath.h"
 #include "cmComputeLinkInformation.h"
 #include "cmCryptoHash.h"
 #include "cmCustomCommand.h"
@@ -1864,9 +1865,20 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateRunScriptBuildPhase(
   std::set<std::string> allConfigInputs;
   std::set<std::string> allConfigOutputs;
 
+  cmXCodeObject* buildPhase =
+    this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase,
+                       cmStrCat(gt->GetName(), ':', sf->GetFullPath()));
+
+  auto depfilesDirectory = cmStrCat(
+    gt->GetLocalGenerator()->GetCurrentBinaryDirectory(), "/CMakeFiles/d/");
+  auto depfilesPrefix = cmStrCat(depfilesDirectory, buildPhase->GetId(), ".");
+
   std::string shellScript = "set -e\n";
   for (std::string const& configName : this->CurrentConfigurationTypes) {
-    cmCustomCommandGenerator ccg(cc, configName, this->CurrentLocalGenerator);
+    cmCustomCommandGenerator ccg(
+      cc, configName, this->CurrentLocalGenerator, true, {},
+      [&depfilesPrefix](const std::string& config, const std::string&)
+        -> std::string { return cmStrCat(depfilesPrefix, config, ".d"); });
     std::vector<std::string> realDepends;
     realDepends.reserve(ccg.GetDepends().size());
     for (auto const& d : ccg.GetDepends()) {
@@ -1886,9 +1898,22 @@ cmXCodeObject* cmGlobalXCodeGenerator::CreateRunScriptBuildPhase(
                "\"; then :\n", this->ConstructScript(ccg), "fi\n");
   }
 
-  cmXCodeObject* buildPhase =
-    this->CreateObject(cmXCodeObject::PBXShellScriptBuildPhase,
-                       cmStrCat(gt->GetName(), ':', sf->GetFullPath()));
+  if (!cc.GetDepfile().empty()) {
+    buildPhase->AddAttribute(
+      "dependencyFile",
+      this->CreateString(cmStrCat(depfilesDirectory, buildPhase->GetId(),
+                                  ".$(CONFIGURATION).d")));
+    // to avoid spurious errors during first build,  create empty dependency
+    // files
+    cmSystemTools::MakeDirectory(depfilesDirectory);
+    for (std::string const& configName : this->CurrentConfigurationTypes) {
+      auto file = cmStrCat(depfilesPrefix, configName, ".d");
+      if (!cmSystemTools::FileExists(file)) {
+        cmSystemTools::Touch(file, true);
+      }
+    }
+  }
+
   buildPhase->AddAttribute("buildActionMask",
                            this->CreateString("2147483647"));
   cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h
index 1ab56e20eb08f99aae12f35efa044eb48bd92b62..240647235178a5f62525b7fef9077f20110f7bb7 100644
--- a/Source/cmGlobalXCodeGenerator.h
+++ b/Source/cmGlobalXCodeGenerator.h
@@ -14,6 +14,7 @@
 #include <cm/string_view>
 
 #include "cmGlobalGenerator.h"
+#include "cmTransformDepfile.h"
 #include "cmXCodeObject.h"
 
 class cmCustomCommand;
@@ -111,6 +112,18 @@ public:
 
   bool ShouldStripResourcePath(cmMakefile*) const override;
 
+  /**
+   * Used to determine if this generator supports DEPFILE option.
+   */
+  bool SupportsCustomCommandDepfile() const override
+  {
+    return this->XcodeBuildSystem >= BuildSystem::Twelve;
+  }
+  virtual cm::optional<cmDepfileFormat> DepfileFormat() const override
+  {
+    return cmDepfileFormat::GccDepfile;
+  }
+
   bool SetSystemName(std::string const& s, cmMakefile* mf) override;
   bool SetGeneratorToolset(std::string const& ts, bool build,
                            cmMakefile* mf) override;
diff --git a/Source/cmTransformDepfile.cxx b/Source/cmTransformDepfile.cxx
index 78aa4b2df352b2617b0fb3552ad3bd841386ee9c..b693582ec321a884524b6ffbecd2cd78b10a3556 100644
--- a/Source/cmTransformDepfile.cxx
+++ b/Source/cmTransformDepfile.cxx
@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmTransformDepfile.h"
 
+#include <functional>
 #include <string>
 #include <type_traits>
 #include <utility>
@@ -13,6 +14,7 @@
 
 #include "cmGccDepfileReader.h"
 #include "cmGccDepfileReaderTypes.h"
+#include "cmGlobalGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmSystemTools.h"
 
@@ -38,6 +40,14 @@ void WriteGccDepfile(cmsys::ofstream& fout, const cmLocalGenerator& lg,
                      const cmGccDepfileContent& content)
 {
   const auto& binDir = lg.GetBinaryDirectory();
+  std::function<std::string(const std::string&)> formatPath =
+    [&lg, &binDir](const std::string& path) -> std::string {
+    return lg.MaybeConvertToRelativePath(binDir, path);
+  };
+  if (lg.GetGlobalGenerator()->GetName() == "Xcode") {
+    // full paths must be preserved for Xcode compliance
+    formatPath = [](const std::string& path) -> std::string { return path; };
+  }
 
   for (auto const& dep : content) {
     bool first = true;
@@ -46,12 +56,12 @@ void WriteGccDepfile(cmsys::ofstream& fout, const cmLocalGenerator& lg,
         fout << " \\\n  ";
       }
       first = false;
-      WriteFilenameGcc(fout, lg.MaybeConvertToRelativePath(binDir, rule));
+      WriteFilenameGcc(fout, formatPath(rule));
     }
     fout << ':';
     for (auto const& path : dep.paths) {
       fout << " \\\n  ";
-      WriteFilenameGcc(fout, lg.MaybeConvertToRelativePath(binDir, path));
+      WriteFilenameGcc(fout, formatPath(path));
     }
     fout << '\n';
   }
diff --git a/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake b/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
index 62326345e4d6db98852ff96312e1b9797a000a66..a6e08da51f6f78495bd16e2880ce9ce2deaee899 100644
--- a/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
+++ b/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
@@ -155,7 +155,8 @@ if (RunCMake_GENERATOR MATCHES "Makefiles")
   run_cmake(CustomCommandDependencies-BadArgs)
 endif()
 
-if(RunCMake_GENERATOR MATCHES "Make|Ninja")
+if(RunCMake_GENERATOR MATCHES "Make|Ninja" OR
+    (RunCMake_GENERATOR STREQUAL "Xcode" AND CMAKE_XCODE_BUILD_SYSTEM GREATER_EQUAL "12"))
   unset(run_BuildDepends_skip_step_3)
   run_BuildDepends(CustomCommandDepfile)
   set(run_BuildDepends_skip_step_3 1)
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 5f23c0582cb9ea71f6de4dc802444fdf36b721e1..be59a55eb276d4dc42b9344c090099144aacd333 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -222,6 +222,7 @@ endif()
 
 add_RunCMake_test(BuildDepends
   -DMSVC_VERSION=${MSVC_VERSION}
+  -DCMAKE_XCODE_BUILD_SYSTEM=${CMAKE_XCODE_BUILD_SYSTEM}
   -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
   -DCMake_TEST_BuildDepends_GNU_AS=${CMake_TEST_BuildDepends_GNU_AS}
   )