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.
This commit is contained in:
parent
2c2ffd3874
commit
1d601c6cb9
|
@ -49,12 +49,16 @@ Operation
|
||||||
Start :manual:`cmake(1)` in the server command mode, supplying the path to
|
Start :manual:`cmake(1)` in the server command mode, supplying the path to
|
||||||
the build directory to process::
|
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 ==[
|
[== CMake Server ==[
|
||||||
{"supportedProtocolVersions":[{"major":0,"minor":1}],"type":"hello"}
|
{"supportedProtocolVersions":[{"major":1,"minor":0}],"type":"hello"}
|
||||||
]== CMake Server ==]
|
]== CMake Server ==]
|
||||||
|
|
||||||
Messages sent to and from the process are wrapped in magic strings::
|
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 ==]
|
]== 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
|
Debugging
|
||||||
|
|
|
@ -789,6 +789,7 @@ target_link_libraries(cmake CMakeLib)
|
||||||
if(CMake_HAVE_SERVER_MODE)
|
if(CMake_HAVE_SERVER_MODE)
|
||||||
add_library(CMakeServerLib
|
add_library(CMakeServerLib
|
||||||
cmServer.cxx cmServer.h
|
cmServer.cxx cmServer.h
|
||||||
|
cmServerConnection.cxx cmServerConnection.h
|
||||||
cmServerProtocol.cxx cmServerProtocol.h
|
cmServerProtocol.cxx cmServerProtocol.h
|
||||||
)
|
)
|
||||||
target_link_libraries(CMakeServerLib CMakeLib)
|
target_link_libraries(CMakeServerLib CMakeLib)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#include "cmServer.h"
|
#include "cmServer.h"
|
||||||
|
|
||||||
|
#include "cmServerConnection.h"
|
||||||
#include "cmServerProtocol.h"
|
#include "cmServerProtocol.h"
|
||||||
#include "cmSystemTools.h"
|
#include "cmSystemTools.h"
|
||||||
#include "cmVersionMacros.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 kSTART_MAGIC = "[== CMake Server ==[";
|
||||||
static const std::string kEND_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
|
class cmServer::DebugInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -105,9 +55,11 @@ public:
|
||||||
uint64_t StartTime;
|
uint64_t StartTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
cmServer::cmServer(bool supportExperimental)
|
cmServer::cmServer(cmServerConnection* conn, bool supportExperimental)
|
||||||
: SupportExperimental(supportExperimental)
|
: Connection(conn)
|
||||||
|
, SupportExperimental(supportExperimental)
|
||||||
{
|
{
|
||||||
|
this->Connection->SetServer(this);
|
||||||
// Register supported protocols:
|
// Register supported protocols:
|
||||||
this->RegisterProtocol(new cmServerProtocol1_0);
|
this->RegisterProtocol(new cmServerProtocol1_0);
|
||||||
}
|
}
|
||||||
|
@ -118,18 +70,15 @@ cmServer::~cmServer()
|
||||||
return;
|
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) {
|
for (cmServerProtocol* p : this->SupportedProtocols) {
|
||||||
delete p;
|
delete p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete this->Connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cmServer::PopOne()
|
void cmServer::PopOne()
|
||||||
{
|
{
|
||||||
this->Writing = false;
|
|
||||||
if (this->Queue.empty()) {
|
if (this->Queue.empty()) {
|
||||||
return;
|
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)
|
void cmServer::RegisterProtocol(cmServerProtocol* protocol)
|
||||||
{
|
{
|
||||||
if (protocol->IsExperimental() && !this->SupportExperimental) {
|
if (protocol->IsExperimental() && !this->SupportExperimental) {
|
||||||
|
@ -245,6 +161,12 @@ void cmServer::PrintHello() const
|
||||||
this->WriteJsonObject(hello, nullptr);
|
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)
|
void cmServer::reportProgress(const char* msg, float progress, void* data)
|
||||||
{
|
{
|
||||||
const cmServerRequest* request = static_cast<const cmServerRequest*>(data);
|
const cmServerRequest* request = static_cast<const cmServerRequest*>(data);
|
||||||
|
@ -312,43 +234,16 @@ cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request)
|
||||||
return request.Reply(Json::objectValue);
|
return request.Reply(Json::objectValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cmServer::Serve()
|
bool cmServer::Serve(std::string* errorMessage)
|
||||||
{
|
{
|
||||||
if (this->SupportedProtocols.empty()) {
|
if (this->SupportedProtocols.empty()) {
|
||||||
|
*errorMessage =
|
||||||
|
"No protocol versions defined. Maybe you need --experimental?";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
assert(!this->Protocol);
|
assert(!this->Protocol);
|
||||||
|
|
||||||
this->Loop = uv_default_loop();
|
return Connection->ProcessEvents(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);
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cmServer::WriteJsonObject(const Json::Value& jsonValue,
|
void cmServer::WriteJsonObject(const Json::Value& jsonValue,
|
||||||
|
@ -385,10 +280,8 @@ void cmServer::WriteJsonObject(const Json::Value& jsonValue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->Writing = true;
|
Connection->WriteData(std::string("\n") + kSTART_MAGIC + std::string("\n") +
|
||||||
write_data(this->OutputStream, std::string("\n") + kSTART_MAGIC +
|
result + kEND_MAGIC + std::string("\n"));
|
||||||
std::string("\n") + result + kEND_MAGIC + std::string("\n"),
|
|
||||||
on_stdout_write);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmServerProtocol* cmServer::FindMatchingProtocol(
|
cmServerProtocol* cmServer::FindMatchingProtocol(
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
class cmServerConnection;
|
||||||
class cmServerProtocol;
|
class cmServerProtocol;
|
||||||
class cmServerRequest;
|
class cmServerRequest;
|
||||||
class cmServerResponse;
|
class cmServerResponse;
|
||||||
|
@ -33,18 +34,18 @@ class cmServer
|
||||||
public:
|
public:
|
||||||
class DebugInfo;
|
class DebugInfo;
|
||||||
|
|
||||||
cmServer(bool supportExperimental);
|
cmServer(cmServerConnection* conn, bool supportExperimental);
|
||||||
~cmServer();
|
~cmServer();
|
||||||
|
|
||||||
bool Serve();
|
bool Serve(std::string* errorMessage);
|
||||||
|
|
||||||
// for callbacks:
|
|
||||||
void PopOne();
|
|
||||||
void handleData(std::string const& data);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RegisterProtocol(cmServerProtocol* protocol);
|
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 reportProgress(const char* msg, float progress, void* data);
|
||||||
static void reportMessage(const char* msg, const char* title, bool& cancel,
|
static void reportMessage(const char* msg, const char* title, bool& cancel,
|
||||||
void* data);
|
void* data);
|
||||||
|
@ -69,6 +70,7 @@ private:
|
||||||
static cmServerProtocol* FindMatchingProtocol(
|
static cmServerProtocol* FindMatchingProtocol(
|
||||||
const std::vector<cmServerProtocol*>& protocols, int major, int minor);
|
const std::vector<cmServerProtocol*>& protocols, int major, int minor);
|
||||||
|
|
||||||
|
cmServerConnection* Connection = nullptr;
|
||||||
const bool SupportExperimental;
|
const bool SupportExperimental;
|
||||||
|
|
||||||
cmServerProtocol* Protocol = nullptr;
|
cmServerProtocol* Protocol = nullptr;
|
||||||
|
@ -94,4 +96,5 @@ private:
|
||||||
mutable bool Writing = false;
|
mutable bool Writing = false;
|
||||||
|
|
||||||
friend class cmServerRequest;
|
friend class cmServerRequest;
|
||||||
|
friend class cmServerConnection;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
#if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
|
#if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
|
||||||
#include "cmServer.h"
|
#include "cmServer.h"
|
||||||
|
#include "cmServerConnection.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
|
@ -913,32 +914,49 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
} else if (args[1] == "server") {
|
} else if (args[1] == "server") {
|
||||||
if (args.size() > 3) {
|
const std::string pipePrefix = "--pipe=";
|
||||||
cmSystemTools::Error("Too many arguments to start server mode");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
bool supportExperimental = false;
|
bool supportExperimental = false;
|
||||||
if (args.size() == 3) {
|
bool isDebug = false;
|
||||||
if (args[2] == "--experimental") {
|
std::string pipe;
|
||||||
|
|
||||||
|
for (size_t i = 2; i < args.size(); ++i) {
|
||||||
|
const std::string& a = args[i];
|
||||||
|
|
||||||
|
if (a == "--experimental") {
|
||||||
supportExperimental = true;
|
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 {
|
} else {
|
||||||
cmSystemTools::Error("Unknown argument for server mode");
|
cmSystemTools::Error("Unknown argument for server mode");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
|
#if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
|
||||||
cmServer server(supportExperimental);
|
cmServerConnection* conn;
|
||||||
if (server.Serve()) {
|
if (isDebug) {
|
||||||
|
conn = new cmServerStdIoConnection;
|
||||||
|
} else {
|
||||||
|
conn = new cmServerPipeConnection(pipe);
|
||||||
|
}
|
||||||
|
cmServer server(conn, supportExperimental);
|
||||||
|
std::string errorMessage;
|
||||||
|
if (server.Serve(&errorMessage)) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
cmSystemTools::Error(
|
cmSystemTools::Error(errorMessage.c_str());
|
||||||
"CMake server could not find any supported protocol. "
|
|
||||||
"Try with \"--experimental\" to enable "
|
|
||||||
"experimental support.");
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static_cast<void>(supportExperimental);
|
static_cast<void>(supportExperimental);
|
||||||
|
static_cast<void>(isDebug);
|
||||||
cmSystemTools::Error("CMake was not built with server mode enabled");
|
cmSystemTools::Error("CMake was not built with server mode enabled");
|
||||||
return 1;
|
return 1;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
2
|
|
@ -0,0 +1 @@
|
||||||
|
^CMake Error: No pipe given after --pipe=$
|
|
@ -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_echo_append ${CMAKE_COMMAND} -E echo_append)
|
||||||
run_cmake_command(E_rename-no-arg ${CMAKE_COMMAND} -E rename)
|
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-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_touch_nocreate-no-arg ${CMAKE_COMMAND} -E touch_nocreate)
|
||||||
|
|
||||||
run_cmake_command(E_time ${CMAKE_COMMAND} -E time ${CMAKE_COMMAND} -E echo "hello world")
|
run_cmake_command(E_time ${CMAKE_COMMAND} -E time ${CMAKE_COMMAND} -E echo "hello world")
|
||||||
|
|
|
@ -79,7 +79,7 @@ def writePayload(cmakeCommand, obj):
|
||||||
writeRawData(cmakeCommand, json.dumps(obj))
|
writeRawData(cmakeCommand, json.dumps(obj))
|
||||||
|
|
||||||
def initProc(cmakeCommand):
|
def initProc(cmakeCommand):
|
||||||
cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental"],
|
cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--debug"],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue