aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/cxx/init.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/cxx/init.cxx')
-rw-r--r--libbuild2/cxx/init.cxx885
1 files changed, 719 insertions, 166 deletions
diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx
index e2d7343..8159d18 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));
- //bool concepts (false);
- //auto& v_c (vp.insert<bool> ("cxx.features.concepts"));
+ 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 modules (false);
- auto& v_m (vp.insert<bool> ("cxx.features.modules"));
+ 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_;
+ }
+
+ 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,48 +238,78 @@ 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));
@@ -210,42 +335,100 @@ 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 >= 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
+ // c++2a mode uncovers some Concepts-related bugs in MSVC STL
+ // (LLVM bug #44956). So in this case we map `latest` to
+ // c++17.
//
- // 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.
+ // 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 (latest && mj == 10 && tt.system == "win32-msvc")
+
+ 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;
}
- 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;
}
@@ -254,22 +437,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.
//
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 ())
@@ -279,6 +473,8 @@ namespace build2
}
}
+ // Additional experimental options.
+ //
if (experimental)
{
switch (ct)
@@ -298,93 +494,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 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 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)
{
- 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};
@@ -413,15 +734,20 @@ 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,
@@ -440,23 +766,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.
+ //
+ // A header or group can also be excluded from being translated, for
+ // example:
+ //
+ // std-importable <vector>@false
//
- &vp.insert<strings> ("config.cxx.translatable_headers"),
+ // 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"),
@@ -466,7 +915,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"],
@@ -478,13 +930,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"),
@@ -494,7 +949,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
@@ -548,6 +1005,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);
@@ -579,27 +1040,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,
@@ -621,7 +1061,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"]));
@@ -639,39 +1079,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;
}
@@ -681,10 +1230,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*