diff options
Diffstat (limited to 'libbuild2/cxx')
-rw-r--r-- | libbuild2/cxx/buildfile | 8 | ||||
-rw-r--r-- | libbuild2/cxx/init.cxx | 919 | ||||
-rw-r--r-- | libbuild2/cxx/init.hxx | 16 | ||||
-rw-r--r-- | libbuild2/cxx/target.cxx | 41 | ||||
-rw-r--r-- | libbuild2/cxx/target.hxx | 71 |
5 files changed, 859 insertions, 196 deletions
diff --git a/libbuild2/cxx/buildfile b/libbuild2/cxx/buildfile index a95da97..5862bec 100644 --- a/libbuild2/cxx/buildfile +++ b/libbuild2/cxx/buildfile @@ -4,13 +4,13 @@ # NOTE: shared imports should go into root.build. # include ../ -imp_libs = ../lib{build2} # Implied interface dependency. +impl_libs = ../lib{build2} # Implied interface dependency. include ../cc/ -int_libs = ../cc/lib{build2-cc} +intf_libs = ../cc/lib{build2-cc} ./: lib{build2-cxx}: libul{build2-cxx}: {hxx ixx txx cxx}{** -**.test...} \ - $int_libs $imp_libs + $intf_libs $impl_libs # Unit tests. # @@ -52,7 +52,7 @@ if! $cross lib{build2-cxx}: { cxx.export.poptions = "-I$out_root" "-I$src_root" - cxx.export.libs = $int_libs + cxx.export.libs = $intf_libs } liba{build2-cxx}: cxx.export.poptions += -DLIBBUILD2_CXX_STATIC diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx index 21acedc..3185eaa 100644 --- a/libbuild2/cxx/init.cxx +++ b/libbuild2/cxx/init.cxx @@ -6,9 +6,13 @@ #include <libbuild2/scope.hxx> #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 @@ -60,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. @@ -89,15 +93,79 @@ 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 ()); + auto& vp (rs.var_pool (true /* public */)); // All qualified. + + // Similar to config.cxx.std, config.cxx.features.* overrides + // cxx.features.*. + // + struct feature + { + optional<bool> value; // cxx.features.* value. + optional<bool> c_value; // config.cxx.features.* value. + bool result; // Calculated result value. + + feature& operator= (bool r) {result = r; return *this;} + + build2::value& value_; // cxx.features.* variable value. + const char* name_; // Feature name. + }; + + auto get_feature = [&rs, &vp] (const char* name) -> feature + { + auto& var (vp.insert<bool> (string ("cxx.features.") + name)); + auto& c_var (vp.insert<bool> (string ("config.cxx.features.") + name)); + + pair<value&, bool> val (rs.vars.insert (var)); + lookup l (config::lookup_config (rs, c_var)); + + optional<bool> v, c_v; + if (l.defined ()) + v = c_v = cast_false<bool> (*l); + else if (!val.second) + v = cast_false<bool> (val.first); - //bool concepts (false); - //auto& v_c (vp.insert<bool> ("cxx.features.concepts")); + return feature {v, c_v, false, val.first, name}; + }; + + auto set_feature = [&rs, &ci, v] (const feature& f) + { + if (f.c_value && *f.c_value != f.result) + { + fail << f.name_ << " cannot be " + << (*f.c_value ? "enabled" : "disabled") << " for " + << project (rs) << '@' << rs << + info << "C++ language standard is " + << (v != nullptr ? v->c_str () : "compiler-default") << + info << "C++ compiler is " << ci.signature << + info << f.name_ << " state requested with config.cxx.features." + << f.name_; + } - bool modules (false); - auto& v_m (vp.insert<bool> ("cxx.features.modules")); + f.value_ = f.result; + }; + + feature modules (get_feature ("modules")); + //feature concepts (get_feature ("concepts")); // NOTE: see also module sidebuild subproject if changing anything about // modules here. @@ -109,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: @@ -116,11 +188,14 @@ namespace build2 // C++ standard-wise, with VC you got what you got up until 14.2. // Starting with 14.3 there is now the /std: switch which defaults // to c++14 but can be set to c++latest. And from 15.3 it can be - // c++17. + // c++17. And from 16.11 it can be c++20 (we start with the compiler + // version for 16.11.4 since 16.11.0 seems to be indistinguishable + // from 16.10). // - bool v16_0 ( mj > 19 || (mj == 19 && mi >= 20)); - bool v15_3 (v16_0 || (mj == 19 && mi >= 11)); - bool v14_3 (v15_3 || (mj == 19 && (mi > 0 || (mi == 0 && p >= 24215)))); + bool v16_11 ( mj > 19 || (mj == 19 && (mi > 29 || (mi == 29 && p >= 30136)))); + bool v16_0 (v16_11 || (mj == 19 && mi >= 20)); + bool v15_3 (v16_0 || (mj == 19 && mi >= 11)); + bool v14_3 (v15_3 || (mj == 19 && (mi > 0 || (mi == 0 && p >= 24215)))); // The question is also whether we should verify that the requested // standard is provided by this VC version. And if so, from which @@ -135,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) { @@ -143,52 +238,93 @@ namespace build2 // for this mode. So starting from 16 we only enable it in // `experimental`. // - if (v16_0) + // Note: no /std:c++23 yet as of MSVC 17.6. + // + if (v16_11) + o = "/std:c++20"; + else if (v16_0) 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 (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 == "14") o = "/std:c++14"; - else if (*v == "17") o = "/std:c++17"; + 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)); + // Since VC 15.7 we can get a (more) accurate __cplusplus value if + // we ask for it with /Zc:__cplusplus: + // + // https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ + // + if (mj > 19 || (mj == 19 && mi >= 14)) + { + if (!find_option_prefix ("/Zc:__cplusplus", mode)) + prepend ("/Zc:__cplusplus"); + } + break; } case compiler_class::gcc: @@ -199,42 +335,109 @@ namespace build2 { case compiler_type::gcc: { - 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 - // (LLVM bug #44956). So in this case we for now map `latest` - // to c++17. @@ TMP - // - // Note that if 10.0.0 is released without a workaround, then - // we will need to carry this forward at least for 16.4 since - // this version is unlikely to ever be fixed. Which means we - // will somehow need to pass the version of MSVC Clang is - // targeting. - // - if (latest && mj == 10 && tt.system == "win32-msvc") + 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; } - 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"; 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; } @@ -243,22 +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, and 23 to 2b - // for compatibility with older versions of the compilers. + // 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 == "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 ()) @@ -268,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 @@ -287,93 +509,218 @@ namespace build2 default: break; } + } - // Unless disabled by the user, try to enable C++ modules. - // - lookup l; - if (!(l = rs[v_m]) || cast<bool> (l)) + // 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. - // - if (mj > 19 || (mj == 19 && mi >= (l ? 10 : 12))) + 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 now use extended/experimental module mapper support which - // is currently only available in our c++-modules-ex branch. - // But let's allow forcing it to plain c++-modules in case - // things got merged or the user feels adventurous. - // - // @@ TMP: revise: for now must be forced (also in modules - // tests). - // - if (mj >= 11 && - l && - ci.version.build.find ("c++-modules") - /* - ci.version.build.find (l - ? "c++-modules" - : "c++-modules-ex")*/ != string::npos) + + if (mj < 19 || (mj == 19 && mi < 36)) { - // Defines __cpp_modules=201907. @@ TMP: confirm. - // - 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 (l) + 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) { - prepend ("-D__cpp_modules=201704"); // p0629r0 - mode.push_back ("-fmodules-ts"); // For the hack to work. - 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; + + 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; + } + + // 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. } } - rs.assign (v_m) = modules; - //rs.assign (v_c) = concepts; + set_feature (modules); + //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}; @@ -402,18 +749,25 @@ namespace build2 // Enter all the variables and initialize the module data. // - auto& vp (rs.var_pool ()); + // All the variables we enter are qualified so go straight for the + // public variable pool. + // + auto& vp (rs.var_pool (true /* public */)); cc::config_data d { cc::lang::cxx, "cxx", "c++", + "obj-c++", BUILD2_DEFAULT_CXX, ".ii", + ".mii", hinters, + vp["bin.binless"], + // NOTE: remember to update documentation if changing anything here. // vp.insert<strings> ("config.cxx"), @@ -427,23 +781,146 @@ namespace build2 vp.insert<strings> ("config.cxx.aoptions"), vp.insert<strings> ("config.cxx.libs"), - // List of translatable headers. Inclusions of such headers are + // Project's internal scope. + // + // A header search path (-I) exported by a library that is outside of + // the internal scope is considered external and, if supported by the + // compiler, the corresponding -I option is translated to an + // appropriate "external header search path" option (-isystem for + // GCC/Clang, /external:I for MSVC 16.10 and later or clang-cl 13 and + // later). In particular, this suppresses compiler warnings in such + // external headers (/external:W0 is automatically added unless a + // custom /external:Wn is specified). + // + // The internal scope can be specified by the project with the + // cxx.internal.scope variable and overridden by the user with the + // config.cxx.internal.scope variable. Note that cxx.internal.scope + // must be specified before loading the cxx module (cxx.config, more + // precisely) and after which it contains the effective value (see + // below). For example: + // + // # root.build + // + // cxx.internal.scope = current + // + // using cxx + // + // Valid values for cxx.internal.scope are: + // + // current -- current root scope (where variable is assigned) + // base -- target's base scope + // root -- target's root scope + // bundle -- target's bundle amalgamation (see scope::bundle_root()) + // strong -- target's strong amalgamation (see scope::strong_root()) + // weak -- target's weak amalgamation (see scope::weak_root()) + // global -- global scope (everything is internal) + // + // Valid values for config.cxx.internal.scope are the same except for + // `current`. + // + // Note also that there are [config.]cc.internal.scope variables that + // can be used to specify the internal scope for all the cc-based + // modules. + // + // The project's effective internal scope is chosen based on the + // following priority list: + // + // 1. config.cxx.internal.scope + // + // 2. config.cc.internal.scope + // + // 3. effective scope from bundle amalgamation + // + // 4. cxx.internal.scope + // + // 5. cc.internal.scope + // + // In particular, item #3 allows an amalgamation that bundles a + // project to override its internal scope. + // + // The recommended value for a typical project is `current`, meaning + // that only headers inside the project will be considered internal. + // The tests subproject, if present, will inherit its value from the + // project (which acts as a bundle amalgamation), unless it is being + // built out of source (for example, to test an installed library). + // + // A project can also whitelist specific libraries using the + // cxx.internal.libs variable. If a library target name (that is, the + // name inside lib{}) matches any of the wildcard patterns listed in + // this variable, then the library is considered internal regardless + // of its location. For example (notice that the pattern is quoted): + // + // # root.build + // + // cxx.internal.scope = current + // cxx.internal.libs = foo 'bar-*' + // + // using cxx + // + // Note that this variable should also be set before loading the + // cxx module and there is the common cc.internal.libs equivalent. + // However, there are no config.* versions nor the override by the + // bundle amalgamation semantics. + // + // Typically you would want to whitelist libraries that are developed + // together but reside in separate build system projects. In + // particular, a separate *-tests project for a library should + // whitelist the library being tested if the internal scope + // functionality is in use. Another reason to whitelist is to catch + // warnings in instantiations of templates that belong to a library + // that is otherwise warning-free (see the MSVC /external:templates- + // option for background). + // + // Note also that if multiple libraries are installed into the same + // location (or otherwise share the same header search paths, for + // example, as a family of libraries), then the whitelist may not + // be effective. + // + vp.insert<string> ("config.cxx.internal.scope"), + + // Headers and header groups whose inclusion should or should not be // translated to the corresponding header unit imports. // // A header can be specified either as an absolute and normalized path - // or as a <>-style include name. The latter kind is automatically - // translated to the absolute form based on the compiler's system (as - // opposed to -I) header search paths. Note also that all entries must - // be specified before loading the cxx module. + // or as a <>-style include file or file pattern (for example, + // <vector>, <boost/**.hpp>). The latter kind is automatically + // resolved to the absolute form based on the compiler's system (as + // opposed to project's) header search paths. + // + // Currently recognized header groups are: + // + // std-importable -- translate importable standard library headers + // std -- translate all standard library headers + // all-importable -- translate all importable headers + // all -- translate all headers + // + // Note that a header may belong to multiple groups which are looked + // up from the most to least specific, for example: <vector>, + // std-importable, std, all-importable, all. // - &vp.insert<strings> ("config.cxx.translatable_headers"), + // A header or group can also be excluded from being translated, for + // example: + // + // std-importable <vector>@false + // + // The config.cxx.translate_include value is prepended (merged with + // override) into cxx.translate_include while loading the cxx.config + // module. The headers and header groups in cxx.translate_include are + // resolved while loading the cxx module. For example: + // + // cxx.translate_include = <map>@false # Can be overriden. + // using cxx.config + // cxx.translate_include =+ <set>@false # Cannot be overriden. + // using cxx + // + &vp.insert<cc::translatable_headers> ("config.cxx.translate_include"), vp.insert<process_path_ex> ("cxx.path"), vp.insert<strings> ("cxx.mode"), vp.insert<path> ("cxx.config.path"), vp.insert<strings> ("cxx.config.mode"), vp.insert<dir_paths> ("cxx.sys_lib_dirs"), - vp.insert<dir_paths> ("cxx.sys_inc_dirs"), + vp.insert<dir_paths> ("cxx.sys_hdr_dirs"), vp.insert<string> ("cxx.std"), @@ -453,7 +930,10 @@ namespace build2 vp.insert<strings> ("cxx.aoptions"), vp.insert<strings> ("cxx.libs"), - &vp.insert<strings> ("cxx.translatable_headers"), + vp.insert<string> ("cxx.internal.scope"), + vp.insert<strings> ("cxx.internal.libs"), + + &vp.insert<cc::translatable_headers> ("cxx.translate_include"), vp["cc.poptions"], vp["cc.coptions"], @@ -465,13 +945,16 @@ namespace build2 vp.insert<strings> ("cxx.export.coptions"), vp.insert<strings> ("cxx.export.loptions"), vp.insert<vector<name>> ("cxx.export.libs"), - vp.insert<vector<name>> ("cxx.export.imp_libs"), + vp.insert<vector<name>> ("cxx.export.impl_libs"), vp["cc.export.poptions"], vp["cc.export.coptions"], vp["cc.export.loptions"], vp["cc.export.libs"], - vp["cc.export.imp_libs"], + vp["cc.export.impl_libs"], + + vp["cc.pkgconfig.include"], + vp["cc.pkgconfig.lib"], vp.insert<string> ("cxx.stdlib"), @@ -481,7 +964,9 @@ namespace build2 vp["cc.type"], vp["cc.system"], 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 @@ -535,6 +1020,10 @@ namespace build2 // vp.insert_alias (d.c_runtime, "cxx.runtime"); vp.insert_alias (d.c_module_name, "cxx.module_name"); + vp.insert_alias (d.c_importable, "cxx.importable"); + + vp.insert_alias (d.c_pkgconfig_include, "cxx.pkgconfig.include"); + vp.insert_alias (d.c_pkgconfig_lib, "cxx.pkgconfig.lib"); auto& m (extra.set_module (new config_module (move (d)))); m.guess (rs, loc, extra.hints); @@ -566,27 +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 - }; - - 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, - nullptr - }; - bool init (scope& rs, scope& bs, @@ -608,7 +1076,7 @@ namespace build2 auto& cm ( load_module<config_module> (rs, rs, "cxx.config", loc, extra.hints)); - auto& vp (rs.var_pool ()); + auto& vp (rs.var_pool (true /* public */)); // All qualified. bool modules (cast<bool> (rs["cxx.features.modules"])); @@ -626,39 +1094,148 @@ namespace build2 "cxx.compile", "cxx.link", "cxx.install", - "cxx.uninstall", - 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, + cm.x_info->variant_version ? cm.x_info->variant_version->major : 0, + cm.x_info->variant_version ? cm.x_info->variant_version->minor : 0, cast<process_path> (rs[cm.x_path]), cast<strings> (rs[cm.x_mode]), cast<target_triplet> (rs[cm.x_target]), + cm.env_checksum, modules, symexport, + cm.iscope, + cm.iscope_current, + + cast_null<strings> (rs["cc.internal.libs"]), + cast_null<strings> (rs[cm.x_internal_libs]), + cast<dir_paths> (rs[cm.x_sys_lib_dirs]), - cast<dir_paths> (rs[cm.x_sys_inc_dirs]), + cast<dir_paths> (rs[cm.x_sys_hdr_dirs]), cm.x_info->sys_mod_dirs ? &cm.x_info->sys_mod_dirs->first : nullptr, cm.sys_lib_dirs_mode, - cm.sys_inc_dirs_mode, + cm.sys_hdr_dirs_mode, cm.sys_mod_dirs_mode, cm.sys_lib_dirs_extra, - cm.sys_inc_dirs_extra, + cm.sys_hdr_dirs_extra, cxx::static_type, modules ? &mxx::static_type : nullptr, + cxx_inc::static_type, hdr, inc }; - auto& m (extra.set_module (new module (move (d)))); - m.init (rs, loc, extra.hints); + auto& m (extra.set_module (new module (move (d), rs))); + m.init (rs, loc, extra.hints, *cm.x_info); + + return true; + } + + 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&) + { + tracer trace ("cxx::objcxx_init"); + l5 ([&]{trace << "for " << bs;}); + + // We only support root loading (which means there can only be one). + // + if (rs != bs) + fail (loc) << "cxx.objcxx module must be loaded in project root"; + + module* mod (rs.find_module<module> ("cxx")); + + if (mod == nullptr) + fail (loc) << "cxx.objcxx module must be loaded after cxx module"; + + // Register the target type and "enable" it in the module. + // + // Note that we must register the target type regardless of whether the + // C++ compiler is capable of compiling Objective-C++. But we enable + // only if it is. + // + // Note: see similar code in the c module. + // + 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 + // let's keep the check simple for now. + // + if (mod->ctype == compiler_type::gcc || + mod->ctype == compiler_type::clang) + mod->x_obj = &mm::static_type; + + 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; } @@ -668,10 +1245,14 @@ namespace build2 // 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", 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* diff --git a/libbuild2/cxx/init.hxx b/libbuild2/cxx/init.hxx index 094fea4..a193e74 100644 --- a/libbuild2/cxx/init.hxx +++ b/libbuild2/cxx/init.hxx @@ -19,9 +19,19 @@ namespace build2 // // Submodules: // - // `cxx.guess` -- registers and sets some variables. - // `cxx.config` -- loads cxx.guess and sets more variables. - // `cxx` -- loads cxx.config and registers target types and rules. + // `cxx.types` -- registers target types. + // `cxx.guess` -- registers and sets some variables. + // `cxx.config` -- loads cxx.guess and sets more variables. + // `cxx` -- loads cxx.{types,config} and registers rules + // and functions. + // + // `cxx.objcxx.types` -- registers mm{} target type. + // `cxx.objcxx` -- loads cxx.objcxx and enables Objective-C++ + // compilation. + // + // `cxx.predefs` -- registers rule for generating a C++ header with + // predefined compiler macros. Must be loaded after + // cxx. // extern "C" LIBBUILD2_CXX_SYMEXPORT const module_functions* build2_cxx_load (); diff --git a/libbuild2/cxx/target.cxx b/libbuild2/cxx/target.cxx index 982dcb4..37096c3 100644 --- a/libbuild2/cxx/target.cxx +++ b/libbuild2/cxx/target.cxx @@ -3,10 +3,6 @@ #include <libbuild2/cxx/target.hxx> -#include <libbuild2/context.hxx> - -using namespace std; - namespace build2 { namespace cxx @@ -22,7 +18,7 @@ namespace build2 &target_pattern_var<hxx_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char ixx_ext_def[] = "ixx"; @@ -36,7 +32,7 @@ namespace build2 &target_pattern_var<ixx_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char txx_ext_def[] = "txx"; @@ -50,7 +46,7 @@ namespace build2 &target_pattern_var<txx_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char cxx_ext_def[] = "cxx"; @@ -64,7 +60,7 @@ namespace build2 &target_pattern_var<cxx_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char mxx_ext_def[] = "mxx"; @@ -78,7 +74,34 @@ namespace build2 &target_pattern_var<mxx_ext_def>, nullptr, &file_search, - false + target_type::flag::none + }; + + extern const char mm_ext_def[] = "mm"; + const target_type mm::static_type + { + "mm", + &cc::static_type, + &target_factory<mm>, + nullptr, /* fixed_extension */ + &target_extension_var<mm_ext_def>, + &target_pattern_var<mm_ext_def>, + nullptr, + &file_search, + target_type::flag::none + }; + + const target_type cxx_inc::static_type + { + "cxx_inc", + &cc::static_type, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + &target_search, + target_type::flag::none }; } } diff --git a/libbuild2/cxx/target.hxx b/libbuild2/cxx/target.hxx index cddab68..06e8a67 100644 --- a/libbuild2/cxx/target.hxx +++ b/libbuild2/cxx/target.hxx @@ -7,7 +7,6 @@ #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> -#include <libbuild2/target.hxx> #include <libbuild2/cc/target.hxx> #include <libbuild2/cxx/export.hxx> @@ -18,45 +17,58 @@ namespace build2 { using cc::h; using cc::c; + using cc::m; class LIBBUILD2_CXX_SYMEXPORT hxx: public cc::cc { public: - using cc::cc; + hxx (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; class LIBBUILD2_CXX_SYMEXPORT ixx: public cc::cc { public: - using cc::cc; + ixx (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; class LIBBUILD2_CXX_SYMEXPORT txx: public cc::cc { public: - using cc::cc; + txx (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; class LIBBUILD2_CXX_SYMEXPORT cxx: public cc::cc { public: - using cc::cc; + cxx (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; // The module interface unit is both like a header (e.g., we need to @@ -67,11 +79,48 @@ namespace build2 class LIBBUILD2_CXX_SYMEXPORT mxx: public cc::cc { public: - using cc::cc; + mxx (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } + + public: + static const target_type static_type; + }; + + // Objective-C++ source file. + // + class LIBBUILD2_CXX_SYMEXPORT mm: public cc::cc + { + public: + mm (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } + + public: + static const target_type static_type; + }; + + // This is an abstract base target for deriving additional targets (for + // example, Qt moc{}) that can be #include'd in C++ translation units. In + // particular, only such targets will be considered to reverse-lookup + // extensions to target types (see dyndep_rule::map_extension() for + // background). + // + class LIBBUILD2_CXX_SYMEXPORT cxx_inc: public cc::cc + { + public: + cxx_inc (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; } } |