From 533386ca2961060b81fce2f0532a55ed76b1b53d Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 5 Jan 2021 09:28:19 -0500 Subject: cmStandardLevelResolver: Factor out helper to capture stoi exceptions --- Source/cmStandardLevelResolver.cxx | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Source/cmStandardLevelResolver.cxx b/Source/cmStandardLevelResolver.cxx index 5d8ccf1..bf6925e 100644 --- a/Source/cmStandardLevelResolver.cxx +++ b/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 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 \"", -- cgit v0.12 From dacd93a2dbea64d4021ef2ddc4cd1ad03be415b6 Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 1 Dec 2020 16:31:14 -0500 Subject: ninja: De-duplicate version numbers required for ninja features --- Source/cmGlobalNinjaGenerator.cxx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index a098f81..b0b0cd6 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -766,7 +766,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 +786,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()); -- cgit v0.12 From 988f9971006d09d675f8d406367d9021915ef9c0 Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 1 Dec 2020 16:47:44 -0500 Subject: cmScanDepFormat: Fix name of our internal tool in parse errors We parse the scan result `.ddi` files in `-E cmake_ninja_dyndep`, not `-E cmake_ninja_depends`. --- Source/cmScanDepFormat.cxx | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Source/cmScanDepFormat.cxx b/Source/cmScanDepFormat.cxx index 40bf4c9..e046069 100644 --- a/Source/cmScanDepFormat.cxx +++ b/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; } -- cgit v0.12 From b0fc2993e11fb8b4bcf8a44e6d165f5634ad21bb Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Thu, 4 Apr 2019 15:31:56 -0400 Subject: Treat the '.mpp' file extension as C++ code This is the extension required in build2 for C++ module support. --- Modules/CMakeCXXCompiler.cmake.in | 2 +- Source/cmMakefile.cxx | 7 ++++--- Source/cmSourceFile.h | 2 +- Source/cmake.cxx | 5 +++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Modules/CMakeCXXCompiler.cmake.in b/Modules/CMakeCXXCompiler.cmake.in index 92ae2ab..45acfe7 100644 --- a/Modules/CMakeCXXCompiler.cmake.in +++ b/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) diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 9d37d61..348a06c 100644 --- a/Source/cmMakefile.cxx +++ b/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)) { diff --git a/Source/cmSourceFile.h b/Source/cmSourceFile.h index 94b5cc8..76a5ded 100644 --- a/Source/cmSourceFile.h +++ b/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)$" diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 1691037..fb8fb3e 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -325,8 +325,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" }); -- cgit v0.12 From f814d3b3c6f5ba45854da06c341af57bad2590b4 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 12 Mar 2019 15:34:27 -0400 Subject: cmNinjaTargetGenerator: use $OBJ_FILE for the object This will not be $out in all cases in the future. --- Source/cmNinjaTargetGenerator.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index 9075563..d3f4a00 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -529,7 +529,7 @@ 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.DependencyFile = rule.DepFile.c_str(); scanVars.DependencyTarget = "$out"; -- cgit v0.12 From 4b233591179f9fe266e9157ef89e0a97854aa54c Mon Sep 17 00:00:00 2001 From: Brad King Date: Tue, 24 Nov 2020 14:56:25 -0500 Subject: ninja: Add experimental infrastructure for C++20 module dependency scanning Optionally enable this infrastructure through an undocumented `CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP` variable. Currently this is experimental and intended for use by compiler writers to implement their scanning tools. Warn as such when the feature is activated. Later when compilers provide the needed scanning tools we can enable this variable from our corresponding compiler information modules. It is never meant to be set by project code. When enabled, generate a build graph similar to what we use for Fortran module dependencies. There are some differences needed because we can scan dependencies without explicit preprocessing, and can directly compile the original source afterward. Co-Author: Ben Boeckel --- Help/dev/experimental.rst | 42 +++++++++++++++++- Source/cmGlobalNinjaGenerator.cxx | 32 ++++++++++++++ Source/cmGlobalNinjaGenerator.h | 4 ++ Source/cmNinjaTargetGenerator.cxx | 84 +++++++++++++++++++++++++++--------- Source/cmNinjaTargetGenerator.h | 4 +- Source/cmRulePlaceholderExpander.cxx | 5 +++ Source/cmRulePlaceholderExpander.h | 1 + 7 files changed, 150 insertions(+), 22 deletions(-) diff --git a/Help/dev/experimental.rst b/Help/dev/experimental.rst index 4d2b076..4cf1c62 100644 --- a/Help/dev/experimental.rst +++ b/Help/dev/experimental.rst @@ -7,4 +7,44 @@ 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 + " " + " -MT -MD -MF " + " ${flags_to_scan_deps} -fdep-file= -fdep-output=" + ) + +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 ```` placeholder, and write module +dependencies to the file specified by the ```` 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. + +.. _`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 diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index b0b0cd6..16cdcd3 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/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) { diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h index 5e9defe..b21586b 100644 --- a/Source/cmGlobalNinjaGenerator.h +++ b/Source/cmGlobalNinjaGenerator.h @@ -445,6 +445,8 @@ public: bool IsSingleConfigUtility(cmGeneratorTarget const* target) const; + bool CheckCxxModuleSupport(); + protected: void Generate() override; @@ -565,6 +567,8 @@ private: bool NinjaSupportsMultipleOutputs = false; bool NinjaSupportsMetadataOnRegeneration = false; + bool DiagnosedCxxModuleSupport = false; + private: void InitOutputPathPrefix(); diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index d3f4a00..124e885 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/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( @@ -531,6 +549,7 @@ cmNinjaRule GetScanRule( scanVars.Language = vars.Language; scanVars.Object = "$OBJ_FILE"; scanVars.PreprocessedSource = "$out"; + scanVars.DynDepFile = "$DYNDEP_INTERMEDIATE_FILE"; scanVars.DependencyFile = rule.DepFile.c_str(); scanVars.DependencyTarget = "$out"; @@ -585,7 +604,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"; @@ -623,16 +642,26 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang, // Rule to scan dependencies of sources that need preprocessing. { std::vector 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(), @@ -640,12 +669,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)) { @@ -1258,7 +1293,7 @@ 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); @@ -1440,17 +1475,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(); diff --git a/Source/cmNinjaTargetGenerator.h b/Source/cmNinjaTargetGenerator.h index 83a4342..79dc622 100644 --- a/Source/cmNinjaTargetGenerator.h +++ b/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); diff --git a/Source/cmRulePlaceholderExpander.cxx b/Source/cmRulePlaceholderExpander.cxx index 5363fef..d00bbdf 100644 --- a/Source/cmRulePlaceholderExpander.cxx +++ b/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; diff --git a/Source/cmRulePlaceholderExpander.h b/Source/cmRulePlaceholderExpander.h index 710f8a6..f8dc368 100644 --- a/Source/cmRulePlaceholderExpander.h +++ b/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; -- cgit v0.12 From 791b4d26d6cbcb69615ddfbd1872a34a2e214b39 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 10 Feb 2020 11:18:46 -0500 Subject: ninja: add experimental infrastructure to generate modmap files with dyndep The scan step may need to output additional information for the compiler, not just the build tool. The modmap is assumed to be beside the object output. Additional refactoring may open up a channel to inform per-source paths to the dyndep rule in the future, but is not done here. --- Source/cmGlobalNinjaGenerator.cxx | 26 ++++++++++++++++++++-- Source/cmGlobalNinjaGenerator.h | 16 ++++++------- Source/cmNinjaTargetGenerator.cxx | 47 +++++++++++++++++++++++++++++++++++---- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index 16cdcd3..25d6a56 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -2369,7 +2369,7 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile( std::string const& arg_dd, std::vector const& arg_ddis, std::string const& module_dir, std::vector const& linked_target_dirs, - std::string const& arg_lang) + std::string const& arg_lang, std::string const& arg_modmapfmt) { // Setup path conversions. { @@ -2456,6 +2456,25 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile( build.Variables.emplace("restat", "1"); } + if (arg_modmapfmt.empty()) { + // nothing to do. + } else { + std::stringstream mm; + if (false) { + } 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); } } @@ -2479,6 +2498,7 @@ int cmcmd_cmake_ninja_dyndep(std::vector::const_iterator argBeg, std::string arg_dd; std::string arg_lang; std::string arg_tdi; + std::string arg_modmapfmt; std::vector arg_ddis; for (std::string const& arg : arg_full) { if (cmHasLiteralPrefix(arg, "--tdi=")) { @@ -2487,6 +2507,8 @@ int cmcmd_cmake_ninja_dyndep(std::vector::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); @@ -2545,7 +2567,7 @@ int cmcmd_cmake_ninja_dyndep(std::vector::const_iterator argBeg, if (!ggd || !cm::static_reference_cast(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; diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h index b21586b..96065ed 100644 --- a/Source/cmGlobalNinjaGenerator.h +++ b/Source/cmGlobalNinjaGenerator.h @@ -393,15 +393,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 const& arg_ddis, - std::string const& module_dir, - std::vector 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 const& arg_ddis, + std::string const& module_dir, + std::vector 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 diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index 124e885..5aaa79e 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -619,6 +619,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 rulePlaceholderExpander( this->GetLocalGenerator()->CreateRulePlaceholderExpander()); @@ -715,12 +719,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 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); @@ -782,6 +790,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, "", + "$DYNDEP_MODULE_MAP_FILE"); + flags += cmStrCat(' ', modmapFlags); + } + vars.Flags = flags.c_str(); vars.DependencyFile = rule.DepFile.c_str(); @@ -1084,6 +1100,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) { @@ -1154,6 +1171,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; } } @@ -1297,6 +1323,13 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( 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 @@ -1326,7 +1359,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 @@ -1362,6 +1395,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); + } } EnsureParentDirectoryExists(objectFileName); -- cgit v0.12 From 39cbbb59a52c63cd90eebaecea64fd42c3fad1d8 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 12 Mar 2019 15:36:12 -0400 Subject: ninja: add experimental infrastructure to generate gcc-format modmap files --- Help/dev/experimental.rst | 20 ++++++++++++++++++++ Source/cmGlobalNinjaGenerator.cxx | 25 ++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Help/dev/experimental.rst b/Help/dev/experimental.rst index 4cf1c62..d019161 100644 --- a/Help/dev/experimental.rst +++ b/Help/dev/experimental.rst @@ -45,6 +45,26 @@ 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=") + +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 diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index 25d6a56..b7ca9b1 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -2460,7 +2460,30 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile( // nothing to do. } else { std::stringstream mm; - if (false) { + 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 ", -- cgit v0.12