Browse Source

Merge topic 'cpp-modules'

39cbbb59a5 ninja: add experimental infrastructure to generate gcc-format modmap files
791b4d26d6 ninja: add experimental infrastructure to generate modmap files with dyndep
4b23359117 ninja: Add experimental infrastructure for C++20 module dependency scanning
f814d3b3c6 cmNinjaTargetGenerator: use $OBJ_FILE for the object
b0fc2993e1 Treat the '.mpp' file extension as C++ code
988f997100 cmScanDepFormat: Fix name of our internal tool in parse errors
dacd93a2db ninja: De-duplicate version numbers required for ninja features
533386ca29 cmStandardLevelResolver: Factor out helper to capture stoi exceptions

Acked-by: Kitware Robot <[email protected]>
Acked-by: Ben Boeckel <[email protected]>
Acked-by: Robert Maynard <[email protected]>
Acked-by: Shannon Booth <[email protected]>
Merge-request: !5562
Brad King 5 years ago
parent
commit
05f4248e3d

+ 61 - 1
Help/dev/experimental.rst

@@ -7,4 +7,64 @@ See documentation on `CMake Development`_ for more information.
 
 .. _`CMake Development`: README.rst
 
-No experimental features are under development in this version of CMake.
+C++20 Module Dependencies
+=========================
+
+The Ninja generator has experimental infrastructure supporting C++20 module
+dependency scanning.  This is similar to the Fortran modules support, but
+relies on external tools to scan C++20 translation units for module
+dependencies.  The approach is described by Kitware's `D1483r1`_ paper.
+
+The ``CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP`` variable can be set to ``1``
+in order to activate this undocumented experimental infrastructure.  This
+is **intended to make the functionality available to compiler writers** so
+they can use it to develop and test their dependency scanning tool.
+The ``CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE`` variable must also be set
+to tell CMake how to invoke the C++20 module dependency scanning tool.
+
+For example, add code like the following to a test project:
+
+.. code-block:: cmake
+
+  set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
+  string(CONCAT CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE
+    "<CMAKE_CXX_COMPILER> <DEFINES> <INCLUDES> <FLAGS> <SOURCE>"
+    " -MT <DYNDEP_FILE> -MD -MF <DEP_FILE>"
+    " ${flags_to_scan_deps} -fdep-file=<DYNDEP_FILE> -fdep-output=<OBJECT>"
+    )
+
+The tool specified by ``CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE`` is
+expected to process the translation unit, write preprocessor dependencies
+to the file specified by the ``<DEP_FILE>`` placeholder, and write module
+dependencies to the file specified by the ``<DYNDEP_FILE>`` placeholder.
+
+The module dependencies should be written in the format described
+by the `P1689r3`_ paper.
+
+Compiler writers may try out their scanning functionality using
+the `cxx-modules-sandbox`_ test project, modified to set variables
+as above for their compiler.
+
+For compilers that generate module maps, tell CMake as follows:
+
+.. code-block:: cmake
+
+  set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "gcc")
+  set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG
+    "${compiler_flags_for_module_map} -fmodule-mapper=<MODULE_MAP_FILE>")
+
+Currently, the only supported format is ``gcc``.  The format is described in
+the GCC documentation, but the relevant section for the purposes of CMake is:
+
+    A mapping file consisting of space-separated module-name, filename
+    pairs, one per line.  Only the mappings for the direct imports and any
+    module export name need be provided.  If other mappings are provided,
+    they override those stored in any imported CMI files.  A repository
+    root may be specified in the mapping file by using ``$root`` as the
+    module name in the first active line.
+
+    -- GCC module mapper documentation
+
+.. _`D1483r1`: https://mathstuf.fedorapeople.org/fortran-modules/fortran-modules.html
+.. _`P1689r3`: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1689r3.html
+.. _`cxx-modules-sandbox`: https://github.com/mathstuf/cxx-modules-sandbox

+ 1 - 1
Modules/CMakeCXXCompiler.cmake.in

@@ -44,7 +44,7 @@ if(CMAKE_COMPILER_IS_MINGW)
   set(MINGW 1)
 endif()
 set(CMAKE_CXX_COMPILER_ID_RUN 1)
-set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;CPP)
+set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;mpp;CPP)
 set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC)
 
 foreach (lang C OBJC OBJCXX)

+ 84 - 4
Source/cmGlobalNinjaGenerator.cxx

@@ -555,6 +555,7 @@ void cmGlobalNinjaGenerator::Generate()
   this->TargetAll = this->NinjaOutputPath("all");
   this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
   this->DisableCleandead = false;
+  this->DiagnosedCxxModuleSupport = false;
 
   this->PolicyCMP0058 =
     this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus(
@@ -755,6 +756,37 @@ bool cmGlobalNinjaGenerator::CheckLanguages(
   return true;
 }
 
+bool cmGlobalNinjaGenerator::CheckCxxModuleSupport()
+{
+  bool const diagnose = !this->DiagnosedCxxModuleSupport &&
+    !this->CMakeInstance->GetIsInTryCompile();
+  if (diagnose) {
+    this->DiagnosedCxxModuleSupport = true;
+    this->GetCMakeInstance()->IssueMessage(
+      MessageType::AUTHOR_WARNING,
+      "C++20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP "
+      "is experimental.  It is meant only for compiler developers to try.");
+  }
+  if (this->NinjaSupportsDyndeps) {
+    return true;
+  }
+  if (diagnose) {
+    std::ostringstream e;
+    /* clang-format off */
+    e <<
+      "The Ninja generator does not support C++20 modules "
+      "using Ninja version \n"
+      "  " << this->NinjaVersion << "\n"
+      "due to lack of required features.  "
+      "Ninja " << RequiredNinjaVersionForDyndeps() << " or higher is required."
+      ;
+    /* clang-format on */
+    this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str());
+    cmSystemTools::SetFatalErrorOccured();
+  }
+  return false;
+}
+
 bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
 {
   if (this->NinjaSupportsDyndeps) {
@@ -766,7 +798,8 @@ bool cmGlobalNinjaGenerator::CheckFortran(cmMakefile* mf) const
   e <<
     "The Ninja generator does not support Fortran using Ninja version\n"
     "  " << this->NinjaVersion << "\n"
-    "due to lack of required features.  Ninja 1.10 or higher is required."
+    "due to lack of required features.  "
+    "Ninja " << RequiredNinjaVersionForDyndeps() << " or higher is required."
     ;
   /* clang-format on */
   mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
@@ -785,7 +818,9 @@ bool cmGlobalNinjaGenerator::CheckISPC(cmMakefile* mf) const
   e <<
     "The Ninja generator does not support ISPC using Ninja version\n"
     "  " << this->NinjaVersion << "\n"
-    "due to lack of required features.  Ninja 1.10 or higher is required."
+    "due to lack of required features.  "
+    "Ninja " << RequiredNinjaVersionForMultipleOutputs() <<
+    " or higher is required."
     ;
   /* clang-format on */
   mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
@@ -2336,7 +2371,7 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
   std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
   std::string const& module_dir,
   std::vector<std::string> const& linked_target_dirs,
-  std::string const& arg_lang)
+  std::string const& arg_lang, std::string const& arg_modmapfmt)
 {
   // Setup path conversions.
   {
@@ -2423,6 +2458,48 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
         build.Variables.emplace("restat", "1");
       }
 
+      if (arg_modmapfmt.empty()) {
+        // nothing to do.
+      } else {
+        std::stringstream mm;
+        if (arg_modmapfmt == "gcc") {
+          // 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 .\n";
+
+          for (auto const& l : object.Provides) {
+            auto m = mod_files.find(l.LogicalName);
+            if (m != mod_files.end()) {
+              mm << l.LogicalName << " " << this->ConvertToNinjaPath(m->second)
+                 << "\n";
+            }
+          }
+          for (auto const& r : object.Requires) {
+            auto m = mod_files.find(r.LogicalName);
+            if (m != mod_files.end()) {
+              mm << r.LogicalName << " " << this->ConvertToNinjaPath(m->second)
+                 << "\n";
+            }
+          }
+        } else {
+          cmSystemTools::Error(
+            cmStrCat("-E cmake_ninja_dyndep does not understand the ",
+                     arg_modmapfmt, " module map format"));
+          return false;
+        }
+
+        // XXX(modmap): If changing this path construction, change
+        // `cmNinjaTargetGenerator::WriteObjectBuildStatements` to generate the
+        // corresponding file path.
+        cmGeneratedFileStream mmf(cmStrCat(object.PrimaryOutput, ".modmap"));
+        mmf << mm.str();
+      }
+
       this->WriteBuild(ddf, build);
     }
   }
@@ -2446,6 +2523,7 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
   std::string arg_dd;
   std::string arg_lang;
   std::string arg_tdi;
+  std::string arg_modmapfmt;
   std::vector<std::string> arg_ddis;
   for (std::string const& arg : arg_full) {
     if (cmHasLiteralPrefix(arg, "--tdi=")) {
@@ -2454,6 +2532,8 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
       arg_lang = arg.substr(7);
     } else if (cmHasLiteralPrefix(arg, "--dd=")) {
       arg_dd = arg.substr(5);
+    } else if (cmHasLiteralPrefix(arg, "--modmapfmt=")) {
+      arg_modmapfmt = arg.substr(12);
     } else if (!cmHasLiteralPrefix(arg, "--") &&
                cmHasLiteralSuffix(arg, ".ddi")) {
       arg_ddis.push_back(arg);
@@ -2512,7 +2592,7 @@ int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
   if (!ggd ||
       !cm::static_reference_cast<cmGlobalNinjaGenerator>(ggd).WriteDyndepFile(
         dir_top_src, dir_top_bld, dir_cur_src, dir_cur_bld, arg_dd, arg_ddis,
-        module_dir, linked_target_dirs, arg_lang)) {
+        module_dir, linked_target_dirs, arg_lang, arg_modmapfmt)) {
     return 1;
   }
   return 0;

+ 11 - 9
Source/cmGlobalNinjaGenerator.h

@@ -396,15 +396,13 @@ public:
   bool HasOutputPathPrefix() const { return !this->OutputPathPrefix.empty(); }
   void StripNinjaOutputPathPrefixAsSuffix(std::string& path);
 
-  bool WriteDyndepFile(std::string const& dir_top_src,
-                       std::string const& dir_top_bld,
-                       std::string const& dir_cur_src,
-                       std::string const& dir_cur_bld,
-                       std::string const& arg_dd,
-                       std::vector<std::string> const& arg_ddis,
-                       std::string const& module_dir,
-                       std::vector<std::string> const& linked_target_dirs,
-                       std::string const& arg_lang);
+  bool WriteDyndepFile(
+    std::string const& dir_top_src, std::string const& dir_top_bld,
+    std::string const& dir_cur_src, std::string const& dir_cur_bld,
+    std::string const& arg_dd, std::vector<std::string> const& arg_ddis,
+    std::string const& module_dir,
+    std::vector<std::string> const& linked_target_dirs,
+    std::string const& arg_lang, std::string const& arg_modmapfmt);
 
   virtual std::string BuildAlias(const std::string& alias,
                                  const std::string& /*config*/) const
@@ -448,6 +446,8 @@ public:
 
   bool IsSingleConfigUtility(cmGeneratorTarget const* target) const;
 
+  bool CheckCxxModuleSupport();
+
 protected:
   void Generate() override;
 
@@ -568,6 +568,8 @@ private:
   bool NinjaSupportsMultipleOutputs = false;
   bool NinjaSupportsMetadataOnRegeneration = false;
 
+  bool DiagnosedCxxModuleSupport = false;
+
 private:
   void InitOutputPathPrefix();
 

+ 4 - 3
Source/cmMakefile.cxx

@@ -1216,9 +1216,10 @@ void cmMakefile::AddCustomCommandOldStyle(
   };
 
   // Each output must get its own copy of this rule.
-  cmsys::RegularExpression sourceFiles("\\.(C|M|c|c\\+\\+|cc|cpp|cxx|cu|m|mm|"
-                                       "rc|def|r|odl|idl|hpj|bat|h|h\\+\\+|"
-                                       "hm|hpp|hxx|in|txx|inl)$");
+  cmsys::RegularExpression sourceFiles(
+    "\\.(C|M|c|c\\+\\+|cc|cpp|cxx|mpp|cu|m|mm|"
+    "rc|def|r|odl|idl|hpj|bat|h|h\\+\\+|"
+    "hm|hpp|hxx|in|txx|inl)$");
 
   // Choose whether to use a main dependency.
   if (sourceFiles.find(source)) {

+ 108 - 25
Source/cmNinjaTargetGenerator.cxx

@@ -35,6 +35,7 @@
 #include "cmRange.h"
 #include "cmRulePlaceholderExpander.h"
 #include "cmSourceFile.h"
+#include "cmStandardLevelResolver.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
@@ -146,9 +147,26 @@ std::string cmNinjaTargetGenerator::LanguageDyndepRule(
     '_', config);
 }
 
-bool cmNinjaTargetGenerator::NeedDyndep(std::string const& lang) const
+bool cmNinjaTargetGenerator::NeedCxxModuleSupport(
+  std::string const& lang, std::string const& config) const
 {
-  return lang == "Fortran";
+  if (lang != "CXX") {
+    return false;
+  }
+  if (!this->Makefile->IsOn("CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP")) {
+    return false;
+  }
+  cmGeneratorTarget const* tgt = this->GetGeneratorTarget();
+  cmStandardLevelResolver standardResolver(this->Makefile);
+  bool const uses_cxx20 =
+    standardResolver.HaveStandardAvailable(tgt, "CXX", config, "cxx_std_20");
+  return uses_cxx20 && this->GetGlobalGenerator()->CheckCxxModuleSupport();
+}
+
+bool cmNinjaTargetGenerator::NeedDyndep(std::string const& lang,
+                                        std::string const& config) const
+{
+  return lang == "Fortran" || this->NeedCxxModuleSupport(lang, config);
 }
 
 std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget(
@@ -530,8 +548,9 @@ cmNinjaRule GetScanRule(
   scanVars.CMTargetName = vars.CMTargetName;
   scanVars.CMTargetType = vars.CMTargetType;
   scanVars.Language = vars.Language;
-  scanVars.Object = "$out"; // for RULE_LAUNCH_COMPILE
+  scanVars.Object = "$OBJ_FILE";
   scanVars.PreprocessedSource = "$out";
+  scanVars.DynDepFile = "$DYNDEP_INTERMEDIATE_FILE";
   scanVars.DependencyFile = rule.DepFile.c_str();
   scanVars.DependencyTarget = "$out";
 
@@ -586,7 +605,7 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
   cmMakefile* mf = this->GetMakefile();
 
   // For some cases we scan to dynamically discover dependencies.
-  bool const needDyndep = this->NeedDyndep(lang);
+  bool const needDyndep = this->NeedDyndep(lang, config);
   bool const compilationPreprocesses = !this->NeedExplicitPreprocessing(lang);
 
   std::string flags = "$FLAGS";
@@ -601,6 +620,10 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
       responseFlag = "@";
     }
   }
+  std::string const modmapFormatVar =
+    cmStrCat("CMAKE_EXPERIMENTAL_", lang, "_MODULE_MAP_FORMAT");
+  std::string const modmapFormat =
+    this->Makefile->GetSafeDefinition(modmapFormatVar);
 
   std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
     this->GetLocalGenerator()->CreateRulePlaceholderExpander());
@@ -624,16 +647,26 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
     // Rule to scan dependencies of sources that need preprocessing.
     {
       std::vector<std::string> scanCommands;
-      std::string const& scanRuleName =
-        this->LanguagePreprocessAndScanRule(lang, config);
-      std::string const& ppCommmand = mf->GetRequiredDefinition(
-        cmStrCat("CMAKE_", lang, "_PREPROCESS_SOURCE"));
-      cmExpandList(ppCommmand, scanCommands);
-      for (std::string& i : scanCommands) {
-        i = cmStrCat(launcher, i);
+      std::string scanRuleName;
+      if (compilationPreprocesses) {
+        scanRuleName = this->LanguageScanRule(lang, config);
+        std::string const& scanCommand = mf->GetRequiredDefinition(
+          cmStrCat("CMAKE_EXPERIMENTAL_", lang, "_SCANDEP_SOURCE"));
+        cmExpandList(scanCommand, scanCommands);
+        for (std::string& i : scanCommands) {
+          i = cmStrCat(launcher, i);
+        }
+      } else {
+        scanRuleName = this->LanguagePreprocessAndScanRule(lang, config);
+        std::string const& ppCommmand = mf->GetRequiredDefinition(
+          cmStrCat("CMAKE_", lang, "_PREPROCESS_SOURCE"));
+        cmExpandList(ppCommmand, scanCommands);
+        for (std::string& i : scanCommands) {
+          i = cmStrCat(launcher, i);
+        }
+        scanCommands.emplace_back(GetScanCommand(cmakeCmd, tdi, lang, "$out",
+                                                 "$DYNDEP_INTERMEDIATE_FILE"));
       }
-      scanCommands.emplace_back(GetScanCommand(cmakeCmd, tdi, lang, "$out",
-                                               "$DYNDEP_INTERMEDIATE_FILE"));
 
       auto scanRule = GetScanRule(
         scanRuleName, vars, responseFlag, flags, rulePlaceholderExpander.get(),
@@ -641,12 +674,18 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
 
       scanRule.Comment =
         cmStrCat("Rule for generating ", lang, " dependencies.");
-      scanRule.Description = cmStrCat("Building ", lang, " preprocessed $out");
+      if (compilationPreprocesses) {
+        scanRule.Description =
+          cmStrCat("Scanning $in for ", lang, " dependencies");
+      } else {
+        scanRule.Description =
+          cmStrCat("Building ", lang, " preprocessed $out");
+      }
 
       this->GetGlobalGenerator()->AddRule(scanRule);
     }
 
-    {
+    if (!compilationPreprocesses) {
       // Compilation will not preprocess, so it does not need the defines
       // unless the compiler wants them for some other purpose.
       if (!this->CompileWithDefines(lang)) {
@@ -681,12 +720,16 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
 
     // Run CMake dependency scanner on the source file (using the preprocessed
     // source if that was performed).
+    std::string ddModmapArg;
+    if (!modmapFormat.empty()) {
+      ddModmapArg += cmStrCat(" --modmapfmt=", modmapFormat);
+    }
     {
       std::vector<std::string> ddCmds;
       {
-        std::string ccmd =
-          cmStrCat(cmakeCmd, " -E cmake_ninja_dyndep --tdi=", tdi,
-                   " --lang=", lang, " --dd=$out @", rule.RspFile);
+        std::string ccmd = cmStrCat(
+          cmakeCmd, " -E cmake_ninja_dyndep --tdi=", tdi, " --lang=", lang,
+          ddModmapArg, " --dd=$out @", rule.RspFile);
         ddCmds.emplace_back(std::move(ccmd));
       }
       rule.Command = this->GetLocalGenerator()->BuildCommandLine(ddCmds);
@@ -748,6 +791,14 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang,
     }
   }
 
+  if (needDyndep && !modmapFormat.empty()) {
+    std::string modmapFlags = mf->GetRequiredDefinition(
+      cmStrCat("CMAKE_EXPERIMENTAL_", lang, "_MODULE_MAP_FLAG"));
+    cmSystemTools::ReplaceString(modmapFlags, "<MODULE_MAP_FILE>",
+                                 "$DYNDEP_MODULE_MAP_FILE");
+    flags += cmStrCat(' ', modmapFlags);
+  }
+
   vars.Flags = flags.c_str();
   vars.DependencyFile = rule.DepFile.c_str();
 
@@ -1053,6 +1104,7 @@ cmNinjaBuild GetScanBuildStatement(const std::string& ruleName,
                                    const std::string& ppFileName,
                                    bool compilePP, bool compilePPWithDefines,
                                    cmNinjaBuild& objBuild, cmNinjaVars& vars,
+                                   std::string const& modmapFormat,
                                    const std::string& objectFileName,
                                    cmLocalGenerator* lg)
 {
@@ -1123,6 +1175,15 @@ cmNinjaBuild GetScanBuildStatement(const std::string& ruleName,
     vars.erase("DEP_FILE");
   }
 
+  if (!modmapFormat.empty()) {
+    // XXX(modmap): If changing this path construction, change
+    // `cmGlobalNinjaGenerator::WriteDyndep` to expect the corresponding
+    // file path.
+    std::string const ddModmapFile = cmStrCat(objectFileName, ".modmap");
+    scanBuild.Variables["DYNDEP_MODULE_MAP_FILE"] = ddModmapFile;
+    scanBuild.ImplicitOuts.push_back(ddModmapFile);
+  }
+
   return scanBuild;
 }
 }
@@ -1262,10 +1323,17 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
   }
 
   // For some cases we scan to dynamically discover dependencies.
-  bool const needDyndep = this->NeedDyndep(language);
+  bool const needDyndep = this->NeedDyndep(language, config);
   bool const compilationPreprocesses =
     !this->NeedExplicitPreprocessing(language);
 
+  std::string modmapFormat;
+  if (needDyndep) {
+    std::string const modmapFormatVar =
+      cmStrCat("CMAKE_EXPERIMENTAL_", language, "_MODULE_MAP_FORMAT");
+    modmapFormat = this->Makefile->GetSafeDefinition(modmapFormatVar);
+  }
+
   if (needDyndep) {
     // If source/target has preprocessing turned off, we still need to
     // generate an explicit dependency step
@@ -1295,7 +1363,7 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
 
     cmNinjaBuild ppBuild = GetScanBuildStatement(
       scanRuleName, ppFileName, compilePP, compilePPWithDefines, objBuild,
-      vars, objectFileName, this->LocalGenerator);
+      vars, modmapFormat, objectFileName, this->LocalGenerator);
 
     if (compilePP) {
       // In case compilation requires flags that are incompatible with
@@ -1331,6 +1399,12 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
     std::string const dyndep = this->GetDyndepFilePath(language, config);
     objBuild.OrderOnlyDeps.push_back(dyndep);
     vars["dyndep"] = dyndep;
+
+    if (!modmapFormat.empty()) {
+      std::string const ddModmapFile = cmStrCat(objectFileName, ".modmap");
+      vars["DYNDEP_MODULE_MAP_FILE"] = ddModmapFile;
+      objBuild.OrderOnlyDeps.push_back(ddModmapFile);
+    }
   }
 
   this->EnsureParentDirectoryExists(objectFileName);
@@ -1444,17 +1518,26 @@ void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
   tdi["compiler-id"] = this->Makefile->GetSafeDefinition(
     cmStrCat("CMAKE_", lang, "_COMPILER_ID"));
 
+  std::string mod_dir;
   if (lang == "Fortran") {
-    std::string mod_dir = this->GeneratorTarget->GetFortranModuleDirectory(
+    mod_dir = this->GeneratorTarget->GetFortranModuleDirectory(
       this->Makefile->GetHomeOutputDirectory());
-    if (mod_dir.empty()) {
-      mod_dir = this->Makefile->GetCurrentBinaryDirectory();
-    }
-    tdi["module-dir"] = mod_dir;
+  } else if (lang == "CXX") {
+    mod_dir =
+      cmSystemTools::CollapseFullPath(this->GeneratorTarget->ObjectDirectory);
+  }
+  if (mod_dir.empty()) {
+    mod_dir = this->Makefile->GetCurrentBinaryDirectory();
+  }
+  tdi["module-dir"] = mod_dir;
+
+  if (lang == "Fortran") {
     tdi["submodule-sep"] =
       this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP");
     tdi["submodule-ext"] =
       this->Makefile->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT");
+  } else if (lang == "CXX") {
+    // No extra information necessary.
   }
 
   tdi["dir-cur-bld"] = this->Makefile->GetCurrentBinaryDirectory();

+ 3 - 1
Source/cmNinjaTargetGenerator.h

@@ -71,9 +71,11 @@ protected:
                                const std::string& config) const;
   std::string LanguageDyndepRule(std::string const& lang,
                                  const std::string& config) const;
-  bool NeedDyndep(std::string const& lang) const;
+  bool NeedDyndep(std::string const& lang, std::string const& config) const;
   bool NeedExplicitPreprocessing(std::string const& lang) const;
   bool CompileWithDefines(std::string const& lang) const;
+  bool NeedCxxModuleSupport(std::string const& lang,
+                            std::string const& config) const;
 
   std::string OrderDependsTargetForTarget(const std::string& config);
 

+ 5 - 0
Source/cmRulePlaceholderExpander.cxx

@@ -44,6 +44,11 @@ std::string cmRulePlaceholderExpander::ExpandRuleVariable(
       return replaceValues.Source;
     }
   }
+  if (replaceValues.DynDepFile) {
+    if (variable == "DYNDEP_FILE") {
+      return replaceValues.DynDepFile;
+    }
+  }
   if (replaceValues.PreprocessedSource) {
     if (variable == "PREPROCESSED_SOURCE") {
       return replaceValues.PreprocessedSource;

+ 1 - 0
Source/cmRulePlaceholderExpander.h

@@ -41,6 +41,7 @@ public:
     const char* Source = nullptr;
     const char* AssemblySource = nullptr;
     const char* PreprocessedSource = nullptr;
+    const char* DynDepFile = nullptr;
     const char* Output = nullptr;
     const char* Object = nullptr;
     const char* ObjectDir = nullptr;

+ 11 - 13
Source/cmScanDepFormat.cxx

@@ -55,9 +55,8 @@ static Json::Value EncodeFilename(std::string const& path)
 #define PARSE_BLOB(val, res)                                                  \
   do {                                                                        \
     if (!ParseFilename(val, res)) {                                           \
-      cmSystemTools::Error(                                                   \
-        cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp,           \
-                 ": invalid blob"));                                          \
+      cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", \
+                                    arg_pp, ": invalid blob"));               \
       return false;                                                           \
     }                                                                         \
   } while (0)
@@ -65,9 +64,8 @@ static Json::Value EncodeFilename(std::string const& path)
 #define PARSE_FILENAME(val, res)                                              \
   do {                                                                        \
     if (!ParseFilename(val, res)) {                                           \
-      cmSystemTools::Error(                                                   \
-        cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp,           \
-                 ": invalid filename"));                                      \
+      cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", \
+                                    arg_pp, ": invalid filename"));           \
       return false;                                                           \
     }                                                                         \
                                                                               \
@@ -84,7 +82,7 @@ bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, cmSourceInfo* info)
   {
     Json::Reader reader;
     if (!reader.parse(ppf, ppio, false)) {
-      cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ",
+      cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
                                     arg_pp,
                                     reader.getFormattedErrorMessages()));
       return false;
@@ -93,7 +91,7 @@ bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, cmSourceInfo* info)
 
   Json::Value const& version = ppi["version"];
   if (version.asUInt() != 0) {
-    cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ",
+    cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
                                   arg_pp, ": version ", version.asString()));
     return false;
   }
@@ -101,7 +99,7 @@ bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, cmSourceInfo* info)
   Json::Value const& rules = ppi["rules"];
   if (rules.isArray()) {
     if (rules.size() != 1) {
-      cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ",
+      cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
                                     arg_pp, ": expected 1 source entry"));
       return false;
     }
@@ -109,9 +107,9 @@ bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, cmSourceInfo* info)
     for (auto const& rule : rules) {
       Json::Value const& workdir = rule["work-directory"];
       if (!workdir.isString()) {
-        cmSystemTools::Error(
-          cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp,
-                   ": work-directory is not a string"));
+        cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ",
+                                      arg_pp,
+                                      ": work-directory is not a string"));
         return false;
       }
       std::string work_directory;
@@ -134,7 +132,7 @@ bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, cmSourceInfo* info)
           if (outputs.isArray()) {
             if (outputs.empty()) {
               cmSystemTools::Error(
-                cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp,
+                cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp,
                          ": expected at least one 1 output"));
               return false;
             }

+ 1 - 1
Source/cmSourceFile.h

@@ -175,7 +175,7 @@ private:
 #define CM_HEADER_REGEX "\\.(h|hh|h\\+\\+|hm|hpp|hxx|in|txx|inl)$"
 
 #define CM_SOURCE_REGEX                                                       \
-  "\\.(C|F|M|c|c\\+\\+|cc|cpp|cxx|cu|f|f90|for|fpp|ftn|m|mm|"                 \
+  "\\.(C|F|M|c|c\\+\\+|cc|cpp|mpp|cxx|cu|f|f90|for|fpp|ftn|m|mm|"             \
   "rc|def|r|odl|idl|hpj|bat)$"
 
 #define CM_PCH_REGEX "cmake_pch(_[^.]+)?\\.(h|hxx)$"

+ 16 - 15
Source/cmStandardLevelResolver.cxx

@@ -44,6 +44,16 @@ struct StandardNeeded
   int value;
 };
 
+int ParseStd(std::string const& level)
+{
+  try {
+    return std::stoi(level);
+  } catch (std::invalid_argument&) {
+    // Fall through to use an invalid value.
+  }
+  return -1;
+}
+
 struct StanardLevelComputer
 {
   explicit StanardLevelComputer(std::string lang, std::vector<int> levels,
@@ -113,17 +123,8 @@ struct StanardLevelComputer
       standardStr = "03";
     }
 
-    int standardValue = -1;
-    int defaultValue = -1;
-    try {
-      standardValue = std::stoi(standardStr);
-      defaultValue = std::stoi(*defaultStd);
-    } catch (std::invalid_argument&) {
-      // fall through as we want an error
-      // when we can't find the bad value in the `stds` vector
-    }
-
-    auto stdIt = std::find(cm::cbegin(stds), cm::cend(stds), standardValue);
+    auto stdIt =
+      std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(standardStr));
     if (stdIt == cm::cend(stds)) {
       std::string e =
         cmStrCat(this->Language, "_STANDARD is set to invalid value '",
@@ -134,7 +135,7 @@ struct StanardLevelComputer
     }
 
     auto defaultStdIt =
-      std::find(cm::cbegin(stds), cm::cend(stds), defaultValue);
+      std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(*defaultStd));
     if (defaultStdIt == cm::cend(stds)) {
       std::string e = cmStrCat("CMAKE_", this->Language,
                                "_STANDARD_DEFAULT is set to invalid value '",
@@ -195,7 +196,7 @@ struct StanardLevelComputer
     if (existingStandard) {
       existingLevelIter =
         std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
-                  std::stoi(*existingStandard));
+                  ParseStd(*existingStandard));
       if (existingLevelIter == cm::cend(this->Levels)) {
         const std::string e =
           cmStrCat("The ", this->Language, "_STANDARD property on target \"",
@@ -240,7 +241,7 @@ struct StanardLevelComputer
     }
     // convert defaultStandard to an integer
     if (std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
-                  std::stoi(*defaultStandard)) == cm::cend(this->Levels)) {
+                  ParseStd(*defaultStandard)) == cm::cend(this->Levels)) {
       const std::string e = cmStrCat("The CMAKE_", this->Language,
                                      "_STANDARD_DEFAULT variable contains an "
                                      "invalid value: \"",
@@ -257,7 +258,7 @@ struct StanardLevelComputer
 
     auto existingLevelIter =
       std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
-                std::stoi(*existingStandard));
+                ParseStd(*existingStandard));
     if (existingLevelIter == cm::cend(this->Levels)) {
       const std::string e =
         cmStrCat("The ", this->Language, "_STANDARD property on target \"",

+ 3 - 2
Source/cmake.cxx

@@ -208,8 +208,9 @@ cmake::cmake(Role role, cmState::Mode mode)
     };
 
     // The "c" extension MUST precede the "C" extension.
-    setupExts(this->CLikeSourceFileExtensions,
-              { "c", "C", "c++", "cc", "cpp", "cxx", "cu", "m", "M", "mm" });
+    setupExts(
+      this->CLikeSourceFileExtensions,
+      { "c", "C", "c++", "cc", "cpp", "cxx", "cu", "mpp", "m", "M", "mm" });
     setupExts(this->HeaderFileExtensions,
               { "h", "hh", "h++", "hm", "hpp", "hxx", "in", "txx" });
     setupExts(this->CudaFileExtensions, { "cu" });