summaryrefslogtreecommitdiff
path: root/Source/cmCxxModuleMapper.cxx
blob: 59bf4c715aea5594f3b566902c9f54a35b7db82c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmCxxModuleMapper.h"

#include <cassert>
#include <cstddef>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include <cm/string_view>
#include <cmext/string_view>

#include "cmScanDepFormat.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"

cm::optional<std::string> CxxModuleLocations::BmiGeneratorPathForModule(
  std::string const& logical_name) const
{
  if (auto l = this->BmiLocationForModule(logical_name)) {
    return this->PathForGenerator(std::move(*l));
  }
  return {};
}

namespace {

std::string CxxModuleMapContentClang(CxxModuleLocations const& loc,
                                     cmScanDepInfo const& obj)
{
  std::stringstream mm;

  // Clang's command line only supports a single output. If more than one is
  // expected, we cannot make a useful module map file.
  if (obj.Provides.size() > 1) {
    return {};
  }

  // A series of flags which tell the compiler where to look for modules.

  for (auto const& p : obj.Provides) {
    if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
      // Force the TU to be considered a C++ module source file regardless of
      // extension.
      mm << "-x c++-module\n";

      mm << "-fmodule-output=" << *bmi_loc << '\n';
      break;
    }
  }
  for (auto const& r : obj.Requires) {
    if (auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName)) {
      mm << "-fmodule-file=" << r.LogicalName << "=" << *bmi_loc << '\n';
    }
  }

  return mm.str();
}

std::string CxxModuleMapContentGcc(CxxModuleLocations const& loc,
                                   cmScanDepInfo const& obj)
{
  std::stringstream mm;

  // Documented in GCC's documentation. The format is a series of
  // lines with a module name and the associated filename separated
  // by spaces. The first line may use `$root` as the module name
  // to specify a "repository root". That is used to anchor any
  // relative paths present in the file (CMake should never
  // generate any).

  // Write the root directory to use for module paths.
  mm << "$root " << loc.RootDirectory << "\n";

  for (auto const& p : obj.Provides) {
    if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
      mm << p.LogicalName << ' ' << *bmi_loc << '\n';
    }
  }
  for (auto const& r : obj.Requires) {
    if (auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName)) {
      mm << r.LogicalName << ' ' << *bmi_loc << '\n';
    }
  }

  return mm.str();
}

std::string CxxModuleMapContentMsvc(CxxModuleLocations const& loc,
                                    cmScanDepInfo const& obj,
                                    CxxModuleUsage const& usages)
{
  std::stringstream mm;

  // A response file of `-reference NAME=PATH` arguments.

  // MSVC's command line only supports a single output. If more than one is
  // expected, we cannot make a useful module map file.
  if (obj.Provides.size() > 1) {
    return {};
  }

  auto flag_for_method = [](LookupMethod method) -> cm::static_string_view {
    switch (method) {
      case LookupMethod::ByName:
        return "-reference"_s;
      case LookupMethod::IncludeAngle:
        return "-headerUnit:angle"_s;
      case LookupMethod::IncludeQuote:
        return "-headerUnit:quote"_s;
    }
    assert(false && "unsupported lookup method");
    return ""_s;
  };

  for (auto const& p : obj.Provides) {
    if (p.IsInterface) {
      mm << "-interface\n";
    } else {
      mm << "-internalPartition\n";
    }

    if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
      mm << "-ifcOutput " << *bmi_loc << '\n';
    }
  }

  std::set<std::string> transitive_usage_directs;
  std::set<std::string> transitive_usage_names;

  for (auto const& r : obj.Requires) {
    if (auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName)) {
      auto flag = flag_for_method(r.Method);

      mm << flag << ' ' << r.LogicalName << '=' << *bmi_loc << "\n";
      transitive_usage_directs.insert(r.LogicalName);

      // Insert transitive usages.
      auto transitive_usages = usages.Usage.find(r.LogicalName);
      if (transitive_usages != usages.Usage.end()) {
        transitive_usage_names.insert(transitive_usages->second.begin(),
                                      transitive_usages->second.end());
      }
    }
  }

  for (auto const& transitive_name : transitive_usage_names) {
    if (transitive_usage_directs.count(transitive_name)) {
      continue;
    }

    auto module_ref = usages.Reference.find(transitive_name);
    if (module_ref != usages.Reference.end()) {
      auto flag = flag_for_method(module_ref->second.Method);
      mm << flag << ' ' << transitive_name << '=' << module_ref->second.Path
         << "\n";
    }
  }

  return mm.str();
}
}

bool CxxModuleUsage::AddReference(std::string const& logical,
                                  std::string const& loc, LookupMethod method)
{
  auto r = this->Reference.find(logical);
  if (r != this->Reference.end()) {
    auto& ref = r->second;

    if (ref.Path == loc && ref.Method == method) {
      return true;
    }

    auto method_name = [](LookupMethod m) -> cm::static_string_view {
      switch (m) {
        case LookupMethod::ByName:
          return "by-name"_s;
        case LookupMethod::IncludeAngle:
          return "include-angle"_s;
        case LookupMethod::IncludeQuote:
          return "include-quote"_s;
      }
      assert(false && "unsupported lookup method");
      return ""_s;
    };

    cmSystemTools::Error(cmStrCat("Disagreement of the location of the '",
                                  logical,
                                  "' module. "
                                  "Location A: '",
                                  ref.Path, "' via ", method_name(ref.Method),
                                  "; "
                                  "Location B: '",
                                  loc, "' via ", method_name(method), "."));
    return false;
  }

  auto& ref = this->Reference[logical];
  ref.Path = loc;
  ref.Method = method;

  return true;
}

cm::static_string_view CxxModuleMapExtension(
  cm::optional<CxxModuleMapFormat> format)
{
  if (format) {
    switch (*format) {
      case CxxModuleMapFormat::Clang:
        return ".pcm"_s;
      case CxxModuleMapFormat::Gcc:
        return ".gcm"_s;
      case CxxModuleMapFormat::Msvc:
        return ".ifc"_s;
    }
  }

  return ".bmi"_s;
}

std::set<std::string> CxxModuleUsageSeed(
  CxxModuleLocations const& loc, std::vector<cmScanDepInfo> const& objects,
  CxxModuleUsage& usages)
{
  // Track inner usages to populate usages from internal bits.
  //
  // This is a map of modules that required some other module that was not
  // found to those that were not found.
  std::map<std::string, std::set<std::string>> internal_usages;
  std::set<std::string> unresolved;

  for (cmScanDepInfo const& object : objects) {
    // Add references for each of the provided modules.
    for (auto const& p : object.Provides) {
      if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
        // XXX(cxx-modules): How to support header units?
        usages.AddReference(p.LogicalName, *bmi_loc, LookupMethod::ByName);
      }
    }

    // For each requires, pull in what is required.
    for (auto const& r : object.Requires) {
      // Find transitive usages.
      auto transitive_usages = usages.Usage.find(r.LogicalName);
      // Find the required name in the current target.
      auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);

      for (auto const& p : object.Provides) {
        auto& this_usages = usages.Usage[p.LogicalName];

        // Add the direct usage.
        this_usages.insert(r.LogicalName);

        // Add the transitive usage.
        if (transitive_usages != usages.Usage.end()) {
          this_usages.insert(transitive_usages->second.begin(),
                             transitive_usages->second.end());
        } else if (bmi_loc) {
          // Mark that we need to update transitive usages later.
          internal_usages[p.LogicalName].insert(r.LogicalName);
        }
      }

      if (bmi_loc) {
        usages.AddReference(r.LogicalName, *bmi_loc, r.Method);
      }
    }
  }

  // While we have internal usages to manage.
  while (!internal_usages.empty()) {
    size_t starting_size = internal_usages.size();

    // For each internal usage.
    for (auto usage = internal_usages.begin(); usage != internal_usages.end();
         /* see end of loop */) {
      auto& this_usages = usages.Usage[usage->first];

      for (auto use = usage->second.begin(); use != usage->second.end();
           /* see end of loop */) {
        // Check if this required module uses other internal modules; defer
        // if so.
        if (internal_usages.count(*use)) {
          // Advance the iterator.
          ++use;
          continue;
        }

        auto transitive_usages = usages.Usage.find(*use);
        if (transitive_usages != usages.Usage.end()) {
          this_usages.insert(transitive_usages->second.begin(),
                             transitive_usages->second.end());
        }

        // Remove the entry and advance the iterator.
        use = usage->second.erase(use);
      }

      // Erase the entry if it doesn't have any remaining usages.
      if (usage->second.empty()) {
        usage = internal_usages.erase(usage);
      } else {
        ++usage;
      }
    }

    // Check that at least one usage was resolved.
    if (starting_size == internal_usages.size()) {
      // Nothing could be resolved this loop; we have a cycle, so record the
      // cycle and exit.
      for (auto const& usage : internal_usages) {
        unresolved.insert(usage.first);
      }
      break;
    }
  }

  return unresolved;
}

std::string CxxModuleMapContent(CxxModuleMapFormat format,
                                CxxModuleLocations const& loc,
                                cmScanDepInfo const& obj,
                                CxxModuleUsage const& usages)
{
  switch (format) {
    case CxxModuleMapFormat::Clang:
      return CxxModuleMapContentClang(loc, obj);
    case CxxModuleMapFormat::Gcc:
      return CxxModuleMapContentGcc(loc, obj);
    case CxxModuleMapFormat::Msvc:
      return CxxModuleMapContentMsvc(loc, obj, usages);
  }

  assert(false);
  return {};
}