From 5745a2fe944dca5612b31c099101914d6c60b223 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 14 Dec 2023 07:42:44 +0200 Subject: Allow enabling C++ modules for C++20 and later std.cxx values --- libbuild2/cc/compile-rule.cxx | 8 +- libbuild2/cxx/init.cxx | 325 +++++++++++++++++++++++++++---------- tests/cc/modules/common.testscript | 10 +- tests/cc/preprocessed/testscript | 1 + 4 files changed, 249 insertions(+), 95 deletions(-) diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index be5a680..b4d405b 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -5393,7 +5393,9 @@ namespace build2 if (!modules) { if (ut != unit_type::non_modular || !mi.imports.empty ()) - fail << "modules support required by " << src; + fail << "modules support required by " << src << + info << "consider enabling modules with " + << x << ".features.modules=true in root.build"; } else { @@ -6574,6 +6576,10 @@ namespace build2 // Find or create a modules sidebuild subproject returning its root // directory. // + // @@ Could we omit creating a subproject if the sidebuild scope is the + // project scope itself? This would speed up simple examples (and + // potentially direct compilation that we may support). + // pair compile_rule:: find_modules_sidebuild (const scope& rs) const { diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx index 8a2ad88..7dadce8 100644 --- a/libbuild2/cxx/init.cxx +++ b/libbuild2/cxx/init.cxx @@ -175,6 +175,10 @@ namespace build2 i = mode.insert (i, move (o)) + 1; }; + // Derive approximate __cplusplus value from the standard if possible. + // + optional cplusplus; + switch (cl) { case compiler_class::msvc: @@ -204,6 +208,26 @@ namespace build2 { if (v14_3) o = "/std:c++latest"; + + // According to the documentation: + // + // "The value of __cplusplus with the /std:c++latest option + // depends on the version of Visual Studio. It's always at least + // one higher than the highest supported __cplusplus standard + // value supported by your version of Visual Studio." + // + if (v16_11) + cplusplus = 202002 + 1; + else if (v16_0) + cplusplus = 201703 + 1; + else if (v14_3) + cplusplus = 201402 + 1; + else if (mj >= 19) + cplusplus = 201402; + else if (mj >= 16) + cplusplus = 201103; + else + cplusplus = 199711; } else if (latest) { @@ -220,9 +244,24 @@ namespace build2 o = "/std:c++17"; else if (v14_3) o = "/std:c++latest"; + + if (v16_11) + cplusplus = 202002; + else if (v16_0) + cplusplus = 201703; + else if (v14_3) + cplusplus = 201402 + 1; + else if (mj >= 19) + cplusplus = 201402; + else if (mj >= 16) + cplusplus = 201103; + else + cplusplus = 199711; } else if (v == nullptr) - ; + { + // @@ TODO: map defaults to cplusplus for each version. + } else if (!stdcmp ("98") && !stdcmp ("03")) { bool sup (false); @@ -230,10 +269,12 @@ namespace build2 if (stdcmp ("11", "0x")) // C++11 since VS2010/10.0. { sup = mj >= 16; + cplusplus = 201103; } else if (stdcmp ("14", "1y")) // C++14 since VS2015/14.0. { sup = mj >= 19; + cplusplus = 201402; } else if (stdcmp ("17", "1z")) // C++17 since VS2015/14.0u2. { @@ -241,10 +282,12 @@ namespace build2 // sup = (mj > 19 || (mj == 19 && (mi > 0 || (mi == 0 && p >= 23918)))); + cplusplus = 201703; } else if (stdcmp ("20", "2a")) // C++20 since VS2019/16.11. { sup = v16_11; + cplusplus = 202002; } if (!sup) @@ -263,6 +306,8 @@ namespace build2 else if (stdcmp ("17", "1z")) o = "/std:c++latest"; } } + else + cplusplus = 199711; if (!o.empty ()) prepend (move (o)); @@ -288,11 +333,33 @@ namespace build2 { case compiler_type::gcc: { - if (mj >= 11) o = "-std=c++23"; // 23 - else if (mj >= 8) o = "-std=c++2a"; // 20 - else if (mj >= 5) o = "-std=c++1z"; // 17 - else if (mj == 4 && mi >= 8) o = "-std=c++1y"; // 14 - else if (mj == 4 && mi >= 4) o = "-std=c++0x"; // 11 + if (mj >= 11) + { + o = "-std=c++23"; + cplusplus = 202302; + } + else if (mj >= 8) + { + o = "-std=c++2a"; + cplusplus = 202002; + } + else if (mj >= 5) + { + o = "-std=c++1z"; + cplusplus = 201703; + } + else if (mj == 4 && mi >= 8) + { + o = "-std=c++1y"; + cplusplus = 201402; + } + else if (mj == 4 && mi >= 4) + { + o = "-std=c++0x"; + cplusplus = 201103; + } + else + cplusplus = 199711; break; } @@ -310,21 +377,56 @@ namespace build2 // MSVC. // - if (mj >= 13) o = "-std=c++2b"; - else if (mj == 10 && - latest && tt.system == "win32-msvc") o = "-std=c++17"; - else if (mj >= 5) o = "-std=c++2a"; - else if (mj > 3 || (mj == 3 && mi >= 5)) o = "-std=c++1z"; - else if (mj == 3 && mi >= 4) o = "-std=c++1y"; - else /* ??? */ o = "-std=c++0x"; + if (mj >= 13) + { + o = "-std=c++2b"; + cplusplus = 202302; + } + else if (mj == 10 && latest && tt.system == "win32-msvc") + { + o = "-std=c++17"; + cplusplus = 201703; + } + else if (mj >= 5) + { + o = "-std=c++2a"; + cplusplus = 202002; + } + else if (mj > 3 || (mj == 3 && mi >= 5)) + { + o = "-std=c++1z"; + cplusplus = 201703; + } + else if (mj == 3 && mi >= 4) + { + o = "-std=c++1y"; + cplusplus = 201402; + } + else /* ??? */ + { + o = "-std=c++0x"; + cplusplus = 201103; + } break; } case compiler_type::icc: { - if (mj >= 17) o = "-std=c++1z"; - else if (mj > 15 || (mj == 15 && p >= 3)) o = "-std=c++1y"; - else /* ??? */ o = "-std=c++0x"; + if (mj >= 17) + { + o = "-std=c++1z"; + cplusplus = 201703; + } + else if (mj > 15 || (mj == 15 && p >= 3)) + { + o = "-std=c++1y"; + cplusplus = 201402; + } + else /* ??? */ + { + o = "-std=c++0x"; + cplusplus = 201103; + } break; } @@ -333,24 +435,33 @@ namespace build2 } } else if (v == nullptr) - ; + { + // @@ TODO: map defaults to cplusplus for each version. + } else { // Translate 11 to 0x, 14 to 1y, 17 to 1z, 20 to 2a, 23 to 2b, and // 26 to 2c for compatibility with older versions of the // compilers. // + // @@ TMP: update C++26 __cplusplus value once known. + // o = "-std="; - if (stdcmp ("26", "2c")) o += "c++2c"; - else if (stdcmp ("23", "2b")) o += "c++2b"; - else if (stdcmp ("20", "2a")) o += "c++2a"; - else if (stdcmp ("17", "1z")) o += "c++1z"; - else if (stdcmp ("14", "1y")) o += "c++1y"; - else if (stdcmp ("11", "0x")) o += "c++0x"; - else if (stdcmp ("03") ) o += "c++03"; - else if (stdcmp ("98") ) o += "c++98"; - else o += *v; // In case the user specifies `gnu++NN` or some such. + if (stdcmp ("26", "2c")) {o += "c++2c"; cplusplus = 202400;} + else if (stdcmp ("23", "2b")) {o += "c++2b"; cplusplus = 202302;} + else if (stdcmp ("20", "2a")) {o += "c++2a"; cplusplus = 202002;} + else if (stdcmp ("17", "1z")) {o += "c++1z"; cplusplus = 201703;} + else if (stdcmp ("14", "1y")) {o += "c++1y"; cplusplus = 201402;} + else if (stdcmp ("11", "0x")) {o += "c++0x"; cplusplus = 201103;} + else if (stdcmp ("03") ) {o += "c++03"; cplusplus = 199711;} + else if (stdcmp ("98") ) {o += "c++98"; cplusplus = 199711;} + else + { + o += *v; // In case the user specifies `gnu++NN` or some such. + + // @@ TODO: can we still try to derive cplusplus value? + } } if (!o.empty ()) @@ -360,6 +471,8 @@ namespace build2 } } + // Additional experimental options. + // if (experimental) { switch (ct) @@ -379,90 +492,124 @@ namespace build2 default: break; } + } - // Unless disabled by the user, try to enable C++ modules. - // - if (!modules.value || *modules.value) + // Unless disabled by the user, try to enable C++ modules. + // + // NOTE: see also diagnostics about modules support required in compile + // rule. + // + if (!modules.value || *modules.value) + { + switch (ct) { - switch (ct) + case compiler_type::msvc: { - case compiler_type::msvc: + // Modules are enabled by default in /std:c++20 and + // /std:c++latest with both defining __cpp_modules to 201907 + // (final C++20 module), at least as of 17.6 (LTS). + // + // @@ Should we enable modules by default? There are still some + // serious bugs, like inability to both `import std;` and + // `#include ` in the same translation unit (see Visual + // Studio issue #10541166). + // + if (modules.value) { - // Modules are enabled by default in /std:c++20 and - // /std:c++latest with both defining __cpp_modules to 201907 - // (final C++20 module), at least as of 17.6 (LTS). - // - // @@ Should we enable modules by default? - // - if (modules.value) + if (cplusplus && *cplusplus < 202002) { - if (mj < 19 || (mj == 19 && mi < 36)) - { - fail << "support for C++ modules requires MSVC 17.6 or later" << - info << "C++ compiler is " << ci.signature << - info << "required by " << project (rs) << '@' << rs; - } + fail << "support for C++ modules requires C++20 or later" << + info << "standard in use is " << *cplusplus << + info << "required by " << project (rs) << '@' << rs; + } - modules = true; + if (mj < 19 || (mj == 19 && mi < 36)) + { + fail << "support for C++ modules requires MSVC 17.6 or later" << + info << "C++ compiler is " << ci.signature << + info << "required by " << project (rs) << '@' << rs; } - break; + modules = true; } - case compiler_type::gcc: + + break; + } + case compiler_type::gcc: + { + // We use the module mapper support which is only available since + // GCC 11. And since we are not yet capable of supporting + // generated headers via the mapper, we require the user to + // explicitly request modules. + // + // @@ Actually, now that we pre-generate headers by default, this + // is probably no longer the reason. But GCC modules being + // unusable due to bugs is stil a reason. + // + if (modules.value) { - // We use the module mapper support which is only available - // since GCC 11. And since we are not yet capable of supporting - // generated headers via the mapper, we require the user to - // explicitly request modules. - // - // @@ Actually, now that we pre-generate headers by default, - // this is probably no longer the reason. But GCC modules being - // unusable due to bugs is a reason enough. - // - if (mj >= 11 && modules.value) + if (cplusplus && *cplusplus < 202002) { - // Defines __cpp_modules: - // - // 11 -- 201810 - // - prepend ("-fmodules-ts"); - modules = true; + fail << "support for C++ modules requires C++20 or later" << + info << "standard in use is " << *cplusplus << + info << "required by " << project (rs) << '@' << rs; } - break; - } - case compiler_type::clang: - { - // Things (command line options, semantics) changed quite a bit - // around Clang 16 so we don't support anything earlier than - // that (it's not practically usable anyway). + if (mj < 11) + { + fail << "support for C++ modules requires GCC 11 or later" << + info << "C++ compiler is " << ci.signature << + info << "required by " << project (rs) << '@' << rs; + } + + // Defines __cpp_modules: // - // Clang enable modules by default in c++20 or later but they - // don't yet (as of Clang 18) define __cpp_modules. When they - // do, we can consider enabling modules by default on our side. - // For now, we only enable modules if forced with explicit - // cxx.features.modules=true. + // 11 -- 201810 // - if (modules.value) + prepend ("-fmodules-ts"); + modules = true; + } + + break; + } + case compiler_type::clang: + { + // Things (command line options, semantics) changed quite a bit + // around Clang 16 so we don't support anything earlier than + // that (it's not practically usable anyway). + // + // Clang enable modules by default in c++20 or later but they + // don't yet (as of Clang 18) define __cpp_modules. When they + // do, we can consider enabling modules by default on our side. + // For now, we only enable modules if forced with explicit + // cxx.features.modules=true. + // + if (modules.value) + { + if (cplusplus && *cplusplus < 202002) { - if (mj < 16) - { - fail << "support for C++ modules requires Clang 16 or later" << - info << "C++ compiler is " << ci.signature << - info << "required by " << project (rs) << '@' << rs; - } + fail << "support for C++ modules requires C++20 or later" << + info << "standard in use is " << *cplusplus << + info << "required by " << project (rs) << '@' << rs; + } - // See https://github.com/llvm/llvm-project/issues/71364 - // - prepend ("-D__cpp_modules=201907L"); - modules = true; + if (mj < 16) + { + fail << "support for C++ modules requires Clang 16 or later" << + info << "C++ compiler is " << ci.signature << + info << "required by " << project (rs) << '@' << rs; } - break; + // See https://github.com/llvm/llvm-project/issues/71364 + // + prepend ("-D__cpp_modules=201907L"); + modules = true; } - case compiler_type::icc: - break; // No modules support yet. + + break; } + case compiler_type::icc: + break; // No modules support yet. } } diff --git a/tests/cc/modules/common.testscript b/tests/cc/modules/common.testscript index f430f8a..9883e42 100644 --- a/tests/cc/modules/common.testscript +++ b/tests/cc/modules/common.testscript @@ -25,11 +25,11 @@ cxx.features.symexport = true # Note: there are some issues with enabling modules in Apple Clang 15 so # for now we only test vanilla Clang. # -if ($cxx.id == 'gcc' || \ - ($cxx.id == 'msvc' && ($cxx.version.major > 19 || \ - ($cxx.version.major == 19 && \ - $cxx.version.minor >= 36))) || \ - ($cxx.id == 'clang' && $cxx.version.major >= 16)) +if (($cxx.id == 'gcc' && $cxx.version.major >= 11) || \ + ($cxx.id == 'clang' && $cxx.version.major >= 16) || \ + ($cxx.id == 'msvc' && ($cxx.version.major > 19 || \ + ($cxx.version.major == 19 && \ + $cxx.version.minor >= 36)))) cxx.features.modules = true using cxx diff --git a/tests/cc/preprocessed/testscript b/tests/cc/preprocessed/testscript index 507a92d..53e7755 100644 --- a/tests/cc/preprocessed/testscript +++ b/tests/cc/preprocessed/testscript @@ -98,6 +98,7 @@ $* &test* <>EOE != 0 exe{test}: cxx{test} EOI error: modules support required by cxx{test} + info: consider enabling modules with cxx.features.modules=true in root.build EOE : all -- cgit v1.1