cmServer.cxx 10.4 KB
Newer Older
1
2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3
4
#include "cmServer.h"

5
#include "cmServerConnection.h"
6
#include "cmServerDictionary.h"
7
#include "cmServerProtocol.h"
8
#include "cmSystemTools.h"
9
#include "cm_jsoncpp_reader.h"
10
11
12
#include "cm_jsoncpp_writer.h"
#include "cmake.h"
#include "cmsys/FStream.hxx"
13

14
#include <algorithm>
15
16
17
#include <cassert>
#include <cstdint>
#include <utility>
Tobias Hunger's avatar
Tobias Hunger committed
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

class cmServer::DebugInfo
{
public:
  DebugInfo()
    : StartTime(uv_hrtime())
  {
  }

  bool PrintStatistics = false;

  std::string OutputFile;
  uint64_t StartTime;
};

33
34
35
cmServer::cmServer(cmServerConnection* conn, bool supportExperimental)
  : Connection(conn)
  , SupportExperimental(supportExperimental)
36
{
37
  this->Connection->SetServer(this);
38
39
  // Register supported protocols:
  this->RegisterProtocol(new cmServerProtocol1_0);
40
41
42
43
}

cmServer::~cmServer()
{
44
  if (!this->Protocol) { // Server was never fully started!
45
    return;
46
  }
47
48
49
50

  for (cmServerProtocol* p : this->SupportedProtocols) {
    delete p;
  }
51
52

  delete this->Connection;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
}

void cmServer::PopOne()
{
  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;
  }

Tobias Hunger's avatar
Tobias Hunger committed
71
72
73
74
75
76
77
78
  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();
  }

79
80
81
82
83
84
  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.");
Tobias Hunger's avatar
Tobias Hunger committed
85
    this->WriteResponse(response, nullptr);
86
87
88
    return;
  }

89
90
  cmSystemTools::SetMessageCallback(reportMessage,
                                    const_cast<cmServerRequest*>(&request));
91
92
93
  if (this->Protocol) {
    this->Protocol->CMakeInstance()->SetProgressCallback(
      reportProgress, const_cast<cmServerRequest*>(&request));
Tobias Hunger's avatar
Tobias Hunger committed
94
    this->WriteResponse(this->Protocol->Process(request), debug.get());
95
  } else {
Tobias Hunger's avatar
Tobias Hunger committed
96
    this->WriteResponse(this->SetProtocolVersion(request), debug.get());
97
  }
98
99
100
101
}

void cmServer::RegisterProtocol(cmServerProtocol* protocol)
{
102
103
104
  if (protocol->IsExperimental() && !this->SupportExperimental) {
    return;
  }
105
106
107
108
109
110
111
112
  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;
                         });
113
  if (it == this->SupportedProtocols.end()) {
114
    this->SupportedProtocols.push_back(protocol);
115
  }
116
117
118
119
120
121
122
}

void cmServer::PrintHello() const
{
  Json::Value hello = Json::objectValue;
  hello[kTYPE_KEY] = "hello";

123
  Json::Value& protocolVersions = hello[kSUPPORTED_PROTOCOL_VERSIONS] =
124
125
126
127
128
    Json::arrayValue;

  for (auto const& proto : this->SupportedProtocols) {
    auto version = proto->ProtocolVersion();
    Json::Value tmp = Json::objectValue;
129
130
    tmp[kMAJOR_KEY] = version.first;
    tmp[kMINOR_KEY] = version.second;
131
    if (proto->IsExperimental()) {
132
      tmp[kIS_EXPERIMENTAL_KEY] = true;
133
    }
134
135
136
    protocolVersions.append(tmp);
  }

Tobias Hunger's avatar
Tobias Hunger committed
137
  this->WriteJsonObject(hello, nullptr);
138
139
}

140
141
142
143
144
145
void cmServer::QueueRequest(const std::string& request)
{
  this->Queue.push_back(request);
  this->PopOne();
}

146
147
148
149
void cmServer::reportProgress(const char* msg, float progress, void* data)
{
  const cmServerRequest* request = static_cast<const cmServerRequest*>(data);
  assert(request);
150
  if (progress < 0.0f || progress > 1.0f) {
151
    request->ReportMessage(msg, "");
152
153
154
155
156
  } else {
    request->ReportProgress(0, static_cast<int>(progress * 1000), 1000, msg);
  }
}

157
158
159
160
161
162
163
164
165
166
167
168
169
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);
}

170
171
cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request)
{
172
  if (request.Type != kHANDSHAKE_TYPE) {
173
174
    return request.ReportError("Waiting for type \"" + kHANDSHAKE_TYPE +
                               "\".");
175
  }
176

177
  Json::Value requestedProtocolVersion = request.Data[kPROTOCOL_VERSION_KEY];
178
  if (requestedProtocolVersion.isNull()) {
179
180
181
    return request.ReportError("\"" + kPROTOCOL_VERSION_KEY +
                               "\" is required for \"" + kHANDSHAKE_TYPE +
                               "\".");
182
  }
183

184
  if (!requestedProtocolVersion.isObject()) {
185
186
    return request.ReportError("\"" + kPROTOCOL_VERSION_KEY +
                               "\" must be a JSON object.");
187
  }
188

189
  Json::Value majorValue = requestedProtocolVersion[kMAJOR_KEY];
190
  if (!majorValue.isInt()) {
191
192
    return request.ReportError("\"" + kMAJOR_KEY +
                               "\" must be set and an integer.");
193
  }
194

195
  Json::Value minorValue = requestedProtocolVersion[kMINOR_KEY];
196
  if (!minorValue.isNull() && !minorValue.isInt()) {
197
198
    return request.ReportError("\"" + kMINOR_KEY +
                               "\" must be unset or an integer.");
199
  }
200
201
202

  const int major = majorValue.asInt();
  const int minor = minorValue.isNull() ? -1 : minorValue.asInt();
203
  if (major < 0) {
204
    return request.ReportError("\"" + kMAJOR_KEY + "\" must be >= 0.");
205
206
  }
  if (!minorValue.isNull() && minor < 0) {
207
208
    return request.ReportError("\"" + kMINOR_KEY +
                               "\" must be >= 0 when set.");
209
  }
210
211
212
213
214
215
216
217

  this->Protocol =
    this->FindMatchingProtocol(this->SupportedProtocols, major, minor);
  if (!this->Protocol) {
    return request.ReportError("Protocol version not supported.");
  }

  std::string errorMessage;
218
  if (!this->Protocol->Activate(this, request, &errorMessage)) {
219
220
221
222
223
224
225
    this->Protocol = CM_NULLPTR;
    return request.ReportError("Failed to activate protocol version: " +
                               errorMessage);
  }
  return request.Reply(Json::objectValue);
}

226
bool cmServer::Serve(std::string* errorMessage)
227
{
228
  if (this->SupportedProtocols.empty()) {
229
230
    *errorMessage =
      "No protocol versions defined. Maybe you need --experimental?";
231
232
    return false;
  }
233
234
  assert(!this->Protocol);

235
  return Connection->ProcessEvents(errorMessage);
236
237
}

238
239
240
241
242
cmFileMonitor* cmServer::FileMonitor() const
{
  return Connection->FileMonitor();
}

Tobias Hunger's avatar
Tobias Hunger committed
243
244
void cmServer::WriteJsonObject(const Json::Value& jsonValue,
                               const DebugInfo* debug) const
245
246
247
{
  Json::FastWriter writer;

Tobias Hunger's avatar
Tobias Hunger committed
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
  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()) {
270
      cmsys::ofstream myfile(debug->OutputFile.c_str());
Tobias Hunger's avatar
Tobias Hunger committed
271
272
273
      myfile << result;
    }
  }
274

275
276
  Connection->WriteData(std::string("\n") + kSTART_MAGIC + std::string("\n") +
                        result + kEND_MAGIC + std::string("\n"));
277
278
279
280
281
282
283
284
}

cmServerProtocol* cmServer::FindMatchingProtocol(
  const std::vector<cmServerProtocol*>& protocols, int major, int minor)
{
  cmServerProtocol* bestMatch = nullptr;
  for (auto protocol : protocols) {
    auto version = protocol->ProtocolVersion();
285
    if (major != version.first) {
286
      continue;
287
288
    }
    if (minor == version.second) {
289
      return protocol;
290
291
    }
    if (!bestMatch || bestMatch->ProtocolVersion().second < version.second) {
292
      bestMatch = protocol;
293
    }
294
295
296
297
298
299
300
301
302
303
304
305
  }
  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;
306
307
  obj[kTYPE_KEY] = kPROGRESS_TYPE;
  obj[kREPLY_TO_KEY] = request.Type;
308
  obj[kCOOKIE_KEY] = request.Cookie;
309
310
311
312
  obj[kPROGRESS_MESSAGE_KEY] = message;
  obj[kPROGRESS_MINIMUM_KEY] = min;
  obj[kPROGRESS_MAXIMUM_KEY] = max;
  obj[kPROGRESS_CURRENT_KEY] = current;
313

Tobias Hunger's avatar
Tobias Hunger committed
314
  this->WriteJsonObject(obj, nullptr);
315
316
}

317
318
319
320
void cmServer::WriteMessage(const cmServerRequest& request,
                            const std::string& message,
                            const std::string& title) const
{
321
  if (message.empty()) {
322
    return;
323
  }
324
325
326
327
328

  Json::Value obj = Json::objectValue;
  obj[kTYPE_KEY] = kMESSAGE_TYPE;
  obj[kREPLY_TO_KEY] = request.Type;
  obj[kCOOKIE_KEY] = request.Cookie;
329
  obj[kMESSAGE_KEY] = message;
330
  if (!title.empty()) {
331
    obj[kTITLE_KEY] = title;
332
333
  }

Tobias Hunger's avatar
Tobias Hunger committed
334
  WriteJsonObject(obj, nullptr);
335
336
}

337
338
339
void cmServer::WriteParseError(const std::string& message) const
{
  Json::Value obj = Json::objectValue;
340
341
342
  obj[kTYPE_KEY] = kERROR_TYPE;
  obj[kERROR_MESSAGE_KEY] = message;
  obj[kREPLY_TO_KEY] = "";
343
344
  obj[kCOOKIE_KEY] = "";

Tobias Hunger's avatar
Tobias Hunger committed
345
  this->WriteJsonObject(obj, nullptr);
346
347
}

348
349
350
351
352
353
354
355
356
357
358
359
360
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);
}

Tobias Hunger's avatar
Tobias Hunger committed
361
362
void cmServer::WriteResponse(const cmServerResponse& response,
                             const DebugInfo* debug) const
363
364
365
366
367
{
  assert(response.IsComplete());

  Json::Value obj = response.Data();
  obj[kCOOKIE_KEY] = response.Cookie;
368
369
  obj[kTYPE_KEY] = response.IsError() ? kERROR_TYPE : kREPLY_TYPE;
  obj[kREPLY_TO_KEY] = response.Type;
370
  if (response.IsError()) {
371
    obj[kERROR_MESSAGE_KEY] = response.ErrorMessage();
372
373
  }

Tobias Hunger's avatar
Tobias Hunger committed
374
  this->WriteJsonObject(obj, debug);
375
}