CMake/Source/cmServer.cxx

546 lines
14 KiB
C++
Raw Normal View History

Simplify CMake per-source license notices Per-source copyright/license notice headers that spell out copyright holder names and years are hard to maintain and often out-of-date or plain wrong. Precise contributor information is already maintained automatically by the version control tool. Ultimately it is the receiver of a file who is responsible for determining its licensing status, and per-source notices are merely a convenience. Therefore it is simpler and more accurate for each source to have a generic notice of the license name and references to more detailed information on copyright holders and full license terms. Our `Copyright.txt` file now contains a list of Contributors whose names appeared source-level copyright notices. It also references version control history for more precise information. Therefore we no longer need to spell out the list of Contributors in each source file notice. Replace CMake per-source copyright/license notice headers with a short description of the license and links to `Copyright.txt` and online information available from "https://cmake.org/licensing". The online URL also handles cases of modules being copied out of our source into other projects, so we can drop our notices about replacing links with full license text. Run the `Utilities/Scripts/filter-notices.bash` script to perform the majority of the replacements mechanically. Manually fix up shebang lines and trailing newlines in a few files. Manually update the notices in a few files that the script does not handle.
2016-09-27 19:01:08 +00:00
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmServer.h"
#include "cmConnection.h"
#include "cmFileMonitor.h"
#include "cmServerDictionary.h"
#include "cmServerProtocol.h"
#include "cmSystemTools.h"
#include "cm_jsoncpp_reader.h"
2017-05-05 21:21:39 +00:00
#include "cm_jsoncpp_writer.h"
#include "cmake.h"
#include "cmsys/FStream.hxx"
#include <algorithm>
2017-05-05 21:21:39 +00:00
#include <cassert>
#include <cstdint>
#include <memory>
2017-05-05 21:21:39 +00:00
#include <utility>
void on_signal(uv_signal_t* signal, int signum)
{
auto conn = reinterpret_cast<cmServerBase*>(signal->data);
conn->OnSignal(signum);
}
static void on_walk_to_shutdown(uv_handle_t* handle, void* arg)
{
(void)arg;
if (!uv_is_closing(handle)) {
uv_close(handle, &cmEventBasedConnection::on_close);
}
}
class cmServer::DebugInfo
{
public:
DebugInfo()
: StartTime(uv_hrtime())
{
}
bool PrintStatistics = false;
std::string OutputFile;
uint64_t StartTime;
};
cmServer::cmServer(cmConnection* conn, bool supportExperimental)
: cmServerBase(conn)
, SupportExperimental(supportExperimental)
{
// Register supported protocols:
this->RegisterProtocol(new cmServerProtocol1);
}
cmServer::~cmServer()
{
if (!this->Protocol) { // Server was never fully started!
return;
}
for (cmServerProtocol* p : this->SupportedProtocols) {
delete p;
}
}
void cmServer::ProcessRequest(cmConnection* connection,
const std::string& input)
{
Json::Reader reader;
Json::Value value;
if (!reader.parse(input, value)) {
this->WriteParseError(connection, "Failed to parse JSON input.");
return;
}
std::unique_ptr<DebugInfo> debug;
Json::Value debugValue = value["debug"];
if (!debugValue.isNull()) {
debug = std::make_unique<DebugInfo>();
debug->OutputFile = debugValue["dumpToFile"].asString();
debug->PrintStatistics = debugValue["showStats"].asBool();
}
const cmServerRequest request(this, connection, value[kTYPE_KEY].asString(),
value[kCOOKIE_KEY].asString(), value);
if (request.Type == "") {
cmServerResponse response(request);
response.SetError("No type given in request.");
this->WriteResponse(connection, response, nullptr);
return;
}
cmSystemTools::SetMessageCallback(reportMessage,
const_cast<cmServerRequest*>(&request));
if (this->Protocol) {
this->Protocol->CMakeInstance()->SetProgressCallback(
reportProgress, const_cast<cmServerRequest*>(&request));
this->WriteResponse(connection, this->Protocol->Process(request),
debug.get());
} else {
this->WriteResponse(connection, this->SetProtocolVersion(request),
debug.get());
}
}
void cmServer::RegisterProtocol(cmServerProtocol* protocol)
{
if (protocol->IsExperimental() && !this->SupportExperimental) {
return;
}
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(cmConnection* connection) const
{
Json::Value hello = Json::objectValue;
hello[kTYPE_KEY] = "hello";
Json::Value& protocolVersions = hello[kSUPPORTED_PROTOCOL_VERSIONS] =
Json::arrayValue;
for (auto const& proto : this->SupportedProtocols) {
auto version = proto->ProtocolVersion();
Json::Value tmp = Json::objectValue;
tmp[kMAJOR_KEY] = version.first;
tmp[kMINOR_KEY] = version.second;
if (proto->IsExperimental()) {
tmp[kIS_EXPERIMENTAL_KEY] = true;
}
protocolVersions.append(tmp);
}
this->WriteJsonObject(connection, hello, nullptr);
}
void cmServer::reportProgress(const char* msg, float progress, void* data)
{
const cmServerRequest* request = static_cast<const cmServerRequest*>(data);
assert(request);
if (progress < 0.0f || progress > 1.0f) {
request->ReportMessage(msg, "");
} else {
request->ReportProgress(0, static_cast<int>(progress * 1000), 1000, msg);
}
}
void cmServer::reportMessage(const char* msg, const char* title,
bool& /* cancel */, void* data)
{
const cmServerRequest* request = static_cast<const cmServerRequest*>(data);
assert(request);
assert(msg);
std::string titleString;
if (title) {
titleString = title;
}
request->ReportMessage(std::string(msg), titleString);
}
cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request)
{
if (request.Type != kHANDSHAKE_TYPE) {
return request.ReportError("Waiting for type \"" + kHANDSHAKE_TYPE +
"\".");
}
Json::Value requestedProtocolVersion = request.Data[kPROTOCOL_VERSION_KEY];
if (requestedProtocolVersion.isNull()) {
return request.ReportError("\"" + kPROTOCOL_VERSION_KEY +
"\" is required for \"" + kHANDSHAKE_TYPE +
"\".");
}
if (!requestedProtocolVersion.isObject()) {
return request.ReportError("\"" + kPROTOCOL_VERSION_KEY +
"\" must be a JSON object.");
}
Json::Value majorValue = requestedProtocolVersion[kMAJOR_KEY];
if (!majorValue.isInt()) {
return request.ReportError("\"" + kMAJOR_KEY +
"\" must be set and an integer.");
}
Json::Value minorValue = requestedProtocolVersion[kMINOR_KEY];
if (!minorValue.isNull() && !minorValue.isInt()) {
return request.ReportError("\"" + kMINOR_KEY +
"\" 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("\"" + kMAJOR_KEY + "\" must be >= 0.");
}
if (!minorValue.isNull() && minor < 0) {
return request.ReportError("\"" + kMINOR_KEY +
"\" 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(this, request, &errorMessage)) {
this->Protocol = CM_NULLPTR;
return request.ReportError("Failed to activate protocol version: " +
errorMessage);
}
return request.Reply(Json::objectValue);
}
bool cmServer::Serve(std::string* errorMessage)
{
if (this->SupportedProtocols.empty()) {
*errorMessage =
"No protocol versions defined. Maybe you need --experimental?";
return false;
}
assert(!this->Protocol);
return cmServerBase::Serve(errorMessage);
}
cmFileMonitor* cmServer::FileMonitor() const
{
return fileMonitor.get();
}
void cmServer::WriteJsonObject(const Json::Value& jsonValue,
const DebugInfo* debug) const
{
for (auto& connection : this->Connections) {
WriteJsonObject(connection.get(), jsonValue, debug);
}
}
void cmServer::WriteJsonObject(cmConnection* connection,
const Json::Value& jsonValue,
const DebugInfo* debug) const
{
Json::FastWriter writer;
auto beforeJson = uv_hrtime();
std::string result = writer.write(jsonValue);
if (debug) {
Json::Value copy = jsonValue;
if (debug->PrintStatistics) {
Json::Value stats = Json::objectValue;
auto endTime = uv_hrtime();
stats["jsonSerialization"] = double(endTime - beforeJson) / 1000000.0;
stats["totalTime"] = double(endTime - debug->StartTime) / 1000000.0;
stats["size"] = static_cast<int>(result.size());
if (!debug->OutputFile.empty()) {
stats["dumpFile"] = debug->OutputFile;
}
copy["zzzDebug"] = stats;
result = writer.write(copy); // Update result to include debug info
}
if (!debug->OutputFile.empty()) {
2017-05-05 21:21:39 +00:00
cmsys::ofstream myfile(debug->OutputFile.c_str());
myfile << result;
}
}
connection->WriteData(std::string("\n") + kSTART_MAGIC + std::string("\n") +
result + kEND_MAGIC + std::string("\n"));
}
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] = kPROGRESS_TYPE;
obj[kREPLY_TO_KEY] = request.Type;
obj[kCOOKIE_KEY] = request.Cookie;
obj[kPROGRESS_MESSAGE_KEY] = message;
obj[kPROGRESS_MINIMUM_KEY] = min;
obj[kPROGRESS_MAXIMUM_KEY] = max;
obj[kPROGRESS_CURRENT_KEY] = current;
this->WriteJsonObject(request.Connection, obj, nullptr);
}
void cmServer::WriteMessage(const cmServerRequest& request,
const std::string& message,
const std::string& title) const
{
if (message.empty()) {
return;
}
Json::Value obj = Json::objectValue;
obj[kTYPE_KEY] = kMESSAGE_TYPE;
obj[kREPLY_TO_KEY] = request.Type;
obj[kCOOKIE_KEY] = request.Cookie;
obj[kMESSAGE_KEY] = message;
if (!title.empty()) {
obj[kTITLE_KEY] = title;
}
WriteJsonObject(request.Connection, obj, nullptr);
}
void cmServer::WriteParseError(cmConnection* connection,
const std::string& message) const
{
Json::Value obj = Json::objectValue;
obj[kTYPE_KEY] = kERROR_TYPE;
obj[kERROR_MESSAGE_KEY] = message;
obj[kREPLY_TO_KEY] = "";
obj[kCOOKIE_KEY] = "";
this->WriteJsonObject(connection, obj, nullptr);
}
void cmServer::WriteSignal(const std::string& name,
const Json::Value& data) const
{
assert(data.isObject());
Json::Value obj = data;
obj[kTYPE_KEY] = kSIGNAL_TYPE;
obj[kREPLY_TO_KEY] = "";
obj[kCOOKIE_KEY] = "";
obj[kNAME_KEY] = name;
WriteJsonObject(obj, nullptr);
}
void cmServer::WriteResponse(cmConnection* connection,
const cmServerResponse& response,
const DebugInfo* debug) const
{
assert(response.IsComplete());
Json::Value obj = response.Data();
obj[kCOOKIE_KEY] = response.Cookie;
obj[kTYPE_KEY] = response.IsError() ? kERROR_TYPE : kREPLY_TYPE;
obj[kREPLY_TO_KEY] = response.Type;
if (response.IsError()) {
obj[kERROR_MESSAGE_KEY] = response.ErrorMessage();
}
this->WriteJsonObject(connection, obj, debug);
}
void cmServer::OnConnected(cmConnection* connection)
{
PrintHello(connection);
}
void cmServer::OnServeStart()
{
cmServerBase::OnServeStart();
fileMonitor = std::make_shared<cmFileMonitor>(GetLoop());
}
void cmServer::StartShutDown()
{
if (fileMonitor) {
fileMonitor->StopMonitoring();
fileMonitor.reset();
}
cmServerBase::StartShutDown();
}
static void __start_thread(void* arg)
{
auto server = reinterpret_cast<cmServerBase*>(arg);
std::string error;
server->Serve(&error);
}
bool cmServerBase::StartServeThread()
{
ServeThreadRunning = true;
uv_thread_create(&ServeThread, __start_thread, this);
return true;
}
bool cmServerBase::Serve(std::string* errorMessage)
{
errorMessage->clear();
uv_signal_init(&Loop, &this->SIGINTHandler);
uv_signal_init(&Loop, &this->SIGHUPHandler);
this->SIGINTHandler.data = this;
this->SIGHUPHandler.data = this;
uv_signal_start(&this->SIGINTHandler, &on_signal, SIGINT);
uv_signal_start(&this->SIGHUPHandler, &on_signal, SIGHUP);
OnServeStart();
for (auto& connection : Connections) {
if (!connection->OnServeStart(errorMessage)) {
return false;
}
}
if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
*errorMessage = "Internal Error: Event loop stopped in unclean state.";
StartShutDown();
return false;
}
ServeThreadRunning = false;
return true;
}
void cmServerBase::OnConnected(cmConnection*)
{
}
void cmServerBase::OnDisconnect()
{
}
void cmServerBase::OnServeStart()
{
uv_signal_start(&this->SIGINTHandler, &on_signal, SIGINT);
uv_signal_start(&this->SIGHUPHandler, &on_signal, SIGHUP);
}
void cmServerBase::StartShutDown()
{
if (!uv_is_closing((const uv_handle_t*)&this->SIGINTHandler)) {
uv_signal_stop(&this->SIGINTHandler);
}
if (!uv_is_closing((const uv_handle_t*)&this->SIGHUPHandler)) {
uv_signal_stop(&this->SIGHUPHandler);
}
for (auto& connection : Connections) {
connection->OnConnectionShuttingDown();
}
Connections.clear();
uv_stop(&Loop);
uv_walk(&Loop, on_walk_to_shutdown, CM_NULLPTR);
uv_run(&Loop, UV_RUN_DEFAULT);
}
bool cmServerBase::OnSignal(int signum)
{
(void)signum;
StartShutDown();
return true;
}
cmServerBase::cmServerBase(cmConnection* connection)
{
uv_loop_init(&Loop);
uv_signal_init(&Loop, &this->SIGINTHandler);
uv_signal_init(&Loop, &this->SIGHUPHandler);
this->SIGINTHandler.data = this;
this->SIGHUPHandler.data = this;
AddNewConnection(connection);
}
cmServerBase::~cmServerBase()
{
if (ServeThreadRunning) {
StartShutDown();
uv_thread_join(&ServeThread);
}
uv_loop_close(&Loop);
}
void cmServerBase::AddNewConnection(cmConnection* ownedConnection)
{
Connections.emplace_back(ownedConnection);
ownedConnection->SetServer(this);
}
uv_loop_t* cmServerBase::GetLoop()
{
return &Loop;
}
void cmServerBase::OnDisconnect(cmConnection* pConnection)
{
auto pred = [pConnection](const std::unique_ptr<cmConnection>& m) {
return m.get() == pConnection;
};
Connections.erase(
std::remove_if(Connections.begin(), Connections.end(), pred),
Connections.end());
if (Connections.empty()) {
StartShutDown();
}
}