
This allows collation to give a useful error message when it finds usage of a private module rather than collation just not informing the compilation and the compiler erroring out about not being able to import unknown modules (which exists, but it was not told about due to visibility). Fixes: #24652
406 lines
12 KiB
C++
406 lines
12 KiB
C++
/* 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 {};
|
|
}
|