Merge topic 'vs-custom-depfile'

526e2ef71c VS: Add support for add_custom_command DEPFILE
794ad78abb Help: Generalize release note filename for add_custom_command DEPFILE
7291f31254 cmTransformDepfile: Add support for MSBuild AdditionalInputs format
a6de8ec51b cmTransformDepfile: Make directory for transformed depfile automatically

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !6206
diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst
index a053e59..d881a66 100644
--- a/Help/command/add_custom_command.rst
+++ b/Help/command/add_custom_command.rst
@@ -282,17 +282,19 @@
   :generator:`Xcode` or :ref:`Makefile <Makefile Generators>` is an error.
 
   .. versionadded:: 3.20
-    Added the support of :ref:`Makefile Generators`.
+    Added support for :ref:`Makefile Generators`.
 
   .. versionadded:: 3.21
-    Added the support of  :generator:`Xcode` generator and
+    Added support for :ref:`Visual Studio Generators` with VS 2012 and above,
+    for the :generator:`Xcode` generator, and for
     :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 <Makefile Generators>` and :generator:`Xcode` generators).
+  :ref:`Makefile Generators`, :ref:`Visual Studio Generators`,
+  and the :generator:`Xcode` generator).
 
   .. note::
 
diff --git a/Help/release/dev/Xcode-add_custom_command-DEPFILE.rst b/Help/release/dev/Xcode-add_custom_command-DEPFILE.rst
deleted file mode 100644
index 4c4d48c..0000000
--- a/Help/release/dev/Xcode-add_custom_command-DEPFILE.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Xcode-add_custom_command-DEPFILE
---------------------------------
-
-* The :command:`add_custom_command` command gained ``DEPFILE`` support on
-  :generator:`Xcode` generator.
diff --git a/Help/release/dev/add_custom_command-DEPFILE.rst b/Help/release/dev/add_custom_command-DEPFILE.rst
new file mode 100644
index 0000000..893c374
--- /dev/null
+++ b/Help/release/dev/add_custom_command-DEPFILE.rst
@@ -0,0 +1,6 @@
+add_custom_command-DEPFILE
+--------------------------
+
+* The :command:`add_custom_command` command gained ``DEPFILE`` support on
+  the :generator:`Xcode` generator, and on :ref:`Visual Studio Generators`
+  for VS 2012 and above.
diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx
index 10a6491..5a2683b 100644
--- a/Source/cmCustomCommandGenerator.cxx
+++ b/Source/cmCustomCommandGenerator.cxx
@@ -230,6 +230,9 @@
       case cmDepfileFormat::MakeDepfile:
         argv.emplace_back("makedepfile");
         break;
+      case cmDepfileFormat::MSBuildAdditionalInputs:
+        argv.emplace_back("MSBuildAdditionalInputs");
+        break;
     }
     argv.push_back(this->LG->GetSourceDirectory());
     argv.push_back(this->LG->GetCurrentSourceDirectory());
@@ -437,6 +440,9 @@
     case cmDepfileFormat::MakeDepfile:
       extension = ".d";
       break;
+    case cmDepfileFormat::MSBuildAdditionalInputs:
+      extension = ".AdditionalInputs";
+      break;
   }
   return cmStrCat(this->LG->GetBinaryDirectory(), "/CMakeFiles/d/",
                   hash.HashString(depfile), extension);
diff --git a/Source/cmGlobalVisualStudio11Generator.h b/Source/cmGlobalVisualStudio11Generator.h
index 6e409cf..b11905e 100644
--- a/Source/cmGlobalVisualStudio11Generator.h
+++ b/Source/cmGlobalVisualStudio11Generator.h
@@ -24,6 +24,13 @@
 
   bool MatchesGeneratorName(const std::string& name) const override;
 
+  bool SupportsCustomCommandDepfile() const override { return true; }
+
+  cm::optional<cmDepfileFormat> DepfileFormat() const override
+  {
+    return cmDepfileFormat::MSBuildAdditionalInputs;
+  }
+
 protected:
   cmGlobalVisualStudio11Generator(cmake* cm, const std::string& name,
                                   std::string const& platformInGeneratorName);
diff --git a/Source/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx
index 8142599..7f7b1e7 100644
--- a/Source/cmLocalNinjaGenerator.cxx
+++ b/Source/cmLocalNinjaGenerator.cxx
@@ -698,8 +698,6 @@
           case cmPolicies::REQUIRED_IF_USED:
           case cmPolicies::REQUIRED_ALWAYS:
           case cmPolicies::NEW:
-            cmSystemTools::MakeDirectory(
-              cmStrCat(this->GetBinaryDirectory(), "/CMakeFiles/d"));
             depfile = ccg.GetInternalDepfile();
             break;
         }
diff --git a/Source/cmTransformDepfile.cxx b/Source/cmTransformDepfile.cxx
index 0df9550..4032596 100644
--- a/Source/cmTransformDepfile.cxx
+++ b/Source/cmTransformDepfile.cxx
@@ -2,7 +2,9 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmTransformDepfile.h"
 
+#include <algorithm>
 #include <functional>
+#include <memory>
 #include <string>
 #include <type_traits>
 #include <utility>
@@ -78,6 +80,32 @@
     }
   }
 }
+
+void WriteMSBuildAdditionalInputs(cmsys::ofstream& fout,
+                                  cmLocalGenerator const& lg,
+                                  cmGccDepfileContent const& content)
+{
+  if (content.empty()) {
+    return;
+  }
+
+  // Write a UTF-8 BOM so MSBuild knows the encoding when reading the file.
+  static const char utf8bom[] = { char(0xEF), char(0xBB), char(0xBF) };
+  fout.write(utf8bom, sizeof(utf8bom));
+
+  // Write the format expected by MSBuild CustomBuild AdditionalInputs.
+  const char* sep = "";
+  for (std::string path : content.front().paths) {
+    if (!cmSystemTools::FileIsFullPath(path)) {
+      path =
+        cmSystemTools::CollapseFullPath(path, lg.GetCurrentBinaryDirectory());
+    }
+    std::replace(path.begin(), path.end(), '/', '\\');
+    fout << sep << path;
+    sep = ";";
+  }
+  fout << "\n";
+}
 }
 
 bool cmTransformDepfile(cmDepfileFormat format, const cmLocalGenerator& lg,
@@ -93,6 +121,7 @@
     content = *std::move(result);
   }
 
+  cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(outfile));
   cmsys::ofstream fout(outfile.c_str());
   if (!fout) {
     return false;
@@ -102,6 +131,9 @@
     case cmDepfileFormat::MakeDepfile:
       WriteDepfile(format, fout, lg, content);
       break;
+    case cmDepfileFormat::MSBuildAdditionalInputs:
+      WriteMSBuildAdditionalInputs(fout, lg, content);
+      break;
   }
   return true;
 }
diff --git a/Source/cmTransformDepfile.h b/Source/cmTransformDepfile.h
index ce7cd66..379e8bc 100644
--- a/Source/cmTransformDepfile.h
+++ b/Source/cmTransformDepfile.h
@@ -7,7 +7,8 @@
 enum class cmDepfileFormat
 {
   GccDepfile,
-  MakeDepfile
+  MakeDepfile,
+  MSBuildAdditionalInputs,
 };
 
 class cmLocalGenerator;
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index 98d56df..b79c6fd 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/Source/cmVisualStudio10TargetGenerator.cxx
@@ -768,6 +768,11 @@
         Elem(e1, "Import").Attribute("Project", nasmTargets);
       }
     }
+    if (this->ProjectType == vcxproj && this->HaveCustomCommandDepfile) {
+      std::string depfileTargets =
+        GetCMakeFilePath("Templates/MSBuild/CustomBuildDepFile.targets");
+      Elem(e0, "Import").Attribute("Project", depfileTargets);
+    }
     if (this->ProjectType == csproj) {
       for (std::string const& c : this->Configurations) {
         Elem e1(e0, "PropertyGroup");
@@ -1460,7 +1465,7 @@
     e2.SetHasElements();
   }
   for (std::string const& c : this->Configurations) {
-    cmCustomCommandGenerator ccg(command, c, lg);
+    cmCustomCommandGenerator ccg(command, c, lg, true);
     std::string comment = lg->ConstructComment(ccg);
     comment = cmVS10EscapeComment(comment);
     std::string script = lg->ConstructScript(ccg);
@@ -1524,10 +1529,10 @@
       std::string name = "CustomCommand_" + c + "_" +
         cmSystemTools::ComputeStringMD5(sourcePath);
       this->WriteCustomRuleCSharp(e0, c, name, script, additional_inputs.str(),
-                                  outputs.str(), comment);
+                                  outputs.str(), comment, ccg);
     } else {
       this->WriteCustomRuleCpp(*spe2, c, script, additional_inputs.str(),
-                               outputs.str(), comment, symbolic);
+                               outputs.str(), comment, ccg, symbolic);
     }
   }
 }
@@ -1535,7 +1540,8 @@
 void cmVisualStudio10TargetGenerator::WriteCustomRuleCpp(
   Elem& e2, std::string const& config, std::string const& script,
   std::string const& additional_inputs, std::string const& outputs,
-  std::string const& comment, bool symbolic)
+  std::string const& comment, cmCustomCommandGenerator const& ccg,
+  bool symbolic)
 {
   const std::string cond = this->CalcCondition(config);
   e2.WritePlatformConfigTag("Message", cond, comment);
@@ -1554,13 +1560,29 @@
     // outputs is marked SYMBOLIC and not expected to be created.
     e2.WritePlatformConfigTag("VerifyInputsAndOutputsExist", cond, "false");
   }
+
+  std::string depfile = ccg.GetFullDepfile();
+  if (!depfile.empty()) {
+    this->HaveCustomCommandDepfile = true;
+    std::string internal_depfile = ccg.GetInternalDepfile();
+    ConvertToWindowsSlash(internal_depfile);
+    e2.WritePlatformConfigTag("DepFileAdditionalInputsFile", cond,
+                              internal_depfile);
+  }
 }
 
 void cmVisualStudio10TargetGenerator::WriteCustomRuleCSharp(
   Elem& e0, std::string const& config, std::string const& name,
   std::string const& script, std::string const& inputs,
-  std::string const& outputs, std::string const& comment)
+  std::string const& outputs, std::string const& comment,
+  cmCustomCommandGenerator const& ccg)
 {
+  if (!ccg.GetFullDepfile().empty()) {
+    this->Makefile->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("CSharp target \"", this->GeneratorTarget->GetName(),
+               "\" does not support add_custom_command DEPFILE."));
+  }
   this->CSharpCustomCommandNames.insert(name);
   Elem e1(e0, "Target");
   e1.Attribute("Condition", this->CalcCondition(config));
diff --git a/Source/cmVisualStudio10TargetGenerator.h b/Source/cmVisualStudio10TargetGenerator.h
index 35dbba8..55c5444 100644
--- a/Source/cmVisualStudio10TargetGenerator.h
+++ b/Source/cmVisualStudio10TargetGenerator.h
@@ -16,6 +16,7 @@
 
 class cmComputeLinkInformation;
 class cmCustomCommand;
+class cmCustomCommandGenerator;
 class cmGeneratedFileStream;
 class cmGlobalVisualStudio10Generator;
 class cmLocalVisualStudio10Generator;
@@ -143,13 +144,15 @@
                           std::string const& script,
                           std::string const& additional_inputs,
                           std::string const& outputs,
-                          std::string const& comment, bool symbolic);
+                          std::string const& comment,
+                          cmCustomCommandGenerator const& ccg, bool symbolic);
   void WriteCustomRuleCSharp(Elem& e0, std::string const& config,
                              std::string const& commandName,
                              std::string const& script,
                              std::string const& inputs,
                              std::string const& outputs,
-                             std::string const& comment);
+                             std::string const& comment,
+                             cmCustomCommandGenerator const& ccg);
   void WriteCustomCommands(Elem& e0);
   void WriteCustomCommand(Elem& e0, cmSourceFile const* sf);
   void WriteGroups();
@@ -216,6 +219,7 @@
   bool Managed;
   bool NsightTegra;
   bool Android;
+  bool HaveCustomCommandDepfile = false;
   unsigned int NsightTegraVersion[4];
   bool TargetCompileAsWinRT;
   std::set<std::string> IPOEnabledConfigurations;
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index 84ac189..1f4c0b8 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -1531,6 +1531,8 @@
         format = cmDepfileFormat::GccDepfile;
       } else if (args[3] == "makedepfile") {
         format = cmDepfileFormat::MakeDepfile;
+      } else if (args[3] == "MSBuildAdditionalInputs") {
+        format = cmDepfileFormat::MSBuildAdditionalInputs;
       } else {
         return 1;
       }
diff --git a/Templates/MSBuild/CustomBuildDepFile.targets b/Templates/MSBuild/CustomBuildDepFile.targets
new file mode 100644
index 0000000..2387ab5
--- /dev/null
+++ b/Templates/MSBuild/CustomBuildDepFile.targets
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <!-- Update AdditionalInputs with depfile-discovered inputs.  -->
+  <Target Name="CMakeCustomBuildDepFileAdditionalInputs" BeforeTargets="CustomBuild" Condition="'@(CustomBuild)' != ''">
+    <ItemGroup>
+      <!-- Save original AdditionalInputs generated by CMake.  -->
+      <CustomBuild>
+        <CMakeAdditionalInputs>%(CustomBuild.AdditionalInputs)</CMakeAdditionalInputs>
+      </CustomBuild>
+      <!-- Read depfile-discovered inputs.  -->
+      <CustomBuild Condition="Exists('%(CustomBuild.DepFileAdditionalInputsFile)')">
+        <DepFileAdditionalInputs>$([System.IO.File]::ReadAllText('%(CustomBuild.DepFileAdditionalInputsFile)').TrimEnd())</DepFileAdditionalInputs>
+      </CustomBuild>
+      <!-- Add depfile-discovered inputs to AdditionalInputs.  -->
+      <CustomBuild Condition="'%(CustomBuild.DepFileAdditionalInputs)' != ''">
+        <AdditionalInputs Condition="'%(CustomBuild.AdditionalInputs)' == ''">%(CustomBuild.DepFileAdditionalInputs)</AdditionalInputs>
+        <AdditionalInputs Condition="'%(CustomBuild.AdditionalInputs)' != ''">%(CustomBuild.AdditionalInputs);%(CustomBuild.DepFileAdditionalInputs)</AdditionalInputs>
+      </CustomBuild>
+    </ItemGroup>
+  </Target>
+
+  <!-- Update the tracking log with depfile-discovered inputs.  -->
+  <Target Name="CMakeCustomBuildDepFileTrackingLog" AfterTargets="CustomBuild" Condition="'@(CustomBuild)' != ''">
+    <!-- Compute the tracking log content for each CustomBuild item individually.  -->
+    <ItemGroup>
+      <!-- Read depfile-discovered inputs.  -->
+      <CustomBuild Condition="Exists('%(CustomBuild.DepFileAdditionalInputsFile)')">
+        <DepFileAdditionalInputs>$([System.IO.File]::ReadAllText('%(CustomBuild.DepFileAdditionalInputsFile)').TrimEnd())</DepFileAdditionalInputs>
+      </CustomBuild>
+      <!-- Generate tracking log representation of all inputs.  -->
+      <CustomBuild>
+        <ReadTLog>^%(CustomBuild.Identity)&#xD;&#xA;</ReadTLog>
+      </CustomBuild>
+      <CustomBuild Condition="'%(CustomBuild.CMakeAdditionalInputs)' != ''">
+        <ReadTLog>%(ReadTLog)$([System.String]::Copy('%(CustomBuild.CMakeAdditionalInputs)').Trim(';').Replace(';', '&#xD;&#xA;'))&#xD;&#xA;</ReadTLog>
+      </CustomBuild>
+      <CustomBuild Condition="'%(CustomBuild.DepFileAdditionalInputs)' != ''">
+        <ReadTLog>%(ReadTLog)$([System.String]::Copy('%(CustomBuild.DepFileAdditionalInputs)').Trim(';').Replace(';', '&#xD;&#xA;'))&#xD;&#xA;</ReadTLog>
+      </CustomBuild>
+    </ItemGroup>
+    <!-- Compute the combined tracking log for all CustomBuild items together.  -->
+    <PropertyGroup>
+      <CustomBuildReadTLog>@(CustomBuild->'%(ReadTLog)','')</CustomBuildReadTLog>
+    </PropertyGroup>
+    <!-- Replace the combined tracking log on disk.  -->
+    <WriteLinesToFile File="$(TLogLocation)CustomBuild.read.1.tlog" Overwrite="true" Lines="$(CustomBuildReadTLog.ToUpper())" />
+  </Target>
+</Project>
diff --git a/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake b/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
index 0a80580..f8c20c2 100644
--- a/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
+++ b/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
@@ -172,7 +172,8 @@
   run_cmake(CustomCommandDependencies-BadArgs)
 endif()
 
-if(RunCMake_GENERATOR MATCHES "Make|Ninja|Xcode")
+if(RunCMake_GENERATOR MATCHES "Make|Ninja|Visual Studio|Xcode" AND
+    NOT RunCMake_GENERATOR MATCHES "Visual Studio (9|10)( |$)")
   unset(run_BuildDepends_skip_step_3)
   run_BuildDepends(CustomCommandDepfile)
   set(run_BuildDepends_skip_step_3 1)