Coverage Report

Created: 2026-02-09 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmDyndepCollation.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
4
#include "cmDyndepCollation.h"
5
6
#include <algorithm>
7
#include <map>
8
#include <ostream>
9
#include <set>
10
#include <utility>
11
#include <vector>
12
13
#include <cm/memory>
14
#include <cm/string_view>
15
#include <cmext/string_view>
16
17
#include <cm3p/json/value.h>
18
19
#include "cmBuildDatabase.h"
20
#include "cmCxxModuleMetadata.h"
21
#include "cmExportBuildFileGenerator.h"
22
#include "cmExportSet.h"
23
#include "cmFileSet.h"
24
#include "cmGenExContext.h"
25
#include "cmGeneratedFileStream.h"
26
#include "cmGeneratorExpression.h" // IWYU pragma: keep
27
#include "cmGeneratorTarget.h"
28
#include "cmGlobalGenerator.h"
29
#include "cmInstallCxxModuleBmiGenerator.h"
30
#include "cmInstallExportGenerator.h"
31
#include "cmInstallFileSetGenerator.h"
32
#include "cmInstallGenerator.h"
33
#include "cmListFileCache.h"
34
#include "cmMakefile.h"
35
#include "cmMessageType.h"
36
#include "cmOutputConverter.h"
37
#include "cmScanDepFormat.h"
38
#include "cmSourceFile.h"
39
#include "cmStringAlgorithms.h"
40
#include "cmSystemTools.h"
41
#include "cmTarget.h"
42
#include "cmTargetExport.h"
43
44
namespace {
45
46
struct TdiSourceInfo
47
{
48
  Json::Value Sources;
49
  Json::Value CxxModules;
50
};
51
52
TdiSourceInfo CollationInformationSources(cmGeneratorTarget const* gt,
53
                                          std::string const& config,
54
                                          cmDyndepGeneratorCallbacks const& cb)
55
0
{
56
0
  cm::GenEx::Context const context(gt->LocalGenerator, config);
57
0
  TdiSourceInfo info;
58
0
  cmTarget const* tgt = gt->Target;
59
0
  auto all_file_sets = tgt->GetAllFileSetNames();
60
0
  Json::Value& tdi_sources = info.Sources = Json::objectValue;
61
0
  Json::Value& tdi_cxx_module_info = info.CxxModules = Json::objectValue;
62
63
0
  enum class CompileType
64
0
  {
65
0
    ObjectAndBmi,
66
0
    BmiOnly,
67
0
  };
68
0
  std::map<std::string, std::pair<cmSourceFile const*, CompileType>> sf_map;
69
0
  {
70
0
    auto fill_sf_map = [gt, tgt, &sf_map](cmSourceFile const* sf,
71
0
                                          CompileType type) {
72
0
      auto full_path = sf->GetFullPath();
73
0
      if (full_path.empty()) {
74
0
        gt->Makefile->IssueMessage(
75
0
          MessageType::INTERNAL_ERROR,
76
0
          cmStrCat("Target \"", tgt->GetName(),
77
0
                   "\" has a full path-less source file."));
78
0
        return;
79
0
      }
80
0
      sf_map[full_path] = std::make_pair(sf, type);
81
0
    };
82
83
0
    std::vector<cmSourceFile const*> objectSources;
84
0
    gt->GetObjectSources(objectSources, config);
85
0
    for (auto const* sf : objectSources) {
86
0
      fill_sf_map(sf, CompileType::ObjectAndBmi);
87
0
    }
88
89
0
    std::vector<cmSourceFile const*> cxxModuleSources;
90
0
    gt->GetCxxModuleSources(cxxModuleSources, config);
91
0
    for (auto const* sf : cxxModuleSources) {
92
0
      fill_sf_map(sf, CompileType::BmiOnly);
93
0
    }
94
0
  }
95
96
0
  for (auto const& file_set_name : all_file_sets) {
97
0
    auto const* file_set = tgt->GetFileSet(file_set_name);
98
0
    if (!file_set) {
99
0
      gt->Makefile->IssueMessage(MessageType::INTERNAL_ERROR,
100
0
                                 cmStrCat("Target \"", tgt->GetName(),
101
0
                                          "\" is tracked to have file set \"",
102
0
                                          file_set_name,
103
0
                                          "\", but it was not found."));
104
0
      continue;
105
0
    }
106
0
    auto fs_type = file_set->GetType();
107
    // We only care about C++ module sources here.
108
0
    if (fs_type != "CXX_MODULES"_s) {
109
0
      continue;
110
0
    }
111
112
0
    auto fileEntries = file_set->CompileFileEntries();
113
0
    auto directoryEntries = file_set->CompileDirectoryEntries();
114
115
0
    auto directories =
116
0
      file_set->EvaluateDirectoryEntries(directoryEntries, context, gt);
117
0
    std::map<std::string, std::vector<std::string>> files_per_dirs;
118
0
    for (auto const& entry : fileEntries) {
119
0
      file_set->EvaluateFileEntry(directories, files_per_dirs, entry, context,
120
0
                                  gt);
121
0
    }
122
123
0
    Json::Value fs_dest = Json::nullValue;
124
0
    for (auto const& ig : gt->Makefile->GetInstallGenerators()) {
125
0
      if (auto const* fsg =
126
0
            dynamic_cast<cmInstallFileSetGenerator const*>(ig.get())) {
127
0
        if (fsg->GetTarget() == gt && fsg->GetFileSet() == file_set) {
128
0
          fs_dest = fsg->GetDestination(config);
129
0
          continue;
130
0
        }
131
0
      }
132
0
    }
133
134
    // Detect duplicate sources.
135
0
    std::set<std::string> visited_sources;
136
137
0
    for (auto const& files_per_dir : files_per_dirs) {
138
0
      for (auto const& file : files_per_dir.second) {
139
0
        auto const full_file = cmSystemTools::CollapseFullPath(file);
140
0
        auto lookup = sf_map.find(full_file);
141
0
        if (lookup == sf_map.end()) {
142
0
          if (visited_sources.count(full_file)) {
143
            // Duplicate source; raise an author warning.
144
0
            gt->Makefile->IssueMessage(
145
0
              MessageType::AUTHOR_WARNING,
146
0
              cmStrCat(
147
0
                "Target \"", tgt->GetName(), "\" has source file\n  ", file,
148
0
                "\nin a \"FILE_SET TYPE CXX_MODULES\" multiple times."));
149
0
            continue;
150
0
          }
151
0
          gt->Makefile->IssueMessage(
152
0
            MessageType::FATAL_ERROR,
153
0
            cmStrCat("Target \"", tgt->GetName(), "\" has source file\n  ",
154
0
                     file,
155
0
                     "\nin a \"FILE_SET TYPE CXX_MODULES\" but it is not "
156
0
                     "scheduled for compilation."));
157
0
          continue;
158
0
        }
159
0
        visited_sources.insert(full_file);
160
161
0
        auto const* sf = lookup->second.first;
162
0
        CompileType const ct = lookup->second.second;
163
164
0
        sf_map.erase(lookup);
165
166
0
        if (!sf) {
167
0
          gt->Makefile->IssueMessage(
168
0
            MessageType::INTERNAL_ERROR,
169
0
            cmStrCat("Target \"", tgt->GetName(), "\" has source file \"",
170
0
                     file, "\" which has not been tracked properly."));
171
0
          continue;
172
0
        }
173
174
0
        auto obj_path = ct == CompileType::ObjectAndBmi
175
0
          ? cb.ObjectFilePath(sf, config)
176
0
          : cb.BmiFilePath(sf, config);
177
0
        Json::Value& tdi_module_info = tdi_cxx_module_info[obj_path] =
178
0
          Json::objectValue;
179
180
0
        Json::Value& tdi_include_dirs =
181
0
          tdi_module_info["include-directories"] = Json::arrayValue;
182
0
        for (auto const& i : gt->GetIncludeDirectories(config, "CXX")) {
183
0
          tdi_include_dirs.append(i.Value);
184
0
        }
185
186
0
        Json::Value& tdi_defs = tdi_module_info["definitions"] =
187
0
          Json::arrayValue;
188
0
        for (auto const& i : gt->GetCompileDefinitions(config, "CXX")) {
189
0
          tdi_defs.append(i.Value);
190
0
        }
191
192
0
        Json::Value& tdi_opts = tdi_module_info["compile-options"] =
193
0
          Json::arrayValue;
194
0
        for (auto const& i : gt->GetCompileOptions(config, "CXX")) {
195
0
          tdi_opts.append(i.Value);
196
0
        }
197
198
0
        Json::Value& tdi_feats = tdi_module_info["compile-features"] =
199
0
          Json::arrayValue;
200
0
        for (auto const& i : gt->GetCompileFeatures(config)) {
201
0
          tdi_feats.append(i.Value);
202
0
        }
203
204
0
        tdi_module_info["source"] = full_file;
205
0
        tdi_module_info["bmi-only"] = ct == CompileType::BmiOnly;
206
0
        tdi_module_info["relative-directory"] = files_per_dir.first;
207
0
        tdi_module_info["name"] = file_set->GetName();
208
0
        tdi_module_info["type"] = file_set->GetType();
209
0
        tdi_module_info["visibility"] =
210
0
          std::string(cmFileSetVisibilityToName(file_set->GetVisibility()));
211
0
        tdi_module_info["destination"] = fs_dest;
212
0
      }
213
0
    }
214
0
  }
215
216
0
  for (auto const& sf_entry : sf_map) {
217
0
    CompileType const ct = sf_entry.second.second;
218
0
    if (ct == CompileType::BmiOnly) {
219
0
      continue;
220
0
    }
221
222
0
    auto const* sf = sf_entry.second.first;
223
0
    if (!gt->NeedDyndepForSource(sf->GetLanguage(), config, sf)) {
224
0
      continue;
225
0
    }
226
227
0
    auto full_file = cmSystemTools::CollapseFullPath(sf->GetFullPath());
228
0
    auto obj_path = cb.ObjectFilePath(sf, config);
229
0
    Json::Value& tdi_source_info = tdi_sources[obj_path] = Json::objectValue;
230
231
0
    tdi_source_info["source"] = full_file;
232
0
    tdi_source_info["language"] = sf->GetLanguage();
233
0
  }
234
235
0
  return info;
236
0
}
237
238
Json::Value CollationInformationDatabaseInfo(cmGeneratorTarget const* gt,
239
                                             std::string const& config)
240
0
{
241
0
  Json::Value db_info;
242
243
0
  auto db_path = gt->BuildDatabasePath("CXX", config);
244
0
  if (!db_path.empty()) {
245
0
    db_info["template-path"] = cmStrCat(db_path, ".in");
246
0
    db_info["output"] = db_path;
247
0
  }
248
249
0
  return db_info;
250
0
}
251
252
Json::Value CollationInformationBmiInstallation(cmGeneratorTarget const* gt,
253
                                                std::string const& config)
254
0
{
255
0
  cmInstallCxxModuleBmiGenerator const* bmi_gen = nullptr;
256
0
  for (auto const& ig : gt->Makefile->GetInstallGenerators()) {
257
0
    if (auto const* bmig =
258
0
          dynamic_cast<cmInstallCxxModuleBmiGenerator const*>(ig.get())) {
259
0
      if (bmig->GetTarget() == gt) {
260
0
        bmi_gen = bmig;
261
0
        continue;
262
0
      }
263
0
    }
264
0
  }
265
0
  if (bmi_gen) {
266
0
    Json::Value tdi_bmi_info = Json::objectValue;
267
268
0
    tdi_bmi_info["permissions"] = bmi_gen->GetFilePermissions();
269
0
    tdi_bmi_info["destination"] = bmi_gen->GetDestination(config);
270
0
    char const* msg_level = "";
271
0
    switch (bmi_gen->GetMessageLevel()) {
272
0
      case cmInstallGenerator::MessageDefault:
273
0
        break;
274
0
      case cmInstallGenerator::MessageAlways:
275
0
        msg_level = "MESSAGE_ALWAYS";
276
0
        break;
277
0
      case cmInstallGenerator::MessageLazy:
278
0
        msg_level = "MESSAGE_LAZY";
279
0
        break;
280
0
      case cmInstallGenerator::MessageNever:
281
0
        msg_level = "MESSAGE_NEVER";
282
0
        break;
283
0
    }
284
0
    tdi_bmi_info["message-level"] = msg_level;
285
0
    tdi_bmi_info["script-location"] = bmi_gen->GetScriptLocation(config);
286
287
0
    return tdi_bmi_info;
288
0
  }
289
0
  return Json::nullValue;
290
0
}
291
292
Json::Value CollationInformationExports(cmGeneratorTarget const* gt)
293
0
{
294
0
  Json::Value tdi_exports = Json::arrayValue;
295
0
  std::string export_name = gt->GetExportName();
296
0
  std::string fs_export_name = gt->GetFilesystemExportName();
297
298
0
  auto const& all_install_exports = gt->GetGlobalGenerator()->GetExportSets();
299
0
  for (auto const& exp : all_install_exports) {
300
    // Ignore exports sets which are not for this target.
301
0
    auto const& targets = exp.second.GetTargetExports();
302
0
    auto tgt_export =
303
0
      std::find_if(targets.begin(), targets.end(),
304
0
                   [gt](std::unique_ptr<cmTargetExport> const& te) {
305
0
                     return te->Target == gt;
306
0
                   });
307
0
    if (tgt_export == targets.end()) {
308
0
      continue;
309
0
    }
310
311
0
    auto const* installs = exp.second.GetInstallations();
312
0
    for (auto const* install : *installs) {
313
0
      Json::Value tdi_export_info = Json::objectValue;
314
315
0
      auto const& ns = install->GetNamespace();
316
0
      auto const& dest = install->GetDestination();
317
0
      auto const& cxxm_dir = install->GetCxxModuleDirectory();
318
0
      auto const& export_prefix = install->GetTempDir();
319
320
0
      tdi_export_info["namespace"] = ns;
321
0
      tdi_export_info["export-name"] = export_name;
322
0
      tdi_export_info["filesystem-export-name"] = fs_export_name;
323
0
      tdi_export_info["destination"] = dest;
324
0
      tdi_export_info["cxx-module-info-dir"] = cxxm_dir;
325
0
      tdi_export_info["export-prefix"] = export_prefix;
326
0
      tdi_export_info["install"] = true;
327
328
0
      tdi_exports.append(tdi_export_info);
329
0
    }
330
0
  }
331
332
0
  auto const& all_build_exports =
333
0
    gt->GetGlobalGenerator()->GetBuildExportSets();
334
0
  for (auto const& exp_entry : all_build_exports) {
335
0
    auto const* exp = exp_entry.second;
336
0
    std::vector<cmExportBuildFileGenerator::TargetExport> targets;
337
0
    exp->GetTargets(targets);
338
339
    // Ignore exports sets which are not for this target.
340
0
    auto const& name = gt->GetName();
341
0
    bool has_current_target =
342
0
      std::any_of(targets.begin(), targets.end(),
343
0
                  [name](cmExportBuildFileGenerator::TargetExport const& te) {
344
0
                    return te.Name == name;
345
0
                  });
346
0
    if (!has_current_target) {
347
0
      continue;
348
0
    }
349
350
0
    Json::Value tdi_export_info = Json::objectValue;
351
352
0
    auto const& ns = exp->GetNamespace();
353
0
    auto const& main_fn = exp->GetMainExportFileName();
354
0
    auto const& cxxm_dir = exp->GetCxxModuleDirectory();
355
0
    auto dest = cmsys::SystemTools::GetParentDirectory(main_fn);
356
0
    auto const& export_prefix =
357
0
      cmSystemTools::GetFilenamePath(exp->GetMainExportFileName());
358
359
0
    tdi_export_info["namespace"] = ns;
360
0
    tdi_export_info["export-name"] = export_name;
361
0
    tdi_export_info["filesystem-export-name"] = fs_export_name;
362
0
    tdi_export_info["destination"] = dest;
363
0
    tdi_export_info["cxx-module-info-dir"] = cxxm_dir;
364
0
    tdi_export_info["export-prefix"] = export_prefix;
365
0
    tdi_export_info["install"] = false;
366
367
0
    tdi_exports.append(tdi_export_info);
368
0
  }
369
370
0
  return tdi_exports;
371
0
}
372
373
}
374
375
void cmDyndepCollation::AddCollationInformation(
376
  Json::Value& tdi, cmGeneratorTarget const* gt, std::string const& config,
377
  cmDyndepGeneratorCallbacks const& cb)
378
0
{
379
0
  auto sourcesInfo = CollationInformationSources(gt, config, cb);
380
0
  tdi["sources"] = sourcesInfo.Sources;
381
0
  tdi["cxx-modules"] = sourcesInfo.CxxModules;
382
0
  tdi["database-info"] = CollationInformationDatabaseInfo(gt, config);
383
0
  tdi["bmi-installation"] = CollationInformationBmiInstallation(gt, config);
384
0
  tdi["exports"] = CollationInformationExports(gt);
385
0
  tdi["config"] = config;
386
0
}
387
388
struct SourceInfo
389
{
390
  std::string SourcePath;
391
  std::string Language;
392
};
393
394
struct CxxModuleFileSet
395
{
396
  std::string Name;
397
  bool BmiOnly = false;
398
  std::string RelativeDirectory;
399
  std::string SourcePath;
400
  std::string Type;
401
  cmFileSetVisibility Visibility = cmFileSetVisibility::Private;
402
  cm::optional<std::string> Destination;
403
  std::vector<std::string> IncludeDirectories;
404
  std::vector<std::string> Definitions;
405
  std::vector<std::string> CompileOptions;
406
  std::vector<std::string> CompileFeatures;
407
};
408
409
struct CxxModuleDatabaseInfo
410
{
411
  std::string TemplatePath;
412
  std::string Output;
413
};
414
415
struct CxxModuleBmiInstall
416
{
417
  std::string Component;
418
  std::string Destination;
419
  bool ExcludeFromAll;
420
  bool Optional;
421
  std::string Permissions;
422
  std::string MessageLevel;
423
  std::string ScriptLocation;
424
};
425
426
struct CxxModuleExport
427
{
428
  std::string Name;
429
  std::string FilesystemName;
430
  std::string Destination;
431
  std::string Prefix;
432
  std::string CxxModuleInfoDir;
433
  std::string Namespace;
434
  bool Install;
435
};
436
437
struct CxxModuleExportOutputHelper
438
{
439
  CxxModuleExport const* Export;
440
  std::unique_ptr<cmGeneratedFileStream> File;
441
  cmCxxModuleMetadata Manifest;
442
};
443
444
struct cmCxxModuleExportInfo
445
{
446
  std::map<std::string, SourceInfo> ObjectToSource;
447
  std::map<std::string, CxxModuleFileSet> ObjectToFileSet;
448
  cm::optional<CxxModuleDatabaseInfo> DatabaseInfo;
449
  cm::optional<CxxModuleBmiInstall> BmiInstallation;
450
  std::vector<CxxModuleExport> Exports;
451
  std::string Config;
452
};
453
454
void cmCxxModuleExportInfoDeleter::operator()(cmCxxModuleExportInfo* ei) const
455
0
{
456
0
  delete ei;
457
0
}
458
459
std::unique_ptr<cmCxxModuleExportInfo, cmCxxModuleExportInfoDeleter>
460
cmDyndepCollation::ParseExportInfo(Json::Value const& tdi)
461
0
{
462
0
  auto export_info =
463
0
    std::unique_ptr<cmCxxModuleExportInfo, cmCxxModuleExportInfoDeleter>(
464
0
      new cmCxxModuleExportInfo);
465
466
0
  export_info->Config = tdi["config"].asString();
467
0
  if (export_info->Config.empty()) {
468
0
    export_info->Config = "noconfig";
469
0
  }
470
0
  Json::Value const& tdi_exports = tdi["exports"];
471
0
  if (tdi_exports.isArray()) {
472
0
    for (auto const& tdi_export : tdi_exports) {
473
0
      CxxModuleExport exp;
474
0
      exp.Install = tdi_export["install"].asBool();
475
0
      exp.Name = tdi_export["export-name"].asString();
476
0
      exp.FilesystemName = tdi_export["filesystem-export-name"].asString();
477
0
      exp.Destination = tdi_export["destination"].asString();
478
0
      exp.Prefix = tdi_export["export-prefix"].asString();
479
0
      exp.CxxModuleInfoDir = tdi_export["cxx-module-info-dir"].asString();
480
0
      exp.Namespace = tdi_export["namespace"].asString();
481
482
0
      export_info->Exports.push_back(exp);
483
0
    }
484
0
  }
485
0
  auto const& database_info = tdi["database-info"];
486
0
  if (database_info.isObject()) {
487
0
    CxxModuleDatabaseInfo db_info;
488
489
0
    db_info.TemplatePath = database_info["template-path"].asString();
490
0
    db_info.Output = database_info["output"].asString();
491
492
0
    export_info->DatabaseInfo = db_info;
493
0
  }
494
0
  auto const& bmi_installation = tdi["bmi-installation"];
495
0
  if (bmi_installation.isObject()) {
496
0
    CxxModuleBmiInstall bmi_install;
497
498
0
    bmi_install.Component = bmi_installation["component"].asString();
499
0
    bmi_install.Destination = bmi_installation["destination"].asString();
500
0
    bmi_install.ExcludeFromAll = bmi_installation["exclude-from-all"].asBool();
501
0
    bmi_install.Optional = bmi_installation["optional"].asBool();
502
0
    bmi_install.Permissions = bmi_installation["permissions"].asString();
503
0
    bmi_install.MessageLevel = bmi_installation["message-level"].asString();
504
0
    bmi_install.ScriptLocation =
505
0
      bmi_installation["script-location"].asString();
506
507
0
    export_info->BmiInstallation = bmi_install;
508
0
  }
509
0
  Json::Value const& tdi_cxx_modules = tdi["cxx-modules"];
510
0
  if (tdi_cxx_modules.isObject()) {
511
0
    for (auto i = tdi_cxx_modules.begin(); i != tdi_cxx_modules.end(); ++i) {
512
0
      CxxModuleFileSet& fsi = export_info->ObjectToFileSet[i.key().asString()];
513
0
      auto const& tdi_cxx_module_info = *i;
514
0
      fsi.Name = tdi_cxx_module_info["name"].asString();
515
0
      fsi.BmiOnly = tdi_cxx_module_info["bmi-only"].asBool();
516
0
      fsi.RelativeDirectory =
517
0
        tdi_cxx_module_info["relative-directory"].asString();
518
0
      if (!fsi.RelativeDirectory.empty() &&
519
0
          fsi.RelativeDirectory.back() != '/') {
520
0
        fsi.RelativeDirectory = cmStrCat(fsi.RelativeDirectory, '/');
521
0
      }
522
0
      fsi.SourcePath = tdi_cxx_module_info["source"].asString();
523
0
      fsi.Type = tdi_cxx_module_info["type"].asString();
524
0
      fsi.Visibility = cmFileSetVisibilityFromName(
525
0
        tdi_cxx_module_info["visibility"].asString(), nullptr);
526
0
      auto const& tdi_fs_dest = tdi_cxx_module_info["destination"];
527
0
      if (tdi_fs_dest.isString()) {
528
0
        fsi.Destination = tdi_fs_dest.asString();
529
0
      }
530
0
      for (auto const& j : tdi_cxx_module_info["include-directories"]) {
531
0
        fsi.IncludeDirectories.push_back(j.asString());
532
0
      }
533
0
      for (auto const& j : tdi_cxx_module_info["definitions"]) {
534
0
        fsi.Definitions.push_back(j.asString());
535
0
      }
536
0
      for (auto const& j : tdi_cxx_module_info["compile-options"]) {
537
0
        fsi.CompileOptions.push_back(j.asString());
538
0
      }
539
0
      for (auto const& j : tdi_cxx_module_info["compile-features"]) {
540
0
        fsi.CompileFeatures.push_back(j.asString());
541
0
      }
542
0
    }
543
0
  }
544
0
  Json::Value const& tdi_sources = tdi["sources"];
545
0
  if (tdi_sources.isObject()) {
546
0
    for (auto i = tdi_sources.begin(); i != tdi_sources.end(); ++i) {
547
0
      SourceInfo& si = export_info->ObjectToSource[i.key().asString()];
548
0
      auto const& tdi_source = *i;
549
0
      si.SourcePath = tdi_source["source"].asString();
550
0
      si.Language = tdi_source["language"].asString();
551
0
    }
552
0
  }
553
554
0
  return export_info;
555
0
}
556
557
bool cmDyndepCollation::WriteDyndepMetadata(
558
  std::string const& lang, std::vector<cmScanDepInfo> const& objects,
559
  cmCxxModuleExportInfo const& export_info,
560
  cmDyndepMetadataCallbacks const& cb)
561
0
{
562
  // Only C++ supports any of the file-set or BMI installation considered
563
  // below.
564
0
  if (lang != "CXX"_s) {
565
0
    return true;
566
0
  }
567
568
0
  bool result = true;
569
570
  // Prepare the export information blocks.
571
0
  std::string const config_upper =
572
0
    cmSystemTools::UpperCase(export_info.Config);
573
0
  std::vector<CxxModuleExportOutputHelper> exports;
574
0
  for (auto const& exp : export_info.Exports) {
575
0
    CxxModuleExportOutputHelper exp_helper;
576
577
0
    std::string const export_dir =
578
0
      cmStrCat(exp.Prefix, '/', exp.CxxModuleInfoDir, '/');
579
0
    std::string const property_file_path =
580
0
      cmStrCat(export_dir, "target-"_s, exp.FilesystemName, '-',
581
0
               export_info.Config, ".cmake"_s);
582
0
    exp_helper.Manifest.MetadataFilePath =
583
0
      cmStrCat(exp.Destination, '/', exp.CxxModuleInfoDir, "/target-"_s,
584
0
               exp.FilesystemName, '-', export_info.Config, ".modules.json"_s);
585
586
0
    exp_helper.File =
587
0
      cm::make_unique<cmGeneratedFileStream>(property_file_path);
588
589
    // Set up the preamble.
590
0
    *exp_helper.File << "set_property(TARGET \"" << exp.Namespace << exp.Name
591
0
                     << "\"\n"
592
0
                        "  PROPERTY IMPORTED_CXX_MODULES_"
593
0
                     << config_upper << '\n';
594
595
0
    exp_helper.Export = &exp;
596
0
    exports.emplace_back(std::move(exp_helper));
597
0
  }
598
599
0
  std::unique_ptr<cmBuildDatabase> module_database;
600
0
  cmBuildDatabase::LookupTable build_database_lookup;
601
0
  if (export_info.DatabaseInfo) {
602
0
    module_database =
603
0
      cmBuildDatabase::Load(export_info.DatabaseInfo->TemplatePath);
604
0
    if (module_database) {
605
0
      build_database_lookup = module_database->GenerateLookupTable();
606
0
    } else {
607
0
      cmSystemTools::Error(
608
0
        cmStrCat("Failed to read the template build database ",
609
0
                 export_info.DatabaseInfo->TemplatePath));
610
0
      result = false;
611
0
    }
612
0
  }
613
614
0
  std::unique_ptr<cmGeneratedFileStream> bmi_install_script;
615
0
  if (export_info.BmiInstallation) {
616
0
    bmi_install_script = cm::make_unique<cmGeneratedFileStream>(
617
0
      export_info.BmiInstallation->ScriptLocation);
618
0
  }
619
620
0
  auto cmEscape = [](cm::string_view str) {
621
0
    return cmOutputConverter::EscapeForCMake(
622
0
      str, cmOutputConverter::WrapQuotes::NoWrap);
623
0
  };
624
0
  auto install_destination =
625
0
    [&cmEscape](std::string const& dest) -> std::pair<bool, std::string> {
626
0
    if (cmSystemTools::FileIsFullPath(dest)) {
627
0
      return std::make_pair(true, cmEscape(dest));
628
0
    }
629
0
    return std::make_pair(false,
630
0
                          cmStrCat("${_IMPORT_PREFIX}/", cmEscape(dest)));
631
0
  };
632
633
  // public/private requirement tracking.
634
0
  std::set<std::string> private_modules;
635
0
  std::map<std::string, std::set<std::string>> public_source_requires;
636
637
0
  for (cmScanDepInfo const& object : objects) {
638
    // Convert to forward slashes.
639
0
    auto output_path = object.PrimaryOutput;
640
#ifdef _WIN32
641
    cmSystemTools::ConvertToUnixSlashes(output_path);
642
#endif
643
644
0
    auto source_info_itr = export_info.ObjectToSource.find(output_path);
645
646
    // Update the module compilation database `requires` field if needed.
647
0
    if (source_info_itr != export_info.ObjectToSource.end()) {
648
0
      auto const& sourcePath = source_info_itr->second.SourcePath;
649
0
      auto bdb_entry = build_database_lookup.find(sourcePath);
650
0
      if (bdb_entry != build_database_lookup.end()) {
651
0
        bdb_entry->second->Requires.clear();
652
0
        for (auto const& req : object.Requires) {
653
0
          bdb_entry->second->Requires.push_back(req.LogicalName);
654
0
        }
655
0
      } else if (export_info.DatabaseInfo) {
656
0
        cmSystemTools::Error(
657
0
          cmStrCat("Failed to find module database entry for ", sourcePath));
658
0
        result = false;
659
0
      }
660
0
    }
661
662
    // Find the fileset for this object.
663
0
    auto fileset_info_itr = export_info.ObjectToFileSet.find(output_path);
664
0
    bool const has_provides = !object.Provides.empty();
665
0
    if (fileset_info_itr == export_info.ObjectToFileSet.end()) {
666
      // If it provides anything, it should have type `CXX_MODULES`
667
      // and be present.
668
0
      if (has_provides) {
669
        // Take the first module provided to provide context.
670
0
        auto const& provides = object.Provides[0];
671
0
        cmSystemTools::Error(
672
0
          cmStrCat("Output ", object.PrimaryOutput, " provides the `",
673
0
                   provides.LogicalName,
674
0
                   "` module but it is not found in a `FILE_SET` of type "
675
0
                   "`CXX_MODULES`"));
676
0
        result = false;
677
0
      }
678
679
      // This object file does not provide anything, so nothing more needs to
680
      // be done.
681
0
      continue;
682
0
    }
683
684
0
    auto const& file_set = fileset_info_itr->second;
685
686
    // Update the module compilation database `provides` field if needed.
687
0
    {
688
0
      auto bdb_entry = build_database_lookup.find(file_set.SourcePath);
689
0
      if (bdb_entry != build_database_lookup.end()) {
690
        // Clear the provides mapping; we will re-initialize it here.
691
0
        if (!object.Provides.empty()) {
692
0
          bdb_entry->second->Provides.clear();
693
0
        }
694
0
        for (auto const& prov : object.Provides) {
695
0
          auto bmiName = cb.ModuleFile(prov.LogicalName);
696
0
          if (bmiName) {
697
0
            bdb_entry->second->Provides[prov.LogicalName] = *bmiName;
698
0
          } else {
699
0
            cmSystemTools::Error(
700
0
              cmStrCat("Failed to find BMI location for ", prov.LogicalName));
701
0
            result = false;
702
0
          }
703
0
        }
704
0
        for (auto const& req : object.Requires) {
705
0
          bdb_entry->second->Requires.push_back(req.LogicalName);
706
0
        }
707
0
      } else if (export_info.DatabaseInfo) {
708
0
        cmSystemTools::Error(cmStrCat(
709
0
          "Failed to find module database entry for ", file_set.SourcePath));
710
0
        result = false;
711
0
      }
712
0
    }
713
714
    // Verify the fileset type for the object.
715
0
    if (file_set.Type == "CXX_MODULES"_s) {
716
0
      if (!has_provides) {
717
0
        cmSystemTools::Error(
718
0
          cmStrCat("Output ", object.PrimaryOutput,
719
0
                   " is of type `CXX_MODULES` but does not provide a module "
720
0
                   "interface unit or partition"));
721
0
        result = false;
722
0
        continue;
723
0
      }
724
0
    } else if (file_set.Type == "CXX_MODULE_HEADERS"_s) {
725
      // TODO.
726
0
    } else {
727
0
      if (has_provides) {
728
0
        auto const& provides = object.Provides[0];
729
0
        cmSystemTools::Error(cmStrCat(
730
0
          "Source ", file_set.SourcePath, " provides the `",
731
0
          provides.LogicalName, "` C++ module but is of type `", file_set.Type,
732
0
          "` module but must be of type `CXX_MODULES`"));
733
0
        result = false;
734
0
      }
735
736
      // Not a C++ module; ignore.
737
0
      continue;
738
0
    }
739
740
0
    if (!cmFileSetVisibilityIsForInterface(file_set.Visibility)) {
741
      // Nothing needs to be conveyed about non-`PUBLIC` modules.
742
0
      for (auto const& p : object.Provides) {
743
0
        private_modules.insert(p.LogicalName);
744
0
      }
745
0
      continue;
746
0
    }
747
748
    // The module is public. Record what it directly requires.
749
0
    {
750
0
      auto& reqs = public_source_requires[file_set.SourcePath];
751
0
      for (auto const& r : object.Requires) {
752
0
        reqs.insert(r.LogicalName);
753
0
      }
754
0
    }
755
756
    // Write out properties and install rules for any exports.
757
0
    for (auto const& p : object.Provides) {
758
0
      bool bmi_dest_is_abs = false;
759
0
      std::string bmi_destination;
760
0
      if (export_info.BmiInstallation) {
761
0
        auto dest =
762
0
          install_destination(export_info.BmiInstallation->Destination);
763
0
        bmi_dest_is_abs = dest.first;
764
0
        bmi_destination = cmStrCat(dest.second, '/');
765
0
      }
766
767
0
      std::string install_bmi_path;
768
0
      std::string build_bmi_path;
769
0
      auto m = cb.ModuleFile(p.LogicalName);
770
0
      if (m) {
771
0
        install_bmi_path = cmStrCat(
772
0
          bmi_destination, cmEscape(cmSystemTools::GetFilenameNameView(*m)));
773
0
        build_bmi_path = cmEscape(*m);
774
0
      }
775
776
0
      for (auto& exp : exports) {
777
0
        std::string iface_source;
778
0
        cmCxxModuleMetadata::ModuleData mod;
779
780
0
        if (exp.Export->Install && file_set.Destination) {
781
0
          auto rel =
782
0
            cmStrCat('/', file_set.RelativeDirectory,
783
0
                     cmSystemTools::GetFilenameNameView(file_set.SourcePath));
784
0
          iface_source = cmStrCat(
785
0
            install_destination(*file_set.Destination).second, cmEscape(rel));
786
0
          mod.SourcePath = cmStrCat(*file_set.Destination, rel);
787
0
        } else {
788
0
          iface_source = cmEscape(file_set.SourcePath);
789
0
          mod.SourcePath = file_set.SourcePath;
790
0
        }
791
792
0
        std::string bmi_path;
793
0
        if (exp.Export->Install && export_info.BmiInstallation) {
794
0
          bmi_path = install_bmi_path;
795
0
        } else if (!exp.Export->Install) {
796
0
          bmi_path = build_bmi_path;
797
0
        }
798
799
0
        if (iface_source.empty()) {
800
          // No destination for the C++ module source; ignore this property
801
          // value.
802
0
          continue;
803
0
        }
804
805
0
        mod.LogicalName = p.LogicalName;
806
0
        mod.IsInterface = p.IsInterface;
807
808
        // FIXME(#27565): Local arguments may refer to include directories or
809
        // other resources which live in unknown locations on the consuming
810
        // machine. There's no general-purpose way to solve this with module
811
        // manifests as currently specified. For now, forego serializing them
812
        // and rely on CPS to fill in the blanks.
813
814
0
        exp.Manifest.Modules.emplace_back(std::move(mod));
815
816
0
        *exp.File << "    \"" << cmEscape(p.LogicalName) << '='
817
0
                  << iface_source;
818
0
        if (!bmi_path.empty()) {
819
0
          *exp.File << ',' << bmi_path;
820
0
        }
821
0
        *exp.File << "\"\n";
822
0
      }
823
824
0
      if (bmi_install_script) {
825
0
        auto const& bmi_install = *export_info.BmiInstallation;
826
827
0
        *bmi_install_script << "if (CMAKE_INSTALL_COMPONENT STREQUAL \""
828
0
                            << cmEscape(bmi_install.Component) << '\"';
829
0
        if (!bmi_install.ExcludeFromAll) {
830
0
          *bmi_install_script << " OR NOT CMAKE_INSTALL_COMPONENT";
831
0
        }
832
0
        *bmi_install_script << ")\n";
833
0
        *bmi_install_script << "  file(INSTALL\n"
834
0
                               "    DESTINATION \"";
835
0
        if (!bmi_dest_is_abs) {
836
0
          *bmi_install_script << "${CMAKE_INSTALL_PREFIX}/";
837
0
        }
838
0
        *bmi_install_script << cmEscape(bmi_install.Destination)
839
0
                            << "\"\n"
840
0
                               "    TYPE FILE\n";
841
0
        if (bmi_install.Optional) {
842
0
          *bmi_install_script << "    OPTIONAL\n";
843
0
        }
844
0
        if (!bmi_install.MessageLevel.empty()) {
845
0
          *bmi_install_script << "    " << bmi_install.MessageLevel << "\n";
846
0
        }
847
0
        if (!bmi_install.Permissions.empty()) {
848
0
          *bmi_install_script << "    PERMISSIONS" << bmi_install.Permissions
849
0
                              << "\n";
850
0
        }
851
0
        *bmi_install_script << "    FILES \"" << *m << "\")\n";
852
0
        if (bmi_dest_is_abs) {
853
0
          *bmi_install_script
854
0
            << "  list(APPEND CMAKE_ABSOLUTE_DESTINATION_FILES\n"
855
0
               "    \""
856
0
            << cmEscape(cmSystemTools::GetFilenameNameView(*m))
857
0
            << "\")\n"
858
0
               "  if (CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION)\n"
859
0
               "    message(WARNING\n"
860
0
               "      \"ABSOLUTE path INSTALL DESTINATION : "
861
0
               "${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n"
862
0
               "  endif ()\n"
863
0
               "  if (CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION)\n"
864
0
               "    message(FATAL_ERROR\n"
865
0
               "      \"ABSOLUTE path INSTALL DESTINATION forbidden (by "
866
0
               "caller): ${CMAKE_ABSOLUTE_DESTINATION_FILES}\")\n"
867
0
               "  endif ()\n";
868
0
        }
869
0
        *bmi_install_script << "endif ()\n";
870
0
      }
871
0
    }
872
0
  }
873
874
0
  for (auto const& exp : exports) {
875
876
0
    cmCxxModuleMetadata::SaveToFile(
877
0
      cmStrCat(exp.Export->Prefix, '/', exp.Export->CxxModuleInfoDir,
878
0
               "/target-"_s, exp.Export->FilesystemName, '-',
879
0
               export_info.Config, ".modules.json"_s),
880
0
      exp.Manifest);
881
882
0
    *exp.File << ")\n";
883
0
  }
884
885
  // Check that public sources only require public modules.
886
0
  for (auto const& pub_reqs : public_source_requires) {
887
0
    for (auto const& req : pub_reqs.second) {
888
0
      if (private_modules.count(req)) {
889
0
        cmSystemTools::Error(cmStrCat(
890
0
          "Public C++ module source `", pub_reqs.first, "` requires the `",
891
0
          req, "` C++ module which is provided by a private source"));
892
0
        result = false;
893
0
      }
894
0
    }
895
0
  }
896
897
0
  if (module_database) {
898
0
    if (module_database->HasPlaceholderNames()) {
899
0
      cmSystemTools::Error(
900
0
        "Module compilation database still contains placeholders");
901
0
      result = false;
902
0
    } else {
903
0
      module_database->Write(export_info.DatabaseInfo->Output);
904
0
    }
905
0
  }
906
907
0
  return result;
908
0
}
909
910
bool cmDyndepCollation::IsObjectPrivate(
911
  std::string const& object, cmCxxModuleExportInfo const& export_info)
912
0
{
913
#ifdef _WIN32
914
  std::string output_path = object;
915
  cmSystemTools::ConvertToUnixSlashes(output_path);
916
#else
917
0
  std::string const& output_path = object;
918
0
#endif
919
0
  auto fileset_info_itr = export_info.ObjectToFileSet.find(output_path);
920
0
  if (fileset_info_itr == export_info.ObjectToFileSet.end()) {
921
0
    return false;
922
0
  }
923
0
  auto const& file_set = fileset_info_itr->second;
924
0
  return !cmFileSetVisibilityIsForInterface(file_set.Visibility);
925
0
}
926
927
bool cmDyndepCollation::IsBmiOnly(cmCxxModuleExportInfo const& exportInfo,
928
                                  std::string const& object)
929
0
{
930
#ifdef _WIN32
931
  auto object_path = object;
932
  cmSystemTools::ConvertToUnixSlashes(object_path);
933
#else
934
0
  auto const& object_path = object;
935
0
#endif
936
0
  auto fs = exportInfo.ObjectToFileSet.find(object_path);
937
0
  return (fs != exportInfo.ObjectToFileSet.end()) && fs->second.BmiOnly;
938
0
}