Coverage Report

Created: 2026-10-28 06:38

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