Coverage Report

Created: 2026-02-09 06:05

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 << '\n';
241
0
  }
242
243
0
  return mm.str();
244
0
}
245
}
246
247
bool CxxModuleUsage::AddReference(std::string const& logical,
248
                                  std::string const& loc, LookupMethod method)
249
0
{
250
0
  auto r = this->Reference.find(logical);
251
0
  if (r != this->Reference.end()) {
252
0
    auto& ref = r->second;
253
254
0
    if (ref.Path == loc && ref.Method == method) {
255
0
      return true;
256
0
    }
257
258
0
    auto method_name = [](LookupMethod m) -> cm::static_string_view {
259
0
      switch (m) {
260
0
        case LookupMethod::ByName:
261
0
          return "by-name"_s;
262
0
        case LookupMethod::IncludeAngle:
263
0
          return "include-angle"_s;
264
0
        case LookupMethod::IncludeQuote:
265
0
          return "include-quote"_s;
266
0
      }
267
0
      assert(false && "unsupported lookup method");
268
0
      return ""_s;
269
0
    };
270
271
0
    cmSystemTools::Error(cmStrCat("Disagreement of the location of the '",
272
0
                                  logical,
273
0
                                  "' module. "
274
0
                                  "Location A: '",
275
0
                                  ref.Path, "' via ", method_name(ref.Method),
276
0
                                  "; "
277
0
                                  "Location B: '",
278
0
                                  loc, "' via ", method_name(method), '.'));
279
0
    return false;
280
0
  }
281
282
0
  auto& ref = this->Reference[logical];
283
0
  ref.Path = loc;
284
0
  ref.Method = method;
285
286
0
  return true;
287
0
}
288
289
cm::static_string_view CxxModuleMapExtension(
290
  cm::optional<CxxModuleMapFormat> format)
291
0
{
292
0
  if (format) {
293
0
    switch (*format) {
294
0
      case CxxModuleMapFormat::Clang:
295
0
        return ".pcm"_s;
296
0
      case CxxModuleMapFormat::Gcc:
297
0
        return ".gcm"_s;
298
0
      case CxxModuleMapFormat::Msvc:
299
0
        return ".ifc"_s;
300
0
    }
301
0
  }
302
303
0
  return ".bmi"_s;
304
0
}
305
306
std::set<std::string> CxxModuleUsageSeed(
307
  CxxModuleLocations const& loc, std::vector<cmScanDepInfo> const& objects,
308
  CxxModuleUsage& usages, bool& private_usage_found)
309
0
{
310
  // Track inner usages to populate usages from internal bits.
311
  //
312
  // This is a map of modules that required some other module that was not
313
  // found to those that were not found.
314
0
  std::map<std::string, std::set<std::string>> internal_usages;
315
0
  std::set<std::string> unresolved;
316
317
0
  for (cmScanDepInfo const& object : objects) {
318
    // Add references for each of the provided modules.
319
0
    for (auto const& p : object.Provides) {
320
0
      auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
321
0
      if (bmi_loc.IsKnown()) {
322
        // XXX(cxx-modules): How to support header units?
323
0
        usages.AddReference(p.LogicalName, bmi_loc.Location(),
324
0
                            LookupMethod::ByName);
325
0
      }
326
0
    }
327
328
    // For each requires, pull in what is required.
329
0
    for (auto const& r : object.Requires) {
330
      // Find the required name in the current target.
331
0
      auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
332
0
      if (bmi_loc.IsPrivate()) {
333
0
        cmSystemTools::Error(
334
0
          cmStrCat("Unable to use module '", r.LogicalName,
335
0
                   "' as it is 'PRIVATE' and therefore not accessible outside "
336
0
                   "of its owning target."));
337
0
        private_usage_found = true;
338
0
        continue;
339
0
      }
340
341
      // Find transitive usages.
342
0
      auto transitive_usages = usages.Usage.find(r.LogicalName);
343
344
0
      for (auto const& p : object.Provides) {
345
0
        auto& this_usages = usages.Usage[p.LogicalName];
346
347
        // Add the direct usage.
348
0
        this_usages.insert(r.LogicalName);
349
350
0
        if (transitive_usages == usages.Usage.end() ||
351
0
            internal_usages.find(r.LogicalName) != internal_usages.end()) {
352
          // Mark that we need to update transitive usages later.
353
0
          if (bmi_loc.IsKnown()) {
354
0
            internal_usages[p.LogicalName].insert(r.LogicalName);
355
0
          }
356
0
        } else {
357
          // Add the transitive usage.
358
0
          this_usages.insert(transitive_usages->second.begin(),
359
0
                             transitive_usages->second.end());
360
0
        }
361
0
      }
362
363
0
      if (bmi_loc.IsKnown()) {
364
0
        usages.AddReference(r.LogicalName, bmi_loc.Location(), r.Method);
365
0
      }
366
0
    }
367
0
  }
368
369
  // While we have internal usages to manage.
370
0
  while (!internal_usages.empty()) {
371
0
    size_t starting_size = internal_usages.size();
372
373
    // For each internal usage.
374
0
    for (auto usage = internal_usages.begin(); usage != internal_usages.end();
375
0
         /* see end of loop */) {
376
0
      auto& this_usages = usages.Usage[usage->first];
377
378
0
      for (auto use = usage->second.begin(); use != usage->second.end();
379
0
           /* see end of loop */) {
380
        // Check if this required module uses other internal modules; defer
381
        // if so.
382
0
        if (internal_usages.count(*use)) {
383
          // Advance the iterator.
384
0
          ++use;
385
0
          continue;
386
0
        }
387
388
0
        auto transitive_usages = usages.Usage.find(*use);
389
0
        if (transitive_usages != usages.Usage.end()) {
390
0
          this_usages.insert(transitive_usages->second.begin(),
391
0
                             transitive_usages->second.end());
392
0
        }
393
394
        // Remove the entry and advance the iterator.
395
0
        use = usage->second.erase(use);
396
0
      }
397
398
      // Erase the entry if it doesn't have any remaining usages.
399
0
      if (usage->second.empty()) {
400
0
        usage = internal_usages.erase(usage);
401
0
      } else {
402
0
        ++usage;
403
0
      }
404
0
    }
405
406
    // Check that at least one usage was resolved.
407
0
    if (starting_size == internal_usages.size()) {
408
      // Nothing could be resolved this loop; we have a cycle, so record the
409
      // cycle and exit.
410
0
      for (auto const& usage : internal_usages) {
411
0
        unresolved.insert(usage.first);
412
0
      }
413
0
      break;
414
0
    }
415
0
  }
416
417
0
  return unresolved;
418
0
}
419
420
std::string CxxModuleMapContent(CxxModuleMapFormat format,
421
                                CxxModuleLocations const& loc,
422
                                cmScanDepInfo const& obj,
423
                                CxxModuleUsage const& usages)
424
0
{
425
0
  switch (format) {
426
0
    case CxxModuleMapFormat::Clang:
427
0
      return CxxModuleMapContentClang(loc, obj, usages);
428
0
    case CxxModuleMapFormat::Gcc:
429
0
      return CxxModuleMapContentGcc(loc, obj);
430
0
    case CxxModuleMapFormat::Msvc:
431
0
      return CxxModuleMapContentMsvc(loc, obj, usages);
432
0
  }
433
434
0
  assert(false);
435
0
  return {};
436
0
}
437
438
CxxModuleMapMode CxxModuleMapOpenMode(CxxModuleMapFormat format)
439
0
{
440
0
  switch (format) {
441
0
    case CxxModuleMapFormat::Gcc:
442
0
      return CxxModuleMapMode::Binary;
443
0
    case CxxModuleMapFormat::Clang:
444
0
    case CxxModuleMapFormat::Msvc:
445
0
      return CxxModuleMapMode::Default;
446
0
  }
447
448
0
  assert(false);
449
0
  return CxxModuleMapMode::Default;
450
0
}