Coverage Report

Created: 2026-03-12 06:35

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