Merge topic 'curl-tls-verify'
[kiteware-cmake.git] / Source / cmCxxModuleMapper.cxx
blob4b2aec7c577567effd2b37cd58e2724658f77a8c
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCxxModuleMapper.h"
5 #include <cassert>
6 #include <cstddef>
7 #include <set>
8 #include <sstream>
9 #include <string>
10 #include <utility>
11 #include <vector>
13 #include <cm/string_view>
14 #include <cmext/string_view>
16 #include "cmScanDepFormat.h"
17 #include "cmStringAlgorithms.h"
18 #include "cmSystemTools.h"
20 CxxBmiLocation::CxxBmiLocation() = default;
22 CxxBmiLocation::CxxBmiLocation(std::string path)
23 : BmiLocation(std::move(path))
27 CxxBmiLocation CxxBmiLocation::Unknown()
29 return {};
32 CxxBmiLocation CxxBmiLocation::Private()
34 return { std::string{} };
37 CxxBmiLocation CxxBmiLocation::Known(std::string path)
39 return { std::move(path) };
42 bool CxxBmiLocation::IsKnown() const
44 return this->BmiLocation.has_value();
47 bool CxxBmiLocation::IsPrivate() const
49 if (auto const& loc = this->BmiLocation) {
50 return loc->empty();
52 return false;
55 std::string const& CxxBmiLocation::Location() const
57 if (auto const& loc = this->BmiLocation) {
58 return *loc;
60 static std::string empty;
61 return empty;
64 CxxBmiLocation CxxModuleLocations::BmiGeneratorPathForModule(
65 std::string const& logical_name) const
67 auto bmi_loc = this->BmiLocationForModule(logical_name);
68 if (bmi_loc.IsKnown() && !bmi_loc.IsPrivate()) {
69 bmi_loc =
70 CxxBmiLocation::Known(this->PathForGenerator(bmi_loc.Location()));
72 return bmi_loc;
75 namespace {
77 struct TransitiveUsage
79 TransitiveUsage(std::string name, std::string location, LookupMethod method)
80 : LogicalName(std::move(name))
81 , Location(std::move(location))
82 , Method(method)
86 std::string LogicalName;
87 std::string Location;
88 LookupMethod Method;
91 std::vector<TransitiveUsage> GetTransitiveUsages(
92 CxxModuleLocations const& loc, std::vector<cmSourceReqInfo> const& required,
93 CxxModuleUsage const& usages)
95 std::set<std::string> transitive_usage_directs;
96 std::set<std::string> transitive_usage_names;
98 std::vector<TransitiveUsage> all_usages;
100 for (auto const& r : required) {
101 auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
102 if (bmi_loc.IsKnown()) {
103 all_usages.emplace_back(r.LogicalName, bmi_loc.Location(), r.Method);
104 transitive_usage_directs.insert(r.LogicalName);
106 // Insert transitive usages.
107 auto transitive_usages = usages.Usage.find(r.LogicalName);
108 if (transitive_usages != usages.Usage.end()) {
109 transitive_usage_names.insert(transitive_usages->second.begin(),
110 transitive_usages->second.end());
115 for (auto const& transitive_name : transitive_usage_names) {
116 if (transitive_usage_directs.count(transitive_name)) {
117 continue;
120 auto module_ref = usages.Reference.find(transitive_name);
121 if (module_ref != usages.Reference.end()) {
122 all_usages.emplace_back(transitive_name, module_ref->second.Path,
123 module_ref->second.Method);
127 return all_usages;
130 std::string CxxModuleMapContentClang(CxxModuleLocations const& loc,
131 cmScanDepInfo const& obj,
132 CxxModuleUsage const& usages)
134 std::stringstream mm;
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 if (obj.Provides.size() > 1) {
139 return {};
142 // A series of flags which tell the compiler where to look for modules.
144 for (auto const& p : obj.Provides) {
145 auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
146 if (bmi_loc.IsKnown()) {
147 // Force the TU to be considered a C++ module source file regardless of
148 // extension.
149 mm << "-x c++-module\n";
151 mm << "-fmodule-output=" << bmi_loc.Location() << '\n';
152 break;
156 auto all_usages = GetTransitiveUsages(loc, obj.Requires, usages);
157 for (auto const& usage : all_usages) {
158 mm << "-fmodule-file=" << usage.LogicalName << '=' << usage.Location
159 << '\n';
162 return mm.str();
165 std::string CxxModuleMapContentGcc(CxxModuleLocations const& loc,
166 cmScanDepInfo const& obj)
168 std::stringstream mm;
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).
177 // Write the root directory to use for module paths.
178 mm << "$root " << loc.RootDirectory << '\n';
180 for (auto const& p : obj.Provides) {
181 auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
182 if (bmi_loc.IsKnown()) {
183 mm << p.LogicalName << ' ' << bmi_loc.Location() << '\n';
186 for (auto const& r : obj.Requires) {
187 auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
188 if (bmi_loc.IsKnown()) {
189 mm << r.LogicalName << ' ' << bmi_loc.Location() << '\n';
193 return mm.str();
196 std::string CxxModuleMapContentMsvc(CxxModuleLocations const& loc,
197 cmScanDepInfo const& obj,
198 CxxModuleUsage const& usages)
200 std::stringstream mm;
202 // A response file of `-reference NAME=PATH` arguments.
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 if (obj.Provides.size() > 1) {
207 return {};
210 auto flag_for_method = [](LookupMethod method) -> cm::static_string_view {
211 switch (method) {
212 case LookupMethod::ByName:
213 return "-reference"_s;
214 case LookupMethod::IncludeAngle:
215 return "-headerUnit:angle"_s;
216 case LookupMethod::IncludeQuote:
217 return "-headerUnit:quote"_s;
219 assert(false && "unsupported lookup method");
220 return ""_s;
223 for (auto const& p : obj.Provides) {
224 if (p.IsInterface) {
225 mm << "-interface\n";
226 } else {
227 mm << "-internalPartition\n";
230 auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
231 if (bmi_loc.IsKnown()) {
232 mm << "-ifcOutput " << bmi_loc.Location() << '\n';
236 auto all_usages = GetTransitiveUsages(loc, obj.Requires, usages);
237 for (auto const& usage : all_usages) {
238 auto flag = flag_for_method(usage.Method);
240 mm << flag << ' ' << usage.LogicalName << '=' << usage.Location << '\n';
243 return mm.str();
247 bool CxxModuleUsage::AddReference(std::string const& logical,
248 std::string const& loc, LookupMethod method)
250 auto r = this->Reference.find(logical);
251 if (r != this->Reference.end()) {
252 auto& ref = r->second;
254 if (ref.Path == loc && ref.Method == method) {
255 return true;
258 auto method_name = [](LookupMethod m) -> cm::static_string_view {
259 switch (m) {
260 case LookupMethod::ByName:
261 return "by-name"_s;
262 case LookupMethod::IncludeAngle:
263 return "include-angle"_s;
264 case LookupMethod::IncludeQuote:
265 return "include-quote"_s;
267 assert(false && "unsupported lookup method");
268 return ""_s;
271 cmSystemTools::Error(cmStrCat("Disagreement of the location of the '",
272 logical,
273 "' module. "
274 "Location A: '",
275 ref.Path, "' via ", method_name(ref.Method),
276 "; "
277 "Location B: '",
278 loc, "' via ", method_name(method), "."));
279 return false;
282 auto& ref = this->Reference[logical];
283 ref.Path = loc;
284 ref.Method = method;
286 return true;
289 cm::static_string_view CxxModuleMapExtension(
290 cm::optional<CxxModuleMapFormat> format)
292 if (format) {
293 switch (*format) {
294 case CxxModuleMapFormat::Clang:
295 return ".pcm"_s;
296 case CxxModuleMapFormat::Gcc:
297 return ".gcm"_s;
298 case CxxModuleMapFormat::Msvc:
299 return ".ifc"_s;
303 return ".bmi"_s;
306 std::set<std::string> CxxModuleUsageSeed(
307 CxxModuleLocations const& loc, std::vector<cmScanDepInfo> const& objects,
308 CxxModuleUsage& usages, bool& private_usage_found)
310 // Track inner usages to populate usages from internal bits.
312 // This is a map of modules that required some other module that was not
313 // found to those that were not found.
314 std::map<std::string, std::set<std::string>> internal_usages;
315 std::set<std::string> unresolved;
317 for (cmScanDepInfo const& object : objects) {
318 // Add references for each of the provided modules.
319 for (auto const& p : object.Provides) {
320 auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
321 if (bmi_loc.IsKnown()) {
322 // XXX(cxx-modules): How to support header units?
323 usages.AddReference(p.LogicalName, bmi_loc.Location(),
324 LookupMethod::ByName);
328 // For each requires, pull in what is required.
329 for (auto const& r : object.Requires) {
330 // Find the required name in the current target.
331 auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
332 if (bmi_loc.IsPrivate()) {
333 cmSystemTools::Error(
334 cmStrCat("Unable to use module '", r.LogicalName,
335 "' as it is 'PRIVATE' and therefore not accessible outside "
336 "of its owning target."));
337 private_usage_found = true;
338 continue;
341 // Find transitive usages.
342 auto transitive_usages = usages.Usage.find(r.LogicalName);
344 for (auto const& p : object.Provides) {
345 auto& this_usages = usages.Usage[p.LogicalName];
347 // Add the direct usage.
348 this_usages.insert(r.LogicalName);
350 if (transitive_usages == usages.Usage.end() ||
351 internal_usages.find(r.LogicalName) != internal_usages.end()) {
352 // Mark that we need to update transitive usages later.
353 if (bmi_loc.IsKnown()) {
354 internal_usages[p.LogicalName].insert(r.LogicalName);
356 } else {
357 // Add the transitive usage.
358 this_usages.insert(transitive_usages->second.begin(),
359 transitive_usages->second.end());
363 if (bmi_loc.IsKnown()) {
364 usages.AddReference(r.LogicalName, bmi_loc.Location(), r.Method);
369 // While we have internal usages to manage.
370 while (!internal_usages.empty()) {
371 size_t starting_size = internal_usages.size();
373 // For each internal usage.
374 for (auto usage = internal_usages.begin(); usage != internal_usages.end();
375 /* see end of loop */) {
376 auto& this_usages = usages.Usage[usage->first];
378 for (auto use = usage->second.begin(); use != usage->second.end();
379 /* see end of loop */) {
380 // Check if this required module uses other internal modules; defer
381 // if so.
382 if (internal_usages.count(*use)) {
383 // Advance the iterator.
384 ++use;
385 continue;
388 auto transitive_usages = usages.Usage.find(*use);
389 if (transitive_usages != usages.Usage.end()) {
390 this_usages.insert(transitive_usages->second.begin(),
391 transitive_usages->second.end());
394 // Remove the entry and advance the iterator.
395 use = usage->second.erase(use);
398 // Erase the entry if it doesn't have any remaining usages.
399 if (usage->second.empty()) {
400 usage = internal_usages.erase(usage);
401 } else {
402 ++usage;
406 // Check that at least one usage was resolved.
407 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 for (auto const& usage : internal_usages) {
411 unresolved.insert(usage.first);
413 break;
417 return unresolved;
420 std::string CxxModuleMapContent(CxxModuleMapFormat format,
421 CxxModuleLocations const& loc,
422 cmScanDepInfo const& obj,
423 CxxModuleUsage const& usages)
425 switch (format) {
426 case CxxModuleMapFormat::Clang:
427 return CxxModuleMapContentClang(loc, obj, usages);
428 case CxxModuleMapFormat::Gcc:
429 return CxxModuleMapContentGcc(loc, obj);
430 case CxxModuleMapFormat::Msvc:
431 return CxxModuleMapContentMsvc(loc, obj, usages);
434 assert(false);
435 return {};
438 CxxModuleMapMode CxxModuleMapOpenMode(CxxModuleMapFormat format)
440 switch (format) {
441 case CxxModuleMapFormat::Gcc:
442 return CxxModuleMapMode::Binary;
443 case CxxModuleMapFormat::Clang:
444 case CxxModuleMapFormat::Msvc:
445 return CxxModuleMapMode::Default;
448 assert(false);
449 return CxxModuleMapMode::Default;