diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2020-11-05 15:52:13 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2020-11-05 15:52:13 +0200 |
commit | 8671b4dc516994b7f070a341c004aab28e525431 (patch) | |
tree | ae44903ad4960ed5ace4bcb26401be5dd592ff4f /libbuild2/cc | |
parent | fb4272e816ad949811f9f8b709771c5aeb646d1f (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.
Diffstat (limited to 'libbuild2/cc')
-rw-r--r-- | libbuild2/cc/compile-rule.cxx | 523 | ||||
-rw-r--r-- | libbuild2/cc/guess.cxx | 398 | ||||
-rw-r--r-- | libbuild2/cc/guess.hxx | 21 | ||||
-rw-r--r-- | libbuild2/cc/link-rule.cxx | 44 | ||||
-rw-r--r-- | libbuild2/cc/module.cxx | 61 |
5 files changed, 650 insertions, 397 deletions
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); + } } } } |