From 8ab2097c01f1fc3490de9f556bbc0fb179816284 Mon Sep 17 00:00:00 2001
From: Martin Ankerl <martin.ankerl@gmail.com>
Date: Fri, 20 May 2016 09:23:56 +0200
Subject: [PATCH] Support for many custom commands in Windows.

Windows has a limit of 8191 characters per command line, so this patch
creates a batch file instead if the commands get too long.

don't be noisy in the custom command, @echo off

Uses a hash of the commands as the filename for a .cmd file in case
command line is too long. Also, this has a boolean parameter to check if
no ninja variables are set. Otherwise we can't generate the file.
---
 Source/cmLocalNinjaGenerator.cxx | 55 +++++++++++++++++++++++++++++++++++++---
 Source/cmLocalNinjaGenerator.h   |  2 +-
 2 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/Source/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx
index c7e1a90f8..1c22d9e 100644
--- a/Source/cmLocalNinjaGenerator.cxx
+++ b/Source/cmLocalNinjaGenerator.cxx
@@ -12,6 +12,7 @@
 ============================================================================*/
 #include "cmLocalNinjaGenerator.h"
 
+#include "cmCryptoHash.h"
 #include "cmCustomCommandGenerator.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalNinjaGenerator.h"
@@ -20,6 +21,7 @@
 #include "cmSourceFile.h"
 #include "cmState.h"
 #include "cmake.h"
+#include <cmsys/Base64.h>
 
 #include <assert.h>
 
@@ -286,7 +288,7 @@ void cmLocalNinjaGenerator::AppendCustomCommandDeps(
 }
 
 std::string cmLocalNinjaGenerator::BuildCommandLine(
-  const std::vector<std::string>& cmdLines)
+  const std::vector<std::string>& cmdLines, bool cmdUsesNinjaVariables)
 {
   // If we have no commands but we need to build a command anyway, use ":".
   // This happens when building a POST_BUILD value for link targets that
@@ -299,9 +301,9 @@ std::string cmLocalNinjaGenerator::BuildCommandLine(
 #endif
 
   std::ostringstream cmd;
+#ifdef _WIN32
   for (std::vector<std::string>::const_iterator li = cmdLines.begin();
        li != cmdLines.end(); ++li)
-#ifdef _WIN32
   {
     if (li != cmdLines.begin()) {
       cmd << " && ";
@@ -313,7 +315,54 @@ std::string cmLocalNinjaGenerator::BuildCommandLine(
   if (cmdLines.size() > 1) {
     cmd << "\"";
   }
+
+  // windows command line cannot be longer than 8191 https://support.microsoft.com/en-us/kb/830473
+  if (cmd.str().size() > 8191) {
+    // fail if command is too long and we can't generate it due to ninja variables
+    if (cmdUsesNinjaVariables) {
+      std::ostringstream err;
+      err << "CMake could not generate Ninja command since it uses more than 8191 characters"
+        " and ninja variables prevented it from being generated as an external file. You "
+        "probably need to select a different build tool.";
+      cmSystemTools::Error(err.str().c_str());
+      cmSystemTools::SetFatalErrorOccured();
+      return cmd.str();
+    }
+
+    // we need a command file. Create one from the hash of the commandfile.
+    cmCryptoHashSHA1 hasher;
+    const std::string cmdFile = hasher.HashString(cmd.str()) + ".cmd";
+
+    // TODO fail if can't open file
+    std::ofstream fout(cmdFile);
+    if (!fout.is_open()) {
+      std::ostringstream err;
+      err << "CMake failed to create " << cmdFile;
+      cmSystemTools::Error(err.str().c_str());
+      cmSystemTools::SetFatalErrorOccured();
+      return cmd.str();
+    }
+
+    // cmd just calls the batch file
+    cmd.clear();
+    cmd.str("");
+    cmd << "cmd.exe /C " << cmdFile;
+
+    // don't be noisy
+    fout << "@echo off" << std::endl;
+    for (std::vector<std::string>::const_iterator li = cmdLines.begin();
+      li != cmdLines.end(); ++li)
+    {
+      if (li != cmdLines.begin()) {
+        fout << "if %errorlevel% neq 0 exit /b %errorlevel%" << std::endl;
+      }
+      fout << *li << std::endl;
+    }
+  }
+
 #else
+  for (std::vector<std::string>::const_iterator li = cmdLines.begin();
+    li != cmdLines.end(); ++li)
   {
     if (li != cmdLines.begin()) {
       cmd << " && ";
@@ -399,7 +448,7 @@ void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
       cmNinjaDeps(), orderOnlyDeps, cmNinjaVars());
   } else {
     this->GetGlobalNinjaGenerator()->WriteCustomCommandBuild(
-      this->BuildCommandLine(cmdLines), this->ConstructComment(ccg),
+      this->BuildCommandLine(cmdLines, false), this->ConstructComment(ccg),
       "Custom command for " + ninjaOutputs[0], cc->GetUsesTerminal(),
       /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, ninjaDeps,
       orderOnlyDeps);
diff --git a/Source/cmLocalNinjaGenerator.h b/Source/cmLocalNinjaGenerator.h
index afaa24c..b0f1f5b 100644
--- a/Source/cmLocalNinjaGenerator.h
+++ b/Source/cmLocalNinjaGenerator.h
@@ -60,7 +60,7 @@ public:
     cmLocalGenerator::ExpandRuleVariables(string, replaceValues);
   }
 
-  std::string BuildCommandLine(const std::vector<std::string>& cmdLines);
+  std::string BuildCommandLine(const std::vector<std::string>& cmdLines, bool cmdUsesNinjaVariables = true);
 
   void AppendTargetOutputs(cmGeneratorTarget* target, cmNinjaDeps& outputs);
   void AppendTargetDepends(cmGeneratorTarget* target, cmNinjaDeps& outputs);
-- 
2.6.2.windows.1

