| /* 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" |
| |
| CxxBmiLocation::CxxBmiLocation() = default; |
| |
| CxxBmiLocation::CxxBmiLocation(std::string path) |
| : BmiLocation(std::move(path)) |
| { |
| } |
| |
| CxxBmiLocation CxxBmiLocation::Unknown() |
| { |
| return {}; |
| } |
| |
| CxxBmiLocation CxxBmiLocation::Private() |
| { |
| return { std::string{} }; |
| } |
| |
| CxxBmiLocation CxxBmiLocation::Known(std::string path) |
| { |
| return { std::move(path) }; |
| } |
| |
| bool CxxBmiLocation::IsKnown() const |
| { |
| return this->BmiLocation.has_value(); |
| } |
| |
| bool CxxBmiLocation::IsPrivate() const |
| { |
| if (auto const& loc = this->BmiLocation) { |
| return loc->empty(); |
| } |
| return false; |
| } |
| |
| std::string const& CxxBmiLocation::Location() const |
| { |
| if (auto const& loc = this->BmiLocation) { |
| return *loc; |
| } |
| static std::string empty; |
| return empty; |
| } |
| |
| CxxBmiLocation CxxModuleLocations::BmiGeneratorPathForModule( |
| std::string const& logical_name) const |
| { |
| auto bmi_loc = this->BmiLocationForModule(logical_name); |
| if (bmi_loc.IsKnown() && !bmi_loc.IsPrivate()) { |
| bmi_loc = |
| CxxBmiLocation::Known(this->PathForGenerator(bmi_loc.Location())); |
| } |
| return bmi_loc; |
| } |
| |
| 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) { |
| auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName); |
| if (bmi_loc.IsKnown()) { |
| // Force the TU to be considered a C++ module source file regardless of |
| // extension. |
| mm << "-x c++-module\n"; |
| |
| mm << "-fmodule-output=" << bmi_loc.Location() << '\n'; |
| break; |
| } |
| } |
| for (auto const& r : obj.Requires) { |
| auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName); |
| if (bmi_loc.IsKnown()) { |
| mm << "-fmodule-file=" << r.LogicalName << "=" << bmi_loc.Location() |
| << '\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) { |
| auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName); |
| if (bmi_loc.IsKnown()) { |
| mm << p.LogicalName << ' ' << bmi_loc.Location() << '\n'; |
| } |
| } |
| for (auto const& r : obj.Requires) { |
| auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName); |
| if (bmi_loc.IsKnown()) { |
| mm << r.LogicalName << ' ' << bmi_loc.Location() << '\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"; |
| } |
| |
| auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName); |
| if (bmi_loc.IsKnown()) { |
| mm << "-ifcOutput " << bmi_loc.Location() << '\n'; |
| } |
| } |
| |
| std::set<std::string> transitive_usage_directs; |
| std::set<std::string> transitive_usage_names; |
| |
| for (auto const& r : obj.Requires) { |
| auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName); |
| if (bmi_loc.IsKnown()) { |
| auto flag = flag_for_method(r.Method); |
| |
| mm << flag << ' ' << r.LogicalName << '=' << bmi_loc.Location() << "\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) { |
| auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName); |
| if (bmi_loc.IsKnown()) { |
| // XXX(cxx-modules): How to support header units? |
| usages.AddReference(p.LogicalName, bmi_loc.Location(), |
| LookupMethod::ByName); |
| } |
| } |
| |
| // For each requires, pull in what is required. |
| for (auto const& r : object.Requires) { |
| // Find the required name in the current target. |
| auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName); |
| if (bmi_loc.IsPrivate()) { |
| cmSystemTools::Error( |
| cmStrCat("Unable to use module '", r.LogicalName, |
| "' as it is 'PRIVATE' and therefore not accessible outside " |
| "of its owning target.")); |
| continue; |
| } |
| |
| // Find transitive usages. |
| auto transitive_usages = usages.Usage.find(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.IsKnown()) { |
| // Mark that we need to update transitive usages later. |
| internal_usages[p.LogicalName].insert(r.LogicalName); |
| } |
| } |
| |
| if (bmi_loc.IsKnown()) { |
| usages.AddReference(r.LogicalName, bmi_loc.Location(), 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 {}; |
| } |