From 9fa5f73d00905568e8979d0c93ec4a8f645c81d5 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 9 Aug 2016 11:31:53 +0200 Subject: Implement support for C compilation We now have two new modules: cc (c-common) and c. --- build2/cxx/module.cxx | 505 ++++++++++++++++++-------------------------------- 1 file changed, 184 insertions(+), 321 deletions(-) (limited to 'build2/cxx/module.cxx') diff --git a/build2/cxx/module.cxx b/build2/cxx/module.cxx index f66ef53..fd98114 100644 --- a/build2/cxx/module.cxx +++ b/build2/cxx/module.cxx @@ -4,23 +4,13 @@ #include -#include - #include #include #include -#include -#include - -#include +#include -#include -#include #include -#include -#include -#include using namespace std; using namespace butl; @@ -29,355 +19,228 @@ namespace build2 { namespace cxx { - bool - init (scope& r, - scope& b, - const location& loc, - unique_ptr&, - bool first, - bool, - const variable_map& config_hints) - { - tracer trace ("cxx::init"); - l5 ([&]{trace << "for " << b.out_path ();}); - - // Enter module variables. - // - if (first) - { - auto& v (var_pool); + using cc::config_module; - // Note: some overridable, some not. - // - v.insert ("config.cxx", true); - v.insert ("config.cxx.poptions", true); - v.insert ("config.cxx.coptions", true); - v.insert ("config.cxx.loptions", true); - v.insert ("config.cxx.libs", true); - - v.insert ("cxx.poptions"); - v.insert ("cxx.coptions"); - v.insert ("cxx.loptions"); - v.insert ("cxx.libs"); - - v.insert ("cxx.export.poptions"); - v.insert ("cxx.export.coptions"); - v.insert ("cxx.export.loptions"); - v.insert ("cxx.export.libs"); - - v.insert ("cxx.std", true); - } - - // Configure. - // - - assert (config_hints.empty ()); // We don't known any hints. - - // config.cxx.{p,c,l}options - // config.cxx.libs - // - // These are optional. We also merge them into the corresponding - // cxx.* variables. - // - // The merging part gets a bit tricky if this module has already - // been loaded in one of the outer scopes. By doing the straight - // append we would just be repeating the same options over and - // over. So what we are going to do is only append to a value if - // it came from this scope. Then the usage for merging becomes: - // - // cxx.coptions = # Note: '='. - // using cxx - // cxx.coptions += # Note: '+='. - // - b.assign ("cxx.poptions") += cast_null ( - config::optional (r, "config.cxx.poptions")); - - b.assign ("cxx.coptions") += cast_null ( - config::optional (r, "config.cxx.coptions")); - - b.assign ("cxx.loptions") += cast_null ( - config::optional (r, "config.cxx.loptions")); + class module: public cc::module + { + public: + explicit + module (data&& d): common (move (d)), cc::module (move (d)) {} - b.assign ("cxx.libs") += cast_null ( - config::optional (r, "config.cxx.libs")); + bool + translate_std (string&, scope&, const value&) const override; + }; - // Configuration hints for the bin module. They will only be used on the - // first loading of the bin module (for this project) so we only - // populate them on our first loading. - // - variable_map bin_hints; + bool module:: + translate_std (string& s, scope& r, const value& val) const + { + const string& v (cast (val)); - // config.cxx - // - if (first) + if (cid == "msvc") { - auto p (config::required (r, "config.cxx", path ("g++"))); - - // Figure out which compiler we are dealing with, its target, etc. + // C++ standard-wise, with VC you get what you get. The question is + // whether we should verify that the requested standard is provided by + // this VC version. And if so, from which version should we say VC + // supports 11, 14, and 17? We should probably be as loose as possible + // here since the author will always be able to tighten (but not + // loosen) this in the buildfile (i.e., detect unsupported versions). // - const path& cxx (cast (p.first)); - compiler_info ci (guess (cxx, cast_null (r["cxx.coptions"]))); - - // If this is a new value (e.g., we are configuring), then print the - // report at verbosity level 2 and up (-v). + // For now we are not going to bother doing this for C++03. // - if (verb >= (p.second ? 2 : 3)) + if (v != "98" && v != "03") { - text << "cxx " << project (r) << '@' << r.out_path () << '\n' - << " cxx " << cxx << '\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; - } - - r.assign ("cxx.id") = ci.id.string (); - r.assign ("cxx.id.type") = move (ci.id.type); - r.assign ("cxx.id.variant") = move (ci.id.variant); - - r.assign ("cxx.version") = move (ci.version.string); - r.assign ("cxx.version.major") = ci.version.major; - r.assign ("cxx.version.minor") = ci.version.minor; - r.assign ("cxx.version.patch") = ci.version.patch; - r.assign ("cxx.version.build") = move (ci.version.build); + uint64_t cver (cast (r[x_version_major])); - r.assign ("cxx.signature") = move (ci.signature); - r.assign ("cxx.checksum") = move (ci.checksum); - - // While we still have the original, compiler-reported target, see if - // we can derive a binutils program pattern. - // - // BTW, for GCC we also get gcc-{ar,ranlib} which add support for the - // LTO plugin though it seems more recent GNU binutils (2.25) are able - // to load the plugin when needed automatically. So it doesn't seem we - // should bother trying to support this on our end (the way we could - // do it is by passing config.bin.{ar,ranlib} as hints). - // - string pattern; - - if (cast (r["cxx.id"]) == "msvc") - { - // If the compiler name is/starts with 'cl' (e.g., cl.exe, cl-14), - // then replace it with '*' and use it as a pattern for lib, link, - // etc. + // @@ Is mapping for 14 and 17 correct? Maybe Update 2 for 14? // - if (cxx.size () > 2) + if ((v == "11" && cver < 16) || // C++11 since VS2010/10.0. + (v == "14" && cver < 19) || // C++14 since VS2015/14.0. + (v == "17" && cver < 20)) // C++17 since VS20??/15.0. { - const string& l (cxx.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 (cxx.directory ()); - p /= "*"; - p += l.c_str () + 2; - pattern = move (p).string (); - } + fail << "C++" << v << " is not supported by " + << cast (r[x_signature]) << + info << "required by " << project (r) << '@' << r.out_path (); } } + + return false; + } + else + { + // Translate 11 to 0x, 14 to 1y, and 17 to 1z for compatibility with + // older versions of the compilers. + // + s = "-std="; + + if (v == "98") + s += "c++98"; + else if (v == "03") + s += "c++03"; + else if (v == "11") + s += "c++0x"; + else if (v == "14") + s += "c++1y"; + else if (v == "17") + s += "c++1z"; else - { - // When cross-compiling the whole toolchain is normally prefixed - // with the target triplet, e.g., x86_64-w64-mingw32-{g++,ar,ld}. - // - const string& t (ci.target); - size_t n (t.size ()); + s += v; // In case the user specifies something like 'gnu++17'. - if (cxx.size () > n + 1) - { - const string& l (cxx.leaf ().string ()); - - if (l.size () > n + 1 && l.compare (0, n, t) == 0 && l[n] == '-') - { - path p (cxx.directory ()); - p /= t; - p += "-*"; - pattern = move (p).string (); - } - } - } + return true; + } + } - if (!pattern.empty ()) - bin_hints.assign ("config.bin.pattern") = move (pattern); + bool + config_init (scope& r, + scope& b, + const location& loc, + unique_ptr& m, + bool first, + bool, + const variable_map& hints) + { + tracer trace ("cxx::config_init"); + l5 ([&]{trace << "for " << b.out_path ();}); - // Split/canonicalize the target. + if (first) + { + // Load cc.vars so that we can cache all the cc.* variables. // + if (!cast_false (b["cc.vars.loaded"])) + load_module ("cc.vars", r, b, loc); - // Did the user ask us to use config.sub? + // Enter all the variables and initialize the module data. // - if (ops.config_sub_specified ()) - { - ci.target = run (ops.config_sub (), - ci.target.c_str (), - [] (string& l) {return move (l);}); - l5 ([&]{trace << "config.sub target: '" << ci.target << "'";}); - } - - try - { - string canon; - triplet t (ci.target, canon); + auto& v (var_pool); - l5 ([&]{trace << "canonical target: '" << canon << "'; " - << "class: " << t.class_;}); + cc::config_data d { + cc::lang::cxx, - // Pass the target we extracted from the C++ compiler as a config - // hint to the bin module. - // - bin_hints.assign ("config.bin.target") = canon; + "cxx", + "c++", + "g++", - // Enter as cxx.target.{cpu,vendor,system,version,class}. + // Note: some overridable, some not. // - r.assign ("cxx.target") = move (canon); - r.assign ("cxx.target.cpu") = move (t.cpu); - r.assign ("cxx.target.vendor") = move (t.vendor); - r.assign ("cxx.target.system") = move (t.system); - r.assign ("cxx.target.version") = move (t.version); - r.assign ("cxx.target.class") = move (t.class_); - } - catch (const invalid_argument& e) - { - // This is where we suggest that the user specifies --config-sub to - // help us out. - // - fail << "unable to parse compiler target '" << ci.target << "': " - << e.what () << - info << "consider using the --config-sub option"; - } - } - - const string& cid (cast (r["cxx.id"])); - const string& tsys (cast (r["cxx.target.system"])); - - // Initialize the bin module. Only do this if it hasn't already been - // loaded so that we don't overwrite user's bin.* settings. - // - if (!cast_false (b["bin.loaded"])) - load_module ("bin", r, b, loc, false, bin_hints); - - // Verify bin's target matches ours. - // - { - const string& bt (cast (r["bin.target"])); - const string& ct (cast (r["cxx.target"])); - - if (bt != ct) - fail (loc) << "bin and cxx module target platform mismatch" << - info << "bin.target is " << bt << - info << "cxx.target is " << ct; + v.insert ("config.cxx", true), + v.insert ("config.cxx.poptions", true), + v.insert ("config.cxx.coptions", true), + v.insert ("config.cxx.loptions", true), + v.insert ("config.cxx.libs", true), + + v.insert ("cxx.poptions"), + v.insert ("cxx.coptions"), + v.insert ("cxx.loptions"), + v.insert ("cxx.libs"), + + v["cc.poptions"], + v["cc.coptions"], + v["cc.loptions"], + v["cc.libs"], + + v.insert ("cxx.export.poptions"), + v.insert ("cxx.export.coptions"), + v.insert ("cxx.export.loptions"), + v.insert ("cxx.export.libs"), + + v["cc.export.poptions"], + v["cc.export.coptions"], + v["cc.export.loptions"], + v["cc.export.libs"], + + v.insert ("cxx.std", true), + + v.insert ("cxx.id"), + v.insert ("cxx.id.type"), + v.insert ("cxx.id.variant"), + + v.insert ("cxx.version"), + v.insert ("cxx.version.major"), + v.insert ("cxx.version.minor"), + v.insert ("cxx.version.patch"), + v.insert ("cxx.version.build"), + + v.insert ("cxx.signature"), + v.insert ("cxx.checksum"), + + v.insert ("cxx.target"), + v.insert ("cxx.target.cpu"), + v.insert ("cxx.target.vendor"), + v.insert ("cxx.target.system"), + v.insert ("cxx.target.version"), + v.insert ("cxx.target.class") + }; + + assert (m == nullptr); + m.reset (new config_module (move (d))); } - // Load the bin.ar module unless we were asked to only build shared - // libraries. - // - if (auto l = r["config.bin.lib"]) - { - if (cast (l) != "shared") - { - if (!cast_false (b["bin.ar.loaded"])) - load_module ("bin.ar", r, b, loc, false, bin_hints); - } - } + static_cast (*m).init (r, b, loc, first, hints); + return true; + } - // In the VC world you link things directly with link.exe so load the - // bin.ld module. - // - if (cid == "msvc") - { - if (!cast_false (b["bin.ld.loaded"])) - load_module ("bin.ld", r, b, loc, false, bin_hints); - } + static const target_type* hdr[] = + { + &hxx::static_type, + &ixx::static_type, + &txx::static_type, + &h::static_type, + nullptr + }; + + static const target_type* inc[] = + { + &hxx::static_type, + &ixx::static_type, + &txx::static_type, + &cxx::static_type, + &h::static_type, + &c::static_type, + nullptr + }; - // If our target is MinGW, then we will need the resource compiler - // (windres) in order to embed the manifest. - // - if (tsys == "mingw32") - { - if (!cast_false (b["bin.rc.loaded"])) - load_module ("bin.rc", r, b, loc, false, bin_hints); - } + bool + init (scope& r, + scope& b, + const location& loc, + unique_ptr& m, + bool first, + bool, + const variable_map& hints) + { + tracer trace ("cxx::init"); + l5 ([&]{trace << "for " << b.out_path ();}); - // Register target types. + // Load cxx.config. // - { - auto& t (b.target_types); - - t.insert (); - t.insert (); + if (!cast_false (b["cxx.config.loaded"])) + load_module ("cxx.config", r, b, loc, false, hints); - t.insert (); - t.insert (); - t.insert (); - t.insert (); - } - - // Register rules. - // + if (first) { - using namespace bin; - - auto& r (b.rules); - - // We register for configure so that we detect unresolved imports - // during configuration rather that later, e.g., during update. - // - // @@ Should we check if install module was loaded (see bin)? - // - - r.insert (perform_update_id, "cxx.compile", compile::instance); - r.insert (perform_clean_id, "cxx.compile", compile::instance); - r.insert (configure_update_id, "cxx.compile", compile::instance); + config_module& cm (*r.modules.lookup ("cxx.config")); - r.insert (perform_update_id, "cxx.link", link::instance); - r.insert (perform_clean_id, "cxx.link", link::instance); - r.insert (configure_update_id, "cxx.link", link::instance); + cc::data d { + cm, - r.insert (perform_install_id, "cxx.install", install::instance); + "cxx.compile", + "cxx.link", + "cxx.install", - // Only register static object/library rules if the bin.ar module is - // loaded (by us or by the user). - // - if (cast_false (b["bin.ar.loaded"])) - { - r.insert (perform_update_id, "cxx.compile", compile::instance); - r.insert (perform_clean_id, "cxx.compile", compile::instance); - r.insert (configure_update_id, "cxx.compile", compile::instance); + cast (r[cm.x_id]), + cast (r[cm.x_target]), + cast (r[cm.x_target_system]), + cast (r[cm.x_target_class]), - r.insert (perform_update_id, "cxx.link", link::instance); - r.insert (perform_clean_id, "cxx.link", link::instance); - r.insert (configure_update_id, "cxx.link", link::instance); + cxx::static_type, + hdr, + inc + }; - r.insert (perform_install_id, "cxx.install", install::instance); - } - - r.insert (perform_update_id, "cxx.compile", compile::instance); - r.insert (perform_clean_id, "cxx.compile", compile::instance); - r.insert (configure_update_id, "cxx.compile", compile::instance); - - r.insert (perform_update_id, "cxx.link", link::instance); - r.insert (perform_clean_id, "cxx.link", link::instance); - r.insert (configure_update_id, "cxx.link", link::instance); - - r.insert (perform_install_id, "cxx.install", install::instance); + assert (m == nullptr); + m.reset (new module (move (d))); } - // Configure "installability" of our target types. - // - using namespace install; - - install_path (b, dir_path ("include")); // Into install.include. - install_path (b, dir_path ("include")); - install_path (b, dir_path ("include")); - install_path (b, dir_path ("include")); - + static_cast (*m).init (r, b, loc, first, hints); return true; } } -- cgit v1.1