Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmBuildDatabase.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 "cmBuildDatabase.h"
4
5
#include <cstdlib>
6
#include <set>
7
#include <utility>
8
9
#include <cm/memory>
10
#include <cm/string_view>
11
#include <cmext/string_view>
12
13
#include <cm3p/json/reader.h>
14
#include <cm3p/json/value.h>
15
#include <cm3p/json/writer.h>
16
17
#include "cmsys/FStream.hxx"
18
19
#include "cmComputeLinkInformation.h"
20
#include "cmFileSetMetadata.h"
21
#include "cmGeneratedFileStream.h"
22
#include "cmGeneratorFileSet.h"
23
#include "cmGeneratorTarget.h"
24
#include "cmGlobalGenerator.h"
25
#include "cmListFileCache.h"
26
#include "cmLocalGenerator.h"
27
#include "cmSourceFile.h"
28
#include "cmStringAlgorithms.h"
29
#include "cmSystemTools.h"
30
31
namespace {
32
33
std::string PlaceholderName = "<__CMAKE_UNKNOWN>";
34
35
}
36
37
0
cmBuildDatabase::cmBuildDatabase() = default;
38
0
cmBuildDatabase::cmBuildDatabase(cmBuildDatabase const&) = default;
39
0
cmBuildDatabase::~cmBuildDatabase() = default;
40
41
cmBuildDatabase::LookupTable cmBuildDatabase::GenerateLookupTable()
42
0
{
43
0
  LookupTable lut;
44
45
0
  for (auto& Set_ : this->Sets) {
46
0
    for (auto& TranslationUnit_ : Set_.TranslationUnits) {
47
      // This table is from source path to TU instance. This is fine because a
48
      // single target (where this is used) cannot contain the same source file
49
      // multiple times.
50
0
      lut[TranslationUnit_.Source] = &TranslationUnit_;
51
0
    }
52
0
  }
53
54
0
  return lut;
55
0
}
56
57
bool cmBuildDatabase::HasPlaceholderNames() const
58
0
{
59
0
  for (auto const& Set_ : this->Sets) {
60
0
    for (auto const& TranslationUnit_ : Set_.TranslationUnits) {
61
0
      for (auto const& provide : TranslationUnit_.Provides) {
62
0
        if (provide.first == PlaceholderName) {
63
0
          return true;
64
0
        }
65
0
        if (provide.second == PlaceholderName) {
66
0
          return true;
67
0
        }
68
0
      }
69
0
    }
70
0
  }
71
72
0
  return false;
73
0
}
74
75
void cmBuildDatabase::Write(std::string const& path) const
76
0
{
77
0
  Json::Value mcdb = Json::objectValue;
78
79
0
  mcdb["version"] = 1;
80
0
  mcdb["revision"] = 0;
81
82
0
  Json::Value& sets = mcdb["sets"] = Json::arrayValue;
83
84
0
  for (auto const& Set_ : this->Sets) {
85
0
    Json::Value set = Json::objectValue;
86
87
0
    set["name"] = Set_.Name;
88
0
    set["family-name"] = Set_.FamilyName;
89
90
0
    Json::Value& visible_sets = set["visible-sets"] = Json::arrayValue;
91
0
    for (auto const& VisibleSet : Set_.VisibleSets) {
92
0
      visible_sets.append(VisibleSet);
93
0
    }
94
95
0
    Json::Value& tus = set["translation-units"] = Json::arrayValue;
96
0
    for (auto const& TranslationUnit_ : Set_.TranslationUnits) {
97
0
      Json::Value tu = Json::objectValue;
98
99
0
      if (!TranslationUnit_.WorkDirectory.empty()) {
100
0
        tu["work-directory"] = TranslationUnit_.WorkDirectory;
101
0
      }
102
0
      tu["source"] = TranslationUnit_.Source;
103
0
      if (TranslationUnit_.Object) {
104
0
        tu["object"] = *TranslationUnit_.Object;
105
0
      }
106
0
      tu["private"] = TranslationUnit_.Private;
107
108
0
      Json::Value& reqs = tu["requires"] = Json::arrayValue;
109
0
      for (auto const& Require : TranslationUnit_.Requires) {
110
0
        reqs.append(Require);
111
0
      }
112
113
0
      Json::Value& provides = tu["provides"] = Json::objectValue;
114
0
      for (auto const& Provide : TranslationUnit_.Provides) {
115
0
        provides[Provide.first] = Provide.second;
116
0
      }
117
118
0
      Json::Value& baseline_arguments = tu["baseline-arguments"] =
119
0
        Json::arrayValue;
120
0
      for (auto const& BaselineArgument : TranslationUnit_.BaselineArguments) {
121
0
        baseline_arguments.append(BaselineArgument);
122
0
      }
123
124
0
      Json::Value& local_arguments = tu["local-arguments"] = Json::arrayValue;
125
0
      for (auto const& LocalArgument : TranslationUnit_.LocalArguments) {
126
0
        local_arguments.append(LocalArgument);
127
0
      }
128
129
0
      Json::Value& arguments = tu["arguments"] = Json::arrayValue;
130
0
      for (auto const& Argument : TranslationUnit_.Arguments) {
131
0
        arguments.append(Argument);
132
0
      }
133
134
0
      tus.append(tu);
135
0
    }
136
137
0
    sets.append(set);
138
0
  }
139
140
0
  cmGeneratedFileStream mcdbf(path);
141
0
  mcdbf << mcdb;
142
0
}
143
144
static bool ParseFilename(Json::Value const& val, std::string& result)
145
0
{
146
0
  if (val.isString()) {
147
0
    result = val.asString();
148
0
  } else {
149
0
    return false;
150
0
  }
151
152
0
  return true;
153
0
}
154
155
#define PARSE_BLOB(val, res)                                                  \
156
0
  do {                                                                        \
157
0
    if (!ParseFilename(val, res)) {                                           \
158
0
      cmSystemTools::Error(cmStrCat("-E cmake_module_compile_db failed to ",  \
159
0
                                    "parse ", path, ": invalid blob"));       \
160
0
      return {};                                                              \
161
0
    }                                                                         \
162
0
  } while (0)
163
164
#define PARSE_FILENAME(val, res, make_full)                                   \
165
0
  do {                                                                        \
166
0
    if (!ParseFilename(val, res)) {                                           \
167
0
      cmSystemTools::Error(cmStrCat("-E cmake_module_compile_db failed to ",  \
168
0
                                    "parse ", path, ": invalid filename"));   \
169
0
      return {};                                                              \
170
0
    }                                                                         \
171
0
                                                                              \
172
0
    if (make_full && work_directory && !work_directory->empty() &&            \
173
0
        !cmSystemTools::FileIsFullPath(res)) {                                \
174
0
      res = cmStrCat(*work_directory, '/', res);                              \
175
0
    }                                                                         \
176
0
  } while (0)
177
178
std::unique_ptr<cmBuildDatabase> cmBuildDatabase::Load(std::string const& path)
179
0
{
180
0
  Json::Value mcdb;
181
0
  {
182
0
    cmsys::ifstream mcdbf(path.c_str(), std::ios::in | std::ios::binary);
183
0
    Json::Reader reader;
184
0
    if (!reader.parse(mcdbf, mcdb, false)) {
185
0
      cmSystemTools::Error(
186
0
        cmStrCat("-E cmake_module_compile_db failed to parse ", path,
187
0
                 reader.getFormattedErrorMessages()));
188
0
      return {};
189
0
    }
190
0
  }
191
192
0
  Json::Value const& version = mcdb["version"];
193
0
  if (version.asUInt() > 1) {
194
0
    cmSystemTools::Error(
195
0
      cmStrCat("-E cmake_module_compile_db failed to parse ", path,
196
0
               ": version ", version.asString()));
197
0
    return {};
198
0
  }
199
200
0
  auto db = cm::make_unique<cmBuildDatabase>();
201
202
0
  Json::Value const& sets = mcdb["sets"];
203
0
  if (sets.isArray()) {
204
0
    for (auto const& set : sets) {
205
0
      Set Set_;
206
207
0
      Json::Value const& name = set["name"];
208
0
      if (!name.isString()) {
209
0
        cmSystemTools::Error(
210
0
          cmStrCat("-E cmake_module_compile_db failed to parse ", path,
211
0
                   ": name is not a string"));
212
0
        return {};
213
0
      }
214
0
      Set_.Name = name.asString();
215
216
0
      Json::Value const& family_name = set["family-name"];
217
0
      if (!family_name.isString()) {
218
0
        cmSystemTools::Error(
219
0
          cmStrCat("-E cmake_module_compile_db failed to parse ", path,
220
0
                   ": family-name is not a string"));
221
0
        return {};
222
0
      }
223
0
      Set_.FamilyName = family_name.asString();
224
225
0
      Json::Value const& visible_sets = set["visible-sets"];
226
0
      if (!visible_sets.isArray()) {
227
0
        cmSystemTools::Error(
228
0
          cmStrCat("-E cmake_module_compile_db failed to parse ", path,
229
0
                   ": visible-sets is not an array"));
230
0
        return {};
231
0
      }
232
0
      for (auto const& visible_set : visible_sets) {
233
0
        if (!visible_set.isString()) {
234
0
          cmSystemTools::Error(
235
0
            cmStrCat("-E cmake_module_compile_db failed to parse ", path,
236
0
                     ": a visible-sets item is not a string"));
237
0
          return {};
238
0
        }
239
240
0
        Set_.VisibleSets.emplace_back(visible_set.asString());
241
0
      }
242
243
0
      Json::Value const& translation_units = set["translation-units"];
244
0
      if (!translation_units.isArray()) {
245
0
        cmSystemTools::Error(
246
0
          cmStrCat("-E cmake_module_compile_db failed to parse ", path,
247
0
                   ": translation-units is not an array"));
248
0
        return {};
249
0
      }
250
0
      for (auto const& translation_unit : translation_units) {
251
0
        if (!translation_unit.isObject()) {
252
0
          cmSystemTools::Error(
253
0
            cmStrCat("-E cmake_module_compile_db failed to parse ", path,
254
0
                     ": a translation-units item is not an object"));
255
0
          return {};
256
0
        }
257
258
0
        TranslationUnit TranslationUnit_;
259
260
0
        cm::optional<std::string> work_directory;
261
0
        Json::Value const& workdir = translation_unit["work-directory"];
262
0
        if (workdir.isString()) {
263
0
          PARSE_BLOB(workdir, TranslationUnit_.WorkDirectory);
264
0
          work_directory = TranslationUnit_.WorkDirectory;
265
0
        } else if (!workdir.isNull()) {
266
0
          cmSystemTools::Error(
267
0
            cmStrCat("-E cmake_module_compile_db failed to parse ", path,
268
0
                     ": work-directory is not a string"));
269
0
          return {};
270
0
        }
271
272
0
        Json::Value const& source = translation_unit["source"];
273
0
        PARSE_FILENAME(source, TranslationUnit_.Source, true);
274
275
0
        if (translation_unit.isMember("object")) {
276
0
          Json::Value const& object = translation_unit["object"];
277
0
          if (!object.isNull()) {
278
0
            TranslationUnit_.Object = "";
279
0
            PARSE_FILENAME(object, *TranslationUnit_.Object, false);
280
0
          }
281
0
        }
282
283
0
        if (translation_unit.isMember("private")) {
284
0
          Json::Value const& priv = translation_unit["private"];
285
0
          if (!priv.isBool()) {
286
0
            cmSystemTools::Error(
287
0
              cmStrCat("-E cmake_module_compile_db failed to parse ", path,
288
0
                       ": private is not a boolean"));
289
0
            return {};
290
0
          }
291
0
          TranslationUnit_.Private = priv.asBool();
292
0
        }
293
294
0
        if (translation_unit.isMember("requires")) {
295
0
          Json::Value const& reqs = translation_unit["requires"];
296
0
          if (!reqs.isArray()) {
297
0
            cmSystemTools::Error(
298
0
              cmStrCat("-E cmake_module_compile_db failed to parse ", path,
299
0
                       ": requires is not an array"));
300
0
            return {};
301
0
          }
302
303
0
          for (auto const& require : reqs) {
304
0
            if (!require.isString()) {
305
0
              cmSystemTools::Error(
306
0
                cmStrCat("-E cmake_module_compile_db failed to parse ", path,
307
0
                         ": a requires item is not a string"));
308
0
              return {};
309
0
            }
310
311
0
            TranslationUnit_.Requires.emplace_back(require.asString());
312
0
          }
313
0
        }
314
315
0
        if (translation_unit.isMember("provides")) {
316
0
          Json::Value const& provides = translation_unit["provides"];
317
0
          if (!provides.isObject()) {
318
0
            cmSystemTools::Error(
319
0
              cmStrCat("-E cmake_module_compile_db failed to parse ", path,
320
0
                       ": provides is not an object"));
321
0
            return {};
322
0
          }
323
324
0
          for (auto i = provides.begin(); i != provides.end(); ++i) {
325
0
            if (!i->isString()) {
326
0
              cmSystemTools::Error(
327
0
                cmStrCat("-E cmake_module_compile_db failed to parse ", path,
328
0
                         ": a provides value is not a string"));
329
0
              return {};
330
0
            }
331
332
0
            TranslationUnit_.Provides[i.key().asString()] = i->asString();
333
0
          }
334
0
        }
335
336
0
        if (translation_unit.isMember("baseline-arguments")) {
337
0
          Json::Value const& baseline_arguments =
338
0
            translation_unit["baseline-arguments"];
339
0
          if (!baseline_arguments.isArray()) {
340
0
            cmSystemTools::Error(
341
0
              cmStrCat("-E cmake_module_compile_db failed to parse ", path,
342
0
                       ": baseline_arguments is not an array"));
343
0
            return {};
344
0
          }
345
346
0
          for (auto const& baseline_argument : baseline_arguments) {
347
0
            if (baseline_argument.isString()) {
348
0
              TranslationUnit_.BaselineArguments.emplace_back(
349
0
                baseline_argument.asString());
350
0
            } else {
351
0
              cmSystemTools::Error(
352
0
                cmStrCat("-E cmake_module_compile_db failed to parse ", path,
353
0
                         ": a baseline argument is not a string"));
354
0
              return {};
355
0
            }
356
0
          }
357
0
        }
358
359
0
        if (translation_unit.isMember("local-arguments")) {
360
0
          Json::Value const& local_arguments =
361
0
            translation_unit["local-arguments"];
362
0
          if (!local_arguments.isArray()) {
363
0
            cmSystemTools::Error(
364
0
              cmStrCat("-E cmake_module_compile_db failed to parse ", path,
365
0
                       ": local_arguments is not an array"));
366
0
            return {};
367
0
          }
368
369
0
          for (auto const& local_argument : local_arguments) {
370
0
            if (local_argument.isString()) {
371
0
              TranslationUnit_.LocalArguments.emplace_back(
372
0
                local_argument.asString());
373
0
            } else {
374
0
              cmSystemTools::Error(
375
0
                cmStrCat("-E cmake_module_compile_db failed to parse ", path,
376
0
                         ": a local argument is not a string"));
377
0
              return {};
378
0
            }
379
0
          }
380
0
        }
381
382
0
        if (translation_unit.isMember("arguments")) {
383
0
          Json::Value const& arguments = translation_unit["arguments"];
384
0
          if (!arguments.isArray()) {
385
0
            cmSystemTools::Error(
386
0
              cmStrCat("-E cmake_module_compile_db failed to parse ", path,
387
0
                       ": arguments is not an array"));
388
0
            return {};
389
0
          }
390
391
0
          for (auto const& argument : arguments) {
392
0
            if (argument.isString()) {
393
0
              TranslationUnit_.Arguments.emplace_back(argument.asString());
394
0
            } else {
395
0
              cmSystemTools::Error(
396
0
                cmStrCat("-E cmake_module_compile_db failed to parse ", path,
397
0
                         ": an argument is not a string"));
398
0
              return {};
399
0
            }
400
0
          }
401
0
        }
402
403
0
        Set_.TranslationUnits.emplace_back(std::move(TranslationUnit_));
404
0
      }
405
406
0
      db->Sets.emplace_back(std::move(Set_));
407
0
    }
408
0
  }
409
410
0
  return db;
411
0
}
412
413
cmBuildDatabase cmBuildDatabase::Merge(
414
  std::vector<cmBuildDatabase> const& components)
415
0
{
416
0
  cmBuildDatabase db;
417
418
0
  for (auto const& component : components) {
419
0
    db.Sets.insert(db.Sets.end(), component.Sets.begin(),
420
0
                   component.Sets.end());
421
0
  }
422
423
0
  return db;
424
0
}
425
426
cmBuildDatabase cmBuildDatabase::ForTarget(cmGeneratorTarget* gt,
427
                                           std::string const& config)
428
0
{
429
0
  cmBuildDatabase db;
430
431
0
  Set set;
432
0
  set.Name = cmStrCat(gt->GetName(), '@', config);
433
0
  set.FamilyName = gt->GetFamilyName();
434
0
  if (auto* cli = gt->GetLinkInformation(config)) {
435
0
    std::set<cmGeneratorTarget const*> emitted;
436
0
    std::vector<cmGeneratorTarget const*> targets;
437
0
    for (auto const& item : cli->GetItems()) {
438
0
      auto const* linkee = item.Target;
439
0
      if (linkee && linkee->HaveCxx20ModuleSources() &&
440
0
          !linkee->IsImported() && emitted.insert(linkee).second) {
441
0
        set.VisibleSets.push_back(cmStrCat(linkee->GetName(), '@', config));
442
0
      }
443
0
    }
444
0
  }
445
446
0
  for (auto const& sfbt : gt->GetSourceFiles(config)) {
447
0
    auto const* sf = sfbt.Value;
448
449
0
    bool isCXXModule = false;
450
0
    bool isPrivate = true;
451
0
    if (sf->GetLanguage() == "CXX"_s) {
452
0
      auto const* fs = gt->GetFileSetForSource(config, sf);
453
0
      if (fs && fs->GetType() == cm::FileSetMetadata::CXX_MODULES) {
454
0
        isCXXModule = true;
455
0
        isPrivate = !fs->IsForInterface();
456
0
      }
457
0
    }
458
459
0
    TranslationUnit tu;
460
461
    // FIXME: Makefiles will want this to be the current working directory.
462
0
    tu.WorkDirectory = gt->GetLocalGenerator()->GetBinaryDirectory();
463
0
    tu.Source = sf->GetFullPath();
464
0
    if (!gt->IsSynthetic()) {
465
0
      auto* gg = gt->GetGlobalGenerator();
466
0
      std::string const objectDir = gg->ConvertToOutputPath(
467
0
        cmStrCat(gt->GetSupportDirectory(), gg->GetConfigDirectory(config)));
468
0
      std::string const objectFileName = gt->GetObjectName(sf);
469
0
      tu.Object = cmStrCat(objectDir, '/', objectFileName);
470
0
    }
471
0
    if (isCXXModule) {
472
0
      tu.Provides[PlaceholderName] = PlaceholderName;
473
0
    }
474
475
0
    cmGeneratorTarget::ClassifiedFlags classifiedFlags =
476
0
      gt->GetClassifiedFlagsForSource(sf, config);
477
0
    for (auto const& classifiedFlag : classifiedFlags) {
478
0
      if (classifiedFlag.Classification ==
479
0
          cmGeneratorTarget::FlagClassification::BaselineFlag) {
480
0
        tu.BaselineArguments.push_back(classifiedFlag.Flag);
481
0
        tu.LocalArguments.push_back(classifiedFlag.Flag);
482
0
      } else if (classifiedFlag.Classification ==
483
0
                 cmGeneratorTarget::FlagClassification::PrivateFlag) {
484
0
        tu.LocalArguments.push_back(classifiedFlag.Flag);
485
0
      }
486
0
      tu.Arguments.push_back(classifiedFlag.Flag);
487
0
    }
488
0
    tu.Private = isPrivate;
489
490
0
    set.TranslationUnits.emplace_back(std::move(tu));
491
0
  }
492
493
0
  db.Sets.emplace_back(std::move(set));
494
495
0
  return db;
496
0
}
497
498
int cmcmd_cmake_module_compile_db(
499
  std::vector<std::string>::const_iterator argBeg,
500
  std::vector<std::string>::const_iterator argEnd)
501
0
{
502
0
  std::string const* command = nullptr;
503
0
  std::string const* output = nullptr;
504
0
  std::vector<std::string const*> inputs;
505
506
0
  bool next_is_output = false;
507
0
  for (auto i = argBeg; i != argEnd; ++i) {
508
    // The first argument is always the command.
509
0
    if (!command) {
510
0
      command = &(*i);
511
0
      continue;
512
0
    }
513
514
0
    if (*i == "-o"_s) {
515
0
      next_is_output = true;
516
0
      continue;
517
0
    }
518
0
    if (next_is_output) {
519
0
      if (output) {
520
0
        cmSystemTools::Error(
521
0
          "-E cmake_module_compile_db only supports one output file");
522
0
        return EXIT_FAILURE;
523
0
      }
524
525
0
      output = &(*i);
526
0
      next_is_output = false;
527
0
      continue;
528
0
    }
529
530
0
    inputs.emplace_back(&(*i));
531
0
  }
532
533
0
  if (!command) {
534
0
    cmSystemTools::Error("-E cmake_module_compile_db requires a subcommand");
535
0
    return EXIT_FAILURE;
536
0
  }
537
538
0
  int ret = EXIT_SUCCESS;
539
540
0
  if (*command == "verify"_s) {
541
0
    if (output) {
542
0
      cmSystemTools::Error(
543
0
        "-E cmake_module_compile_db verify does not support an output");
544
0
      return EXIT_FAILURE;
545
0
    }
546
547
0
    for (auto const* i : inputs) {
548
0
      auto db = cmBuildDatabase::Load(*i);
549
0
      if (!db) {
550
0
        cmSystemTools::Error(cmStrCat("failed to verify ", *i));
551
0
        ret = EXIT_FAILURE;
552
0
      }
553
0
    }
554
0
  } else if (*command == "merge"_s) {
555
0
    if (!output) {
556
0
      cmSystemTools::Error(
557
0
        "-E cmake_module_compile_db verify requires an output");
558
0
      return EXIT_FAILURE;
559
0
    }
560
561
0
    std::vector<cmBuildDatabase> dbs;
562
563
0
    for (auto const* i : inputs) {
564
0
      auto db = cmBuildDatabase::Load(*i);
565
0
      if (!db) {
566
0
        cmSystemTools::Error(cmStrCat("failed to read ", *i));
567
0
        return EXIT_FAILURE;
568
0
      }
569
570
0
      dbs.emplace_back(std::move(*db));
571
0
    }
572
573
0
    auto db = cmBuildDatabase::Merge(dbs);
574
0
    db.Write(*output);
575
0
  } else {
576
0
    cmSystemTools::Error(
577
0
      cmStrCat("-E cmake_module_compile_db unknown subcommand ", *command));
578
0
    return EXIT_FAILURE;
579
0
  }
580
581
0
  return ret;
582
0
}