aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/bin/guess.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/bin/guess.cxx')
-rw-r--r--libbuild2/bin/guess.cxx341
1 files changed, 295 insertions, 46 deletions
diff --git a/libbuild2/bin/guess.cxx b/libbuild2/bin/guess.cxx
index 21936d9..e9759b8 100644
--- a/libbuild2/bin/guess.cxx
+++ b/libbuild2/bin/guess.cxx
@@ -34,9 +34,12 @@ namespace build2
// Return 0-version if the version is invalid.
//
static inline semantic_version
- parse_version (const string& s, size_t p = 0, const char* bs = ".-+~ ")
+ parse_version (const string& s, size_t p = 0,
+ semantic_version::flags f = semantic_version::allow_omit_patch |
+ semantic_version::allow_build,
+ const char* bs = ".-+~ ")
{
- optional<semantic_version> v (parse_semantic_version (s, p, bs));
+ optional<semantic_version> v (parse_semantic_version (s, p, f, bs));
return v ? *v : semantic_version ();
}
@@ -54,7 +57,7 @@ namespace build2
{
process_path r (
run_try_search (prog,
- true /* init */,
+ false /* init (cached) */,
dir_path () /* fallback */,
true /* path_only */,
paths));
@@ -80,14 +83,33 @@ namespace build2
dr << info << "use " << var << " to override";
});
- return run_search (prog, true, dir_path (), true);
+ return run_search (prog, false, dir_path (), true);
}
- ar_info
- guess_ar (const path& ar, const path* rl, const char* paths)
+ // Extracting ar/ranlib information requires running them which can become
+ // expensive if done repeatedly. So we cache the result.
+ //
+ static global_cache<ar_info> ar_cache;
+
+ const ar_info&
+ guess_ar (context& ctx, const path& ar, const path* rl, const char* paths)
{
tracer trace ("bin::guess_ar");
+ // First check the cache.
+ //
+ string key;
+ {
+ sha256 cs;
+ cs.append (ar.string ());
+ if (rl != nullptr) cs.append (rl->string ());
+ if (paths != nullptr) cs.append (paths);
+ key = cs.string ();
+
+ if (const ar_info* r = ar_cache.find (key))
+ return *r;
+ }
+
guess_result arr, rlr;
process_path arp (search (ar, paths, "config.bin.ar"));
@@ -158,7 +180,11 @@ namespace build2
// "LLVM version 3.5.2"
// "LLVM version 5.0.0"
//
- if (l.compare (0, 13, "LLVM version ") == 0)
+ // But it can also be prefixed with some stuff, for example:
+ //
+ // "Debian LLVM version 14.0.6"
+ //
+ if (l.find ("LLVM version ") != string::npos)
{
semantic_version v (parse_version (l, l.rfind (' ') + 1));
return guess_result ("llvm", move (l), move (v));
@@ -208,7 +234,11 @@ namespace build2
// (yes, it goes to stdout) but that seems harmless.
//
sha256 cs;
- arr = run<guess_result> (3, are, "--version", f, false, false, &cs);
+ arr = run<guess_result> (ctx,
+ 3,
+ are, "--version",
+ f,
+ false , false, &cs);
if (!arr.empty ())
arr.checksum = cs.string ();
@@ -228,10 +258,10 @@ namespace build2
: guess_result ();
};
- // Redirect STDERR to STDOUT and ignore exit status.
+ // Redirect stderr to stdout and ignore exit status.
//
sha256 cs;
- arr = run<guess_result> (3, are, f, false, true, &cs);
+ arr = run<guess_result> (ctx, 3, are, f, false, true, &cs);
if (!arr.empty ())
{
@@ -261,7 +291,7 @@ namespace build2
// "LLVM version ".
//
- if (l.compare (0, 13, "LLVM version ") == 0)
+ if (l.find ("LLVM version ") != string::npos)
return guess_result ("llvm", move (l), semantic_version ());
// On FreeBSD we get "ranlib" rather than "BSD ranlib" for some
@@ -274,7 +304,11 @@ namespace build2
};
sha256 cs;
- rlr = run<guess_result> (3, rle, "--version", f, false, false, &cs);
+ rlr = run<guess_result> (ctx,
+ 3,
+ rle, "--version",
+ f,
+ false, false, &cs);
if (!rlr.empty ())
rlr.checksum = cs.string ();
@@ -291,10 +325,10 @@ namespace build2
: guess_result ();
};
- // Redirect STDERR to STDOUT and ignore exit status.
+ // Redirect stderr to stdout and ignore exit status.
//
sha256 cs;
- rlr = run<guess_result> (3, rle, f, false, true, &cs);
+ rlr = run<guess_result> (ctx, 3, rle, f, false, true, &cs);
if (!rlr.empty ())
{
@@ -307,24 +341,78 @@ namespace build2
fail << "unable to guess " << *rl << " signature";
}
- return ar_info {
- move (arp),
- move (arr.id),
- move (arr.signature),
- move (arr.checksum),
- move (*arr.version),
-
- move (rlp),
- move (rlr.id),
- move (rlr.signature),
- move (rlr.checksum)};
+ // None of the ar/ranlib implementations we recognize seem to use
+ // environment variables (not even Microsoft lib.exe).
+ //
+ return ar_cache.insert (move (key),
+ ar_info {
+ move (arp),
+ move (arr.id),
+ move (arr.signature),
+ move (arr.checksum),
+ move (*arr.version),
+ nullptr,
+
+ move (rlp),
+ move (rlr.id),
+ move (rlr.signature),
+ move (rlr.checksum),
+ nullptr});
}
- ld_info
- guess_ld (const path& ld, const char* paths)
+ // Linker environment variables (see also the cc module which duplicates
+ // some of these).
+ //
+ // Notes:
+ //
+ // - GNU linkers search in LD_LIBRARY_PATH in addition to LD_RUN_PATH but
+ // we assume the former is part of the built-in list. Interestingly,
+ // LLD does not search in either.
+ //
+ // - The LLD family of linkers have a bunch of undocumented, debugging-
+ // related variables (LLD_REPRODUCE, LLD_VERSION, LLD_IN_TEST) that we
+ // ignore.
+ //
+ // - ld64 uses a ton of environment variables (according to the source
+ // code) but none of them are documented in the man pages. So someone
+ // will need to figure out what's important (some of them are clearly
+ // for debugging of ld itself).
+ //
+ // See also the note on environment and caching below if adding any new
+ // variables.
+ //
+ static const char* gnu_ld_env[] = {
+ "LD_RUN_PATH", "GNUTARGET", "LDEMULATION", "COLLECT_NO_DEMANGLE", nullptr};
+
+ static const char* msvc_ld_env[] = {
+ "LIB", "LINK", "_LINK_", nullptr};
+
+ // Extracting ld information requires running it which can become
+ // expensive if done repeatedly. So we cache the result.
+ //
+ static global_cache<ld_info> ld_cache;
+
+ const ld_info&
+ guess_ld (context& ctx, const path& ld, const char* paths)
{
tracer trace ("bin::guess_ld");
+ // First check the cache.
+ //
+ // Note that none of the information that we cache can be affected by
+ // the environment.
+ //
+ string key;
+ {
+ sha256 cs;
+ cs.append (ld.string ());
+ if (paths != nullptr) cs.append (paths);
+ key = cs.string ();
+
+ if (const ld_info* r = ld_cache.find (key))
+ return *r;
+ }
+
guess_result r;
process_path pp (search (ld, paths, "config.bin.ld"));
@@ -364,17 +452,22 @@ namespace build2
string id;
optional<semantic_version> ver;
+ size_t p;
+
// Microsoft link.exe output starts with "Microsoft (R) ".
//
if (l.compare (0, 14, "Microsoft (R) ") == 0)
{
id = "msvc";
}
- // LLD prints a line in the form "LLD X.Y.Z ...".
+ // LLD prints a line in the form "LLD X.Y.Z ...". But it can also
+ // be prefixed with some stuff, for example:
//
- else if (l.compare (0, 4, "LLD ") == 0)
+ // Debian LLD 14.0.6 (compatible with GNU linkers)
+ //
+ else if ((p = l.find ("LLD ")) != string::npos)
{
- ver = parse_version (l, 4);
+ ver = parse_version (l, p + 4);
// The only way to distinguish between various LLD drivers is via
// their name. Handle potential prefixes (say a target) and
@@ -412,12 +505,12 @@ namespace build2
: guess_result (move (id), move (l), move (ver)));
};
- // Redirect STDERR to STDOUT and ignore exit status. Note that in case
+ // Redirect stderr to stdout and ignore exit status. Note that in case
// of link.exe we will hash the diagnostics (yes, it goes to stdout)
// but that seems harmless.
//
sha256 cs;
- r = run<guess_result> (3, env, "--version", f, false, true, &cs);
+ r = run<guess_result> (ctx, 3, env, "--version", f, false, true, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -448,7 +541,7 @@ namespace build2
};
sha256 cs;
- r = run<guess_result> (3, env, "-v", f, false, false, &cs);
+ r = run<guess_result> (ctx, 3, env, "-v", f, false, false, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -475,7 +568,7 @@ namespace build2
// option.
//
sha256 cs;
- r = run<guess_result> (3, env, "-version", f, false, false, &cs);
+ r = run<guess_result> (ctx, 3, env, "-version", f, false, false, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -484,19 +577,55 @@ namespace build2
if (r.empty ())
fail << "unable to guess " << ld << " signature";
- return ld_info {
- move (pp),
- move (r.id),
- move (r.signature),
- move (r.checksum),
- move (r.version)};
+ const char* const* ld_env ((r.id == "gnu" ||
+ r.id == "gnu-gold") ? gnu_ld_env :
+ (r.id == "msvc" ||
+ r.id == "msvc-lld") ? msvc_ld_env :
+ nullptr);
+
+ return ld_cache.insert (move (key),
+ ld_info {
+ move (pp),
+ move (r.id),
+ move (r.signature),
+ move (r.checksum),
+ move (r.version),
+ ld_env});
}
- rc_info
- guess_rc (const path& rc, const char* paths)
+ // Resource compiler environment variables.
+ //
+ // See also the note on environment and caching below if adding any new
+ // variables.
+ //
+ static const char* msvc_rc_env[] = {"INCLUDE", nullptr};
+
+ // Extracting rc information requires running it which can become
+ // expensive if done repeatedly. So we cache the result.
+ //
+ static global_cache<rc_info> rc_cache;
+
+ const rc_info&
+ guess_rc (context& ctx, const path& rc, const char* paths)
{
tracer trace ("bin::guess_rc");
+ // First check the cache.
+ //
+ // Note that none of the information that we cache can be affected by
+ // the environment.
+ //
+ string key;
+ {
+ sha256 cs;
+ cs.append (rc.string ());
+ if (paths != nullptr) cs.append (paths);
+ key = cs.string ();
+
+ if (const rc_info* r = rc_cache.find (key))
+ return *r;
+ }
+
guess_result r;
process_path pp (search (rc, paths, "config.bin.rc"));
@@ -533,7 +662,7 @@ namespace build2
// option.
//
sha256 cs;
- r = run<guess_result> (3, env, "--version", f, false, false, &cs);
+ r = run<guess_result> (ctx, 3, env, "--version", f, false, false, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -566,7 +695,7 @@ namespace build2
};
sha256 cs;
- r = run<guess_result> (3, env, "/?", f, false, false, &cs);
+ r = run<guess_result> (ctx, 3, env, "/?", f, false, false, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -575,8 +704,128 @@ namespace build2
if (r.empty ())
fail << "unable to guess " << rc << " signature";
- return rc_info {
- move (pp), move (r.id), move (r.signature), move (r.checksum)};
+ const char* const* rc_env ((r.id == "msvc" ||
+ r.id == "msvc-llvm") ? msvc_rc_env :
+ nullptr);
+
+ return rc_cache.insert (move (key),
+ rc_info {
+ move (pp),
+ move (r.id),
+ move (r.signature),
+ move (r.checksum),
+ rc_env});
+ }
+
+ // Extracting nm information requires running it which can become
+ // expensive if done repeatedly. So we cache the result.
+ //
+ static global_cache<nm_info> nm_cache;
+
+ const nm_info&
+ guess_nm (context& ctx, const path& nm, const char* paths)
+ {
+ tracer trace ("bin::guess_nm");
+
+ // First check the cache.
+ //
+ // Note that none of the information that we cache can be affected by
+ // the environment.
+ //
+ string key;
+ {
+ sha256 cs;
+ cs.append (nm.string ());
+ if (paths != nullptr) cs.append (paths);
+ key = cs.string ();
+
+ if (const nm_info* r = nm_cache.find (key))
+ return *r;
+ }
+
+ guess_result r;
+
+ process_path pp (search (nm, paths, "config.bin.nm"));
+
+ // We should probably assume the utility output language words can be
+ // translated and even rearranged. Thus pass LC_ALL=C.
+ //
+ process_env env (pp);
+
+ // For now let's assume that all the platforms other than Windows
+ // recognize LC_ALL.
+ //
+#ifndef _WIN32
+ const char* evars[] = {"LC_ALL=C", nullptr};
+ env.vars = evars;
+#endif
+
+ // Both GNU Binutils and LLVM nm recognize the --version option.
+ //
+ // Microsoft dumpbin.exe does not recogize --version but will still
+ // issue its standard banner (and even exit with zero status).
+ //
+ // FreeBSD uses nm from ELF Toolchain which recognizes --version.
+ //
+ // Mac OS X nm doesn't have an option to display version or help. If we
+ // run it without any arguments, then it looks for a.out. So there
+ // doesn't seem to be a way to detect it.
+ //
+ // Version extraction is a @@ TODO.
+ {
+ auto f = [] (string& l, bool) -> guess_result
+ {
+ // Binutils nm --version output first line starts with "GNU nm" but
+ // search for "GNU ", similar to other tools.
+ //
+ if (l.find ("GNU ") != string::npos)
+ return guess_result ("gnu", move (l), semantic_version ());
+
+ // LLVM nm --version output has a line that starts with
+ // "LLVM version" followed by a version.
+ //
+ // But let's assume it can be prefixed with some stuff like the rest
+ // of the LLVM tools (see above).
+ //
+ if (l.find ("LLVM version ") != string::npos)
+ return guess_result ("llvm", move (l), semantic_version ());
+
+ if (l.compare (0, 14, "Microsoft (R) ") == 0)
+ return guess_result ("msvc", move (l), semantic_version ());
+
+ // nm --version from ELF Toolchain prints:
+ //
+ // nm (elftoolchain r3477M)
+ //
+ if (l.find ("elftoolchain") != string::npos)
+ return guess_result ("elftoolchain", move (l), semantic_version ());
+
+ return guess_result ();
+ };
+
+ // Suppress all the errors because we may be trying an unsupported
+ // option.
+ //
+ sha256 cs;
+ r = run<guess_result> (ctx, 3, env, "--version", f, false, false, &cs);
+
+ if (!r.empty ())
+ r.checksum = cs.string ();
+ }
+
+ // Since there are some unrecognizable nm's (e.g., on Mac OS X), we will
+ // have to assume generic if we managed to find the executable.
+ //
+ if (r.empty ())
+ r = guess_result ("generic", "", semantic_version ());
+
+ return nm_cache.insert (move (key),
+ nm_info {
+ move (pp),
+ move (r.id),
+ move (r.signature),
+ move (r.checksum),
+ nullptr /* environment */});
}
}
}