From 296575ba025ded840304c1e3b6365a6b6ee7ea48 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 26 Oct 2018 19:07:42 +0200 Subject: Add config.{c,cxx}.{id,version,target} configuration variables These variables allow overriding guessed compiler id/version/target, for example, in case of mis-guesses or when working with compilers that don't report their base (e.g., GCC, Clang) with -v/--version (common in the embedded space). --- build2/bin/guess.cxx | 18 +- build2/bin/init.cxx | 2 +- build2/c/init.cxx | 3 + build2/cc/common.hxx | 3 + build2/cc/guess.cxx | 1120 +++++++++++++++++++++++---------------- build2/cc/guess.hxx | 14 +- build2/cc/module.cxx | 26 +- build2/context.cxx | 4 +- build2/cxx/init.cxx | 3 + build2/utility.hxx | 4 +- build2/utility.txx | 10 +- build2/version/snapshot-git.cxx | 7 +- 12 files changed, 714 insertions(+), 500 deletions(-) (limited to 'build2') diff --git a/build2/bin/guess.cxx b/build2/bin/guess.cxx index 78a1940..61c9f20 100644 --- a/build2/bin/guess.cxx +++ b/build2/bin/guess.cxx @@ -74,7 +74,7 @@ namespace build2 // that. // { - auto f = [] (string& l) -> guess_result + 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: @@ -155,7 +155,7 @@ namespace build2 // if (arr.empty ()) { - auto f = [] (string& l) -> guess_result + auto f = [] (string& l, bool) -> guess_result { return l.find (" ar ") != string::npos ? guess_result ("generic", move (l), semantic_version ()) @@ -185,7 +185,7 @@ namespace build2 // Binutils, LLVM, and FreeBSD. // { - auto f = [] (string& l) -> guess_result + auto f = [] (string& l, bool) -> guess_result { // The same story as with ar: normally starts with "GNU ranlib " // but can vary. @@ -218,7 +218,7 @@ namespace build2 // if (rlr.empty ()) { - auto f = [] (string& l) -> guess_result + auto f = [] (string& l, bool) -> guess_result { return l.find ("ranlib") != string::npos ? guess_result ("generic", move (l), semantic_version ()) @@ -285,7 +285,7 @@ namespace build2 // Version extraction is a @@ TODO. // { - auto f = [] (string& l) -> guess_result + auto f = [] (string& l, bool) -> guess_result { // Microsoft link.exe output starts with "Microsoft (R) ". // @@ -321,7 +321,7 @@ namespace build2 // if (r.empty ()) { - auto f = [] (string& l) -> guess_result + auto f = [] (string& l, bool) -> guess_result { // New ld64 has "PROJECT:ld64" in the first line (output to stderr), // for example: @@ -352,7 +352,7 @@ namespace build2 // if (r.empty ()) { - auto f = [] (string& l) -> guess_result + auto f = [] (string& l, bool) -> guess_result { // Unlike other LLVM tools (e.g., ar), the lld's version is printed // (to stderr) as: @@ -404,7 +404,7 @@ namespace build2 // // Version extraction is a @@ TODO. { - auto f = [] (string& l) -> guess_result + 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. @@ -430,7 +430,7 @@ namespace build2 // if (r.empty ()) { - auto f = [] (string& l) -> guess_result + auto f = [] (string& l, bool) -> guess_result { if (l.compare (0, 14, "Microsoft (R) ") == 0) return guess_result ("msvc", move (l), semantic_version ()); diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index 9112773..6b3aeb3 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -262,7 +262,7 @@ namespace build2 s = run (3, ops.config_sub (), s.c_str (), - [] (string& l) {return move (l);}); + [] (string& l, bool) {return move (l);}); l5 ([&]{trace << "config.sub target: '" << s << "'";}); } diff --git a/build2/c/init.cxx b/build2/c/init.cxx index 141c810..c0fbdde 100644 --- a/build2/c/init.cxx +++ b/build2/c/init.cxx @@ -161,6 +161,9 @@ namespace build2 // Note: some overridable, some not. // v.insert ("config.c", true), + v.insert ("config.c.id", true), + v.insert ("config.c.version", true), + v.insert ("config.c.target", true), v.insert ("config.c.poptions", true), v.insert ("config.c.coptions", true), v.insert ("config.c.loptions", true), diff --git a/build2/cc/common.hxx b/build2/cc/common.hxx index 9629fa3..dccf62e 100644 --- a/build2/cc/common.hxx +++ b/build2/cc/common.hxx @@ -40,6 +40,9 @@ namespace build2 const char* const* x_hinters; const variable& config_x; + const variable& config_x_id; // [-] + const variable& config_x_version; + const variable& config_x_target; const variable& config_x_poptions; const variable& config_x_coptions; const variable& config_x_loptions; diff --git a/build2/cc/guess.cxx b/build2/cc/guess.cxx index 4e581a7..de262cc 100644 --- a/build2/cc/guess.cxx +++ b/build2/cc/guess.cxx @@ -31,19 +31,29 @@ namespace build2 return r; } -#if 0 compiler_id:: - compiler_id (const std::string& t, std::string v) - : type (t == "gcc" ? compiler_type::gcc : - t == "clang" ? compiler_type::clang : - t == "msvc" ? compiler_type::msvc : - t == "icc" ? compiler_type::icc : invalid_compiler_type), - variant (move (v)) + compiler_id (const std::string& id) { - if (t == invalid_compiler_type) - throw invalid_argument ("invalid compiler type value '" + t + "'"); + using std::string; + + size_t p (id.find ('-')); + + if (id.compare (0, p, "gcc" ) == 0) type = compiler_type::gcc; + else if (id.compare (0, p, "clang") == 0) type = compiler_type::clang; + else if (id.compare (0, p, "msvc" ) == 0) type = compiler_type::msvc; + else if (id.compare (0, p, "icc" ) == 0) type = compiler_type::icc; + else + throw invalid_argument ( + "invalid compiler type '" + string (id, 0, p) + "'"); + + if (p != string::npos) + { + variant.assign (id, p + 1, string::npos); + + if (variant.empty ()) + throw invalid_argument ("empty compiler variant"); + } } -#endif string compiler_id:: string () const @@ -202,7 +212,7 @@ namespace build2 // only guesses the type, not the variant. // static pair - pre_guess (lang xl, const path& xc) + pre_guess (lang xl, const path& xc, const optional& xi) { tracer trace ("cc::pre_guess"); @@ -231,16 +241,30 @@ namespace build2 : string::npos; }; + using type = compiler_type; + using pair = std::pair; + + // If the user specified the compiler id, then only check the stem for + // that compiler. + // + auto check = [&xi, &stem] (type t, const char* s) -> optional + { + if (!xi || xi->type == t) + { + size_t p (stem (s)); + + if (p != string::npos) + return pair (t, p); + } + + return nullopt; + }; + // Warn if the user specified a C compiler instead of C++ or vice versa. // lang o; // Other language. const char* as (nullptr); // Actual stem. const char* es (nullptr); // Expected stem. - size_t p; // Executable name position. - - using type = compiler_type; - using pair = std::pair; - const size_t npos (string::npos); switch (xl) { @@ -248,15 +272,15 @@ namespace build2 { // Keep msvc last since 'cl' is very generic. // - if ((p = stem ("gcc")) != npos) return pair (type::gcc, p); - if ((p = stem ("clang")) != npos) return pair (type::clang, p); - if ((p = stem ("icc")) != npos) return pair (type::icc, p); - if ((p = stem ("cl")) != npos) return pair (type::msvc, p); + if (auto r = check (type::gcc, "gcc") ) return *r; + if (auto r = check (type::clang, "clang")) return *r; + if (auto r = check (type::icc, "icc") ) return *r; + if (auto r = check (type::msvc, "cl") ) return *r; - if (stem (as = "g++") != npos) es = "gcc"; - else if (stem (as = "clang++") != npos) es = "clang"; - else if (stem (as = "icpc") != npos) es = "icc"; - else if (stem (as = "c++") != npos) es = "cc"; + if (check (type::gcc, as = "g++") ) es = "gcc"; + else if (check (type::clang, as = "clang++")) es = "clang"; + else if (check (type::icc, as = "icpc") ) es = "icc"; + else if (check (type::msvc, as = "c++") ) es = "cc"; o = lang::cxx; break; @@ -265,15 +289,15 @@ namespace build2 { // Keep msvc last since 'cl' is very generic. // - if ((p = stem ("g++")) != npos) return pair (type::gcc, p); - if ((p = stem ("clang++")) != npos) return pair (type::clang, p); - if ((p = stem ("icpc")) != npos) return pair (type::icc, p); - if ((p = stem ("cl")) != npos) return pair (type::msvc, p); + if (auto r = check (type::gcc, "g++") ) return *r; + if (auto r = check (type::clang, "clang++")) return *r; + if (auto r = check (type::icc, "icpc") ) return *r; + if (auto r = check (type::msvc, "cl") ) return *r; - if (stem (as = "gcc") != npos) es = "g++"; - else if (stem (as = "clang") != npos) es = "clang++"; - else if (stem (as = "icc") != npos) es = "icpc"; - else if (stem (as = "cc") != npos) es = "c++"; + if (check (type::gcc, as = "gcc") ) es = "g++"; + else if (check (type::clang, as = "clang")) es = "clang++"; + else if (check (type::icc, as = "icc") ) es = "icpc"; + else if (check (type::msvc, as = "cc") ) es = "c++"; o = lang::c; break; @@ -284,6 +308,11 @@ namespace build2 warn << xc << " looks like a " << o << " compiler" << info << "should it be '" << es << "' instead of '" << as << "'?"; + // If the user specified the id, then continue as if we pre-guessed. + // + if (xi) + return pair (xi->type, string::npos); + l4 ([&]{trace << "unable to guess compiler type of " << xc;}); return pair (invalid_compiler_type, string::npos); @@ -301,7 +330,7 @@ namespace build2 process_path path; guess_result () = default; - guess_result (compiler_id&& i, string&& s) + guess_result (compiler_id i, string&& s) : id (move (i)), signature (move (s)) {} bool @@ -311,18 +340,24 @@ namespace build2 // Allowed to change pre if succeeds. // static guess_result - guess (lang, const string& xv, const path& xc, compiler_type& pre) + guess (const char* xm, + lang, + const path& xc, + const optional& xi, + compiler_type& pre) { tracer trace ("cc::guess"); + assert (!xi || xi->type == pre); + guess_result r; process_path xp; { auto df = make_diag_frame ( - [&xv](const diag_record& dr) + [&xm](const diag_record& dr) { - dr << info << "use " << xv << " to override"; + dr << info << "use config." << xm << " to override"; }); xp = run_search (xc, false /* init */); // Note: cached. @@ -350,10 +385,17 @@ namespace build2 pre == type::gcc || pre == type::clang)) { - auto f = [] (string& l) -> guess_result + auto f = [&xi] (string& l, bool last) -> guess_result { - // The gcc/g++ -v output will have a line (currently last) in the - // form: + if (xi) + { + // The signature line is first in Clang and last in GCC. + // + if (xi->type != type::gcc || last) + return guess_result (*xi, move (l)); + } + + // The gcc/g++ -v output will have a last line in the form: // // "gcc version X.Y.Z ..." // @@ -367,7 +409,7 @@ namespace build2 // gcc version 5.1.0 (Ubuntu 5.1.0-0ubuntu11~14.04.1) // gcc version 6.0.0 20160131 (experimental) (GCC) // - if (l.compare (0, 4, "gcc ") == 0) + 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 @@ -400,8 +442,8 @@ namespace build2 l.compare (6, 6, "clang ") == 0)) return guess_result (compiler_id {type::clang, "apple"}, move (l)); - // The vanilla clang/clang++ -v output will have a line (currently - // first) in the form: + // The vanilla clang/clang++ -v output will have a first line in the + // form: // // "[... ]clang version X.Y.Z[-...] ..." // @@ -434,7 +476,12 @@ namespace build2 // r = run (3, xp, "-v", f, false, false, &cs); - if (!r.empty ()) + if (r.empty ()) + { + if (xi) + fail << "unable to obtain " << xc << " signature with -v"; + } + else { // If this is clang-apple and pre-guess was gcc then change it so // that we don't issue any warnings. @@ -452,8 +499,13 @@ namespace build2 // if (r.empty () && (pre == invalid || pre == type::icc)) { - auto f = [] (string& l) -> guess_result + auto f = [&xi] (string& l, bool) -> guess_result { + // Assume the first line is the signature. + // + if (xi) + return guess_result (*xi, move (l)); + // The first line has the " (ICC) " in it, for example: // // icpc (ICC) 9.0 20060120 @@ -471,6 +523,12 @@ namespace build2 }; r = run (3, xp, "--version", f, false); + + if (r.empty ()) + { + if (xi) + fail << "unable to obtain " << xc << " signature with --version"; + } } // Finally try to run it without any options to detect msvc. @@ -478,8 +536,13 @@ namespace build2 // if (r.empty () && (pre == invalid || pre == type::msvc)) { - auto f = [] (string& l) -> guess_result + auto f = [&xi] (string& l, bool) -> guess_result { + // Assume the first line is the signature. + // + if (xi) + return guess_result (*xi, move (l)); + // Check for "Microsoft (R)" and "C/C++" in the first line as a // signature since all other words/positions can be translated. For // example: @@ -513,6 +576,12 @@ namespace build2 const char* env[] = {"CL=", "_CL_=", nullptr}; r = run (3, process_env (xp, env), f, false); + + if (r.empty ()) + { + if (xi) + fail << "unable to obtain " << xc << " signature"; + } } if (!r.empty ()) @@ -590,8 +659,11 @@ namespace build2 static compiler_info - guess_gcc (lang xl, + guess_gcc (const char* xm, + lang xl, const path& xc, + const string* xv, + const string* xt, const strings* c_po, const strings* x_po, const strings* c_co, const strings* x_co, const strings*, const strings*, @@ -607,52 +679,63 @@ namespace build2 // // "gcc version A.B.C[ ...]" // - string& s (gr.signature); - - // Scan the string as words and look for one that looks like a version. - // - size_t b (0), e (0); - while (next_word (s, b, e)) + compiler_version v; { - // The third argument to find_first_not_of() is the length of the - // first argument, not the length of the interval to check. So to - // limit it to [b, e) we are also going to compare the result to the - // end of the word position (first space). In fact, we can just check - // if it is >= e. + 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 (s.find_first_not_of ("1234567890.", b, 11) >= e) - break; - } + const string& s (xv == nullptr ? gr.signature : *xv); - if (b == e) - fail << "unable to extract gcc version from '" << s << "'"; + // Scan the string as words and look for one that looks like a + // version. + // + size_t b (0), e (0); + while (next_word (s, b, e)) + { + // The third argument to find_first_not_of() is the length of the + // first argument, not the length of the interval to check. So to + // limit it to [b, e) we are also going to compare the result to the + // 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) + break; + } - compiler_version v; - v.string.assign (s, b, string::npos); + if (b == e) + fail << "unable to extract gcc version from '" << s << "'"; - // Split the version into components. - // - size_t vb (b), ve (b); - auto next = [&s, b, e, &vb, &ve] (const char* m) -> uint64_t - { - try + v.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) -> uint64_t { - if (next_word (s, e, vb, ve, '.')) - return stoull (string (s, vb, ve - vb)); - } - catch (const invalid_argument&) {} - catch (const out_of_range&) {} + 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; - }; + fail << "unable to extract gcc " << m << " version from '" + << string (s, b, e - b) << "'" << endf; + }; - v.major = next ("major"); - v.minor = next ("minor"); - v.patch = next ("patch"); + v.major = next ("major"); + v.minor = next ("minor"); + v.patch = next ("patch"); - if (e != s.size ()) - v.build.assign (s, e + 1, string::npos); + if (e != s.size ()) + v.build.assign (s, e + 1, string::npos); + } // Figure out the target architecture. This is actually a lot trickier // than one would have hoped. @@ -675,32 +758,40 @@ namespace build2 // multi-arch support), then use the result. Otherwise, fallback to // -dumpmachine (older gcc or not multi-arch). // - cstrings args {xp.recall_string (), "-print-multiarch"}; - if (c_co != nullptr) append_options (args, *c_co); - if (x_co != nullptr) append_options (args, *x_co); - args.push_back (nullptr); + string t, ot; - // The output of both -print-multiarch and -dumpmachine is a single line - // containing just the target triplet. - // - auto f = [] (string& l) {return move (l);}; + if (xt == nullptr) + { + cstrings args {xp.recall_string (), "-print-multiarch"}; + if (c_co != nullptr) append_options (args, *c_co); + if (x_co != nullptr) append_options (args, *x_co); + args.push_back (nullptr); - string t (run (3, xp, args.data (), f, false)); + // The output of both -print-multiarch and -dumpmachine is a single + // line containing just the target triplet. + // + auto f = [] (string& l, bool) {return move (l);}; - if (t.empty ()) - { - l5 ([&]{trace << xc << " doesn's support -print-multiarch, " - << "falling back to -dumpmachine";}); + t = run (3, xp, args.data (), f, false); - args[1] = "-dumpmachine"; - t = run (3, xp, args.data (), f); - } + if (t.empty ()) + { + l5 ([&]{trace << xc << " doesn's support -print-multiarch, " + << "falling back to -dumpmachine";}); - if (t.empty ()) - fail << "unable to extract target architecture from " << xc - << " -print-multiarch or -dumpmachine output"; + args[1] = "-dumpmachine"; + t = run (3, xp, args.data (), f, false); + } - string ot (t); + if (t.empty ()) + fail << "unable to extract target architecture from " << xc + << " using -print-multiarch or -dumpmachine output" << + info << "use config." << xm << ".target to override"; + + ot = t; + } + else + ot = t = *xt; // Parse the target into triplet (for further tests) ignoring any // failures. @@ -759,8 +850,11 @@ namespace build2 } static compiler_info - guess_clang (lang xl, + guess_clang (const char* xm, + lang xl, const path& xc, + const string* xv, + const string* xt, const strings* c_po, const strings* x_po, const strings* c_co, const strings* x_co, const strings* c_lo, const strings* x_lo, @@ -776,87 +870,105 @@ namespace build2 // "[... ]clang version A.B.C[( |-)...]" // "Apple (clang|LLVM) version A.B[.C] ..." // - string& s (gr.signature); + compiler_version v; + { + auto df = make_diag_frame ( + [&xm](const diag_record& dr) + { + dr << info << "use config." << xm << ".version to override"; + }); - // 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)"; + // Treat the custom version as just a tail of the signature. + // + const string& s (xv == nullptr ? gr.signature : *xv); - // 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, ' ', '-')) - { - // The third argument to find_first_not_of() is the length of the - // first argument, not the length of the interval to check. So to - // limit it to [b, e) we are also going to compare the result to the - // end of the word position (first space). In fact, we can just check - // if it is >= e. + // Some overrides for testing. // - if (s.find_first_not_of ("1234567890.", b, 11) >= e) - break; - } + //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)"; + + // 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, ' ', '-')) + { + // The third argument to find_first_not_of() is the length of the + // first argument, not the length of the interval to check. So to + // limit it to [b, e) we are also going to compare the result to the + // 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) + break; + } - if (b == e) - fail << "unable to extract clang version from '" << s << "'"; + if (b == e) + fail << "unable to extract clang version from '" << s << "'"; - compiler_version v; - v.string.assign (s, b, string::npos); + v.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 - { - try + // 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 { - if (next_word (s, e, vb, ve, '.')) - return stoull (string (s, vb, ve - vb)); + try + { + if (next_word (s, e, vb, ve, '.')) + return stoull (string (s, vb, ve - vb)); - if (opt) - return 0; - } - catch (const invalid_argument&) {} - catch (const out_of_range&) {} + if (opt) + return 0; + } + catch (const invalid_argument&) {} + catch (const out_of_range&) {} - fail << "unable to extract clang " << m << " version from '" - << string (s, b, e - b) << "'" << endf; - }; + fail << "unable to extract clang " << m << " version from '" + << string (s, b, e - b) << "'" << endf; + }; - v.major = next ("major", false); - v.minor = next ("minor", false); - v.patch = next ("patch", gr.id.variant == "apple"); + v.major = next ("major", false); + v.minor = next ("minor", false); + v.patch = next ("patch", gr.id.variant == "apple"); - if (e != s.size ()) - v.build.assign (s, e + 1, string::npos); + if (e != s.size ()) + v.build.assign (s, e + 1, string::npos); + } // Figure out the target architecture. // // Unlike gcc, clang doesn't have -print-multiarch. Its -dumpmachine, // however, respects the compile options (e.g., -m32). // - cstrings args {xp.recall_string (), "-dumpmachine"}; - if (c_co != nullptr) append_options (args, *c_co); - if (x_co != nullptr) append_options (args, *x_co); - args.push_back (nullptr); + string t, ot; - // The output of -dumpmachine is a single line containing just the - // target triplet. - // - auto f = [] (string& l) {return move (l);}; - string t (run (3, xp, args.data (), f)); + if (xt == nullptr) + { + cstrings args {xp.recall_string (), "-dumpmachine"}; + if (c_co != nullptr) append_options (args, *c_co); + if (x_co != nullptr) append_options (args, *x_co); + args.push_back (nullptr); - if (t.empty ()) - fail << "unable to extract target architecture from " << xc - << " -dumpmachine output"; + // The output of -dumpmachine is a single line containing just the + // target triplet. + // + auto f = [] (string& l, bool) {return move (l);}; + t = run (3, xp, args.data (), f, false); - string ot (t); + if (t.empty ()) + fail << "unable to extract target architecture from " << xc + << " using -dumpmachine output" << + info << "use config." << xm << ".target to override"; + + ot = t; + } + else + ot = t = *xt; // Parse the target into triplet (for further tests) ignoring any // failures. @@ -978,8 +1090,11 @@ namespace build2 } static compiler_info - guess_icc (lang xl, + guess_icc (const char* xm, + lang xl, const path& xc, + const string* xv, + const string* xt, const strings* c_po, const strings* x_po, const strings* c_co, const strings* x_co, const strings*, const strings*, @@ -1012,95 +1127,110 @@ namespace build2 // We should probably also assume the language words can be translated // and even rearranged. // - string& s (gr.signature); - s.clear (); - - auto f = [] (string& l) + auto f = [] (string& l, bool) { return l.compare (0, 5, "Intel") == 0 && (l[5] == '(' || l[5] == ' ') ? move (l) : string (); }; - // The -V output is sent to STDERR. - // - s = run (3, xp, "-V", f, false); + if (xv == nullptr) + { + string& s (gr.signature); + s.clear (); - if (s.empty ()) - fail << "unable to extract signature from " << xc << " -V output"; + // The -V output is sent to STDERR. + // + s = run (3, xp, "-V", f, false); - if (s.find (xl == lang::c ? " C " : " C++ ") == string::npos) - fail << xc << " does not appear to be the Intel " << xl - << " compiler" << - info << "extracted signature: '" << s << "'"; + if (s.empty ()) + fail << "unable to extract signature from " << xc << " -V output"; + + if (s.find (xl == lang::c ? " C " : " C++ ") == string::npos) + fail << xc << " does not appear to be the Intel " << xl + << " compiler" << + info << "extracted signature: '" << s << "'"; + } // Scan the string as words and look for the version. It consist of only // digits and periods and contains at least one period. // + compiler_version v; + { + auto df = make_diag_frame ( + [&xm](const diag_record& dr) + { + dr << info << "use config." << xm << ".version to override"; + }); - // Some overrides for testing. - // - //s = "Intel(R) C++ Compiler for 32-bit applications, Version 9.1 Build 20070215Z Package ID: l_cc_c_9.1.047"; - //s = "Intel(R) C++ Compiler for applications running on Intel(R) 64, Version 10.1 Build 20071116"; - //s = "Intel(R) C++ Compiler for applications running on IA-32, Version 10.1 Build 20071116 Package ID: l_cc_p_10.1.010"; - //s = "Intel C++ Intel 64 Compiler Professional for applications running on Intel 64, Version 11.0 Build 20081105 Package ID: l_cproc_p_11.0.074"; - //s = "Intel(R) C++ Intel(R) 64 Compiler Professional for applications running on Intel(R) 64, Version 11.1 Build 20091130 Package ID: l_cproc_p_11.1.064"; - //s = "Intel C++ Intel 64 Compiler XE for applications running on Intel 64, Version 12.0.4.191 Build 20110427"; + // Treat the custom version as just a tail of the signature. + // + const string& s (xv == nullptr ? gr.signature : *xv); - size_t b (0), e (0), n; - while (next_word (s, b, e, ' ', ',') != 0) - { - // The third argument to find_first_not_of() is the length of the - // first argument, not the length of the interval to check. So to - // limit it to [b, e) we are also going to compare the result to the - // end of the word position (first space). In fact, we can just check - // if it is >= e. Similar logic for find_first_of() except that we add - // space to the list of character to make sure we don't go too far. + // Some overrides for testing. // - if (s.find_first_not_of ("1234567890.", b, 11) >= e && - s.find_first_of (". ", b, 2) < e) - break; - } + //s = "Intel(R) C++ Compiler for 32-bit applications, Version 9.1 Build 20070215Z Package ID: l_cc_c_9.1.047"; + //s = "Intel(R) C++ Compiler for applications running on Intel(R) 64, Version 10.1 Build 20071116"; + //s = "Intel(R) C++ Compiler for applications running on IA-32, Version 10.1 Build 20071116 Package ID: l_cc_p_10.1.010"; + //s = "Intel C++ Intel 64 Compiler Professional for applications running on Intel 64, Version 11.0 Build 20081105 Package ID: l_cproc_p_11.0.074"; + //s = "Intel(R) C++ Intel(R) 64 Compiler Professional for applications running on Intel(R) 64, Version 11.1 Build 20091130 Package ID: l_cproc_p_11.1.064"; + //s = "Intel C++ Intel 64 Compiler XE for applications running on Intel 64, Version 12.0.4.191 Build 20110427"; + + size_t b (0), e (0); + while (next_word (s, b, e, ' ', ',') != 0) + { + // The third argument to find_first_not_of() is the length of the + // first argument, not the length of the interval to check. So to + // limit it to [b, e) we are also going to compare the result to the + // end of the word position (first space). In fact, we can just + // check if it is >= e. Similar logic for find_first_of() except + // that we add space to the list of character to make sure we don't + // go too far. + // + if (s.find_first_not_of ("1234567890.", b, 11) >= e && + s.find_first_of (". ", b, 2) < e) + break; + } - if (b == e) - fail << "unable to extract icc version from '" << s << "'"; + if (b == e) + fail << "unable to extract icc version from '" << s << "'"; - compiler_version v; - v.string.assign (s, b, string::npos); + v.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 - { - try + // 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 { - if (next_word (s, e, vb, ve, '.')) - return stoull (string (s, vb, ve - vb)); + try + { + if (next_word (s, e, vb, ve, '.')) + return stoull (string (s, vb, ve - vb)); - if (opt) - return 0; - } - catch (const invalid_argument&) {} - catch (const out_of_range&) {} + if (opt) + return 0; + } + catch (const invalid_argument&) {} + catch (const out_of_range&) {} - fail << "unable to extract icc " << m << " version from '" - << string (s, b, e - b) << "'" << endf; - }; + fail << "unable to extract icc " << m << " version from '" + << string (s, b, e - b) << "'" << endf; + }; - v.major = next ("major", false); - v.minor = next ("minor", false); - v.patch = next ("patch", true); + v.major = next ("major", false); + v.minor = next ("minor", false); + v.patch = next ("patch", true); - if (vb != ve && next_word (s, e, vb, ve, '.')) - v.build.assign (s, vb, ve - vb); + if (vb != ve && next_word (s, e, vb, ve, '.')) + v.build.assign (s, vb, ve - vb); - if (e != s.size ()) - { - if (!v.build.empty ()) - v.build += ' '; + if (e != s.size ()) + { + if (!v.build.empty ()) + v.build += ' '; - v.build.append (s, e + 1, string::npos); + v.build.append (s, e + 1, string::npos); + } } // Figure out the target CPU by re-running the compiler with -V and @@ -1116,75 +1246,91 @@ namespace build2 // "Intel(R)" "64" // "Intel(R)" "MIC" (-dumpmachine says: x86_64-k1om-linux) // - cstrings args {xp.recall_string (), "-V"}; - if (c_co != nullptr) append_options (args, *c_co); - if (x_co != nullptr) append_options (args, *x_co); - args.push_back (nullptr); + string t, ot; - // The -V output is sent to STDERR. - // - string t (run (3, xp, args.data (), f, false)); + if (xt == nullptr) + { + auto df = make_diag_frame ( + [&xm](const diag_record& dr) + { + dr << info << "use config." << xm << ".target to override"; + }); - if (t.empty ()) - fail << "unable to extract target architecture from " << xc - << " -V output"; + cstrings args {xp.recall_string (), "-V"}; + if (c_co != nullptr) append_options (args, *c_co); + if (x_co != nullptr) append_options (args, *x_co); + args.push_back (nullptr); - string arch; - for (b = e = 0; (n = next_word (t, b, e, ' ', ',')) != 0; ) - { - if (t.compare (b, n, "Intel(R)", 8) == 0 || - t.compare (b, n, "Intel", 5) == 0) + // The -V output is sent to STDERR. + // + t = run (3, xp, args.data (), f, false); + + if (t.empty ()) + fail << "unable to extract target architecture from " << xc + << " -V output"; + + string arch; + for (size_t b (0), e (0), n; + (n = next_word (t, b, e, ' ', ',')) != 0; ) { - if ((n = next_word (t, b, e, ' ', ',')) != 0) + if (t.compare (b, n, "Intel(R)", 8) == 0 || + t.compare (b, n, "Intel", 5) == 0) { - if (t.compare (b, n, "64", 2) == 0) - { - arch = "x86_64"; - } - else if (t.compare (b, n, "MIC", 3) == 0) + if ((n = next_word (t, b, e, ' ', ',')) != 0) { - arch = "x86_64"; // Plus "-k1om-linux" from -dumpmachine below. + if (t.compare (b, n, "64", 2) == 0) + { + arch = "x86_64"; + } + else if (t.compare (b, n, "MIC", 3) == 0) + { + arch = "x86_64"; // Plus "-k1om-linux" from -dumpmachine below. + } } + else + break; } - else - break; - } - else if (t.compare (b, n, "IA-32", 5) == 0 || + else if (t.compare (b, n, "IA-32", 5) == 0 || t.compare (b, n, "32-bit", 6) == 0) - { - arch = "i386"; + { + arch = "i386"; + } } - } - if (arch.empty ()) - fail << "unable to extract icc target architecture from '" << t << "'"; + if (arch.empty ()) + fail << "unable to extract icc target architecture from '" + << t << "'"; - // So we have the CPU but we still need the rest of the triplet. While - // icc currently doesn't support cross-compilation (at least on Linux) - // and we could have just used the build triplet (i.e., the architecture - // on which we are running), who knows what will happen in the future. - // So instead we are going to use -dumpmachine and substitute the CPU. - // - { - auto f = [] (string& l) {return move (l);}; - t = run (3, xp, "-dumpmachine", f); - } + // So we have the CPU but we still need the rest of the triplet. While + // icc currently doesn't support cross-compilation (at least on Linux) + // and we could have just used the build triplet (i.e., the + // architecture on which we are running), who knows what will happen + // in the future. So instead we are going to use -dumpmachine and + // substitute the CPU. + // + { + auto f = [] (string& l, bool) {return move (l);}; + t = run (3, xp, "-dumpmachine", f); + } - if (t.empty ()) - fail << "unable to extract target architecture from " << xc - << " -dumpmachine output"; + if (t.empty ()) + fail << "unable to extract target architecture from " << xc + << " using -dumpmachine output"; - // The first component in the triplet is always CPU. - // - size_t p (t.find ('-')); + // The first component in the triplet is always CPU. + // + size_t p (t.find ('-')); - if (p == string::npos) - fail << "unable to parse icc target architecture '" << t << "'"; + if (p == string::npos) + fail << "unable to parse icc target architecture '" << t << "'"; - t.swap (arch); - t.append (arch, p, string::npos); + t.swap (arch); + t.append (arch, p, string::npos); - string ot (t); + ot = t; + } + else + ot = t = *xt; // Parse the target into triplet (for further tests) ignoring any // failures. @@ -1198,7 +1344,7 @@ namespace build2 // Use the signature line to generate the checksum. // - sha256 cs (s); + sha256 cs (gr.signature); // Runtime and standard library. // @@ -1237,8 +1383,11 @@ namespace build2 } static compiler_info - guess_msvc (lang xl, + guess_msvc (const char* xm, + lang xl, const path& xc, + const string* xv, + const string* xt, const strings*, const strings*, const strings*, const strings*, const strings*, const strings*, @@ -1257,190 +1406,199 @@ namespace build2 // "x64" // "ARM" // - string& s (gr.signature); - - // Some overrides for testing. - // - //s = "Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86"; - //s = "Compilador de optimizacion de C/C++ de Microsoft (R) version 16.00.30319.01 para x64"; + compiler_version v; + { + auto df = make_diag_frame ( + [&xm](const diag_record& dr) + { + dr << info << "use config." << xm << ".version to override"; + }); - // Scan the string as words and look for the version. While doing this - // also keep an eye on the CPU keywords. - // - string arch; - size_t b (0), e (0); + // Treat the custom version as just a tail of the signature. + // + const string& s (xv == nullptr ? gr.signature : *xv); - auto check_cpu = [&arch, &s, &b, &e] () -> bool - { - size_t n (e - b); + // Some overrides for testing. + // + //s = "Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86"; + //s = "Compilador de optimizacion de C/C++ de Microsoft (R) version 16.00.30319.01 para x64"; - if (s.compare (b, n, "x64", 3) == 0 || - s.compare (b, n, "x86", 3) == 0 || - s.compare (b, n, "ARM", 3) == 0 || - s.compare (b, n, "80x86", 5) == 0) + // Scan the string as words and look for the version. + // + size_t b (0), e (0); + while (next_word (s, b, e, ' ', ',')) { - arch.assign (s, b, n); - return true; + // The third argument to find_first_not_of() is the length of the + // first argument, not the length of the interval to check. So to + // limit it to [b, e) we are also going to compare the result to the + // 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) + break; } - return false; - }; + if (b == e) + fail << "unable to extract msvc version from '" << s << "'"; - while (next_word (s, b, e, ' ', ',')) - { - // First check for the CPU keywords in case in some language they come - // before the version. - // - if (check_cpu ()) - continue; - - // The third argument to find_first_not_of() is the length of the - // first argument, not the length of the interval to check. So to - // limit it to [b, e) we are also going to compare the result to the - // end of the word position (first space). In fact, we can just check - // if it is >= e. + v.string.assign (s, b, e - b); + + // Split the version into components. // - if (s.find_first_not_of ("1234567890.", b, 11) >= e) - break; - } + size_t vb (b), ve (b); + auto next = [&s, b, e, &vb, &ve] (const char* m) -> uint64_t + { + try + { + if (next_word (s, e, vb, ve, '.')) + return stoull (string (s, vb, ve - vb)); + } + catch (const invalid_argument&) {} + catch (const out_of_range&) {} - if (b == e) - fail << "unable to extract msvc version from '" << s << "'"; + fail << "unable to extract msvc " << m << " version from '" + << string (s, b, e - b) << "'" << endf; + }; - compiler_version v; - v.string.assign (s, b, e - b); + v.major = next ("major"); + v.minor = next ("minor"); + v.patch = next ("patch"); - // Split the version into components. + if (next_word (s, e, vb, ve, '.')) + v.build.assign (s, vb, ve - vb); + } + + + // Figure out the target architecture. // - size_t vb (b), ve (b); - auto next = [&s, b, e, &vb, &ve] (const char* m) -> uint64_t - { - try - { - if (next_word (s, e, vb, ve, '.')) - return stoull (string (s, vb, ve - vb)); - } - catch (const invalid_argument&) {} - catch (const out_of_range&) {} + string t, ot; - fail << "unable to extract msvc " << m << " version from '" - << string (s, b, e - b) << "'" << endf; - }; + if (xt == nullptr) + { + auto df = make_diag_frame ( + [&xm](const diag_record& dr) + { + dr << info << "use config." << xm << ".target to override"; + }); - v.major = next ("major"); - v.minor = next ("minor"); - v.patch = next ("patch"); + const string& s (gr.signature); - if (next_word (s, e, vb, ve, '.')) - v.build.assign (s, vb, ve - vb); + // Scan the string as words and look for the CPU. + // + string arch; - // Continue scanning for the CPU. - // - if (e != s.size ()) - { - while (next_word (s, b, e, ' ', ',')) + for (size_t b (0), e (0), n; + (n = next_word (s, b, e, ' ', ',')) != 0; ) { - if (check_cpu ()) + if (s.compare (b, n, "x64", 3) == 0 || + s.compare (b, n, "x86", 3) == 0 || + s.compare (b, n, "ARM", 3) == 0 || + s.compare (b, n, "80x86", 5) == 0) + { + arch.assign (s, b, n); break; + } } - } - - if (arch.empty ()) - fail << "unable to extract msvc target architecture 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 - // , 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'). - // - // The (toolchain) VENDOR is also straightforward: 'microsoft'. Why not - // omit it? Two reasons: firstly, there are other compilers with the - // otherwise same target, for example Intel C/C++, and it could be - // useful to distinguish between them. Secondly, by having all four - // components we remove any parsing ambiguity. - // - // 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 following values - // for OS: 'win32', 'wince', 'winrt', 'winup'. - // - // For 'win32' the ABI part could signal the Microsoft C/C++ runtime by - // calling it 'msvc'. And seeing that the runtimes are incompatible from - // version to version, we should probably add the 'X.Y' version at the - // end (so we essentially mimic the DLL name, e.g, msvcr120.dll). Some - // suggested we also encode the runtime type (those /M* options) though - // I am not sure: the only "redistributable" runtime is multi-threaded - // release DLL. - // - // The ABI part for the other OS values needs thinking. For 'winrt' and - // 'winup' it probably makes sense to encode the WINAPI_FAMILY macro - // value (perhaps also with the version). Some of its values: - // - // WINAPI_FAMILY_APP Windows 10 - // WINAPI_FAMILY_PC_APP Windows 8.1 - // WINAPI_FAMILY_PHONE_APP Windows Phone 8.1 - // - // For 'wince' we may also want to add the OS version, e.g., 'wince4.2'. - // - // 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-??? - // - string t; - - if (arch == "ARM") - fail << "cl.exe ARM/WinRT/UWP target is not yet supported"; - else - { - if (arch == "x64") - t = "x86_64-microsoft-win32-msvc"; - else if (arch == "x86" || arch == "80x86") - t = "i386-microsoft-win32-msvc"; - else - assert (false); + if (arch.empty ()) + fail << "unable to extract msvc target architecture from " + << "'" << s << "'"; - // Mapping of compiler versions to runtime versions: + // 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 + // , we need something in the + // CPU-VENDOR-OS-ABI form. // - // Note that VC15 has runtime version 14.1 but the DLLs are still - // called *140.dll (they are said to be backwards-compatible). + // 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'). // - // year ver cl.exe crt/dll + // The (toolchain) VENDOR is also straightforward: 'microsoft'. Why + // not omit it? Two reasons: firstly, there are other compilers with + // the otherwise same target, for example Intel C/C++, and it could be + // useful to distinguish between them. Secondly, by having all four + // components we remove any parsing ambiguity. // - // 2017 15u7 19.14 14.1/140 - // 2017 15u6 19.13 14.1/140 - // 2017 15u5 19.12 14.1/140 - // 2017 15u3 19.11 14.1/140 - // 2017 15 19.10 14.1/140 - // 2015 14 19.00 14.0/140 - // 2013 12 18.00 12.0/120 - // 2012 11 17.00 11.0/110 - // 2010 10 16.00 10.0/100 - // 2008 9 15.00 9.0/90 - // 2005 8 14.00 8.0/80 - // 2003 7.1 13.10 7.1/71 + // 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 + // following values for OS: 'win32', 'wince', 'winrt', 'winup'. // - /**/ if (v.major == 19 && v.minor >= 10) t += "14.1"; - else if (v.major == 19 && v.minor == 0) t += "14.0"; - else if (v.major == 18 && v.minor == 0) t += "12.0"; - else if (v.major == 17 && v.minor == 0) t += "11.0"; - else if (v.major == 16 && v.minor == 0) t += "10.0"; - else if (v.major == 15 && v.minor == 0) t += "9.0"; - else if (v.major == 14 && v.minor == 0) t += "8.0"; - else if (v.major == 13 && v.minor == 10) t += "7.1"; - else fail << "unable to map msvc compiler version '" << v.string - << "' to runtime version"; - } + // For 'win32' the ABI part could signal the Microsoft C/C++ runtime + // by calling it 'msvc'. And seeing that the runtimes are incompatible + // from version to version, we should probably add the 'X.Y' version + // at the end (so we essentially mimic the DLL name, for example, + // msvcr120.dll). Some suggested we also encode the runtime type + // (those pesky /M* options) though I am not sure: the only + // "redistributable" runtime is multi-threaded release DLL. + // + // The ABI part for the other OS values needs thinking. For 'winrt' + // and 'winup' it probably makes sense to encode the WINAPI_FAMILY + // macro value (perhaps also with the version). Some of its values: + // + // WINAPI_FAMILY_APP Windows 10 + // WINAPI_FAMILY_PC_APP Windows 8.1 + // WINAPI_FAMILY_PHONE_APP Windows Phone 8.1 + // + // For 'wince' we may also want to add the OS version, for example, + // 'wince4.2'. + // + // 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-??? + // + if (arch == "ARM") + fail << "cl.exe ARM/WinRT/UWP target is not yet supported"; + else + { + if (arch == "x64") + t = "x86_64-microsoft-win32-msvc"; + else if (arch == "x86" || arch == "80x86") + t = "i386-microsoft-win32-msvc"; + else + assert (false); - string ot (t); + // Mapping of compiler versions to runtime versions: + // + // Note that VC15 has runtime version 14.1 but the DLLs are still + // called *140.dll (they are said to be backwards-compatible). + // + // year ver cl.exe crt/dll + // + // 2017 15u8 19.15 14.1/140 + // 2017 15u7 19.14 14.1/140 + // 2017 15u6 19.13 14.1/140 + // 2017 15u5 19.12 14.1/140 + // 2017 15u3 19.11 14.1/140 + // 2017 15 19.10 14.1/140 + // 2015 14 19.00 14.0/140 + // 2013 12 18.00 12.0/120 + // 2012 11 17.00 11.0/110 + // 2010 10 16.00 10.0/100 + // 2008 9 15.00 9.0/90 + // 2005 8 14.00 8.0/80 + // 2003 7.1 13.10 7.1/71 + // + /**/ if (v.major == 19 && v.minor >= 10) t += "14.1"; + else if (v.major == 19 && v.minor == 0) t += "14.0"; + else if (v.major == 18 && v.minor == 0) t += "12.0"; + else if (v.major == 17 && v.minor == 0) t += "11.0"; + else if (v.major == 16 && v.minor == 0) t += "10.0"; + else if (v.major == 15 && v.minor == 0) t += "9.0"; + else if (v.major == 14 && v.minor == 0) t += "8.0"; + else if (v.major == 13 && v.minor == 10) t += "7.1"; + else fail << "unable to map msvc compiler version '" << v.string + << "' to runtime version"; + } + + ot = t; + } + else + ot = t = *xt; // Derive the toolchain pattern. // @@ -1453,7 +1611,7 @@ namespace build2 // Use the signature line to generate the checksum. // - sha256 cs (s); + sha256 cs (gr.signature); // Runtime and standard library. // @@ -1488,9 +1646,12 @@ namespace build2 static map cache; const compiler_info& - guess (lang xl, - const string& xv, + guess (const char* xm, + lang xl, const path& xc, + const string* xis, + const string* xv, + const string* xt, const strings* c_po, const strings* x_po, const strings* c_co, const strings* x_co, const strings* c_lo, const strings* x_lo) @@ -1502,6 +1663,7 @@ namespace build2 sha256 cs; cs.append (static_cast (xl)); cs.append (xc.string ()); + if (xis != nullptr) cs.append (*xis); if (c_po != nullptr) hash_options (cs, *c_po); if (x_po != nullptr) hash_options (cs, *x_po); if (c_co != nullptr) hash_options (cs, *c_co); @@ -1515,7 +1677,23 @@ namespace build2 return i->second; } - pair pre (pre_guess (xl, xc)); + // Parse the user-specified compiler id (config.x.id). + // + optional xi; + if (xis != nullptr) + { + try + { + xi = compiler_id (*xis); + } + catch (const invalid_argument& e) + { + fail << "invalid compiler id '" << *xis << "' " + << "specified in variable config." << xm << ".id: " << e; + } + } + + pair pre (pre_guess (xl, xc, xi)); compiler_type& type (pre.first); // If we could pre-guess the type based on the excutable name, then @@ -1525,20 +1703,23 @@ namespace build2 if (type != invalid_compiler_type) { - gr = guess (xl, xv, xc, type); + gr = guess (xm, xl, xc, xi, type); if (gr.empty ()) + { warn << xc << " looks like " << type << " but it is not" << - info << "use " << xv << " to override"; + info << "use config." << xm << " to override"; - type = invalid_compiler_type; + type = invalid_compiler_type; // Clear pre-guess. + } } if (gr.empty ()) - gr = guess (xl, xv, xc, type); + gr = guess (xm, xl, xc, xi, type); if (gr.empty ()) - fail << "unable to guess " << xl << " compiler type of " << xc; + fail << "unable to guess " << xl << " compiler type of " << xc << + info << "use config." << xm << ".id to specify explicitly"; compiler_info r; const compiler_id& id (gr.id); @@ -1547,28 +1728,28 @@ namespace build2 { case compiler_type::gcc: { - r = guess_gcc (xl, xc, + r = guess_gcc (xm, xl, xc, xv, xt, c_po, x_po, c_co, x_co, c_lo, x_lo, move (gr)); break; } case compiler_type::clang: { - r = guess_clang (xl, xc, + r = guess_clang (xm, xl, xc, xv, xt, c_po, x_po, c_co, x_co, c_lo, x_lo, move (gr)); break; } case compiler_type::msvc: { - r = guess_msvc (xl, xc, + r = guess_msvc (xm, xl, xc, xv, xt, c_po, x_po, c_co, x_co, c_lo, x_lo, move (gr)); break; } case compiler_type::icc: { - r = guess_icc (xl, xc, + r = guess_icc (xm, xl, xc, xv, xt, c_po, x_po, c_co, x_co, c_lo, x_lo, move (gr)); break; @@ -1645,29 +1826,36 @@ namespace build2 } path - guess_default (lang xl, const string& c, const string& pat) + guess_default (lang xl, const string& cid, const string& pat) { + compiler_id id (cid); const char* s (nullptr); + using type = compiler_type; + switch (xl) { case lang::c: { - if (c == "gcc") s = "gcc"; - else if (c == "clang") s = "clang"; - else if (c == "clang-apple") s = "clang"; - else if (c == "icc") s = "icc"; - else if (c == "msvc") s = "cl"; + switch (id.type) + { + case type::gcc: s = "gcc"; break; + case type::clang: s = "clang"; break; + case type::icc: s = "icc"; break; + case type::msvc: s = "cl"; break; + } break; } case lang::cxx: { - if (c == "gcc") s = "g++"; - else if (c == "clang") s = "clang++"; - else if (c == "clang-apple") s = "clang++"; - else if (c == "icc") s = "icpc"; - else if (c == "msvc") s = "cl"; + switch (id.type) + { + case type::gcc: s = "g++"; break; + case type::clang: s = "clang++"; break; + case type::icc: s = "icpc"; break; + case type::msvc: s = "cl"; break; + } break; } diff --git a/build2/cc/guess.hxx b/build2/cc/guess.hxx index 8b10b29..5fa4de7 100644 --- a/build2/cc/guess.hxx +++ b/build2/cc/guess.hxx @@ -66,7 +66,8 @@ namespace build2 compiler_id (compiler_type t, std::string v) : type (t), variant (move (v)) {} - //compiler_id (const std::string& type, std::string variant); + explicit + compiler_id (const std::string&); }; inline ostream& @@ -222,14 +223,17 @@ namespace build2 // that most of it will be the same, at least for C and C++. // const compiler_info& - guess (lang, - const string& xv, // Override variable (config.x) for diagnostics. - const path& xc, + guess (const char* xm, // Module (for variable names in diagnostics). + lang xl, // Language. + const path& xc, // Compiler path. + const string* xi, // Compiler id (optional). + const string* xv, // Compiler version (optional). + const string* xt, // Compiler target (optional). const strings* c_poptions, const strings* x_poptions, const strings* c_coptions, const strings* x_coptions, const strings* c_loptions, const strings* x_loptions); - // Given a language, toolchain id, and optionally (empty) a pattern, + // Given a language, compiler id, and optionally an (empty) pattern, // return an appropriate default compiler path. // // For example, for (lang::cxx, gcc, *-4.9) we will get g++-4.9. diff --git a/build2/cc/module.cxx b/build2/cc/module.cxx index 363f1d0..5ef1f2a 100644 --- a/build2/cc/module.cxx +++ b/build2/cc/module.cxx @@ -115,16 +115,20 @@ namespace build2 // Figure out which compiler we are dealing with, its target, etc. // - const path& xc (cast (*p.first)); - ci_ = &build2::cc::guess (x_lang, - config_x.name, - xc, - cast_null (rs[config_c_poptions]), - cast_null (rs[config_x_poptions]), - cast_null (rs[config_c_coptions]), - cast_null (rs[config_x_coptions]), - cast_null (rs[config_c_loptions]), - cast_null (rs[config_x_loptions])); + ci_ = &build2::cc::guess ( + x, + x_lang, + cast (*p.first), + cast_null (config::omitted (rs, config_x_id).first), + cast_null (config::omitted (rs, config_x_version).first), + cast_null (config::omitted (rs, config_x_target).first), + cast_null (rs[config_c_poptions]), + cast_null (rs[config_x_poptions]), + cast_null (rs[config_c_coptions]), + cast_null (rs[config_x_coptions]), + cast_null (rs[config_c_loptions]), + cast_null (rs[config_x_loptions])); + const compiler_info& ci (*ci_); // Split/canonicalize the target. First see if the user asked us to @@ -139,7 +143,7 @@ namespace build2 ct = run (3, ops.config_sub (), ci.target.c_str (), - [] (string& l) {return move (l);}); + [] (string& l, bool) {return move (l);}); l5 ([&]{trace << "config.sub target: '" << ct << "'";}); } diff --git a/build2/context.cxx b/build2/context.cxx index a8cf042..066e620 100644 --- a/build2/context.cxx +++ b/build2/context.cxx @@ -498,7 +498,9 @@ namespace build2 // string orig ( ops.config_guess_specified () - ? run (3, ops.config_guess (), [](string& l) {return move (l);}) + ? run (3, + ops.config_guess (), + [](string& l, bool) {return move (l);}) : BUILD2_HOST_TRIPLET); l5 ([&]{trace << "original host: '" << orig << "'";}); diff --git a/build2/cxx/init.cxx b/build2/cxx/init.cxx index 80a7b66..84b7bb6 100644 --- a/build2/cxx/init.cxx +++ b/build2/cxx/init.cxx @@ -366,6 +366,9 @@ namespace build2 // Note: some overridable, some not. // v.insert ("config.cxx", true), + v.insert ("config.cxx.id", true), + v.insert ("config.cxx.version", true), + v.insert ("config.cxx.target", true), v.insert ("config.cxx.poptions", true), v.insert ("config.cxx.coptions", true), v.insert ("config.cxx.loptions", true), diff --git a/build2/utility.hxx b/build2/utility.hxx index cc34d59..b75cb5a 100644 --- a/build2/utility.hxx +++ b/build2/utility.hxx @@ -280,7 +280,9 @@ namespace build2 // // The predicate can move the value out of the passed string but, if error // is false, only in case of a "content match" (so that any diagnostics - // lines are left intact). + // lines are left intact). The function signature should be: + // + // T (string& line, bool last) // // If ignore_exit is true, then the program's exist status is ignored (if it // is false and the program exits with the non-zero status, then an empty T diff --git a/build2/utility.txx b/build2/utility.txx index 1f65b76..73bd47a 100644 --- a/build2/utility.txx +++ b/build2/utility.txx @@ -79,9 +79,13 @@ namespace build2 { ifdstream is (move (pr.in_ofd), butl::fdstream_mode::skip); - while (is.peek () != ifdstream::traits_type::eof () && // Keep last line. - getline (is, l)) + // Make sure we keep the last line. + // + for (bool last (is.peek () == ifdstream::traits_type::eof ()); + !last && getline (is, l); ) { + last = (is.peek () == ifdstream::traits_type::eof ()); + trim (l); if (checksum != nullptr) @@ -89,7 +93,7 @@ namespace build2 if (r.empty ()) { - r = f (l); + r = f (l, last); if (!r.empty () && checksum == nullptr) break; diff --git a/build2/version/snapshot-git.cxx b/build2/version/snapshot-git.cxx index 278c5ce..3f19e62 100644 --- a/build2/version/snapshot-git.cxx +++ b/build2/version/snapshot-git.cxx @@ -31,9 +31,10 @@ namespace build2 // { const char* args[] {"git", "-C", d, "status", "--porcelain", nullptr}; - r.committed = run (3 /* verbosity */, - args, - [](string& s) {return move (s);}).empty (); + r.committed = run ( + 3 /* verbosity */, + args, + [](string& s, bool) {return move (s);}).empty (); } // Now extract the commit id and date. One might think that would be -- cgit v1.1