aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2020-11-05 15:52:13 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2020-11-05 15:52:13 +0200
commit8671b4dc516994b7f070a341c004aab28e525431 (patch)
treeae44903ad4960ed5ace4bcb26401be5dd592ff4f
parentfb4272e816ad949811f9f8b709771c5aeb646d1f (diff)
Initial Emscripten support
- Target: wasm32-emscripten (wasm32-unknown-emscripten). - Compiler id: clang-emscripten (type clang, variant emscripten, class gcc). - Ability to build executables (.js plus .wasm) and static libraries (.a). Set executable bit on the .js file (so it can be executed with a suitable binfmt interpreter). - Default config.bin.lib for wasm32-emscripten is static instead of both. - Full C++ exception support is enable unless disabled explicitly by the user with -s DISABLE_EXCEPTION_CATCHING=1|2. - The bin module registers the wasm{} target type for wasm32-emscripten.
-rw-r--r--libbuild2/bin/init.cxx291
-rw-r--r--libbuild2/cc/compile-rule.cxx523
-rw-r--r--libbuild2/cc/guess.cxx398
-rw-r--r--libbuild2/cc/guess.hxx21
-rw-r--r--libbuild2/cc/link-rule.cxx44
-rw-r--r--libbuild2/cc/module.cxx61
6 files changed, 803 insertions, 535 deletions
diff --git a/libbuild2/bin/init.cxx b/libbuild2/bin/init.cxx
index 2f1e6db..83c4b38 100644
--- a/libbuild2/bin/init.cxx
+++ b/libbuild2/bin/init.cxx
@@ -145,7 +145,7 @@ namespace build2
config_init (scope& rs,
scope& bs,
const location& loc,
- bool first,
+ bool,
bool,
module_init_extra& extra)
{
@@ -177,6 +177,126 @@ namespace build2
//
config::save_module (rs, "bin", 350);
+ bool new_cfg (false); // Any new configuration values?
+
+ // config.bin.target
+ //
+ const target_triplet* tgt;
+ {
+ const variable& var (ctx.var_pool["config.bin.target"]);
+
+ // We first see if the value was specified via the configuration
+ // mechanism.
+ //
+ lookup l (lookup_config (new_cfg, rs, var));
+
+ // Then see if there is a config hint (e.g., from the cc module).
+ //
+ bool hint (false);
+ if (!l)
+ {
+ // Note: new_cfg is false for a hinted value.
+ //
+ if (auto hl = extra.hints[var])
+ {
+ l = hl;
+ hint = true;
+ }
+ }
+
+ if (!l)
+ fail (loc) << "unable to determine binutils target" <<
+ info << "consider specifying it with " << var <<
+ info << "or first load a module that can provide it as a hint, "
+ << "such as c or cxx";
+
+ // Split/canonicalize the target.
+ //
+ string s (cast<string> (l));
+
+ // Did the user ask us to use config.sub? If this is a hinted value,
+ // then we assume it has already been passed through config.sub.
+ //
+ if (!hint && config_sub)
+ {
+ s = run<string> (3,
+ *config_sub,
+ s.c_str (),
+ [] (string& l, bool) {return move (l);});
+ l5 ([&]{trace << "config.sub target: '" << s << "'";});
+ }
+
+ try
+ {
+ target_triplet t (s);
+
+ l5 ([&]{trace << "canonical target: '" << t.string () << "'; "
+ << "class: " << t.class_;});
+
+ assert (!hint || s == t.representation ());
+
+ // Also enter as bin.target.{cpu,vendor,system,version,class}
+ // for convenience of access.
+ //
+ rs.assign<string> ("bin.target.cpu") = t.cpu;
+ rs.assign<string> ("bin.target.vendor") = t.vendor;
+ rs.assign<string> ("bin.target.system") = t.system;
+ rs.assign<string> ("bin.target.version") = t.version;
+ rs.assign<string> ("bin.target.class") = t.class_;
+
+ tgt = &rs.assign<target_triplet> ("bin.target", move (t));
+ }
+ catch (const invalid_argument& e)
+ {
+ // This is where we suggest that the user specifies --config-sub
+ // to help us out.
+ //
+ fail << "unable to parse binutils target '" << s << "': " << e <<
+ info << "consider using the --config-sub option";
+ }
+ }
+
+ // config.bin.pattern
+ //
+ const string* pat (nullptr);
+ {
+ const variable& var (ctx.var_pool["config.bin.pattern"]);
+
+ // We first see if the value was specified via the configuration
+ // mechanism.
+ //
+ lookup l (lookup_config (new_cfg, rs, var));
+
+ // Then see if there is a config hint (e.g., from the C++ module).
+ //
+ if (!l)
+ {
+ // Note: new_cfg is false for a hinted value.
+ //
+ if (auto hl = extra.hints[var])
+ l = hl;
+ }
+
+ // For ease of use enter it as bin.pattern (since it can come from
+ // different places).
+ //
+ if (l)
+ {
+ const string& s (cast<string> (l));
+
+ if (s.empty () ||
+ (!path::traits_type::is_separator (s.back ()) &&
+ s.find ('*') == string::npos))
+ {
+ fail << "missing '*' or trailing '"
+ << char (path::traits_type::directory_separator)
+ << "' in binutils pattern '" << s << "'";
+ }
+
+ pat = &rs.assign<string> ("bin.pattern", s);
+ }
+ }
+
// The idea here is as follows: if we already have one of
// the bin.* variables set, then we assume this is static
// project configuration and don't bother setting the
@@ -184,15 +304,20 @@ namespace build2
//
//@@ Need to validate the values. Would be more efficient
// to do it once on assignment than every time on query.
- // Custom var type?
//
// config.bin.lib
//
+ // By default it's both unless the target doesn't support one of the
+ // variants.
+ //
{
value& v (rs.assign ("bin.lib"));
if (!v)
- v = *lookup_config (rs, "config.bin.lib", "both");
+ v = *lookup_config (rs,
+ "config.bin.lib",
+ tgt->system == "emscripten" ? "static" :
+ "both");
}
// config.bin.exe.lib
@@ -272,140 +397,19 @@ namespace build2
set ("bin.exe.suffix", "config.bin.exe.suffix", s);
}
- if (first)
+ // If this is a configuration with new values, then print the report
+ // at verbosity level 2 and up (-v).
+ //
+ if (verb >= (new_cfg ? 2 : 3))
{
- bool new_cfg (false); // Any new configuration values?
+ diag_record dr (text);
- // config.bin.target
- //
- {
- const variable& var (ctx.var_pool["config.bin.target"]);
-
- // We first see if the value was specified via the configuration
- // mechanism.
- //
- lookup l (lookup_config (new_cfg, rs, var));
-
- // Then see if there is a config hint (e.g., from the cc module).
- //
- bool hint (false);
- if (!l)
- {
- // Note: new_cfg is false for a hinted value.
- //
- if (auto hl = extra.hints[var])
- {
- l = hl;
- hint = true;
- }
- }
-
- if (!l)
- fail (loc) << "unable to determine binutils target" <<
- info << "consider specifying it with " << var <<
- info << "or first load a module that can provide it as a hint, "
- << "such as c or cxx";
-
- // Split/canonicalize the target.
- //
- string s (cast<string> (l));
-
- // Did the user ask us to use config.sub? If this is a hinted value,
- // then we assume it has already been passed through config.sub.
- //
- if (!hint && config_sub)
- {
- s = run<string> (3,
- *config_sub,
- s.c_str (),
- [] (string& l, bool) {return move (l);});
- l5 ([&]{trace << "config.sub target: '" << s << "'";});
- }
-
- try
- {
- target_triplet t (s);
-
- l5 ([&]{trace << "canonical target: '" << t.string () << "'; "
- << "class: " << t.class_;});
-
- assert (!hint || s == t.representation ());
-
- // Also enter as bin.target.{cpu,vendor,system,version,class}
- // for convenience of access.
- //
- rs.assign<string> ("bin.target.cpu") = t.cpu;
- rs.assign<string> ("bin.target.vendor") = t.vendor;
- rs.assign<string> ("bin.target.system") = t.system;
- rs.assign<string> ("bin.target.version") = t.version;
- rs.assign<string> ("bin.target.class") = t.class_;
-
- rs.assign<target_triplet> ("bin.target") = move (t);
- }
- catch (const invalid_argument& e)
- {
- // This is where we suggest that the user specifies --config-sub
- // to help us out.
- //
- fail << "unable to parse binutils target '" << s << "': " << e <<
- info << "consider using the --config-sub option";
- }
- }
-
- // config.bin.pattern
- //
- {
- const variable& var (ctx.var_pool["config.bin.pattern"]);
-
- // We first see if the value was specified via the configuration
- // mechanism.
- //
- lookup l (lookup_config (new_cfg, rs, var));
-
- // Then see if there is a config hint (e.g., from the C++ module).
- //
- if (!l)
- {
- // Note: new_cfg is false for a hinted value.
- //
- if (auto hl = extra.hints[var])
- l = hl;
- }
-
- // For ease of use enter it as bin.pattern (since it can come from
- // different places).
- //
- if (l)
- {
- const string& s (cast<string> (l));
-
- if (s.empty () ||
- (!path::traits_type::is_separator (s.back ()) &&
- s.find ('*') == string::npos))
- {
- fail << "missing '*' or trailing '"
- << char (path::traits_type::directory_separator)
- << "' in binutils pattern '" << s << "'";
- }
-
- rs.assign<string> ("bin.pattern") = s;
- }
- }
-
- // If this is a configuration with new values, then print the report
- // at verbosity level 2 and up (-v).
- //
- if (verb >= (new_cfg ? 2 : 3))
- {
- diag_record dr (text);
+ dr << "bin " << project (rs) << '@' << rs << '\n'
+ << " target " << *tgt;
- dr << "bin " << project (rs) << '@' << rs << '\n'
- << " target " << cast<target_triplet> (rs["bin.target"]);
-
- if (auto l = rs["bin.pattern"])
- dr << '\n'
- << " pattern " << cast<string> (l);
- }
+ if (pat != nullptr)
+ dr << '\n'
+ << " pattern " << *pat;
}
return true;
@@ -428,7 +432,7 @@ namespace build2
// Cache some config values we will be needing below.
//
- const string& tclass (cast<string> (rs["bin.target.class"]));
+ const target_triplet& tgt (cast<target_triplet> (rs["bin.target"]));
// Register target types and configure their default "installability".
//
@@ -497,12 +501,12 @@ namespace build2
// bin/, not lib/.
//
if (install_loaded)
- install_path<libs> (bs,
- dir_path (tclass == "windows" ? "bin" : "lib"));
+ install_path<libs> (
+ bs, dir_path (tgt.class_ == "windows" ? "bin" : "lib"));
// Create additional target types for certain targets.
//
- if (tclass == "windows")
+ if (tgt.class_ == "windows")
{
// Import library.
//
@@ -515,6 +519,17 @@ namespace build2
install_mode<libi> (bs, "644");
}
}
+
+ if (tgt.cpu == "wasm32" || tgt.cpu == "wasm64")
+ {
+ const target_type& wasm (bs.derive_target_type<file> ("wasm").first);
+
+ if (install_loaded)
+ {
+ install_path (bs, wasm, dir_path ("bin")); // Goes to install.bin
+ install_mode (bs, wasm, "644"); // But not executable.
+ }
+ }
}
// Register rules.
diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx
index 8274a04..832ed9b 100644
--- a/libbuild2/cc/compile-rule.cxx
+++ b/libbuild2/cc/compile-rule.cxx
@@ -3124,6 +3124,18 @@ namespace build2
}
}
+ if (ctype == compiler_type::clang && cvariant == "emscripten")
+ {
+ if (x_lang == lang::cxx)
+ {
+ if (!find_option_prefix ("DISABLE_EXCEPTION_CATCHING=", args))
+ {
+ args.push_back ("-s");
+ args.push_back ("DISABLE_EXCEPTION_CATCHING=0");
+ }
+ }
+ }
+
append_options (args, cmode,
cmode.size () - (modules && clang ? 1 : 0));
append_sys_inc_options (args); // Extra system header dirs (last).
@@ -4355,6 +4367,18 @@ namespace build2
}
}
+ if (ctype == compiler_type::clang && cvariant == "emscripten")
+ {
+ if (x_lang == lang::cxx)
+ {
+ if (!find_option_prefix ("DISABLE_EXCEPTION_CATCHING=", args))
+ {
+ args.push_back ("-s");
+ args.push_back ("DISABLE_EXCEPTION_CATCHING=0");
+ }
+ }
+ }
+
append_options (args, cmode,
cmode.size () - (modules && clang ? 1 : 0));
append_sys_inc_options (args);
@@ -5921,299 +5945,324 @@ namespace build2
size_t out_i (0); // Index of the -o option.
size_t lang_n (0); // Number of lang options.
- if (cclass == compiler_class::msvc)
+ switch (cclass)
{
- // The /F*: option variants with separate names only became available
- // in VS2013/12.0. Why do we bother? Because the command line suddenly
- // becomes readable.
- //
- // Also, clang-cl does not yet support them, at least not in 8 or 9.
- //
- bool fc (cmaj >= 18 && cvariant != "clang");
+ case compiler_class::msvc:
+ {
+ // The /F*: option variants with separate names only became
+ // available in VS2013/12.0. Why do we bother? Because the command
+ // line suddenly becomes readable.
+ //
+ // Also, clang-cl does not yet support them, at least not in 8 or 9.
+ //
+ bool fc (cmaj >= 18 && cvariant != "clang");
- args.push_back ("/nologo");
+ args.push_back ("/nologo");
- append_options (args, cmode);
+ append_options (args, cmode);
- if (md.pp != preprocessed::all)
- append_sys_inc_options (args); // Extra system header dirs (last).
+ if (md.pp != preprocessed::all)
+ append_sys_inc_options (args); // Extra system header dirs (last).
+
+ // While we want to keep the low-level build as "pure" as possible,
+ // the two misguided defaults, C++ exceptions and runtime, just have
+ // to be fixed. Otherwise the default build is pretty much unusable.
+ // But we also make sure that the user can easily disable our
+ // defaults: if we see any relevant options explicitly specified, we
+ // take our hands off.
+ //
+ // For C looks like no /EH* (exceptions supported but no C++ objects
+ // destroyed) is a reasonable default.
+ //
+ if (x_lang == lang::cxx && !find_option_prefix ("/EH", args))
+ args.push_back ("/EHsc");
+
+ // The runtime is a bit more interesting. At first it may seem like
+ // a good idea to be a bit clever and use the static runtime if we
+ // are building obja{}. And for obje{} we could decide which runtime
+ // to use based on the library link order: if it is static-only,
+ // then we could assume the static runtime. But it is indeed too
+ // clever: when building liba{} we have no idea who is going to use
+ // it. It could be an exe{} that links both static and shared
+ // libraries (and is therefore built with the shared runtime). And
+ // to safely use the static runtime, everything must be built with
+ // /MT and there should be no DLLs in the picture. So we are going
+ // to play it safe and always default to the shared runtime.
+ //
+ // In a similar vein, it would seem reasonable to use the debug
+ // runtime if we are compiling with debug. But, again, there will be
+ // fireworks if we have some projects built with debug and some
+ // without and then we try to link them together (which is not an
+ // unreasonable thing to do). So by default we will always use the
+ // release runtime.
+ //
+ if (!find_option_prefixes ({"/MD", "/MT"}, args))
+ args.push_back ("/MD");
- // While we want to keep the low-level build as "pure" as possible,
- // the two misguided defaults, C++ exceptions and runtime, just have
- // to be fixed. Otherwise the default build is pretty much unusable.
- // But we also make sure that the user can easily disable our
- // defaults: if we see any relevant options explicitly specified, we
- // take our hands off.
- //
- // For C looks like no /EH* (exceptions supported but no C++ objects
- // destroyed) is a reasonable default.
- //
- if (x_lang == lang::cxx && !find_option_prefix ("/EH", args))
- args.push_back ("/EHsc");
-
- // The runtime is a bit more interesting. At first it may seem like a
- // good idea to be a bit clever and use the static runtime if we are
- // building obja{}. And for obje{} we could decide which runtime to
- // use based on the library link order: if it is static-only, then we
- // could assume the static runtime. But it is indeed too clever: when
- // building liba{} we have no idea who is going to use it. It could be
- // an exe{} that links both static and shared libraries (and is
- // therefore built with the shared runtime). And to safely use the
- // static runtime, everything must be built with /MT and there should
- // be no DLLs in the picture. So we are going to play it safe and
- // always default to the shared runtime.
- //
- // In a similar vein, it would seem reasonable to use the debug runtime
- // if we are compiling with debug. But, again, there will be fireworks
- // if we have some projects built with debug and some without and then
- // we try to link them together (which is not an unreasonable thing to
- // do). So by default we will always use the release runtime.
- //
- if (!find_option_prefixes ({"/MD", "/MT"}, args))
- args.push_back ("/MD");
+ msvc_sanitize_cl (args);
- msvc_sanitize_cl (args);
+ append_header_options (env, args, header_args, a, t, md, md.dd);
+ append_module_options (env, args, module_args, a, t, md, md.dd);
- append_header_options (env, args, header_args, a, t, md, md.dd);
- append_module_options (env, args, module_args, a, t, md, md.dd);
+ // The presence of /Zi or /ZI causes the compiler to write debug
+ // info to the .pdb file. By default it is a shared file called
+ // vcNN.pdb (where NN is the VC version) created (wait for it) in
+ // the current working directory (and not the directory of the .obj
+ // file). Also, because it is shared, there is a special Windows
+ // service that serializes access. We, of course, want none of that
+ // so we will create a .pdb per object file.
+ //
+ // Note that this also changes the name of the .idb file (used for
+ // minimal rebuild and incremental compilation): cl.exe take the /Fd
+ // value and replaces the .pdb extension with .idb.
+ //
+ // Note also that what we are doing here appears to be incompatible
+ // with PCH (/Y* options) and /Gm (minimal rebuild).
+ //
+ if (find_options ({"/Zi", "/ZI"}, args))
+ {
+ if (fc)
+ args.push_back ("/Fd:");
+ else
+ out1 = "/Fd";
+
+ out1 += relo.string ();
+ out1 += ".pdb";
+
+ args.push_back (out1.c_str ());
+ }
- // The presence of /Zi or /ZI causes the compiler to write debug info
- // to the .pdb file. By default it is a shared file called vcNN.pdb
- // (where NN is the VC version) created (wait for it) in the current
- // working directory (and not the directory of the .obj file). Also,
- // because it is shared, there is a special Windows service that
- // serializes access. We, of course, want none of that so we will
- // create a .pdb per object file.
- //
- // Note that this also changes the name of the .idb file (used for
- // minimal rebuild and incremental compilation): cl.exe take the /Fd
- // value and replaces the .pdb extension with .idb.
- //
- // Note also that what we are doing here appears to be incompatible
- // with PCH (/Y* options) and /Gm (minimal rebuild).
- //
- if (find_options ({"/Zi", "/ZI"}, args))
- {
if (fc)
- args.push_back ("/Fd:");
+ {
+ args.push_back ("/Fo:");
+ args.push_back (relo.string ().c_str ());
+ }
else
- out1 = "/Fd";
+ {
+ out = "/Fo" + relo.string ();
+ args.push_back (out.c_str ());
+ }
- out1 += relo.string ();
- out1 += ".pdb";
+ // @@ MODHDR MSVC
+ //
+ if (ut == unit_type::module_iface)
+ {
+ relm = relative (tp);
- args.push_back (out1.c_str ());
- }
+ args.push_back ("/module:interface");
+ args.push_back ("/module:output");
+ args.push_back (relm.string ().c_str ());
+ }
- if (fc)
- {
- args.push_back ("/Fo:");
- args.push_back (relo.string ().c_str ());
- }
- else
- {
- out = "/Fo" + relo.string ();
- args.push_back (out.c_str ());
- }
+ // Note: no way to indicate that the source if already preprocessed.
- // @@ MODHDR MSVC
- //
- if (ut == unit_type::module_iface)
- {
- relm = relative (tp);
+ args.push_back ("/c"); // Compile only.
+ append_lang_options (args, md); // Compile as.
+ args.push_back (sp->string ().c_str ()); // Note: relied on being last.
- args.push_back ("/module:interface");
- args.push_back ("/module:output");
- args.push_back (relm.string ().c_str ());
+ break;
}
-
- // Note: no way to indicate that the source if already preprocessed.
-
- args.push_back ("/c"); // Compile only.
- append_lang_options (args, md); // Compile as.
- args.push_back (sp->string ().c_str ()); // Note: relied on being last.
- }
- else
- {
- if (ot == otype::s)
+ case compiler_class::gcc:
{
- // On Darwin, Win32 -fPIC is the default.
- //
- if (tclass == "linux" || tclass == "bsd")
- args.push_back ("-fPIC");
- }
+ if (ot == otype::s)
+ {
+ // On Darwin, Win32 -fPIC is the default.
+ //
+ if (tclass == "linux" || tclass == "bsd")
+ args.push_back ("-fPIC");
+ }
- if (tsys == "win32-msvc")
- {
- switch (ctype)
+ if (tsys == "win32-msvc")
{
- case compiler_type::clang:
+ switch (ctype)
{
- // Default to the /EHsc exceptions support for C++, similar to
- // the the MSVC case above.
- //
- // Note that both vanilla clang++ and clang-cl drivers add
- // -fexceptions and -fcxx-exceptions by default. However,
- // clang-cl also adds -fexternc-nounwind, which implements the
- // 'c' part in /EHsc. Note that adding this option is not a mere
- // optimization, as we have discovered through some painful
- // experience; see Clang bug #45021.
- //
- // Let's also omit this option if -f[no]-exceptions is specified
- // explicitly.
- //
- if (x_lang == lang::cxx)
+ case compiler_type::clang:
{
- if (!find_options ({"-fexceptions", "-fno-exceptions"}, args))
+ // Default to the /EHsc exceptions support for C++, similar to
+ // the the MSVC case above.
+ //
+ // Note that both vanilla clang++ and clang-cl drivers add
+ // -fexceptions and -fcxx-exceptions by default. However,
+ // clang-cl also adds -fexternc-nounwind, which implements the
+ // 'c' part in /EHsc. Note that adding this option is not a
+ // mere optimization, as we have discovered through some
+ // painful experience; see Clang bug #45021.
+ //
+ // Let's also omit this option if -f[no]-exceptions is
+ // specified explicitly.
+ //
+ if (x_lang == lang::cxx)
{
- args.push_back ("-Xclang");
- args.push_back ("-fexternc-nounwind");
+ if (!find_options ({"-fexceptions", "-fno-exceptions"}, args))
+ {
+ args.push_back ("-Xclang");
+ args.push_back ("-fexternc-nounwind");
+ }
}
- }
- // Default to the multi-threaded DLL runtime (/MD), similar to
- // the MSVC case above.
- //
- // Clang's MSVC.cpp will not link the default runtime if either
- // -nostdlib or -nostartfiles is specified. Let's do the same.
- //
- initializer_list<const char*> os {"-nostdlib", "-nostartfiles"};
- if (!find_options (os, cmode) && !find_options (os, args))
- {
- args.push_back ("-D_MT");
- args.push_back ("-D_DLL");
-
- // All these -Xclang --dependent-lib=... add quite a bit of
- // noise to the command line. The alternative is to use the
- // /DEFAULTLIB option during linking. The drawback of that
- // approach is that now we can theoretically build the object
- // file for one runtime but try to link it with something
- // else.
- //
- // For example, an installed static library was built for a
- // non-debug runtime while a project that links it uses
- // debug. With the --dependent-lib approach we will try to
- // link multiple runtimes while with /DEFAULTLIB we may end up
- // with unresolved symbols (but things might also work out
- // fine, unless the runtimes have incompatible ABIs).
+ // Default to the multi-threaded DLL runtime (/MD), similar to
+ // the MSVC case above.
//
- // Let's start with /DEFAULTLIB and see how it goes (see the
- // link rule).
+ // Clang's MSVC.cpp will not link the default runtime if
+ // either -nostdlib or -nostartfiles is specified. Let's do
+ // the same.
//
+ initializer_list<const char*> os {"-nostdlib", "-nostartfiles"};
+ if (!find_options (os, cmode) && !find_options (os, args))
+ {
+ args.push_back ("-D_MT");
+ args.push_back ("-D_DLL");
+
+ // All these -Xclang --dependent-lib=... add quite a bit of
+ // noise to the command line. The alternative is to use the
+ // /DEFAULTLIB option during linking. The drawback of that
+ // approach is that now we can theoretically build the
+ // object file for one runtime but try to link it with
+ // something else.
+ //
+ // For example, an installed static library was built for a
+ // non-debug runtime while a project that links it uses
+ // debug. With the --dependent-lib approach we will try to
+ // link multiple runtimes while with /DEFAULTLIB we may end
+ // up with unresolved symbols (but things might also work
+ // out fine, unless the runtimes have incompatible ABIs).
+ //
+ // Let's start with /DEFAULTLIB and see how it goes (see the
+ // link rule).
+ //
#if 0
- args.push_back ("-Xclang");
- args.push_back ("--dependent-lib=msvcrt");
+ args.push_back ("-Xclang");
+ args.push_back ("--dependent-lib=msvcrt");
- // This provides POSIX compatibility (map open() to _open(),
- // etc).
- //
- args.push_back ("-Xclang");
- args.push_back ("--dependent-lib=oldnames");
+ // This provides POSIX compatibility (map open() to _open(),
+ // etc).
+ //
+ args.push_back ("-Xclang");
+ args.push_back ("--dependent-lib=oldnames");
#endif
+ }
+
+ break;
}
+ case compiler_type::gcc:
+ case compiler_type::msvc:
+ case compiler_type::icc:
+ assert (false);
+ }
+ }
- break;
+ // For now Emscripten defaults to partial C++ exceptions support
+ // (you can throw but not catch). We enable full support unless it
+ // was explicitly disabled by the user.
+ //
+ if (ctype == compiler_type::clang && cvariant == "emscripten")
+ {
+ if (x_lang == lang::cxx)
+ {
+ if (!find_option_prefix ("DISABLE_EXCEPTION_CATCHING=", args))
+ {
+ args.push_back ("-s");
+ args.push_back ("DISABLE_EXCEPTION_CATCHING=0");
+ }
}
- case compiler_type::gcc:
- case compiler_type::msvc:
- case compiler_type::icc:
- assert (false);
}
- }
- append_options (args, cmode);
+ append_options (args, cmode);
- if (md.pp != preprocessed::all)
- append_sys_inc_options (args); // Extra system header dirs (last).
+ if (md.pp != preprocessed::all)
+ append_sys_inc_options (args); // Extra system header dirs (last).
- append_header_options (env, args, header_args, a, t, md, md.dd);
- append_module_options (env, args, module_args, a, t, md, md.dd);
+ append_header_options (env, args, header_args, a, t, md, md.dd);
+ append_module_options (env, args, module_args, a, t, md, md.dd);
- // Note: the order of the following options is relied upon below.
- //
- out_i = args.size (); // Index of the -o option.
+ // Note: the order of the following options is relied upon below.
+ //
+ out_i = args.size (); // Index of the -o option.
- if (ut == unit_type::module_iface || ut == unit_type::module_header)
- {
- switch (ctype)
+ if (ut == unit_type::module_iface || ut == unit_type::module_header)
{
- case compiler_type::gcc:
+ switch (ctype)
{
- // Output module file is specified in the mapping file, the
- // same as input.
- //
- if (ut != unit_type::module_header) // No object file.
+ case compiler_type::gcc:
{
- args.push_back ("-o");
- args.push_back (relo.string ().c_str ());
- args.push_back ("-c");
+ // Output module file is specified in the mapping file, the
+ // same as input.
+ //
+ if (ut != unit_type::module_header) // No object file.
+ {
+ args.push_back ("-o");
+ args.push_back (relo.string ().c_str ());
+ args.push_back ("-c");
+ }
+ break;
}
- break;
- }
- case compiler_type::clang:
- {
- relm = relative (tp);
+ case compiler_type::clang:
+ {
+ relm = relative (tp);
- args.push_back ("-o");
- args.push_back (relm.string ().c_str ());
- args.push_back ("--precompile");
+ args.push_back ("-o");
+ args.push_back (relm.string ().c_str ());
+ args.push_back ("--precompile");
- // Without this option Clang's .pcm will reference source files.
- // In our case this file may be transient (.ii). Plus, it won't
- // play nice with distributed compilation.
- //
- args.push_back ("-Xclang");
- args.push_back ("-fmodules-embed-all-files");
+ // Without this option Clang's .pcm will reference source
+ // files. In our case this file may be transient (.ii). Plus,
+ // it won't play nice with distributed compilation.
+ //
+ args.push_back ("-Xclang");
+ args.push_back ("-fmodules-embed-all-files");
- break;
+ break;
+ }
+ case compiler_type::msvc:
+ case compiler_type::icc:
+ assert (false);
}
- case compiler_type::msvc:
- case compiler_type::icc:
- assert (false);
}
- }
- else
- {
- args.push_back ("-o");
- args.push_back (relo.string ().c_str ());
- args.push_back ("-c");
- }
+ else
+ {
+ args.push_back ("-o");
+ args.push_back (relo.string ().c_str ());
+ args.push_back ("-c");
+ }
- lang_n = append_lang_options (args, md);
+ lang_n = append_lang_options (args, md);
- if (md.pp == preprocessed::all)
- {
- // Note that the mode we select must still handle comments and line
- // continuations. So some more compiler-specific voodoo.
- //
- switch (ctype)
+ if (md.pp == preprocessed::all)
{
- case compiler_type::gcc:
+ // Note that the mode we select must still handle comments and
+ // line continuations. So some more compiler-specific voodoo.
+ //
+ switch (ctype)
{
- // -fdirectives-only is available since GCC 4.3.0.
- //
- if (cmaj > 4 || (cmaj == 4 && cmin >= 3))
+ case compiler_type::gcc:
{
- args.push_back ("-fpreprocessed");
- args.push_back ("-fdirectives-only");
+ // -fdirectives-only is available since GCC 4.3.0.
+ //
+ if (cmaj > 4 || (cmaj == 4 && cmin >= 3))
+ {
+ args.push_back ("-fpreprocessed");
+ args.push_back ("-fdirectives-only");
+ }
+ break;
}
- break;
- }
- case compiler_type::clang:
- {
- // Clang handles comments and line continuations in the
- // preprocessed source (it does not have -fpreprocessed).
- //
- break;
+ case compiler_type::clang:
+ {
+ // Clang handles comments and line continuations in the
+ // preprocessed source (it does not have -fpreprocessed).
+ //
+ break;
+ }
+ case compiler_type::icc:
+ break; // Compile as normal source for now.
+ case compiler_type::msvc:
+ assert (false);
}
- case compiler_type::icc:
- break; // Compile as normal source for now.
- case compiler_type::msvc:
- assert (false);
}
- }
- args.push_back (sp->string ().c_str ());
+ args.push_back (sp->string ().c_str ());
+
+ break;
+ }
}
args.push_back (nullptr);
diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx
index c0d04ef..43220b0 100644
--- a/libbuild2/cc/guess.cxx
+++ b/libbuild2/cc/guess.cxx
@@ -265,6 +265,8 @@ namespace build2
" stdlib:=\"netbsd\" \n"
"# elif defined(__APPLE__) \n"
" stdlib:=\"apple\" \n"
+"# elif defined(__EMSCRIPTEN__) \n"
+" stdlib:=\"emscripten\" \n"
"# else \n"
" stdlib:=\"other\" \n"
"# endif \n"
@@ -349,15 +351,17 @@ namespace build2
// Try more specific variants first. Keep msvc last since 'cl' is
// very generic.
//
- if (auto r = check (type::msvc, "clang-cl", "clang")) return *r;
- if (auto r = check (type::clang, "clang" )) return *r;
- if (auto r = check (type::gcc, "gcc" )) return *r;
- if (auto r = check (type::icc, "icc" )) return *r;
- if (auto r = check (type::msvc, "cl" )) return *r;
+ if (auto r = check (type::msvc, "clang-cl", "clang" )) return *r;
+ if (auto r = check (type::clang, "clang" )) return *r;
+ if (auto r = check (type::gcc, "gcc" )) return *r;
+ if (auto r = check (type::icc, "icc" )) return *r;
+ if (auto r = check (type::clang, "emcc", "emscripten")) return *r;
+ if (auto r = check (type::msvc, "cl" )) return *r;
if (check (type::clang, as = "clang++")) es = "clang";
else if (check (type::gcc, as = "g++") ) es = "gcc";
else if (check (type::icc, as = "icpc") ) es = "icc";
+ else if (check (type::clang, as = "em++") ) es = "emcc";
else if (check (type::msvc, as = "c++") ) es = "cc";
o = lang::cxx;
@@ -368,15 +372,17 @@ namespace build2
// Try more specific variants first. Keep msvc last since 'cl' is
// very generic.
//
- if (auto r = check (type::msvc, "clang-cl", "clang")) return *r;
- if (auto r = check (type::clang, "clang++" )) return *r;
- if (auto r = check (type::gcc, "g++" )) return *r;
- if (auto r = check (type::icc, "icpc" )) return *r;
- if (auto r = check (type::msvc, "cl" )) return *r;
+ if (auto r = check (type::msvc, "clang-cl", "clang" )) return *r;
+ if (auto r = check (type::clang, "clang++" )) return *r;
+ if (auto r = check (type::gcc, "g++" )) return *r;
+ if (auto r = check (type::icc, "icpc" )) return *r;
+ if (auto r = check (type::clang, "em++", "emscripten")) return *r;
+ if (auto r = check (type::msvc, "cl" )) return *r;
if (check (type::clang, as = "clang")) es = "clang++";
else if (check (type::gcc, as = "gcc") ) es = "g++";
else if (check (type::icc, as = "icc") ) es = "icpc";
+ else if (check (type::clang, as = "emcc") ) es = "em++";
else if (check (type::msvc, as = "cc") ) es = "c++";
o = lang::c;
@@ -738,10 +744,15 @@ namespace build2
// is not empty, then only "confirm" the pre-guess. Return empty result if
// unable to guess.
//
+ // If the compiler has both type and variant signatures (say, like
+ // clang-emscripten), then the variant goes to signature and type goes to
+ // type_signature. Otherwise, type_signature is not used.
+ //
struct guess_result
{
compiler_id id;
string signature;
+ string type_signature;
string checksum;
process_path path;
@@ -755,8 +766,8 @@ namespace build2
info_ptr info = {nullptr, null_info_deleter};
guess_result () = default;
- guess_result (compiler_id i, string&& s)
- : id (move (i)), signature (move (s)) {}
+ guess_result (compiler_id i, string&& s, string&& ts = {})
+ : id (move (i)), signature (move (s)), type_signature (move (ts)) {}
bool
empty () const {return id.empty ();}
@@ -952,12 +963,14 @@ namespace build2
env.vars = evars;
#endif
- auto run = [&cs, &env, &args] (const char* o,
- auto&& f,
- bool checksum = false) -> guess_result
+ string cache;
+ auto run = [&cs, &env, &args, &cache] (
+ const char* o,
+ auto&& f,
+ bool checksum = false) -> guess_result
{
args[args.size () - 2] = o;
-
+ cache.clear ();
return build2::run<guess_result> (
3 /* verbosity */,
env,
@@ -968,7 +981,8 @@ namespace build2
checksum ? &cs : nullptr);
};
- // Start with -v. This will cover gcc and clang (including clang-cl).
+ // Start with -v. This will cover gcc and clang (including clang-cl and
+ // Emscripten clang).
//
// While icc also writes what may seem like something we can use to
// detect it:
@@ -988,17 +1002,22 @@ namespace build2
pt == type::clang ||
(pt == type::msvc && pv && *pv == "clang")))
{
- auto f = [&xi, &pt] (string& l, bool last) -> guess_result
+ auto f = [&xi, &pt, &cache] (string& l, bool last) -> guess_result
{
if (xi)
{
+ //@@ TODO: what about type_signature? Or do we just assume that
+ // the variant version will be specified along with type
+ // version? Do we even have this ability?
+
// The signature line is first in Clang and last in GCC.
//
- if (xi->type != type::gcc || last)
- return guess_result (*xi, move (l));
+ return (xi->type != type::gcc || last
+ ? guess_result (*xi, move (l))
+ : guess_result ());
}
- // The gcc/g++ -v output will have a last line in the form:
+ // The gcc -v output will have a last line in the form:
//
// "gcc version X.Y.Z ..."
//
@@ -1012,11 +1031,14 @@ namespace build2
// gcc version 5.1.0 (Ubuntu 5.1.0-0ubuntu11~14.04.1)
// gcc version 6.0.0 20160131 (experimental) (GCC)
//
- if (last && l.compare (0, 4, "gcc ") == 0)
- return guess_result (compiler_id {type::gcc, ""}, move (l));
+ if (cache.empty ())
+ {
+ if (last && l.compare (0, 4, "gcc ") == 0)
+ return guess_result (compiler_id {type::gcc, ""}, move (l));
+ }
- // The Apple clang/clang++ -v output will have a line (currently
- // first) in the form:
+ // The Apple clang -v output will have a line (currently first) in
+ // the form:
//
// "Apple (LLVM|clang) version X.Y.Z ..."
//
@@ -1040,13 +1062,42 @@ namespace build2
// Check for Apple clang before the vanilla one since the above line
// also includes "clang".
//
- if (l.compare (0, 6, "Apple ") == 0 &&
- (l.compare (6, 5, "LLVM ") == 0 ||
- l.compare (6, 6, "clang ") == 0))
- return guess_result (compiler_id {type::clang, "apple"}, move (l));
+ if (cache.empty ())
+ {
+ if (l.compare (0, 6, "Apple ") == 0 &&
+ (l.compare (6, 5, "LLVM ") == 0 ||
+ l.compare (6, 6, "clang ") == 0))
+ return guess_result (compiler_id {type::clang, "apple"}, move (l));
+ }
- // The vanilla clang/clang++ -v output will have a first line in the
- // form:
+ // Emscripten emcc -v prints its own version and the clang version,
+ // for example:
+ //
+ // emcc (...) 2.0.8
+ // clang version 12.0.0 (...)
+ //
+ // The order, however is not guaranteed (see Emscripten issue
+ // #12654). So things are going to get hairy.
+ //
+ if (l.compare (0, 5, "emcc ") == 0)
+ {
+ if (cache.empty ())
+ {
+ // Cache the emcc line and continue in order to get the clang
+ // line.
+ //
+ cache = move (l);
+ return guess_result ();
+ }
+ else if (cache.find ("clang ") != string::npos)
+ {
+ return guess_result (compiler_id {type::clang, "emscripten"},
+ move (l),
+ move (cache));
+ }
+ }
+
+ // The vanilla clang -v output will have a first line in the form:
//
// "[... ]clang version X.Y.Z[-...] ..."
//
@@ -1062,10 +1113,31 @@ namespace build2
//
if (l.find ("clang ") != string::npos)
{
- return guess_result (pt == type::msvc
- ? compiler_id {type::msvc, "clang"}
- : compiler_id {type::clang, ""},
- move (l));
+ if (cache.empty ())
+ {
+ // Cache the clang line and continue in order to get the variant
+ // line, if any.
+ //
+ cache = move (l);
+ return guess_result ();
+ }
+ else if (cache.compare (0, 5, "emcc ") == 0)
+ {
+ return guess_result (compiler_id {type::clang, "emscripten"},
+ move (cache),
+ move (l));
+ }
+ }
+
+ if (last)
+ {
+ if (cache.find ("clang ") != string::npos)
+ {
+ return guess_result (pt == type::msvc
+ ? compiler_id {type::msvc, "clang"}
+ : compiler_id {type::clang, ""},
+ move (cache));
+ }
}
return guess_result ();
@@ -1076,10 +1148,6 @@ namespace build2
// clang) which makes sense to include into the compiler checksum. So
// ask run() to calculate it for every line of the -v ouput.
//
- // One notable consequence of this is that if the locale changes
- // (e.g., via LC_ALL), then the compiler signature will most likely
- // change as well because of the translated text.
- //
r = run ("-v", f, true /* checksum */);
if (r.empty ())
@@ -2155,8 +2223,8 @@ namespace build2
const strings* c_lo, const strings* x_lo,
guess_result&& gr, sha256& cs)
{
- // This function handles both vanialla Clang, including its clang-cl
- // variant, as well as Apple Clang.
+ // This function handles vanialla Clang, including its clang-cl variant,
+ // as well as Apple and Emscripten variants.
//
// The clang-cl variant appears to be a very thin wrapper over the
// standard clang/clang++ drivers. In addition to the cl options, it
@@ -2166,6 +2234,7 @@ namespace build2
//
bool cl (gr.id.type == compiler_type::msvc);
bool apple (gr.id.variant == "apple");
+ bool emscr (gr.id.variant == "emscripten");
const process_path& xp (gr.path);
@@ -2177,31 +2246,16 @@ namespace build2
// "[... ]clang version A.B.C[( |-)...]"
// "Apple (clang|LLVM) version A.B[.C] ..."
//
- compiler_version ver;
- optional<compiler_version> var_ver;
+ // We will also reuse this code to parse the Emscripten version which
+ // is quite similar:
+ //
+ // emcc (...) 2.0.8
+ //
+ auto extract_version = [] (const string& s, bool patch, const char* what)
+ -> compiler_version
{
- auto df = make_diag_frame (
- [&xm](const diag_record& dr)
- {
- dr << info << "use config." << xm << ".version to override";
- });
-
- // Treat the custom version as just a tail of the signature.
- //
- const string& s (xv == nullptr ? gr.signature : *xv);
-
- // Some overrides for testing.
- //
- //s = "clang version 3.7.0 (tags/RELEASE_370/final)";
- //
- //gr.id.variant = "apple";
- //s = "Apple LLVM version 7.3.0 (clang-703.0.16.1)";
- //s = "Apple clang version 3.1 (tags/Apple/clang-318.0.58) (based on LLVM 3.1svn)";
+ compiler_version ver;
- // Scan the string as words and look for one that looks like a
- // version. Use '-' as a second delimiter to handle versions like
- // "3.6.0-2ubuntu1~trusty1".
- //
size_t b (0), e (0);
while (next_word (s, b, e, ' ', '-'))
{
@@ -2216,14 +2270,17 @@ namespace build2
}
if (b == e)
- fail << "unable to extract Clang version from '" << s << "'";
+ fail << "unable to extract " << what << " version from '" << s << "'"
+ << endf;
ver.string.assign (s, b, string::npos);
// Split the version into components.
//
size_t vb (b), ve (b);
- auto next = [&s, b, e, &vb, &ve] (const char* m, bool opt) -> uint64_t
+ auto next = [&s, what,
+ b, e,
+ &vb, &ve] (const char* m, bool opt) -> uint64_t
{
try
{
@@ -2236,77 +2293,124 @@ namespace build2
catch (const invalid_argument&) {}
catch (const out_of_range&) {}
- fail << "unable to extract Clang " << m << " version from '"
+ fail << "unable to extract " << what << ' ' << m << " version from '"
<< string (s, b, e - b) << "'" << endf;
};
ver.major = next ("major", false);
ver.minor = next ("minor", false);
- ver.patch = next ("patch", apple);
+ ver.patch = next ("patch", patch);
if (e != s.size ())
ver.build.assign (s, e + 1, string::npos);
- // Map Apple to vanilla Clang version, preserving the original as
- // the variant version.
+ return ver;
+ };
+
+ compiler_version ver;
+ {
+ auto df = make_diag_frame (
+ [&xm](const diag_record& dr)
+ {
+ dr << info << "use config." << xm << ".version to override";
+ });
+
+ // Treat the custom version as just a tail of the signature.
//
- if (apple)
- {
- var_ver = move (ver);
+ // @@ TODO: should we have type_version here (and suggest that
+ // in diag_frame above?
+ //
+ const string& s (xv != nullptr
+ ? *xv
+ : emscr ? gr.type_signature : gr.signature);
- // Apple no longer discloses the mapping so it's a guesswork and we
- // better be conservative. For details see:
- //
- // https://gist.github.com/yamaya/2924292
- //
- // Specifically, we now look in the libc++'s __config file for the
- // _LIBCPP_VERSION and use the previous version as a conservative
- // estimate.
- //
- // Note that this is Apple Clang version and not XCode version.
- //
- // 4.2 -> 3.2svn
- // 5.0 -> 3.3svn
- // 5.1 -> 3.4svn
- // 6.0 -> 3.5svn
- // 6.1.0 -> 3.6svn
- // 7.0.0 -> 3.7
- // 7.3.0 -> 3.8
- // 8.0.0 -> 3.9
- // 8.1.0 -> ?
- // 9.0.0 -> 4.0
- // 9.1.0 -> 5.0
- // 10.0.0 -> 6.0
- // 11.0.0 -> 7.0
- // 11.0.3 -> 8.0
- //
- uint64_t mj (var_ver->major);
- uint64_t mi (var_ver->minor);
- uint64_t pa (var_ver->patch);
-
- if (mj >= 12) {mj = 8; mi = 0;}
- else if (mj == 11 && (mi > 0 || pa >= 3)) {mj = 8; mi = 0;}
- else if (mj == 11) {mj = 7; mi = 0;}
- else if (mj == 10) {mj = 6; mi = 0;}
- else if (mj == 9 && mi >= 1) {mj = 5; mi = 0;}
- else if (mj == 9) {mj = 4; mi = 0;}
- else if (mj == 8) {mj = 3; mi = 9;}
- else if (mj == 7 && mi >= 3) {mj = 3; mi = 8;}
- else if (mj == 7) {mj = 3; mi = 7;}
- else if (mj == 6 && mi >= 1) {mj = 3; mi = 5;}
- else if (mj == 6) {mj = 3; mi = 4;}
- else if (mj == 5 && mi >= 1) {mj = 3; mi = 3;}
- else if (mj == 5) {mj = 3; mi = 2;}
- else if (mj == 4 && mi >= 2) {mj = 3; mi = 1;}
- else {mj = 3; mi = 0;}
-
- ver = compiler_version {
- to_string (mj) + '.' + to_string (mi) + ".0",
- mj,
- mi,
- 0,
- ""};
- }
+ // Some overrides for testing.
+ //
+ //s = "clang version 3.7.0 (tags/RELEASE_370/final)";
+ //
+ //gr.id.variant = "apple";
+ //s = "Apple LLVM version 7.3.0 (clang-703.0.16.1)";
+ //s = "Apple clang version 3.1 (tags/Apple/clang-318.0.58) (based on LLVM 3.1svn)";
+
+ // Scan the string as words and look for one that looks like a
+ // version. Use '-' as a second delimiter to handle versions like
+ // "3.6.0-2ubuntu1~trusty1".
+ //
+ ver = extract_version (s, apple, "Clang");
+ }
+
+ optional<compiler_version> var_ver;
+ if (apple)
+ {
+ // Map Apple to vanilla Clang version, preserving the original as the
+ // variant version.
+ //
+ var_ver = move (ver);
+
+ // Apple no longer discloses the mapping so it's a guesswork and we
+ // better be conservative. For details see:
+ //
+ // https://gist.github.com/yamaya/2924292
+ //
+ // Specifically, we now look in the libc++'s __config file for the
+ // _LIBCPP_VERSION and use the previous version as a conservative
+ // estimate.
+ //
+ // Note that this is Apple Clang version and not XCode version.
+ //
+ // 4.2 -> 3.2svn
+ // 5.0 -> 3.3svn
+ // 5.1 -> 3.4svn
+ // 6.0 -> 3.5svn
+ // 6.1.0 -> 3.6svn
+ // 7.0.0 -> 3.7
+ // 7.3.0 -> 3.8
+ // 8.0.0 -> 3.9
+ // 8.1.0 -> ?
+ // 9.0.0 -> 4.0
+ // 9.1.0 -> 5.0
+ // 10.0.0 -> 6.0
+ // 11.0.0 -> 7.0
+ // 11.0.3 -> 8.0
+ //
+ uint64_t mj (var_ver->major);
+ uint64_t mi (var_ver->minor);
+ uint64_t pa (var_ver->patch);
+
+ if (mj >= 12) {mj = 8; mi = 0;}
+ else if (mj == 11 && (mi > 0 || pa >= 3)) {mj = 8; mi = 0;}
+ else if (mj == 11) {mj = 7; mi = 0;}
+ else if (mj == 10) {mj = 6; mi = 0;}
+ else if (mj == 9 && mi >= 1) {mj = 5; mi = 0;}
+ else if (mj == 9) {mj = 4; mi = 0;}
+ else if (mj == 8) {mj = 3; mi = 9;}
+ else if (mj == 7 && mi >= 3) {mj = 3; mi = 8;}
+ else if (mj == 7) {mj = 3; mi = 7;}
+ else if (mj == 6 && mi >= 1) {mj = 3; mi = 5;}
+ else if (mj == 6) {mj = 3; mi = 4;}
+ else if (mj == 5 && mi >= 1) {mj = 3; mi = 3;}
+ else if (mj == 5) {mj = 3; mi = 2;}
+ else if (mj == 4 && mi >= 2) {mj = 3; mi = 1;}
+ else {mj = 3; mi = 0;}
+
+ ver = compiler_version {
+ to_string (mj) + '.' + to_string (mi) + ".0",
+ mj,
+ mi,
+ 0,
+ ""};
+ }
+ else if (emscr)
+ {
+ // Extract Emscripten version.
+ //
+ auto df = make_diag_frame (
+ [&xm](const diag_record& dr)
+ {
+ dr << info << "use config." << xm << ".version to override";
+ });
+
+ var_ver = extract_version (gr.signature, false, "Emscripten");
}
// Figure out the target architecture.
@@ -2414,13 +2518,24 @@ namespace build2
}
}
- // Derive the compiler toolchain pattern. Try clang/clang++, the gcc/g++
- // alias, as well as cc/c++.
+ // Derive the compiler toolchain pattern.
//
string cpat;
- if (!cl)
+ if (cl)
+ ;
+ else if (emscr)
+ {
+ cpat = pattern (xc, xl == lang::c ? "emcc" : "em++");
+
+ // Emscripten provides the emar/emranlib wrappers (over llvm-*).
+ //
+ bpat = pattern (xc, xl == lang::c ? "cc" : "++", "m");
+ }
+ else
{
+ // Try clang/clang++, the gcc/g++ alias, as well as cc/c++.
+ //
cpat = pattern (xc, xl == lang::c ? "clang" : "clang++");
if (cpat.empty ())
@@ -2950,11 +3065,16 @@ namespace build2
x_mo, c_po, x_po, c_co, x_co, c_lo, x_lo,
move (gr), cs));
- // By default use the signature line to generate the checksum.
+ // By default use the signature line(s) to generate the checksum.
//
if (cs.empty ())
+ {
cs.append (r.signature);
+ if (!gr.type_signature.empty ())
+ cs.append (gr.type_signature);
+ }
+
r.checksum = cs.string ();
// Derive binutils pattern unless this has already been done by the
@@ -3051,7 +3171,14 @@ namespace build2
switch (id.type)
{
case type::gcc: s = "gcc"; break;
- case type::clang: s = "clang"; break;
+ case type::clang:
+ {
+ if (id.variant == "emscripten")
+ s = "emcc";
+ else
+ s = "clang";
+ break;
+ }
case type::icc: s = "icc"; break;
case type::msvc:
{
@@ -3067,7 +3194,14 @@ namespace build2
switch (id.type)
{
case type::gcc: s = "g++"; break;
- case type::clang: s = "clang++"; break;
+ case type::clang:
+ {
+ if (id.variant == "emscripten")
+ s = "em++";
+ else
+ s = "clang";
+ break;
+ }
case type::icc: s = "icpc"; break;
case type::msvc:
{
diff --git a/libbuild2/cc/guess.hxx b/libbuild2/cc/guess.hxx
index 3c58bec..868e925 100644
--- a/libbuild2/cc/guess.hxx
+++ b/libbuild2/cc/guess.hxx
@@ -19,12 +19,13 @@ namespace build2
//
// Currently recognized compilers and their ids:
//
- // gcc GCC gcc/g++
- // clang Vanilla Clang clang/clang++
- // clang-apple Apple Clang clang/clang++ and the gcc/g++ "alias"
- // msvc Microsoft cl.exe
- // msvc-clang Clang in the cl compatibility mode (clang-cl)
- // icc Intel icc/icpc
+ // gcc GCC gcc/g++
+ // clang Vanilla Clang clang/clang++
+ // clang-apple Apple Clang clang/clang++ and the gcc/g++ "alias"
+ // clang-emscripten Emscripten emcc/em++.
+ // msvc Microsoft cl.exe
+ // msvc-clang Clang in the cl compatibility mode (clang-cl)
+ // icc Intel icc/icpc
//
// Note that the user can provide a custom id with one of the predefined
// types and a custom variant (say 'gcc-tasking').
@@ -83,7 +84,7 @@ namespace build2
//
// Currently defined compiler classes:
//
- // gcc gcc, clang, clang-apple, icc (on non-Windows)
+ // gcc gcc, clang, clang-{apple,emscripten}, icc (on non-Windows)
// msvc msvc, clang-cl, icc (Windows)
//
enum class compiler_class
@@ -117,8 +118,9 @@ namespace build2
//
// A compiler variant may also have a variant version:
//
- // clang-apple A.B[.C] ... {A, B, C, ...}
- // msvc-clang A.B.C[( |-)...] {A, B, C, ...} (native Clang version)
+ // clang-apple A.B[.C] ... {A, B, C, ...}
+ // clang-emscripten A.B.C ... {A, B, C, ...}
+ // msvc-clang A.B.C[( |-)...] {A, B, C, ...} (native Clang version)
//
// Note that the clang-apple variant version is a custom Apple version
// that doesn't correspond to the vanilla Clang version nor is the mapping
@@ -211,6 +213,7 @@ namespace build2
// uclibc
// musl
// dietlibc
+ // emscripten
// other
// none
//
diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx
index b0457e7..6acb1c7 100644
--- a/libbuild2/cc/link-rule.cxx
+++ b/libbuild2/cc/link-rule.cxx
@@ -953,6 +953,8 @@ namespace build2
{
if (tclass == "windows")
e = "exe";
+ else if (tsys == "emscripten")
+ e = "js";
else
e = "";
@@ -998,6 +1000,18 @@ namespace build2
}
}
+ // Add Emscripten .wasm.
+ //
+ if (ot == otype::e && tsys == "emscripten")
+ {
+ const target_type& tt (*bs.find_target_type ("wasm"));
+
+ file& wasm (add_adhoc_member<file> (t, tt));
+
+ if (wasm.path ().empty ())
+ wasm.derive_path ("wasm");
+ }
+
// Add VC's .pdb. Note that we are looking for the link.exe /DEBUG
// option.
//
@@ -2438,7 +2452,22 @@ namespace build2
}
if (ldc)
+ {
+ // See the compile rule for details. Note that here we don't really
+ // know whether it's a C++ executable so we may end up with some
+ // unnecessary overhead.
+ //
+ if (ctype == compiler_type::clang && cvariant == "emscripten")
+ {
+ if (!find_option_prefix ("DISABLE_EXCEPTION_CATCHING=", args))
+ {
+ args.push_back ("-s");
+ args.push_back ("DISABLE_EXCEPTION_CATCHING=0");
+ }
+ }
+
append_options (args, cmode);
+ }
// Extra system library dirs (last).
//
@@ -3244,6 +3273,21 @@ namespace build2
try_rmfile (relt + ".lib", true /* ignore_errors */);
try_rmfile (relt + ".exp", true /* ignore_errors */);
}
+
+ // Set executable bit on the .js file so that it can be run with a
+ // suitable binfmt interpreter (e.g., nodejs). See Emscripten issue
+ // 12707 for details.
+ //
+#ifndef _WIN32
+ if (lt.executable () && tsys == "emscripten")
+ {
+ path_perms (relt,
+ (path_perms (relt) |
+ permissions::xu |
+ permissions::xg |
+ permissions::xo));
+ }
+#endif
}
if (ranlib)
diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx
index 1aa1e01..4b4f5e2 100644
--- a/libbuild2/cc/module.cxx
+++ b/libbuild2/cc/module.cxx
@@ -792,6 +792,11 @@ namespace build2
{
using namespace bin;
+ // If the target doesn't support shared libraries, then don't register
+ // the corresponding rules.
+ //
+ bool s (tsys != "emscripten");
+
auto& r (rs.rules);
// We register for configure so that we detect unresolved imports
@@ -808,9 +813,12 @@ namespace build2
r.insert<obja> (perform_clean_id, x_compile, cr);
r.insert<obja> (configure_update_id, x_compile, cr);
- r.insert<objs> (perform_update_id, x_compile, cr);
- r.insert<objs> (perform_clean_id, x_compile, cr);
- r.insert<objs> (configure_update_id, x_compile, cr);
+ if (s)
+ {
+ r.insert<objs> (perform_update_id, x_compile, cr);
+ r.insert<objs> (perform_clean_id, x_compile, cr);
+ r.insert<objs> (configure_update_id, x_compile, cr);
+ }
if (modules)
{
@@ -830,13 +838,16 @@ namespace build2
r.insert<hbmia> (perform_clean_id, x_compile, cr);
r.insert<hbmia> (configure_update_id, x_compile, cr);
- r.insert<bmis> (perform_update_id, x_compile, cr);
- r.insert<bmis> (perform_clean_id, x_compile, cr);
- r.insert<bmis> (configure_update_id, x_compile, cr);
+ if (s)
+ {
+ r.insert<bmis> (perform_update_id, x_compile, cr);
+ r.insert<bmis> (perform_clean_id, x_compile, cr);
+ r.insert<bmis> (configure_update_id, x_compile, cr);
- r.insert<hbmis> (perform_update_id, x_compile, cr);
- r.insert<hbmis> (perform_clean_id, x_compile, cr);
- r.insert<hbmis> (configure_update_id, x_compile, cr);
+ r.insert<hbmis> (perform_update_id, x_compile, cr);
+ r.insert<hbmis> (perform_clean_id, x_compile, cr);
+ r.insert<hbmis> (configure_update_id, x_compile, cr);
+ }
}
r.insert<libue> (perform_update_id, x_link, lr);
@@ -847,9 +858,12 @@ namespace build2
r.insert<libua> (perform_clean_id, x_link, lr);
r.insert<libua> (configure_update_id, x_link, lr);
- r.insert<libus> (perform_update_id, x_link, lr);
- r.insert<libus> (perform_clean_id, x_link, lr);
- r.insert<libus> (configure_update_id, x_link, lr);
+ if (s)
+ {
+ r.insert<libus> (perform_update_id, x_link, lr);
+ r.insert<libus> (perform_clean_id, x_link, lr);
+ r.insert<libus> (configure_update_id, x_link, lr);
+ }
r.insert<exe> (perform_update_id, x_link, lr);
r.insert<exe> (perform_clean_id, x_link, lr);
@@ -859,9 +873,12 @@ namespace build2
r.insert<liba> (perform_clean_id, x_link, lr);
r.insert<liba> (configure_update_id, x_link, lr);
- r.insert<libs> (perform_update_id, x_link, lr);
- r.insert<libs> (perform_clean_id, x_link, lr);
- r.insert<libs> (configure_update_id, x_link, lr);
+ if (s)
+ {
+ r.insert<libs> (perform_update_id, x_link, lr);
+ r.insert<libs> (perform_clean_id, x_link, lr);
+ r.insert<libs> (configure_update_id, x_link, lr);
+ }
// Note that while libu*{} are not installable, we need to see through
// them in case they depend on stuff that we need to install (see the
@@ -877,8 +894,11 @@ namespace build2
r.insert<liba> (perform_install_id, x_install, ir);
r.insert<liba> (perform_uninstall_id, x_uninstall, ir);
- r.insert<libs> (perform_install_id, x_install, ir);
- r.insert<libs> (perform_uninstall_id, x_uninstall, ir);
+ if (s)
+ {
+ r.insert<libs> (perform_install_id, x_install, ir);
+ r.insert<libs> (perform_uninstall_id, x_uninstall, ir);
+ }
const libux_install_rule& lr (*this);
@@ -888,8 +908,11 @@ namespace build2
r.insert<libua> (perform_install_id, x_install, lr);
r.insert<libua> (perform_uninstall_id, x_uninstall, lr);
- r.insert<libus> (perform_install_id, x_install, lr);
- r.insert<libus> (perform_uninstall_id, x_uninstall, lr);
+ if (s)
+ {
+ r.insert<libus> (perform_install_id, x_install, lr);
+ r.insert<libus> (perform_uninstall_id, x_uninstall, lr);
+ }
}
}
}