diff options
Diffstat (limited to 'libbuild2/bin/guess.cxx')
-rw-r--r-- | libbuild2/bin/guess.cxx | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/libbuild2/bin/guess.cxx b/libbuild2/bin/guess.cxx new file mode 100644 index 0000000..68ef827 --- /dev/null +++ b/libbuild2/bin/guess.cxx @@ -0,0 +1,464 @@ +// file : libbuild2/bin/guess.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/bin/guess.hxx> + +#include <libbuild2/diagnostics.hxx> + +using namespace std; + +namespace build2 +{ + namespace bin + { + struct guess_result + { + string id; + string signature; + string checksum; + semantic_version version; + + guess_result () = default; + guess_result (string&& i, string&& s, semantic_version&& v) + : id (move (i)), signature (move (s)), version (move (v)) {} + + bool + empty () const {return id.empty ();} + }; + + // Try to parse a semantic-like version from the specified position. + // Return 0-version if the version is invalid. + // + static inline semantic_version + parse_version (const string& s, size_t p = 0, const char* bs = ".-+~ ") + { + optional<semantic_version> v (parse_semantic_version (s, p, bs)); + return v ? *v : semantic_version (); + } + + ar_info + guess_ar (const path& ar, const path* rl, const dir_path& fallback) + { + tracer trace ("bin::guess_ar"); + + process_path arp, rlp; + guess_result arr, rlr; + + { + auto df = make_diag_frame ( + [](const diag_record& dr) + { + dr << info << "use config.bin.ar to override"; + }); + + // Only search in PATH (specifically, omitting the current + // executable's directory on Windows). + // + arp = run_search (ar, true, fallback, true /* path_only */); + } + + if (rl != nullptr) + { + auto df = make_diag_frame ( + [](const diag_record& dr) + { + dr << info << "use config.bin.ranlib to override"; + }); + + rlp = run_search (*rl, true, fallback, true /* path_only */); + } + + // Binutils, LLVM, and FreeBSD ar/ranlib all recognize the --version + // option. While Microsoft's lib.exe doesn't support --version, it only + // issues a warning and exits with zero status, printing its usual + // banner before that (running lib.exe without any options result in + // non-zero exit status -- go figure). So we are going to start with + // that. + // + { + auto f = [] (string& l, bool) -> guess_result + { + // Normally GNU binutils ar --version output has a line that starts + // with "GNU ar" and ends with the version. For example: + // + // "GNU ar (GNU Binutils) 2.26" + // "GNU ar (GNU Binutils for Ubuntu) 2.26.1" + // + // However, some embedded toolchain makers customize this stuff in + // all kinds of ways. For example: + // + // "ppc-vle-ar (HighTec Release HDP-v4.6.6.1-bosch-1.3-3c1e3bc) build on 2017-03-23 (GNU Binutils) 2.20" + // "GNU ar version 2.13 (tricore) using BFD version 2.13 (2008-12-10)" + // + // So let's look for "GNU " and be prepared to find junk instead of + // the version. + // + if (l.find ("GNU ") != string::npos) + { + semantic_version v (parse_version (l, l.rfind (' ') + 1)); + return guess_result ("gnu", move (l), move (v)); + } + + // LLVM ar --version output has a line that starts with + // "LLVM version " and ends with the version, for example: + // + // "LLVM version 3.5.2" + // "LLVM version 5.0.0" + // + if (l.compare (0, 13, "LLVM version ") == 0) + { + semantic_version v (parse_version (l, l.rfind (' ') + 1)); + return guess_result ("llvm", move (l), move (v)); + } + + // FreeBSD ar --verison output starts with "BSD ar " followed by + // the version and some extra information, for example: + // + // "BSD ar 1.1.0 - libarchive 3.1.2" + // + // We will treat the extra information as the build component. + // + if (l.compare (0, 7, "BSD ar ") == 0) + { + semantic_version v (parse_version (l, 7)); + return guess_result ("bsd", move (l), move (v)); + } + + // Microsoft lib.exe output starts with "Microsoft (R) " and ends + // with a four-component version, for example: + // + // "Microsoft (R) Library Manager Version 14.00.24215.1" + // "Microsoft (R) Library Manager Version 14.14.26428.1" + // + if (l.compare (0, 14, "Microsoft (R) ") == 0) + { + semantic_version v (parse_version (l, l.rfind (' ') + 1)); + return guess_result ("msvc", move (l), move (v)); + } + + return guess_result (); + }; + + // Suppress all the errors because we may be trying an unsupported + // option. Note that in case of lib.exe we will hash the warning + // (yes, it goes to stdout) but that seems harmless. + // + sha256 cs; + arr = run<guess_result> (3, arp, "--version", f, false, false, &cs); + + if (!arr.empty ()) + arr.checksum = cs.string (); + } + + // On Mac OS X (and probably also older BSDs) ar/ranlib doesn't have an + // option to display version or help. If we run it without any arguments + // it dumps usage and exist with an error status. So we will have to use + // that. + // + if (arr.empty ()) + { + auto f = [] (string& l, bool) -> guess_result + { + return l.find (" ar ") != string::npos + ? guess_result ("generic", move (l), semantic_version ()) + : guess_result (); + }; + + // Redirect STDERR to STDOUT and ignore exit status. + // + sha256 cs; + arr = run<guess_result> (3, arp, f, false, true, &cs); + + if (!arr.empty ()) + { + l4 ([&]{trace << "generic ar '" << arr.signature << "'";}); + arr.checksum = cs.string (); + } + } + + if (arr.empty ()) + fail << "unable to guess " << ar << " signature"; + + // Now repeat pretty much the same steps for ranlib if requested. We + // don't bother with the version assuming it is the same as for ar. + // + if (rl != nullptr) + { + // Binutils, LLVM, and FreeBSD. + // + { + auto f = [] (string& l, bool) -> guess_result + { + // The same story as with ar: normally starts with "GNU ranlib " + // but can vary. + // + if (l.find ("GNU ") != string::npos) + return guess_result ("gnu", move (l), semantic_version ()); + + // "LLVM version ". + // + if (l.compare (0, 13, "LLVM version ") == 0) + return guess_result ("llvm", move (l), semantic_version ()); + + // On FreeBSD we get "ranlib" rather than "BSD ranlib" for some + // reason. Which means we can't really call it 'bsd' for sure. + // + //if (l.compare (0, 7, "ranlib ") == 0) + // return guess_result ("bsd", move (l), semantic_version ()); + + return guess_result (); + }; + + sha256 cs; + rlr = run<guess_result> (3, rlp, "--version", f, false, false, &cs); + + if (!rlr.empty ()) + rlr.checksum = cs.string (); + } + + // Mac OS X (and probably also older BSDs). + // + if (rlr.empty ()) + { + auto f = [] (string& l, bool) -> guess_result + { + return l.find ("ranlib") != string::npos + ? guess_result ("generic", move (l), semantic_version ()) + : guess_result (); + }; + + // Redirect STDERR to STDOUT and ignore exit status. + // + sha256 cs; + rlr = run<guess_result> (3, rlp, f, false, true, &cs); + + if (!rlr.empty ()) + { + l4 ([&]{trace << "generic ranlib '" << rlr.signature << "'";}); + rlr.checksum = cs.string (); + } + } + + if (rlr.empty ()) + 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)}; + } + + ld_info + guess_ld (const path& ld, const dir_path& fallback) + { + tracer trace ("bin::guess_ld"); + + guess_result r; + + process_path pp; + { + auto df = make_diag_frame ( + [](const diag_record& dr) + { + dr << info << "use config.bin.ld to override"; + }); + + // Only search in PATH (specifically, omitting the current + // executable's directory on Windows). + // + pp = run_search (ld, true, fallback, true /* path_only */); + } + + // Binutils ld recognizes the --version option. Microsoft's link.exe + // doesn't support --version (nor any other way to get the version + // without the error exit status) but it will still print its banner. + // We also want to recognize link.exe as fast as possible since it will + // be the most commonly configured linker (for other platoforms the + // linker will normally be used indirectly via the compiler and the + // bin.ld module won't be loaded). So we are going to ignore the error + // exit status. Our signatures are fairly specific to avoid any kind + // of false positives. + // + // Version extraction is a @@ TODO. + // + { + auto f = [] (string& l, bool) -> guess_result + { + // Microsoft link.exe output starts with "Microsoft (R) ". + // + if (l.compare (0, 14, "Microsoft (R) ") == 0) + return guess_result ("msvc", move (l), semantic_version ()); + + // Binutils ld.bfd --version output has a line that starts with + // "GNU ld " while ld.gold -- "GNU gold". Again, fortify it against + // embedded toolchain customizations by search for "GNU " in the + // former case. + // + if (l.compare (0, 9, "GNU gold ") == 0) + return guess_result ("gold", move (l), semantic_version ()); + + if (l.find ("GNU ") != string::npos) + return guess_result ("gnu", move (l), semantic_version ()); + + return guess_result (); + }; + + // 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, pp, "--version", f, false, true, &cs); + + if (!r.empty ()) + r.checksum = cs.string (); + } + + // Next try -v which will cover Apple's linkers. + // + if (r.empty ()) + { + auto f = [] (string& l, bool) -> guess_result + { + // New ld64 has "PROJECT:ld64" in the first line (output to stderr), + // for example: + // + // @(#)PROGRAM:ld PROJECT:ld64-242.2 + // + if (l.find ("PROJECT:ld64") != string::npos) + return guess_result ("ld64", move (l), semantic_version ()); + + // Old ld has "cctools" in the first line, for example: + // + // Apple Computer, Inc. version cctools-622.9~2 + // + if (l.find ("cctools") != string::npos) + return guess_result ("cctools", move (l), semantic_version ()); + + return guess_result (); + }; + + sha256 cs; + r = run<guess_result> (3, pp, "-v", f, false, false, &cs); + + if (!r.empty ()) + r.checksum = cs.string (); + } + + // Finally try -version which will take care of LLVM's lld. + // + if (r.empty ()) + { + auto f = [] (string& l, bool) -> guess_result + { + // Unlike other LLVM tools (e.g., ar), the lld's version is printed + // (to stderr) as: + // + // LLVM Linker Version: 3.7 + // + if (l.compare (0, 19, "LLVM Linker Version") == 0) + return guess_result ("llvm", 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> (3, pp, "-version", f, false, false, &cs); + + if (!r.empty ()) + r.checksum = cs.string (); + } + + if (r.empty ()) + fail << "unable to guess " << ld << " signature"; + + return ld_info { + move (pp), move (r.id), move (r.signature), move (r.checksum)}; + } + + rc_info + guess_rc (const path& rc, const dir_path& fallback) + { + tracer trace ("bin::guess_rc"); + + guess_result r; + + process_path pp; + { + auto df = make_diag_frame ( + [](const diag_record& dr) + { + dr << info << "use config.bin.rc to override"; + }); + + // Only search in PATH (specifically, omitting the current + // executable's directory on Windows). + // + pp = run_search (rc, true, fallback, true /* path_only */); + } + + // Binutils windres recognizes the --version option. + // + // Version extraction is a @@ TODO. + { + auto f = [] (string& l, bool) -> guess_result + { + // Binutils windres --version output has a line that starts with + // "GNU windres " but search for "GNU ", similar to other tools. + // + if (l.find ("GNU ") != string::npos) + return guess_result ("gnu", 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> (3, pp, "--version", f, false, false, &cs); + + if (!r.empty ()) + r.checksum = cs.string (); + } + + // Microsoft rc.exe /? prints its standard banner and exits with zero + // status. + // + if (r.empty ()) + { + auto f = [] (string& l, bool) -> guess_result + { + if (l.compare (0, 14, "Microsoft (R) ") == 0) + return guess_result ("msvc", move (l), semantic_version ()); + + return guess_result (); + }; + + sha256 cs; + r = run<guess_result> (3, pp, "/?", f, false, false, &cs); + + if (!r.empty ()) + r.checksum = cs.string (); + } + + if (r.empty ()) + fail << "unable to guess " << rc << " signature"; + + return rc_info { + move (pp), move (r.id), move (r.signature), move (r.checksum)}; + } + } +} |