diff options
Diffstat (limited to 'libbuild2/bin/guess.cxx')
-rw-r--r-- | libbuild2/bin/guess.cxx | 238 |
1 files changed, 213 insertions, 25 deletions
diff --git a/libbuild2/bin/guess.cxx b/libbuild2/bin/guess.cxx index 9f15030..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 (); } @@ -89,7 +92,7 @@ namespace build2 static global_cache<ar_info> ar_cache; const ar_info& - guess_ar (const path& ar, const path* rl, const char* paths) + guess_ar (context& ctx, const path& ar, const path* rl, const char* paths) { tracer trace ("bin::guess_ar"); @@ -177,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)); @@ -227,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 (); @@ -247,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 ()) { @@ -280,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 @@ -293,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 (); @@ -310,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 ()) { @@ -326,6 +341,9 @@ namespace build2 fail << "unable to guess " << *rl << " signature"; } + // 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), @@ -333,25 +351,57 @@ namespace build2 move (arr.signature), move (arr.checksum), move (*arr.version), + nullptr, move (rlp), move (rlr.id), move (rlr.signature), - move (rlr.checksum)}); + move (rlr.checksum), + nullptr}); } + // 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 (const path& ld, const char* paths) + 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; @@ -402,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: + // + // Debian LLD 14.0.6 (compatible with GNU linkers) // - else if (l.compare (0, 4, "LLD ") == 0) + 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 @@ -450,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 (); @@ -486,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 (); @@ -513,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 (); @@ -522,27 +577,44 @@ namespace build2 if (r.empty ()) fail << "unable to guess " << ld << " signature"; + 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)}); + move (r.version), + ld_env}); } + // 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 (const path& rc, const char* paths) + 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; @@ -590,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 (); @@ -623,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 (); @@ -632,12 +704,128 @@ namespace build2 if (r.empty ()) fail << "unable to guess " << rc << " signature"; + 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)}); + 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 */}); } } } |