Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmFileAPI.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmFileAPI.h"
4
5
#include <algorithm>
6
#include <cassert>
7
#include <chrono>
8
#include <ctime>
9
#include <iomanip>
10
#include <iterator>
11
#include <sstream>
12
#include <utility>
13
14
#include <cm/optional>
15
#include <cmext/string_view>
16
17
#include "cmsys/Directory.hxx"
18
#include "cmsys/FStream.hxx"
19
20
#include "cmCryptoHash.h"
21
#include "cmFileAPICMakeFiles.h"
22
#include "cmFileAPICache.h"
23
#include "cmFileAPICodemodel.h"
24
#include "cmFileAPIConfigureLog.h"
25
#include "cmFileAPIToolchains.h"
26
#include "cmGlobalGenerator.h"
27
#include "cmStringAlgorithms.h"
28
#include "cmSystemTools.h"
29
#include "cmTimestamp.h"
30
#include "cmake.h"
31
32
#if defined(__clang__) && defined(__has_warning)
33
#  if __has_warning("-Wrange-loop-analysis")
34
#    if defined(__apple_build_version__)
35
#      if __apple_build_version__ < 13000000
36
#        define CM_CLANG_SUPPRESS_WARN_RANGE_LOOP_ANALYSIS
37
#      endif
38
#    else
39
#      if __clang_major__ < 11
40
#        define CM_CLANG_SUPPRESS_WARN_RANGE_LOOP_ANALYSIS
41
#      endif
42
#    endif
43
#  endif
44
#endif
45
46
cmFileAPI::cmFileAPI(cmake* cm)
47
0
  : CMakeInstance(cm)
48
0
{
49
0
  this->APIv1 =
50
0
    cmStrCat(this->CMakeInstance->GetHomeOutputDirectory(), "/.cmake/api/v1");
51
52
0
  if (cm::optional<std::string> cmakeConfigDir =
53
0
        cmSystemTools::GetCMakeConfigDirectory()) {
54
0
    this->UserAPIv1 = cmStrCat(std::move(*cmakeConfigDir), "/api/v1"_s);
55
0
  }
56
57
0
  Json::CharReaderBuilder rbuilder;
58
0
  rbuilder["collectComments"] = false;
59
0
  rbuilder["failIfExtra"] = true;
60
0
  rbuilder["rejectDupKeys"] = false;
61
0
  rbuilder["strictRoot"] = true;
62
0
  this->JsonReader =
63
0
    std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
64
65
0
  Json::StreamWriterBuilder wbuilder;
66
0
  wbuilder["indentation"] = "\t";
67
0
  this->JsonWriter =
68
0
    std::unique_ptr<Json::StreamWriter>(wbuilder.newStreamWriter());
69
0
}
70
71
void cmFileAPI::ReadQueries()
72
0
{
73
0
  std::string const query_dir = cmStrCat(this->APIv1, "/query");
74
0
  std::string const user_query_dir = cmStrCat(this->UserAPIv1, "/query");
75
0
  this->QueryExists =
76
0
    this->QueryExists || cmSystemTools::FileIsDirectory(query_dir);
77
0
  if (!this->UserAPIv1.empty()) {
78
0
    this->QueryExists =
79
0
      this->QueryExists || cmSystemTools::FileIsDirectory(user_query_dir);
80
0
  }
81
0
  if (!this->QueryExists) {
82
0
    return;
83
0
  }
84
85
  // Load queries at the top level.
86
0
  std::vector<std::string> queries = cmFileAPI::LoadDir(query_dir);
87
0
  if (!this->UserAPIv1.empty()) {
88
0
    std::vector<std::string> user_queries = cmFileAPI::LoadDir(user_query_dir);
89
0
    std::move(user_queries.begin(), user_queries.end(),
90
0
              std::back_inserter(queries));
91
0
  }
92
93
  // Read the queries and save for later.
94
0
  for (std::string& query : queries) {
95
0
    if (cmHasLiteralPrefix(query, "client-")) {
96
0
      this->ReadClient(query);
97
0
    } else if (!cmFileAPI::ReadQuery(query, this->TopQuery.Known)) {
98
0
      this->TopQuery.Unknown.push_back(std::move(query));
99
0
    }
100
0
  }
101
0
}
102
103
std::vector<unsigned int> cmFileAPI::GetConfigureLogVersions()
104
0
{
105
0
  std::vector<unsigned int> versions;
106
0
  auto getConfigureLogVersions = [&versions](Query const& q) {
107
#ifdef CM_CLANG_SUPPRESS_WARN_RANGE_LOOP_ANALYSIS
108
#  pragma clang diagnostic push
109
#  pragma clang diagnostic ignored "-Wrange-loop-analysis"
110
#endif
111
0
    for (Object const o : q.Known) {
112
#ifdef CM_CLANG_SUPPRESS_WARN_RANGE_LOOP_ANALYSIS
113
#  pragma clang diagnostic pop
114
#endif
115
0
      if (o.Kind == ObjectKind::ConfigureLog) {
116
0
        versions.emplace_back(o.Version);
117
0
      }
118
0
    }
119
0
  };
120
0
  getConfigureLogVersions(this->TopQuery);
121
0
  for (auto const& client : this->ClientQueries) {
122
0
    getConfigureLogVersions(client.second.DirQuery);
123
0
  }
124
0
  std::sort(versions.begin(), versions.end());
125
0
  versions.erase(std::unique(versions.begin(), versions.end()),
126
0
                 versions.end());
127
0
  return versions;
128
0
}
129
130
void cmFileAPI::WriteReplies(IndexFor indexFor)
131
0
{
132
0
  bool const success = indexFor == IndexFor::Success;
133
0
  this->ReplyIndexFor = indexFor;
134
135
0
  if (this->QueryExists) {
136
0
    cmSystemTools::MakeDirectory(this->APIv1 + "/reply");
137
0
    this->WriteJsonFile(this->BuildReplyIndex(), success ? "index" : "error",
138
0
                        ComputeSuffixTime);
139
0
  }
140
141
0
  if (success) {
142
0
    this->RemoveOldReplyFiles();
143
0
  }
144
0
}
145
146
std::vector<std::string> cmFileAPI::LoadDir(std::string const& dir)
147
0
{
148
0
  std::vector<std::string> files;
149
0
  cmsys::Directory d;
150
0
  d.Load(dir);
151
0
  for (unsigned int i = 0; i < d.GetNumberOfFiles(); ++i) {
152
0
    std::string f = d.GetFile(i);
153
0
    if (f != "." && f != "..") {
154
0
      files.push_back(std::move(f));
155
0
    }
156
0
  }
157
0
  std::sort(files.begin(), files.end());
158
0
  return files;
159
0
}
160
161
void cmFileAPI::RemoveOldReplyFiles()
162
0
{
163
0
  std::string const reply_dir = this->APIv1 + "/reply";
164
0
  std::vector<std::string> files = this->LoadDir(reply_dir);
165
0
  for (std::string const& f : files) {
166
0
    if (this->ReplyFiles.find(f) == this->ReplyFiles.end()) {
167
0
      std::string file = cmStrCat(reply_dir, '/', f);
168
0
      cmSystemTools::RemoveFile(file);
169
0
    }
170
0
  }
171
0
}
172
173
bool cmFileAPI::ReadJsonFile(std::string const& file, Json::Value& value,
174
                             std::string& error)
175
0
{
176
0
  std::vector<char> content;
177
178
0
  cmsys::ifstream fin;
179
0
  if (!cmSystemTools::FileIsDirectory(file)) {
180
0
    fin.open(file.c_str(), std::ios::binary);
181
0
  }
182
0
  auto finEnd = fin.rdbuf()->pubseekoff(0, std::ios::end);
183
0
  if (finEnd > 0) {
184
0
    size_t finSize = finEnd;
185
0
    try {
186
      // Allocate a buffer to read the whole file.
187
0
      content.resize(finSize);
188
189
      // Now read the file from the beginning.
190
0
      fin.seekg(0, std::ios::beg);
191
0
      fin.read(content.data(), finSize);
192
0
    } catch (...) {
193
0
      fin.setstate(std::ios::failbit);
194
0
    }
195
0
  }
196
0
  fin.close();
197
0
  if (!fin) {
198
0
    value = Json::Value();
199
0
    error = "failed to read from file";
200
0
    return false;
201
0
  }
202
203
  // Parse our buffer as json.
204
0
  if (!this->JsonReader->parse(content.data(), content.data() + content.size(),
205
0
                               &value, &error)) {
206
0
    value = Json::Value();
207
0
    return false;
208
0
  }
209
210
0
  return true;
211
0
}
212
213
std::string cmFileAPI::WriteJsonFile(
214
  Json::Value const& value, std::string const& prefix,
215
  std::string (*computeSuffix)(std::string const&))
216
0
{
217
0
  std::string fileName;
218
219
  // Write the json file with a temporary name.
220
0
  std::string const& tmpFile = this->APIv1 + "/tmp.json";
221
0
  cmsys::ofstream ftmp(tmpFile.c_str());
222
0
  this->JsonWriter->write(value, &ftmp);
223
0
  ftmp << "\n";
224
0
  ftmp.close();
225
0
  if (!ftmp) {
226
0
    cmSystemTools::RemoveFile(tmpFile);
227
0
    return fileName;
228
0
  }
229
230
  // Compute the final name for the file.
231
0
  std::string suffix = computeSuffix(tmpFile);
232
0
  std::string suffixWithExtension = cmStrCat('-', suffix, ".json");
233
0
  fileName = cmStrCat(prefix, suffixWithExtension);
234
235
  // Truncate the file name length
236
  // eCryptFS has a maximal file name length recommendation of 140
237
0
  size_t const maxFileNameLength = 140;
238
0
  size_t const fileNameLength = fileName.size();
239
0
  if (fileNameLength > maxFileNameLength) {
240
0
    size_t const newHashLength = 20;
241
0
    size_t const newSuffixLength =
242
0
      suffixWithExtension.size() - suffix.size() + newHashLength;
243
0
    size_t const overLength =
244
0
      fileNameLength - maxFileNameLength + newSuffixLength;
245
0
    size_t const startPos = fileNameLength - overLength;
246
0
    std::string const toBeRemoved = fileName.substr(startPos, overLength);
247
0
    suffix = cmCryptoHash(cmCryptoHash::AlgoSHA256)
248
0
               .HashString(toBeRemoved)
249
0
               .substr(0, newHashLength);
250
0
    suffixWithExtension = cmStrCat('-', suffix, ".json");
251
0
    fileName.replace(startPos, overLength, suffixWithExtension);
252
0
  }
253
254
  // Create the destination.
255
0
  std::string file = this->APIv1 + "/reply";
256
0
  cmSystemTools::MakeDirectory(file);
257
0
  file += "/";
258
0
  file += fileName;
259
260
  // If the final name already exists then assume it has proper content.
261
  // Otherwise, atomically place the reply file at its final name
262
0
  if (cmSystemTools::FileExists(file, true) ||
263
0
      !cmSystemTools::RenameFile(tmpFile, file)) {
264
0
    cmSystemTools::RemoveFile(tmpFile);
265
0
  }
266
267
  // Record this among files we have just written.
268
0
  this->ReplyFiles.insert(fileName);
269
270
0
  return fileName;
271
0
}
272
273
Json::Value cmFileAPI::MaybeJsonFile(Json::Value in, std::string const& prefix)
274
0
{
275
0
  Json::Value out;
276
0
  if (in.isObject() || in.isArray()) {
277
0
    out = Json::objectValue;
278
0
    out["jsonFile"] = this->WriteJsonFile(in, prefix);
279
0
  } else {
280
0
    out = std::move(in);
281
0
  }
282
0
  return out;
283
0
}
284
285
std::string cmFileAPI::ComputeSuffixHash(std::string const& file)
286
0
{
287
0
  cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_256);
288
0
  std::string hash = hasher.HashFile(file);
289
0
  hash.resize(20, '0');
290
0
  return hash;
291
0
}
292
293
std::string cmFileAPI::ComputeSuffixTime(std::string const&)
294
0
{
295
0
  std::chrono::milliseconds ms =
296
0
    std::chrono::duration_cast<std::chrono::milliseconds>(
297
0
      std::chrono::system_clock::now().time_since_epoch());
298
0
  std::chrono::seconds s =
299
0
    std::chrono::duration_cast<std::chrono::seconds>(ms);
300
301
0
  std::time_t ts = s.count();
302
0
  std::size_t tms = ms.count() % 1000;
303
304
0
  cmTimestamp cmts;
305
0
  std::ostringstream ss;
306
0
  ss << cmts.CreateTimestampFromTimeT(ts, "%Y-%m-%dT%H-%M-%S", true) << '-'
307
0
     << std::setfill('0') << std::setw(4) << tms;
308
0
  return ss.str();
309
0
}
310
311
bool cmFileAPI::ReadQuery(std::string const& query,
312
                          std::vector<Object>& objects)
313
0
{
314
  // Parse the "<kind>-" syntax.
315
0
  std::string::size_type sep_pos = query.find('-');
316
0
  if (sep_pos == std::string::npos) {
317
0
    return false;
318
0
  }
319
0
  std::string kindName = query.substr(0, sep_pos);
320
0
  std::string verStr = query.substr(sep_pos + 1);
321
0
  if (kindName == ObjectKindName(ObjectKind::CodeModel)) {
322
0
    Object o;
323
0
    o.Kind = ObjectKind::CodeModel;
324
0
    if (verStr == "v2") {
325
0
      o.Version = 2;
326
0
    } else {
327
0
      return false;
328
0
    }
329
0
    objects.push_back(o);
330
0
    return true;
331
0
  }
332
0
  if (kindName == ObjectKindName(ObjectKind::ConfigureLog)) {
333
0
    Object o;
334
0
    o.Kind = ObjectKind::ConfigureLog;
335
0
    if (verStr == "v1") {
336
0
      o.Version = 1;
337
0
    } else {
338
0
      return false;
339
0
    }
340
0
    objects.push_back(o);
341
0
    return true;
342
0
  }
343
0
  if (kindName == ObjectKindName(ObjectKind::Cache)) {
344
0
    Object o;
345
0
    o.Kind = ObjectKind::Cache;
346
0
    if (verStr == "v2") {
347
0
      o.Version = 2;
348
0
    } else {
349
0
      return false;
350
0
    }
351
0
    objects.push_back(o);
352
0
    return true;
353
0
  }
354
0
  if (kindName == ObjectKindName(ObjectKind::CMakeFiles)) {
355
0
    Object o;
356
0
    o.Kind = ObjectKind::CMakeFiles;
357
0
    if (verStr == "v1") {
358
0
      o.Version = 1;
359
0
    } else {
360
0
      return false;
361
0
    }
362
0
    objects.push_back(o);
363
0
    return true;
364
0
  }
365
0
  if (kindName == ObjectKindName(ObjectKind::Toolchains)) {
366
0
    Object o;
367
0
    o.Kind = ObjectKind::Toolchains;
368
0
    if (verStr == "v1") {
369
0
      o.Version = 1;
370
0
    } else {
371
0
      return false;
372
0
    }
373
0
    objects.push_back(o);
374
0
    return true;
375
0
  }
376
0
  if (kindName == ObjectKindName(ObjectKind::InternalTest)) {
377
0
    Object o;
378
0
    o.Kind = ObjectKind::InternalTest;
379
0
    if (verStr == "v1") {
380
0
      o.Version = 1;
381
0
    } else if (verStr == "v2") {
382
0
      o.Version = 2;
383
0
    } else {
384
0
      return false;
385
0
    }
386
0
    objects.push_back(o);
387
0
    return true;
388
0
  }
389
0
  return false;
390
0
}
391
392
void cmFileAPI::ReadClient(std::string const& client)
393
0
{
394
  // Load queries for the client.
395
0
  std::string clientDir = cmStrCat(this->APIv1, "/query/", client);
396
0
  std::vector<std::string> queries = this->LoadDir(clientDir);
397
398
  // Read the queries and save for later.
399
0
  ClientQuery& clientQuery = this->ClientQueries[client];
400
0
  for (std::string& query : queries) {
401
0
    if (query == "query.json") {
402
0
      clientQuery.HaveQueryJson = true;
403
0
      this->ReadClientQuery(client, clientQuery.QueryJson);
404
0
    } else if (!this->ReadQuery(query, clientQuery.DirQuery.Known)) {
405
0
      clientQuery.DirQuery.Unknown.push_back(std::move(query));
406
0
    }
407
0
  }
408
0
}
409
410
void cmFileAPI::ReadClientQuery(std::string const& client, ClientQueryJson& q)
411
0
{
412
  // Read the query.json file.
413
0
  std::string queryFile =
414
0
    cmStrCat(this->APIv1, "/query/", client, "/query.json");
415
0
  Json::Value query;
416
0
  if (!this->ReadJsonFile(queryFile, query, q.Error)) {
417
0
    return;
418
0
  }
419
0
  if (!query.isObject()) {
420
0
    q.Error = "query root is not an object";
421
0
    return;
422
0
  }
423
424
0
  Json::Value const& clientValue = query["client"];
425
0
  if (!clientValue.isNull()) {
426
0
    q.ClientValue = clientValue;
427
0
  }
428
0
  q.RequestsValue = std::move(query["requests"]);
429
0
  q.Requests = this->BuildClientRequests(q.RequestsValue);
430
0
}
431
432
Json::Value cmFileAPI::BuildReplyIndex()
433
0
{
434
0
  Json::Value index(Json::objectValue);
435
436
  // Report information about this version of CMake.
437
0
  index["cmake"] = this->BuildCMake();
438
439
  // Reply to all queries that we loaded.
440
0
  Json::Value& reply = index["reply"] = this->BuildReply(this->TopQuery);
441
0
  for (auto const& client : this->ClientQueries) {
442
0
    std::string const& clientName = client.first;
443
0
    ClientQuery const& clientQuery = client.second;
444
0
    reply[clientName] = this->BuildClientReply(clientQuery);
445
0
  }
446
447
  // Move our index of generated objects into its field.
448
0
  Json::Value& objects = index["objects"] = Json::arrayValue;
449
0
  for (auto& entry : this->ReplyIndexObjects) {
450
0
    objects.append(std::move(entry.second)); // NOLINT(*)
451
0
  }
452
453
0
  return index;
454
0
}
455
456
Json::Value cmFileAPI::BuildCMake()
457
0
{
458
0
  Json::Value cmake = Json::objectValue;
459
0
  cmake["version"] = this->CMakeInstance->ReportVersionJson();
460
0
  Json::Value& cmake_paths = cmake["paths"] = Json::objectValue;
461
0
  cmake_paths["cmake"] = cmSystemTools::GetCMakeCommand();
462
0
  cmake_paths["ctest"] = cmSystemTools::GetCTestCommand();
463
0
  cmake_paths["cpack"] = cmSystemTools::GetCPackCommand();
464
0
  cmake_paths["root"] = cmSystemTools::GetCMakeRoot();
465
0
  cmake["generator"] = this->CMakeInstance->GetGlobalGenerator()->GetJson();
466
0
  return cmake;
467
0
}
468
469
Json::Value cmFileAPI::BuildReply(Query const& q)
470
0
{
471
0
  Json::Value reply = Json::objectValue;
472
#ifdef CM_CLANG_SUPPRESS_WARN_RANGE_LOOP_ANALYSIS
473
#  pragma clang diagnostic push
474
#  pragma clang diagnostic ignored "-Wrange-loop-analysis"
475
#endif
476
0
  for (Object const o : q.Known) {
477
#ifdef CM_CLANG_SUPPRESS_WARN_RANGE_LOOP_ANALYSIS
478
#  pragma clang diagnostic pop
479
#endif
480
0
    std::string const& name = ObjectName(o);
481
0
    reply[name] = this->BuildReplyEntry(o);
482
0
  }
483
484
0
  for (std::string const& name : q.Unknown) {
485
0
    reply[name] = cmFileAPI::BuildReplyError("unknown query file");
486
0
  }
487
0
  return reply;
488
0
}
489
490
Json::Value cmFileAPI::BuildReplyEntry(Object object)
491
0
{
492
0
  if (this->ReplyIndexFor != IndexFor::Success) {
493
0
    switch (object.Kind) {
494
0
      case ObjectKind::ConfigureLog:
495
0
        break;
496
0
      case ObjectKind::CodeModel:
497
0
      case ObjectKind::Cache:
498
0
      case ObjectKind::CMakeFiles:
499
0
      case ObjectKind::Toolchains:
500
0
      case ObjectKind::InternalTest:
501
0
        return this->BuildReplyError("no buildsystem generated");
502
0
    }
503
0
  }
504
0
  return this->AddReplyIndexObject(object);
505
0
}
506
507
Json::Value cmFileAPI::BuildReplyError(std::string const& error)
508
0
{
509
0
  Json::Value e = Json::objectValue;
510
0
  e["error"] = error;
511
0
  return e;
512
0
}
513
514
Json::Value const& cmFileAPI::AddReplyIndexObject(Object o)
515
0
{
516
0
  Json::Value& indexEntry = this->ReplyIndexObjects[o];
517
0
  if (!indexEntry.isNull()) {
518
    // The reply object has already been generated.
519
0
    return indexEntry;
520
0
  }
521
522
  // Generate this reply object.
523
0
  Json::Value const& object = this->BuildObject(o);
524
0
  assert(object.isObject());
525
526
  // Populate this index entry.
527
0
  indexEntry = Json::objectValue;
528
0
  indexEntry["kind"] = object["kind"];
529
0
  indexEntry["version"] = object["version"];
530
0
  indexEntry["jsonFile"] = this->WriteJsonFile(object, ObjectName(o));
531
0
  return indexEntry;
532
0
}
533
534
char const* cmFileAPI::ObjectKindName(ObjectKind kind)
535
0
{
536
  // Keep in sync with ObjectKind enum.
537
0
  static char const* objectKindNames[] = {
538
0
    "codemodel",    //
539
0
    "configureLog", //
540
0
    "cache",        //
541
0
    "cmakeFiles",   //
542
0
    "toolchains",   //
543
0
    "__test"        //
544
0
  };
545
0
  return objectKindNames[static_cast<size_t>(kind)];
546
0
}
547
548
std::string cmFileAPI::ObjectName(Object o)
549
0
{
550
0
  std::string name = cmStrCat(ObjectKindName(o.Kind), "-v", o.Version);
551
0
  return name;
552
0
}
553
554
Json::Value cmFileAPI::BuildVersion(unsigned int major, unsigned int minor)
555
0
{
556
0
  Json::Value version;
557
0
  version["major"] = major;
558
0
  version["minor"] = minor;
559
0
  return version;
560
0
}
561
562
Json::Value cmFileAPI::BuildObject(Object object)
563
0
{
564
0
  Json::Value value;
565
566
0
  switch (object.Kind) {
567
0
    case ObjectKind::CodeModel:
568
0
      value = this->BuildCodeModel(object);
569
0
      break;
570
0
    case ObjectKind::ConfigureLog:
571
0
      value = this->BuildConfigureLog(object);
572
0
      break;
573
0
    case ObjectKind::Cache:
574
0
      value = this->BuildCache(object);
575
0
      break;
576
0
    case ObjectKind::CMakeFiles:
577
0
      value = this->BuildCMakeFiles(object);
578
0
      break;
579
0
    case ObjectKind::Toolchains:
580
0
      value = this->BuildToolchains(object);
581
0
      break;
582
0
    case ObjectKind::InternalTest:
583
0
      value = this->BuildInternalTest(object);
584
0
      break;
585
0
  }
586
587
0
  return value;
588
0
}
589
590
cmFileAPI::ClientRequests cmFileAPI::BuildClientRequests(
591
  Json::Value const& requests)
592
0
{
593
0
  ClientRequests result;
594
0
  if (requests.isNull()) {
595
0
    result.Error = "'requests' member missing";
596
0
    return result;
597
0
  }
598
0
  if (!requests.isArray()) {
599
0
    result.Error = "'requests' member is not an array";
600
0
    return result;
601
0
  }
602
603
0
  result.reserve(requests.size());
604
0
  for (Json::Value const& request : requests) {
605
0
    result.emplace_back(this->BuildClientRequest(request));
606
0
  }
607
608
0
  return result;
609
0
}
610
611
cmFileAPI::ClientRequest cmFileAPI::BuildClientRequest(
612
  Json::Value const& request)
613
0
{
614
0
  ClientRequest r;
615
616
0
  if (!request.isObject()) {
617
0
    r.Error = "request is not an object";
618
0
    return r;
619
0
  }
620
621
0
  Json::Value const& kind = request["kind"];
622
0
  if (kind.isNull()) {
623
0
    r.Error = "'kind' member missing";
624
0
    return r;
625
0
  }
626
0
  if (!kind.isString()) {
627
0
    r.Error = "'kind' member is not a string";
628
0
    return r;
629
0
  }
630
0
  std::string const& kindName = kind.asString();
631
632
0
  if (kindName == this->ObjectKindName(ObjectKind::CodeModel)) {
633
0
    r.Kind = ObjectKind::CodeModel;
634
0
  } else if (kindName == this->ObjectKindName(ObjectKind::ConfigureLog)) {
635
0
    r.Kind = ObjectKind::ConfigureLog;
636
0
  } else if (kindName == this->ObjectKindName(ObjectKind::Cache)) {
637
0
    r.Kind = ObjectKind::Cache;
638
0
  } else if (kindName == this->ObjectKindName(ObjectKind::CMakeFiles)) {
639
0
    r.Kind = ObjectKind::CMakeFiles;
640
0
  } else if (kindName == this->ObjectKindName(ObjectKind::Toolchains)) {
641
0
    r.Kind = ObjectKind::Toolchains;
642
0
  } else if (kindName == this->ObjectKindName(ObjectKind::InternalTest)) {
643
0
    r.Kind = ObjectKind::InternalTest;
644
0
  } else {
645
0
    r.Error = cmStrCat("unknown request kind '", kindName, '\'');
646
0
    return r;
647
0
  }
648
649
0
  Json::Value const& version = request["version"];
650
0
  if (version.isNull()) {
651
0
    r.Error = "'version' member missing";
652
0
    return r;
653
0
  }
654
0
  std::vector<RequestVersion> versions;
655
0
  if (!cmFileAPI::ReadRequestVersions(version, versions, r.Error)) {
656
0
    return r;
657
0
  }
658
659
0
  switch (r.Kind) {
660
0
    case ObjectKind::CodeModel:
661
0
      this->BuildClientRequestCodeModel(r, versions);
662
0
      break;
663
0
    case ObjectKind::ConfigureLog:
664
0
      this->BuildClientRequestConfigureLog(r, versions);
665
0
      break;
666
0
    case ObjectKind::Cache:
667
0
      this->BuildClientRequestCache(r, versions);
668
0
      break;
669
0
    case ObjectKind::CMakeFiles:
670
0
      this->BuildClientRequestCMakeFiles(r, versions);
671
0
      break;
672
0
    case ObjectKind::Toolchains:
673
0
      this->BuildClientRequestToolchains(r, versions);
674
0
      break;
675
0
    case ObjectKind::InternalTest:
676
0
      this->BuildClientRequestInternalTest(r, versions);
677
0
      break;
678
0
  }
679
680
0
  return r;
681
0
}
682
683
Json::Value cmFileAPI::BuildClientReply(ClientQuery const& q)
684
0
{
685
0
  Json::Value reply = this->BuildReply(q.DirQuery);
686
687
0
  if (!q.HaveQueryJson) {
688
0
    return reply;
689
0
  }
690
691
0
  Json::Value& reply_query_json = reply["query.json"];
692
0
  ClientQueryJson const& qj = q.QueryJson;
693
694
0
  if (!qj.Error.empty()) {
695
0
    reply_query_json = this->BuildReplyError(qj.Error);
696
0
    return reply;
697
0
  }
698
699
0
  if (!qj.ClientValue.isNull()) {
700
0
    reply_query_json["client"] = qj.ClientValue;
701
0
  }
702
703
0
  if (!qj.RequestsValue.isNull()) {
704
0
    reply_query_json["requests"] = qj.RequestsValue;
705
0
  }
706
707
0
  reply_query_json["responses"] = this->BuildClientReplyResponses(qj.Requests);
708
709
0
  return reply;
710
0
}
711
712
Json::Value cmFileAPI::BuildClientReplyResponses(
713
  ClientRequests const& requests)
714
0
{
715
0
  Json::Value responses;
716
717
0
  if (!requests.Error.empty()) {
718
0
    responses = this->BuildReplyError(requests.Error);
719
0
    return responses;
720
0
  }
721
722
0
  responses = Json::arrayValue;
723
0
  for (ClientRequest const& request : requests) {
724
0
    responses.append(this->BuildClientReplyResponse(request));
725
0
  }
726
727
0
  return responses;
728
0
}
729
730
Json::Value cmFileAPI::BuildClientReplyResponse(ClientRequest const& request)
731
0
{
732
0
  Json::Value response;
733
0
  if (!request.Error.empty()) {
734
0
    response = this->BuildReplyError(request.Error);
735
0
    return response;
736
0
  }
737
0
  response = this->BuildReplyEntry(request);
738
0
  return response;
739
0
}
740
741
bool cmFileAPI::ReadRequestVersions(Json::Value const& version,
742
                                    std::vector<RequestVersion>& versions,
743
                                    std::string& error)
744
0
{
745
0
  if (version.isArray()) {
746
0
    for (Json::Value const& v : version) {
747
0
      if (!ReadRequestVersion(v, /*inArray=*/true, versions, error)) {
748
0
        return false;
749
0
      }
750
0
    }
751
0
  } else {
752
0
    if (!ReadRequestVersion(version, /*inArray=*/false, versions, error)) {
753
0
      return false;
754
0
    }
755
0
  }
756
0
  return true;
757
0
}
758
759
bool cmFileAPI::ReadRequestVersion(Json::Value const& version, bool inArray,
760
                                   std::vector<RequestVersion>& result,
761
                                   std::string& error)
762
0
{
763
0
  if (version.isUInt()) {
764
0
    RequestVersion v;
765
0
    v.Major = version.asUInt();
766
0
    result.push_back(v);
767
0
    return true;
768
0
  }
769
770
0
  if (!version.isObject()) {
771
0
    if (inArray) {
772
0
      error = "'version' array entry is not a non-negative integer or object";
773
0
    } else {
774
0
      error =
775
0
        "'version' member is not a non-negative integer, object, or array";
776
0
    }
777
0
    return false;
778
0
  }
779
780
0
  Json::Value const& major = version["major"];
781
0
  if (major.isNull()) {
782
0
    error = "'version' object 'major' member missing";
783
0
    return false;
784
0
  }
785
0
  if (!major.isUInt()) {
786
0
    error = "'version' object 'major' member is not a non-negative integer";
787
0
    return false;
788
0
  }
789
790
0
  RequestVersion v;
791
0
  v.Major = major.asUInt();
792
793
0
  Json::Value const& minor = version["minor"];
794
0
  if (minor.isUInt()) {
795
0
    v.Minor = minor.asUInt();
796
0
  } else if (!minor.isNull()) {
797
0
    error = "'version' object 'minor' member is not a non-negative integer";
798
0
    return false;
799
0
  }
800
801
0
  result.push_back(v);
802
803
0
  return true;
804
0
}
805
806
std::string cmFileAPI::NoSupportedVersion(
807
  std::vector<RequestVersion> const& versions)
808
0
{
809
0
  std::ostringstream msg;
810
0
  msg << "no supported version specified";
811
0
  if (!versions.empty()) {
812
0
    msg << " among:";
813
0
    for (RequestVersion const& v : versions) {
814
0
      msg << " " << v.Major << "." << v.Minor;
815
0
    }
816
0
  }
817
0
  return msg.str();
818
0
}
819
820
// The "codemodel" object kind.
821
822
// Update the following files as well when updating this constant:
823
//   Help/manual/cmake-file-api.7.rst
824
//   Tests/RunCMake/FileAPI/codemodel-v2-check.py (check_objects())
825
static unsigned int const CodeModelV2Minor = 10;
826
827
void cmFileAPI::BuildClientRequestCodeModel(
828
  ClientRequest& r, std::vector<RequestVersion> const& versions)
829
0
{
830
  // Select a known version from those requested.
831
0
  for (RequestVersion const& v : versions) {
832
0
    if ((v.Major == 2 && v.Minor <= CodeModelV2Minor)) {
833
0
      r.Version = v.Major;
834
0
      break;
835
0
    }
836
0
  }
837
0
  if (!r.Version) {
838
0
    r.Error = NoSupportedVersion(versions);
839
0
  }
840
0
}
841
842
Json::Value cmFileAPI::BuildCodeModel(Object object)
843
0
{
844
0
  assert(object.Version == 2);
845
0
  Json::Value codemodel =
846
0
    cmFileAPICodemodelDump(*this, object.Version, CodeModelV2Minor);
847
0
  codemodel["kind"] = this->ObjectKindName(object.Kind);
848
849
0
  Json::Value& version = codemodel["version"];
850
0
  if (object.Version == 2) {
851
0
    version = BuildVersion(2, CodeModelV2Minor);
852
0
  } else {
853
0
    return codemodel; // should be unreachable
854
0
  }
855
856
0
  return codemodel;
857
0
}
858
859
// The "configureLog" object kind.
860
861
// Update Help/manual/cmake-file-api.7.rst when updating this constant.
862
static unsigned int const ConfigureLogV1Minor = 0;
863
864
void cmFileAPI::BuildClientRequestConfigureLog(
865
  ClientRequest& r, std::vector<RequestVersion> const& versions)
866
0
{
867
  // Select a known version from those requested.
868
0
  for (RequestVersion const& v : versions) {
869
0
    if ((v.Major == 1 && v.Minor <= ConfigureLogV1Minor)) {
870
0
      r.Version = v.Major;
871
0
      break;
872
0
    }
873
0
  }
874
0
  if (!r.Version) {
875
0
    r.Error = NoSupportedVersion(versions);
876
0
  }
877
0
}
878
879
Json::Value cmFileAPI::BuildConfigureLog(Object object)
880
0
{
881
0
  Json::Value configureLog = cmFileAPIConfigureLogDump(*this, object.Version);
882
0
  configureLog["kind"] = this->ObjectKindName(object.Kind);
883
884
0
  Json::Value& version = configureLog["version"];
885
0
  if (object.Version == 1) {
886
0
    version = BuildVersion(1, ConfigureLogV1Minor);
887
0
  } else {
888
0
    return configureLog; // should be unreachable
889
0
  }
890
891
0
  return configureLog;
892
0
}
893
894
// The "cache" object kind.
895
896
static unsigned int const CacheV2Minor = 0;
897
898
void cmFileAPI::BuildClientRequestCache(
899
  ClientRequest& r, std::vector<RequestVersion> const& versions)
900
0
{
901
  // Select a known version from those requested.
902
0
  for (RequestVersion const& v : versions) {
903
0
    if ((v.Major == 2 && v.Minor <= CacheV2Minor)) {
904
0
      r.Version = v.Major;
905
0
      break;
906
0
    }
907
0
  }
908
0
  if (!r.Version) {
909
0
    r.Error = NoSupportedVersion(versions);
910
0
  }
911
0
}
912
913
Json::Value cmFileAPI::BuildCache(Object object)
914
0
{
915
0
  Json::Value cache = cmFileAPICacheDump(*this, object.Version);
916
0
  cache["kind"] = this->ObjectKindName(object.Kind);
917
918
0
  Json::Value& version = cache["version"];
919
0
  if (object.Version == 2) {
920
0
    version = BuildVersion(2, CacheV2Minor);
921
0
  } else {
922
0
    return cache; // should be unreachable
923
0
  }
924
925
0
  return cache;
926
0
}
927
928
// The "cmakeFiles" object kind.
929
930
static unsigned int const CMakeFilesV1Minor = 1;
931
932
void cmFileAPI::BuildClientRequestCMakeFiles(
933
  ClientRequest& r, std::vector<RequestVersion> const& versions)
934
0
{
935
  // Select a known version from those requested.
936
0
  for (RequestVersion const& v : versions) {
937
0
    if ((v.Major == 1 && v.Minor <= CMakeFilesV1Minor)) {
938
0
      r.Version = v.Major;
939
0
      break;
940
0
    }
941
0
  }
942
0
  if (!r.Version) {
943
0
    r.Error = NoSupportedVersion(versions);
944
0
  }
945
0
}
946
947
Json::Value cmFileAPI::BuildCMakeFiles(Object object)
948
0
{
949
0
  Json::Value cmakeFiles = cmFileAPICMakeFilesDump(*this, object.Version);
950
0
  cmakeFiles["kind"] = this->ObjectKindName(object.Kind);
951
952
0
  Json::Value& version = cmakeFiles["version"];
953
0
  if (object.Version == 1) {
954
0
    version = BuildVersion(1, CMakeFilesV1Minor);
955
0
  } else {
956
0
    return cmakeFiles; // should be unreachable
957
0
  }
958
959
0
  return cmakeFiles;
960
0
}
961
962
// The "toolchains" object kind.
963
964
static unsigned int const ToolchainsV1Minor = 1;
965
966
void cmFileAPI::BuildClientRequestToolchains(
967
  ClientRequest& r, std::vector<RequestVersion> const& versions)
968
0
{
969
  // Select a known version from those requested.
970
0
  for (RequestVersion const& v : versions) {
971
0
    if ((v.Major == 1 && v.Minor <= ToolchainsV1Minor)) {
972
0
      r.Version = v.Major;
973
0
      break;
974
0
    }
975
0
  }
976
0
  if (!r.Version) {
977
0
    r.Error = NoSupportedVersion(versions);
978
0
  }
979
0
}
980
981
Json::Value cmFileAPI::BuildToolchains(Object object)
982
0
{
983
0
  Json::Value toolchains = cmFileAPIToolchainsDump(*this, object.Version);
984
0
  toolchains["kind"] = this->ObjectKindName(object.Kind);
985
986
0
  Json::Value& version = toolchains["version"];
987
0
  if (object.Version == 1) {
988
0
    version = BuildVersion(1, ToolchainsV1Minor);
989
0
  } else {
990
0
    return toolchains; // should be unreachable
991
0
  }
992
993
0
  return toolchains;
994
0
}
995
996
// The "__test" object kind is for internal testing of CMake.
997
998
static unsigned int const InternalTestV1Minor = 3;
999
static unsigned int const InternalTestV2Minor = 0;
1000
1001
void cmFileAPI::BuildClientRequestInternalTest(
1002
  ClientRequest& r, std::vector<RequestVersion> const& versions)
1003
0
{
1004
  // Select a known version from those requested.
1005
0
  for (RequestVersion const& v : versions) {
1006
0
    if ((v.Major == 1 && v.Minor <= InternalTestV1Minor) || //
1007
0
        (v.Major == 2 && v.Minor <= InternalTestV2Minor)) {
1008
0
      r.Version = v.Major;
1009
0
      break;
1010
0
    }
1011
0
  }
1012
0
  if (!r.Version) {
1013
0
    r.Error = NoSupportedVersion(versions);
1014
0
  }
1015
0
}
1016
1017
Json::Value cmFileAPI::BuildInternalTest(Object object)
1018
0
{
1019
0
  Json::Value test = Json::objectValue;
1020
0
  test["kind"] = this->ObjectKindName(object.Kind);
1021
0
  Json::Value& version = test["version"];
1022
0
  if (object.Version == 2) {
1023
0
    version = BuildVersion(2, InternalTestV2Minor);
1024
0
  } else {
1025
0
    version = BuildVersion(1, InternalTestV1Minor);
1026
0
  }
1027
0
  return test;
1028
0
}
1029
1030
Json::Value cmFileAPI::ReportCapabilities()
1031
0
{
1032
0
  Json::Value capabilities = Json::objectValue;
1033
0
  Json::Value& requests = capabilities["requests"] = Json::arrayValue;
1034
1035
0
  {
1036
0
    Json::Value request = Json::objectValue;
1037
0
    request["kind"] = ObjectKindName(ObjectKind::CodeModel);
1038
0
    Json::Value& versions = request["version"] = Json::arrayValue;
1039
0
    versions.append(BuildVersion(2, CodeModelV2Minor));
1040
0
    requests.append(std::move(request)); // NOLINT(*)
1041
0
  }
1042
1043
0
  {
1044
0
    Json::Value request = Json::objectValue;
1045
0
    request["kind"] = ObjectKindName(ObjectKind::ConfigureLog);
1046
0
    Json::Value& versions = request["version"] = Json::arrayValue;
1047
0
    versions.append(BuildVersion(1, ConfigureLogV1Minor));
1048
0
    requests.append(std::move(request)); // NOLINT(*)
1049
0
  }
1050
1051
0
  {
1052
0
    Json::Value request = Json::objectValue;
1053
0
    request["kind"] = ObjectKindName(ObjectKind::Cache);
1054
0
    Json::Value& versions = request["version"] = Json::arrayValue;
1055
0
    versions.append(BuildVersion(2, CacheV2Minor));
1056
0
    requests.append(std::move(request)); // NOLINT(*)
1057
0
  }
1058
1059
0
  {
1060
0
    Json::Value request = Json::objectValue;
1061
0
    request["kind"] = ObjectKindName(ObjectKind::CMakeFiles);
1062
0
    Json::Value& versions = request["version"] = Json::arrayValue;
1063
0
    versions.append(BuildVersion(1, CMakeFilesV1Minor));
1064
0
    requests.append(std::move(request)); // NOLINT(*)
1065
0
  }
1066
1067
0
  {
1068
0
    Json::Value request = Json::objectValue;
1069
0
    request["kind"] = ObjectKindName(ObjectKind::Toolchains);
1070
0
    Json::Value& versions = request["version"] = Json::arrayValue;
1071
0
    versions.append(BuildVersion(1, ToolchainsV1Minor));
1072
0
    requests.append(std::move(request)); // NOLINT(*)
1073
0
  }
1074
1075
0
  return capabilities;
1076
0
}
1077
1078
bool cmFileAPI::AddProjectQuery(cmFileAPI::ObjectKind kind,
1079
                                unsigned majorVersion, unsigned minorVersion)
1080
0
{
1081
0
  switch (kind) {
1082
0
    case ObjectKind::CodeModel:
1083
0
      if (majorVersion != 2 || minorVersion > CodeModelV2Minor) {
1084
0
        return false;
1085
0
      }
1086
0
      break;
1087
0
    case ObjectKind::Cache:
1088
0
      if (majorVersion != 2 || minorVersion > CacheV2Minor) {
1089
0
        return false;
1090
0
      }
1091
0
      break;
1092
0
    case ObjectKind::CMakeFiles:
1093
0
      if (majorVersion != 1 || minorVersion > CMakeFilesV1Minor) {
1094
0
        return false;
1095
0
      }
1096
0
      break;
1097
0
    case ObjectKind::Toolchains:
1098
0
      if (majorVersion != 1 || minorVersion > ToolchainsV1Minor) {
1099
0
        return false;
1100
0
      }
1101
0
      break;
1102
    // These cannot be requested by the project
1103
0
    case ObjectKind::ConfigureLog:
1104
0
    case ObjectKind::InternalTest:
1105
0
      return false;
1106
0
  }
1107
1108
0
  Object query;
1109
0
  query.Kind = kind;
1110
0
  query.Version = majorVersion;
1111
0
  if (std::find(this->TopQuery.Known.begin(), this->TopQuery.Known.end(),
1112
0
                query) == this->TopQuery.Known.end()) {
1113
0
    this->TopQuery.Known.emplace_back(query);
1114
0
    this->QueryExists = true;
1115
0
  }
1116
1117
0
  return true;
1118
0
}