| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmCxxModuleMapper.h" | |
| #include <cstddef> | |
| #include <set> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <cmext/string_view> | |
| #include "cmScanDepFormat.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmUnreachable.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 { | |
| struct TransitiveUsage | |
| { | |
| TransitiveUsage(std::string name, std::string location, LookupMethod method) | |
| : LogicalName(std::move(name)) | |
| , Location(std::move(location)) | |
| , Method(method) | |
| { | |
| } | |
| std::string LogicalName; | |
| std::string Location; | |
| LookupMethod Method; | |
| }; | |
| std::vector<TransitiveUsage> GetTransitiveUsages( | |
| CxxModuleLocations const& loc, std::vector<cmSourceReqInfo> const& required, | |
| CxxModuleUsage const& usages) | |
| { | |
| std::set<std::string> transitive_usage_directs; | |
| std::set<std::string> transitive_usage_names; | |
| std::vector<TransitiveUsage> all_usages; | |
| for (auto const& r : required) { | |
| auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName); | |
| if (bmi_loc.IsKnown()) { | |
| all_usages.emplace_back(r.LogicalName, bmi_loc.Location(), r.Method); | |
| 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()) { | |
| all_usages.emplace_back(transitive_name, module_ref->second.Path, | |
| module_ref->second.Method); | |
| } | |
| } | |
| return all_usages; | |
| } | |
| std::string CxxModuleMapContentClang(CxxModuleLocations const& loc, | |
| cmScanDepInfo const& obj, | |
| CxxModuleUsage const& usages) | |
| { | |
| // 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. | |
| std::string mm; | |
| 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 = cmStrCat(std::move(mm), | |
| "-x c++-module\n" | |
| "-fmodule-output=\"", | |
| bmi_loc.Location(), "\"\n"); | |
| break; | |
| } | |
| } | |
| auto all_usages = GetTransitiveUsages(loc, obj.Requires, usages); | |
| for (auto const& usage : all_usages) { | |
| mm = cmStrCat(std::move(mm), "-fmodule-file=\"", usage.LogicalName, '=', | |
| usage.Location, "\"\n"); | |
| } | |
| return mm; | |
| } | |
| std::string CxxModuleMapContentGcc(CxxModuleLocations const& loc, | |
| cmScanDepInfo const& obj) | |
| { | |
| // 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. | |
| std::string mm = cmStrCat("$root ", loc.RootDirectory, '\n'); | |
| for (auto const& p : obj.Provides) { | |
| auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName); | |
| if (bmi_loc.IsKnown()) { | |
| mm = | |
| cmStrCat(std::move(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 = | |
| cmStrCat(std::move(mm), r.LogicalName, ' ', bmi_loc.Location(), '\n'); | |
| } | |
| } | |
| return mm; | |
| } | |
| std::string CxxModuleMapContentMsvc(CxxModuleLocations const& loc, | |
| cmScanDepInfo const& obj, | |
| CxxModuleUsage const& usages) | |
| { | |
| // 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; | |
| } | |
| CM_UNREACHABLE; | |
| return ""_s; | |
| }; | |
| std::string mm; | |
| 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 = | |
| cmStrCat(std::move(mm), "-ifcOutput \"", bmi_loc.Location(), "\"\n"); | |
| } | |
| } | |
| auto all_usages = GetTransitiveUsages(loc, obj.Requires, usages); | |
| for (auto const& usage : all_usages) { | |
| auto flag = flag_for_method(usage.Method); | |
| mm = cmStrCat(std::move(mm), flag, " \"", usage.LogicalName, '=', | |
| usage.Location, "\"\n"); | |
| } | |
| return mm; | |
| } | |
| } | |
| 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; | |
| } | |
| CM_UNREACHABLE; | |
| 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, bool& private_usage_found) | |
| { | |
| // 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.")); | |
| private_usage_found = true; | |
| 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); | |
| if (transitive_usages == usages.Usage.end() || | |
| internal_usages.find(r.LogicalName) != internal_usages.end()) { | |
| // Mark that we need to update transitive usages later. | |
| if (bmi_loc.IsKnown()) { | |
| internal_usages[p.LogicalName].insert(r.LogicalName); | |
| } | |
| } else { | |
| // Add the transitive usage. | |
| this_usages.insert(transitive_usages->second.begin(), | |
| transitive_usages->second.end()); | |
| } | |
| } | |
| 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, usages); | |
| case CxxModuleMapFormat::Gcc: | |
| return CxxModuleMapContentGcc(loc, obj); | |
| case CxxModuleMapFormat::Msvc: | |
| return CxxModuleMapContentMsvc(loc, obj, usages); | |
| } | |
| CM_UNREACHABLE; | |
| return {}; | |
| } | |
| CxxModuleMapMode CxxModuleMapOpenMode(CxxModuleMapFormat format) | |
| { | |
| switch (format) { | |
| case CxxModuleMapFormat::Gcc: | |
| return CxxModuleMapMode::Binary; | |
| case CxxModuleMapFormat::Clang: | |
| case CxxModuleMapFormat::Msvc: | |
| return CxxModuleMapMode::Default; | |
| } | |
| CM_UNREACHABLE; | |
| return CxxModuleMapMode::Default; | |
| } |