diff options
Diffstat (limited to 'libbuild2/cxx/init.cxx')
-rw-r--r-- | libbuild2/cxx/init.cxx | 612 |
1 files changed, 462 insertions, 150 deletions
diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx index 3ca920e..3185eaa 100644 --- a/libbuild2/cxx/init.cxx +++ b/libbuild2/cxx/init.cxx @@ -7,10 +7,12 @@ #include <libbuild2/diagnostics.hxx> #include <libbuild2/config/utility.hxx> +#include <libbuild2/install/utility.hxx> #include <libbuild2/cc/guess.hxx> #include <libbuild2/cc/module.hxx> +#include <libbuild2/cc/target.hxx> // pc* #include <libbuild2/cxx/target.hxx> #ifndef BUILD2_DEFAULT_CXX @@ -62,8 +64,8 @@ namespace build2 uint64_t mi (ci.version.minor); uint64_t p (ci.version.patch); - // Besides various `c++NN` we have two special values: `latest` and - // `experimental`. + // Besides various `NN` we have two special values: `latest` and + // `experimental`. It can also be `gnu++NN`. // // The semantics of the `latest` value is the latest available standard // that is not necessarily complete or final but is practically usable. @@ -91,6 +93,24 @@ namespace build2 bool latest (v != nullptr && *v == "latest"); bool experimental (v != nullptr && *v == "experimental"); + // This helper helps recognize both NN and [cC]++NN to avoid an endless + // stream of user questions. It can also be used to recognize Nx in + // addition to NN (e.g., "14" and "1y"). + // + auto stdcmp = [v] (const char* nn, const char* nx = nullptr) + { + if (v != nullptr) + { + const char* s (v->c_str ()); + if ((s[0] == 'c' || s[0] == 'C') && s[1] == '+' && s[2] == '+') + s += 3; + + return strcmp (s, nn) == 0 || (nx != nullptr && strcmp (s, nx) == 0); + } + + return false; + }; + // Feature flags. // auto& vp (rs.var_pool (true /* public */)); // All qualified. @@ -157,6 +177,10 @@ namespace build2 i = mode.insert (i, move (o)) + 1; }; + // Derive approximate __cplusplus value from the standard if possible. + // + optional<uint32_t> cplusplus; + switch (cl) { case compiler_class::msvc: @@ -186,6 +210,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) { @@ -202,49 +246,70 @@ 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) - ; - else if (*v != "98" && *v != "03") + { + // @@ TODO: map defaults to cplusplus for each version. + } + else if (!stdcmp ("98") && !stdcmp ("03")) { bool sup (false); - if (*v == "11") // C++11 since VS2010/10.0. + if (stdcmp ("11", "0x")) // C++11 since VS2010/10.0. { sup = mj >= 16; + cplusplus = 201103; } - else if (*v == "14") // C++14 since VS2015/14.0. + else if (stdcmp ("14", "1y")) // C++14 since VS2015/14.0. { sup = mj >= 19; + cplusplus = 201402; } - else if (*v == "17") // C++17 since VS2015/14.0u2. + else if (stdcmp ("17", "1z")) // C++17 since VS2015/14.0u2. { // Note: the VC15 compiler version is 19.10. // sup = (mj > 19 || (mj == 19 && (mi > 0 || (mi == 0 && p >= 23918)))); + cplusplus = 201703; } - else if (*v == "20") // C++20 since VS2019/16.11. + else if (stdcmp ("20", "2a")) // C++20 since VS2019/16.11. { sup = v16_11; + cplusplus = 202002; } if (!sup) - fail << "C++" << *v << " is not supported by " << ci.signature << + fail << "C++ " << *v << " is not supported by " << ci.signature << info << "required by " << project (rs) << '@' << rs; if (v15_3) { - if (*v == "20") o = "/std:c++20"; - else if (*v == "17") o = "/std:c++17"; - else if (*v == "14") o = "/std:c++14"; + if (stdcmp ("20", "2a")) o = "/std:c++20"; + else if (stdcmp ("17", "1z")) o = "/std:c++17"; + else if (stdcmp ("14", "1y")) o = "/std:c++14"; } else if (v14_3) { - if (*v == "14") o = "/std:c++14"; - else if (*v == "17") o = "/std:c++latest"; + if (stdcmp ("14", "1y")) o = "/std:c++14"; + else if (stdcmp ("17", "1z")) o = "/std:c++latest"; } } + else + cplusplus = 199711; if (!o.empty ()) prepend (move (o)); @@ -270,43 +335,109 @@ 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 >= 14) + { + o = "-std=c++26"; + cplusplus = 202400; + } + else 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; } case compiler_type::clang: { - // Clang 10.0.0 targeting MSVC 16.4 and 16.5 (preview) in the - // c++2a mode uncovers some Concepts-related bugs in MSVC STL - // (LLVM bug #44956). So in this case we map `latest` to - // c++17. - // - // While reportedly this has been fixed in the later versions - // of MSVC, instead of somehow passing the version of MSVC - // Clang is targeting, we will just assume that Clang 11 - // and later are used with a sufficiently new version of - // 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 >= 18) + { + o = "-std=c++26"; + cplusplus = 202400; + } + else if (mj >= 13) + { + o = "-std=c++2b"; + cplusplus = 202302; + } + else if (mj == 10 && latest && tt.system == "win32-msvc") + { + // Clang 10.0.0 targeting MSVC 16.4 and 16.5 (preview) in + // the c++2a mode uncovers some Concepts-related bugs in + // MSVC STL (LLVM bug #44956). So in this case we map + // `latest` to c++17. + // + // While reportedly this has been fixed in the later + // versions of MSVC, instead of somehow passing the version + // of MSVC Clang is targeting, we will just assume that + // Clang 11 and later are used with a sufficiently new + // version of 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; } @@ -315,24 +446,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 (and above). + // o = "-std="; - if (*v == "26") o += "c++2c"; - else if (*v == "23") o += "c++2b"; - else if (*v == "20") o += "c++2a"; - else if (*v == "17") o += "c++1z"; - else if (*v == "14") o += "c++1y"; - else if (*v == "11") o += "c++0x"; - else if (*v == "03") o += "c++03"; - else if (*v == "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 ()) @@ -342,12 +482,20 @@ namespace build2 } } + // Additional experimental options. + // if (experimental) { switch (ct) { case compiler_type::msvc: { + // Let's enable the new preprocessor in this mode. For background, + // see MSVC issue 10537317. + // + if (mj > 19 || (mj == 19 && mi >= 39)) + prepend ("/Zc:preprocessor"); + // Starting with 15.5 (19.12) Visual Studio-created projects // default to the strict mode. However, this flag currently tends // to trigger too many compiler bugs. So for now we leave it to @@ -361,85 +509,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 (if + // attempting to use) 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 <string>` in the same translation unit (see Visual + // Studio issue #10541166). + // + if (modules.value) { - // While modules are supported in VC 15.0 (19.10), there is a - // bug in the separate interface/implementation unit support - // which makes them pretty much unusable. This has been fixed in - // 15.3 (19.11). And 15.5 (19.12) supports the `export module - // M;` syntax. And 16.4 (19.24) supports the global module - // fragment. And in 16.8 all the modules-related options have - // been changed. Seeing that the whole thing is unusable anyway, - // we disable it for 16.8 or later for now. - // - if ((mj > 19 || (mj == 19 && mi >= (modules.value ? 10 : 12))) && - (mj < 19 || (mj == 19 && mi < 28) || modules.value)) + if (cplusplus && *cplusplus < 202002) { - prepend ( - mj > 19 || mi >= 24 ? - "/D__cpp_modules=201810" : // p1103 (merged modules) - mj == 19 || mi >= 12 ? - "/D__cpp_modules=201704" : // p0629r0 (export module M;) - "/D__cpp_modules=201703"); // n4647 ( module M;) - - prepend ("/experimental:module"); - 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::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. - // - if (mj >= 11 && modules.value) + + if (mj < 19 || (mj == 19 && mi < 36)) { - // Defines __cpp_modules: - // - // 11 -- 201810 - // - prepend ("-fmodules-ts"); - modules = true; + 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::clang: + + 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) { - // At the time of this writing, support for C++20 modules in - // Clang is incomplete. And starting with Clang 9 (Apple Clang - // 11.0.3), they are enabled by default in the C++2a mode which - // breaks the way we set things up for partial preprocessing; - // see this post for details: - // - // http://lists.llvm.org/pipermail/cfe-dev/2019-October/063637.html - // - // As a result, for now, we only enable modules if forced with - // explicit cxx.features.modules=true. + if (cplusplus && *cplusplus < 202002) + { + fail << "support for C++ modules requires C++20 or later" << + info << "standard in use is " << *cplusplus << + info << "required by " << project (rs) << '@' << rs; + } + + 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: // - // Also see Clang modules support hack in cc::compile. + // 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 enables 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) + { + fail << "support for C++ modules requires C++20 or later" << + info << "standard in use is " << *cplusplus << + info << "required by " << project (rs) << '@' << rs; + } + + if (mj < 16) { - prepend ("-D__cpp_modules=201704"); // p0629r0 - mode.push_back ("-fmodules-ts"); // For the hack to work. - modules = true; + 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. } } @@ -447,6 +634,95 @@ namespace build2 //set_feature (concepts); } + // See cc::data::x_{hdr,inc} for background. + // + static const target_type* const hdr[] = + { + &hxx::static_type, + &ixx::static_type, + &txx::static_type, + &mxx::static_type, + nullptr + }; + + // Note that we don't include S{} here because none of the files we + // compile can plausibly want to include .S. (Maybe in inline assembler + // instructions?) + // + static const target_type* const inc[] = + { + &hxx::static_type, + &h::static_type, + &ixx::static_type, + &txx::static_type, + &mxx::static_type, + &cxx::static_type, + &c::static_type, + &mm::static_type, + &m::static_type, + &cxx_inc::static_type, + &cc::c_inc::static_type, + nullptr + }; + + bool + types_init (scope& rs, + scope& bs, + const location& loc, + bool, + bool, + module_init_extra&) + { + tracer trace ("cxx::types_init"); + l5 ([&]{trace << "for " << bs;}); + + // We only support root loading (which means there can only be one). + // + if (rs != bs) + fail (loc) << "cxx.types module must be loaded in project root"; + + // Register target types and configure their "installability". + // + using namespace install; + + bool install_loaded (cast_false<bool> (rs["install.loaded"])); + + // Note: not registering mm{} (it is registered seperately by the + // respective optional .types submodule). + // + // Note: mxx{} is in hdr. @@ But maybe it shouldn't be... + // + rs.insert_target_type<cxx> (); + + auto insert_hdr = [&rs, install_loaded] (const target_type& tt) + { + rs.insert_target_type (tt); + + // Install headers into install.include. + // + if (install_loaded) + install_path (rs, tt, dir_path ("include")); + }; + + for (const target_type* const* ht (hdr); *ht != nullptr; ++ht) + insert_hdr (**ht); + + // Also register the C header for C-derived languages. + // + insert_hdr (h::static_type); + + // @@ PERF: maybe factor this to cc.types? + // + rs.insert_target_type<cc::pc> (); + rs.insert_target_type<cc::pca> (); + rs.insert_target_type<cc::pcs> (); + + if (install_loaded) + install_path<cc::pc> (rs, dir_path ("pkgconfig")); + + return true; + } + static const char* const hinters[] = {"c", nullptr}; // See cc::module for details on guess_init vs config_init. @@ -677,8 +953,8 @@ namespace build2 vp["cc.export.libs"], vp["cc.export.impl_libs"], - vp["cc.pkconfig.include"], - vp["cc.pkconfig.lib"], + vp["cc.pkgconfig.include"], + vp["cc.pkgconfig.lib"], vp.insert<string> ("cxx.stdlib"), @@ -690,6 +966,7 @@ namespace build2 vp["cc.module_name"], vp["cc.importable"], vp["cc.reprocess"], + vp["cc.serialize"], // Ability to signal that source is already (partially) preprocessed. // Valid values are 'none' (not preprocessed), 'includes' (no #include @@ -778,33 +1055,6 @@ namespace build2 return true; } - static const target_type* const hdr[] = - { - &hxx::static_type, - &ixx::static_type, - &txx::static_type, - &mxx::static_type, - nullptr - }; - - // Note that we don't include S{} here because none of the files we - // compile can plausibly want to include .S. (Maybe in inline assember - // instrcutions?) - // - static const target_type* const inc[] = - { - &hxx::static_type, - &h::static_type, - &ixx::static_type, - &txx::static_type, - &mxx::static_type, - &cxx::static_type, - &c::static_type, - &mm::static_type, - &m::static_type, - nullptr - }; - bool init (scope& rs, scope& bs, @@ -845,8 +1095,7 @@ namespace build2 "cxx.link", "cxx.install", - cm.x_info->id.type, - cm.x_info->id.variant, + cm.x_info->id, cm.x_info->class_, cm.x_info->version.major, cm.x_info->version.minor, @@ -879,6 +1128,7 @@ namespace build2 cxx::static_type, modules ? &mxx::static_type : nullptr, + cxx_inc::static_type, hdr, inc }; @@ -890,12 +1140,35 @@ namespace build2 } bool + objcxx_types_init (scope& rs, + scope& bs, + const location& loc, + bool, + bool, + module_init_extra&) + { + tracer trace ("cxx::objcxx_types_init"); + l5 ([&]{trace << "for " << bs;}); + + // We only support root loading (which means there can only be one). + // + if (rs != bs) + fail (loc) << "cxx.objcxx.types module must be loaded in project root"; + + // Register the mm{} target type. + // + rs.insert_target_type<mm> (); + + return true; + } + + bool objcxx_init (scope& rs, scope& bs, const location& loc, bool, bool, - module_init_extra&) + module_init_extra&) { tracer trace ("cxx::objcxx_init"); l5 ([&]{trace << "for " << bs;}); @@ -918,7 +1191,7 @@ namespace build2 // // Note: see similar code in the c module. // - rs.insert_target_type<mm> (); + load_module (rs, rs, "cxx.objcxx.types", loc); // Note that while Objective-C++ is supported by MinGW GCC, it's // unlikely Clang supports it when targeting MSVC or Emscripten. But @@ -931,16 +1204,55 @@ namespace build2 return true; } + bool + predefs_init (scope& rs, + scope& bs, + const location& loc, + bool, + bool, + module_init_extra&) + { + tracer trace ("cxx::predefs_init"); + l5 ([&]{trace << "for " << bs;}); + + // We only support root loading (which means there can only be one). + // + if (rs != bs) + fail (loc) << "cxx.predefs module must be loaded in project root"; + + module* mod (rs.find_module<module> ("cxx")); + + if (mod == nullptr) + fail (loc) << "cxx.predefs module must be loaded after cxx module"; + + // Register the cxx.predefs rule. + // + // Why invent a separate module instead of just always registering it in + // the cxx module? The reason is performance: this rule will be called + // for every C++ header. + // + cc::predefs_rule& r (*mod); + + rs.insert_rule<hxx> (perform_update_id, r.rule_name, r); + rs.insert_rule<hxx> (perform_clean_id, r.rule_name, r); + rs.insert_rule<hxx> (configure_update_id, r.rule_name, r); + + return true; + } + static const module_functions mod_functions[] = { // NOTE: don't forget to also update the documentation in init.hxx if // changing anything here. - {"cxx.guess", nullptr, guess_init}, - {"cxx.config", nullptr, config_init}, - {"cxx.objcxx", nullptr, objcxx_init}, - {"cxx", nullptr, init}, - {nullptr, nullptr, nullptr} + {"cxx.types", nullptr, types_init}, + {"cxx.guess", nullptr, guess_init}, + {"cxx.config", nullptr, config_init}, + {"cxx.objcxx.types", nullptr, objcxx_types_init}, + {"cxx.objcxx", nullptr, objcxx_init}, + {"cxx.predefs", nullptr, predefs_init}, + {"cxx", nullptr, init}, + {nullptr, nullptr, nullptr} }; const module_functions* |