Coverage Report

Created: 2026-04-29 07:01

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