/* 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 #include #include #include #include #include #include #include #include #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 { 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 GetTransitiveUsages( CxxModuleLocations const& loc, std::vector const& required, CxxModuleUsage const& usages) { std::set transitive_usage_directs; std::set transitive_usage_names; std::vector 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) { 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; } } auto all_usages = GetTransitiveUsages(loc, obj.Requires, usages); for (auto const& usage : all_usages) { mm << "-fmodule-file=" << usage.LogicalName << '=' << usage.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'; } } auto all_usages = GetTransitiveUsages(loc, obj.Requires, usages); for (auto const& usage : all_usages) { auto flag = flag_for_method(usage.Method); mm << flag << ' ' << usage.LogicalName << '=' << usage.Location << '\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 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 CxxModuleUsageSeed( CxxModuleLocations const& loc, std::vector 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> internal_usages; std::set 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); } assert(false); return {}; } CxxModuleMapMode CxxModuleMapOpenMode(CxxModuleMapFormat format) { switch (format) { case CxxModuleMapFormat::Gcc: return CxxModuleMapMode::Binary; case CxxModuleMapFormat::Clang: case CxxModuleMapFormat::Msvc: return CxxModuleMapMode::Default; } assert(false); return CxxModuleMapMode::Default; }