Merge topic 'cmake-server-basic'
7263667c
Help: Add notes for topic 'cmake-server-basic'5adde4e7
cmake-server: Add documentationb63c1f6c
cmake-server: Add unit testd341d077
cmake-server: Implement ServerProtocol 1.0b13d3e0d
cmake-server: Bare-bones server implementationcd049f01
cmake-server: Report server mode availablitily in Capabilities
This commit is contained in:
commit
5c87b92b1b
|
@ -702,6 +702,18 @@ endif()
|
||||||
# setup some Testing support (a macro defined in this file)
|
# setup some Testing support (a macro defined in this file)
|
||||||
CMAKE_SETUP_TESTING()
|
CMAKE_SETUP_TESTING()
|
||||||
|
|
||||||
|
# Check whether to build server mode or not:
|
||||||
|
set(CMake_HAVE_SERVER_MODE 0)
|
||||||
|
if(NOT CMake_TEST_EXTERNAL_CMAKE AND NOT CMAKE_BOOTSTRAP AND CMAKE_USE_LIBUV)
|
||||||
|
list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_auto_type CMake_HAVE_CXX_AUTO_TYPE)
|
||||||
|
list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_range_for CMake_HAVE_CXX_RANGE_FOR)
|
||||||
|
if(CMake_HAVE_CXX_AUTO_TYPE AND CMake_HAVE_CXX_RANGE_FOR)
|
||||||
|
if(CMake_HAVE_CXX_MAKE_UNIQUE)
|
||||||
|
set(CMake_HAVE_SERVER_MODE 1)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT CMake_TEST_EXTERNAL_CMAKE)
|
if(NOT CMake_TEST_EXTERNAL_CMAKE)
|
||||||
if(NOT CMake_VERSION_IS_RELEASE)
|
if(NOT CMake_VERSION_IS_RELEASE)
|
||||||
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND
|
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND
|
||||||
|
|
|
@ -32,6 +32,7 @@ Reference Manuals
|
||||||
/manual/cmake-generator-expressions.7
|
/manual/cmake-generator-expressions.7
|
||||||
/manual/cmake-generators.7
|
/manual/cmake-generators.7
|
||||||
/manual/cmake-language.7
|
/manual/cmake-language.7
|
||||||
|
/manual/cmake-server.7
|
||||||
/manual/cmake-modules.7
|
/manual/cmake-modules.7
|
||||||
/manual/cmake-packages.7
|
/manual/cmake-packages.7
|
||||||
/manual/cmake-policies.7
|
/manual/cmake-policies.7
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
.. cmake-manual-description: CMake Server
|
||||||
|
|
||||||
|
cmake-server(7)
|
||||||
|
***************
|
||||||
|
|
||||||
|
.. only:: html
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
:manual:`cmake(1)` is capable of providing semantic information about
|
||||||
|
CMake code it executes to generate a buildsystem. If executed with
|
||||||
|
the ``-E server`` command line options, it starts in a long running mode
|
||||||
|
and allows a client to request the available information via a JSON protocol.
|
||||||
|
|
||||||
|
The protocol is designed to be useful to IDEs, refactoring tools, and
|
||||||
|
other tools which have a need to understand the buildsystem in entirety.
|
||||||
|
|
||||||
|
A single :manual:`cmake-buildsystem(7)` may describe buildsystem contents
|
||||||
|
and build properties which differ based on
|
||||||
|
:manual:`generation-time context <cmake-generator-expressions(7)>`
|
||||||
|
including:
|
||||||
|
|
||||||
|
* The Platform (eg, Windows, APPLE, Linux).
|
||||||
|
* The build configuration (eg, Debug, Release, Coverage).
|
||||||
|
* The Compiler (eg, MSVC, GCC, Clang) and compiler version.
|
||||||
|
* The language of the source files compiled.
|
||||||
|
* Available compile features (eg CXX variadic templates).
|
||||||
|
* CMake policies.
|
||||||
|
|
||||||
|
The protocol aims to provide information to tooling to satisfy several
|
||||||
|
needs:
|
||||||
|
|
||||||
|
#. Provide a complete and easily parsed source of all information relevant
|
||||||
|
to the tooling as it relates to the source code. There should be no need
|
||||||
|
for tooling to parse generated buildsystems to access include directories
|
||||||
|
or compile definitions for example.
|
||||||
|
#. Semantic information about the CMake buildsystem itself.
|
||||||
|
#. Provide a stable interface for reading the information in the CMake cache.
|
||||||
|
#. Information for determining when cmake needs to be re-run as a result of
|
||||||
|
file changes.
|
||||||
|
|
||||||
|
|
||||||
|
Operation
|
||||||
|
=========
|
||||||
|
|
||||||
|
Start :manual:`cmake(1)` in the server command mode, supplying the path to
|
||||||
|
the build directory to process::
|
||||||
|
|
||||||
|
cmake -E server
|
||||||
|
|
||||||
|
The server will start up and reply with an hello message on stdout::
|
||||||
|
|
||||||
|
[== CMake Server ==[
|
||||||
|
{"supportedProtocolVersions":[{"major":0,"minor":1}],"type":"hello"}
|
||||||
|
]== CMake Server ==]
|
||||||
|
|
||||||
|
Messages sent to and from the process are wrapped in magic strings::
|
||||||
|
|
||||||
|
[== CMake Server ==[
|
||||||
|
{
|
||||||
|
... some JSON message ...
|
||||||
|
}
|
||||||
|
]== CMake Server ==]
|
||||||
|
|
||||||
|
The server is now ready to accept further requests via stdin.
|
||||||
|
|
||||||
|
|
||||||
|
Protocol API
|
||||||
|
============
|
||||||
|
|
||||||
|
|
||||||
|
General Message Layout
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
All messages need to have a "type" value, which identifies the type of
|
||||||
|
message that is passed back or forth. E.g. the initial message sent by the
|
||||||
|
server is of type "hello". Messages without a type will generate an response
|
||||||
|
of type "error".
|
||||||
|
|
||||||
|
All requests sent to the server may contain a "cookie" value. This value
|
||||||
|
will he handed back unchanged in all responses triggered by the request.
|
||||||
|
|
||||||
|
All responses will contain a value "inReplyTo", which may be empty in
|
||||||
|
case of parse errors, but will contain the type of the request message
|
||||||
|
in all other cases.
|
||||||
|
|
||||||
|
|
||||||
|
Type "reply"
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This type is used by the server to reply to requests.
|
||||||
|
|
||||||
|
The message may -- depending on the type of the original request --
|
||||||
|
contain values.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
[== CMake Server ==[
|
||||||
|
{"cookie":"zimtstern","inReplyTo":"handshake","type":"reply"}
|
||||||
|
]== CMake Server ==]
|
||||||
|
|
||||||
|
|
||||||
|
Type "error"
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This type is used to return an error condition to the client. It will
|
||||||
|
contain an "errorMessage".
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
[== CMake Server ==[
|
||||||
|
{"cookie":"","errorMessage":"Protocol version not supported.","inReplyTo":"handshake","type":"error"}
|
||||||
|
]== CMake Server ==]
|
||||||
|
|
||||||
|
|
||||||
|
Type "progress"
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When the server is busy for a long time, it is polite to send back replies of
|
||||||
|
type "progress" to the client. These will contain a "progressMessage" with a
|
||||||
|
string describing the action currently taking place as well as
|
||||||
|
"progressMinimum", "progressMaximum" and "progressCurrent" with integer values
|
||||||
|
describing the range of progess.
|
||||||
|
|
||||||
|
Messages of type "progress" will be followed by more "progress" messages or with
|
||||||
|
a message of type "reply" or "error" that complete the request.
|
||||||
|
|
||||||
|
"progress" messages may not be emitted after the "reply" or "error" message for
|
||||||
|
the request that triggered the responses was delivered.
|
||||||
|
|
||||||
|
|
||||||
|
Specific Message Types
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
|
||||||
|
Type "hello"
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The initial message send by the cmake server on startup is of type "hello".
|
||||||
|
This is the only message ever sent by the server that is not of type "reply",
|
||||||
|
"progress" or "error".
|
||||||
|
|
||||||
|
It will contain "supportedProtocolVersions" with an array of server protocol
|
||||||
|
versions supported by the cmake server. These are JSON objects with "major" and
|
||||||
|
"minor" keys containing non-negative integer values.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
[== CMake Server ==[
|
||||||
|
{"supportedProtocolVersions":[{"major":0,"minor":1}],"type":"hello"}
|
||||||
|
]== CMake Server ==]
|
||||||
|
|
||||||
|
|
||||||
|
Type "handshake"
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The first request that the client may send to the server is of type "handshake".
|
||||||
|
|
||||||
|
This request needs to pass one of the "supportedProtocolVersions" of the "hello"
|
||||||
|
type response received earlier back to the server in the "protocolVersion" field.
|
||||||
|
|
||||||
|
Each protocol version may request additional attributes to be present.
|
||||||
|
|
||||||
|
Protocol version 1.0 requires the following attributes to be set:
|
||||||
|
|
||||||
|
* "sourceDirectory" with a path to the sources
|
||||||
|
* "buildDirectory" with a path to the build directory
|
||||||
|
* "generator" with the generator name
|
||||||
|
* "extraGenerator" (optional!) with the extra generator to be used.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
[== CMake Server ==[
|
||||||
|
{"cookie":"zimtstern","type":"handshake","protocolVersion":{"major":0},
|
||||||
|
"sourceDirectory":"/home/code/cmake", "buildDirectory":"/tmp/testbuild",
|
||||||
|
"generator":"Ninja"}
|
||||||
|
]== CMake Server ==]
|
||||||
|
|
||||||
|
which will result in a response type "reply"::
|
||||||
|
|
||||||
|
[== CMake Server ==[
|
||||||
|
{"cookie":"zimtstern","inReplyTo":"handshake","type":"reply"}
|
||||||
|
]== CMake Server ==]
|
||||||
|
|
||||||
|
indicating that the server is ready for action.
|
|
@ -273,6 +273,9 @@ Available commands are:
|
||||||
``rename <oldname> <newname>``
|
``rename <oldname> <newname>``
|
||||||
Rename a file or directory (on one volume).
|
Rename a file or directory (on one volume).
|
||||||
|
|
||||||
|
``server``
|
||||||
|
Launch :manual:`cmake-server(7)` mode.
|
||||||
|
|
||||||
``sleep <number>...``
|
``sleep <number>...``
|
||||||
Sleep for given number of seconds.
|
Sleep for given number of seconds.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
cmake-server-basic
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* A new :manual:`cmake-server(7)` mode was added to provide semantic
|
||||||
|
information about a CMake-generated buildsystem to clients through
|
||||||
|
a JSON protocol.
|
|
@ -786,6 +786,17 @@ add_executable(cmake cmakemain.cxx cmcmd.cxx cmcmd.h ${MANIFEST_FILE})
|
||||||
list(APPEND _tools cmake)
|
list(APPEND _tools cmake)
|
||||||
target_link_libraries(cmake CMakeLib)
|
target_link_libraries(cmake CMakeLib)
|
||||||
|
|
||||||
|
if(CMake_HAVE_SERVER_MODE)
|
||||||
|
add_library(CMakeServerLib
|
||||||
|
cmServer.cxx cmServer.h
|
||||||
|
cmServerProtocol.cxx cmServerProtocol.h
|
||||||
|
)
|
||||||
|
target_link_libraries(CMakeServerLib CMakeLib)
|
||||||
|
set_property(SOURCE cmcmd.cxx APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SERVER_MODE=1)
|
||||||
|
|
||||||
|
target_link_libraries(cmake CMakeServerLib)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Build CTest executable
|
# Build CTest executable
|
||||||
add_executable(ctest ctest.cxx ${MANIFEST_FILE})
|
add_executable(ctest ctest.cxx ${MANIFEST_FILE})
|
||||||
list(APPEND _tools ctest)
|
list(APPEND _tools ctest)
|
||||||
|
|
|
@ -0,0 +1,355 @@
|
||||||
|
/*============================================================================
|
||||||
|
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 "cmServer.h"
|
||||||
|
|
||||||
|
#include "cmServerProtocol.h"
|
||||||
|
#include "cmVersionMacros.h"
|
||||||
|
#include "cmake.h"
|
||||||
|
|
||||||
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
|
#include "cm_jsoncpp_reader.h"
|
||||||
|
#include "cm_jsoncpp_value.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char kTYPE_KEY[] = "type";
|
||||||
|
const char kCOOKIE_KEY[] = "cookie";
|
||||||
|
const char REPLY_TO_KEY[] = "inReplyTo";
|
||||||
|
const char ERROR_MESSAGE_KEY[] = "errorMessage";
|
||||||
|
|
||||||
|
const char ERROR_TYPE[] = "error";
|
||||||
|
const char REPLY_TYPE[] = "reply";
|
||||||
|
const char PROGRESS_TYPE[] = "progress";
|
||||||
|
|
||||||
|
const char START_MAGIC[] = "[== CMake Server ==[";
|
||||||
|
const char END_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmServer::cmServer()
|
||||||
|
{
|
||||||
|
// Register supported protocols:
|
||||||
|
this->RegisterProtocol(new cmServerProtocol1_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmServer::~cmServer()
|
||||||
|
{
|
||||||
|
if (!this->Protocol) // Daemon was never fully started!
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServer::PopOne()
|
||||||
|
{
|
||||||
|
this->Writing = false;
|
||||||
|
if (this->Queue.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Reader reader;
|
||||||
|
Json::Value value;
|
||||||
|
const std::string input = this->Queue.front();
|
||||||
|
this->Queue.erase(this->Queue.begin());
|
||||||
|
|
||||||
|
if (!reader.parse(input, value)) {
|
||||||
|
this->WriteParseError("Failed to parse JSON input.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmServerRequest request(this, value[kTYPE_KEY].asString(),
|
||||||
|
value[kCOOKIE_KEY].asString(), value);
|
||||||
|
|
||||||
|
if (request.Type == "") {
|
||||||
|
cmServerResponse response(request);
|
||||||
|
response.SetError("No type given in request.");
|
||||||
|
this->WriteResponse(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->WriteResponse(this->Protocol ? this->Protocol->Process(request)
|
||||||
|
: this->SetProtocolVersion(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == START_MAGIC) {
|
||||||
|
this->JsonData.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line == END_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)
|
||||||
|
{
|
||||||
|
auto version = protocol->ProtocolVersion();
|
||||||
|
assert(version.first >= 0);
|
||||||
|
assert(version.second >= 0);
|
||||||
|
auto it = std::find_if(this->SupportedProtocols.begin(),
|
||||||
|
this->SupportedProtocols.end(),
|
||||||
|
[version](cmServerProtocol* p) {
|
||||||
|
return p->ProtocolVersion() == version;
|
||||||
|
});
|
||||||
|
if (it == this->SupportedProtocols.end())
|
||||||
|
this->SupportedProtocols.push_back(protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServer::PrintHello() const
|
||||||
|
{
|
||||||
|
Json::Value hello = Json::objectValue;
|
||||||
|
hello[kTYPE_KEY] = "hello";
|
||||||
|
|
||||||
|
Json::Value& protocolVersions = hello["supportedProtocolVersions"] =
|
||||||
|
Json::arrayValue;
|
||||||
|
|
||||||
|
for (auto const& proto : this->SupportedProtocols) {
|
||||||
|
auto version = proto->ProtocolVersion();
|
||||||
|
Json::Value tmp = Json::objectValue;
|
||||||
|
tmp["major"] = version.first;
|
||||||
|
tmp["minor"] = version.second;
|
||||||
|
protocolVersions.append(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->WriteJsonObject(hello);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request)
|
||||||
|
{
|
||||||
|
if (request.Type != "handshake")
|
||||||
|
return request.ReportError("Waiting for type \"handshake\".");
|
||||||
|
|
||||||
|
Json::Value requestedProtocolVersion = request.Data["protocolVersion"];
|
||||||
|
if (requestedProtocolVersion.isNull())
|
||||||
|
return request.ReportError(
|
||||||
|
"\"protocolVersion\" is required for \"handshake\".");
|
||||||
|
|
||||||
|
if (!requestedProtocolVersion.isObject())
|
||||||
|
return request.ReportError("\"protocolVersion\" must be a JSON object.");
|
||||||
|
|
||||||
|
Json::Value majorValue = requestedProtocolVersion["major"];
|
||||||
|
if (!majorValue.isInt())
|
||||||
|
return request.ReportError("\"major\" must be set and an integer.");
|
||||||
|
|
||||||
|
Json::Value minorValue = requestedProtocolVersion["minor"];
|
||||||
|
if (!minorValue.isNull() && !minorValue.isInt())
|
||||||
|
return request.ReportError("\"minor\" must be unset or an integer.");
|
||||||
|
|
||||||
|
const int major = majorValue.asInt();
|
||||||
|
const int minor = minorValue.isNull() ? -1 : minorValue.asInt();
|
||||||
|
if (major < 0)
|
||||||
|
return request.ReportError("\"major\" must be >= 0.");
|
||||||
|
if (!minorValue.isNull() && minor < 0)
|
||||||
|
return request.ReportError("\"minor\" must be >= 0 when set.");
|
||||||
|
|
||||||
|
this->Protocol =
|
||||||
|
this->FindMatchingProtocol(this->SupportedProtocols, major, minor);
|
||||||
|
if (!this->Protocol) {
|
||||||
|
return request.ReportError("Protocol version not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string errorMessage;
|
||||||
|
if (!this->Protocol->Activate(request, &errorMessage)) {
|
||||||
|
this->Protocol = CM_NULLPTR;
|
||||||
|
return request.ReportError("Failed to activate protocol version: " +
|
||||||
|
errorMessage);
|
||||||
|
}
|
||||||
|
return request.Reply(Json::objectValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServer::Serve()
|
||||||
|
{
|
||||||
|
assert(!this->SupportedProtocols.empty());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServer::WriteJsonObject(const Json::Value& jsonValue) const
|
||||||
|
{
|
||||||
|
Json::FastWriter writer;
|
||||||
|
|
||||||
|
std::string result = std::string("\n") + std::string(START_MAGIC) +
|
||||||
|
std::string("\n") + writer.write(jsonValue) + std::string(END_MAGIC) +
|
||||||
|
std::string("\n");
|
||||||
|
|
||||||
|
this->Writing = true;
|
||||||
|
write_data(this->OutputStream, result, on_stdout_write);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmServerProtocol* cmServer::FindMatchingProtocol(
|
||||||
|
const std::vector<cmServerProtocol*>& protocols, int major, int minor)
|
||||||
|
{
|
||||||
|
cmServerProtocol* bestMatch = nullptr;
|
||||||
|
for (auto protocol : protocols) {
|
||||||
|
auto version = protocol->ProtocolVersion();
|
||||||
|
if (major != version.first)
|
||||||
|
continue;
|
||||||
|
if (minor == version.second)
|
||||||
|
return protocol;
|
||||||
|
if (!bestMatch || bestMatch->ProtocolVersion().second < version.second)
|
||||||
|
bestMatch = protocol;
|
||||||
|
}
|
||||||
|
return minor < 0 ? bestMatch : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServer::WriteProgress(const cmServerRequest& request, int min,
|
||||||
|
int current, int max,
|
||||||
|
const std::string& message) const
|
||||||
|
{
|
||||||
|
assert(min <= current && current <= max);
|
||||||
|
assert(message.length() != 0);
|
||||||
|
|
||||||
|
Json::Value obj = Json::objectValue;
|
||||||
|
obj[kTYPE_KEY] = PROGRESS_TYPE;
|
||||||
|
obj[REPLY_TO_KEY] = request.Type;
|
||||||
|
obj[kCOOKIE_KEY] = request.Cookie;
|
||||||
|
obj["progressMessage"] = message;
|
||||||
|
obj["progressMinimum"] = min;
|
||||||
|
obj["progressMaximum"] = max;
|
||||||
|
obj["progressCurrent"] = current;
|
||||||
|
|
||||||
|
this->WriteJsonObject(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServer::WriteParseError(const std::string& message) const
|
||||||
|
{
|
||||||
|
Json::Value obj = Json::objectValue;
|
||||||
|
obj[kTYPE_KEY] = ERROR_TYPE;
|
||||||
|
obj[ERROR_MESSAGE_KEY] = message;
|
||||||
|
obj[REPLY_TO_KEY] = "";
|
||||||
|
obj[kCOOKIE_KEY] = "";
|
||||||
|
|
||||||
|
this->WriteJsonObject(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServer::WriteResponse(const cmServerResponse& response) const
|
||||||
|
{
|
||||||
|
assert(response.IsComplete());
|
||||||
|
|
||||||
|
Json::Value obj = response.Data();
|
||||||
|
obj[kCOOKIE_KEY] = response.Cookie;
|
||||||
|
obj[kTYPE_KEY] = response.IsError() ? ERROR_TYPE : REPLY_TYPE;
|
||||||
|
obj[REPLY_TO_KEY] = response.Type;
|
||||||
|
if (response.IsError()) {
|
||||||
|
obj[ERROR_MESSAGE_KEY] = response.ErrorMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->WriteJsonObject(obj);
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*============================================================================
|
||||||
|
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 "cmListFileCache.h"
|
||||||
|
#include "cmState.h"
|
||||||
|
|
||||||
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
|
#include "cm_jsoncpp_value.h"
|
||||||
|
#include "cm_uv.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class cmServerProtocol;
|
||||||
|
class cmServerRequest;
|
||||||
|
class cmServerResponse;
|
||||||
|
|
||||||
|
class cmServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
cmServer();
|
||||||
|
~cmServer();
|
||||||
|
|
||||||
|
void Serve();
|
||||||
|
|
||||||
|
// for callbacks:
|
||||||
|
void PopOne();
|
||||||
|
void handleData(std::string const& data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RegisterProtocol(cmServerProtocol* protocol);
|
||||||
|
|
||||||
|
// Handle requests:
|
||||||
|
cmServerResponse SetProtocolVersion(const cmServerRequest& request);
|
||||||
|
|
||||||
|
void PrintHello() const;
|
||||||
|
|
||||||
|
// Write responses:
|
||||||
|
void WriteProgress(const cmServerRequest& request, int min, int current,
|
||||||
|
int max, const std::string& message) const;
|
||||||
|
void WriteResponse(const cmServerResponse& response) const;
|
||||||
|
void WriteParseError(const std::string& message) const;
|
||||||
|
|
||||||
|
void WriteJsonObject(Json::Value const& jsonValue) const;
|
||||||
|
|
||||||
|
static cmServerProtocol* FindMatchingProtocol(
|
||||||
|
const std::vector<cmServerProtocol*>& protocols, int major, int minor);
|
||||||
|
|
||||||
|
cmServerProtocol* Protocol = nullptr;
|
||||||
|
std::vector<cmServerProtocol*> SupportedProtocols;
|
||||||
|
std::vector<std::string> Queue;
|
||||||
|
|
||||||
|
std::string DataBuffer;
|
||||||
|
std::string JsonData;
|
||||||
|
|
||||||
|
uv_loop_t* Loop = nullptr;
|
||||||
|
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
uv_tty_t tty;
|
||||||
|
uv_pipe_t pipe;
|
||||||
|
} InOutUnion;
|
||||||
|
|
||||||
|
InOutUnion Input;
|
||||||
|
InOutUnion Output;
|
||||||
|
uv_stream_t* InputStream = nullptr;
|
||||||
|
uv_stream_t* OutputStream = nullptr;
|
||||||
|
|
||||||
|
mutable bool Writing = false;
|
||||||
|
|
||||||
|
friend class cmServerRequest;
|
||||||
|
};
|
|
@ -0,0 +1,264 @@
|
||||||
|
/*============================================================================
|
||||||
|
CMake - Cross Platform Makefile Generator
|
||||||
|
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 "cmServerProtocol.h"
|
||||||
|
|
||||||
|
#include "cmExternalMakefileProjectGenerator.h"
|
||||||
|
#include "cmServer.h"
|
||||||
|
#include "cmSystemTools.h"
|
||||||
|
#include "cmake.h"
|
||||||
|
|
||||||
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
|
#include "cm_jsoncpp_reader.h"
|
||||||
|
#include "cm_jsoncpp_value.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Vocabulary:
|
||||||
|
|
||||||
|
const std::string kBUILD_DIRECTORY_KEY = "buildDirectory";
|
||||||
|
const std::string kCOOKIE_KEY = "cookie";
|
||||||
|
const std::string kEXTRA_GENERATOR_KEY = "extraGenerator";
|
||||||
|
const std::string kGENERATOR_KEY = "generator";
|
||||||
|
const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory";
|
||||||
|
const std::string kTYPE_KEY = "type";
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
cmServerRequest::cmServerRequest(cmServer* server, const std::string& t,
|
||||||
|
const std::string& c, const Json::Value& d)
|
||||||
|
: Type(t)
|
||||||
|
, Cookie(c)
|
||||||
|
, Data(d)
|
||||||
|
, m_Server(server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServerRequest::ReportProgress(int min, int current, int max,
|
||||||
|
const std::string& message) const
|
||||||
|
{
|
||||||
|
this->m_Server->WriteProgress(*this, min, current, max, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmServerResponse cmServerRequest::Reply(const Json::Value& data) const
|
||||||
|
{
|
||||||
|
cmServerResponse response(*this);
|
||||||
|
response.SetData(data);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmServerResponse cmServerRequest::ReportError(const std::string& message) const
|
||||||
|
{
|
||||||
|
cmServerResponse response(*this);
|
||||||
|
response.SetError(message);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmServerResponse::cmServerResponse(const cmServerRequest& request)
|
||||||
|
: Type(request.Type)
|
||||||
|
, Cookie(request.Cookie)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServerResponse::SetData(const Json::Value& data)
|
||||||
|
{
|
||||||
|
assert(this->m_Payload == PAYLOAD_UNKNOWN);
|
||||||
|
if (!data[kCOOKIE_KEY].isNull() || !data[kTYPE_KEY].isNull()) {
|
||||||
|
this->SetError("Response contains cookie or type field.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->m_Payload = PAYLOAD_DATA;
|
||||||
|
this->m_Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmServerResponse::SetError(const std::string& message)
|
||||||
|
{
|
||||||
|
assert(this->m_Payload == PAYLOAD_UNKNOWN);
|
||||||
|
this->m_Payload = PAYLOAD_ERROR;
|
||||||
|
this->m_ErrorMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmServerResponse::IsComplete() const
|
||||||
|
{
|
||||||
|
return this->m_Payload != PAYLOAD_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmServerResponse::IsError() const
|
||||||
|
{
|
||||||
|
assert(this->m_Payload != PAYLOAD_UNKNOWN);
|
||||||
|
return this->m_Payload == PAYLOAD_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cmServerResponse::ErrorMessage() const
|
||||||
|
{
|
||||||
|
if (this->m_Payload == PAYLOAD_ERROR)
|
||||||
|
return this->m_ErrorMessage;
|
||||||
|
else
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value cmServerResponse::Data() const
|
||||||
|
{
|
||||||
|
assert(this->m_Payload != PAYLOAD_UNKNOWN);
|
||||||
|
return this->m_Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmServerProtocol::Activate(const cmServerRequest& request,
|
||||||
|
std::string* errorMessage)
|
||||||
|
{
|
||||||
|
this->m_CMakeInstance = std::make_unique<cmake>();
|
||||||
|
const bool result = this->DoActivate(request, errorMessage);
|
||||||
|
if (!result)
|
||||||
|
this->m_CMakeInstance = CM_NULLPTR;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmake* cmServerProtocol::CMakeInstance() const
|
||||||
|
{
|
||||||
|
return this->m_CMakeInstance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmServerProtocol::DoActivate(const cmServerRequest& /*request*/,
|
||||||
|
std::string* /*errorMessage*/)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> cmServerProtocol1_0::ProtocolVersion() const
|
||||||
|
{
|
||||||
|
return std::make_pair(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmServerProtocol1_0::DoActivate(const cmServerRequest& request,
|
||||||
|
std::string* errorMessage)
|
||||||
|
{
|
||||||
|
std::string sourceDirectory = request.Data[kSOURCE_DIRECTORY_KEY].asString();
|
||||||
|
const std::string buildDirectory =
|
||||||
|
request.Data[kBUILD_DIRECTORY_KEY].asString();
|
||||||
|
std::string generator = request.Data[kGENERATOR_KEY].asString();
|
||||||
|
std::string extraGenerator = request.Data[kEXTRA_GENERATOR_KEY].asString();
|
||||||
|
|
||||||
|
if (buildDirectory.empty()) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage =
|
||||||
|
std::string("\"") + kBUILD_DIRECTORY_KEY + "\" is missing.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cmake* cm = CMakeInstance();
|
||||||
|
if (cmSystemTools::PathExists(buildDirectory)) {
|
||||||
|
if (!cmSystemTools::FileIsDirectory(buildDirectory)) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage = std::string("\"") + kBUILD_DIRECTORY_KEY +
|
||||||
|
"\" exists but is not a directory.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string cachePath = cm->FindCacheFile(buildDirectory);
|
||||||
|
if (cm->LoadCache(cachePath)) {
|
||||||
|
cmState* state = cm->GetState();
|
||||||
|
|
||||||
|
// Check generator:
|
||||||
|
const std::string cachedGenerator =
|
||||||
|
std::string(state->GetCacheEntryValue("CMAKE_GENERATOR"));
|
||||||
|
if (cachedGenerator.empty() && generator.empty()) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage =
|
||||||
|
std::string("\"") + kGENERATOR_KEY + "\" is required but unset.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (generator.empty()) {
|
||||||
|
generator = cachedGenerator;
|
||||||
|
}
|
||||||
|
if (generator != cachedGenerator) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage = std::string("\"") + kGENERATOR_KEY +
|
||||||
|
"\" set but incompatible with configured generator.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check extra generator:
|
||||||
|
const std::string cachedExtraGenerator =
|
||||||
|
std::string(state->GetCacheEntryValue("CMAKE_EXTRA_GENERATOR"));
|
||||||
|
if (!cachedExtraGenerator.empty() && !extraGenerator.empty() &&
|
||||||
|
cachedExtraGenerator != extraGenerator) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage = std::string("\"") + kEXTRA_GENERATOR_KEY +
|
||||||
|
"\" is set but incompatible with configured extra generator.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (extraGenerator.empty()) {
|
||||||
|
extraGenerator = cachedExtraGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check sourcedir:
|
||||||
|
const std::string cachedSourceDirectory =
|
||||||
|
std::string(state->GetCacheEntryValue("CMAKE_HOME_DIRECTORY"));
|
||||||
|
if (!cachedSourceDirectory.empty() && !sourceDirectory.empty() &&
|
||||||
|
cachedSourceDirectory != sourceDirectory) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage = std::string("\"") + kSOURCE_DIRECTORY_KEY +
|
||||||
|
"\" is set but incompatible with configured source directory.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sourceDirectory.empty()) {
|
||||||
|
sourceDirectory = cachedSourceDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceDirectory.empty()) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage = std::string("\"") + kSOURCE_DIRECTORY_KEY +
|
||||||
|
"\" is unset but required.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!cmSystemTools::FileIsDirectory(sourceDirectory)) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage =
|
||||||
|
std::string("\"") + kSOURCE_DIRECTORY_KEY + "\" is not a directory.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (generator.empty()) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage =
|
||||||
|
std::string("\"") + kGENERATOR_KEY + "\" is unset but required.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string fullGeneratorName =
|
||||||
|
cmExternalMakefileProjectGenerator::CreateFullGeneratorName(
|
||||||
|
generator, extraGenerator);
|
||||||
|
|
||||||
|
cmGlobalGenerator* gg = cm->CreateGlobalGenerator(fullGeneratorName);
|
||||||
|
if (!gg) {
|
||||||
|
if (errorMessage)
|
||||||
|
*errorMessage =
|
||||||
|
std::string("Could not set up the requested combination of \"") +
|
||||||
|
kGENERATOR_KEY + "\" and \"" + kEXTRA_GENERATOR_KEY + "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cm->SetGlobalGenerator(gg);
|
||||||
|
cm->SetHomeDirectory(sourceDirectory);
|
||||||
|
cm->SetHomeOutputDirectory(buildDirectory);
|
||||||
|
|
||||||
|
this->m_State = STATE_ACTIVE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmServerResponse cmServerProtocol1_0::Process(
|
||||||
|
const cmServerRequest& request)
|
||||||
|
{
|
||||||
|
assert(this->m_State >= STATE_ACTIVE);
|
||||||
|
|
||||||
|
return request.ReportError("Unknown command!");
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*============================================================================
|
||||||
|
CMake - Cross Platform Makefile Generator
|
||||||
|
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 "cmListFileCache.h"
|
||||||
|
|
||||||
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
|
#include "cm_jsoncpp_writer.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class cmake;
|
||||||
|
class cmServer;
|
||||||
|
|
||||||
|
class cmServerRequest;
|
||||||
|
|
||||||
|
class cmServerResponse
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit cmServerResponse(const cmServerRequest& request);
|
||||||
|
|
||||||
|
void SetData(const Json::Value& data);
|
||||||
|
void SetError(const std::string& message);
|
||||||
|
|
||||||
|
bool IsComplete() const;
|
||||||
|
bool IsError() const;
|
||||||
|
std::string ErrorMessage() const;
|
||||||
|
Json::Value Data() const;
|
||||||
|
|
||||||
|
const std::string Type;
|
||||||
|
const std::string Cookie;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum PayLoad
|
||||||
|
{
|
||||||
|
PAYLOAD_UNKNOWN,
|
||||||
|
PAYLOAD_ERROR,
|
||||||
|
PAYLOAD_DATA
|
||||||
|
};
|
||||||
|
PayLoad m_Payload = PAYLOAD_UNKNOWN;
|
||||||
|
std::string m_ErrorMessage;
|
||||||
|
Json::Value m_Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class cmServerRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void ReportProgress(int min, int current, int max,
|
||||||
|
const std::string& message) const;
|
||||||
|
|
||||||
|
cmServerResponse Reply(const Json::Value& data) const;
|
||||||
|
cmServerResponse ReportError(const std::string& message) const;
|
||||||
|
|
||||||
|
const std::string Type;
|
||||||
|
const std::string Cookie;
|
||||||
|
const Json::Value Data;
|
||||||
|
|
||||||
|
private:
|
||||||
|
cmServerRequest(cmServer* server, const std::string& t, const std::string& c,
|
||||||
|
const Json::Value& d);
|
||||||
|
|
||||||
|
cmServer* m_Server;
|
||||||
|
|
||||||
|
friend class cmServer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class cmServerProtocol
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~cmServerProtocol() {}
|
||||||
|
|
||||||
|
virtual std::pair<int, int> ProtocolVersion() const = 0;
|
||||||
|
virtual const cmServerResponse Process(const cmServerRequest& request) = 0;
|
||||||
|
|
||||||
|
bool Activate(const cmServerRequest& request, std::string* errorMessage);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
cmake* CMakeInstance() const;
|
||||||
|
// Implement protocol specific activation tasks here. Called from Activate().
|
||||||
|
virtual bool DoActivate(const cmServerRequest& request,
|
||||||
|
std::string* errorMessage);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<cmake> m_CMakeInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
class cmServerProtocol1_0 : public cmServerProtocol
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::pair<int, int> ProtocolVersion() const override;
|
||||||
|
const cmServerResponse Process(const cmServerRequest& request) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool DoActivate(const cmServerRequest& request,
|
||||||
|
std::string* errorMessage) override;
|
||||||
|
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
STATE_INACTIVE,
|
||||||
|
STATE_ACTIVE
|
||||||
|
};
|
||||||
|
State m_State = STATE_INACTIVE;
|
||||||
|
};
|
|
@ -234,7 +234,7 @@ cmake::~cmake()
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
Json::Value cmake::ReportCapabilitiesJson() const
|
Json::Value cmake::ReportCapabilitiesJson(bool haveServerMode) const
|
||||||
{
|
{
|
||||||
Json::Value obj = Json::objectValue;
|
Json::Value obj = Json::objectValue;
|
||||||
// Version information:
|
// Version information:
|
||||||
|
@ -280,22 +280,18 @@ Json::Value cmake::ReportCapabilitiesJson() const
|
||||||
generators.append(i->second);
|
generators.append(i->second);
|
||||||
}
|
}
|
||||||
obj["generators"] = generators;
|
obj["generators"] = generators;
|
||||||
|
obj["serverMode"] = haveServerMode;
|
||||||
|
|
||||||
#if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
|
|
||||||
obj["serverMode"] = true;
|
|
||||||
#else
|
|
||||||
obj["serverMode"] = false;
|
|
||||||
#endif
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string cmake::ReportCapabilities() const
|
std::string cmake::ReportCapabilities(bool haveServerMode) const
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
Json::FastWriter writer;
|
Json::FastWriter writer;
|
||||||
result = writer.write(this->ReportCapabilitiesJson());
|
result = writer.write(this->ReportCapabilitiesJson(haveServerMode));
|
||||||
#else
|
#else
|
||||||
result = "Not supported";
|
result = "Not supported";
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -123,9 +123,9 @@ public:
|
||||||
~cmake();
|
~cmake();
|
||||||
|
|
||||||
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
Json::Value ReportCapabilitiesJson() const;
|
Json::Value ReportCapabilitiesJson(bool haveServerMode) const;
|
||||||
#endif
|
#endif
|
||||||
std::string ReportCapabilities() const;
|
std::string ReportCapabilities(bool haveServerMode) const;
|
||||||
|
|
||||||
static const char* GetCMakeFilesDirectory() { return "/CMakeFiles"; }
|
static const char* GetCMakeFilesDirectory() { return "/CMakeFiles"; }
|
||||||
static const char* GetCMakeFilesDirectoryPostSlash()
|
static const char* GetCMakeFilesDirectoryPostSlash()
|
||||||
|
|
|
@ -23,6 +23,10 @@
|
||||||
#include "cm_auto_ptr.hxx"
|
#include "cm_auto_ptr.hxx"
|
||||||
#include "cmake.h"
|
#include "cmake.h"
|
||||||
|
|
||||||
|
#if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
|
||||||
|
#include "cmServer.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
#include "cmDependsFortran.h" // For -E cmake_copy_f90_mod callback.
|
#include "cmDependsFortran.h" // For -E cmake_copy_f90_mod callback.
|
||||||
#endif
|
#endif
|
||||||
|
@ -91,6 +95,7 @@ void CMakeCommandUsage(const char* program)
|
||||||
<< " remove_directory dir - remove a directory and its contents\n"
|
<< " remove_directory dir - remove a directory and its contents\n"
|
||||||
<< " rename oldname newname - rename a file or directory "
|
<< " rename oldname newname - rename a file or directory "
|
||||||
"(on one volume)\n"
|
"(on one volume)\n"
|
||||||
|
<< " server - start cmake in server mode\n"
|
||||||
<< " sleep <number>... - sleep for given number of seconds\n"
|
<< " sleep <number>... - sleep for given number of seconds\n"
|
||||||
<< " tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]\n"
|
<< " tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]\n"
|
||||||
<< " - create or extract a tar or zip archive\n"
|
<< " - create or extract a tar or zip archive\n"
|
||||||
|
@ -527,7 +532,11 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
cmake cm;
|
cmake cm;
|
||||||
std::cout << cm.ReportCapabilities();
|
#if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
|
||||||
|
std::cout << cm.ReportCapabilities(true);
|
||||||
|
#else
|
||||||
|
std::cout << cm.ReportCapabilities(false);
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -903,6 +912,19 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string>& args)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
} else if (args[1] == "server") {
|
||||||
|
if (args.size() > 2) {
|
||||||
|
cmSystemTools::Error("Too many arguments to start server mode");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#if defined(HAVE_SERVER_MODE) && HAVE_SERVER_MODE
|
||||||
|
cmServer server;
|
||||||
|
server.Serve();
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
cmSystemTools::Error("CMake was not built with server mode enabled");
|
||||||
|
return 1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
#if defined(CMAKE_BUILD_WITH_CMAKE)
|
||||||
|
|
|
@ -2722,6 +2722,15 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
|
||||||
ADD_TEST_MACRO(CMakeCommands.target_compile_definitions target_compile_definitions)
|
ADD_TEST_MACRO(CMakeCommands.target_compile_definitions target_compile_definitions)
|
||||||
ADD_TEST_MACRO(CMakeCommands.target_compile_options target_compile_options)
|
ADD_TEST_MACRO(CMakeCommands.target_compile_options target_compile_options)
|
||||||
|
|
||||||
|
if(CMake_HAVE_SERVER_MODE)
|
||||||
|
# The cmake server-mode test requires python for a simple client.
|
||||||
|
find_package(PythonInterp QUIET)
|
||||||
|
if(PYTHON_EXECUTABLE)
|
||||||
|
set(Server_BUILD_OPTIONS -DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE})
|
||||||
|
ADD_TEST_MACRO(Server Server)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
configure_file(
|
configure_file(
|
||||||
"${CMake_SOURCE_DIR}/Tests/CTestTestCrash/test.cmake.in"
|
"${CMake_SOURCE_DIR}/Tests/CTestTestCrash/test.cmake.in"
|
||||||
"${CMake_BINARY_DIR}/Tests/CTestTestCrash/test.cmake"
|
"${CMake_BINARY_DIR}/Tests/CTestTestCrash/test.cmake"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
1
|
|
@ -0,0 +1 @@
|
||||||
|
^CMake Error: Too many arguments to start server mode$
|
|
@ -12,6 +12,7 @@ run_cmake_command(E_capabilities ${CMAKE_COMMAND} -E capabilities)
|
||||||
run_cmake_command(E_capabilities-arg ${CMAKE_COMMAND} -E capabilities --extra-arg)
|
run_cmake_command(E_capabilities-arg ${CMAKE_COMMAND} -E capabilities --extra-arg)
|
||||||
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_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")
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
cmake_minimum_required(VERSION 3.4)
|
||||||
|
project(Server CXX)
|
||||||
|
|
||||||
|
find_package(PythonInterp REQUIRED)
|
||||||
|
|
||||||
|
macro(do_test bsname file)
|
||||||
|
execute_process(COMMAND ${PYTHON_EXECUTABLE}
|
||||||
|
"${CMAKE_SOURCE_DIR}/server-test.py"
|
||||||
|
"${CMAKE_COMMAND}"
|
||||||
|
"${CMAKE_SOURCE_DIR}/${file}"
|
||||||
|
"${CMAKE_SOURCE_DIR}"
|
||||||
|
"${CMAKE_BINARY_DIR}"
|
||||||
|
RESULT_VARIABLE test_result
|
||||||
|
)
|
||||||
|
|
||||||
|
if (NOT test_result EQUAL 0)
|
||||||
|
message(SEND_ERROR "TEST FAILED")
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
do_test("test_handshake" "tc_handshake.json")
|
||||||
|
|
||||||
|
add_executable(Server empty.cpp)
|
|
@ -0,0 +1,126 @@
|
||||||
|
import sys, subprocess, json
|
||||||
|
|
||||||
|
termwidth = 150
|
||||||
|
|
||||||
|
print_communication = True
|
||||||
|
|
||||||
|
def ordered(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return sorted((k, ordered(v)) for k, v in obj.items())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return sorted(ordered(x) for x in obj)
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def col_print(title, array):
|
||||||
|
print
|
||||||
|
print
|
||||||
|
print(title)
|
||||||
|
|
||||||
|
indentwidth = 4
|
||||||
|
indent = " " * indentwidth
|
||||||
|
|
||||||
|
if not array:
|
||||||
|
print(indent + "<None>")
|
||||||
|
return
|
||||||
|
|
||||||
|
padwidth = 2
|
||||||
|
|
||||||
|
maxitemwidth = len(max(array, key=len))
|
||||||
|
|
||||||
|
numCols = max(1, int((termwidth - indentwidth + padwidth) / (maxitemwidth + padwidth)))
|
||||||
|
|
||||||
|
numRows = len(array) // numCols + 1
|
||||||
|
|
||||||
|
pad = " " * padwidth
|
||||||
|
|
||||||
|
for index in range(numRows):
|
||||||
|
print(indent + pad.join(item.ljust(maxitemwidth) for item in array[index::numRows]))
|
||||||
|
|
||||||
|
def waitForRawMessage(cmakeCommand):
|
||||||
|
stdoutdata = ""
|
||||||
|
payload = ""
|
||||||
|
while not cmakeCommand.poll():
|
||||||
|
stdoutdataLine = cmakeCommand.stdout.readline()
|
||||||
|
if stdoutdataLine:
|
||||||
|
stdoutdata += stdoutdataLine.decode('utf-8')
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
begin = stdoutdata.find("[== CMake Server ==[\n")
|
||||||
|
end = stdoutdata.find("]== CMake Server ==]")
|
||||||
|
|
||||||
|
if (begin != -1 and end != -1):
|
||||||
|
begin += len("[== CMake Server ==[\n")
|
||||||
|
payload = stdoutdata[begin:end]
|
||||||
|
if print_communication:
|
||||||
|
print("\nSERVER>", json.loads(payload), "\n")
|
||||||
|
return json.loads(payload)
|
||||||
|
|
||||||
|
def writeRawData(cmakeCommand, content):
|
||||||
|
writeRawData.counter += 1
|
||||||
|
payload = """
|
||||||
|
[== CMake Server ==[
|
||||||
|
%s
|
||||||
|
]== CMake Server ==]
|
||||||
|
""" % content
|
||||||
|
|
||||||
|
rn = ( writeRawData.counter % 2 ) == 0
|
||||||
|
|
||||||
|
if rn:
|
||||||
|
payload = payload.replace('\n', '\r\n')
|
||||||
|
|
||||||
|
if print_communication:
|
||||||
|
print("\nCLIENT>", content, "(Use \\r\\n:", rn, ")\n")
|
||||||
|
cmakeCommand.stdin.write(payload.encode('utf-8'))
|
||||||
|
cmakeCommand.stdin.flush()
|
||||||
|
writeRawData.counter = 0
|
||||||
|
|
||||||
|
def writePayload(cmakeCommand, obj):
|
||||||
|
writeRawData(cmakeCommand, json.dumps(obj))
|
||||||
|
|
||||||
|
def initProc(cmakeCommand):
|
||||||
|
cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server"],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
packet = waitForRawMessage(cmakeCommand)
|
||||||
|
if packet == None:
|
||||||
|
print("Not in server mode")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if packet['type'] != 'hello':
|
||||||
|
print("No hello message")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return cmakeCommand
|
||||||
|
|
||||||
|
def waitForMessage(cmakeCommand, expected):
|
||||||
|
data = ordered(expected)
|
||||||
|
packet = ordered(waitForRawMessage(cmakeCommand))
|
||||||
|
|
||||||
|
if packet != data:
|
||||||
|
sys.exit(-1)
|
||||||
|
return packet
|
||||||
|
|
||||||
|
def waitForReply(cmakeCommand, originalType, cookie):
|
||||||
|
packet = waitForRawMessage(cmakeCommand)
|
||||||
|
if packet['cookie'] != cookie or packet['type'] != 'reply' or packet['inReplyTo'] != originalType:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def waitForError(cmakeCommand, originalType, cookie, message):
|
||||||
|
packet = waitForRawMessage(cmakeCommand)
|
||||||
|
if packet['cookie'] != cookie or packet['type'] != 'error' or packet['inReplyTo'] != originalType or packet['errorMessage'] != message:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def waitForProgress(cmakeCommand, originalType, cookie, current, message):
|
||||||
|
packet = waitForRawMessage(cmakeCommand)
|
||||||
|
if packet['cookie'] != cookie or packet['type'] != 'progress' or packet['inReplyTo'] != originalType or packet['progressCurrent'] != current or packet['progressMessage'] != message:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def handshake(cmakeCommand, major, minor):
|
||||||
|
version = { 'major': major }
|
||||||
|
if minor >= 0:
|
||||||
|
version['minor'] = minor
|
||||||
|
|
||||||
|
writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version, 'cookie': 'TEST_HANDSHAKE' })
|
||||||
|
waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE')
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import sys, cmakelib, json
|
||||||
|
|
||||||
|
debug = True
|
||||||
|
|
||||||
|
cmakeCommand = sys.argv[1]
|
||||||
|
testFile = sys.argv[2]
|
||||||
|
sourceDir = sys.argv[3]
|
||||||
|
buildDir = sys.argv[4]
|
||||||
|
|
||||||
|
print("SourceDir: ", sourceDir, " -- BuildDir: ", buildDir)
|
||||||
|
|
||||||
|
proc = cmakelib.initProc(cmakeCommand)
|
||||||
|
|
||||||
|
with open(testFile) as f:
|
||||||
|
testText = f.read()
|
||||||
|
testText = testText.replace('%BUILDDIR%', buildDir)
|
||||||
|
testText = testText.replace('%SOURCEDIR%', sourceDir)
|
||||||
|
testData = json.loads(testText)
|
||||||
|
|
||||||
|
buildDir = sys.argv[3]
|
||||||
|
sourceDir = sys.argv[4]
|
||||||
|
|
||||||
|
for obj in testData:
|
||||||
|
if 'sendRaw' in obj:
|
||||||
|
data = obj['sendRaw']
|
||||||
|
if debug: print("Sending raw:", data)
|
||||||
|
cmakelib.writeRawData(proc, data)
|
||||||
|
elif 'send' in obj:
|
||||||
|
data = obj['send']
|
||||||
|
if debug: print("Sending:", json.dumps(data))
|
||||||
|
cmakelib.writePayload(proc, data)
|
||||||
|
elif 'recv' in obj:
|
||||||
|
data = obj['recv']
|
||||||
|
if debug: print("Waiting for:", json.dumps(data))
|
||||||
|
cmakelib.waitForMessage(proc, data)
|
||||||
|
elif 'reply' in obj:
|
||||||
|
data = obj['reply']
|
||||||
|
if debug: print("Waiting for reply:", json.dumps(data))
|
||||||
|
originalType = ""
|
||||||
|
cookie = ""
|
||||||
|
if 'cookie' in data: cookie = data['cookie']
|
||||||
|
if 'type' in data: originalType = data['type']
|
||||||
|
cmakelib.waitForReply(proc, originalType, cookie)
|
||||||
|
elif 'error' in obj:
|
||||||
|
data = obj['error']
|
||||||
|
if debug: print("Waiting for error:", json.dumps(data))
|
||||||
|
originalType = ""
|
||||||
|
cookie = ""
|
||||||
|
message = ""
|
||||||
|
if 'cookie' in data: cookie = data['cookie']
|
||||||
|
if 'type' in data: originalType = data['type']
|
||||||
|
if 'message' in data: message = data['message']
|
||||||
|
cmakelib.waitForError(proc, originalType, cookie, message)
|
||||||
|
elif 'progress' in obj:
|
||||||
|
data = obj['progress']
|
||||||
|
if debug: print("Waiting for progress:", json.dumps(data))
|
||||||
|
originalType = ''
|
||||||
|
cookie = ""
|
||||||
|
current = 0
|
||||||
|
message = ""
|
||||||
|
if 'cookie' in data: cookie = data['cookie']
|
||||||
|
if 'type' in data: originalType = data['type']
|
||||||
|
if 'current' in data: current = data['current']
|
||||||
|
if 'message' in data: message = data['message']
|
||||||
|
cmakelib.waitForProgress(proc, originalType, cookie, current, message)
|
||||||
|
elif 'handshake' in obj:
|
||||||
|
data = obj['handshake']
|
||||||
|
if debug: print("Doing handshake:", json.dumps(data))
|
||||||
|
major = -1
|
||||||
|
minor = -1
|
||||||
|
if 'major' in data: major = data['major']
|
||||||
|
if 'minor' in data: minor = data['minor']
|
||||||
|
cmakelib.handshake(proc, major, minor)
|
||||||
|
elif 'message' in obj:
|
||||||
|
print("MESSAGE:", obj["message"])
|
||||||
|
else:
|
||||||
|
print("Unknown command:", json.dumps(obj))
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
print("Completed")
|
||||||
|
|
||||||
|
sys.exit(0)
|
|
@ -0,0 +1,71 @@
|
||||||
|
[
|
||||||
|
{ "message": "Testing basic message handling:" },
|
||||||
|
|
||||||
|
{ "sendRaw": "Sometext"},
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"Failed to parse JSON input.","inReplyTo":"","type":"error"} },
|
||||||
|
|
||||||
|
{ "message": "Testing invalid json input"},
|
||||||
|
{ "send": { "test": "sometext" } },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"No type given in request.","inReplyTo":"","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"test": "sometext","cookie":"monster"} },
|
||||||
|
{ "recv": {"cookie":"monster","errorMessage":"No type given in request.","inReplyTo":"","type":"error"} },
|
||||||
|
|
||||||
|
{ "message": "Testing handshake" },
|
||||||
|
{ "send": {"type": "sometype","cookie":"monster2"} },
|
||||||
|
{ "recv": {"cookie":"monster2","errorMessage":"Waiting for type \"handshake\".","inReplyTo":"sometype","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake"} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" is required for \"handshake\".","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","foo":"bar"} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" is required for \"handshake\".","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","protocolVersion":"bar"} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" must be a JSON object.","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","protocolVersion":{}} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"\"major\" must be set and an integer.","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","protocolVersion":{"major":"foo"}} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"\"major\" must be set and an integer.","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","protocolVersion":{"major":1, "minor":"foo"}} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"\"minor\" must be unset or an integer.","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","protocolVersion":{"major":-1, "minor":-1}} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"\"major\" must be >= 0.","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","protocolVersion":{"major":10, "minor":-1}} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"\"minor\" must be >= 0 when set.","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","protocolVersion":{"major":10000}} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"Protocol version not supported.","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"type": "handshake","protocolVersion":{"major":1, "minor":10000}} },
|
||||||
|
{ "recv": {"cookie":"","errorMessage":"Protocol version not supported.","inReplyTo":"handshake","type":"error"} },
|
||||||
|
|
||||||
|
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1}} },
|
||||||
|
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"buildDirectory\" is missing."} },
|
||||||
|
|
||||||
|
{ "message": "Testing protocol version specific options (1.0):" },
|
||||||
|
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":"/tmp/src"} },
|
||||||
|
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"buildDirectory\" is missing."} },
|
||||||
|
|
||||||
|
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":"/tmp/src","buildDirectory":"/tmp/build"} },
|
||||||
|
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"sourceDirectory\" is not a directory."} },
|
||||||
|
|
||||||
|
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","extraGenerator":"CodeBlocks"} },
|
||||||
|
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"generator\" is unset but required."} },
|
||||||
|
|
||||||
|
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"XXXX","extraGenerator":"CodeBlocks"} },
|
||||||
|
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: Could not set up the requested combination of \"generator\" and \"extraGenerator\""} },
|
||||||
|
|
||||||
|
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"Ninja","extraGenerator":"XXXX"} },
|
||||||
|
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: Could not set up the requested combination of \"generator\" and \"extraGenerator\""} },
|
||||||
|
|
||||||
|
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"Ninja","extraGenerator":"CodeBlocks"} },
|
||||||
|
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"reply"} },
|
||||||
|
|
||||||
|
{ "message": "Everything ok." }
|
||||||
|
]
|
Loading…
Reference in New Issue