diff options
Diffstat (limited to 'libbuild2/cc/guess.cxx')
-rw-r--r-- | libbuild2/cc/guess.cxx | 1139 |
1 files changed, 873 insertions, 266 deletions
diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx index c0d04ef..d7e9c63 100644 --- a/libbuild2/cc/guess.cxx +++ b/libbuild2/cc/guess.cxx @@ -65,7 +65,6 @@ # include <libbuild2/filesystem.hxx> #endif -#include <map> #include <cstring> // strlen(), strchr(), strstr() #include <libbuild2/diagnostics.hxx> @@ -107,7 +106,7 @@ namespace build2 else if (id.compare (0, p, "icc" ) == 0) type = compiler_type::icc; else throw invalid_argument ( - "invalid compiler type '" + string (id, 0, p) + "'"); + "invalid compiler type '" + string (id, 0, p) + '\''); if (p != string::npos) { @@ -182,12 +181,12 @@ namespace build2 // could also be because there is something wrong with the compiler or // options but that we simply leave to blow up later). // - process pr (run_start (3 /* verbosity */, + process pr (run_start (3 /* verbosity */, xp, args, - -1 /* stdin */, - -1 /* stdout */, - false /* error */)); + -1 /* stdin */, + -1 /* stdout */, + 1 /* stderr (to stdout) */)); string l, r; try { @@ -223,7 +222,7 @@ namespace build2 // that. } - if (!run_finish_code (args.data (), pr, l)) + if (!run_finish_code (args.data (), pr, l, 2 /* verbosity */)) r = "none"; if (r.empty ()) @@ -263,8 +262,12 @@ namespace build2 " stdlib:=\"freebsd\" \n" "# elif defined(__NetBSD__) \n" " stdlib:=\"netbsd\" \n" +"# elif defined(__OpenBSD__) \n" +" stdlib:=\"openbsd\" \n" "# elif defined(__APPLE__) \n" " stdlib:=\"apple\" \n" +"# elif defined(__EMSCRIPTEN__) \n" +" stdlib:=\"emscripten\" \n" "# else \n" " stdlib:=\"other\" \n" "# endif \n" @@ -349,15 +352,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 +373,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; @@ -405,11 +412,13 @@ namespace build2 // // Note that Visual Studio versions prior to 15.0 are not supported. // + // Note also the directories are absolute and normalized. + // struct msvc_info { - dir_path msvc_dir; // VC directory (...\Tools\MSVC\<ver>\). - dir_path psdk_dir; // Platfor SDK version (under Include/, Lib/, etc). - string psdk_ver; // Platfor SDK directory (...\Windows Kits\<ver>\). + dir_path msvc_dir; // VC tools directory (...\Tools\MSVC\<ver>\). + dir_path psdk_dir; // Platform SDK directory (...\Windows Kits\<ver>\). + string psdk_ver; // Platform SDK version (under Include/, Lib/, etc). }; #if defined(_WIN32) && !defined(BUILD2_BOOTSTRAP) @@ -451,13 +460,16 @@ namespace build2 {0x87, 0xBF, 0xD5, 0x77, 0x83, 0x8F, 0x1D, 0x5C}}; // If cl is not empty, then find an installation that contains this cl.exe - // path. + // path. In this case the path must be absolute and normalized. // static optional<msvc_info> - find_msvc (const path& cl = path ()) + find_msvc (const path& cl = path ()) { using namespace butl; + assert (cl.empty () || + (cl.absolute () && cl.normalized (false /* sep */))); + msvc_info r; // Try to obtain the MSVC directory. @@ -523,7 +535,7 @@ namespace build2 // Note: we cannot use bstr_t due to the Clang 9.0 bug #42842. // BSTR p; - if (vs->ResolvePath (L"VC", &p) != S_OK) + if (vs->ResolvePath (L"VC", &p) != S_OK) return dir_path (); unique_ptr<wchar_t, bstr_deleter> deleter (p); @@ -629,36 +641,73 @@ namespace build2 return nullopt; } - // Read the VC version from the file and bail out on error. + // If cl.exe path is not specified, then deduce the default VC tools + // directory for this Visual Studio instance. Otherwise, extract the + // tools directory from this path. // - string vc_ver; // For example, 14.23.28105. + // Note that in the latter case we could potentially avoid the above + // iterating over the VS instances, but let's make sure that the + // specified cl.exe path actually belongs to one of them as a sanity + // check. + // + if (cl.empty ()) + { + // Read the VC version from the file and bail out on error. + // + string vc_ver; // For example, 14.23.28105. - path vp ( - r.msvc_dir / - path ("Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt")); + path vp ( + r.msvc_dir / + path ("Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt")); - try - { - ifdstream is (vp); - vc_ver = trim (is.read_text ()); - } - catch (const io_error&) {} + try + { + ifdstream is (vp); + vc_ver = trim (is.read_text ()); + } + catch (const io_error&) {} - // Make sure that the VC version directory exists. - // - if (!vc_ver.empty ()) - try - { - ((r.msvc_dir /= "Tools") /= "MSVC") /= vc_ver; + if (vc_ver.empty ()) + return nullopt; - if (!dir_exists (r.msvc_dir)) - r.msvc_dir.clear (); + // Make sure that the VC version directory exists. + // + try + { + ((r.msvc_dir /= "Tools") /= "MSVC") /= vc_ver; + + if (!dir_exists (r.msvc_dir)) + return nullopt; + } + catch (const invalid_path&) {return nullopt;} + catch (const system_error&) {return nullopt;} } - catch (const invalid_path&) {} - catch (const system_error&) {} + else + { + (r.msvc_dir /= "Tools") /= "MSVC"; - if (r.msvc_dir.empty ()) - return nullopt; + // Extract the VC tools version from the cl.exe path and append it + // to r.msvc_dir. + // + if (!cl.sub (r.msvc_dir)) + return nullopt; + + // For example, 14.23.28105\bin\Hostx64\x64\cl.exe. + // + path p (cl.leaf (r.msvc_dir)); // Can't throw. + + auto i (p.begin ()); // Tools version. + if (i == p.end ()) + return nullopt; + + r.msvc_dir /= *i; // Can't throw. + + // For good measure, make sure that the tools version is not the + // last component in the cl.exe path. + // + if (++i == p.end ()) + return nullopt; + } } // Try to obtain the latest Platform SDK directory and version. @@ -712,7 +761,7 @@ namespace build2 // for (const dir_entry& de: dir_iterator (r.psdk_dir / dir_path ("Include"), - false /* ignore_dangling */)) + dir_iterator::no_follow)) { if (de.type () == entry_type::directory) { @@ -730,6 +779,16 @@ namespace build2 return nullopt; } + try + { + r.msvc_dir.normalize (); + r.psdk_dir.normalize (); + } + catch (const invalid_path&) + { + return nullopt; + } + return r; } #endif @@ -738,10 +797,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 +819,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 ();} @@ -765,7 +829,8 @@ namespace build2 // Note: allowed to change pre if succeeds. // static guess_result - guess (const char* xm, + guess (context& ctx, + const char* xm, lang xl, const path& xc, const strings& x_mo, @@ -819,7 +884,9 @@ namespace build2 // // The main drawback of the latter, of course, is that the commands we // print are no longer re-runnable (even though we may have supplied - // the rest of the "environment" explicitly on the command line). + // the rest of the "environment" explicitly on the command line). Plus + // we would need to save whatever environment variables we used to + // form the fallback path in case of hermetic configuration. // // An alternative strategy is to try and obtain the corresponding // "environment" in case of the effective (absolute) path similar to @@ -914,10 +981,12 @@ namespace build2 // We try to find the matching installation only for MSVC (for Clang // we extract this information from the compiler). // - if (xc.absolute () && - (pt == type::msvc && !pv)) + if (xc.absolute () && (pt == type::msvc && !pv)) { - if (optional<msvc_info> mi = find_msvc (xc)) + path cl (xc); // Absolute but may not be normalized. + cl.normalize (); // Can't throw since this is an existing path. + + if (optional<msvc_info> mi = find_msvc (cl)) { search_info = info_ptr ( new msvc_info (move (*mi)), msvc_info_deleter); @@ -952,23 +1021,27 @@ 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 = [&ctx, &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> ( + ctx, 3 /* verbosity */, env, - args.data (), - forward<decltype(f)> (f), + args, + forward<decltype (f)> (f), false /* error */, false /* ignore_exit */, 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,19 +1061,26 @@ 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: + size_t p; + + // The gcc -v output will have a last line in the form: // - // "gcc version X.Y.Z ..." + // "gcc version X[.Y[.Z]][...] ..." // // The "version" word can probably be translated. For example: // @@ -1011,12 +1091,17 @@ namespace build2 // gcc version 4.9.2 (Ubuntu 4.9.2-0ubuntu1~14.04) // gcc version 5.1.0 (Ubuntu 5.1.0-0ubuntu11~14.04.1) // gcc version 6.0.0 20160131 (experimental) (GCC) + // gcc version 9.3-win32 20200320 (GCC) + // gcc version 10-win32 20220324 (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 ..." // @@ -1032,6 +1117,7 @@ namespace build2 // Apple LLVM version 7.0.0 (clang-700.1.76) // Apple LLVM version 7.0.2 (clang-700.1.81) // Apple LLVM version 7.3.0 (clang-703.0.16.1) + // Apple clang version 12.0.0 (clang-1200.0.32.27) // // Note that the gcc/g++ "aliases" for clang/clang++ also include // this line but it is (currently) preceded by "Configured with: @@ -1040,13 +1126,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)); + } + + // 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/clang++ -v output will have a first line in the - // form: + // The vanilla clang -v output will have a first line in the form: // // "[... ]clang version X.Y.Z[-...] ..." // @@ -1060,12 +1175,38 @@ namespace build2 // The clang-cl output is exactly the same, which means the only way // to distinguish it is based on the executable name. // - if (l.find ("clang ") != string::npos) + // We must also watch out for potential misdetections, for example: + // + // Configured with: ../gcc/configure CC=clang CXX=clang++ ... + // + if ((p = l.find ("clang ")) != string::npos && + (p == 0 || l[p - 1] == ' ')) { - 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 +1217,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 ()) @@ -1195,7 +1332,11 @@ namespace build2 // const char* evars[] = {"CL=", "_CL_=", nullptr}; - r = build2::run<guess_result> (3, process_env (xp, evars), f, false); + r = build2::run<guess_result> (ctx, + 3, + process_env (xp, evars), + f, + false); if (r.empty ()) { @@ -1346,10 +1487,12 @@ namespace build2 // And VC 16 seems to have the runtime version 14.1 (and not 14.2, as // one might expect; DLLs are still *140.dll but there are now _1 and _2 // variants for, say, msvcp140.dll). We will, however, call it 14.2 - // (which is the version of the "toolset") in our target triplet. + // (which is the version of the "toolset") in our target triplet. And we + // will call VC 17 14.3 (which is also the version of the "toolset"). // // year ver cl crt/dll toolset // + // 2022 17.X 19.3X 14.?/140 14.3X // 2019 16.X 19.2X 14.2/140 14.2X // 2017 15.9 19.16 14.1/140 14.16 // 2017 15.8 19.15 14.1/140 @@ -1368,7 +1511,8 @@ namespace build2 // // _MSC_VER is the numeric cl version, e.g., 1921 for 19.21. // - /**/ if (v.major == 19 && v.minor >= 20) return "14.2"; + /**/ if (v.major == 19 && v.minor >= 30) return "14.3"; + else if (v.major == 19 && v.minor >= 20) return "14.2"; else if (v.major == 19 && v.minor >= 10) return "14.1"; else if (v.major == 19 && v.minor == 0) return "14.0"; else if (v.major == 18 && v.minor == 0) return "12.0"; @@ -1392,19 +1536,21 @@ namespace build2 // Studio command prompt puts into INCLUDE) including any paths from the // compiler mode and their count. // - // Note that currently we don't add any ATL/MFC or WinRT paths (but could - // do that probably first checking if they exist/empty). + // Note that currently we don't add any ATL/MFC paths (but could do that + // probably first checking if they exist/empty). // static pair<dir_paths, size_t> - msvc_inc (const msvc_info& mi, const strings& mo) + msvc_hdr (const msvc_info& mi, const strings& mo) { dir_paths r; - // Extract /I paths from the compiler mode. + // Extract /I paths and similar from the compiler mode. // msvc_extract_header_search_dirs (mo, r); size_t rn (r.size ()); + // Note: the resulting directories are normalized by construction. + // r.push_back (dir_path (mi.msvc_dir) /= "include"); // This path structure only appeared in Platform SDK 10 (if anyone wants @@ -1418,6 +1564,7 @@ namespace build2 r.push_back (dir_path (d) /= "ucrt" ); r.push_back (dir_path (d) /= "shared"); r.push_back (dir_path (d) /= "um" ); + r.push_back (dir_path (d) /= "winrt" ); } return make_pair (move (r), rn); @@ -1453,6 +1600,8 @@ namespace build2 msvc_extract_library_search_dirs (mo, r); size_t rn (r.size ()); + // Note: the resulting directories are normalized by construction. + // r.push_back ((dir_path (mi.msvc_dir) /= "lib") /= cpu); // This path structure only appeared in Platform SDK 10 (if anyone wants @@ -1496,8 +1645,19 @@ namespace build2 const char* msvc_cpu (const string&); // msvc.cxx + // Note that LIB, LINK, and _LINK_ are technically link.exe's variables + // but we include them in case linking is done via the compiler without + // loading bin.ld. BTW, the same applies to rc.exe INCLUDE. + // + // See also the note on environment and caching below if adding any new + // variables. + // + static const char* msvc_env[] = {"INCLUDE", "IFCPATH", "CL", "_CL_", + "LIB", "LINK", "_LINK_", nullptr}; + static compiler_info - guess_msvc (const char* xm, + guess_msvc (context&, + const char* xm, lang xl, const path& xc, const string* xv, @@ -1520,6 +1680,7 @@ namespace build2 // "x86" // "x64" // "ARM" + // "ARM64" // compiler_version ver; { @@ -1583,9 +1744,10 @@ namespace build2 for (size_t b (0), e (0), n; (n = next_word (s, b, e, ' ', ',')) != 0; ) { - if (s.compare (b, n, "x64", 3) == 0 || - s.compare (b, n, "x86", 3) == 0 || - s.compare (b, n, "ARM", 3) == 0 || + if (s.compare (b, n, "x64", 3) == 0 || + s.compare (b, n, "x86", 3) == 0 || + s.compare (b, n, "ARM64", 5) == 0 || + s.compare (b, n, "ARM", 3) == 0 || s.compare (b, n, "80x86", 5) == 0) { cpu.assign (s, b, n); @@ -1596,15 +1758,15 @@ namespace build2 if (cpu.empty ()) fail << "unable to extract MSVC target CPU from " << "'" << s << "'"; - // Now we need to map x86, x64, and ARM to the target triplets. The - // problem is, there aren't any established ones so we got to invent - // them ourselves. Based on the discussion in - // <libbutl/target-triplet.mxx>, we need something in the + // Now we need to map x86, x64, ARM, and ARM64 to the target + // triplets. The problem is, there aren't any established ones so we + // got to invent them ourselves. Based on the discussion in + // <libbutl/target-triplet.hxx>, we need something in the // CPU-VENDOR-OS-ABI form. // // The CPU part is fairly straightforward with x86 mapped to 'i386' - // (or maybe 'i686'), x64 to 'x86_64', and ARM to 'arm' (it could also - // include the version, e.g., 'amrv8'). + // (or maybe 'i686'), x64 to 'x86_64', ARM to 'arm' (it could also + // include the version, e.g., 'amrv8'), and ARM64 to 'aarch64'. // // The (toolchain) VENDOR is also straightforward: 'microsoft'. Why // not omit it? Two reasons: firstly, there are other compilers with @@ -1614,7 +1776,7 @@ namespace build2 // // OS-ABI is where things are not as clear cut. The OS part shouldn't // probably be just 'windows' since we have Win32 and WinCE. And - // WinRT. And Universal Windows Platform (UWP). So perhaps the + // WinRT. And Universal Windows Platform (UWP). So perhaps the // following values for OS: 'win32', 'wince', 'winrt', 'winup'. // // For 'win32' the ABI part could signal the Microsoft C/C++ runtime @@ -1639,9 +1801,10 @@ namespace build2 // Putting it all together, Visual Studio 2015 will then have the // following target triplets: // - // x86 i386-microsoft-win32-msvc14.0 - // x64 x86_64-microsoft-win32-msvc14.0 - // ARM arm-microsoft-winup-??? + // x86 i386-microsoft-win32-msvc14.0 + // x64 x86_64-microsoft-win32-msvc14.0 + // ARM arm-microsoft-winup-??? + // ARM64 aarch64-microsoft-win32-msvc14.0 // if (cpu == "ARM") fail << "cl.exe ARM/WinRT/UWP target is not yet supported"; @@ -1651,6 +1814,8 @@ namespace build2 t = "x86_64-microsoft-win32-msvc"; else if (cpu == "x86" || cpu == "80x86") t = "i386-microsoft-win32-msvc"; + else if (cpu == "ARM64") + t = "aarch64-microsoft-win32-msvc"; else assert (false); @@ -1662,21 +1827,23 @@ namespace build2 else ot = t = *xt; + target_triplet tt (t); // Shouldn't fail. + // If we have the MSVC installation information, then this means we are // running out of the Visual Studio command prompt and will have to // supply PATH/INCLUDE/LIB/IFCPATH equivalents ourselves. // optional<pair<dir_paths, size_t>> lib_dirs; - optional<pair<dir_paths, size_t>> inc_dirs; + optional<pair<dir_paths, size_t>> hdr_dirs; optional<pair<dir_paths, size_t>> mod_dirs; string bpat; if (const msvc_info* mi = static_cast<msvc_info*> (gr.info.get ())) { - const char* cpu (msvc_cpu (target_triplet (t).cpu)); + const char* cpu (msvc_cpu (tt.cpu)); lib_dirs = msvc_lib (*mi, x_mo, cpu); - inc_dirs = msvc_inc (*mi, x_mo); + hdr_dirs = msvc_hdr (*mi, x_mo); mod_dirs = msvc_mod (*mi, x_mo, cpu); bpat = msvc_bin (*mi, cpu); @@ -1720,12 +1887,49 @@ namespace build2 move (csl), move (xsl), move (lib_dirs), - move (inc_dirs), - move (mod_dirs)}; + move (hdr_dirs), + move (mod_dirs), + msvc_env, + nullptr}; } + // See "Environment Variables Affecting GCC". + // + // Note that we also check below that the following variables are not set + // since they would interfere with what we are doing. + // + // DEPENDENCIES_OUTPUT + // SUNPRO_DEPENDENCIES + // + // Note also that we include (some) linker's variables in case linking is + // done via the compiler without loading bin.ld (to do this precisely we + // would need to detect which linker is being used at which point we might + // as well load bin.ld). + // + // See also the note on environment and caching below if adding any new + // variables. + // + static const char* gcc_c_env[] = { + "CPATH", "C_INCLUDE_PATH", + "LIBRARY_PATH", "LD_RUN_PATH", + "SOURCE_DATE_EPOCH", "GCC_EXEC_PREFIX", "COMPILER_PATH", + nullptr}; + + static const char* gcc_cxx_env[] = { + "CPATH", "CPLUS_INCLUDE_PATH", + "LIBRARY_PATH", "LD_RUN_PATH", + "SOURCE_DATE_EPOCH", "GCC_EXEC_PREFIX", "COMPILER_PATH", + nullptr}; + + // Note that Clang recognizes a whole family of *_DEPLOYMENT_TARGET + // variables (as does ld64). + // + static const char* macos_env[] = { + "SDKROOT", "MACOSX_DEPLOYMENT_TARGET", nullptr}; + static compiler_info - guess_gcc (const char* xm, + guess_gcc (context& ctx, + const char* xm, lang xl, const path& xc, const string* xv, @@ -1744,7 +1948,7 @@ namespace build2 // though language words can be translated and even rearranged (see // examples above). // - // "gcc version A.B.C[ ...]" + // "gcc version X[.Y[.Z]][...]" // compiler_version ver; { @@ -1770,38 +1974,34 @@ namespace build2 // end of the word position (first space). In fact, we can just // check if it is >= e. // - if (s.find_first_not_of ("1234567890.", b, 11) >= e) + size_t p (s.find_first_not_of ("1234567890.", b, 11)); + if (p >= e || (p > b && (s[p] == '-' || s[p] == '+'))) break; } if (b == e) fail << "unable to extract GCC version from '" << s << "'"; - ver.string.assign (s, b, string::npos); - - // Split the version into components. + // Split the version into components by parsing it as semantic-like + // version. // - size_t vb (b), ve (b); - auto next = [&s, b, e, &vb, &ve] (const char* m) -> uint64_t + try { - try - { - if (next_word (s, e, vb, ve, '.')) - return stoull (string (s, vb, ve - vb)); - } - catch (const invalid_argument&) {} - catch (const out_of_range&) {} - - fail << "unable to extract GCC " << m << " version from '" - << string (s, b, e - b) << "'" << endf; - }; - - ver.major = next ("major"); - ver.minor = next ("minor"); - ver.patch = next ("patch"); + semantic_version v (string (s, b, e - b), + semantic_version::allow_omit_minor | + semantic_version::allow_build, + ".-+"); + ver.major = v.major; + ver.minor = v.minor; + ver.patch = v.patch; + ver.build = move (v.build); + } + catch (const invalid_argument& e) + { + fail << "unable to extract GCC version from '" << s << "': " << e; + } - if (e != s.size ()) - ver.build.assign (s, e + 1, string::npos); + ver.string.assign (s, b, string::npos); } // Figure out the target architecture. This is actually a lot trickier @@ -1842,7 +2042,7 @@ namespace build2 // auto f = [] (string& l, bool) {return move (l);}; - t = run<string> (3, xp, args.data (), f, false); + t = run<string> (ctx, 3, xp, args, f, false); if (t.empty ()) { @@ -1850,7 +2050,7 @@ namespace build2 << "falling back to -dumpmachine";}); args[args.size () - 2] = "-dumpmachine"; - t = run<string> (3, xp, args.data (), f, false); + t = run<string> (ctx, 3, xp, args, f, false); } if (t.empty ()) @@ -1904,6 +2104,23 @@ namespace build2 } } + // Environment. + // + if (getenv ("DEPENDENCIES_OUTPUT")) + fail << "GCC DEPENDENCIES_OUTPUT environment variable is set"; + + if (getenv ("SUNPRO_DEPENDENCIES")) + fail << "GCC SUNPRO_DEPENDENCIES environment variable is set"; + + const char* const* c_env (nullptr); + switch (xl) + { + case lang::c: c_env = gcc_c_env; break; + case lang::cxx: c_env = gcc_cxx_env; break; + } + + const char* const* p_env (tt.system == "darwin" ? macos_env : nullptr); + return compiler_info { move (gr.path), move (gr.id), @@ -1921,7 +2138,9 @@ namespace build2 move (xsl), nullopt, nullopt, - nullopt}; + nullopt, + c_env, + p_env}; } struct clang_msvc_info: msvc_info @@ -1974,9 +2193,9 @@ namespace build2 process pr (run_start (3 /* verbosity */, xp, args, - -2 /* stdin (/dev/null) */, - -1 /* stdout */, - false /* error (2>&1) */)); + -2 /* stdin (to /dev/null) */, + -1 /* stdout */, + 1 /* stderr (to stdout) */)); clang_msvc_info r; @@ -2128,7 +2347,7 @@ namespace build2 // that. } - if (!run_finish_code (args.data (), pr, l)) + if (!run_finish_code (args.data (), pr, l, 2 /* verbosity */)) fail << "unable to extract MSVC information from " << xp; if (const char* w = ( @@ -2143,8 +2362,30 @@ namespace build2 return r; } + // These are derived from gcc_* plus the sparse documentation (clang(1)) + // and source code. + // + // Note that for now for Clang targeting MSVC we use msvc_env but should + // probably use a combined list. + // + // See also the note on environment and caching below if adding any new + // variables. + // + static const char* clang_c_env[] = { + "CPATH", "C_INCLUDE_PATH", "CCC_OVERRIDE_OPTIONS", + "LIBRARY_PATH", "LD_RUN_PATH", + "COMPILER_PATH", + nullptr}; + + static const char* clang_cxx_env[] = { + "CPATH", "CPLUS_INCLUDE_PATH", "CCC_OVERRIDE_OPTIONS", + "LIBRARY_PATH", "LD_RUN_PATH", + "COMPILER_PATH", + nullptr}; + static compiler_info - guess_clang (const char* xm, + guess_clang (context& ctx, + const char* xm, lang xl, const path& xc, const string* xv, @@ -2155,8 +2396,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 vanilla 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 +2407,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 +2419,22 @@ 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 + // + // Pre-releases of the vanilla Clang append `rc` or `git` to the + // version, unfortunately without a separator. So we will handle these + // ad hoc. For example: + // + // FreeBSD clang version 18.1.0rc (https://github.com/llvm/llvm-project.git llvmorg-18-init-18361-g22683463740e) + // + 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, ' ', '-')) { @@ -2211,19 +2444,42 @@ namespace build2 // end of the word position (first space). In fact, we can just // check if it is >= e. // - if (s.find_first_not_of ("1234567890.", b, 11) >= e) + size_t p (s.find_first_not_of ("1234567890.", b, 11)); + if (p >= e) break; + + // Handle the unseparated `rc` and `git` suffixes. + // + if (p != string::npos) + { + if (p + 2 == e && (e - b) > 2 && + s[p] == 'r' && s[p + 1] == 'c') + { + e -= 2; + break; + } + + if (p + 3 == e && (e - b) > 3 && + s[p] == 'g' && s[p + 1] == 'i' && s[p + 2] == 't') + { + e -= 3; + break; + } + } } 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,76 +2492,174 @@ 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); + { + // Skip the separator (it could also be unseparated `rc` or `git`). + // + if (s[e] == ' ' || s[e] == '-') + e++; + + ver.build.assign (s, e, string::npos); + } + + return ver; + }; - // Map Apple to vanilla Clang version, preserving the original as - // the variant version. + 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. + // + //string s (xv != nullptr ? *xv : ""); + // + //s = "clang version 3.7.0 (tags/RELEASE_370/final)"; + //s = "FreeBSD clang version 18.1.0rc (https://github.com/llvm/llvm-project.git llvmorg-18-init-18361-g22683463740e)"; + // + //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 there could be multiple __config files with + // potentially different versions so compile with -v to see which one + // gets picked up). + // + // Also, lately, we started seeing _LIBCPP_VERSION values like 15.0.6 + // or 16.0.2 which would suggest the base is 15.0.5 or 16.0.1. But + // that assumption did not check out with the actual usage. For + // example, vanilla Clang 16 should no longer require -fmodules-ts but + // the Apple's version (that is presumably based on it) still does. So + // the theory here is that Apple upgrades to newer libc++ while + // keeping the old compiler. Which means we must be more conservative + // and assume something like 15.0.6 is still 14-based. But then you + // get -Wunqualified-std-cast-call in 14, which was supposedly only + // introduced in Clang 15. So maybe not. + // + // 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 (yes, seriously!) + // 12.0.0 -> 9.0 + // 12.0.5 -> 10.0 (yes, seriously!) + // 13.0.0 -> 11.0 + // 13.1.6 -> 12.0 + // 14.0.0 -> 12.0 (_LIBCPP_VERSION=130000) + // 14.0.3 -> 15.0 (_LIBCPP_VERSION=150006) + // 15.0.0 -> 16.0 (_LIBCPP_VERSION=160002) + // + uint64_t mj (var_ver->major); + uint64_t mi (var_ver->minor); + uint64_t pa (var_ver->patch); + + if (mj >= 15) {mj = 16; mi = 0; pa = 0;} + else if (mj == 14 && (mi > 0 || pa >= 3)) {mj = 15; mi = 0; pa = 0;} + else if (mj == 14 || (mj == 13 && mi >= 1)) {mj = 12; mi = 0; pa = 0;} + else if (mj == 13) {mj = 11; mi = 0; pa = 0;} + else if (mj == 12 && (mi > 0 || pa >= 5)) {mj = 10; mi = 0; pa = 0;} + else if (mj == 12) {mj = 9; mi = 0; pa = 0;} + else if (mj == 11 && (mi > 0 || pa >= 3)) {mj = 8; mi = 0; pa = 0;} + else if (mj == 11) {mj = 7; mi = 0; pa = 0;} + else if (mj == 10) {mj = 6; mi = 0; pa = 0;} + else if (mj == 9 && mi >= 1) {mj = 5; mi = 0; pa = 0;} + else if (mj == 9) {mj = 4; mi = 0; pa = 0;} + else if (mj == 8) {mj = 3; mi = 9; pa = 0;} + else if (mj == 7 && mi >= 3) {mj = 3; mi = 8; pa = 0;} + else if (mj == 7) {mj = 3; mi = 7; pa = 0;} + else if (mj == 6 && mi >= 1) {mj = 3; mi = 5; pa = 0;} + else if (mj == 6) {mj = 3; mi = 4; pa = 0;} + else if (mj == 5 && mi >= 1) {mj = 3; mi = 3; pa = 0;} + else if (mj == 5) {mj = 3; mi = 2; pa = 0;} + else if (mj == 4 && mi >= 2) {mj = 3; mi = 1; pa = 0;} + else {mj = 3; mi = 0; pa = 0;} + + ver = compiler_version { + to_string (mj) + '.' + to_string (mi) + '.' + to_string (pa), + mj, + mi, + pa, + ""}; + } + 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"); + + // The official Emscripten distributions routinely use unreleased + // Clang snapshots which nevertheless have the next release version + // (which means it's actually somewhere between the previous release + // and the next release). On the other hand, distributions like Debian + // package it to use their Clang package which normally has the + // accurate version. So here we will try to detect the former and + // similar to the Apple case we will conservatively adjust it to the + // previous release. + // + if (gr.type_signature.find ("googlesource") != string::npos) + { + if (ver.patch != 0) ver.patch--; + else if (ver.minor != 0) ver.minor--; + else ver.major--; } } @@ -2330,7 +2684,7 @@ namespace build2 // for LC_ALL. // auto f = [] (string& l, bool) {return move (l);}; - t = run<string> (3, xp, args.data (), f, false); + t = run<string> (ctx, 3, xp, args, f, false); if (t.empty ()) fail << "unable to extract target architecture from " << xc @@ -2390,7 +2744,7 @@ namespace build2 const char* cpu (msvc_cpu (tt.cpu)); // Come up with the system library search paths. Ideally we would want - // to extract this from Clang and -print-search-paths would have been + // to extract this from Clang and -print-search-dirs would have been // the natural way for Clang to report it. But no luck. // lib_dirs = msvc_lib (mi, x_mo, cpu); @@ -2414,13 +2768,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 ()) @@ -2501,6 +2866,29 @@ namespace build2 } } + // Environment. + // + // Note that "Emscripten Compiler Frontend (emcc)" has a long list of + // environment variables with little explanation. So someone will need + // to figure out what's important (some of them are clearly for + // debugging of emcc itself). + // + const char* const* c_env (nullptr); + const char* const* p_env (nullptr); + if (tt.system == "win32-msvc") + c_env = msvc_env; + else + { + switch (xl) + { + case lang::c: c_env = clang_c_env; break; + case lang::cxx: c_env = clang_cxx_env; break; + } + + if (tt.system == "darwin") + p_env = macos_env; + } + return compiler_info { move (gr.path), move (gr.id), @@ -2518,11 +2906,14 @@ namespace build2 move (xsl), move (lib_dirs), nullopt, - nullopt}; + nullopt, + c_env, + p_env}; } static compiler_info - guess_icc (const char* xm, + guess_icc (context& ctx, + const char* xm, lang xl, const path& xc, const string* xv, @@ -2586,7 +2977,7 @@ namespace build2 // // @@ TODO: running without the mode options. // - s = run<string> (3, env, "-V", f, false); + s = run<string> (ctx, 3, env, "-V", f, false); if (s.empty ()) fail << "unable to extract signature from " << xc << " -V output"; @@ -2712,7 +3103,7 @@ namespace build2 // The -V output is sent to STDERR. // - t = run<string> (3, env, args.data (), f, false); + t = run<string> (ctx, 3, env, args, f, false); if (t.empty ()) fail << "unable to extract target architecture from " << xc @@ -2763,7 +3154,7 @@ namespace build2 // { auto f = [] (string& l, bool) {return move (l);}; - t = run<string> (3, xp, "-dumpmachine", f); + t = run<string> (ctx, 3, xp, "-dumpmachine", f); } if (t.empty ()) @@ -2833,18 +3224,21 @@ namespace build2 move (xsl), nullopt, nullopt, - nullopt}; + nullopt, + nullptr, /* TODO */ + nullptr}; } // Compiler checks can be expensive (we often need to run the compiler // several times) so we cache the result. // - static map<string, compiler_info> cache; - static mutex cache_mutex; + static global_cache<compiler_info> cache; const compiler_info& - guess (const char* xm, + guess (context& ctx, + const char* xm, lang xl, + const string& ec, const path& xc, const string* xis, const string* xv, @@ -2856,11 +3250,26 @@ namespace build2 { // First check the cache. // + // Note that in case of MSVC (and Clang targeting MSVC) sys_*_dirs can + // be affected by the environment (INCLUDE, LIB, and IFCPATH) which is + // project-specific. So we have to include those into the key. But we + // don't know yet know whether it's those compilers/targets. So it seems + // we have no better choice than to include the project environment if + // overridden. + // + // @@ We currently include config.{cc,x}.[pc]options into the key which + // means any project-specific tweaks to these result in a different + // key. Perhaps we should assume that any options that can affect the + // result of what we are guessing (-m32, -stdlib=, etc) should be + // specified as part of the mode? While definitely feels correct, + // people will most likely specify these options else where as well. + // string key; { sha256 cs; cs.append (static_cast<size_t> (xl)); cs.append (xc.string ()); + if (!ec.empty ()) cs.append (ec); if (xis != nullptr) cs.append (*xis); append_options (cs, x_mo); if (c_po != nullptr) append_options (cs, *c_po); @@ -2871,11 +3280,8 @@ namespace build2 if (x_lo != nullptr) append_options (cs, *x_lo); key = cs.string (); - mlock l (cache_mutex); - - auto i (cache.find (key)); - if (i != cache.end ()) - return i->second; + if (const compiler_info* r = cache.find (key)) + return *r; } // Parse the user-specified compiler id (config.x.id). @@ -2904,7 +3310,7 @@ namespace build2 if (pre.type != invalid_compiler_type) { - gr = guess (xm, xl, xc, x_mo, xi, pre, cs); + gr = guess (ctx, xm, xl, xc, x_mo, xi, pre, cs); if (gr.empty ()) { @@ -2920,13 +3326,14 @@ namespace build2 } if (gr.empty ()) - gr = guess (xm, xl, xc, x_mo, xi, pre, cs); + gr = guess (ctx, xm, xl, xc, x_mo, xi, pre, cs); if (gr.empty ()) fail << "unable to guess " << xl << " compiler type of " << xc << info << "use config." << xm << ".id to specify explicitly"; compiler_info (*gf) ( + context&, const char*, lang, const path&, const string*, const string*, const strings&, const strings*, const strings*, @@ -2946,15 +3353,21 @@ namespace build2 case compiler_type::icc: gf = &guess_icc; break; } - compiler_info r (gf (xm, xl, xc, xv, xt, + compiler_info r (gf (ctx, + xm, xl, xc, xv, xt, 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 @@ -3022,15 +3435,7 @@ namespace build2 r.bin_pattern = p.directory ().representation (); // Trailing slash. } - // It's possible the cache entry already exists, in which case we - // ignore our value. - // - // But what if the compiler information it contains is different? Well, - // we don't generally deal with toolchain changes during the build so we - // ignore this special case as well. - // - mlock l (cache_mutex); - return cache.insert (make_pair (move (key), move (r))).first->second; + return cache.insert (move (key), move (r)); } strings @@ -3051,7 +3456,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 +3479,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: { @@ -3087,5 +3506,193 @@ namespace build2 return r; } + + // Table 23 [tab:headers.cpp]. + // + // In the future we will probably have to maintain per-standard additions. + // + static const char* std_importable[] = { + "<initializer_list>", // Note: keep first (present in freestanding). + "<algorithm>", + "<any>", + "<array>", + "<atomic>", + "<barrier>", + "<bit>", + "<bitset>", + "<charconv>", + "<chrono>", + "<codecvt>", + "<compare>", + "<complex>", + "<concepts>", + "<condition_variable>", + "<coroutine>", + "<deque>", + "<exception>", + "<execution>", + "<filesystem>", + "<format>", + "<forward_list>", + "<fstream>", + "<functional>", + "<future>", + "<iomanip>", + "<ios>", + "<iosfwd>", + "<iostream>", + "<istream>", + "<iterator>", + "<latch>", + "<limits>", + "<list>", + "<locale>", + "<map>", + "<memory>", + "<memory_resource>", + "<mutex>", + "<new>", + "<numbers>", + "<numeric>", + "<optional>", + "<ostream>", + "<queue>", + "<random>", + "<ranges>", + "<ratio>", + "<regex>", + "<scoped_allocator>", + "<semaphore>", + "<set>", + "<shared_mutex>", + "<source_location>", + "<span>", + "<sstream>", + "<stack>", + "<stdexcept>", + "<stop_token>", + "<streambuf>", + "<string>", + "<string_view>", + "<strstream>", + "<syncstream>", + "<system_error>", + "<thread>", + "<tuple>", + "<typeindex>", + "<typeinfo>", + "<type_traits>", + "<unordered_map>", + "<unordered_set>", + "<utility>", + "<valarray>", + "<variant>", + "<vector>", + "<version>" + }; + + // Table 24 ([tab:headers.cpp.c]) + // + static const char* std_non_importable[] = { + "<cassert>", + "<cctype>", + "<cerrno>", + "<cfenv>", + "<cfloat>", + "<cinttypes>", + "<climits>", + "<clocale>", + "<cmath>", + "<csetjmp>", + "<csignal>", + "<cstdarg>", + "<cstddef>", + "<cstdint>", + "<cstdio>", + "<cstdlib>", + "<cstring>", + "<ctime>", + "<cuchar>", + "<cwchar>", + "<cwctype>" + }; + + void + guess_std_importable_headers (const compiler_info& ci, + const dir_paths& sys_hdr_dirs, + importable_headers& hs) + { + hs.group_map.emplace (header_group_std, 0); + hs.group_map.emplace (header_group_std_importable, 0); + + // For better performance we make compiler-specific assumptions. + // + // For example, we can assume that all these headers are found in the + // same header search directory. This is at least the case for GCC's + // libstdc++. + // + // Note also that some headers could be missing. For example, <format> + // is currently not provided by GCC. Though entering missing headers + // should be harmless. + // + // Plus, a freestanding implementation may only have a subset of such + // headers (see [compliance]). + // + pair<const path, importable_headers::groups>* p; + auto add_groups = [&p] (bool imp) + { + if (imp) + p->second.push_back (header_group_std_importable); // More specific. + + p->second.push_back (header_group_std); + }; + + if (ci.id.type != compiler_type::gcc) + { + for (const char* f: std_importable) + if ((p = hs.insert_angle (sys_hdr_dirs, f)) != nullptr) + add_groups (true); + + for (const char* f: std_non_importable) + if ((p = hs.insert_angle (sys_hdr_dirs, f)) != nullptr) + add_groups (false); + } + else + { + // While according to [compliance] a freestanding implementation + // should provide a subset of headers, including <initializer_list>, + // there seem to be cases where no headers are provided at all (see GH + // issue #219). So if we cannot find <initializer_list>, we just skip + // the whole thing. + // + p = hs.insert_angle (sys_hdr_dirs, std_importable[0]); + + if (p != nullptr) + { + assert (p != nullptr); + + add_groups (true); + + dir_path d (p->first.directory ()); + + auto add_header = [&hs, &d, &p, add_groups] (const char* f, bool imp) + { + path fp (d); + fp.combine (f + 1, strlen (f) - 2, '\0'); // Assuming simple. + + p = &hs.insert_angle (move (fp), f); + add_groups (imp); + }; + + for (size_t i (1); + i != sizeof (std_importable) / sizeof (std_importable[0]); + ++i) + add_header (std_importable[i], true); + + for (const char* f: std_non_importable) + add_header (f, false); + } + } + } } } |