diff options
Diffstat (limited to 'libbuild2/bin/guess.cxx')
-rw-r--r-- | libbuild2/bin/guess.cxx | 341 |
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 */}); } } } |