Coverage Report

Created: 2026-02-09 06:05

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