소스 검색

Merge topic 'stdio-console'

ce6b0f9e10 ci: Drop unused build artifact
7cf81cdb04 Merge branch 'upstream-KWSys' into stdio-console
ad2d94cb9d KWSys 2025-05-14 (1d72955e)
40b0db5ffb cmConsoleBuf: Remove unused infrastructure
57b24e8fef StdIo: Init() automatically in Console()
3e88020aed StdIo: Replace uses of KWSys ConsoleBuf with StdIo::Console
f9f1f9a8cd StdIo: Add a Windows Console adaptor for cin, cout, and cerr

Acked-by: Kitware Robot <[email protected]>
Merge-request: !10769
Brad King 5 달 전
부모
커밋
c4b4e26019

+ 0 - 1
.gitlab/artifacts.yml

@@ -39,7 +39,6 @@
             - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*Lib/*LibTests
             - ${CMAKE_CI_BUILD_DIR}/Tests/CMake*Lib/*LibTests.exe
             - ${CMAKE_CI_BUILD_DIR}/Source/kwsys/cmsysTest*
-            - ${CMAKE_CI_BUILD_DIR}/Source/kwsys/testConsoleBufChild.exe
             - ${CMAKE_CI_BUILD_DIR}/Utilities/cmcurl/curltest
             - ${CMAKE_CI_BUILD_DIR}/Utilities/cmcurl/curltest.exe
             - ${CMAKE_CI_BUILD_DIR}/Utilities/KWIML/test/kwiml_test

+ 2 - 2
Source/CMakeLists.txt

@@ -148,8 +148,6 @@ add_library(
   cmComputeTargetDepends.cxx
   cmConfigureLog.h
   cmConfigureLog.cxx
-  cmConsoleBuf.h
-  cmConsoleBuf.cxx
   cmConstStack.h
   cmConstStack.tcc
   cmCPackPropertiesGenerator.h
@@ -468,6 +466,8 @@ add_library(
   cmStateSnapshot.cxx
   cmStateSnapshot.h
   cmStateTypes.h
+  cmStdIoConsole.h
+  cmStdIoConsole.cxx
   cmStdIoInit.h
   cmStdIoInit.cxx
   cmStdIoStream.h

+ 2 - 7
Source/CPack/cpack.cxx

@@ -24,7 +24,6 @@
 #include "cmCPackGeneratorFactory.h"
 #include "cmCPackLog.h"
 #include "cmCommandLineArgument.h"
-#include "cmConsoleBuf.h"
 #include "cmDocumentation.h"
 #include "cmDocumentationEntry.h"
 #include "cmGlobalGenerator.h"
@@ -33,7 +32,7 @@
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateSnapshot.h"
-#include "cmStdIoInit.h"
+#include "cmStdIoConsole.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -90,11 +89,7 @@ std::vector<cmDocumentationEntry> makeGeneratorDocs(
 // this is CPack.
 int main(int argc, char const* const* argv)
 {
-  cm::StdIo::Init();
-
-  // Replace streambuf so we can output Unicode to console
-  cmConsoleBuf consoleBuf;
-  consoleBuf.SetUTF8Pipes();
+  cm::StdIo::Console console;
 
   cmsys::Encoding::CommandLineArguments args =
     cmsys::Encoding::CommandLineArguments::Main(argc, argv);

+ 0 - 1
Source/Modules/CMakeBuildUtilities.cmake

@@ -21,7 +21,6 @@ set(KWSYS_USE_Base64 1)
 set(KWSYS_USE_MD5 1)
 set(KWSYS_USE_Process 1)
 set(KWSYS_USE_CommandLineArguments 1)
-set(KWSYS_USE_ConsoleBuf 1)
 set(KWSYS_HEADER_ROOT ${CMake_BINARY_DIR}/Source)
 set(KWSYS_INSTALL_DOC_DIR "${CMAKE_DOC_DIR}")
 if(CMake_NO_CXX_STANDARD)

+ 0 - 21
Source/cmConsoleBuf.cxx

@@ -1,21 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file LICENSE.rst or https://cmake.org/licensing for details.  */
-#include "cmConsoleBuf.h"
-
-#if defined(_WIN32) && !defined(CMAKE_BOOTSTRAP)
-cmConsoleBuf::cmConsoleBuf()
-  : m_ConsoleOut(std::cout)
-  , m_ConsoleErr(std::cerr, true)
-{
-}
-#else
-cmConsoleBuf::cmConsoleBuf() = default;
-#endif
-
-void cmConsoleBuf::SetUTF8Pipes()
-{
-#if defined(_WIN32) && !defined(CMAKE_BOOTSTRAP)
-  m_ConsoleOut.SetUTF8Pipes();
-  m_ConsoleErr.SetUTF8Pipes();
-#endif
-}

+ 0 - 23
Source/cmConsoleBuf.h

@@ -1,23 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file LICENSE.rst or https://cmake.org/licensing for details.  */
-#pragma once
-
-#include "cmConfigure.h" // IWYU pragma: keep
-
-#if defined(_WIN32) && !defined(CMAKE_BOOTSTRAP)
-#  include "cmsys/ConsoleBuf.hxx"
-#endif
-
-class cmConsoleBuf
-{
-#if defined(_WIN32) && !defined(CMAKE_BOOTSTRAP)
-  cmsys::ConsoleBuf::Manager m_ConsoleOut;
-  cmsys::ConsoleBuf::Manager m_ConsoleErr;
-#endif
-public:
-  cmConsoleBuf();
-  ~cmConsoleBuf() = default;
-  cmConsoleBuf(cmConsoleBuf const&) = delete;
-  cmConsoleBuf& operator=(cmConsoleBuf const&) = delete;
-  void SetUTF8Pipes();
-};

+ 361 - 0
Source/cmStdIoConsole.cxx

@@ -0,0 +1,361 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#include "cmStdIoConsole.h"
+
+#ifdef _WIN32
+#  include <cstddef>
+#  include <cstdlib>
+#  include <ios>
+#  include <streambuf>
+#  include <utility>
+#  include <vector>
+
+#  include <cm/memory>
+
+#  include <windows.h>
+
+#  include <fcntl.h> // for _O_BINARY
+#  include <io.h>    // for _setmode
+
+#  include "cm_utf8.h"
+
+#  include "cmStdIoStream.h"
+#endif
+
+namespace cm {
+namespace StdIo {
+
+namespace {
+
+#ifdef _WIN32
+// Base class for a streambuf that reads or writes a Windows Console.
+class ConsoleBuf : public std::streambuf
+{
+public:
+  ConsoleBuf(HANDLE console)
+    : console_(console)
+  {
+  }
+
+  ~ConsoleBuf() throw() override {}
+
+protected:
+  HANDLE console_ = nullptr;
+};
+
+// A streambuf that reads from a Windows Console using wide-character
+// encoding to avoid conversion through the console output code page.
+class ConsoleBufRead : public ConsoleBuf
+{
+public:
+  ConsoleBufRead(HANDLE console, DWORD consoleMode)
+    : ConsoleBuf(console)
+    , ConsoleMode_(consoleMode)
+  {
+  }
+
+  ~ConsoleBufRead() throw() override {}
+
+protected:
+  // Called to read an input character when the input buffer may be empty.
+  int_type underflow() override
+  {
+    // If the input buffer is not empty, return the next input character.
+    if (this->gptr() < this->egptr()) {
+      return traits_type::to_int_type(*this->gptr());
+    }
+
+    // The input buffer is empty.  Read more input from the console.
+    static constexpr std::size_t kBufSize = 4096;
+    this->TmpW_.resize(kBufSize);
+    DWORD wlen = 0;
+    if (!ReadConsoleW(this->console_, this->TmpW_.data(),
+                      DWORD(this->TmpW_.size()), &wlen, nullptr)) {
+      // Failure.  Nothing was read.
+      return traits_type::eof();
+    }
+
+    // Emulate ReadFile behavior when the console is in "cooked mode".
+    // Treat a leading Ctrl+Z as EOF.
+    static constexpr char ctrl_z = 26; // Ctrl+Z is Ctrl + 26th letter.
+    if ((this->ConsoleMode_ & ENABLE_LINE_INPUT) &&
+        (wlen > 0 && this->TmpW_.front() == ctrl_z)) {
+      wlen = 0;
+    }
+
+    // Convert the wide-character encoding from the console to our
+    // internal UTF-8 narrow encoding.
+    if (int nlen =
+          WideCharToMultiByte(CP_UTF8, 0, this->TmpW_.data(), int(wlen),
+                              nullptr, 0, nullptr, nullptr)) {
+      this->Buf_.resize(nlen);
+      if (WideCharToMultiByte(CP_UTF8, 0, this->TmpW_.data(), int(wlen),
+                              this->Buf_.data(), int(nlen), nullptr,
+                              nullptr)) {
+        // The converted content is now in the input buffer.
+        this->setg_();
+
+        // Success.  Return the next input character.
+        return traits_type::to_int_type(*this->gptr());
+      }
+    }
+
+    // Failure.  Nothing was read.
+    return traits_type::eof();
+  }
+
+private:
+  DWORD ConsoleMode_ = 0;
+  std::vector<char> Buf_;
+  std::vector<wchar_t> TmpW_;
+
+  // Set input buffer pointers.
+  void setg_()
+  {
+    this->setg(this->Buf_.data(), this->Buf_.data(),
+               this->Buf_.data() + this->Buf_.size());
+  }
+};
+
+// A streambuf that writes to a Windows Console using wide-character
+// encoding to avoid conversion through the console output code page.
+class ConsoleBufWrite : public ConsoleBuf
+{
+public:
+  ConsoleBufWrite(HANDLE console)
+    : ConsoleBuf(console)
+  {
+    this->setp_();
+  }
+
+  ~ConsoleBufWrite() throw() override { sync(); }
+
+protected:
+  // Called to sync input and output buffers with the underlying device.
+  int sync() override
+  {
+    // Flush buffered output, if any.
+    if (this->pptr() != this->pbase()) {
+      // Use overflow() to flush the entire output buffer.
+      // It returns eof on failure.
+      if (traits_type::eq_int_type(this->overflow(), traits_type::eof())) {
+        return -1;
+      }
+    }
+    return 0;
+  }
+
+  // Called to flush at least some content from the output buffer.
+  int_type overflow(int_type ch = traits_type::eof()) override
+  {
+    std::size_t nlen;     // Number of chars to emit.
+    std::size_t rlen = 0; // Number of chars to roll over.
+    if (traits_type::eq_int_type(ch, traits_type::eof())) {
+      // Our caller wants to flush the entire buffer.  If there is a
+      // trailing partial codepoint, it's the caller's fault.
+      nlen = this->pptr() - this->pbase();
+
+      // If the buffer is empty, trivially succeed.
+      if (nlen == 0) {
+        return traits_type::not_eof(ch);
+      }
+    } else {
+      // Our caller had no room for this character in the buffer.
+      // However, setp_() reserved one byte for us to store it.
+      *this->pptr() = traits_type::to_char_type(ch);
+      this->pbump(1);
+
+      // Flush all complete codepoints, of which we expect at least one.
+      // If there is a trailing partial codepoint, roll over those chars.
+      char const* p = this->pptr_();
+      nlen = p - this->pbase();
+      rlen = this->pptr() - p;
+    }
+
+    // Fail unless we emit at least one (wide) character.
+    int_type result = traits_type::eof();
+
+    // Convert our internal UTF-8 narrow encoding to wide-character
+    // encoding to write to the console.
+    if (int wlen = MultiByteToWideChar(CP_UTF8, 0, this->pbase(), int(nlen),
+                                       nullptr, 0)) {
+      this->TmpW_.resize(wlen);
+      if (MultiByteToWideChar(CP_UTF8, 0, this->pbase(), int(nlen),
+                              this->TmpW_.data(), int(wlen)) &&
+          WriteConsoleW(this->console_, this->TmpW_.data(), wlen, nullptr,
+                        nullptr)) {
+        result = traits_type::not_eof(ch);
+      }
+    }
+
+    // Remove emitted contents from the buffer.
+    this->Buf_.erase(this->Buf_.begin(), this->Buf_.begin() + nlen);
+
+    // Re-initialize the output buffer.
+    this->setp_();
+
+    // Move the put-pointer past the rollover content.
+    this->pbump(rlen);
+
+    return result;
+  }
+
+private:
+  std::vector<char> Buf_;
+  std::vector<wchar_t> TmpW_;
+
+  // Initialize the output buffer and set its put-pointer.
+  void setp_()
+  {
+    // Allocate the output buffer.
+    static constexpr std::size_t kBufSize = 4096;
+    this->Buf_.resize(kBufSize);
+
+    // Reserve one byte for the overflow() character.
+    this->setp(this->Buf_.data(), this->Buf_.data() + this->Buf_.size() - 1);
+  }
+
+  // Return pptr() adjusted backward past a partial codepoint.
+  char const* pptr_() const
+  {
+    char const* p = this->pptr();
+    while (p != this->pbase()) {
+      --p;
+      switch (cm_utf8_ones[static_cast<unsigned char>(*p)]) {
+        case 0: // 0xxx xxxx: starts codepoint of size 1
+          return p + 1;
+        case 1: // 10xx xxxx: continues a codepoint
+          continue;
+        case 2: // 110x xxxx: starts codepoint of size 2
+          return ((p + 2) <= this->pptr()) ? (p + 2) : p;
+        case 3: // 1110 xxxx: starts codepoint of size 3
+          return ((p + 3) <= this->pptr()) ? (p + 3) : p;
+        case 4: // 1111 0xxx: starts codepoint of size 4
+          return ((p + 4) <= this->pptr()) ? (p + 4) : p;
+        default: // invalid byte
+          // Roll over the invalid byte.
+          // The next overflow() will fail to convert it.
+          return p;
+      }
+    }
+    // No complete codepoint found.  This overflow() will fail.
+    return p;
+  }
+};
+
+#endif
+
+} // anonymous namespace
+
+#ifdef _WIN32
+class Console::Impl
+{
+protected:
+  class RAII
+  {
+    std::ios* IOS_ = nullptr;
+    int FD_ = -1;
+    std::unique_ptr<ConsoleBuf> ConsoleBuf_;
+    std::streambuf* OldStreamBuf_ = nullptr;
+    int OldMode_ = 0;
+
+    RAII(Stream& s);
+    void Init();
+
+  public:
+    RAII(IStream& is);
+    RAII(OStream& os);
+    ~RAII();
+  };
+  RAII In_;
+  RAII Out_;
+  RAII Err_;
+
+public:
+  Impl();
+  ~Impl();
+};
+
+Console::Impl::RAII::RAII(Stream& s)
+  : IOS_(&s.IOS())
+  , FD_(s.FD())
+{
+}
+
+Console::Impl::RAII::RAII(IStream& is)
+  : RAII(static_cast<Stream&>(is))
+{
+  DWORD mode;
+  if (is.Console() && GetConsoleMode(is.Console(), &mode) &&
+      GetConsoleCP() != CP_UTF8) {
+    // The input stream reads from a console whose input code page is not
+    // UTF-8.  Use a ConsoleBufRead to read wide-character encoding.
+    this->ConsoleBuf_ = cm::make_unique<ConsoleBufRead>(is.Console(), mode);
+  }
+  this->Init();
+}
+
+Console::Impl::RAII::RAII(OStream& os)
+  : RAII(static_cast<Stream&>(os))
+{
+  DWORD mode;
+  if (os.Console() && GetConsoleMode(os.Console(), &mode) &&
+      GetConsoleOutputCP() != CP_UTF8) {
+    // The output stream writes to a console whose output code page is not
+    // UTF-8.  Use a ConsoleBufWrite to write wide-character encoding.
+    this->ConsoleBuf_ = cm::make_unique<ConsoleBufWrite>(os.Console());
+  }
+  this->Init();
+}
+
+void Console::Impl::RAII::Init()
+{
+  if (this->ConsoleBuf_) {
+    this->OldStreamBuf_ = this->IOS_->rdbuf(this->ConsoleBuf_.get());
+  } else if (this->FD_ >= 0) {
+    // The stream reads/writes a pipe, a file, or a console whose code
+    // page is UTF-8.  Read/write UTF-8 using the default streambuf,
+    // but disable newline conversion to match ConsoleBuf behavior.
+    this->OldMode_ = _setmode(this->FD_, _O_BINARY);
+  }
+}
+
+Console::Impl::RAII::~RAII()
+{
+  if (this->ConsoleBuf_) {
+    this->IOS_->rdbuf(this->OldStreamBuf_);
+    this->OldStreamBuf_ = nullptr;
+    this->ConsoleBuf_.reset();
+  } else if (this->FD_ >= 0) {
+    this->IOS_->rdbuf()->pubsync();
+    _setmode(this->FD_, this->OldMode_);
+    this->OldMode_ = 0;
+  }
+  this->FD_ = -1;
+  this->IOS_ = nullptr;
+}
+
+Console::Impl::Impl()
+  : In_(In())
+  , Out_(Out())
+  , Err_(Err())
+{
+}
+
+Console::Impl::~Impl() = default;
+
+Console::Console()
+  : Impl_(cm::make_unique<Impl>())
+{
+}
+#else
+Console::Console() = default;
+#endif
+
+Console::~Console() = default;
+
+Console::Console(Console&&) noexcept = default;
+Console& Console::operator=(Console&&) noexcept = default;
+
+}
+}

+ 49 - 0
Source/cmStdIoConsole.h

@@ -0,0 +1,49 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmStdIoInit.h"
+
+#ifdef _WIN32
+#  include <memory>
+#endif
+
+namespace cm {
+namespace StdIo {
+
+/**
+ * On Windows, enables I/O with `cin`, `cout`, and `cerr` in UTF-8 encoding.
+ * On non-Windows platforms, does nothing.
+ *
+ * Construct an instance of this at the beginning of `main`:
+ *
+ * * If `cin`, `cout`, or `cerr` is attached to a Windows Console whose
+ *   input/output code page is not UTF-8, this replaces its `streambuf`
+ *   with one that reads/writes from/to the console using wide-character
+ *   Windows APIs to avoid limitations of the code page's narrow encoding.
+ *
+ * * If `cin`, `cout`, or `cerr` is not attached to a Windows Console,
+ *   this sets its stream to binary mode for consistency with the case
+ *   that it's attached to a console.
+ *
+ * Destroy the instance of this to restore the original `streambuf`s.
+ */
+class Console : private Init
+{
+#ifdef _WIN32
+  class Impl;
+  std::unique_ptr<Impl> Impl_;
+#endif
+public:
+  Console();
+  ~Console(); // NOLINT(performance-trivially-destructible)
+  Console(Console&&) noexcept;
+  Console(Console const&) = delete;
+  Console& operator=(Console&&) noexcept;
+  Console& operator=(Console const&) = delete;
+};
+
+}
+}

+ 5 - 11
Source/cmakemain.cxx

@@ -16,7 +16,6 @@
 #include <utility>
 #include <vector>
 
-#include <cm/memory>
 #include <cm/optional>
 #include <cmext/algorithm>
 
@@ -24,7 +23,6 @@
 
 #include "cmBuildOptions.h"
 #include "cmCommandLineArgument.h"
-#include "cmConsoleBuf.h"
 #include "cmDocumentationEntry.h"
 #include "cmGlobalGenerator.h"
 #include "cmInstallScriptHandler.h"
@@ -35,7 +33,7 @@
 #include "cmMessageMetadata.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
-#include "cmStdIoInit.h"
+#include "cmStdIoConsole.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -139,13 +137,13 @@ cmDocumentationEntry const cmDocumentationOptions[35] = {
 #endif
 
 int do_command(int ac, char const* const* av,
-               std::unique_ptr<cmConsoleBuf> consoleBuf)
+               cm::optional<cm::StdIo::Console> console)
 {
   std::vector<std::string> args;
   args.reserve(ac - 1);
   args.emplace_back(av[0]);
   cm::append(args, av + 2, av + ac);
-  return cmcmd::ExecuteCMakeCommand(args, std::move(consoleBuf));
+  return cmcmd::ExecuteCMakeCommand(args, std::move(console));
 }
 
 cmMakefile* cmakemainGetMakefile(cmake* cm)
@@ -1143,11 +1141,7 @@ int do_open(int ac, char const* const* av)
 
 int main(int ac, char const* const* av)
 {
-  cm::StdIo::Init();
-
-  // Replace streambuf so we can output Unicode to console
-  auto consoleBuf = cm::make_unique<cmConsoleBuf>();
-  consoleBuf->SetUTF8Pipes();
+  cm::optional<cm::StdIo::Console> console = cm::StdIo::Console();
 
   cmsys::Encoding::CommandLineArguments args =
     cmsys::Encoding::CommandLineArguments::Main(ac, av);
@@ -1170,7 +1164,7 @@ int main(int ac, char const* const* av)
       return do_workflow(ac, av);
     }
     if (strcmp(av[1], "-E") == 0) {
-      return do_command(ac, av, std::move(consoleBuf));
+      return do_command(ac, av, std::move(console));
     }
     if (strcmp(av[1], "--print-config-dir") == 0) {
       std::cout << cmSystemTools::ConvertToOutputPath(

+ 8 - 8
Source/cmcmd.cxx

@@ -12,7 +12,6 @@
 #include <fcntl.h>
 
 #include "cmCommandLineArgument.h"
-#include "cmConsoleBuf.h"
 #include "cmCryptoHash.h"
 #include "cmDuration.h"
 #include "cmGlobalGenerator.h"
@@ -26,6 +25,7 @@
 #include "cmState.h"
 #include "cmStateDirectory.h"
 #include "cmStateSnapshot.h"
+#include "cmStdIoConsole.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmTransformDepfile.h"
@@ -689,7 +689,7 @@ int cmcmd::HandleCoCompileCommands(std::vector<std::string> const& args)
 }
 
 int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
-                               std::unique_ptr<cmConsoleBuf> consoleBuf)
+                               cm::optional<cm::StdIo::Console> console)
 {
   // IF YOU ADD A NEW COMMAND, DOCUMENT IT ABOVE and in cmakemain.cxx
   if (args.size() > 1) {
@@ -1191,7 +1191,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
         if (arg == "-") {
           doing_options = false;
           // Destroy console buffers to drop cout/cerr encoding transform.
-          consoleBuf.reset();
+          console.reset();
           cmCatFile(arg);
         } else if (doing_options && cmHasLiteralPrefix(arg, "-")) {
           if (arg == "--") {
@@ -1215,7 +1215,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
           // Ignore empty files, this is not an error
         } else {
           // Destroy console buffers to drop cout/cerr encoding transform.
-          consoleBuf.reset();
+          console.reset();
           cmCatFile(arg);
         }
       }
@@ -1464,11 +1464,11 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
     }
 
     if (args[1] == "vs_link_exe") {
-      return cmcmd::VisualStudioLink(args, 1, std::move(consoleBuf));
+      return cmcmd::VisualStudioLink(args, 1, std::move(console));
     }
 
     if (args[1] == "vs_link_dll") {
-      return cmcmd::VisualStudioLink(args, 2, std::move(consoleBuf));
+      return cmcmd::VisualStudioLink(args, 2, std::move(console));
     }
 
     if (args[1] == "cmake_llvm_rc") {
@@ -2221,7 +2221,7 @@ private:
 // exe and dll's.  This code does that in such a way that incremental linking
 // still works.
 int cmcmd::VisualStudioLink(std::vector<std::string> const& args, int type,
-                            std::unique_ptr<cmConsoleBuf> consoleBuf)
+                            cm::optional<cm::StdIo::Console> console)
 {
   // MSVC tools print output in the language specified by the VSLANG
   // environment variable, and encoded in the console output code page.
@@ -2229,7 +2229,7 @@ int cmcmd::VisualStudioLink(std::vector<std::string> const& args, int type,
   // RunCommand tells RunSingleCommand to *not* convert encoding, so
   // we buffer the output in its original encoding instead of UTF-8.
   // Drop our output encoding conversion so we print with original encoding.
-  consoleBuf.reset();
+  console.reset();
 
   if (args.size() < 2) {
     return -1;

+ 9 - 4
Source/cmcmd.h

@@ -4,15 +4,20 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
-#include <memory>
 #include <string>
 #include <vector>
 
+#include <cm/optional>
+
 #include "cmsys/Status.hxx"
 
 #include "cmCryptoHash.h"
 
-class cmConsoleBuf;
+namespace cm {
+namespace StdIo {
+class Console;
+}
+}
 
 class cmcmd
 {
@@ -22,7 +27,7 @@ public:
    * as echo, remove file etc.
    */
   static int ExecuteCMakeCommand(std::vector<std::string> const&,
-                                 std::unique_ptr<cmConsoleBuf> consoleBuf);
+                                 cm::optional<cm::StdIo::Console> console);
 
 protected:
   static int HandleCoCompileCommands(std::vector<std::string> const& args);
@@ -40,5 +45,5 @@ protected:
                              std::string const& intermediate_file);
   static int RunLLVMRC(std::vector<std::string> const& args);
   static int VisualStudioLink(std::vector<std::string> const& args, int type,
-                              std::unique_ptr<cmConsoleBuf> consoleBuf);
+                              cm::optional<cm::StdIo::Console> console);
 };

+ 2 - 7
Source/ctest.cxx

@@ -9,12 +9,11 @@
 #include "cmsys/Encoding.hxx"
 
 #include "cmCTest.h"
-#include "cmConsoleBuf.h"
 #include "cmDocumentation.h"
 #include "cmDocumentationEntry.h"
 #include "cmInstrumentation.h"
 #include "cmInstrumentationQuery.h"
-#include "cmStdIoInit.h"
+#include "cmStdIoConsole.h"
 #include "cmSystemTools.h"
 
 #include "CTest/cmCTestLaunch.h"
@@ -167,11 +166,7 @@ cmDocumentationEntry const cmDocumentationOptions[] = {
 // this is a test driver program for cmCTest.
 int main(int argc, char const* const* argv)
 {
-  cm::StdIo::Init();
-
-  // Replace streambuf so we can output Unicode to console
-  cmConsoleBuf consoleBuf;
-  consoleBuf.SetUTF8Pipes();
+  cm::StdIo::Console console;
 
   cmsys::Encoding::CommandLineArguments encoding_args =
     cmsys::Encoding::CommandLineArguments::Main(argc, argv);

+ 1 - 23
Source/kwsys/CMakeLists.txt

@@ -152,7 +152,6 @@ if(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
   set(KWSYS_USE_FStream 1)
   set(KWSYS_USE_String 1)
   set(KWSYS_USE_SystemInformation 1)
-  set(KWSYS_USE_ConsoleBuf 1)
 endif()
 
 # Enforce component dependencies.
@@ -193,9 +192,6 @@ endif()
 if(KWSYS_USE_FStream)
   set(KWSYS_USE_Encoding 1)
 endif()
-if(KWSYS_USE_ConsoleBuf)
-  set(KWSYS_USE_Encoding 1)
-endif()
 
 # Specify default 8 bit encoding for Windows
 if(NOT KWSYS_ENCODING_DEFAULT_CODEPAGE)
@@ -619,7 +615,7 @@ set(KWSYS_HXX_FILES Configure)
 # Add selected C++ classes.
 set(cppclasses
   Directory DynamicLoader Encoding Glob RegularExpression SystemTools
-  CommandLineArguments FStream SystemInformation ConsoleBuf Status
+  CommandLineArguments FStream SystemInformation Status
   )
 foreach(cpp IN LISTS cppclasses)
   if(KWSYS_USE_${cpp})
@@ -970,24 +966,6 @@ if(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
         testFStream.cxx
         )
     endif()
-    if(KWSYS_USE_ConsoleBuf)
-      add_executable(testConsoleBufChild testConsoleBufChild.cxx)
-      set_property(TARGET testConsoleBufChild PROPERTY C_CLANG_TIDY "")
-      set_property(TARGET testConsoleBufChild PROPERTY CXX_CLANG_TIDY "")
-      set_property(TARGET testConsoleBufChild PROPERTY C_INCLUDE_WHAT_YOU_USE "")
-      set_property(TARGET testConsoleBufChild PROPERTY CXX_INCLUDE_WHAT_YOU_USE "")
-      set_property(TARGET testConsoleBufChild PROPERTY LABELS ${KWSYS_LABELS_EXE})
-      target_link_libraries(testConsoleBufChild ${KWSYS_TARGET_LINK})
-      set(KWSYS_CXX_TESTS ${KWSYS_CXX_TESTS}
-        testConsoleBuf.cxx
-        )
-      if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND
-         CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "19.0.23506")
-        set_property(SOURCE testConsoleBuf.cxx testConsoleBufChild.cxx PROPERTY COMPILE_FLAGS /utf-8)
-      endif()
-      set_property(SOURCE testConsoleBuf.cxx APPEND PROPERTY COMPILE_DEFINITIONS
-        KWSYS_ENCODING_DEFAULT_CODEPAGE=${KWSYS_ENCODING_DEFAULT_CODEPAGE})
-    endif()
     if(KWSYS_USE_SystemInformation)
       set(KWSYS_CXX_TESTS ${KWSYS_CXX_TESTS} testSystemInformation.cxx)
     endif()

+ 0 - 398
Source/kwsys/ConsoleBuf.hxx.in

@@ -1,398 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
-#ifndef @KWSYS_NAMESPACE@_ConsoleBuf_hxx
-#define @KWSYS_NAMESPACE@_ConsoleBuf_hxx
-
-#include <@KWSYS_NAMESPACE@/Configure.hxx>
-
-#include <@KWSYS_NAMESPACE@/Encoding.hxx>
-
-#include <cstring>
-#include <iostream>
-#include <sstream>
-#include <stdexcept>
-#include <streambuf>
-#include <string>
-
-#if defined(_WIN32)
-#  include <windows.h>
-#  if __cplusplus >= 201103L
-#    include <system_error>
-#  endif
-#endif
-
-namespace @KWSYS_NAMESPACE@ {
-#if defined(_WIN32)
-
-template <class CharT, class Traits = std::char_traits<CharT> >
-class BasicConsoleBuf : public std::basic_streambuf<CharT, Traits>
-{
-public:
-  typedef typename Traits::int_type int_type;
-  typedef typename Traits::char_type char_type;
-
-  class Manager
-  {
-  public:
-    Manager(std::basic_ios<CharT, Traits>& ios, bool const err = false)
-      : m_consolebuf(0)
-    {
-      m_ios = &ios;
-      try {
-        m_consolebuf = new BasicConsoleBuf<CharT, Traits>(err);
-        m_streambuf = m_ios->rdbuf(m_consolebuf);
-      } catch (std::runtime_error const& ex) {
-        std::cerr << "Failed to create ConsoleBuf!" << std::endl
-                  << ex.what() << std::endl;
-      };
-    }
-
-    BasicConsoleBuf<CharT, Traits>* GetConsoleBuf() { return m_consolebuf; }
-
-    void SetUTF8Pipes()
-    {
-      if (m_consolebuf) {
-        m_consolebuf->input_pipe_codepage = CP_UTF8;
-        m_consolebuf->output_pipe_codepage = CP_UTF8;
-        m_consolebuf->activateCodepageChange();
-      }
-    }
-
-    ~Manager()
-    {
-      if (m_consolebuf) {
-        delete m_consolebuf;
-        m_ios->rdbuf(m_streambuf);
-      }
-    }
-
-  private:
-    std::basic_ios<CharT, Traits>* m_ios;
-    std::basic_streambuf<CharT, Traits>* m_streambuf;
-    BasicConsoleBuf<CharT, Traits>* m_consolebuf;
-  };
-
-  BasicConsoleBuf(bool const err = false)
-    : flush_on_newline(true)
-    , input_pipe_codepage(0)
-    , output_pipe_codepage(0)
-    , input_file_codepage(CP_UTF8)
-    , output_file_codepage(CP_UTF8)
-    , m_consolesCodepage(0)
-  {
-    m_hInput = ::GetStdHandle(STD_INPUT_HANDLE);
-    checkHandle(true, "STD_INPUT_HANDLE");
-    if (!setActiveInputCodepage()) {
-      throw std::runtime_error("setActiveInputCodepage failed!");
-    }
-    m_hOutput = err ? ::GetStdHandle(STD_ERROR_HANDLE)
-                    : ::GetStdHandle(STD_OUTPUT_HANDLE);
-    checkHandle(false, err ? "STD_ERROR_HANDLE" : "STD_OUTPUT_HANDLE");
-    if (!setActiveOutputCodepage()) {
-      throw std::runtime_error("setActiveOutputCodepage failed!");
-    }
-    _setg();
-    _setp();
-  }
-
-  ~BasicConsoleBuf() throw() { sync(); }
-
-  bool activateCodepageChange()
-  {
-    return setActiveInputCodepage() && setActiveOutputCodepage();
-  }
-
-protected:
-  virtual int sync()
-  {
-    bool success = true;
-    if (m_hInput && m_isConsoleInput &&
-        ::FlushConsoleInputBuffer(m_hInput) == 0) {
-      success = false;
-    }
-    if (m_hOutput && !m_obuffer.empty()) {
-      std::wstring const wbuffer = getBuffer(m_obuffer);
-      if (m_isConsoleOutput) {
-        DWORD charsWritten;
-        success =
-          ::WriteConsoleW(m_hOutput, wbuffer.c_str(), (DWORD)wbuffer.size(),
-                          &charsWritten, nullptr) == 0
-          ? false
-          : true;
-      } else {
-        DWORD bytesWritten;
-        std::string buffer;
-        success = encodeOutputBuffer(wbuffer, buffer);
-        if (success) {
-          success =
-            ::WriteFile(m_hOutput, buffer.c_str(), (DWORD)buffer.size(),
-                        &bytesWritten, nullptr) == 0
-            ? false
-            : true;
-        }
-      }
-    }
-    m_ibuffer.clear();
-    m_obuffer.clear();
-    _setg();
-    _setp();
-    return success ? 0 : -1;
-  }
-
-  virtual int_type underflow()
-  {
-    if (this->gptr() >= this->egptr()) {
-      if (!m_hInput) {
-        _setg(true);
-        return Traits::eof();
-      }
-      if (m_isConsoleInput) {
-        // ReadConsole doesn't tell if there's more input available
-        // don't support reading more characters than this
-        wchar_t wbuffer[8192];
-        DWORD charsRead;
-        if (ReadConsoleW(m_hInput, wbuffer,
-                         (sizeof(wbuffer) / sizeof(wbuffer[0])), &charsRead,
-                         nullptr) == 0 ||
-            charsRead == 0) {
-          _setg(true);
-          return Traits::eof();
-        }
-        setBuffer(std::wstring(wbuffer, charsRead), m_ibuffer);
-      } else {
-        std::wstring wbuffer;
-        std::string strbuffer;
-        DWORD bytesRead;
-        LARGE_INTEGER size;
-        if (GetFileSizeEx(m_hInput, &size) == 0) {
-          _setg(true);
-          return Traits::eof();
-        }
-        char* buffer = new char[size.LowPart];
-        while (ReadFile(m_hInput, buffer, size.LowPart, &bytesRead, nullptr) ==
-               0) {
-          if (GetLastError() == ERROR_MORE_DATA) {
-            strbuffer += std::string(buffer, bytesRead);
-            continue;
-          }
-          _setg(true);
-          delete[] buffer;
-          return Traits::eof();
-        }
-        if (bytesRead > 0) {
-          strbuffer += std::string(buffer, bytesRead);
-        }
-        delete[] buffer;
-        if (!decodeInputBuffer(strbuffer, wbuffer)) {
-          _setg(true);
-          return Traits::eof();
-        }
-        setBuffer(wbuffer, m_ibuffer);
-      }
-      _setg();
-    }
-    return Traits::to_int_type(*this->gptr());
-  }
-
-  virtual int_type overflow(int_type ch = Traits::eof())
-  {
-    if (!Traits::eq_int_type(ch, Traits::eof())) {
-      char_type chr = Traits::to_char_type(ch);
-      m_obuffer += chr;
-      if ((flush_on_newline && Traits::eq(chr, '\n')) ||
-          Traits::eq_int_type(ch, 0x00)) {
-        sync();
-      }
-      return ch;
-    }
-    sync();
-    return Traits::eof();
-  }
-
-public:
-  bool flush_on_newline;
-  UINT input_pipe_codepage;
-  UINT output_pipe_codepage;
-  UINT input_file_codepage;
-  UINT output_file_codepage;
-
-private:
-  HANDLE m_hInput;
-  HANDLE m_hOutput;
-  std::basic_string<char_type> m_ibuffer;
-  std::basic_string<char_type> m_obuffer;
-  bool m_isConsoleInput;
-  bool m_isConsoleOutput;
-  UINT m_activeInputCodepage;
-  UINT m_activeOutputCodepage;
-  UINT m_consolesCodepage;
-  void checkHandle(bool input, std::string handleName)
-  {
-    if ((input && m_hInput == INVALID_HANDLE_VALUE) ||
-        (!input && m_hOutput == INVALID_HANDLE_VALUE)) {
-      std::string errmsg =
-        "GetStdHandle(" + handleName + ") returned INVALID_HANDLE_VALUE";
-#  if __cplusplus >= 201103L
-      throw std::system_error(::GetLastError(), std::system_category(),
-                              errmsg);
-#  else
-      throw std::runtime_error(errmsg);
-#  endif
-    }
-  }
-  UINT getConsolesCodepage()
-  {
-    if (!m_consolesCodepage) {
-      m_consolesCodepage = GetConsoleCP();
-      if (!m_consolesCodepage) {
-        m_consolesCodepage = GetACP();
-      }
-    }
-    return m_consolesCodepage;
-  }
-  bool setActiveInputCodepage()
-  {
-    m_isConsoleInput = false;
-    switch (GetFileType(m_hInput)) {
-      case FILE_TYPE_DISK:
-        m_activeInputCodepage = input_file_codepage;
-        break;
-      case FILE_TYPE_CHAR:
-        // Check for actual console.
-        DWORD consoleMode;
-        m_isConsoleInput =
-          GetConsoleMode(m_hInput, &consoleMode) == 0 ? false : true;
-        if (m_isConsoleInput) {
-          break;
-        }
-        @KWSYS_NAMESPACE@_FALLTHROUGH;
-      case FILE_TYPE_PIPE:
-        m_activeInputCodepage = input_pipe_codepage;
-        break;
-      default:
-        return false;
-    }
-    if (!m_isConsoleInput && m_activeInputCodepage == 0) {
-      m_activeInputCodepage = getConsolesCodepage();
-    }
-    return true;
-  }
-  bool setActiveOutputCodepage()
-  {
-    m_isConsoleOutput = false;
-    switch (GetFileType(m_hOutput)) {
-      case FILE_TYPE_DISK:
-        m_activeOutputCodepage = output_file_codepage;
-        break;
-      case FILE_TYPE_CHAR:
-        // Check for actual console.
-        DWORD consoleMode;
-        m_isConsoleOutput =
-          GetConsoleMode(m_hOutput, &consoleMode) == 0 ? false : true;
-        if (m_isConsoleOutput) {
-          break;
-        }
-        @KWSYS_NAMESPACE@_FALLTHROUGH;
-      case FILE_TYPE_PIPE:
-        m_activeOutputCodepage = output_pipe_codepage;
-        break;
-      default:
-        return false;
-    }
-    if (!m_isConsoleOutput && m_activeOutputCodepage == 0) {
-      m_activeOutputCodepage = getConsolesCodepage();
-    }
-    return true;
-  }
-  void _setg(bool empty = false)
-  {
-    if (!empty) {
-      this->setg((char_type*)m_ibuffer.data(), (char_type*)m_ibuffer.data(),
-                 (char_type*)m_ibuffer.data() + m_ibuffer.size());
-    } else {
-      this->setg((char_type*)m_ibuffer.data(),
-                 (char_type*)m_ibuffer.data() + m_ibuffer.size(),
-                 (char_type*)m_ibuffer.data() + m_ibuffer.size());
-    }
-  }
-  void _setp()
-  {
-    this->setp((char_type*)m_obuffer.data(),
-               (char_type*)m_obuffer.data() + m_obuffer.size());
-  }
-  bool encodeOutputBuffer(std::wstring const wbuffer, std::string& buffer)
-  {
-    if (wbuffer.size() == 0) {
-      buffer = std::string();
-      return true;
-    }
-    int const length =
-      WideCharToMultiByte(m_activeOutputCodepage, 0, wbuffer.c_str(),
-                          (int)wbuffer.size(), nullptr, 0, nullptr, nullptr);
-    char* buf = new char[length];
-    bool const success =
-      WideCharToMultiByte(m_activeOutputCodepage, 0, wbuffer.c_str(),
-                          (int)wbuffer.size(), buf, length, nullptr,
-                          nullptr) > 0
-      ? true
-      : false;
-    buffer = std::string(buf, length);
-    delete[] buf;
-    return success;
-  }
-  bool decodeInputBuffer(std::string const buffer, std::wstring& wbuffer)
-  {
-    size_t length = buffer.length();
-    if (length == 0) {
-      wbuffer = std::wstring();
-      return true;
-    }
-    int actualCodepage = m_activeInputCodepage;
-    char const BOM_UTF8[] = { char(0xEF), char(0xBB), char(0xBF) };
-    char const* data = buffer.data();
-    size_t const BOMsize = sizeof(BOM_UTF8);
-    if (length >= BOMsize && std::memcmp(data, BOM_UTF8, BOMsize) == 0) {
-      // PowerShell uses UTF-8 with BOM for pipes
-      actualCodepage = CP_UTF8;
-      data += BOMsize;
-      length -= BOMsize;
-    }
-    size_t const wlength = static_cast<size_t>(MultiByteToWideChar(
-      actualCodepage, 0, data, static_cast<int>(length), nullptr, 0));
-    wchar_t* wbuf = new wchar_t[wlength];
-    bool const success =
-      MultiByteToWideChar(actualCodepage, 0, data, static_cast<int>(length),
-                          wbuf, static_cast<int>(wlength)) > 0
-      ? true
-      : false;
-    wbuffer = std::wstring(wbuf, wlength);
-    delete[] wbuf;
-    return success;
-  }
-  std::wstring getBuffer(std::basic_string<char> const buffer)
-  {
-    return Encoding::ToWide(buffer);
-  }
-  std::wstring getBuffer(std::basic_string<wchar_t> const buffer)
-  {
-    return buffer;
-  }
-  void setBuffer(std::wstring const wbuffer, std::basic_string<char>& target)
-  {
-    target = Encoding::ToNarrow(wbuffer);
-  }
-  void setBuffer(std::wstring const wbuffer,
-                 std::basic_string<wchar_t>& target)
-  {
-    target = wbuffer;
-  }
-
-}; // BasicConsoleBuf class
-
-typedef BasicConsoleBuf<char> ConsoleBuf;
-typedef BasicConsoleBuf<wchar_t> WConsoleBuf;
-
-#endif
-} // KWSYS_NAMESPACE
-
-#endif

+ 0 - 806
Source/kwsys/testConsoleBuf.cxx

@@ -1,806 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
-#include "kwsysPrivate.h"
-
-// Ignore Windows version levels defined by command-line flags.  This
-// source needs access to all APIs available on the host in order for
-// the test to run properly.  The test binary is not installed anyway.
-#undef _WIN32_WINNT
-#undef NTDDI_VERSION
-
-#include KWSYS_HEADER(Encoding.hxx)
-
-// Work-around CMake dependency scanning limitation.  This must
-// duplicate the above list of headers.
-#if 0
-#  include "Encoding.hxx.in"
-#endif
-
-#if defined(_WIN32)
-
-#  include <algorithm>
-#  include <iomanip>
-#  include <iostream>
-#  include <stdexcept>
-#  include <string.h>
-#  include <wchar.h>
-#  include <windows.h>
-
-#  include "testConsoleBuf.hxx"
-
-#  if defined(_MSC_VER) && _MSC_VER >= 1800
-#    define KWSYS_WINDOWS_DEPRECATED_GetVersion
-#  endif
-// يونيكود
-static const WCHAR UnicodeInputTestString[] =
-  L"\u064A\u0648\u0646\u064A\u0643\u0648\u062F!";
-static UINT TestCodepage = KWSYS_ENCODING_DEFAULT_CODEPAGE;
-
-static const DWORD waitTimeout = 10 * 1000;
-static STARTUPINFO startupInfo;
-static PROCESS_INFORMATION processInfo;
-static HANDLE beforeInputEvent;
-static HANDLE afterOutputEvent;
-static std::string encodedInputTestString;
-static std::string encodedTestString;
-
-static void displayError(DWORD errorCode)
-{
-  std::cerr.setf(std::ios::hex, std::ios::basefield);
-  std::cerr << "Failed with error: 0x" << errorCode << "!" << std::endl;
-  LPWSTR message;
-  if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
-                       FORMAT_MESSAGE_FROM_SYSTEM,
-                     nullptr, errorCode, 0, (LPWSTR)&message, 0, nullptr)) {
-    std::cerr << "Error message: " << kwsys::Encoding::ToNarrow(message)
-              << std::endl;
-    HeapFree(GetProcessHeap(), 0, message);
-  } else {
-    std::cerr << "FormatMessage() failed with error: 0x" << GetLastError()
-              << "!" << std::endl;
-  }
-  std::cerr.unsetf(std::ios::hex);
-}
-
-std::basic_streambuf<char>* errstream(char const* unused)
-{
-  static_cast<void>(unused);
-  return std::cerr.rdbuf();
-}
-
-std::basic_streambuf<wchar_t>* errstream(wchar_t const* unused)
-{
-  static_cast<void>(unused);
-  return std::wcerr.rdbuf();
-}
-
-template <typename T>
-static void dumpBuffers(T const* expected, T const* received, size_t size)
-{
-  std::basic_ostream<T> err(errstream(expected));
-  err << "Expected output: '" << std::basic_string<T>(expected, size) << "'"
-      << std::endl;
-  if (err.fail()) {
-    err.clear();
-    err << "--- Error while outputting ---" << std::endl;
-  }
-  err << "Received output: '" << std::basic_string<T>(received, size) << "'"
-      << std::endl;
-  if (err.fail()) {
-    err.clear();
-    err << "--- Error while outputting ---" << std::endl;
-  }
-  std::cerr << "Expected output | Received output" << std::endl;
-  for (size_t i = 0; i < size; i++) {
-    std::cerr << std::setbase(16) << std::setfill('0') << "     "
-              << "0x" << std::setw(8) << static_cast<unsigned int>(expected[i])
-              << " | "
-              << "0x" << std::setw(8)
-              << static_cast<unsigned int>(received[i]);
-    if (static_cast<unsigned int>(expected[i]) !=
-        static_cast<unsigned int>(received[i])) {
-      std::cerr << "   MISMATCH!";
-    }
-    std::cerr << std::endl;
-  }
-  std::cerr << std::endl;
-}
-
-static bool createProcess(HANDLE hIn, HANDLE hOut, HANDLE hErr)
-{
-  BOOL bInheritHandles = FALSE;
-  DWORD dwCreationFlags = 0;
-  memset(&processInfo, 0, sizeof(processInfo));
-  memset(&startupInfo, 0, sizeof(startupInfo));
-  startupInfo.cb = sizeof(startupInfo);
-  startupInfo.dwFlags = STARTF_USESHOWWINDOW;
-  startupInfo.wShowWindow = SW_HIDE;
-  if (hIn || hOut || hErr) {
-    startupInfo.dwFlags |= STARTF_USESTDHANDLES;
-    startupInfo.hStdInput = hIn;
-    startupInfo.hStdOutput = hOut;
-    startupInfo.hStdError = hErr;
-    bInheritHandles = TRUE;
-  }
-
-  WCHAR cmd[MAX_PATH];
-  if (GetModuleFileNameW(nullptr, cmd, MAX_PATH) == 0) {
-    std::cerr << "GetModuleFileName failed!" << std::endl;
-    return false;
-  }
-  WCHAR* p = cmd + wcslen(cmd);
-  while (p > cmd && *p != L'\\')
-    p--;
-  *(p + 1) = 0;
-  wcscat(cmd, cmdConsoleBufChild);
-  wcscat(cmd, L".exe");
-
-  bool success =
-    CreateProcessW(nullptr,         // No module name (use command line)
-                   cmd,             // Command line
-                   nullptr,         // Process handle not inheritable
-                   nullptr,         // Thread handle not inheritable
-                   bInheritHandles, // Set handle inheritance
-                   dwCreationFlags,
-                   nullptr,      // Use parent's environment block
-                   nullptr,      // Use parent's starting directory
-                   &startupInfo, // Pointer to STARTUPINFO structure
-                   &processInfo) !=
-    0; // Pointer to PROCESS_INFORMATION structure
-  if (!success) {
-    DWORD lastError = GetLastError();
-    std::cerr << "CreateProcess(" << kwsys::Encoding::ToNarrow(cmd) << ")"
-              << std::endl;
-    displayError(lastError);
-  }
-  return success;
-}
-
-static void finishProcess(bool success)
-{
-  if (success) {
-    success =
-      WaitForSingleObject(processInfo.hProcess, waitTimeout) == WAIT_OBJECT_0;
-  };
-  if (!success) {
-    TerminateProcess(processInfo.hProcess, 1);
-  }
-  CloseHandle(processInfo.hProcess);
-  CloseHandle(processInfo.hThread);
-}
-
-static bool createPipe(PHANDLE readPipe, PHANDLE writePipe)
-{
-  SECURITY_ATTRIBUTES securityAttributes;
-  securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
-  securityAttributes.bInheritHandle = TRUE;
-  securityAttributes.lpSecurityDescriptor = nullptr;
-  return CreatePipe(readPipe, writePipe, &securityAttributes, 0) == 0 ? false
-                                                                      : true;
-}
-
-static void finishPipe(HANDLE readPipe, HANDLE writePipe)
-{
-  if (readPipe != INVALID_HANDLE_VALUE) {
-    CloseHandle(readPipe);
-  }
-  if (writePipe != INVALID_HANDLE_VALUE) {
-    CloseHandle(writePipe);
-  }
-}
-
-static HANDLE createFile(LPCWSTR fileName)
-{
-  SECURITY_ATTRIBUTES securityAttributes;
-  securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
-  securityAttributes.bInheritHandle = TRUE;
-  securityAttributes.lpSecurityDescriptor = nullptr;
-
-  HANDLE file =
-    CreateFileW(fileName, GENERIC_READ | GENERIC_WRITE,
-                0, // do not share
-                &securityAttributes,
-                CREATE_ALWAYS, // overwrite existing
-                FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
-                nullptr); // no template
-  if (file == INVALID_HANDLE_VALUE) {
-    DWORD lastError = GetLastError();
-    std::cerr << "CreateFile(" << kwsys::Encoding::ToNarrow(fileName) << ")"
-              << std::endl;
-    displayError(lastError);
-  }
-  return file;
-}
-
-static void finishFile(HANDLE file)
-{
-  if (file != INVALID_HANDLE_VALUE) {
-    CloseHandle(file);
-  }
-}
-
-#  ifndef MAPVK_VK_TO_VSC
-#    define MAPVK_VK_TO_VSC (0)
-#  endif
-
-static void writeInputKeyEvent(INPUT_RECORD inputBuffer[], WCHAR chr)
-{
-  inputBuffer[0].EventType = KEY_EVENT;
-  inputBuffer[0].Event.KeyEvent.bKeyDown = TRUE;
-  inputBuffer[0].Event.KeyEvent.wRepeatCount = 1;
-  SHORT keyCode = VkKeyScanW(chr);
-  if (keyCode == -1) {
-    // Character can't be entered with current keyboard layout
-    // Just set any, it doesn't really matter
-    keyCode = 'K';
-  }
-  inputBuffer[0].Event.KeyEvent.wVirtualKeyCode = LOBYTE(keyCode);
-  inputBuffer[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(
-    inputBuffer[0].Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC);
-  inputBuffer[0].Event.KeyEvent.uChar.UnicodeChar = chr;
-  inputBuffer[0].Event.KeyEvent.dwControlKeyState = 0;
-  if ((HIBYTE(keyCode) & 1) == 1) {
-    inputBuffer[0].Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED;
-  }
-  if ((HIBYTE(keyCode) & 2) == 2) {
-    inputBuffer[0].Event.KeyEvent.dwControlKeyState |= RIGHT_CTRL_PRESSED;
-  }
-  if ((HIBYTE(keyCode) & 4) == 4) {
-    inputBuffer[0].Event.KeyEvent.dwControlKeyState |= RIGHT_ALT_PRESSED;
-  }
-  inputBuffer[1].EventType = inputBuffer[0].EventType;
-  inputBuffer[1].Event.KeyEvent.bKeyDown = FALSE;
-  inputBuffer[1].Event.KeyEvent.wRepeatCount = 1;
-  inputBuffer[1].Event.KeyEvent.wVirtualKeyCode =
-    inputBuffer[0].Event.KeyEvent.wVirtualKeyCode;
-  inputBuffer[1].Event.KeyEvent.wVirtualScanCode =
-    inputBuffer[0].Event.KeyEvent.wVirtualScanCode;
-  inputBuffer[1].Event.KeyEvent.uChar.UnicodeChar =
-    inputBuffer[0].Event.KeyEvent.uChar.UnicodeChar;
-  inputBuffer[1].Event.KeyEvent.dwControlKeyState = 0;
-}
-
-static int testPipe()
-{
-  int didFail = 1;
-  HANDLE inPipeRead = INVALID_HANDLE_VALUE;
-  HANDLE inPipeWrite = INVALID_HANDLE_VALUE;
-  HANDLE outPipeRead = INVALID_HANDLE_VALUE;
-  HANDLE outPipeWrite = INVALID_HANDLE_VALUE;
-  HANDLE errPipeRead = INVALID_HANDLE_VALUE;
-  HANDLE errPipeWrite = INVALID_HANDLE_VALUE;
-  UINT currentCodepage = GetConsoleCP();
-  char buffer[200];
-  char buffer2[200];
-  try {
-    if (!createPipe(&inPipeRead, &inPipeWrite) ||
-        !createPipe(&outPipeRead, &outPipeWrite) ||
-        !createPipe(&errPipeRead, &errPipeWrite)) {
-      throw std::runtime_error("createFile failed!");
-    }
-    if (TestCodepage == CP_ACP) {
-      TestCodepage = GetACP();
-    }
-    if (!SetConsoleCP(TestCodepage)) {
-      throw std::runtime_error("SetConsoleCP failed!");
-    }
-
-    DWORD bytesWritten = 0;
-    if (!WriteFile(inPipeWrite, encodedInputTestString.c_str(),
-                   (DWORD)encodedInputTestString.size(), &bytesWritten,
-                   nullptr) ||
-        bytesWritten == 0) {
-      throw std::runtime_error("WriteFile failed!");
-    }
-
-    if (createProcess(inPipeRead, outPipeWrite, errPipeWrite)) {
-      try {
-        DWORD status;
-        if ((status = WaitForSingleObject(afterOutputEvent, waitTimeout)) !=
-            WAIT_OBJECT_0) {
-          std::cerr.setf(std::ios::hex, std::ios::basefield);
-          std::cerr << "WaitForSingleObject returned unexpected status 0x"
-                    << status << std::endl;
-          std::cerr.unsetf(std::ios::hex);
-          throw std::runtime_error("WaitForSingleObject failed!");
-        }
-        DWORD bytesRead = 0;
-        if (!ReadFile(outPipeRead, buffer, sizeof(buffer), &bytesRead,
-                      nullptr) ||
-            bytesRead == 0) {
-          throw std::runtime_error("ReadFile#1 failed!");
-        }
-        buffer[bytesRead] = 0;
-        if ((bytesRead <
-               encodedTestString.size() + 1 + encodedInputTestString.size() &&
-             !ReadFile(outPipeRead, buffer + bytesRead,
-                       sizeof(buffer) - bytesRead, &bytesRead, nullptr)) ||
-            bytesRead == 0) {
-          throw std::runtime_error("ReadFile#2 failed!");
-        }
-        if (memcmp(buffer, encodedTestString.c_str(),
-                   encodedTestString.size()) == 0 &&
-            memcmp(buffer + encodedTestString.size() + 1,
-                   encodedInputTestString.c_str(),
-                   encodedInputTestString.size()) == 0) {
-          bytesRead = 0;
-          if (!ReadFile(errPipeRead, buffer2, sizeof(buffer2), &bytesRead,
-                        nullptr) ||
-              bytesRead == 0) {
-            throw std::runtime_error("ReadFile#3 failed!");
-          }
-          buffer2[bytesRead] = 0;
-          didFail = encodedTestString.compare(0, std::string::npos, buffer2,
-                                              encodedTestString.size()) == 0
-            ? 0
-            : 1;
-        }
-        if (didFail != 0) {
-          std::cerr << "Pipe's output didn't match expected output!"
-                    << std::endl;
-          dumpBuffers<char>(encodedTestString.c_str(), buffer,
-                            encodedTestString.size());
-          dumpBuffers<char>(encodedInputTestString.c_str(),
-                            buffer + encodedTestString.size() + 1,
-                            encodedInputTestString.size());
-          dumpBuffers<char>(encodedTestString.c_str(), buffer2,
-                            encodedTestString.size());
-        }
-      } catch (std::runtime_error const& ex) {
-        DWORD lastError = GetLastError();
-        std::cerr << "In function testPipe, line " << __LINE__ << ": "
-                  << ex.what() << std::endl;
-        displayError(lastError);
-      }
-      finishProcess(didFail == 0);
-    }
-  } catch (std::runtime_error const& ex) {
-    DWORD lastError = GetLastError();
-    std::cerr << "In function testPipe, line " << __LINE__ << ": " << ex.what()
-              << std::endl;
-    displayError(lastError);
-  }
-  finishPipe(inPipeRead, inPipeWrite);
-  finishPipe(outPipeRead, outPipeWrite);
-  finishPipe(errPipeRead, errPipeWrite);
-  SetConsoleCP(currentCodepage);
-  return didFail;
-}
-
-static int testFile()
-{
-  int didFail = 1;
-  HANDLE inFile = INVALID_HANDLE_VALUE;
-  HANDLE outFile = INVALID_HANDLE_VALUE;
-  HANDLE errFile = INVALID_HANDLE_VALUE;
-  try {
-    if ((inFile = createFile(L"stdinFile.txt")) == INVALID_HANDLE_VALUE ||
-        (outFile = createFile(L"stdoutFile.txt")) == INVALID_HANDLE_VALUE ||
-        (errFile = createFile(L"stderrFile.txt")) == INVALID_HANDLE_VALUE) {
-      throw std::runtime_error("createFile failed!");
-    }
-    DWORD bytesWritten = 0;
-    char buffer[200];
-    char buffer2[200];
-
-    int length;
-    if ((length = WideCharToMultiByte(TestCodepage, 0, UnicodeInputTestString,
-                                      -1, buffer, sizeof(buffer), nullptr,
-                                      nullptr)) == 0) {
-      throw std::runtime_error("WideCharToMultiByte failed!");
-    }
-    buffer[length - 1] = '\n';
-    if (!WriteFile(inFile, buffer, length, &bytesWritten, nullptr) ||
-        bytesWritten == 0) {
-      throw std::runtime_error("WriteFile failed!");
-    }
-    if (SetFilePointer(inFile, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) {
-      throw std::runtime_error("SetFilePointer failed!");
-    }
-
-    if (createProcess(inFile, outFile, errFile)) {
-      DWORD bytesRead = 0;
-      try {
-        DWORD status;
-        if ((status = WaitForSingleObject(afterOutputEvent, waitTimeout)) !=
-            WAIT_OBJECT_0) {
-          std::cerr.setf(std::ios::hex, std::ios::basefield);
-          std::cerr << "WaitForSingleObject returned unexpected status 0x"
-                    << status << std::endl;
-          std::cerr.unsetf(std::ios::hex);
-          throw std::runtime_error("WaitForSingleObject failed!");
-        }
-        if (SetFilePointer(outFile, 0, 0, FILE_BEGIN) ==
-            INVALID_SET_FILE_POINTER) {
-          throw std::runtime_error("SetFilePointer#1 failed!");
-        }
-        if (!ReadFile(outFile, buffer, sizeof(buffer), &bytesRead, nullptr) ||
-            bytesRead == 0) {
-          throw std::runtime_error("ReadFile#1 failed!");
-        }
-        buffer[bytesRead] = 0;
-        if (memcmp(buffer, encodedTestString.c_str(),
-                   encodedTestString.size()) == 0 &&
-            memcmp(buffer + encodedTestString.size() + 1,
-                   encodedInputTestString.c_str(),
-                   encodedInputTestString.size()) == 0) {
-          bytesRead = 0;
-          if (SetFilePointer(errFile, 0, 0, FILE_BEGIN) ==
-              INVALID_SET_FILE_POINTER) {
-            throw std::runtime_error("SetFilePointer#2 failed!");
-          }
-
-          if (!ReadFile(errFile, buffer2, sizeof(buffer2), &bytesRead,
-                        nullptr) ||
-              bytesRead == 0) {
-            throw std::runtime_error("ReadFile#2 failed!");
-          }
-          buffer2[bytesRead] = 0;
-          didFail = encodedTestString.compare(0, std::string::npos, buffer2,
-                                              encodedTestString.size()) == 0
-            ? 0
-            : 1;
-        }
-        if (didFail != 0) {
-          std::cerr << "File's output didn't match expected output!"
-                    << std::endl;
-          dumpBuffers<char>(encodedTestString.c_str(), buffer,
-                            encodedTestString.size());
-          dumpBuffers<char>(encodedInputTestString.c_str(),
-                            buffer + encodedTestString.size() + 1,
-                            encodedInputTestString.size());
-          dumpBuffers<char>(encodedTestString.c_str(), buffer2,
-                            encodedTestString.size());
-        }
-      } catch (std::runtime_error const& ex) {
-        DWORD lastError = GetLastError();
-        std::cerr << "In function testFile, line " << __LINE__ << ": "
-                  << ex.what() << std::endl;
-        displayError(lastError);
-      }
-      finishProcess(didFail == 0);
-    }
-  } catch (std::runtime_error const& ex) {
-    DWORD lastError = GetLastError();
-    std::cerr << "In function testFile, line " << __LINE__ << ": " << ex.what()
-              << std::endl;
-    displayError(lastError);
-  }
-  finishFile(inFile);
-  finishFile(outFile);
-  finishFile(errFile);
-  return didFail;
-}
-
-#  ifndef _WIN32_WINNT_VISTA
-#    define _WIN32_WINNT_VISTA 0x0600
-#  endif
-
-static bool consoleIsConhost()
-{
-  wchar_t consoleClassNameBuf[64];
-  int const consoleClassNameLen = GetClassNameW(
-    GetConsoleWindow(), &consoleClassNameBuf[0], sizeof(consoleClassNameBuf));
-  // Windows Console Host: ConsoleWindowClass
-  // Windows Terminal / ConPTY: PseudoConsoleWindow (undocumented)
-  return (consoleClassNameLen > 0 &&
-          wcscmp(consoleClassNameBuf, L"ConsoleWindowClass") == 0);
-}
-
-static bool charIsNUL(wchar_t c)
-{
-  return c == 0;
-}
-
-static int testConsole()
-{
-  int didFail = 1;
-  HANDLE parentIn = GetStdHandle(STD_INPUT_HANDLE);
-  HANDLE parentOut = GetStdHandle(STD_OUTPUT_HANDLE);
-  HANDLE parentErr = GetStdHandle(STD_ERROR_HANDLE);
-  HANDLE hIn = parentIn;
-  HANDLE hOut = parentOut;
-  DWORD consoleMode;
-  bool newConsole = false;
-  bool forceNewConsole = false;
-  bool restoreConsole = false;
-  LPCWSTR TestFaceName = L"Lucida Console";
-  const DWORD TestFontFamily = 0x00000036;
-  const DWORD TestFontSize = 0x000c0000;
-  HKEY hConsoleKey;
-  WCHAR FaceName[200];
-  FaceName[0] = 0;
-  DWORD FaceNameSize = sizeof(FaceName);
-  DWORD FontFamily = TestFontFamily;
-  DWORD FontSize = TestFontSize;
-#  ifdef KWSYS_WINDOWS_DEPRECATED_GetVersion
-#    pragma warning(push)
-#    ifdef __INTEL_COMPILER
-#      pragma warning(disable : 1478)
-#    elif defined __clang__
-#      pragma clang diagnostic push
-#      pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#    else
-#      pragma warning(disable : 4996)
-#    endif
-#  endif
-  bool const isVistaOrGreater =
-    LOBYTE(LOWORD(GetVersion())) >= HIBYTE(_WIN32_WINNT_VISTA);
-#  ifdef KWSYS_WINDOWS_DEPRECATED_GetVersion
-#    ifdef __clang__
-#      pragma clang diagnostic pop
-#    else
-#      pragma warning(pop)
-#    endif
-#  endif
-  if (!isVistaOrGreater) {
-    if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Console", 0, KEY_READ | KEY_WRITE,
-                      &hConsoleKey) == ERROR_SUCCESS) {
-      DWORD dwordSize = sizeof(DWORD);
-      if (RegQueryValueExW(hConsoleKey, L"FontFamily", nullptr, nullptr,
-                           (LPBYTE)&FontFamily, &dwordSize) == ERROR_SUCCESS) {
-        if (FontFamily != TestFontFamily) {
-          RegQueryValueExW(hConsoleKey, L"FaceName", nullptr, nullptr,
-                           (LPBYTE)FaceName, &FaceNameSize);
-          RegQueryValueExW(hConsoleKey, L"FontSize", nullptr, nullptr,
-                           (LPBYTE)&FontSize, &dwordSize);
-
-          RegSetValueExW(hConsoleKey, L"FontFamily", 0, REG_DWORD,
-                         (BYTE*)&TestFontFamily, sizeof(TestFontFamily));
-          RegSetValueExW(hConsoleKey, L"FaceName", 0, REG_SZ,
-                         (BYTE*)TestFaceName,
-                         (DWORD)((wcslen(TestFaceName) + 1) * sizeof(WCHAR)));
-          RegSetValueExW(hConsoleKey, L"FontSize", 0, REG_DWORD,
-                         (BYTE*)&TestFontSize, sizeof(TestFontSize));
-
-          restoreConsole = true;
-          forceNewConsole = true;
-        }
-      } else {
-        std::cerr << "RegGetValueW(FontFamily) failed!" << std::endl;
-      }
-      RegCloseKey(hConsoleKey);
-    } else {
-      std::cerr << "RegOpenKeyExW(HKEY_CURRENT_USER\\Console) failed!"
-                << std::endl;
-    }
-  }
-  if (forceNewConsole || GetConsoleMode(parentOut, &consoleMode) == 0) {
-    // Not a real console, let's create new one.
-    FreeConsole();
-    if (!AllocConsole()) {
-      std::cerr << "AllocConsole failed!" << std::endl;
-      return didFail;
-    }
-    SECURITY_ATTRIBUTES securityAttributes;
-    securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
-    securityAttributes.bInheritHandle = TRUE;
-    securityAttributes.lpSecurityDescriptor = nullptr;
-    hIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
-                      FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes,
-                      OPEN_EXISTING, 0, nullptr);
-    if (hIn == INVALID_HANDLE_VALUE) {
-      DWORD lastError = GetLastError();
-      std::cerr << "CreateFile(CONIN$)" << std::endl;
-      displayError(lastError);
-    }
-    hOut = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE,
-                       FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes,
-                       OPEN_EXISTING, 0, nullptr);
-    if (hOut == INVALID_HANDLE_VALUE) {
-      DWORD lastError = GetLastError();
-      std::cerr << "CreateFile(CONOUT$)" << std::endl;
-      displayError(lastError);
-    }
-    SetStdHandle(STD_INPUT_HANDLE, hIn);
-    SetStdHandle(STD_OUTPUT_HANDLE, hOut);
-    SetStdHandle(STD_ERROR_HANDLE, hOut);
-    newConsole = true;
-  }
-
-#  if _WIN32_WINNT >= _WIN32_WINNT_VISTA
-  if (isVistaOrGreater) {
-    CONSOLE_FONT_INFOEX consoleFont;
-    memset(&consoleFont, 0, sizeof(consoleFont));
-    consoleFont.cbSize = sizeof(consoleFont);
-    HMODULE kernel32 = LoadLibraryW(L"kernel32.dll");
-    typedef BOOL(WINAPI * GetCurrentConsoleFontExFunc)(
-      HANDLE hConsoleOutput, BOOL bMaximumWindow,
-      PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);
-    typedef BOOL(WINAPI * SetCurrentConsoleFontExFunc)(
-      HANDLE hConsoleOutput, BOOL bMaximumWindow,
-      PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);
-    GetCurrentConsoleFontExFunc getConsoleFont =
-      (GetCurrentConsoleFontExFunc)GetProcAddress(kernel32,
-                                                  "GetCurrentConsoleFontEx");
-    SetCurrentConsoleFontExFunc setConsoleFont =
-      (SetCurrentConsoleFontExFunc)GetProcAddress(kernel32,
-                                                  "SetCurrentConsoleFontEx");
-    if (getConsoleFont(hOut, FALSE, &consoleFont)) {
-      if (consoleFont.FontFamily != TestFontFamily) {
-        consoleFont.FontFamily = TestFontFamily;
-        wcscpy(consoleFont.FaceName, TestFaceName);
-        if (!setConsoleFont(hOut, FALSE, &consoleFont)) {
-          std::cerr << "SetCurrentConsoleFontEx failed!" << std::endl;
-        }
-      }
-    } else {
-      std::cerr << "GetCurrentConsoleFontEx failed!" << std::endl;
-    }
-  } else {
-#  endif
-    if (restoreConsole &&
-        RegOpenKeyExW(HKEY_CURRENT_USER, L"Console", 0, KEY_WRITE,
-                      &hConsoleKey) == ERROR_SUCCESS) {
-      RegSetValueExW(hConsoleKey, L"FontFamily", 0, REG_DWORD,
-                     (BYTE*)&FontFamily, sizeof(FontFamily));
-      if (FaceName[0] != 0) {
-        RegSetValueExW(hConsoleKey, L"FaceName", 0, REG_SZ, (BYTE*)FaceName,
-                       FaceNameSize);
-      } else {
-        RegDeleteValueW(hConsoleKey, L"FaceName");
-      }
-      RegSetValueExW(hConsoleKey, L"FontSize", 0, REG_DWORD, (BYTE*)&FontSize,
-                     sizeof(FontSize));
-      RegCloseKey(hConsoleKey);
-    }
-#  if _WIN32_WINNT >= _WIN32_WINNT_VISTA
-  }
-#  endif
-
-  if (createProcess(nullptr, nullptr, nullptr)) {
-    try {
-      DWORD status;
-      if ((status = WaitForSingleObject(beforeInputEvent, waitTimeout)) !=
-          WAIT_OBJECT_0) {
-        std::cerr.setf(std::ios::hex, std::ios::basefield);
-        std::cerr << "WaitForSingleObject returned unexpected status 0x"
-                  << status << std::endl;
-        std::cerr.unsetf(std::ios::hex);
-        throw std::runtime_error("WaitForSingleObject#1 failed!");
-      }
-      INPUT_RECORD inputBuffer[(sizeof(UnicodeInputTestString) /
-                                sizeof(UnicodeInputTestString[0])) *
-                               2];
-      memset(&inputBuffer, 0, sizeof(inputBuffer));
-      unsigned int i;
-      for (i = 0; i < (sizeof(UnicodeInputTestString) /
-                         sizeof(UnicodeInputTestString[0]) -
-                       1);
-           i++) {
-        writeInputKeyEvent(&inputBuffer[i * 2], UnicodeInputTestString[i]);
-      }
-      writeInputKeyEvent(&inputBuffer[i * 2], VK_RETURN);
-      DWORD eventsWritten = 0;
-      // We need to wait a bit before writing to console so child process have
-      // started waiting for input on stdin.
-      Sleep(300);
-      if (!WriteConsoleInputW(hIn, inputBuffer,
-                              sizeof(inputBuffer) / sizeof(inputBuffer[0]),
-                              &eventsWritten) ||
-          eventsWritten == 0) {
-        throw std::runtime_error("WriteConsoleInput failed!");
-      }
-      if ((status = WaitForSingleObject(afterOutputEvent, waitTimeout)) !=
-          WAIT_OBJECT_0) {
-        std::cerr.setf(std::ios::hex, std::ios::basefield);
-        std::cerr << "WaitForSingleObject returned unexpected status 0x"
-                  << status << std::endl;
-        std::cerr.unsetf(std::ios::hex);
-        throw std::runtime_error("WaitForSingleObject#2 failed!");
-      }
-      CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
-      if (!GetConsoleScreenBufferInfo(hOut, &screenBufferInfo)) {
-        throw std::runtime_error("GetConsoleScreenBufferInfo failed!");
-      }
-
-      COORD coord;
-      DWORD charsRead = 0;
-      coord.X = 0;
-      coord.Y = screenBufferInfo.dwCursorPosition.Y - 4;
-      WCHAR* outputBuffer = new WCHAR[screenBufferInfo.dwSize.X * 4];
-      if (!ReadConsoleOutputCharacterW(hOut, outputBuffer,
-                                       screenBufferInfo.dwSize.X * 4, coord,
-                                       &charsRead) ||
-          charsRead == 0) {
-        delete[] outputBuffer;
-        throw std::runtime_error("ReadConsoleOutputCharacter failed!");
-      }
-      std::wstring wideTestString = kwsys::Encoding::ToWide(encodedTestString);
-      if (consoleIsConhost()) {
-        // Windows Console Host converts NUL bytes to spaces.
-        std::replace(wideTestString.begin(), wideTestString.end(), '\0', ' ');
-      } else {
-        // Windows Terminal / ConPTY removes NUL bytes.
-        wideTestString.erase(std::remove_if(wideTestString.begin(),
-                                            wideTestString.end(), charIsNUL),
-                             wideTestString.end());
-      }
-      std::wstring wideInputTestString =
-        kwsys::Encoding::ToWide(encodedInputTestString);
-      if (memcmp(outputBuffer, wideTestString.c_str(),
-                 wideTestString.size() * sizeof(wchar_t)) == 0 &&
-          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 1,
-                 wideTestString.c_str(),
-                 wideTestString.size() * sizeof(wchar_t)) == 0 &&
-          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 2,
-                 UnicodeInputTestString,
-                 sizeof(UnicodeInputTestString) - sizeof(WCHAR)) == 0 &&
-          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 3,
-                 wideInputTestString.c_str(),
-                 (wideInputTestString.size() - 1) * sizeof(wchar_t)) == 0) {
-        didFail = 0;
-      } else {
-        std::cerr << "Console's output didn't match expected output!"
-                  << std::endl;
-        dumpBuffers<wchar_t>(wideTestString.c_str(), outputBuffer,
-                             wideTestString.size());
-        dumpBuffers<wchar_t>(wideTestString.c_str(),
-                             outputBuffer + screenBufferInfo.dwSize.X * 1,
-                             wideTestString.size());
-        dumpBuffers<wchar_t>(
-          UnicodeInputTestString, outputBuffer + screenBufferInfo.dwSize.X * 2,
-          (sizeof(UnicodeInputTestString) - 1) / sizeof(WCHAR));
-        dumpBuffers<wchar_t>(wideInputTestString.c_str(),
-                             outputBuffer + screenBufferInfo.dwSize.X * 3,
-                             wideInputTestString.size() - 1);
-      }
-      delete[] outputBuffer;
-    } catch (std::runtime_error const& ex) {
-      DWORD lastError = GetLastError();
-      std::cerr << "In function testConsole, line " << __LINE__ << ": "
-                << ex.what() << std::endl;
-      displayError(lastError);
-    }
-    finishProcess(didFail == 0);
-  }
-  if (newConsole) {
-    SetStdHandle(STD_INPUT_HANDLE, parentIn);
-    SetStdHandle(STD_OUTPUT_HANDLE, parentOut);
-    SetStdHandle(STD_ERROR_HANDLE, parentErr);
-    CloseHandle(hIn);
-    CloseHandle(hOut);
-    FreeConsole();
-  }
-  return didFail;
-}
-
-#endif
-
-int testConsoleBuf(int, char*[])
-{
-  int ret = 0;
-
-#if defined(_WIN32)
-  beforeInputEvent = CreateEventW(nullptr,
-                                  FALSE, // auto-reset event
-                                  FALSE, // initial state is nonsignaled
-                                  BeforeInputEventName); // object name
-  if (!beforeInputEvent) {
-    std::cerr << "CreateEvent#1 failed " << GetLastError() << std::endl;
-    return 1;
-  }
-
-  afterOutputEvent = CreateEventW(nullptr, FALSE, FALSE, AfterOutputEventName);
-  if (!afterOutputEvent) {
-    std::cerr << "CreateEvent#2 failed " << GetLastError() << std::endl;
-    return 1;
-  }
-
-  encodedTestString = kwsys::Encoding::ToNarrow(std::wstring(
-    UnicodeTestString, sizeof(UnicodeTestString) / sizeof(wchar_t) - 1));
-  encodedInputTestString = kwsys::Encoding::ToNarrow(
-    std::wstring(UnicodeInputTestString,
-                 sizeof(UnicodeInputTestString) / sizeof(wchar_t) - 1));
-  encodedInputTestString += "\n";
-
-  ret |= testPipe();
-  ret |= testFile();
-  ret |= testConsole();
-
-  CloseHandle(beforeInputEvent);
-  CloseHandle(afterOutputEvent);
-#endif
-
-  return ret;
-}

+ 0 - 17
Source/kwsys/testConsoleBuf.hxx

@@ -1,17 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
-#ifndef testConsoleBuf_hxx
-#define testConsoleBuf_hxx
-
-static wchar_t const cmdConsoleBufChild[] = L"testConsoleBufChild";
-
-static wchar_t const BeforeInputEventName[] = L"BeforeInputEvent";
-static wchar_t const AfterOutputEventName[] = L"AfterOutputEvent";
-
-// यूनिकोड είναι здорово!
-static wchar_t const UnicodeTestString[] =
-  L"\u092F\u0942\u0928\u093F\u0915\u094B\u0921 "
-  L"\u03B5\u03AF\u03BD\0\u03B1\u03B9 "
-  L"\u0437\u0434\u043E\u0440\u043E\u0432\u043E!";
-
-#endif

+ 0 - 55
Source/kwsys/testConsoleBufChild.cxx

@@ -1,55 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
-#include "kwsysPrivate.h"
-
-#include KWSYS_HEADER(ConsoleBuf.hxx)
-#include KWSYS_HEADER(Encoding.hxx)
-
-// Work-around CMake dependency scanning limitation.  This must
-// duplicate the above list of headers.
-#if 0
-#  include "ConsoleBuf.hxx.in"
-#  include "Encoding.hxx.in"
-#endif
-
-#include <iostream>
-
-#include "testConsoleBuf.hxx"
-
-int main(int argc, char const* argv[])
-{
-#if defined(_WIN32)
-  kwsys::ConsoleBuf::Manager out(std::cout);
-  kwsys::ConsoleBuf::Manager err(std::cerr, true);
-  kwsys::ConsoleBuf::Manager in(std::cin);
-
-  if (argc > 1) {
-    std::cout << argv[1] << std::endl;
-    std::cerr << argv[1] << std::endl;
-  } else {
-    std::string str = kwsys::Encoding::ToNarrow(std::wstring(
-      UnicodeTestString, sizeof(UnicodeTestString) / sizeof(wchar_t) - 1));
-    std::cout << str << std::endl;
-    std::cerr << str << std::endl;
-  }
-
-  std::string input;
-  HANDLE event = OpenEventW(EVENT_MODIFY_STATE, FALSE, BeforeInputEventName);
-  if (event) {
-    SetEvent(event);
-    CloseHandle(event);
-  }
-
-  std::cin >> input;
-  std::cout << input << std::endl;
-  event = OpenEventW(EVENT_MODIFY_STATE, FALSE, AfterOutputEventName);
-  if (event) {
-    SetEvent(event);
-    CloseHandle(event);
-  }
-#else
-  static_cast<void>(argc);
-  static_cast<void>(argv);
-#endif
-  return 0;
-}

+ 48 - 1
Tests/CMakeLib/testStdIo.cxx

@@ -1,9 +1,12 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file LICENSE.rst or https://cmake.org/licensing for details.  */
 
+#include <string>
+
 #include <cm/string_view>
 #include <cmext/string_view>
 
+#include "cmStdIoConsole.h"
 #include "cmStdIoInit.h"
 #include "cmStdIoStream.h"
 
@@ -11,6 +14,18 @@
 
 namespace {
 
+#ifdef _WIN32
+cm::string_view const kUTF8 =
+  "  Chinese Hindi  Greek English Russian\n  "
+  "\xe6\xb3\xa8\xe6\x84\x8f    "                             // Chinese
+  "\xe0\xa4\xaf\xe0\xa5\x82\xe0\xa4\xa8\xe0"                 // ...
+  "\xa4\xbf\xe0\xa4\x95\xe0\xa5\x8b\xe0\xa4\xa1 "            // Hindi
+  "\xce\xb5\xce\xaf\xce\xbd\xce\xb1\xce\xb9 "                // Greek
+  "very    "                                                 // English
+  "\xd0\xb7\xd0\xb4\xd0\xbe\xd1\x80\xd0\xbe\xd0\xb2\xd0\xbe" // Russian
+  "!"_s;
+#endif
+
 void printTermKind(cm::string_view t, cm::StdIo::Stream& s)
 {
   switch (s.Kind()) {
@@ -37,12 +52,44 @@ bool testStream()
   return true;
 }
 
+bool testConsoleStdIn = false;
+
+bool testConsole()
+{
+  std::cout << "testConsole()\n";
+#ifdef _WIN32
+  std::cout << kUTF8 << '\n';
+#endif
+  if (testConsoleStdIn) {
+    std::cout << "  input: " << std::flush;
+    std::string line;
+    if (std::getline(std::cin, line)) {
+      std::cout << " output: " << line << '\n';
+    }
+  }
+  return true;
+}
+
+cm::string_view const kUsage = "usage: CMakeLibTests testStdIo [--stdin]"_s;
+
 }
 
-int testStdIo(int /*unused*/, char* /*unused*/[])
+int testStdIo(int argc, char* argv[])
 {
   cm::StdIo::Init();
+  cm::StdIo::Console console;
+
+  for (int i = 1; i < argc; ++i) {
+    if (argv[i] == "--stdin"_s && !testConsoleStdIn) {
+      testConsoleStdIn = true;
+    } else {
+      std::cerr << kUsage << '\n';
+      return 1;
+    }
+  }
+
   return runTests({
     testStream,
+    testConsole,
   });
 }

+ 5 - 1
bootstrap

@@ -322,7 +322,6 @@ CMAKE_CXX_SOURCES="\
   cmComputeLinkDepends \
   cmComputeLinkInformation \
   cmComputeTargetDepends \
-  cmConsoleBuf \
   cmConditionEvaluator \
   cmConfigureFileCommand \
   cmContinueCommand \
@@ -489,6 +488,7 @@ CMAKE_CXX_SOURCES="\
   cmState \
   cmStateDirectory \
   cmStateSnapshot \
+  cmStdIoConsole \
   cmStdIoInit \
   cmStdIoStream \
   cmString \
@@ -544,6 +544,10 @@ if ${cmake_system_mingw}; then
   "
 fi
 
+CMAKE_C_SOURCES="\
+  cm_utf8 \
+"
+
 CMAKE_STD_CXX_HEADERS="\
   filesystem \
   memory \