aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/cc/guess.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/cc/guess.cxx')
-rw-r--r--libbuild2/cc/guess.cxx1139
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);
+ }
+ }
+ }
}
}