From 1d601c6cb978a3b6b6143fdf64e284fb3a098d1e Mon Sep 17 00:00:00 2001
From: Tobias Hunger <tobias.hunger@qt.io>
Date: Fri, 9 Sep 2016 10:01:44 +0200
Subject: server-mode: Introduce cmServerConnection

Use it to split pipe and stdin/out handling out of cmServer itself.

The server will shut down when it looses its connection to the client.
This has the nice property that a crashing client will cause the server
to terminate as the OS will close the connection on behave of the client.
---
 Help/manual/cmake-server.7.rst                     |  13 +-
 Source/CMakeLists.txt                              |   1 +
 Source/cmServer.cxx                                | 145 ++--------
 Source/cmServer.h                                  |  15 +-
 Source/cmServerConnection.cxx                      | 307 +++++++++++++++++++++
 Source/cmServerConnection.h                        |  97 +++++++
 Source/cmcmd.cxx                                   |  42 ++-
 .../RunCMake/CommandLine/E_server-pipe-result.txt  |   1 +
 .../RunCMake/CommandLine/E_server-pipe-stderr.txt  |   1 +
 Tests/RunCMake/CommandLine/RunCMakeTest.cmake      |   1 +
 Tests/Server/cmakelib.py                           |   2 +-
 11 files changed, 476 insertions(+), 149 deletions(-)
 create mode 100644 Source/cmServerConnection.cxx
 create mode 100644 Source/cmServerConnection.h
 create mode 100644 Tests/RunCMake/CommandLine/E_server-pipe-result.txt
 create mode 100644 Tests/RunCMake/CommandLine/E_server-pipe-stderr.txt

diff --git a/Help/manual/cmake-server.7.rst b/Help/manual/cmake-server.7.rst
index 75aa0ee..00ffcd1 100644
--- a/Help/manual/cmake-server.7.rst
+++ b/Help/manual/cmake-server.7.rst
@@ -49,12 +49,16 @@ Operation
 Start :manual:`cmake(1)` in the server command mode, supplying the path to
 the build directory to process::
 
-  cmake -E server
+  cmake -E server (--debug|--pipe <NAMED_PIPE>)
 
-The server will start up and reply with an hello message on stdout::
+The server will communicate using stdin/stdout (with the ``--debug`` parameter)
+or using a named pipe (with the ``--pipe <NAMED_PIPE>`` parameter).
+
+When connecting to the server (via named pipe or by starting it in ``--debug``
+mode), the server will reply with a hello message::
 
   [== CMake Server ==[
-  {"supportedProtocolVersions":[{"major":0,"minor":1}],"type":"hello"}
+  {"supportedProtocolVersions":[{"major":1,"minor":0}],"type":"hello"}
   ]== CMake Server ==]
 
 Messages sent to and from the process are wrapped in magic strings::
@@ -65,7 +69,8 @@ Messages sent to and from the process are wrapped in magic strings::
   }
   ]== CMake Server ==]
 
-The server is now ready to accept further requests via stdin.
+The server is now ready to accept further requests via the named pipe
+or stdin.
 
 
 Debugging
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index a2dead6..5e136e4 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -789,6 +789,7 @@ target_link_libraries(cmake CMakeLib)
 if(CMake_HAVE_SERVER_MODE)
   add_library(CMakeServerLib
     cmServer.cxx cmServer.h
+    cmServerConnection.cxx cmServerConnection.h
     cmServerProtocol.cxx cmServerProtocol.h
     )
   target_link_libraries(CMakeServerLib CMakeLib)
diff --git a/Source/cmServer.cxx b/Source/cmServer.cxx
index be001a7..56cd7ba 100644
--- a/Source/cmServer.cxx
+++ b/Source/cmServer.cxx
@@ -13,6 +13,7 @@
 
 #include "cmServer.h"
 
+#include "cmServerConnection.h"
 #include "cmServerProtocol.h"
 #include "cmSystemTools.h"
 #include "cmVersionMacros.h"
@@ -40,57 +41,6 @@ static const std::string kMESSAGE_TYPE = "message";
 static const std::string kSTART_MAGIC = "[== CMake Server ==[";
 static const std::string kEND_MAGIC = "]== CMake Server ==]";
 
-typedef struct
-{
-  uv_write_t req;
-  uv_buf_t buf;
-} write_req_t;
-
-void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
-{
-  (void)handle;
-  *buf = uv_buf_init(static_cast<char*>(malloc(suggested_size)),
-                     static_cast<unsigned int>(suggested_size));
-}
-
-void free_write_req(uv_write_t* req)
-{
-  write_req_t* wr = reinterpret_cast<write_req_t*>(req);
-  free(wr->buf.base);
-  free(wr);
-}
-
-void on_stdout_write(uv_write_t* req, int status)
-{
-  (void)status;
-  auto server = reinterpret_cast<cmServer*>(req->data);
-  free_write_req(req);
-  server->PopOne();
-}
-
-void write_data(uv_stream_t* dest, std::string content, uv_write_cb cb)
-{
-  write_req_t* req = static_cast<write_req_t*>(malloc(sizeof(write_req_t)));
-  req->req.data = dest->data;
-  req->buf = uv_buf_init(static_cast<char*>(malloc(content.size())),
-                         static_cast<unsigned int>(content.size()));
-  memcpy(req->buf.base, content.c_str(), content.size());
-  uv_write(reinterpret_cast<uv_write_t*>(req), static_cast<uv_stream_t*>(dest),
-           &req->buf, 1, cb);
-}
-
-void read_stdin(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
-{
-  if (nread > 0) {
-    auto server = reinterpret_cast<cmServer*>(stream->data);
-    std::string result = std::string(buf->base, buf->base + nread);
-    server->handleData(result);
-  }
-
-  if (buf->base)
-    free(buf->base);
-}
-
 class cmServer::DebugInfo
 {
 public:
@@ -105,9 +55,11 @@ public:
   uint64_t StartTime;
 };
 
-cmServer::cmServer(bool supportExperimental)
-  : SupportExperimental(supportExperimental)
+cmServer::cmServer(cmServerConnection* conn, bool supportExperimental)
+  : Connection(conn)
+  , SupportExperimental(supportExperimental)
 {
+  this->Connection->SetServer(this);
   // Register supported protocols:
   this->RegisterProtocol(new cmServerProtocol1_0);
 }
@@ -118,18 +70,15 @@ cmServer::~cmServer()
     return;
   }
 
-  uv_close(reinterpret_cast<uv_handle_t*>(this->InputStream), NULL);
-  uv_close(reinterpret_cast<uv_handle_t*>(this->OutputStream), NULL);
-  uv_loop_close(this->Loop);
-
   for (cmServerProtocol* p : this->SupportedProtocols) {
     delete p;
   }
+
+  delete this->Connection;
 }
 
 void cmServer::PopOne()
 {
-  this->Writing = false;
   if (this->Queue.empty()) {
     return;
   }
@@ -173,39 +122,6 @@ void cmServer::PopOne()
   }
 }
 
-void cmServer::handleData(const std::string& data)
-{
-  this->DataBuffer += data;
-
-  for (;;) {
-    auto needle = this->DataBuffer.find('\n');
-
-    if (needle == std::string::npos) {
-      return;
-    }
-    std::string line = this->DataBuffer.substr(0, needle);
-    const auto ls = line.size();
-    if (ls > 1 && line.at(ls - 1) == '\r')
-      line.erase(ls - 1, 1);
-    this->DataBuffer.erase(this->DataBuffer.begin(),
-                           this->DataBuffer.begin() + needle + 1);
-    if (line == kSTART_MAGIC) {
-      this->JsonData.clear();
-      continue;
-    }
-    if (line == kEND_MAGIC) {
-      this->Queue.push_back(this->JsonData);
-      this->JsonData.clear();
-      if (!this->Writing) {
-        this->PopOne();
-      }
-    } else {
-      this->JsonData += line;
-      this->JsonData += "\n";
-    }
-  }
-}
-
 void cmServer::RegisterProtocol(cmServerProtocol* protocol)
 {
   if (protocol->IsExperimental() && !this->SupportExperimental) {
@@ -245,6 +161,12 @@ void cmServer::PrintHello() const
   this->WriteJsonObject(hello, nullptr);
 }
 
+void cmServer::QueueRequest(const std::string& request)
+{
+  this->Queue.push_back(request);
+  this->PopOne();
+}
+
 void cmServer::reportProgress(const char* msg, float progress, void* data)
 {
   const cmServerRequest* request = static_cast<const cmServerRequest*>(data);
@@ -312,43 +234,16 @@ cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request)
   return request.Reply(Json::objectValue);
 }
 
-bool cmServer::Serve()
+bool cmServer::Serve(std::string* errorMessage)
 {
   if (this->SupportedProtocols.empty()) {
+    *errorMessage =
+      "No protocol versions defined. Maybe you need --experimental?";
     return false;
   }
   assert(!this->Protocol);
 
-  this->Loop = uv_default_loop();
-
-  if (uv_guess_handle(1) == UV_TTY) {
-    uv_tty_init(this->Loop, &this->Input.tty, 0, 1);
-    uv_tty_set_mode(&this->Input.tty, UV_TTY_MODE_NORMAL);
-    this->Input.tty.data = this;
-    InputStream = reinterpret_cast<uv_stream_t*>(&this->Input.tty);
-
-    uv_tty_init(this->Loop, &this->Output.tty, 1, 0);
-    uv_tty_set_mode(&this->Output.tty, UV_TTY_MODE_NORMAL);
-    this->Output.tty.data = this;
-    OutputStream = reinterpret_cast<uv_stream_t*>(&this->Output.tty);
-  } else {
-    uv_pipe_init(this->Loop, &this->Input.pipe, 0);
-    uv_pipe_open(&this->Input.pipe, 0);
-    this->Input.pipe.data = this;
-    InputStream = reinterpret_cast<uv_stream_t*>(&this->Input.pipe);
-
-    uv_pipe_init(this->Loop, &this->Output.pipe, 0);
-    uv_pipe_open(&this->Output.pipe, 1);
-    this->Output.pipe.data = this;
-    OutputStream = reinterpret_cast<uv_stream_t*>(&this->Output.pipe);
-  }
-
-  this->PrintHello();
-
-  uv_read_start(this->InputStream, alloc_buffer, read_stdin);
-
-  uv_run(this->Loop, UV_RUN_DEFAULT);
-  return true;
+  return Connection->ProcessEvents(errorMessage);
 }
 
 void cmServer::WriteJsonObject(const Json::Value& jsonValue,
@@ -385,10 +280,8 @@ void cmServer::WriteJsonObject(const Json::Value& jsonValue,
     }
   }
 
-  this->Writing = true;
-  write_data(this->OutputStream, std::string("\n") + kSTART_MAGIC +
-               std::string("\n") + result + kEND_MAGIC + std::string("\n"),
-             on_stdout_write);
+  Connection->WriteData(std::string("\n") + kSTART_MAGIC + std::string("\n") +
+                        result + kEND_MAGIC + std::string("\n"));
 }
 
 cmServerProtocol* cmServer::FindMatchingProtocol(
diff --git a/Source/cmServer.h b/Source/cmServer.h
index 38a11bb..dde5333 100644
--- a/Source/cmServer.h
+++ b/Source/cmServer.h
@@ -24,6 +24,7 @@
 #include <string>
 #include <vector>
 
+class cmServerConnection;
 class cmServerProtocol;
 class cmServerRequest;
 class cmServerResponse;
@@ -33,18 +34,18 @@ class cmServer
 public:
   class DebugInfo;
 
-  cmServer(bool supportExperimental);
+  cmServer(cmServerConnection* conn, bool supportExperimental);
   ~cmServer();
 
-  bool Serve();
-
-  // for callbacks:
-  void PopOne();
-  void handleData(std::string const& data);
+  bool Serve(std::string* errorMessage);
 
 private:
   void RegisterProtocol(cmServerProtocol* protocol);
 
+  // Callbacks from cmServerConnection:
+  void PopOne();
+  void QueueRequest(const std::string& request);
+
   static void reportProgress(const char* msg, float progress, void* data);
   static void reportMessage(const char* msg, const char* title, bool& cancel,
                             void* data);
@@ -69,6 +70,7 @@ private:
   static cmServerProtocol* FindMatchingProtocol(
     const std::vector<cmServerProtocol*>& protocols, int major, int minor);
 
+  cmServerConnection* Connection = nullptr;
   const bool SupportExperimental;
 
   cmServerProtocol* Protocol = nullptr;
@@ -94,4 +96,5 @@ private:
   mutable bool Writing = false;
 
   friend class cmServerRequest;
+  friend class cmServerConnection;
 };
diff --git a/Source/cmServerConnection.cxx b/Source/cmServerConnection.cxx
new file mode 100644
index 0000000..398e250
--- /dev/null
+++ b/Source/cmServerConnection.cxx
@@ -0,0 +1,307 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2015 Stephen Kelly <steveire@gmail.com>
+  Copyright 2016 Tobias Hunger <tobias.hunger@qt.io>
+
+  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.
+============================================================================*/
+
+#include "cmServerConnection.h"
+
+#include <cmServer.h>
+
+#include <assert.h>
+
+namespace {
+
+static const std::string kSTART_MAGIC = "[== CMake Server ==[";
+static const std::string kEND_MAGIC = "]== CMake Server ==]";
+
+struct write_req_t
+{
+  uv_write_t req;
+  uv_buf_t buf;
+};
+
+void on_alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
+{
+  (void)(handle);
+  char* rawBuffer = new char[suggested_size];
+  *buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size));
+}
+
+void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
+{
+  auto conn = reinterpret_cast<cmServerConnection*>(stream->data);
+  if (nread >= 0) {
+    conn->ReadData(std::string(buf->base, buf->base + nread));
+  } else {
+    conn->HandleEof();
+  }
+
+  delete[](buf->base);
+}
+
+void on_write(uv_write_t* req, int status)
+{
+  (void)(status);
+  auto conn = reinterpret_cast<cmServerConnection*>(req->data);
+
+  // Free req and buffer
+  write_req_t* wr = reinterpret_cast<write_req_t*>(req);
+  delete[](wr->buf.base);
+  delete wr;
+
+  conn->ProcessNextRequest();
+}
+
+void on_new_connection(uv_stream_t* stream, int status)
+{
+  (void)(status);
+  auto conn = reinterpret_cast<cmServerConnection*>(stream->data);
+  conn->Connect(stream);
+}
+
+} // namespace
+
+class LoopGuard
+{
+public:
+  LoopGuard(cmServerConnection* connection)
+    : Connection(connection)
+  {
+    Connection->mLoop = uv_default_loop();
+  }
+
+  ~LoopGuard()
+  {
+    uv_loop_close(Connection->mLoop);
+    Connection->mLoop = nullptr;
+  }
+
+private:
+  cmServerConnection* Connection;
+};
+
+cmServerConnection::cmServerConnection()
+{
+}
+
+cmServerConnection::~cmServerConnection()
+{
+}
+
+void cmServerConnection::SetServer(cmServer* s)
+{
+  this->Server = s;
+}
+
+bool cmServerConnection::ProcessEvents(std::string* errorMessage)
+{
+  assert(this->Server);
+  errorMessage->clear();
+
+  this->RawReadBuffer.clear();
+  this->RequestBuffer.clear();
+
+  LoopGuard guard(this);
+  (void)(guard);
+  if (!this->mLoop) {
+    *errorMessage = "Internal Error: Failed to create event loop.";
+    return false;
+  }
+
+  if (!DoSetup(errorMessage)) {
+    return false;
+  }
+
+  if (uv_run(this->mLoop, UV_RUN_DEFAULT) != 0) {
+    *errorMessage = "Internal Error: Event loop stopped in unclean state.";
+    return false;
+  }
+
+  // These need to be cleaned up by now:
+  assert(!this->ReadStream);
+  assert(!this->WriteStream);
+
+  this->RawReadBuffer.clear();
+  this->RequestBuffer.clear();
+
+  return true;
+}
+
+void cmServerConnection::ReadData(const std::string& data)
+{
+  this->RawReadBuffer += data;
+
+  for (;;) {
+    auto needle = this->RawReadBuffer.find('\n');
+
+    if (needle == std::string::npos) {
+      return;
+    }
+    std::string line = this->RawReadBuffer.substr(0, needle);
+    const auto ls = line.size();
+    if (ls > 1 && line.at(ls - 1) == '\r')
+      line.erase(ls - 1, 1);
+    this->RawReadBuffer.erase(this->RawReadBuffer.begin(),
+                              this->RawReadBuffer.begin() +
+                                static_cast<long>(needle) + 1);
+    if (line == kSTART_MAGIC) {
+      this->RequestBuffer.clear();
+      continue;
+    }
+    if (line == kEND_MAGIC) {
+      this->Server->QueueRequest(this->RequestBuffer);
+      this->RequestBuffer.clear();
+    } else {
+      this->RequestBuffer += line;
+      this->RequestBuffer += "\n";
+    }
+  }
+}
+
+void cmServerConnection::HandleEof()
+{
+  this->TearDown();
+}
+
+void cmServerConnection::WriteData(const std::string& data)
+{
+  assert(this->WriteStream);
+
+  auto ds = data.size();
+
+  write_req_t* req = new write_req_t;
+  req->req.data = this;
+  req->buf = uv_buf_init(new char[ds], static_cast<unsigned int>(ds));
+  memcpy(req->buf.base, data.c_str(), ds);
+
+  uv_write(reinterpret_cast<uv_write_t*>(req),
+           static_cast<uv_stream_t*>(this->WriteStream), &req->buf, 1,
+           on_write);
+}
+
+void cmServerConnection::ProcessNextRequest()
+{
+  Server->PopOne();
+}
+
+void cmServerConnection::SendGreetings()
+{
+  Server->PrintHello();
+}
+
+bool cmServerStdIoConnection::DoSetup(std::string* errorMessage)
+{
+  (void)(errorMessage);
+
+  if (uv_guess_handle(1) == UV_TTY) {
+    uv_tty_init(this->Loop(), &this->Input.tty, 0, 1);
+    uv_tty_set_mode(&this->Input.tty, UV_TTY_MODE_NORMAL);
+    Input.tty.data = this;
+    this->ReadStream = reinterpret_cast<uv_stream_t*>(&this->Input.tty);
+
+    uv_tty_init(this->Loop(), &this->Output.tty, 1, 0);
+    uv_tty_set_mode(&this->Output.tty, UV_TTY_MODE_NORMAL);
+    Output.tty.data = this;
+    this->WriteStream = reinterpret_cast<uv_stream_t*>(&this->Output.tty);
+  } else {
+    uv_pipe_init(this->Loop(), &this->Input.pipe, 0);
+    uv_pipe_open(&this->Input.pipe, 0);
+    Input.pipe.data = this;
+    this->ReadStream = reinterpret_cast<uv_stream_t*>(&this->Input.pipe);
+
+    uv_pipe_init(this->Loop(), &this->Output.pipe, 0);
+    uv_pipe_open(&this->Output.pipe, 1);
+    Output.pipe.data = this;
+    this->WriteStream = reinterpret_cast<uv_stream_t*>(&this->Output.pipe);
+  }
+
+  SendGreetings();
+  uv_read_start(this->ReadStream, on_alloc_buffer, on_read);
+
+  return true;
+}
+
+void cmServerStdIoConnection::TearDown()
+{
+  uv_close(reinterpret_cast<uv_handle_t*>(this->ReadStream), nullptr);
+  this->ReadStream = nullptr;
+  uv_close(reinterpret_cast<uv_handle_t*>(this->WriteStream), nullptr);
+  this->WriteStream = nullptr;
+}
+
+cmServerPipeConnection::cmServerPipeConnection(const std::string& name)
+  : PipeName(name)
+{
+  this->ServerPipe.data = nullptr;
+  this->ClientPipe.data = nullptr;
+}
+
+bool cmServerPipeConnection::DoSetup(std::string* errorMessage)
+{
+  uv_pipe_init(this->Loop(), &this->ServerPipe, 0);
+  this->ServerPipe.data = this;
+
+  int r;
+  if ((r = uv_pipe_bind(&this->ServerPipe, this->PipeName.c_str())) != 0) {
+    *errorMessage = std::string("Internal Error with ") + this->PipeName +
+      ": " + uv_err_name(r);
+    return false;
+  }
+  auto serverStream = reinterpret_cast<uv_stream_t*>(&this->ServerPipe);
+  serverStream->data = this;
+  if ((r = uv_listen(serverStream, 1, on_new_connection)) != 0) {
+    *errorMessage = std::string("Internal Error with ") + this->PipeName +
+      ": " + uv_err_name(r);
+    return false;
+  }
+
+  return true;
+}
+
+void cmServerPipeConnection::TearDown()
+{
+  if (this->WriteStream->data) {
+    uv_close(reinterpret_cast<uv_handle_t*>(this->WriteStream), nullptr);
+    this->WriteStream->data = nullptr;
+  }
+  uv_close(reinterpret_cast<uv_handle_t*>(&this->ServerPipe), nullptr);
+
+  this->WriteStream = nullptr;
+  this->ReadStream = nullptr;
+}
+
+void cmServerPipeConnection::Connect(uv_stream_t* server)
+{
+  if (this->ClientPipe.data == this) {
+    // Accept and close all pipes but the first:
+    uv_pipe_t rejectPipe;
+
+    uv_pipe_init(this->Loop(), &rejectPipe, 0);
+    auto rejecter = reinterpret_cast<uv_stream_t*>(&rejectPipe);
+    uv_accept(server, rejecter);
+    uv_close(reinterpret_cast<uv_handle_t*>(rejecter), nullptr);
+    return;
+  }
+
+  uv_pipe_init(this->Loop(), &this->ClientPipe, 0);
+  this->ClientPipe.data = this;
+  auto client = reinterpret_cast<uv_stream_t*>(&this->ClientPipe);
+  if (uv_accept(server, client) != 0) {
+    uv_close(reinterpret_cast<uv_handle_t*>(client), nullptr);
+    return;
+  }
+  this->ReadStream = client;
+  this->WriteStream = client;
+
+  uv_read_start(this->ReadStream, on_alloc_buffer, on_read);
+
+  this->SendGreetings();
+}
diff --git a/Source/cmServerConnection.h b/Source/cmServerConnection.h
new file mode 100644
index 0000000..fa86e71
--- /dev/null
+++ b/Source/cmServerConnection.h
@@ -0,0 +1,97 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2015 Stephen Kelly <steveire@gmail.com>
+  Copyright 2016 Tobias Hunger <tobias.hunger@qt.io>
+
+  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.
+============================================================================*/
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#if defined(CMAKE_BUILD_WITH_CMAKE)
+#include "cm_uv.h"
+#endif
+
+class cmServer;
+class LoopGuard;
+
+class cmServerConnection
+{
+public:
+  cmServerConnection();
+  virtual ~cmServerConnection();
+
+  void SetServer(cmServer* s);
+
+  bool ProcessEvents(std::string* errorMessage);
+
+  void ReadData(const std::string& data);
+  void HandleEof();
+  void WriteData(const std::string& data);
+  void ProcessNextRequest();
+
+  virtual void Connect(uv_stream_t* server) { (void)(server); }
+
+protected:
+  virtual bool DoSetup(std::string* errorMessage) = 0;
+  virtual void TearDown() = 0;
+
+  void SendGreetings();
+
+  uv_loop_t* Loop() const { return mLoop; }
+
+protected:
+  std::string RawReadBuffer;
+  std::string RequestBuffer;
+
+  uv_stream_t* ReadStream = nullptr;
+  uv_stream_t* WriteStream = nullptr;
+
+private:
+  uv_loop_t* mLoop = nullptr;
+  cmServer* Server = nullptr;
+
+  friend class LoopGuard;
+};
+
+class cmServerStdIoConnection : public cmServerConnection
+{
+public:
+  bool DoSetup(std::string* errorMessage) override;
+
+  void TearDown() override;
+
+private:
+  typedef union
+  {
+    uv_tty_t tty;
+    uv_pipe_t pipe;
+  } InOutUnion;
+
+  InOutUnion Input;
+  InOutUnion Output;
+};
+
+class cmServerPipeConnection : public cmServerConnection
+{
+public:
+  cmServerPipeConnection(const std::string& name);
+  bool DoSetup(std::string* errorMessage) override;
+
+  void TearDown() override;
+
+  void Connect(uv_stream_t* server) override;
+
+private:
+  const std::string PipeName;
+  uv_pipe_t ServerPipe;
+  uv_pipe_t ClientPipe;
+};
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index 38f00e6..9daed4b 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -25,6 +25,7 @@
 
 #if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
 #include "cmServer.h"
+#include "cmServerConnection.h"
 #endif
 
 #if defined(CMAKE_BUILD_WITH_CMAKE)
@@ -913,32 +914,49 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
       }
       return 0;
     } else if (args[1] == "server") {
-      if (args.size() > 3) {
-        cmSystemTools::Error("Too many arguments to start server mode");
-        return 1;
-      }
+      const std::string pipePrefix = "--pipe=";
       bool supportExperimental = false;
-      if (args.size() == 3) {
-        if (args[2] == "--experimental") {
+      bool isDebug = false;
+      std::string pipe;
+
+      for (size_t i = 2; i < args.size(); ++i) {
+        const std::string& a = args[i];
+
+        if (a == "--experimental") {
           supportExperimental = true;
+        } else if (a == "--debug") {
+          pipe.clear();
+          isDebug = true;
+        } else if (a.substr(0, pipePrefix.size()) == pipePrefix) {
+          isDebug = false;
+          pipe = a.substr(pipePrefix.size());
+          if (pipe.empty()) {
+            cmSystemTools::Error("No pipe given after --pipe=");
+            return 2;
+          }
         } else {
           cmSystemTools::Error("Unknown argument for server mode");
           return 1;
         }
       }
 #if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
-      cmServer server(supportExperimental);
-      if (server.Serve()) {
+      cmServerConnection* conn;
+      if (isDebug) {
+        conn = new cmServerStdIoConnection;
+      } else {
+        conn = new cmServerPipeConnection(pipe);
+      }
+      cmServer server(conn, supportExperimental);
+      std::string errorMessage;
+      if (server.Serve(&errorMessage)) {
         return 0;
       } else {
-        cmSystemTools::Error(
-          "CMake server could not find any supported protocol. "
-          "Try with \"--experimental\" to enable "
-          "experimental support.");
+        cmSystemTools::Error(errorMessage.c_str());
         return 1;
       }
 #else
       static_cast<void>(supportExperimental);
+      static_cast<void>(isDebug);
       cmSystemTools::Error("CMake was not built with server mode enabled");
       return 1;
 #endif
diff --git a/Tests/RunCMake/CommandLine/E_server-pipe-result.txt b/Tests/RunCMake/CommandLine/E_server-pipe-result.txt
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_server-pipe-result.txt
@@ -0,0 +1 @@
+2
diff --git a/Tests/RunCMake/CommandLine/E_server-pipe-stderr.txt b/Tests/RunCMake/CommandLine/E_server-pipe-stderr.txt
new file mode 100644
index 0000000..7193ba6
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_server-pipe-stderr.txt
@@ -0,0 +1 @@
+^CMake Error: No pipe given after --pipe=$
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index 9f76ad9..0c4f71c 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -13,6 +13,7 @@ run_cmake_command(E_capabilities-arg ${CMAKE_COMMAND} -E capabilities --extra-ar
 run_cmake_command(E_echo_append ${CMAKE_COMMAND} -E echo_append)
 run_cmake_command(E_rename-no-arg ${CMAKE_COMMAND} -E rename)
 run_cmake_command(E_server-arg ${CMAKE_COMMAND} -E server --extra-arg)
+run_cmake_command(E_server-pipe ${CMAKE_COMMAND} -E server --pipe=)
 run_cmake_command(E_touch_nocreate-no-arg ${CMAKE_COMMAND} -E touch_nocreate)
 
 run_cmake_command(E_time ${CMAKE_COMMAND} -E time ${CMAKE_COMMAND} -E echo "hello  world")
diff --git a/Tests/Server/cmakelib.py b/Tests/Server/cmakelib.py
index e89b1f0..0f98078 100644
--- a/Tests/Server/cmakelib.py
+++ b/Tests/Server/cmakelib.py
@@ -79,7 +79,7 @@ def writePayload(cmakeCommand, obj):
   writeRawData(cmakeCommand, json.dumps(obj))
 
 def initProc(cmakeCommand):
-  cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental"],
+  cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--debug"],
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE)
 
-- 
cgit v0.12