[cmake-developers] [PATCH v6] For Windows encode process output to internally used encoding

Dāvis Mosāns davispuh at gmail.com
Sat Aug 13 20:18:24 EDT 2016


Typically Windows applications (eg. MSVC compiler) use current console's
codepage for output to pipes so we need to encode that to internally used
encoding (KWSYS_ENCODING_DEFAULT_CODEPAGE).
---
 Source/CMakeLists.txt              |   2 +
 Source/ProcessOutput.hxx.in        | 160 +++++++++++++++++++++++++++++++++++++
 Source/cmExecProgramCommand.cxx    |   3 +
 Source/cmExecuteProcessCommand.cxx |  11 ++-
 Source/cmProcessTools.cxx          |   9 ++-
 Source/cmSystemTools.cxx           |  11 ++-
 bootstrap                          |   3 +
 7 files changed, 193 insertions(+), 6 deletions(-)
 create mode 100644 Source/ProcessOutput.hxx.in

diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index a790994..eb51683 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -419,6 +419,8 @@ foreach(command_file
 endforeach()
 configure_file(cmCommands.cxx.in ${CMAKE_CURRENT_BINARY_DIR}/cmCommands.cxx @ONLY)
 
+configure_file(ProcessOutput.hxx.in ${CMAKE_CURRENT_BINARY_DIR}/ProcessOutput.hxx)
+
 # Kdevelop only works on UNIX and not windows
 if(UNIX)
   set(SRCS ${SRCS} cmGlobalKdevelopGenerator.cxx)
diff --git a/Source/ProcessOutput.hxx.in b/Source/ProcessOutput.hxx.in
new file mode 100644
index 0000000..ea7b881
--- /dev/null
+++ b/Source/ProcessOutput.hxx.in
@@ -0,0 +1,160 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2016 Kitware, Inc., Insight Software Consortium
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#ifndef ProcessOutput_hxx
+#define ProcessOutput_hxx
+
+#include <string>
+#include <vector>
+#if defined(_WIN32)
+#  ifndef WIN32_LEAN_AND_MEAN
+#    define WIN32_LEAN_AND_MEAN 1
+#  endif
+#  include <windows.h>
+#endif
+
+  class ProcessOutput
+  {
+    public:
+#if defined(_WIN32)
+      static const UINT defaultCodepage = @KWSYS_ENCODING_DEFAULT_CODEPAGE@;
+#endif
+      // must match to KWSYSPE_PIPE_BUFFER_SIZE
+      ProcessOutput(unsigned int maxSize = 1024)
+      {
+#if defined(_WIN32)
+        bufferSize = maxSize;
+        codepage = GetConsoleCP();
+        if (!codepage) {
+          codepage = GetACP();
+        }
+#else
+        static_cast<void>(maxSize);
+#endif
+      }
+
+      ~ProcessOutput()
+      {
+      }
+
+      bool DecodeText(std::string raw, std::string& decoded, size_t id = 0)
+      {
+        bool success = true;
+        decoded = raw;
+#if defined(_WIN32)
+        if (id > 0) {
+          if (rawparts.size() < id) {
+            rawparts.reserve(id);
+            while (rawparts.size() < id) rawparts.push_back(std::string());
+          }
+          raw = rawparts[id - 1] + raw;
+          rawparts[id - 1].clear();
+          decoded = raw;
+        }
+        if (raw.size() > 0 && codepage != defaultCodepage) {
+          success = false;
+          CPINFOEXW cpinfo;
+          if (id > 0 && raw.size() == bufferSize && GetCPInfoExW(codepage, 0, &cpinfo) == 1 && cpinfo.MaxCharSize > 1) {
+            if (cpinfo.MaxCharSize == 2 && cpinfo.LeadByte[0] != 0) {
+              LPSTR prevChar = CharPrevExA(codepage, raw.c_str(), raw.c_str() + raw.size(), 0);
+              bool isLeadByte = (*(prevChar + 1) == 0) && IsDBCSLeadByteEx(codepage, *prevChar);
+              if (isLeadByte) {
+                rawparts[id - 1] += *(raw.end() - 1);
+                raw.resize(raw.size() - 1);
+              }
+              success = DoDecodeText(raw, decoded, NULL);
+            } else {
+              bool restoreDecoded = false;
+              std::string firstDecoded = decoded;
+              wchar_t lastChar = 0;
+              for (UINT i = 0; i < cpinfo.MaxCharSize; i++) {
+                success = DoDecodeText(raw, decoded, &lastChar);
+                if (success && lastChar != 0) {
+                  if (i == 0) {
+                    firstDecoded = decoded;
+                  }
+                  if (lastChar == cpinfo.UnicodeDefaultChar) {
+                    restoreDecoded = true;
+                    rawparts[id - 1] = *(raw.end() - 1) + rawparts[id - 1];
+                    raw.resize(raw.size() - 1);
+                  } else {
+                    restoreDecoded = false;
+                    break;
+                  }
+                } else {
+                  break;
+                }
+              }
+              if (restoreDecoded) {
+                decoded = firstDecoded;
+                rawparts[id - 1].clear();
+              }
+            }
+          } else {
+            success = DoDecodeText(raw, decoded, NULL);
+          }
+        }
+#else
+        static_cast<void>(id);
+#endif
+        return success;
+      }
+
+      bool DecodeText(const char* data, size_t length, std::string& decoded, size_t id = 0)
+      {
+        return DecodeText(std::string(data, length), decoded, id);
+      }
+
+      bool DecodeText(std::vector<char> raw, std::vector<char>& decoded, size_t id = 0)
+      {
+        std::string str;
+        const bool success = DecodeText(std::string(raw.begin(), raw.end()), str, id);
+        decoded.assign(str.begin(), str.end());
+        return success;
+      }
+
+    private:
+#if defined(_WIN32)
+      UINT codepage;
+      unsigned int bufferSize;
+      std::vector<std::string> rawparts;
+
+      bool DoDecodeText(std::string raw, std::string& decoded, wchar_t *lastChar)
+      {
+        bool success = false;
+        const int wlength = MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()), NULL, 0);
+        wchar_t* wdata = new wchar_t[wlength];
+        int r = MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()), wdata, wlength);
+        if (r > 0) {
+          if (lastChar) {
+            *lastChar = 0;
+            if ((wlength >= 2 && wdata[wlength - 2] != wdata[wlength - 1]) || wlength >= 1) {
+              *lastChar = wdata[wlength - 1];
+            }
+          }
+          int length = WideCharToMultiByte(defaultCodepage, 0, wdata, wlength, NULL, 0, NULL, NULL);
+          char *data = new char[length + 1];
+          r = WideCharToMultiByte(defaultCodepage, 0, wdata, wlength, data, length, NULL, NULL);
+          if (r > 0) {
+            data[length] = '\0';
+            decoded = data;
+            success = true;
+          }
+          delete[] data;
+        }
+        delete[] wdata;
+        return success;
+      }
+#endif
+  };
+
+#endif
+
diff --git a/Source/cmExecProgramCommand.cxx b/Source/cmExecProgramCommand.cxx
index 58bbc31..fb2e11c 100644
--- a/Source/cmExecProgramCommand.cxx
+++ b/Source/cmExecProgramCommand.cxx
@@ -12,6 +12,7 @@
 #include "cmExecProgramCommand.h"
 
 #include "cmSystemTools.h"
+#include "ProcessOutput.hxx"
 
 #include <cmsys/Process.h>
 
@@ -219,6 +220,7 @@ bool cmExecProgramCommand::RunCommand(const char* command, std::string& output,
   int length;
   char* data;
   int p;
+  ProcessOutput processOutput;
   while ((p = cmsysProcess_WaitForData(cp, &data, &length, CM_NULLPTR), p)) {
     if (p == cmsysProcess_Pipe_STDOUT || p == cmsysProcess_Pipe_STDERR) {
       if (verbose) {
@@ -230,6 +232,7 @@ bool cmExecProgramCommand::RunCommand(const char* command, std::string& output,
 
   // All output has been read.  Wait for the process to exit.
   cmsysProcess_WaitForExit(cp, CM_NULLPTR);
+  processOutput.DecodeText(output, output);
 
   // Check the result of running the process.
   std::string msg;
diff --git a/Source/cmExecuteProcessCommand.cxx b/Source/cmExecuteProcessCommand.cxx
index d97b25f..38f1a15 100644
--- a/Source/cmExecuteProcessCommand.cxx
+++ b/Source/cmExecuteProcessCommand.cxx
@@ -12,6 +12,7 @@
 #include "cmExecuteProcessCommand.h"
 
 #include "cmSystemTools.h"
+#include "ProcessOutput.hxx"
 
 #include <cmsys/Process.h>
 
@@ -228,17 +229,21 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
   int length;
   char* data;
   int p;
+  ProcessOutput processOutput;
+  std::string strdata;
   while ((p = cmsysProcess_WaitForData(cp, &data, &length, CM_NULLPTR), p)) {
     // Put the output in the right place.
     if (p == cmsysProcess_Pipe_STDOUT && !output_quiet) {
       if (output_variable.empty()) {
-        cmSystemTools::Stdout(data, length);
+        processOutput.DecodeText(data, length, strdata, 1);
+        cmSystemTools::Stdout(strdata.c_str(), strdata.size());
       } else {
         cmExecuteProcessCommandAppend(tempOutput, data, length);
       }
     } else if (p == cmsysProcess_Pipe_STDERR && !error_quiet) {
       if (error_variable.empty()) {
-        cmSystemTools::Stderr(data, length);
+        processOutput.DecodeText(data, length, strdata, 2);
+        cmSystemTools::Stderr(strdata.c_str(), strdata.size());
       } else {
         cmExecuteProcessCommandAppend(tempError, data, length);
       }
@@ -247,6 +252,8 @@ bool cmExecuteProcessCommand::InitialPass(std::vector<std::string> const& args,
 
   // All output has been read.  Wait for the process to exit.
   cmsysProcess_WaitForExit(cp, CM_NULLPTR);
+  processOutput.DecodeText(tempOutput, tempOutput);
+  processOutput.DecodeText(tempError, tempError);
 
   // Fix the text in the output strings.
   cmExecuteProcessCommandFixText(tempOutput, output_strip_trailing_whitespace);
diff --git a/Source/cmProcessTools.cxx b/Source/cmProcessTools.cxx
index 34b8df2..a20a04c 100644
--- a/Source/cmProcessTools.cxx
+++ b/Source/cmProcessTools.cxx
@@ -10,6 +10,7 @@
   See the License for more information.
 ============================================================================*/
 #include "cmProcessTools.h"
+#include "ProcessOutput.hxx"
 
 #include <cmsys/Process.h>
 
@@ -20,14 +21,18 @@ void cmProcessTools::RunProcess(struct cmsysProcess_s* cp, OutputParser* out,
   char* data = CM_NULLPTR;
   int length = 0;
   int p;
+  ProcessOutput processOutput;
+  std::string strdata;
   while ((out || err) &&
          (p = cmsysProcess_WaitForData(cp, &data, &length, CM_NULLPTR), p)) {
     if (out && p == cmsysProcess_Pipe_STDOUT) {
-      if (!out->Process(data, length)) {
+      processOutput.DecodeText(data, length, strdata, 1);
+      if (!out->Process(strdata.c_str(), int(strdata.size()))) {
         out = CM_NULLPTR;
       }
     } else if (err && p == cmsysProcess_Pipe_STDERR) {
-      if (!err->Process(data, length)) {
+      processOutput.DecodeText(data, length, strdata, 2);
+      if (!err->Process(strdata.c_str(), int(strdata.size()))) {
         err = CM_NULLPTR;
       }
     }
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index 5745a01..da34d38 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -26,6 +26,7 @@
 #include <cmsys/Glob.hxx>
 #include <cmsys/RegularExpression.hxx>
 #include <cmsys/System.h>
+#include "ProcessOutput.hxx"
 #if defined(CMAKE_BUILD_WITH_CMAKE)
 #include "cmArchiveWrite.h"
 #include "cmLocale.h"
@@ -612,6 +613,8 @@ bool cmSystemTools::RunSingleCommand(std::vector<std::string> const& command,
   char* data;
   int length;
   int pipe;
+  ProcessOutput processOutput;
+  std::string strdata;
   if (outputflag != OUTPUT_PASSTHROUGH &&
       (captureStdOut || captureStdErr || outputflag != OUTPUT_NONE)) {
     while ((pipe = cmsysProcess_WaitForData(cp, &data, &length, CM_NULLPTR)) >
@@ -627,14 +630,16 @@ bool cmSystemTools::RunSingleCommand(std::vector<std::string> const& command,
 
       if (pipe == cmsysProcess_Pipe_STDOUT) {
         if (outputflag != OUTPUT_NONE) {
-          cmSystemTools::Stdout(data, length);
+          processOutput.DecodeText(data, length, strdata, 1);
+          cmSystemTools::Stdout(strdata.c_str(), strdata.size());
         }
         if (captureStdOut) {
           tempStdOut.insert(tempStdOut.end(), data, data + length);
         }
       } else if (pipe == cmsysProcess_Pipe_STDERR) {
         if (outputflag != OUTPUT_NONE) {
-          cmSystemTools::Stderr(data, length);
+          processOutput.DecodeText(data, length, strdata, 2);
+          cmSystemTools::Stderr(strdata.c_str(), strdata.size());
         }
         if (captureStdErr) {
           tempStdErr.insert(tempStdErr.end(), data, data + length);
@@ -646,9 +651,11 @@ bool cmSystemTools::RunSingleCommand(std::vector<std::string> const& command,
   cmsysProcess_WaitForExit(cp, CM_NULLPTR);
   if (captureStdOut) {
     captureStdOut->assign(tempStdOut.begin(), tempStdOut.end());
+    processOutput.DecodeText(*captureStdOut, *captureStdOut);
   }
   if (captureStdErr) {
     captureStdErr->assign(tempStdErr.begin(), tempStdErr.end());
+    processOutput.DecodeText(*captureStdErr, *captureStdErr);
   }
 
   bool result = true;
diff --git a/bootstrap b/bootstrap
index 742fa2b..ae36e54 100755
--- a/bootstrap
+++ b/bootstrap
@@ -1294,6 +1294,9 @@ for h in Configure VersionConfig; do
   fi
 done
 
+cmake_replace_string "${cmake_source_dir}/Source/ProcessOutput.hxx.in" \
+    "${cmake_bootstrap_dir}/ProcessOutput.hxx" KWSYS_ENCODING_DEFAULT_CODEPAGE CP_ACP
+
 # Prepare KWSYS
 cmake_kwsys_config_replace_string \
   "${cmake_source_dir}/Source/kwsys/Configure.hxx.in" \
-- 
2.9.2



More information about the cmake-developers mailing list