Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmCxxModuleMapper.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmCxxModuleMapper.h"
4
5
#include <cstddef>
6
#include <set>
7
#include <string>
8
#include <utility>
9
#include <vector>
10
11
#include <cmext/string_view>
12
13
#include "cmScanDepFormat.h"
14
#include "cmStringAlgorithms.h"
15
#include "cmSystemTools.h"
16
#include "cmUnreachable.h"
17
18
0
CxxBmiLocation::CxxBmiLocation() = default;
19
20
CxxBmiLocation::CxxBmiLocation(std::string path)
21
0
  : BmiLocation(std::move(path))
22
0
{
23
0
}
24
25
CxxBmiLocation CxxBmiLocation::Unknown()
26
0
{
27
0
  return {};
28
0
}
29
30
CxxBmiLocation CxxBmiLocation::Private()
31
0
{
32
0
  return { std::string{} };
33
0
}
34
35
CxxBmiLocation CxxBmiLocation::Known(std::string path)
36
0
{
37
0
  return { std::move(path) };
38
0
}
39
40
bool CxxBmiLocation::IsKnown() const
41
0
{
42
0
  return this->BmiLocation.has_value();
43
0
}
44
45
bool CxxBmiLocation::IsPrivate() const
46
0
{
47
0
  if (auto const& loc = this->BmiLocation) {
48
0
    return loc->empty();
49
0
  }
50
0
  return false;
51
0
}
52
53
std::string const& CxxBmiLocation::Location() const
54
0
{
55
0
  if (auto const& loc = this->BmiLocation) {
56
0
    return *loc;
57
0
  }
58
0
  static std::string empty;
59
0
  return empty;
60
0
}
61
62
CxxBmiLocation CxxModuleLocations::BmiGeneratorPathForModule(
63
  std::string const& logical_name) const
64
0
{
65
0
  auto bmi_loc = this->BmiLocationForModule(logical_name);
66
0
  if (bmi_loc.IsKnown() && !bmi_loc.IsPrivate()) {
67
0
    bmi_loc =
68
0
      CxxBmiLocation::Known(this->PathForGenerator(bmi_loc.Location()));
69
0
  }
70
0
  return bmi_loc;
71
0
}
72
73
namespace {
74
75
struct TransitiveUsage
76
{
77
  TransitiveUsage(std::string name, std::string location, LookupMethod method)
78
0
    : LogicalName(std::move(name))
79
0
    , Location(std::move(location))
80
0
    , Method(method)
81
0
  {
82
0
  }
83
84
  std::string LogicalName;
85
  std::string Location;
86
  LookupMethod Method;
87
};
88
89
std::vector<TransitiveUsage> GetTransitiveUsages(
90
  CxxModuleLocations const& loc, std::vector<cmSourceReqInfo> const& required,
91
  CxxModuleUsage const& usages)
92
0
{
93
0
  std::set<std::string> transitive_usage_directs;
94
0
  std::set<std::string> transitive_usage_names;
95
96
0
  std::vector<TransitiveUsage> all_usages;
97
98
0
  for (auto const& r : required) {
99
0
    auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
100
0
    if (bmi_loc.IsKnown()) {
101
0
      all_usages.emplace_back(r.LogicalName, bmi_loc.Location(), r.Method);
102
0
      transitive_usage_directs.insert(r.LogicalName);
103
104
      // Insert transitive usages.
105
0
      auto transitive_usages = usages.Usage.find(r.LogicalName);
106
0
      if (transitive_usages != usages.Usage.end()) {
107
0
        transitive_usage_names.insert(transitive_usages->second.begin(),
108
0
                                      transitive_usages->second.end());
109
0
      }
110
0
    }
111
0
  }
112
113
0
  for (auto const& transitive_name : transitive_usage_names) {
114
0
    if (transitive_usage_directs.count(transitive_name)) {
115
0
      continue;
116
0
    }
117
118
0
    auto module_ref = usages.Reference.find(transitive_name);
119
0
    if (module_ref != usages.Reference.end()) {
120
0
      all_usages.emplace_back(transitive_name, module_ref->second.Path,
121
0
                              module_ref->second.Method);
122
0
    }
123
0
  }
124
125
0
  return all_usages;
126
0
}
127
128
std::string CxxModuleMapContentClang(CxxModuleLocations const& loc,
129
                                     cmScanDepInfo const& obj,
130
                                     CxxModuleUsage const& usages)
131
0
{
132
  // Clang's command line only supports a single output. If more than one is
133
  // expected, we cannot make a useful module map file.
134
0
  if (obj.Provides.size() > 1) {
135
0
    return {};
136
0
  }
137
138
  // A series of flags which tell the compiler where to look for modules.
139
140
0
  std::string mm;
141
0
  for (auto const& p : obj.Provides) {
142
0
    auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
143
0
    if (bmi_loc.IsKnown()) {
144
      // Force the TU to be considered a C++ module source file regardless of
145
      // extension.
146
0
      mm = cmStrCat(std::move(mm),
147
0
                    "-x c++-module\n"
148
0
                    "-fmodule-output=\"",
149
0
                    bmi_loc.Location(), "\"\n");
150
0
      break;
151
0
    }
152
0
  }
153
154
0
  auto all_usages = GetTransitiveUsages(loc, obj.Requires, usages);
155
0
  for (auto const& usage : all_usages) {
156
0
    mm = cmStrCat(std::move(mm), "-fmodule-file=\"", usage.LogicalName, '=',
157
0
                  usage.Location, "\"\n");
158
0
  }
159
160
0
  return mm;
161
0
}
162
163
std::string CxxModuleMapContentGcc(CxxModuleLocations const& loc,
164
                                   cmScanDepInfo const& obj)
165
0
{
166
  // Documented in GCC's documentation. The format is a series of
167
  // lines with a module name and the associated filename separated
168
  // by spaces. The first line may use `$root` as the module name
169
  // to specify a "repository root". That is used to anchor any
170
  // relative paths present in the file (CMake should never
171
  // generate any).
172
173
  // Write the root directory to use for module paths.
174
0
  std::string mm = cmStrCat("$root ", loc.RootDirectory, '\n');
175
176
0
  for (auto const& p : obj.Provides) {
177
0
    auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
178
0
    if (bmi_loc.IsKnown()) {
179
0
      mm =
180
0
        cmStrCat(std::move(mm), p.LogicalName, ' ', bmi_loc.Location(), '\n');
181
0
    }
182
0
  }
183
0
  for (auto const& r : obj.Requires) {
184
0
    auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
185
0
    if (bmi_loc.IsKnown()) {
186
0
      mm =
187
0
        cmStrCat(std::move(mm), r.LogicalName, ' ', bmi_loc.Location(), '\n');
188
0
    }
189
0
  }
190
191
0
  return mm;
192
0
}
193
194
std::string CxxModuleMapContentMsvc(CxxModuleLocations const& loc,
195
                                    cmScanDepInfo const& obj,
196
                                    CxxModuleUsage const& usages)
197
0
{
198
  // A response file of `-reference NAME=PATH` arguments.
199
200
  // MSVC's command line only supports a single output. If more than one is
201
  // expected, we cannot make a useful module map file.
202
0
  if (obj.Provides.size() > 1) {
203
0
    return {};
204
0
  }
205
206
0
  auto flag_for_method = [](LookupMethod method) -> cm::static_string_view {
207
0
    switch (method) {
208
0
      case LookupMethod::ByName:
209
0
        return "-reference"_s;
210
0
      case LookupMethod::IncludeAngle:
211
0
        return "-headerUnit:angle"_s;
212
0
      case LookupMethod::IncludeQuote:
213
0
        return "-headerUnit:quote"_s;
214
0
    }
215
0
    CM_UNREACHABLE;
216
0
    return ""_s;
217
0
  };
218
219
0
  std::string mm;
220
0
  for (auto const& p : obj.Provides) {
221
0
    if (p.IsInterface) {
222
0
      mm += "-interface\n";
223
0
    } else {
224
0
      mm += "-internalPartition\n";
225
0
    }
226
227
0
    auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
228
0
    if (bmi_loc.IsKnown()) {
229
0
      mm =
230
0
        cmStrCat(std::move(mm), "-ifcOutput \"", bmi_loc.Location(), "\"\n");
231
0
    }
232
0
  }
233
234
0
  auto all_usages = GetTransitiveUsages(loc, obj.Requires, usages);
235
0
  for (auto const& usage : all_usages) {
236
0
    auto flag = flag_for_method(usage.Method);
237
238
0
    mm = cmStrCat(std::move(mm), flag, " \"", usage.LogicalName, '=',
239
0
                  usage.Location, "\"\n");
240
0
  }
241
242
0
  return mm;
243
0
}
244
}
245
246
bool CxxModuleUsage::AddReference(std::string const& logical,
247
                                  std::string const& loc, LookupMethod method)
248
0
{
249
0
  auto r = this->Reference.find(logical);
250
0
  if (r != this->Reference.end()) {
251
0
    auto& ref = r->second;
252
253
0
    if (ref.Path == loc && ref.Method == method) {
254
0
      return true;
255
0
    }
256
257
0
    auto method_name = [](LookupMethod m) -> cm::static_string_view {
258
0
      switch (m) {
259
0
        case LookupMethod::ByName:
260
0
          return "by-name"_s;
261
0
        case LookupMethod::IncludeAngle:
262
0
          return "include-angle"_s;
263
0
        case LookupMethod::IncludeQuote:
264
0
          return "include-quote"_s;
265
0
      }
266
0
      CM_UNREACHABLE;
267
0
      return ""_s;
268
0
    };
269
270
0
    cmSystemTools::Error(cmStrCat("Disagreement of the location of the '",
271
0
                                  logical,
272
0
                                  "' module. "
273
0
                                  "Location A: '",
274
0
                                  ref.Path, "' via ", method_name(ref.Method),
275
0
                                  "; "
276
0
                                  "Location B: '",
277
0
                                  loc, "' via ", method_name(method), '.'));
278
0
    return false;
279
0
  }
280
281
0
  auto& ref = this->Reference[logical];
282
0
  ref.Path = loc;
283
0
  ref.Method = method;
284
285
0
  return true;
286
0
}
287
288
cm::static_string_view CxxModuleMapExtension(
289
  cm::optional<CxxModuleMapFormat> format)
290
0
{
291
0
  if (format) {
292
0
    switch (*format) {
293
0
      case CxxModuleMapFormat::Clang:
294
0
        return ".pcm"_s;
295
0
      case CxxModuleMapFormat::Gcc:
296
0
        return ".gcm"_s;
297
0
      case CxxModuleMapFormat::Msvc:
298
0
        return ".ifc"_s;
299
0
    }
300
0
  }
301
302
0
  return ".bmi"_s;
303
0
}
304
305
std::set<std::string> CxxModuleUsageSeed(
306
  CxxModuleLocations const& loc, std::vector<cmScanDepInfo> const& objects,
307
  CxxModuleUsage& usages, bool& private_usage_found)
308
0
{
309
  // Track inner usages to populate usages from internal bits.
310
  //
311
  // This is a map of modules that required some other module that was not
312
  // found to those that were not found.
313
0
  std::map<std::string, std::set<std::string>> internal_usages;
314
0
  std::set<std::string> unresolved;
315
316
0
  for (cmScanDepInfo const& object : objects) {
317
    // Add references for each of the provided modules.
318
0
    for (auto const& p : object.Provides) {
319
0
      auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
320
0
      if (bmi_loc.IsKnown()) {
321
        // XXX(cxx-modules): How to support header units?
322
0
        usages.AddReference(p.LogicalName, bmi_loc.Location(),
323
0
                            LookupMethod::ByName);
324
0
      }
325
0
    }
326
327
    // For each requires, pull in what is required.
328
0
    for (auto const& r : object.Requires) {
329
      // Find the required name in the current target.
330
0
      auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
331
0
      if (bmi_loc.IsPrivate()) {
332
0
        cmSystemTools::Error(
333
0
          cmStrCat("Unable to use module '", r.LogicalName,
334
0
                   "' as it is 'PRIVATE' and therefore not accessible outside "
335
0
                   "of its owning target."));
336
0
        private_usage_found = true;
337
0
        continue;
338
0
      }
339
340
      // Find transitive usages.
341
0
      auto transitive_usages = usages.Usage.find(r.LogicalName);
342
343
0
      for (auto const& p : object.Provides) {
344
0
        auto& this_usages = usages.Usage[p.LogicalName];
345
346
        // Add the direct usage.
347
0
        this_usages.insert(r.LogicalName);
348
349
0
        if (transitive_usages == usages.Usage.end() ||
350
0
            internal_usages.find(r.LogicalName) != internal_usages.end()) {
351
          // Mark that we need to update transitive usages later.
352
0
          if (bmi_loc.IsKnown()) {
353
0
            internal_usages[p.LogicalName].insert(r.LogicalName);
354
0
          }
355
0
        } else {
356
          // Add the transitive usage.
357
0
          this_usages.insert(transitive_usages->second.begin(),
358
0
                             transitive_usages->second.end());
359
0
        }
360
0
      }
361
362
0
      if (bmi_loc.IsKnown()) {
363
0
        usages.AddReference(r.LogicalName, bmi_loc.Location(), r.Method);
364
0
      }
365
0
    }
366
0
  }
367
368
  // While we have internal usages to manage.
369
0
  while (!internal_usages.empty()) {
370
0
    size_t starting_size = internal_usages.size();
371
372
    // For each internal usage.
373
0
    for (auto usage = internal_usages.begin(); usage != internal_usages.end();
374
0
         /* see end of loop */) {
375
0
      auto& this_usages = usages.Usage[usage->first];
376
377
0
      for (auto use = usage->second.begin(); use != usage->second.end();
378
0
           /* see end of loop */) {
379
        // Check if this required module uses other internal modules; defer
380
        // if so.
381
0
        if (internal_usages.count(*use)) {
382
          // Advance the iterator.
383
0
          ++use;
384
0
          continue;
385
0
        }
386
387
0
        auto transitive_usages = usages.Usage.find(*use);
388
0
        if (transitive_usages != usages.Usage.end()) {
389
0
          this_usages.insert(transitive_usages->second.begin(),
390
0
                             transitive_usages->second.end());
391
0
        }
392
393
        // Remove the entry and advance the iterator.
394
0
        use = usage->second.erase(use);
395
0
      }
396
397
      // Erase the entry if it doesn't have any remaining usages.
398
0
      if (usage->second.empty()) {
399
0
        usage = internal_usages.erase(usage);
400
0
      } else {
401
0
        ++usage;
402
0
      }
403
0
    }
404
405
    // Check that at least one usage was resolved.
406
0
    if (starting_size == internal_usages.size()) {
407
      // Nothing could be resolved this loop; we have a cycle, so record the
408
      // cycle and exit.
409
0
      for (auto const& usage : internal_usages) {
410
0
        unresolved.insert(usage.first);
411
0
      }
412
0
      break;
413
0
    }
414
0
  }
415
416
0
  return unresolved;
417
0
}
418
419
std::string CxxModuleMapContent(CxxModuleMapFormat format,
420
                                CxxModuleLocations const& loc,
421
                                cmScanDepInfo const& obj,
422
                                CxxModuleUsage const& usages)
423
0
{
424
0
  switch (format) {
425
0
    case CxxModuleMapFormat::Clang:
426
0
      return CxxModuleMapContentClang(loc, obj, usages);
427
0
    case CxxModuleMapFormat::Gcc:
428
0
      return CxxModuleMapContentGcc(loc, obj);
429
0
    case CxxModuleMapFormat::Msvc:
430
0
      return CxxModuleMapContentMsvc(loc, obj, usages);
431
0
  }
432
433
0
  CM_UNREACHABLE;
434
0
  return {};
435
0
}
436
437
CxxModuleMapMode CxxModuleMapOpenMode(CxxModuleMapFormat format)
438
0
{
439
0
  switch (format) {
440
0
    case CxxModuleMapFormat::Gcc:
441
0
      return CxxModuleMapMode::Binary;
442
0
    case CxxModuleMapFormat::Clang:
443
0
    case CxxModuleMapFormat::Msvc:
444
0
      return CxxModuleMapMode::Default;
445
0
  }
446
447
0
  CM_UNREACHABLE;
448
0
  return CxxModuleMapMode::Default;
449
0
}