From 0fd7815cbc6557811df4f1b6ffb40461474b8534 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 12 Aug 2016 12:46:21 +0200 Subject: Implement c/cxx toolchain cross-hinting --- build2/bin/module.cxx | 71 +++++++++++--------------- build2/cc/guess | 16 +++++- build2/cc/guess.cxx | 137 +++++++++++++++++++++++++++++++++++++++++--------- build2/cc/init.cxx | 3 +- build2/cc/module.cxx | 96 ++++++++++++++++++++++++----------- build2/utility | 7 +++ build2/utility.cxx | 15 ++++++ 7 files changed, 248 insertions(+), 97 deletions(-) diff --git a/build2/bin/module.cxx b/build2/bin/module.cxx index 6d9cda7..3a3f612 100644 --- a/build2/bin/module.cxx +++ b/build2/bin/module.cxx @@ -370,26 +370,6 @@ namespace build2 return true; } - // Apply the specified stem to the config.bin.pattern. If there is no - // pattern, then return the stem itself. Assume the pattern is valid, - // i.e., contains single '*'. - // - static string - apply (const lookup& pattern, const char* stem) - { - if (!pattern) - return stem; - - const string& p (cast (pattern)); - size_t i (p.find ('*')); - assert (i != string::npos); - - string r (p, 0, i++); - r.append (stem); - r.append (p, i, p.size () - i); - return r; - } - bool ar_config_init (scope& r, scope& b, @@ -435,7 +415,6 @@ namespace build2 // toolchain can be target-unprefixed. Also, without canonicalization, // comparing targets will be unreliable. // - auto pattern (r["bin.pattern"]); // Use the target to decide on the default binutils program names. // @@ -446,17 +425,21 @@ namespace build2 // changes, say, the C++ compiler (which hinted the pattern), then // ar will automatically change as well. // - auto ap (config::required (r, - "config.bin.ar", - path (apply (pattern, ar_d)), - false, - config::save_commented)); - - auto rp (config::required (r, - "config.bin.ranlib", - nullptr, - false, - config::save_commented)); + auto ap ( + config::required ( + r, + "config.bin.ar", + path (apply_pattern (ar_d, cast_null (r["bin.pattern"]))), + false, + config::save_commented)); + + auto rp ( + config::required ( + r, + "config.bin.ranlib", + nullptr, + false, + config::save_commented)); const path& ar (cast (ap.first)); const path* ranlib (cast_null (rp.first)); @@ -566,11 +549,13 @@ namespace build2 const string& tsys (cast (r["bin.target.system"])); const char* ld_d (tsys == "win32-msvc" ? "link" : "ld"); - auto p (config::required (r, - "config.bin.ld", - path (apply (r["bin.pattern"], ld_d)), - false, - config::save_commented)); + auto p ( + config::required ( + r, + "config.bin.ld", + path (apply_pattern (ld_d, cast_null (r["bin.pattern"]))), + false, + config::save_commented)); const path& ld (cast (p.first)); ld_info ldi (guess_ld (ld)); @@ -668,11 +653,13 @@ namespace build2 const string& tsys (cast (r["bin.target.system"])); const char* rc_d (tsys == "win32-msvc" ? "rc" : "windres"); - auto p (config::required (r, - "config.bin.rc", - path (apply (r["bin.pattern"], rc_d)), - false, - config::save_commented)); + auto p ( + config::required ( + r, + "config.bin.rc", + path (apply_pattern (rc_d, cast_null (r["bin.pattern"]))), + false, + config::save_commented)); const path& rc (cast (p.first)); rc_info rci (guess_rc (rc)); diff --git a/build2/cc/guess b/build2/cc/guess index 977e081..d852a5c 100644 --- a/build2/cc/guess +++ b/build2/cc/guess @@ -96,7 +96,10 @@ namespace build2 // unlike all the preceding fields, this one takes into account the // compile options (e.g., -m32). // - // The pattern is the toolchain program pattern that could sometimes be + // The cc_pattern is the toolchain program pattern that could sometimes be + // derived for some toolchains. For example, i686-w64-mingw32-*-4.9. + // + // The bin_pattern is the binutils program pattern that could sometimes be // derived for some toolchains. For example, i686-w64-mingw32-*. // struct compiler_info @@ -106,7 +109,8 @@ namespace build2 string signature; string checksum; string target; - string pattern; + string cc_pattern; + string bin_pattern; }; // In a sense this is analagous to the language standard which we handle @@ -119,6 +123,14 @@ namespace build2 const path& xc, const strings* c_coptions, const strings* x_coptions); + + // Given a language, toolchain id, and optionally a pattern, return an + // appropriate default compiler path. + // + // For example, for (lang::cxx, gcc, *-4.9) we will get g++-4.9. + // + path + guess_default (lang, const string& cid, const string* pattern); } } diff --git a/build2/cc/guess.cxx b/build2/cc/guess.cxx index d80dddd..5001879 100644 --- a/build2/cc/guess.cxx +++ b/build2/cc/guess.cxx @@ -4,7 +4,7 @@ #include -#include // strlen() +#include // strlen(), strchr() #include @@ -299,8 +299,55 @@ namespace build2 return r; } + // Try to derive the toolchain pattern. + // + // The s argument is the stem to look for in the leaf of the path. The ls + // and rs arguments are the left/right separator characters. If either is + // NULL, then the stem should be the prefix/suffix of the leaf, + // respectively. Note that a path that is equal to stem is not considered + // a pattern. + // + static string + pattern (const path& xc, + const char* s, + const char* ls = "-_.", + const char* rs = "-_.") + { + string r; + size_t sn (strlen (s)); + + if (xc.size () > sn) + { + string l (xc.leaf ().string ()); + size_t ln (l.size ()); + + size_t b; + if (ln >= sn && (b = l.find (s)) != string::npos) + { + // Check left separators. + // + if (b == 0 || (ls != nullptr && strchr (ls, l[b - 1]) != nullptr)) + { + // Check right separators. + // + size_t e (b + sn); + if (e == ln || (rs != nullptr && strchr (rs, l[e]) != nullptr)) + { + l.replace (b, sn, "*", 1); + path p (xc.directory ()); + p /= l; + r = move (p).string (); + } + } + } + } + + return r; + } + + static compiler_info - guess_gcc (lang, + guess_gcc (lang xl, const path& xc, const strings* c_coptions, const strings* x_coptions, @@ -408,17 +455,25 @@ namespace build2 fail << "unable to extract target architecture from " << xc << " -print-multiarch or -dumpmachine output"; + // Derive the toolchain pattern. Try cc/c++ as a fallback. + // + string pat (pattern (xc, xl == lang::c ? "gcc" : "g++")); + + if (pat.empty ()) + pat = pattern (xc, xl == lang::c ? "cc" : "c++"); + return compiler_info { move (gr.id), move (v), move (gr.signature), move (gr.checksum), // Calculated on whole -v output. move (t), + move (pat), string ()}; } static compiler_info - guess_clang (lang, + guess_clang (lang xl, const path& xc, const strings* c_coptions, const strings* x_coptions, @@ -512,12 +567,24 @@ namespace build2 fail << "unable to extract target architecture from " << xc << " -dumpmachine output"; + // Derive the toolchain pattern. Try clang/clang++, the gcc/g++ alias, + // as well as cc/c++. + // + string pat (pattern (xc, xl == lang::c ? "clang" : "clang++")); + + if (pat.empty ()) + pat = pattern (xc, xl == lang::c ? "gcc" : "g++"); + + if (pat.empty ()) + pat = pattern (xc, xl == lang::c ? "cc" : "c++"); + return compiler_info { move (gr.id), move (v), move (gr.signature), move (gr.checksum), // Calculated on whole -v output. move (t), + move (pat), string ()}; } @@ -722,6 +789,10 @@ namespace build2 arch.append (t, p, string::npos); + // Derive the toolchain pattern. + // + string pat (pattern (xc, xl == lang::c ? "icc" : "icpc")); + // Use the signature line to generate the checksum. // sha256 cs (s); @@ -732,6 +803,7 @@ namespace build2 move (gr.signature), cs.string (), move (arch), + move (pat), string ()}; } @@ -931,24 +1003,8 @@ namespace build2 // then replace it with '*' and use it as a pattern for lib, link, // etc. // - string pat; - - if (xc.size () > 2) - { - const string& l (xc.leaf ().string ()); - size_t n (l.size ()); - - if (n >= 2 && - (l[0] == 'c' || l[0] == 'C') && - (l[1] == 'l' || l[1] == 'L') && - (n == 2 || l[2] == '.' || l[2] == '-')) - { - path p (xc.directory ()); - p /= "*"; - p += l.c_str () + 2; - pat = move (p).string (); - } - } + string cpat (pattern (xc, "cl", nullptr, ".-")); + string bpat (cpat); // Binutils pattern is the same as toolchain. // Use the signature line to generate the checksum. // @@ -960,7 +1016,8 @@ namespace build2 move (gr.signature), cs.string (), move (arch), - move (pat)}; + move (cpat), + move (bpat)}; } compiler_info @@ -1018,7 +1075,7 @@ namespace build2 // Derive binutils pattern unless this has already been done by the // compiler-specific code. // - if (r.pattern.empty ()) + if (r.bin_pattern.empty ()) { // When cross-compiling the whole toolchain is normally prefixed with // the target triplet, e.g., x86_64-w64-mingw32-{gcc,g++,ar,ld}. @@ -1041,12 +1098,44 @@ namespace build2 path p (xc.directory ()); p /= t; p += "-*"; - r.pattern = move (p).string (); + r.bin_pattern = move (p).string (); } } } return r; } + + path + guess_default (lang xl, const string& c, const string* pat) + { + const char* s (nullptr); + + 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"; + + 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"; + + break; + } + } + + return path (apply_pattern (s, pat)); + } } } diff --git a/build2/cc/init.cxx b/build2/cc/init.cxx index 2623c79..80e5027 100644 --- a/build2/cc/init.cxx +++ b/build2/cc/init.cxx @@ -167,7 +167,8 @@ namespace build2 if (first) { h.assign ("config.bin.target") = cast (r["cc.target"]); - if (auto l = r["cc.pattern"]) + + if (auto l = hints["config.bin.pattern"]) h.assign ("config.bin.pattern") = cast (l); } diff --git a/build2/cc/module.cxx b/build2/cc/module.cxx index 3a7dad2..4b51905 100644 --- a/build2/cc/module.cxx +++ b/build2/cc/module.cxx @@ -35,9 +35,11 @@ namespace build2 { tracer trace (x, "config_init"); + bool cc_loaded (cast_false (b["cc.config.loaded"])); + // Configure. // - string pattern; // Toolchain pattern. + compiler_info ci; // For program patterns. if (first) { @@ -45,33 +47,69 @@ namespace build2 // config.x // - auto p (config::required (r, config_x, path (x_default))); + + // Normally we will have a persistent configuration and computing the + // default value every time will be a waste. So try without a default + // first. + // + auto p (config::required (r, config_x)); + + if (p.first == nullptr) + { + // If someone already loaded cc.config then use its toolchain id + // and (optional) pattern to guess an appropriate default (e.g., + // for {gcc, *-4.9} we will get g++-4.9). + // + path d (cc_loaded + ? guess_default (x_lang, + cast (r["cc.id"]), + cast_null (r["cc.pattern"])) + : path (x_default)); + + auto p1 (config::required (r, config_x, d)); + p.first = &p1.first.get (); + p.second = p1.second; + } // Figure out which compiler we are dealing with, its target, etc. // - const path& xc (cast (p.first)); - compiler_info ci ( - guess (x_lang, - xc, - cast_null (r[config_c_coptions]), - cast_null (r[config_x_coptions]))); + const path& xc (cast (*p.first)); + ci = guess (x_lang, + xc, + cast_null (r[config_c_coptions]), + cast_null (r[config_x_coptions])); // If this is a new value (e.g., we are configuring), then print the // report at verbosity level 2 and up (-v). // if (verb >= (p.second ? 2 : 3)) { - text << x << ' ' << project (r) << '@' << r.out_path () << '\n' + diag_record dr (text); + + { + dr << x << ' ' << project (r) << '@' << r.out_path () << '\n' << " " << left << setw (11) << x << xc << '\n' << " id " << ci.id << '\n' << " version " << ci.version.string << '\n' << " major " << ci.version.major << '\n' << " minor " << ci.version.minor << '\n' - << " patch " << ci.version.patch << '\n' - << " build " << ci.version.build << '\n' - << " signature " << ci.signature << '\n' - << " checksum " << ci.checksum << '\n' - << " target " << ci.target; + << " patch " << ci.version.patch << '\n'; + } + + if (!ci.version.build.empty ()) + dr << " build " << ci.version.build << '\n'; + + { + dr << " signature " << ci.signature << '\n' + << " target " << ci.target << '\n'; + } + + if (!ci.cc_pattern.empty ()) // bin_pattern printed by bin + dr << " pattern " << ci.cc_pattern << '\n'; + + { + dr << " checksum " << ci.checksum; + } } r.assign (x_id) = ci.id.string (); @@ -87,8 +125,6 @@ namespace build2 r.assign (x_signature) = move (ci.signature); r.assign (x_checksum) = move (ci.checksum); - pattern = move (ci.pattern); - // Split/canonicalize the target. First see if the user asked us to // use config.sub. // @@ -158,7 +194,7 @@ namespace build2 // Load cc.config. // - if (!cast_false (b["cc.config.loaded"])) + if (!cc_loaded) { // Prepare configuration hints. They are only used on the first load // of cc.config so we only populate them on our first load. @@ -168,8 +204,12 @@ namespace build2 { h.assign ("config.cc.id") = cast (r[x_id]); h.assign ("config.cc.target") = cast (r[x_target]); - if (!pattern.empty ()) - h.assign ("config.cc.pattern") = move (pattern); + + if (!ci.cc_pattern.empty ()) + h.assign ("config.cc.pattern") = move (ci.cc_pattern); + + if (!ci.bin_pattern.empty ()) + h.assign ("config.bin.pattern") = move (ci.bin_pattern); } load_module ("cc.config", r, b, loc, false, h); @@ -179,24 +219,24 @@ namespace build2 // If cc.config is already loaded, verify its configuration matched // ours since it could have been loaded by another c-family module. // - auto check = [&r, &loc, this](const char* cv, - const variable& xv, + auto check = [&r, &loc, this](const char* cvar, + const variable& xvar, const char* w) { - const string& c (cast (r[cv])); - const string& x (cast (r[xv])); + const string& cv (cast (r[cvar])); + const string& xv (cast (r[xvar])); - if (c != x) + if (cv != xv) fail (loc) << "cc and " << x << " module " << w << " mismatch" << - info << cv << " is " << c << - info << xv.name << " is " << x; + info << cvar << " is " << cv << + info << xvar.name << " is " << xv; }; // Note that we don't require that patterns match. Presumably, if the // toolchain id and target are the same, then where exactly the tools - // (e.g., ar) come from doesn't really matter. + // come from doesn't really matter. // - check ("cc.id", x_id, "toolchain id"); + check ("cc.id", x_id, "toolchain"); check ("cc.target", x_target, "target"); } } diff --git a/build2/utility b/build2/utility index 3ecceed..a636c01 100644 --- a/build2/utility +++ b/build2/utility @@ -278,6 +278,13 @@ namespace build2 const cstrings&, bool = false); + // Apply the specified substitution (stem) to a '*'-pattern. If pattern + // is NULL, then return the stem itself. Assume the pattern is valid, + // i.e., contains a single '*' character. + // + string + apply_pattern (const char* stem, const string* pattern); + // Parse version string in the X.Y.Z[-{a|b}N] to a version integer in the // AABBCCDD form, where: // diff --git a/build2/utility.cxx b/build2/utility.cxx index 971c1fb..3eda7e1 100644 --- a/build2/utility.cxx +++ b/build2/utility.cxx @@ -287,6 +287,21 @@ namespace build2 return false; } + string + apply_pattern (const char* s, const string* p) + { + if (p == nullptr) + return s; + + size_t i (p->find ('*')); + assert (i != string::npos); + + string r (*p, 0, i++); + r.append (s); + r.append (*p, i, p->size () - i); + return r; + } + unsigned int to_version (const string& s) { -- cgit v1.1