summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2022-08-03 13:45:06 +0000
committerKitware Robot <kwrobot@kitware.com>2022-08-03 09:45:15 -0400
commite2112b37786eee4f4f47bc2dd548e51349076254 (patch)
treee424f75bcd555bbba861a72f4ec4c4a248b20613
parentf6917a2f1f434ddb5c27ba59e3332f2d92e66057 (diff)
parent4f95e6b284205c7c01ba1923e6d7f2f5a6dc849a (diff)
downloadcmake-e2112b37786eee4f4f47bc2dd548e51349076254.tar.gz
Merge topic 'cpp-named-module-support-msvc'
4f95e6b284 ci: test BMI exporting and installation with GCC in CI c49d5f137b RunCMake/CXXModules: add a "deep-chain" test 297e0f4dce cmCxxModuleMapper: support MSVC module map format b3c2880cb2 cmCxxModuleMapper: track transitive modules for MSVC a43713d615 CTestCustom: ignore `cm::optional` uninitialized memory false positive b90de0b492 RunCMake/CXXModules: support MSVC extensions a84c186a7d cmScanDepFormat: support the MSVC 17.3 toolchain temporarily d7f5064ff7 cmScanDepFormat: support P1689R5 ... Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !7481
-rw-r--r--.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake2
-rw-r--r--.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake2
-rw-r--r--CTestCustom.cmake.in5
-rw-r--r--Help/dev/experimental.rst4
-rw-r--r--Help/release/dev/p1689r5.rst6
-rw-r--r--Source/cmCxxModuleMapper.cxx234
-rw-r--r--Source/cmCxxModuleMapper.h41
-rw-r--r--Source/cmGlobalNinjaGenerator.cxx100
-rw-r--r--Source/cmScanDepFormat.cxx37
-rw-r--r--Source/cmScanDepFormat.h5
-rw-r--r--Tests/RunCMake/CXXModules/RunCMakeTest.cmake1
-rw-r--r--Tests/RunCMake/CXXModules/examples/cxx-modules-find-bmi.cmake2
-rw-r--r--Tests/RunCMake/CXXModules/examples/deep-chain-stderr.txt9
-rw-r--r--Tests/RunCMake/CXXModules/examples/deep-chain/CMakeLists.txt66
-rw-r--r--Tests/RunCMake/CXXModules/examples/deep-chain/a.cxx6
-rw-r--r--Tests/RunCMake/CXXModules/examples/deep-chain/b.cxx7
-rw-r--r--Tests/RunCMake/CXXModules/examples/deep-chain/c.cxx7
-rw-r--r--Tests/RunCMake/CXXModules/examples/deep-chain/d.cxx7
-rw-r--r--Tests/RunCMake/CXXModules/examples/deep-chain/e.cxx7
-rw-r--r--Tests/RunCMake/CXXModules/examples/deep-chain/main.cxx6
-rw-r--r--Tests/RunCMake/CXXModules/examples/internal-partitions/importable.cxx2
-rw-r--r--Tests/RunCMake/CXXModules/examples/partitions/importable.cxx2
22 files changed, 542 insertions, 16 deletions
diff --git a/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake b/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake
index bf990c8193..2b04e89947 100644
--- a/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake
+++ b/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja.cmake
@@ -1,4 +1,4 @@
-set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions,export_bmi,install_bmi" CACHE STRING "")
set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_gcc.cmake" CACHE STRING "")
include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake b/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake
index bf990c8193..2b04e89947 100644
--- a/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake
+++ b/.gitlab/ci/configure_linux_gcc_cxx_modules_ninja_multi.cmake
@@ -1,4 +1,4 @@
-set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions,export_bmi,install_bmi" CACHE STRING "")
set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_gcc.cmake" CACHE STRING "")
include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/CTestCustom.cmake.in b/CTestCustom.cmake.in
index 967e1bfbe0..85af8eda80 100644
--- a/CTestCustom.cmake.in
+++ b/CTestCustom.cmake.in
@@ -84,6 +84,11 @@ list(APPEND CTEST_CUSTOM_WARNING_EXCEPTION
"compilation completed with warnings" # PGI
"[0-9]+ Warning\\(s\\) detected" # SunPro
+ # Ignore false positive on `cm::optional` usage from GCC
+ "cmGlobalNinjaGenerator.cxx:[0-9]*:[0-9]*: warning: '.*cm::optional<CxxModuleMapFormat>::_mem\\)\\)' may be used uninitialized \\[-Wmaybe-uninitialized\\]"
+ "cmGlobalNinjaGenerator.cxx:[0-9]*:[0-9]*: note: '.*cm::optional<CxxModuleMapFormat>::_mem\\)\\)' was declared here"
+ "cmGlobalNinjaGenerator.cxx:[0-9]*:[0-9]*: warning: '\\*\\(\\(void\\*\\)& modmap_fmt \\+4\\)' may be used uninitialized in this function \\[-Wmaybe-uninitialized\\]"
+
# clang-analyzer exceptions
"cmListFileLexer.c:[0-9]+:[0-9]+: warning: Array subscript is undefined"
"jsoncpp/src/.*:[0-9]+:[0-9]+: warning: Value stored to .* is never read"
diff --git a/Help/dev/experimental.rst b/Help/dev/experimental.rst
index b8d681c366..adfa36fc7e 100644
--- a/Help/dev/experimental.rst
+++ b/Help/dev/experimental.rst
@@ -58,7 +58,7 @@ dependencies to the file specified by the ``<DYNDEP_FILE>`` placeholder. The
for scandep rules which use ``msvc``-style dependency reporting.
The module dependencies should be written in the format described
-by the `P1689r4`_ paper.
+by the `P1689r5`_ paper.
Compiler writers may try out their scanning functionality using
the `cxx-modules-sandbox`_ test project, modified to set variables
@@ -85,5 +85,5 @@ the GCC documentation, but the relevant section for the purposes of CMake is:
-- GCC module mapper documentation
.. _`D1483r1`: https://mathstuf.fedorapeople.org/fortran-modules/fortran-modules.html
-.. _`P1689r4`: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1689r4.html
+.. _`P1689r5`: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html
.. _`cxx-modules-sandbox`: https://github.com/mathstuf/cxx-modules-sandbox
diff --git a/Help/release/dev/p1689r5.rst b/Help/release/dev/p1689r5.rst
new file mode 100644
index 0000000000..a630dc443d
--- /dev/null
+++ b/Help/release/dev/p1689r5.rst
@@ -0,0 +1,6 @@
+p1689r5
+-------
+
+* C++ module scanning now supports the latest revision, `P1689R5`_.
+
+.. _`P1689r5`: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html
diff --git a/Source/cmCxxModuleMapper.cxx b/Source/cmCxxModuleMapper.cxx
index 94ad721702..84691c9fc7 100644
--- a/Source/cmCxxModuleMapper.cxx
+++ b/Source/cmCxxModuleMapper.cxx
@@ -3,10 +3,19 @@
#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"
cm::optional<std::string> CxxModuleLocations::BmiGeneratorPathForModule(
std::string const& logical_name) const
@@ -47,6 +56,122 @@ std::string CxxModuleMapContentGcc(CxxModuleLocations const& loc,
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";
+ }
+
+ if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
+ mm << "-ifcOutput " << *bmi_loc << '\n';
+ }
+ }
+
+ std::set<std::string> transitive_usage_directs;
+ std::set<std::string> transitive_usage_names;
+
+ for (auto const& r : obj.Requires) {
+ if (auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName)) {
+ auto flag = flag_for_method(r.Method);
+
+ mm << flag << ' ' << r.LogicalName << '=' << *bmi_loc << "\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(
@@ -56,19 +181,126 @@ cm::static_string_view CxxModuleMapExtension(
switch (*format) {
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) {
+ if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
+ // XXX(cxx-modules): How to support header units?
+ usages.AddReference(p.LogicalName, loc.PathForGenerator(*bmi_loc),
+ LookupMethod::ByName);
+ }
+ }
+
+ // For each requires, pull in what is required.
+ for (auto const& r : object.Requires) {
+ // Find transitive usages.
+ auto transitive_usages = usages.Usage.find(r.LogicalName);
+ // Find the required name in the current target.
+ auto bmi_loc = loc.BmiGeneratorPathForModule(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) {
+ // Mark that we need to update transitive usages later.
+ internal_usages[p.LogicalName].insert(r.LogicalName);
+ }
+ }
+
+ if (bmi_loc) {
+ usages.AddReference(r.LogicalName, loc.PathForGenerator(*bmi_loc),
+ 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)
+ cmScanDepInfo const& obj,
+ CxxModuleUsage const& usages)
{
switch (format) {
case CxxModuleMapFormat::Gcc:
return CxxModuleMapContentGcc(loc, obj);
+ case CxxModuleMapFormat::Msvc:
+ return CxxModuleMapContentMsvc(loc, obj, usages);
}
assert(false);
diff --git a/Source/cmCxxModuleMapper.h b/Source/cmCxxModuleMapper.h
index 99384c919b..8526a07506 100644
--- a/Source/cmCxxModuleMapper.h
+++ b/Source/cmCxxModuleMapper.h
@@ -5,16 +5,20 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include <functional>
+#include <map>
+#include <set>
#include <string>
+#include <vector>
#include <cm/optional>
#include <cmext/string_view>
-struct cmScanDepInfo;
+#include "cmScanDepFormat.h"
enum class CxxModuleMapFormat
{
Gcc,
+ Msvc,
};
struct CxxModuleLocations
@@ -37,12 +41,45 @@ struct CxxModuleLocations
std::string const& logical_name) const;
};
+struct CxxModuleReference
+{
+ // The path to the module file used.
+ std::string Path;
+ // How the module was looked up.
+ LookupMethod Method;
+};
+
+struct CxxModuleUsage
+{
+ // The usage requirements for this object.
+ std::map<std::string, std::set<std::string>> Usage;
+
+ // The references for this object.
+ std::map<std::string, CxxModuleReference> Reference;
+
+ // Add a reference to a module.
+ //
+ // Returns `true` if it matches how it was found previously, `false` if it
+ // conflicts.
+ bool AddReference(std::string const& logical, std::string const& loc,
+ LookupMethod method);
+};
+
// Return the extension to use for a given modulemap format.
cm::static_string_view CxxModuleMapExtension(
cm::optional<CxxModuleMapFormat> format);
+// Fill in module usage information for internal usages.
+//
+// Returns the set of unresolved module usage requirements (these form an
+// import cycle).
+std::set<std::string> CxxModuleUsageSeed(
+ CxxModuleLocations const& loc, std::vector<cmScanDepInfo> const& objects,
+ CxxModuleUsage& usages);
+
// Return the contents of the module map in the given format for the
// object file.
std::string CxxModuleMapContent(CxxModuleMapFormat format,
CxxModuleLocations const& loc,
- cmScanDepInfo const& obj);
+ cmScanDepInfo const& obj,
+ CxxModuleUsage const& usages);
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index b4d5746d96..c326ca6101 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -3,6 +3,7 @@
#include "cmGlobalNinjaGenerator.h"
#include <algorithm>
+#include <cassert>
#include <cctype>
#include <cstdio>
#include <functional>
@@ -2555,6 +2556,8 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
objects.push_back(std::move(info));
}
+ CxxModuleUsage usages;
+
// Map from module name to module file path, if known.
std::map<std::string, std::string> mod_files;
@@ -2572,8 +2575,47 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
return false;
}
if (ltm.isObject()) {
- for (Json::Value::iterator i = ltm.begin(); i != ltm.end(); ++i) {
- mod_files[i.key().asString()] = i->asString();
+ Json::Value const& target_modules = ltm["modules"];
+ if (target_modules.isObject()) {
+ for (auto i = target_modules.begin(); i != target_modules.end(); ++i) {
+ mod_files[i.key().asString()] = i->asString();
+ }
+ }
+ Json::Value const& target_modules_references = ltm["references"];
+ if (target_modules_references.isObject()) {
+ for (auto i = target_modules_references.begin();
+ i != target_modules_references.end(); ++i) {
+ if (i->isObject()) {
+ Json::Value const& reference_path = (*i)["path"];
+ CxxModuleReference module_reference;
+ if (reference_path.isString()) {
+ module_reference.Path = reference_path.asString();
+ }
+ Json::Value const& reference_method = (*i)["lookup-method"];
+ if (reference_method.isString()) {
+ std::string reference = reference_method.asString();
+ if (reference == "by-name") {
+ module_reference.Method = LookupMethod::ByName;
+ } else if (reference == "include-angle") {
+ module_reference.Method = LookupMethod::IncludeAngle;
+ } else if (reference == "include-quote") {
+ module_reference.Method = LookupMethod::IncludeQuote;
+ }
+ }
+ usages.Reference[i.key().asString()] = module_reference;
+ }
+ }
+ }
+ Json::Value const& target_modules_usage = ltm["usages"];
+ if (target_modules_usage.isObject()) {
+ for (auto i = target_modules_usage.begin();
+ i != target_modules_usage.end(); ++i) {
+ if (i->isArray()) {
+ for (auto j = i->begin(); j != i->end(); ++j) {
+ usages.Usage[i.key().asString()].insert(j->asString());
+ }
+ }
+ }
}
}
}
@@ -2583,6 +2625,8 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
// nothing to do.
} else if (arg_modmapfmt == "gcc") {
modmap_fmt = CxxModuleMapFormat::Gcc;
+ } else if (arg_modmapfmt == "msvc") {
+ modmap_fmt = CxxModuleMapFormat::Msvc;
} else {
cmSystemTools::Error(
cmStrCat("-E cmake_ninja_dyndep does not understand the ", arg_modmapfmt,
@@ -2595,7 +2639,7 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
// Extend the module map with those provided by this target.
// We do this after loading the modules provided by linked targets
// in case we have one of the same name that must be preferred.
- Json::Value tm = Json::objectValue;
+ Json::Value target_modules = Json::objectValue;
for (cmScanDepInfo const& object : objects) {
for (auto const& p : object.Provides) {
std::string mod;
@@ -2614,7 +2658,7 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
mod = cmStrCat(module_dir, safe_logical_name, module_ext);
}
mod_files[p.LogicalName] = mod;
- tm[p.LogicalName] = mod;
+ target_modules[p.LogicalName] = mod;
}
}
@@ -2636,6 +2680,18 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
return {};
};
+ // Insert information about the current target's modules.
+ if (modmap_fmt) {
+ auto cycle_modules = CxxModuleUsageSeed(locs, objects, usages);
+ if (!cycle_modules.empty()) {
+ cmSystemTools::Error(
+ cmStrCat("Circular dependency detected in the C++ module import "
+ "graph. See modules named: \"",
+ cmJoin(cycle_modules, R"(", ")"_s), '"'));
+ return false;
+ }
+ }
+
cmNinjaBuild build("dyndep");
build.Outputs.emplace_back("");
for (cmScanDepInfo const& object : objects) {
@@ -2658,7 +2714,7 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
}
if (modmap_fmt) {
- auto mm = CxxModuleMapContent(*modmap_fmt, locs, object);
+ auto mm = CxxModuleMapContent(*modmap_fmt, locs, object, usages);
// XXX(modmap): If changing this path construction, change
// `cmNinjaTargetGenerator::WriteObjectBuildStatements` to generate the
@@ -2671,12 +2727,44 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
}
}
+ Json::Value target_module_info = Json::objectValue;
+ target_module_info["modules"] = target_modules;
+
+ auto& target_usages = target_module_info["usages"] = Json::objectValue;
+ for (auto const& u : usages.Usage) {
+ auto& mod_usage = target_usages[u.first] = Json::arrayValue;
+ for (auto const& v : u.second) {
+ mod_usage.append(v);
+ }
+ }
+
+ auto name_for_method = [](LookupMethod method) -> cm::static_string_view {
+ switch (method) {
+ 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;
+ };
+
+ auto& target_references = target_module_info["references"] =
+ Json::objectValue;
+ for (auto const& r : usages.Reference) {
+ auto& mod_ref = target_references[r.first] = Json::objectValue;
+ mod_ref["path"] = r.second.Path;
+ mod_ref["lookup-method"] = std::string(name_for_method(r.second.Method));
+ }
+
// Store the map of modules provided by this target in a file for
// use by dependents that reference this target in linked-target-dirs.
std::string const target_mods_file = cmStrCat(
cmSystemTools::GetFilenamePath(arg_dd), '/', arg_lang, "Modules.json");
cmGeneratedFileStream tmf(target_mods_file);
- tmf << tm;
+ tmf << target_module_info;
bool result = true;
diff --git a/Source/cmScanDepFormat.cxx b/Source/cmScanDepFormat.cxx
index 82a374a83b..ec53af5382 100644
--- a/Source/cmScanDepFormat.cxx
+++ b/Source/cmScanDepFormat.cxx
@@ -5,6 +5,7 @@
#include <cctype>
#include <cstdio>
+#include <iostream>
#include <utility>
#include <cm/optional>
@@ -188,6 +189,19 @@ bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp,
return false;
}
+ if (provide.isMember("is-interface")) {
+ Json::Value const& is_interface = provide["is-interface"];
+ if (!is_interface.isBool()) {
+ cmSystemTools::Error(
+ cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp,
+ ": is-interface is not a boolean"));
+ return false;
+ }
+ provide_info.IsInterface = is_interface.asBool();
+ } else {
+ provide_info.IsInterface = true;
+ }
+
info->Provides.push_back(provide_info);
}
}
@@ -267,6 +281,27 @@ bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp,
info->Requires.push_back(require_info);
}
}
+
+ // MSVC 17.3 toolchain bug. Remove when 17.4 is available.
+ if (rule.isMember("is-interface")) {
+ std::cerr
+ << "warning: acknowledging an VS 17.3 toolchain bug; accepting "
+ "until a new release which fixes it is available"
+ << std::endl;
+
+ Json::Value const& is_interface_json = rule["is-interface"];
+ if (!is_interface_json.isBool()) {
+ cmSystemTools::Error(
+ cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp,
+ ": is-interface is not a boolean"));
+ return false;
+ }
+ bool is_interface = is_interface_json.asBool();
+
+ for (auto& provide : info->Provides) {
+ provide.IsInterface = is_interface;
+ }
+ }
}
}
@@ -308,6 +343,8 @@ bool cmScanDepFormat_P1689_Write(std::string const& path,
provide_obj["source-path"] = EncodeFilename(provide.SourcePath);
}
+ provide_obj["is-interface"] = provide.IsInterface;
+
provides.append(provide_obj);
}
diff --git a/Source/cmScanDepFormat.h b/Source/cmScanDepFormat.h
index dae28d950b..dc55bf11b2 100644
--- a/Source/cmScanDepFormat.h
+++ b/Source/cmScanDepFormat.h
@@ -18,6 +18,11 @@ struct cmSourceReqInfo
std::string SourcePath;
std::string CompiledModulePath;
bool UseSourcePath = false;
+
+ // Provides-only fields.
+ bool IsInterface = true;
+
+ // Requires-only fields.
LookupMethod Method = LookupMethod::ByName;
};
diff --git a/Tests/RunCMake/CXXModules/RunCMakeTest.cmake b/Tests/RunCMake/CXXModules/RunCMakeTest.cmake
index ca1bc81f2b..3f17c1ff0e 100644
--- a/Tests/RunCMake/CXXModules/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CXXModules/RunCMakeTest.cmake
@@ -131,6 +131,7 @@ if ("named" IN_LIST CMake_TEST_MODULE_COMPILATION)
run_cxx_module_test(library library-static -DBUILD_SHARED_LIBS=OFF)
run_cxx_module_test(generated)
run_cxx_module_test(public-req-private)
+ run_cxx_module_test(deep-chain)
endif ()
# Tests which use named modules in shared libraries.
diff --git a/Tests/RunCMake/CXXModules/examples/cxx-modules-find-bmi.cmake b/Tests/RunCMake/CXXModules/examples/cxx-modules-find-bmi.cmake
index 8efe508426..91f3995888 100644
--- a/Tests/RunCMake/CXXModules/examples/cxx-modules-find-bmi.cmake
+++ b/Tests/RunCMake/CXXModules/examples/cxx-modules-find-bmi.cmake
@@ -1,6 +1,6 @@
function (check_for_bmi prefix destination name)
set(found 0)
- foreach (ext IN ITEMS gcm)
+ foreach (ext IN ITEMS gcm ifc)
if (EXISTS "${prefix}/${destination}/${name}.${ext}")
set(found 1)
break ()
diff --git a/Tests/RunCMake/CXXModules/examples/deep-chain-stderr.txt b/Tests/RunCMake/CXXModules/examples/deep-chain-stderr.txt
new file mode 100644
index 0000000000..5e4392abc9
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/deep-chain-stderr.txt
@@ -0,0 +1,9 @@
+CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+ CMake's C\+\+ module support is experimental. It is meant only for
+ experimentation and feedback to CMake developers.
+This warning is for project developers. Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+ C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+ experimental. It is meant only for compiler developers to try.
+This warning is for project developers. Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/CXXModules/examples/deep-chain/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/deep-chain/CMakeLists.txt
new file mode 100644
index 0000000000..515b2409d6
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/deep-chain/CMakeLists.txt
@@ -0,0 +1,66 @@
+cmake_minimum_required(VERSION 3.24)
+project(cxx_modules_deep_chain CXX)
+
+include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
+
+add_library(a STATIC)
+target_sources(a
+ PUBLIC
+ FILE_SET CXX_MODULES
+ BASE_DIRS
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ FILES
+ a.cxx)
+target_compile_features(a PUBLIC cxx_std_20)
+
+add_library(b STATIC)
+target_sources(b
+ PUBLIC
+ FILE_SET CXX_MODULES
+ BASE_DIRS
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ FILES
+ b.cxx)
+target_compile_features(b PUBLIC cxx_std_20)
+target_link_libraries(b PUBLIC a)
+
+add_library(c STATIC)
+target_sources(c
+ PUBLIC
+ FILE_SET CXX_MODULES
+ BASE_DIRS
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ FILES
+ c.cxx)
+target_compile_features(c PUBLIC cxx_std_20)
+target_link_libraries(c PUBLIC b)
+
+add_library(d STATIC)
+target_sources(d
+ PUBLIC
+ FILE_SET CXX_MODULES
+ BASE_DIRS
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ FILES
+ d.cxx)
+target_compile_features(d PUBLIC cxx_std_20)
+target_link_libraries(d PUBLIC c)
+
+add_library(e STATIC)
+target_sources(e
+ PUBLIC
+ FILE_SET CXX_MODULES
+ BASE_DIRS
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ FILES
+ e.cxx)
+target_compile_features(e PUBLIC cxx_std_20)
+target_link_libraries(e PUBLIC d)
+
+add_executable(exe)
+target_link_libraries(exe PRIVATE e)
+target_sources(exe
+ PRIVATE
+ main.cxx)
+
+add_test(NAME exe COMMAND exe)
diff --git a/Tests/RunCMake/CXXModules/examples/deep-chain/a.cxx b/Tests/RunCMake/CXXModules/examples/deep-chain/a.cxx
new file mode 100644
index 0000000000..9edaec9677
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/deep-chain/a.cxx
@@ -0,0 +1,6 @@
+export module a;
+
+export int a()
+{
+ return 0;
+}
diff --git a/Tests/RunCMake/CXXModules/examples/deep-chain/b.cxx b/Tests/RunCMake/CXXModules/examples/deep-chain/b.cxx
new file mode 100644
index 0000000000..38ab0c2feb
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/deep-chain/b.cxx
@@ -0,0 +1,7 @@
+export module b;
+import a;
+
+export int b()
+{
+ return a();
+}
diff --git a/Tests/RunCMake/CXXModules/examples/deep-chain/c.cxx b/Tests/RunCMake/CXXModules/examples/deep-chain/c.cxx
new file mode 100644
index 0000000000..580a458f3a
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/deep-chain/c.cxx
@@ -0,0 +1,7 @@
+export module c;
+import b;
+
+export int c()
+{
+ return b();
+}
diff --git a/Tests/RunCMake/CXXModules/examples/deep-chain/d.cxx b/Tests/RunCMake/CXXModules/examples/deep-chain/d.cxx
new file mode 100644
index 0000000000..78bc5baa2a
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/deep-chain/d.cxx
@@ -0,0 +1,7 @@
+export module d;
+import c;
+
+export int d()
+{
+ return c();
+}
diff --git a/Tests/RunCMake/CXXModules/examples/deep-chain/e.cxx b/Tests/RunCMake/CXXModules/examples/deep-chain/e.cxx
new file mode 100644
index 0000000000..e0194401bc
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/deep-chain/e.cxx
@@ -0,0 +1,7 @@
+export module e;
+import d;
+
+export int e()
+{
+ return d();
+}
diff --git a/Tests/RunCMake/CXXModules/examples/deep-chain/main.cxx b/Tests/RunCMake/CXXModules/examples/deep-chain/main.cxx
new file mode 100644
index 0000000000..0b7c15df59
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/deep-chain/main.cxx
@@ -0,0 +1,6 @@
+import e;
+
+int main(int argc, char* argv[])
+{
+ return e();
+}
diff --git a/Tests/RunCMake/CXXModules/examples/internal-partitions/importable.cxx b/Tests/RunCMake/CXXModules/examples/internal-partitions/importable.cxx
index 8be521b183..b872ae98cd 100644
--- a/Tests/RunCMake/CXXModules/examples/internal-partitions/importable.cxx
+++ b/Tests/RunCMake/CXXModules/examples/internal-partitions/importable.cxx
@@ -1,5 +1,5 @@
export module importable;
-import importable : internal_partition;
+import : internal_partition;
#include "internal-partitions_export.h"
diff --git a/Tests/RunCMake/CXXModules/examples/partitions/importable.cxx b/Tests/RunCMake/CXXModules/examples/partitions/importable.cxx
index 1e81aafa1b..d0ac2f47b5 100644
--- a/Tests/RunCMake/CXXModules/examples/partitions/importable.cxx
+++ b/Tests/RunCMake/CXXModules/examples/partitions/importable.cxx
@@ -1,5 +1,5 @@
export module importable;
-export import importable : partition;
+export import : partition;
#include "partitions_export.h"