From db8336a686a85f0e458acb2d5f1ad442585bfc9a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 1 Oct 2021 11:05:49 +0200 Subject: Add notion of internal scope, translate external -I to -isystem or equivalent --- libbuild2/cc/common.hxx | 15 +++ libbuild2/cc/common.ixx | 34 ++++++ libbuild2/cc/compile-rule.cxx | 272 +++++++++++++++++++++++++++++++++++++----- libbuild2/cc/compile-rule.hxx | 4 +- libbuild2/cc/guess.cxx | 2 +- libbuild2/cc/init.cxx | 20 ++++ libbuild2/cc/module.cxx | 125 +++++++++++++++++-- libbuild2/cc/module.hxx | 5 + libbuild2/cc/msvc.cxx | 15 ++- libbuild2/cc/pkgconfig.cxx | 5 +- 10 files changed, 446 insertions(+), 51 deletions(-) create mode 100644 libbuild2/cc/common.ixx (limited to 'libbuild2/cc') diff --git a/libbuild2/cc/common.hxx b/libbuild2/cc/common.hxx index 64de228..a3cd6b6 100644 --- a/libbuild2/cc/common.hxx +++ b/libbuild2/cc/common.hxx @@ -64,6 +64,7 @@ namespace build2 const variable& config_x_loptions; const variable& config_x_aoptions; const variable& config_x_libs; + const variable& config_x_internal_scope; const variable* config_x_translate_include; const variable& x_path; // Compiler process path. @@ -79,6 +80,7 @@ namespace build2 const variable& x_loptions; const variable& x_aoptions; const variable& x_libs; + const variable& x_internal_scope; const variable* x_translate_include; const variable& c_poptions; // cc.* @@ -163,6 +165,8 @@ namespace build2 compiler_class cclass; // x.class uint64_t cmaj; // x.version.major uint64_t cmin; // x.version.minor + uint64_t cvmaj; // x.variant_version.major (0 if no variant) + uint64_t cvmin; // x.variant_version.minor (0 if no variant) const process_path& cpath; // x.path const strings& cmode; // x.mode (options) @@ -175,6 +179,12 @@ namespace build2 bool modules; // x.features.modules bool symexport; // x.features.symexport + const string* internal_scope; // x.internal.scope + const scope* internal_scope_current; + + const scope* + effective_internal_scope (const scope& bs) const; + build2::cc::importable_headers* importable_headers; // The order of sys_*_dirs is the mode entries first, followed by the @@ -229,12 +239,14 @@ namespace build2 const string& cv, compiler_class cl, uint64_t mj, uint64_t mi, + uint64_t vmj, uint64_t vmi, const process_path& path, const strings& mode, const target_triplet& tgt, const string& env_cs, bool fm, bool fs, + const string* ints, const scope* intsc, const dir_paths& sld, const dir_paths& shd, const dir_paths* smd, @@ -251,11 +263,13 @@ namespace build2 x_uninstall (uninstall), ctype (ct), cvariant (cv), cclass (cl), cmaj (mj), cmin (mi), + cvmaj (vmj), cvmin (vmi), cpath (path), cmode (mode), ctgt (tgt), tsys (ctgt.system), tclass (ctgt.class_), env_checksum (env_cs), modules (fm), symexport (fs), + internal_scope (ints), internal_scope_current (intsc), importable_headers (nullptr), sys_lib_dirs (sld), sys_hdr_dirs (shd), sys_mod_dirs (smd), sys_lib_dirs_mode (slm), sys_hdr_dirs_mode (shm), @@ -412,6 +426,7 @@ namespace build2 } } +#include #include #endif // LIBBUILD2_CC_COMMON_HXX diff --git a/libbuild2/cc/common.ixx b/libbuild2/cc/common.ixx new file mode 100644 index 0000000..ce28890 --- /dev/null +++ b/libbuild2/cc/common.ixx @@ -0,0 +1,34 @@ +// file : libbuild2/cc/common.ixx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + namespace cc + { + inline const scope* data:: + effective_internal_scope (const scope& bs) const + { + if (internal_scope == nullptr) + return nullptr; + else + { + const string& s (*internal_scope); + + if (s == "current") + return internal_scope_current; + else if (s == "base") + return &bs; + else if (s == "root") + return bs.root_scope (); + else if (s == "bundle") + return bs.bundle_scope (); + else if (s == "strong") + return bs.strong_scope (); + else if (s == "weak") + return bs.weak_scope (); + else + return nullptr; + } + } + } +} diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index 278f0cc..7636722 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -4,7 +4,7 @@ #include #include // exit() -#include // strlen(), strchr() +#include // strlen(), strchr(), strncmp() #include #include @@ -175,6 +175,42 @@ namespace build2 throw invalid_argument ("invalid preprocessed value '" + s + "'"); } + // Return true if the compiler supports -isystem (GCC class) or + // /external:I (MSVC class). + // + static inline bool + isystem (const data& d) + { + switch (d.cclass) + { + case compiler_class::gcc: + { + return true; + } + case compiler_class::msvc: + { + if (d.cvariant.empty ()) + { + // While /external:I is available since 15.6, it required + // /experimental:external (and was rather buggy) until 16.10. + // + return d.cmaj > 19 || (d.cmaj == 19 && d.cmin >= 29); + } + else if (d.cvariant != "clang") + { + // clang-cl added support for /external:I (by translating it to + // -isystem) in version 13. + // + return d.cvmaj >= 13; + } + else + return false; + } + } + + return false; + } + optional compile_rule:: find_system_header (const path& f) const { @@ -230,13 +266,15 @@ namespace build2 auto m (sys_hdr_dirs.begin () + sys_hdr_dirs_extra); auto e (sys_hdr_dirs.end ()); - // Note: starting from 15.6, MSVC gained /external:I option though it + // Note: starting from 16.10, MSVC gained /external:I option though it // doesn't seem to affect the order, only "system-ness". // append_option_values ( args, cclass == compiler_class::gcc ? "-idirafter" : - cclass == compiler_class::msvc ? "/I" : "-I", + cclass == compiler_class::msvc ? (isystem (*this) + ? "/external:I" + : "/I") : "-I", m, e, [] (const dir_path& d) {return d.string ().c_str ();}); @@ -244,6 +282,9 @@ namespace build2 // add all of them. But we want extras to come first. Note also that // clang-cl takes care of this itself. // + // Note also that we don't use /external:I to have consistent semantics + // with when INCLUDE is set (there is separate /external:env for that). + // if (ctype == compiler_type::msvc && cvariant != "clang") { if (!getenv ("INCLUDE")) @@ -418,6 +459,7 @@ namespace build2 void compile_rule:: append_library_options (appended_libraries& ls, T& args, const scope& bs, + const scope* is, // Internal scope. action a, const file& l, bool la, linfo li, library_cache* lib_cache) const { @@ -425,7 +467,8 @@ namespace build2 { appended_libraries& ls; T& args; - } d {ls, args}; + const scope* is; + } d {ls, args, is}; // See through utility libraries. // @@ -458,7 +501,118 @@ namespace build2 ? x_export_poptions : l.ctx.var_pool[t + ".export.poptions"])); - append_options (d.args, l, var); + if (const strings* ops = cast_null (l[var])) + { + for (auto i (ops->begin ()), e (ops->end ()); i != e; ++i) + { + const string& o (*i); + + // If enabled, remap -I to -isystem or /external:I for paths that + // are outside of the internal scope. + // + if (d.is != nullptr) + { + // See if this is -I or -I (or /I... for MSVC). + // + // While strictly speaking we can only attempt to recognize + // options until we hit something unknown (after that, we don't + // know what's an option and what's a value), it doesn't seem + // likely to cause issues here, where we only expect to see -I, + // -D, and -U. + // + bool msvc (cclass == compiler_class::msvc); + + if ((o[0] == '-' || (msvc && o[0] == '/')) && o[1] == 'I') + { + bool sep (o.size () == 2); // -I vs -I + + const char* v (nullptr); + size_t vn (0); + if (sep) + { + if (i + 1 == e) + ; // Append as is and let the compiler complain. + else + { + ++i; + v = i->c_str (); + vn = i->size (); + } + } + else + { + v = o.c_str () + 2; + vn = o.size () - 2; + } + + if (v != nullptr) + { + // See if we need to translate the option for this path. We + // only do this for absolute paths and try to optimize for + // the already normalized ones. + // + if (path_traits::absolute (v)) + { + const char* p (nullptr); + size_t pn (0); + + dir_path nd; + if (path_traits::normalized (v, vn, true /* separators */)) + { + p = v; + pn = vn; + } + else + try + { + nd = dir_path (v, vn); + nd.normalize (); + p = nd.string ().c_str (); + pn = nd.string ().size (); + } + catch (const invalid_path&) + { + // Ignore this path. + } + + if (p != nullptr) + { + auto sub = [p, pn] (const dir_path& d) + { + return path_traits::sub ( + p, pn, + d.string ().c_str (), d.string ().size ()); + }; + + // Translate if it's neither in src nor in out of the + // internal scope. + // + if (!sub (d.is->src_path ()) && + (d.is->out_eq_src () || !sub (d.is->out_path ()))) + { + // Note: must use original value (path is temporary). + // + append_option (d.args, + msvc ? "/external:I" : "-isystem"); + append_option (d.args, v); + continue; + } + } + } + + // If not translated, preserve the original form. + // + append_option (d.args, o.c_str ()); + if (sep) append_option (d.args, v); + + continue; + } + } + } + + append_option (d.args, o.c_str ()); + } + } // From the process_libraries() semantics we know that the final call // is always for the common options. @@ -479,7 +633,11 @@ namespace build2 const scope& bs, action a, const file& l, bool la, linfo li) const { - append_library_options (ls, args, bs, a, l, la, li, nullptr); + const scope* is (isystem (*this) + ? effective_internal_scope (bs) + : nullptr); + + append_library_options (ls, args, bs, is, a, l, la, li, nullptr); } template @@ -488,6 +646,14 @@ namespace build2 const scope& bs, action a, const target& t, linfo li) const { + auto internal_scope = [this, &bs, is = optional ()] () mutable + { + if (!is) + is = isystem (*this) ? effective_internal_scope (bs) : nullptr; + + return *is; + }; + appended_libraries ls; library_cache lc; @@ -509,7 +675,11 @@ namespace build2 (la = (f = pt->is_a ())) || ( (f = pt->is_a ()))) { - append_library_options (ls, args, bs, a, *f, la, li, &lc); + append_library_options (ls, + args, + bs, internal_scope (), + a, *f, la, li, + &lc); } } } @@ -1452,12 +1622,17 @@ namespace build2 for (auto i (v.begin ()), e (v.end ()); i != e; ++i) { - // -I can either be in the "-Ifoo" or "-I foo" form. For VC it can - // also be /I. - // const string& o (*i); - if (o.size () < 2 || (o[0] != '-' && o[0] != '/') || o[1] != 'I') + // -I can either be in the "-Ifoo" or "-I foo" form. For MSVC it + // can also be /I. + // + // Note that we naturally assume that -isystem, /external:I, etc., + // are not relevant here. + // + bool msvc (cclass == compiler_class::msvc); + + if (!((o[0] == '-' || (msvc && o[0] == '/')) && o[1] == 'I')) continue; dir_path d; @@ -3487,27 +3662,43 @@ namespace build2 for (auto i (args.begin ()), e (args.end ()); i != e; ++i) { + const char* o (*i); + // -I can either be in the "-Ifoo" or "-I foo" form. For VC it // can also be /I. // - const char* o (*i); - size_t n (strlen (o)); - - if (n < 2 || (o[0] != '-' && o[0] != '/') || o[1] != 'I') + // Note also that append_library_options() may have translated + // -I to -isystem or /external:I so we have to recognize those + // as well. + // { - s = nullptr; - continue; - } + bool msvc (cclass == compiler_class::msvc); - if (n == 2) - { - if (++i == e) - break; // Let the compiler complain. + size_t p (0); + if (o[0] == '-' || (msvc && o[0] == '/')) + { + p = (o[1] == 'I' ? 2 : + !msvc && strncmp (o + 1, "isystem", 7) == 0 ? 8 : + msvc && strncmp (o + 1, "external:I", 10) == 0 ? 11 : 0); + } + + if (p == 0) + { + s = nullptr; + continue; + } + + size_t n (strlen (o)); + if (n == p) + { + if (++i == e) + break; // Let the compiler complain. - ds = *i; + ds = *i; + } + else + ds.assign (o + p, n - p); } - else - ds.assign (o + 2, n - 2); if (!ds.empty ()) { @@ -3527,7 +3718,7 @@ namespace build2 if (!d.empty ()) { // Ignore any paths containing '.', '..' components. Allow - // any directory separators thought (think -I$src_root/foo + // any directory separators though (think -I$src_root/foo // on Windows). // if (d.absolute () && d.normalized (false)) @@ -3634,9 +3825,15 @@ namespace build2 append_options (args, cmode); append_sys_hdr_options (args); // Extra system header dirs (last). - // See perform_update() for details on overriding the default - // exceptions and runtime. + // See perform_update() for details on /external:W0, /EHsc, /MD. // + if (cvariant != "clang" && isystem (*this)) + { + if (find_option_prefix ("/external:I", args) && + !find_option_prefix ("/external:W", args)) + args.push_back ("/external:W0"); + } + if (x_lang == lang::cxx && !find_option_prefix ("/EH", args)) args.push_back ("/EHsc"); @@ -4950,6 +5147,15 @@ namespace build2 append_options (args, cmode); append_sys_hdr_options (args); + // See perform_update() for details on /external:W0, /EHsc, /MD. + // + if (cvariant != "clang" && isystem (*this)) + { + if (find_option_prefix ("/external:I", args) && + !find_option_prefix ("/external:W", args)) + args.push_back ("/external:W0"); + } + if (x_lang == lang::cxx && !find_option_prefix ("/EH", args)) args.push_back ("/EHsc"); @@ -6849,6 +7055,16 @@ namespace build2 if (md.pp != preprocessed::all) append_sys_hdr_options (args); // Extra system header dirs (last). + // If we have any /external:I options but no /external:Wn, then add + // /external:W0 to emulate the -isystem semantics. + // + if (cvariant != "clang" && isystem (*this)) + { + if (find_option_prefix ("/external:I", args) && + !find_option_prefix ("/external:W", args)) + args.push_back ("/external:W0"); + } + // While we want to keep the low-level build as "pure" as possible, // the two misguided defaults, C++ exceptions and runtime, just have // to be fixed. Otherwise the default build is pretty much unusable. diff --git a/libbuild2/cc/compile-rule.hxx b/libbuild2/cc/compile-rule.hxx index d65089e..daea600 100644 --- a/libbuild2/cc/compile-rule.hxx +++ b/libbuild2/cc/compile-rule.hxx @@ -36,7 +36,8 @@ namespace build2 size_t copied; // First copied-over bmi*{}, 0 if none. }; - class LIBBUILD2_CC_SYMEXPORT compile_rule: public simple_rule, virtual common + class LIBBUILD2_CC_SYMEXPORT compile_rule: public simple_rule, + virtual common { public: compile_rule (data&&); @@ -80,6 +81,7 @@ namespace build2 void append_library_options (appended_libraries&, T&, const scope&, + const scope*, action, const file&, bool, linfo, library_cache*) const; diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx index 69e8219..098dc86 100644 --- a/libbuild2/cc/guess.cxx +++ b/libbuild2/cc/guess.cxx @@ -1478,7 +1478,7 @@ namespace build2 { dir_paths r; - // Extract /I paths from the compiler mode. + // Extract /I paths and similar from the compiler mode. // msvc_extract_header_search_dirs (mo, r); size_t rn (r.size ()); diff --git a/libbuild2/cc/init.cxx b/libbuild2/cc/init.cxx index 07f082f..769f6bb 100644 --- a/libbuild2/cc/init.cxx +++ b/libbuild2/cc/init.cxx @@ -97,12 +97,14 @@ namespace build2 vp.insert ("config.cc.loptions"); vp.insert ("config.cc.aoptions"); vp.insert ("config.cc.libs"); + vp.insert ("config.cc.internal.scope"); vp.insert ("cc.poptions"); vp.insert ("cc.coptions"); vp.insert ("cc.loptions"); vp.insert ("cc.aoptions"); vp.insert ("cc.libs"); + vp.insert ("cc.internal.scope"); vp.insert ("cc.export.poptions"); vp.insert ("cc.export.coptions"); @@ -298,6 +300,24 @@ namespace build2 rs.assign ("cc.libs") += cast_null ( lookup_config (rs, "config.cc.libs", nullptr)); + // config.cc.internal.scope + // + // Note: save omitted. + // + if (lookup l = lookup_config (rs, "config.cc.internal.scope")) + { + if (cast (l) == "current") + fail << "'current' value in config.cc.internal.scope"; + + // This is necessary in case we are acting as bundle amalgamation. + // + rs.assign ("cc.internal.scope") = *l; + } + + // config.cc.reprocess + // + // Note: save omitted. + // if (lookup l = lookup_config (rs, "config.cc.reprocess")) rs.assign ("cc.reprocess") = *l; diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx index 959d315..117c8c9 100644 --- a/libbuild2/cc/module.cxx +++ b/libbuild2/cc/module.cxx @@ -372,6 +372,18 @@ namespace build2 const compiler_info& xi (*x_info); const target_triplet& tt (cast (rs[x_target])); + // Load cc.core.config. + // + if (!cast_false (rs["cc.core.config.loaded"])) + { + variable_map h (rs.ctx); + + if (!xi.bin_pattern.empty ()) + h.assign ("config.bin.pattern") = xi.bin_pattern; + + init_module (rs, rs, "cc.core.config", loc, false, h); + } + // Configuration. // using config::lookup_config; @@ -441,6 +453,97 @@ namespace build2 translate_std (xi, tt, rs, mode, v); } + // config.x.internal.scope + // + // Note: save omitted. + // + // The effective internal_scope value is chosen based on the following + // priority list: + // + // 1. config.x.internal.scope + // + // 2. config.cc.internal.scope + // + // 3. effective value from bundle amalgamation + // + // 4. x.internal.scope + // + // 5. cc.internal.scope + // + // Note also that we only update x.internal.scope (and not cc.*) to + // reflect the effective value. + // + { + if (lookup l = lookup_config (rs, config_x_internal_scope)) // 1 + { + internal_scope = &cast (l); + + if (*internal_scope == "current") + fail << "'current' value in " << config_x_internal_scope; + } + else if (lookup l = rs["config.cc.internal.scope"]) // 2 + { + internal_scope = &cast (l); + } + else // 3 + { + const scope& as (*rs.bundle_scope ()); + + if (as != rs) + { + // Only use the value if the corresponding module is loaded. + // + bool xl (cast_false (as[string (x) + ".config.loaded"])); + if (xl) + internal_scope = cast_null (as[x_internal_scope]); + + if (internal_scope == nullptr) + { + if (xl || cast_false (as["cc.core.config.loaded"])) + internal_scope = cast_null (as["cc.internal.scope"]); + } + + if (internal_scope != nullptr && *internal_scope == "current") + internal_scope_current = &as; + } + } + + lookup l; + if (internal_scope == nullptr) + { + internal_scope = cast_null (l = rs[x_internal_scope]); // 4 + + if (internal_scope == nullptr) + internal_scope = cast_null (rs["cc.internal.scope"]); // 5 + } + + if (internal_scope != nullptr) + { + const string& s (*internal_scope); + + // Assign effective. + // + if (!l) + rs.assign (x_internal_scope) = s; + + if (s == "current") + { + if (internal_scope_current == nullptr) + internal_scope_current = &rs; + } + else if (s == "base" || + s == "root" || + s == "bundle" || + s == "strong" || + s == "weak") + ; + else if (s == "global") + internal_scope = nullptr; // Nothing to translate; + else + fail << "invalid " << x_internal_scope << " value '" << s << "'"; + } + } + // config.x.translate_include // // It's still fuzzy whether specifying (or maybe tweaking) this list in @@ -686,6 +789,16 @@ namespace build2 auto& incs (hdr_dirs.first); auto& libs (lib_dirs.first); + if (verb >= 3 && internal_scope != nullptr) + { + dr << "\n int scope "; + + if (*internal_scope == "current") + dr << internal_scope_current->out_path (); + else + dr << *internal_scope; + } + if (verb >= 3 && !mods.empty ()) { dr << "\n mod dirs"; @@ -723,18 +836,6 @@ namespace build2 config::save_environment (rs, xi.compiler_environment); config::save_environment (rs, xi.platform_environment); - - // Load cc.core.config. - // - if (!cast_false (rs["cc.core.config.loaded"])) - { - variable_map h (rs.ctx); - - if (!xi.bin_pattern.empty ()) - h.assign ("config.bin.pattern") = xi.bin_pattern; - - init_module (rs, rs, "cc.core.config", loc, false, h); - } } // Global cache of ad hoc importable headers. diff --git a/libbuild2/cc/module.hxx b/libbuild2/cc/module.hxx index e21fb9e..5c68482 100644 --- a/libbuild2/cc/module.hxx +++ b/libbuild2/cc/module.hxx @@ -61,6 +61,11 @@ namespace build2 string env_checksum; // Environment checksum (also in x.path). + // Cached x.internal.scope value. + // + const string* internal_scope = nullptr; + const scope* internal_scope_current = nullptr; + // Temporary storage for data::sys_*_dirs_*. // size_t sys_lib_dirs_mode; diff --git a/libbuild2/cc/msvc.cxx b/libbuild2/cc/msvc.cxx index 9e8ae18..f95cab0 100644 --- a/libbuild2/cc/msvc.cxx +++ b/libbuild2/cc/msvc.cxx @@ -233,12 +233,15 @@ namespace build2 dir_path d; try { - // -I can either be in the "-Ifoo" or "-I foo" form. For VC it can - // also be /I. + // -I can either be in the "-Ifoo" or "-I foo" form. For MSVC it can + // also be /I. And from 16.10 it can also be /external:I. // - if (o.size () > 1 && (o[0] == '-' || o[0] == '/') && o[1] == 'I') + size_t p; + if ((o[0] == '-' || o[0] == '/') && + (p = (o[1] == 'I' ? 2 : + o.compare (1, 10, "external:I") == 0 ? 11 : 0)) != 0) { - if (o.size () == 2) + if (o.size () == p) { if (++i == e) break; // Let the compiler complain. @@ -246,7 +249,7 @@ namespace build2 d = dir_path (*i); } else - d = dir_path (o, 2, string::npos); + d = dir_path (o, p, string::npos); } else continue; @@ -331,7 +334,7 @@ namespace build2 // see guess). Note that this is not used for Clang targeting MSVC (but // is for clang-cl). - // Extract -I paths from the compiler mode. + // Extract /I and similar paths from the compiler mode. // dir_paths r; msvc_extract_header_search_dirs (cast (rs[x_mode]), r); diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx index fd2cd07..7b4f86d 100644 --- a/libbuild2/cc/pkgconfig.cxx +++ b/libbuild2/cc/pkgconfig.cxx @@ -1525,13 +1525,12 @@ namespace build2 for (auto i (v->begin ()); i != v->end (); ++i) { const string& o (*i); - size_t n (o.size ()); // Filter out -I (both -I and -I forms). // - if (n >= 2 && o[0] == '-' && o[1] == 'I') + if (o[0] == '-' && o[1] == 'I') { - if (n == 2) + if (o.size () == 2) ++i; continue; -- cgit v1.1