Coverage Report

Created: 2026-03-12 06:35

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