Coverage Report

Created: 2026-03-12 06:35

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