diff options
51 files changed, 2171 insertions, 692 deletions
diff --git a/build2/b.cxx b/build2/b.cxx index 9a924d6..ecf8c7f 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -1151,7 +1151,7 @@ main (int argc, char* argv[]) // use to the bootstrap files (other than src-root.build, which, // BTW, doesn't need to exist if src_root == out_root). // - scope& rs (create_root (*ctx, out_root, src_root)->second); + scope& rs (*create_root (*ctx, out_root, src_root)->second.scope); bool bstrapped (bootstrapped (rs)); @@ -1561,9 +1561,10 @@ main (int argc, char* argv[]) // If we have a directory, enter the scope, similar to how we do // it in the context ctor. // - scope& s (o.dir - ? sm.insert ((out_base / *o.dir).normalize ())->second - : *rs.weak_scope ()); + scope& s ( + o.dir + ? *sm.insert ((out_base / *o.dir).normalize ())->second.scope + : *rs.weak_scope ()); auto p (s.vars.insert (o.ovr)); @@ -1581,9 +1582,10 @@ main (int argc, char* argv[]) if (o.ovr.visibility == variable_visibility::global) continue; - scope& s (o.dir - ? sm.insert ((out_base / *o.dir).normalize ())->second - : rs); + scope& s ( + o.dir + ? *sm.insert ((out_base / *o.dir).normalize ())->second.scope + : rs); auto p (s.vars.insert (o.ovr)); diff --git a/doc/manual.cli b/doc/manual.cli index d7cf92f..e09aef8 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -6552,8 +6552,8 @@ config.cxx.aoptions config.cxx.libs cxx.libs -config.cxx.translatable_headers - cxx.translatable_headers +config.cxx.translate_include + cxx.translate_include \ diff --git a/libbuild2/bin/init.cxx b/libbuild2/bin/init.cxx index ff00e89..51066cb 100644 --- a/libbuild2/bin/init.cxx +++ b/libbuild2/bin/init.cxx @@ -3,8 +3,6 @@ #include <libbuild2/bin/init.hxx> -#include <map> - #include <libbuild2/scope.hxx> #include <libbuild2/function.hxx> #include <libbuild2/variable.hxx> @@ -144,8 +142,11 @@ namespace build2 vp.insert<string> ("bin.lib.load_suffix"); vp.insert<string> ("bin.lib.load_suffix_pattern"); - vp.insert<map<string, string>> ("bin.lib.version"); - vp.insert<string> ("bin.lib.version_pattern"); + // @@ TMP: update bdep-new generated projects, documentation not to use + // @ for platform-independent version. + // + vp.insert<map<optional<string>, string>> ("bin.lib.version"); + vp.insert<string> ("bin.lib.version_pattern"); return true; } diff --git a/libbuild2/c/init.cxx b/libbuild2/c/init.cxx index 227afb2..afd8f88 100644 --- a/libbuild2/c/init.cxx +++ b/libbuild2/c/init.cxx @@ -175,7 +175,7 @@ namespace build2 vp.insert<strings> ("config.c.loptions"), vp.insert<strings> ("config.c.aoptions"), vp.insert<strings> ("config.c.libs"), - nullptr /* config.c.translatable_headers */, + nullptr /* config.c.translate_include */, vp.insert<process_path_ex> ("c.path"), vp.insert<strings> ("c.mode"), @@ -192,7 +192,7 @@ namespace build2 vp.insert<strings> ("c.aoptions"), vp.insert<strings> ("c.libs"), - nullptr /* c.translatable_headers */, + nullptr /* c.translate_include */, vp["cc.poptions"], vp["cc.coptions"], @@ -220,6 +220,7 @@ namespace build2 vp["cc.type"], vp["cc.system"], vp["cc.module_name"], + vp["cc.importable"], vp["cc.reprocess"], vp.insert<string> ("c.preprocessed"), // See cxx.preprocessed. @@ -259,7 +260,8 @@ namespace build2 // Alias some cc. variables as c. // - vp.insert_alias (d.c_runtime, "c.runtime"); + vp.insert_alias (d.c_runtime, "c.runtime"); + vp.insert_alias (d.c_importable, "c.importable"); auto& m (extra.set_module (new config_module (move (d)))); m.guess (rs, loc, extra.hints); @@ -363,7 +365,7 @@ namespace build2 }; auto& m (extra.set_module (new module (move (d)))); - m.init (rs, loc, extra.hints); + m.init (rs, loc, extra.hints, *cm.x_info); return true; } diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx index 9cac1b0..f307b77 100644 --- a/libbuild2/cc/common.cxx +++ b/libbuild2/cc/common.cxx @@ -46,31 +46,42 @@ namespace build2 // The first argument to proc_lib is a pointer to the last element of an // array that contains the current library dependency chain all the way to // the library passed to process_libraries(). The first element of this - // array is NULL. + // array is NULL. If this argument is NULL, then this is a library without + // a target (e.g., -lpthread) and its name is in the second argument. + // + // If proc_impl always returns false (that is, we are only interested in + // interfaces), then top_li can be absent. This makes process_libraries() + // not to pick the liba/libs{} member for installed libraries instead + // passing the lib{} group itself. This can be used to match the semantics + // of file_rule which, when matching prerequisites, does not pick the + // liba/libs{} member (naturally) but just matches the lib{} group. + // + // Note that if top_li is present, then the target passed to proc_impl, + // proc_lib, and proc_opt is always a file. // void common:: process_libraries ( action a, const scope& top_bs, - linfo top_li, + optional<linfo> top_li, const dir_paths& top_sysd, - const file& l, + const mtime_target& l, // liba/libs{} or lib{} bool la, lflags lf, - const function<bool (const file&, + const function<bool (const target&, bool la)>& proc_impl, // Implementation? - const function<void (const file* const*, // Can be NULL. + const function<void (const target* const*, // Can be NULL. const string& path, // Library path. lflags, // Link flags. bool sys)>& proc_lib, // True if system library. - const function<void (const file&, + const function<void (const target&, const string& type, // cc.type bool com, // cc. or x. bool exp)>& proc_opt, // *.export. bool self /*= false*/, // Call proc_lib on l? - small_vector<const file*, 16>* chain) const + small_vector<const target*, 16>* chain) const { - small_vector<const file*, 16> chain_storage; + small_vector<const target*, 16> chain_storage; if (chain == nullptr) { chain = &chain_storage; @@ -193,7 +204,8 @@ namespace build2 // stub the path to the DLL may not be known and so the path will be // empty (but proc_lib() will use the import stub). // - const path& p (l.path ()); + const file* f; + const path& p ((f = l.is_a<file> ()) ? f->path () : empty_path); bool s (t != nullptr // If cc library (matched or imported). ? cast_false<bool> (l.vars[c_system]) @@ -203,7 +215,7 @@ namespace build2 } const scope& bs (t == nullptr || cc ? top_bs : l.base_scope ()); - optional<linfo> li; // Calculate lazily. + optional<optional<linfo>> li; // Calculate lazily. const dir_paths* sysd (nullptr); // Resolve lazily. // Find system search directories corresponding to this library, i.e., @@ -225,7 +237,7 @@ namespace build2 { li = (t == nullptr || cc) ? top_li - : link_info (bs, link_type (l).type); + : optional<linfo> (link_info (bs, link_type (l).type)); }; // Only go into prerequisites (implementation) if instructed and we are @@ -234,6 +246,8 @@ namespace build2 // if (impl && !c_e_libs.defined () && !x_e_libs.defined ()) { + assert (top_li); // Must pick a member if implementation (see above). + for (const prerequisite_target& pt: l.prerequisite_targets[a]) { // Note: adhoc prerequisites are not part of the library metadata @@ -303,7 +317,7 @@ namespace build2 { // This is something like -lpthread or shell32.lib so should be // a valid path. But it can also be an absolute library path - // (e.g., something that may come from our .static/shared.pc + // (e.g., something that may come from our .{static/shared}.pc // files). // if (proc_lib) @@ -316,7 +330,7 @@ namespace build2 if (sysd == nullptr) find_sysd (); if (!li) find_linfo (); - const file& t ( + const mtime_target& t ( resolve_library (a, bs, n, @@ -335,9 +349,22 @@ namespace build2 // on Windows import-installed DLLs may legally have empty // paths. // - if (t.mtime () == timestamp_unknown) + const char* w (nullptr); + if (t.ctx.phase == run_phase::match) + { + size_t o (t.state[a].task_count.load (memory_order_consume) - + t.ctx.count_base ()); + + if (o != target::offset_applied && + o != target::offset_executed) + w = "not matched"; + } + else if (t.mtime () == timestamp_unknown) + w = "out of date"; + + if (w != nullptr) fail << (impl ? "implementation" : "interface") - << " dependency " << t << " is out of date" << + << " dependency " << t << " is " << w << info << "mentioned in *.export." << (impl ? "imp_" : "") << "libs of target " << l << info << "is it a prerequisite of " << l << "?"; @@ -436,12 +463,16 @@ namespace build2 // will select exactly the same target as the library's matched rule and // that's the only way to guarantee it will be up-to-date. // - const file& common:: + // If li is absent, then don't pick the liba/libs{} member, returning the + // lib{} target itself. If li is present, then the returned target is + // always a file. + // + const mtime_target& common:: resolve_library (action a, const scope& s, const name& cn, const dir_path& out, - linfo li, + optional<linfo> li, const dir_paths& sysd, optional<dir_paths>& usrd) const { @@ -478,12 +509,16 @@ namespace build2 fail << "unable to find library " << pk; } - // If this is lib{}/libu*{}, pick appropriate member. + // If this is lib{}/libu*{}, pick appropriate member unless we were + // instructed not to. // - if (const libx* l = xt->is_a<libx> ()) - xt = link_member (*l, a, li); // Pick lib*{e,a,s}{}. + if (li) + { + if (const libx* l = xt->is_a<libx> ()) + xt = link_member (*l, a, *li); // Pick lib*{e,a,s}{}. + } - return xt->as<file> (); + return xt->as<mtime_target> (); } // Note that pk's scope should not be NULL (even if dir is absolute). @@ -855,6 +890,24 @@ namespace build2 return l; }; + // Mark as a "cc" library (unless already marked) and set the system + // flag. + // + auto mark_cc = [sys, this] (target& t) -> bool + { + auto p (t.vars.insert (c_type)); + + if (p.second) + { + p.first = string ("cc"); + + if (sys) + t.vars.assign (c_system) = true; + } + + return p.second; + }; + target_lock ll (lock (lt)); // Set lib{} group members to indicate what's available. Note that we @@ -866,6 +919,11 @@ namespace build2 { if (s != nullptr) {lt->s = s; mt = s->mtime ();} if (a != nullptr) {lt->a = a; mt = a->mtime ();} + + // Mark the group since sometimes we use it itself instead of one of + // the liba/libs{} members (see process_libraries() for details). + // + mark_cc (*lt); } target_lock al (lock (a)); @@ -877,24 +935,6 @@ namespace build2 if (a != nullptr) a->group = lt; if (s != nullptr) s->group = lt; - // Mark as a "cc" library (unless already marked) and set the system - // flag. - // - auto mark_cc = [sys, this] (target& t) -> bool - { - auto p (t.vars.insert (c_type)); - - if (p.second) - { - p.first.get () = string ("cc"); - - if (sys) - t.vars.assign (c_system) = true; - } - - return p.second; - }; - // If the library already has cc.type, then assume it was either // already imported or was matched by a rule. // @@ -938,7 +978,7 @@ namespace build2 strings o; o.push_back (move (d)); - p.first.get () = move (o); + p.first = move (o); } } }; @@ -950,6 +990,10 @@ namespace build2 // idea is that in .pc files that we generate, we copy those macros // (or custom ones) from *.export.poptions. // + // @@ Should we add .pc files as ad hoc members so pkconfig_save() can + // use their names when deriving -l-names (this would be expecially + // helpful for binless libraries to get hold of prefix/suffix, etc). + // if (pc.first.empty () && pc.second.empty ()) { if (!pkgconfig_load (act, *p.scope, diff --git a/libbuild2/cc/common.hxx b/libbuild2/cc/common.hxx index f072591..612d081 100644 --- a/libbuild2/cc/common.hxx +++ b/libbuild2/cc/common.hxx @@ -64,7 +64,7 @@ namespace build2 const variable& config_x_loptions; const variable& config_x_aoptions; const variable& config_x_libs; - const variable* config_x_translatable_headers; + const variable* config_x_translate_include; const variable& x_path; // Compiler process path. const variable& x_mode; // Compiler mode options. @@ -79,7 +79,7 @@ namespace build2 const variable& x_loptions; const variable& x_aoptions; const variable& x_libs; - const variable* x_translatable_headers; + const variable* x_translate_include; const variable& c_poptions; // cc.* const variable& c_coptions; @@ -107,6 +107,7 @@ namespace build2 const variable& c_type; // cc.type const variable& c_system; // cc.system const variable& c_module_name; // cc.module_name + const variable& c_importable; // cc.importable const variable& c_reprocess; // cc.reprocess const variable& x_preprocessed; // x.preprocessed @@ -172,8 +173,7 @@ namespace build2 bool modules; // x.features.modules bool symexport; // x.features.symexport - const strings* xlate_hdr; // x.translatable_headers (NULL if - // unused/empty). + build2::cc::importable_headers* importable_headers; // The order of sys_*_dirs is the mode entries first, followed by the // compiler built-in entries, and finished off with any extra entries @@ -252,7 +252,7 @@ namespace build2 ctgt (tgt), tsys (ctgt.system), tclass (ctgt.class_), modules (fm), symexport (fs), - xlate_hdr (nullptr), + importable_headers (nullptr), sys_lib_dirs (sld), sys_inc_dirs (sid), sys_mod_dirs (smd), sys_lib_dirs_mode (slm), sys_inc_dirs_mode (sim), sys_mod_dirs_mode (smm), @@ -272,16 +272,16 @@ namespace build2 process_libraries ( action, const scope&, - linfo, + optional<linfo>, const dir_paths&, - const file&, + const mtime_target&, bool, lflags, - const function<bool (const file&, bool)>&, - const function<void (const file* const*, const string&, lflags, bool)>&, - const function<void (const file&, const string&, bool, bool)>&, + const function<bool (const target&, bool)>&, + const function<void (const target* const*, const string&, lflags, bool)>&, + const function<void (const target&, const string&, bool, bool)>&, bool = false, - small_vector<const file*, 16>* = nullptr) const; + small_vector<const target*, 16>* = nullptr) const; const target* search_library (action a, @@ -308,12 +308,12 @@ namespace build2 } public: - const file& + const mtime_target& resolve_library (action, const scope&, const name&, const dir_path&, - linfo, + optional<linfo>, const dir_paths&, optional<dir_paths>&) const; diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index b96c39d..116f67b 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -413,11 +413,13 @@ namespace build2 // See through utility libraries. // - auto imp = [] (const file& l, bool la) {return la && l.is_a<libux> ();}; + auto imp = [] (const target& l, bool la) {return la && l.is_a<libux> ();}; - auto opt = [&d, this] (const file& l, + auto opt = [&d, this] (const target& lt, const string& t, bool com, bool exp) { + const file& l (lt.as<file> ()); + // Note that in our model *.export.poptions are always "interface", // even if set on liba{}/libs{}, unlike loptions. // @@ -450,7 +452,7 @@ namespace build2 }; process_libraries (a, bs, li, sys_lib_dirs, - l, la, 0, // Hack: lflags unused. + l, la, 0, // lflags unused. imp, nullptr, opt); } @@ -504,10 +506,10 @@ namespace build2 target& t, linfo li) const { - auto imp = [] (const file& l, bool la) {return la && l.is_a<libux> ();}; + auto imp = [] (const target& l, bool la) {return la && l.is_a<libux> ();}; auto opt = [&m, this] ( - const file& l, const string& t, bool com, bool exp) + const target& l, const string& t, bool com, bool exp) { if (!exp) return; @@ -524,8 +526,8 @@ namespace build2 // The same logic as in append_library_options(). // - const function<bool (const file&, bool)> impf (imp); - const function<void (const file&, const string&, bool, bool)> optf (opt); + const function<bool (const target&, bool)> impf (imp); + const function<void (const target&, const string&, bool, bool)> optf (opt); for (prerequisite_member p: group_prerequisite_members (a, t)) { @@ -544,7 +546,7 @@ namespace build2 continue; process_libraries (a, bs, li, sys_lib_dirs, - pt->as<file> (), la, 0, // Hack: lflags unused. + pt->as<file> (), la, 0, // lflags unused. impf, nullptr, optf); } } @@ -759,8 +761,8 @@ namespace build2 continue; // A dependency on a library is there so that we can get its - // *.export.poptions, modules, etc. This is the library metadata - // protocol. See also append_library_options(). + // *.export.poptions, modules, importable headers, etc. This is the + // library metadata protocol. See also append_library_options(). // if (pi == include_type::normal && (p.is_a<libx> () || @@ -773,8 +775,8 @@ namespace build2 // Handle (phase two) imported libraries. We know that for such // libraries we don't need to do match() in order to get options // (if any, they would be set by search_library()). But we do need - // to match it if we may need its modules (see search_modules() - // for details). + // to match it if we may need its modules or importable headers + // (see search_modules(), make_header_sidebuild() for details). // if (p.proj ()) { @@ -943,9 +945,30 @@ namespace build2 if (ut == unit_type::module_intf) // Note: still unrefined. cs.append (&md.symexport, sizeof (md.symexport)); - if (xlate_hdr != nullptr) - append_options (cs, *xlate_hdr); - + // If we track translate_include then we should probably also track + // the cc.importable flag for each header we include, which would be + // quite heavy-handed indeed. Or maybe we shouldn't bother with this + // at all: after all include translation is an optimization so why + // rebuild an otherwise up-to-date target? + // +#if 0 + if (modules) + { + // While there is also the companion importable_headers map, it's + // unlikely to change in a way that affects us without changes to + // other things that we track (e.g., compiler version, etc). + // + if (const auto* v = cast_null<translatable_headers> ( + t[x_translate_include])) + { + for (const auto& p: *v) + { + cs.append (p.first); + cs.append (!p.second || *p.second); + } + } + } +#endif if (md.pp != preprocessed::all) { append_options (cs, t, x_poptions); @@ -1813,6 +1836,10 @@ namespace build2 size_t header_units = 0; // Number of header units imported. module_imports& imports; // Unused (potentially duplicate suppression). + // Include translation (looked up lazily). + // + optional<const build2::cc::translatable_headers*> translatable_headers; + small_vector<string, 2> batch; // Reuse buffers. module_mapper_state (size_t s, module_imports& i) @@ -2094,21 +2121,84 @@ namespace build2 // Now handle INCLUDE and IMPORT differences. // - const string& hp (ht->path ().string ()); + const path& hp (ht->path ()); + const string& hs (hp.string ()); // Reduce include translation to the import case. // - if (!imp && xlate_hdr != nullptr) + if (!imp) { - auto i (lower_bound ( - xlate_hdr->begin (), xlate_hdr->end (), - hp, - [] (const string& x, const string& y) - { - return path_traits::compare (x, y) < 0; - })); + if (!st.translatable_headers) + st.translatable_headers = + cast_null<translatable_headers> (t[x_translate_include]); + + if (*st.translatable_headers != nullptr) + { + auto& ths (**st.translatable_headers); + + // First look for the header path in the translatable headers + // itself. + // + auto i (ths.find (hs)), ie (ths.end ()); + + // Next look it up in the importable headers and then look up + // the associated groups in the translatable headers. + // + if (i == ie) + { + slock l (importable_headers->mutex); + auto& ihs (importable_headers->header_map); + + auto j (ihs.find (hp)), je (ihs.end ()); + + if (j != je) + { + // The groups are ordered from the most to least specific. + // + for (const string& g: j->second) + if ((i = ths.find (g)) != ie) + break; + } + + // Finally look for the `all` groups. + // + if (i == ie) + { + i = ths.find (header_group_all_importable); + + if (i != ie) + { + // See if this header is marked as importable. + // + if (lookup l = (*ht)[c_importable]) + { + if (!cast<bool> (l)) + i = ie; + } + else if (j != je) + { + // See if this is one of ad hoc *-importable groups + // (currently only std-importable). + // + const auto& gs (j->second); + if (find (gs.begin (), + gs.end (), + header_group_std_importable) == gs.end ()) + i = ie; + } + else + i = ie; + } - imp = (i != xlate_hdr->end () && *i == hp); + if (i == ie) + i = ths.find (header_group_all); + } + } + + // Translate if we found an entry and it's not false. + // + imp = (i != ie && (!i->second || *i->second)); + } } if (imp) @@ -2118,7 +2208,7 @@ namespace build2 // Synthesize the BMI dependency then update and add the BMI // target as a prerequisite. // - const file& bt (make_header_sidebuild (a, bs, li, *ht)); + const file& bt (make_header_sidebuild (a, bs, t, li, *ht)); if (!skip) { @@ -2151,7 +2241,8 @@ namespace build2 } catch (const failed&) { - r = "ERROR 'unable to update header unit "; r += hp; r += '\''; + r = "ERROR 'unable to update header unit for "; + r += hs; r += '\''; continue; } } @@ -2160,7 +2251,7 @@ namespace build2 if (skip) st.skip--; else - dd.expect (hp); + dd.expect (hs); // Confusingly, TRUE means include textually and FALSE means we // don't know. @@ -2611,7 +2702,7 @@ namespace build2 // Synthesize the BMI dependency then update and add the BMI // target as a prerequisite. // - const file& bt (make_header_sidebuild (a, bs, li, *ht)); + const file& bt (make_header_sidebuild (a, bs, t, li, *ht)); if (!skip) { @@ -2722,18 +2813,11 @@ namespace build2 if (!e.empty ()) n.resize (n.size () - e.size () - 1); // One for the dot. - // See if this directory is part of any project out_root hierarchy and - // if so determine the target type. + // See if this directory is part of any project and if so determine + // the target type. // - // Note that this will miss all the headers that come from src_root - // (so they will be treated as generic C headers below). Generally, we - // don't have the ability to determine that some file belongs to - // src_root of some project. But that's not a problem for our - // purposes: it is only important for us to accurately determine - // target types for headers that could be auto-generated. - // - // While at it also try to determine if this target is from the src or - // out tree of said project. + // While at it also determine if this target is from the src or out + // tree of said project. // dir_path out; @@ -2749,7 +2833,7 @@ namespace build2 { tts = map_extension (bs, n, e); - if (bs.out_path () != bs.src_path () && d.sub (bs.src_path ())) + if (!bs.out_eq_src () && d.sub (bs.src_path ())) out = out_src (d, *rs); } @@ -2925,82 +3009,12 @@ namespace build2 } else { - // We used to just normalize the path but that could result in an - // invalid path (e.g., for some system/compiler headers on CentOS 7 - // with Clang 3.4) because of the symlinks (if a directory component - // is a symlink, then any following `..` are resolved relative to the - // target; see path::normalize() for background). - // - // Initially, to fix this, we realized (i.e., realpath(3)) it instead. - // But that turned out also not to be quite right since now we have - // all the symlinks resolved: conceptually it feels correct to keep - // the original header names since that's how the user chose to - // arrange things and practically this is how the compilers see/report - // them (e.g., the GCC module mapper). - // - // So now we have a pretty elaborate scheme where we try to use the - // normalized path if possible and fallback to realized. Normalized - // paths will work for situations where `..` does not cross symlink - // boundaries, which is the sane case. And for the insane case we only - // really care about out-of-project files (i.e., system/compiler - // headers). In other words, if you have the insane case inside your - // project, then you are on your own. - // - // All of this is unless the path comes from the depdb, in which case + // Normalize the path unless it comes from the depdb, in which case // we've already done that (normally). This is also where we handle // src-out remap (again, not needed if cached). // if (!cache || norm) - { - // Interestingly, on most paltforms and with most compilers (Clang - // on Linux being a notable exception) most system/compiler headers - // are already normalized. - // - path_abnormality a (f.abnormalities ()); - if (a != path_abnormality::none) - { - // While we can reasonably expect this path to exit, things do go - // south from time to time (like compiling under wine with file - // wlantypes.h included as WlanTypes.h). - // - try - { - // If we have any parent components, then we have to verify the - // normalized path matches realized. - // - path r; - if ((a & path_abnormality::parent) == path_abnormality::parent) - { - r = f; - r.realize (); - } - - try - { - f.normalize (); - - // Note that we might still need to resolve symlinks in the - // normalized path. - // - if (!r.empty () && f != r && path (f).realize () != r) - f = move (r); - } - catch (const invalid_path&) - { - assert (!r.empty ()); // Shouldn't have failed if no `..`. - f = move (r); // Fallback to realize. - } - } - catch (const invalid_path&) - { - fail << "invalid header path '" << f.string () << "'"; - } - catch (const system_error& e) - { - fail << "invalid header path '" << f.string () << "': " << e; - } - } - } + normalize_header (f); if (!cache) { @@ -3927,7 +3941,7 @@ namespace build2 // if (inject_header (a, t, *ht, mt, false /* fail */)) { - const file& bt (make_header_sidebuild (a, bs, li, *ht)); + const file& bt (make_header_sidebuild (a, bs, t, li, *ht)); // It doesn't look like we need the cache semantics here since given // the header, we should be able to build its BMI. In other words, a @@ -5234,7 +5248,7 @@ namespace build2 // // In the above examples one common theme about all the file names is // that they contain, in one form or another, the "tail" of the module - // name ('core'). So what we are going to do is require that, within a + // name (`core`). So what we are going to do is require that, within a // pool (library, executable), the interface file names contain enough // of the module name tail to unambiguously resolve all the module // imports. On our side we are going to implement a "fuzzy" module name @@ -5253,7 +5267,7 @@ namespace build2 // abstract-window.mxx: which one is likely to define this module? // Clearly the first, but in the above-described scheme they will get // the same score. More generally, consider these "obvious" (to the - // human) situations: + // human, that is) situations: // // window.mxx vs abstract-window.mxx // details/window.mxx vs abstract-window.mxx @@ -5262,13 +5276,17 @@ namespace build2 // To handle such cases we are going to combine the above primary score // with the following secondary scores (in that order): // - // a) Strength of separation between matched and unmatched parts: + // A) Strength of separation between matched and unmatched parts: // // '\0' > directory separator > other separator > unseparated // // Here '\0' signifies nothing to separate (unmatched part is empty). // - // b) Shortness of the unmatched part. + // B) Shortness of the unmatched part. + // + // Finally, for the fuzzy match we require a complete match of the last + // module (or partition) component. Failed that, we will match `format` + // to `print` because the last character (`t`) is the same. // // For std.* modules we only accept non-fuzzy matches (think std.core vs // some core.mxx). And if such a module is unresolved, then we assume it @@ -5285,8 +5303,12 @@ namespace build2 // // PPPPABBBB // - // We use decimal instead of binary packing to make it easier to - // separate fields in the trace messages, during debugging, etc. + // Where PPPP is the primary score, A is the A) score, and BBBB is + // the B) scope described above. Zero signifies no match. + // + // We use decimal instead of binary packing to make it easier for the + // human to separate fields in the trace messages, during debugging, + // etc. // return m.size () * 100000 + 99999; // Maximum match score. }; @@ -5309,6 +5331,8 @@ namespace build2 (ucase (c1) == c1) != (ucase (c2) == c2)); }; + auto mod_sep = [] (char c) {return c == '.' || c == ':';}; + size_t fn (f.size ()), fi (fn); size_t mn (m.size ()), mi (mn); @@ -5318,6 +5342,10 @@ namespace build2 bool fsep (false); bool msep (false); + // We require complete match of at least last module component. + // + bool match (false); + // Scan backwards for as long as we match. Keep track of the previous // character for case change detection. // @@ -5343,11 +5371,12 @@ namespace build2 // FOObar // bool fs (char_sep (fc)); - bool ms (mc == '_' || mc == '.' || mc == ':'); + bool ms (mod_sep (mc) || mc == '_'); if (fs && ms) { fsep = msep = true; + match = match || mod_sep (mc); continue; } @@ -5365,6 +5394,7 @@ namespace build2 if (fa) {++fi; msep = true;} if (ma) {++mi; fsep = true;} + match = match || mod_sep (mc); continue; } } @@ -5372,6 +5402,14 @@ namespace build2 break; // No match. } + // Deal with edge cases: complete module match and complete file + // match. + // + match = match || mi == 0 || (fi == 0 && mod_sep (m[mi - 1])); + + if (!match) + return 0; + // "Uncount" real separators. // if (fsep) fi++; @@ -5739,8 +5777,13 @@ namespace build2 // // But at this stage this doesn't seem worth the trouble. // - fail (relative (src)) << "unable to resolve module " - << imports[i].name; + fail (relative (src)) + << "unable to resolve module " << imports[i].name << + info << "verify module interface is listed as a prerequisite, " + << "otherwise" << + info << "consider adjusting module interface file names or" << + info << "consider specifying module name with " << x + << ".module_name"; } } } @@ -5859,7 +5902,7 @@ namespace build2 // Find or create a modules sidebuild subproject returning its root // directory. // - dir_path compile_rule:: + pair<dir_path, const scope&> compile_rule:: find_modules_sidebuild (const scope& rs) const { context& ctx (rs.ctx); @@ -5957,7 +6000,7 @@ namespace build2 assert (m != nullptr && m->modules); #endif - return pd; + return pair<dir_path, const scope&> (move (pd), *as); } // Synthesize a dependency for building a module binary interface on @@ -5974,7 +6017,7 @@ namespace build2 // Note: see also make_header_sidebuild() below. - dir_path pd (find_modules_sidebuild (*bs.root_scope ())); + dir_path pd (find_modules_sidebuild (*bs.root_scope ()).first); // We need to come up with a file/target name that will be unique enough // not to conflict with other modules. If we assume that within an @@ -6023,14 +6066,6 @@ namespace build2 if (include (a, lt, p) != include_type::normal) // Excluded/ad hoc. continue; - // @@ TODO: will probably need revision if using sidebuild for - // non-installed libraries (e.g., direct BMI dependencies - // will probably have to be translated to mxx{} or some such). - // Hm, don't think we want it this way: we want BMIs of binless - // library to be built in the library rather than on the side - // (so they can be potentially re-used by multiple independent - // importers). - // if (p.is_a<libx> () || p.is_a<liba> () || p.is_a<libs> () || p.is_a<libux> ()) { @@ -6068,8 +6103,9 @@ namespace build2 // the side. // const file& compile_rule:: - make_header_sidebuild (action, + make_header_sidebuild (action a, const scope& bs, + const file& t, linfo li, const file& ht) const { @@ -6077,7 +6113,95 @@ namespace build2 // Note: similar to make_module_sidebuild() above. - dir_path pd (find_modules_sidebuild (*bs.root_scope ())); + auto sb (find_modules_sidebuild (*bs.root_scope ())); + dir_path pd (move (sb.first)); + const scope& as (sb.second); + + // Determine if this header belongs to one of the libraries we depend + // on. + // + // Note that because libraries are not in prerequisite_targets, we have + // to go through prerequisites, similar to append_library_options(). + // + const target* lt (nullptr); // Can be lib{}. + { + // Note that any such library would necessarily be an interface + // dependency so we never need to go into implementations. + // + auto imp = [] (const target&, bool) { return false; }; + + // The same logic as in append_libraries(). + // + struct data + { + action a; + const file& ht; + const target*& lt; + } d {a, ht, lt}; + + auto lib = [&d] (const target* const* lc, + const string&, + lflags, + bool) + { + // It's unfortunate we have no way to bail out. + // + if (d.lt != nullptr) + return; + + const target* l (lc != nullptr ? *lc : nullptr); // Can be lib{}. + + if (l == nullptr) + return; + + // Feels like we should only consider non-utility libraries with + // utilities being treated as "direct" use. + // + if (l->is_a<libux> ()) + return; + + // Since the library is searched and matched, all the headers should + // be in prerequisite_targets. + // + const auto& pts (l->prerequisite_targets[d.a]); + if (find (pts.begin (), pts.end (), &d.ht) != pts.end ()) + d.lt = l; + }; + + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (include (a, t, p) != include_type::normal) // Excluded/ad hoc. + continue; + + // Should be already searched and matched for libraries. + // + if (const target* pt = p.load ()) + { + if (const libx* l = pt->is_a<libx> ()) + pt = link_member (*l, a, li); + + bool la; + const file* f; + if ((la = (f = pt->is_a<liba> ())) || + (la = (f = pt->is_a<libux> ())) || + ( (f = pt->is_a<libs> ()))) + { + // Note that we are requesting process_libraries() to not pick + // the liba/libs{} member of the installed libraries and return + // the lib{} group itself instead. This is because, for the + // installed case, the library prerequisites (both headers and + // interface dependency libraries) are matched by file_rule + // which won't pick the liba/libs{} member (naturally) but will + // just match the lib{} group. + // + process_libraries ( + a, bs, nullopt, sys_lib_dirs, + *f, la, 0, // lflags unused. + imp, lib, nullptr, true /* self */); + } + } + } + } // What should we use as a file/target name? On one hand we want it // unique enough so that <stdio.h> and <custom/stdio.h> don't end up @@ -6102,7 +6226,14 @@ namespace build2 mf += sha256 (hp.string ()).abbreviated_string (12); } - const target_type& tt (compile_types (li.type).hbmi); + // If the header comes from the library, use its hbmi?{} type to + // maximize reuse. + // + const target_type& tt ( + compile_types ( + lt != nullptr && !lt->is_a<lib> () + ? link_type (*lt).type + : li.type).hbmi); if (const file* bt = bs.ctx.targets.find<file> ( tt, @@ -6116,6 +6247,48 @@ namespace build2 prerequisites ps; ps.push_back (prerequisite (ht)); + // Similar story as for modules: the header may need poptions from its + // library (e.g., -I to find other headers that it includes). + // + if (lt != nullptr) + ps.push_back (prerequisite (*lt)); + else + { + // If the header does not belong to a library then this is a "direct" + // use, for example, by an exe{} target. In this case we need to add + // all the prerequisite libraries as well as scope p/coptions (in a + // sense, we are trying to approximate how all the sources that would + // typically include such a header are build). + // + // Note that this is also the case when we build the library's own + // sources (in a way it would have been cleaner to always build + // library's headers with only its "interface" options/prerequisites + // but that won't be easy to achieve). + // + // Note also that at first it might seem like a good idea to + // incorporate this information into the hash we use to form the BMI + // name. But that would reduce sharing of the BMI. For example, that + // would mean we will build the library header twice, once with the + // implementation options/prerequisites and once -- with interface. + // On the other hand, importable headers are expected to be "modular" + // and should probably not depend on any of the implementation + // options/prerequisites (though one could conceivably build a + // "richer" BMI if it is also to be used to build the library + // implementation -- interesting idea). + // + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (include (a, t, p) != include_type::normal) // Excluded/ad hoc. + continue; + + if (p.is_a<libx> () || + p.is_a<liba> () || p.is_a<libs> () || p.is_a<libux> ()) + { + ps.push_back (p.as_prerequisite ()); + } + } + } + auto p (bs.ctx.targets.insert_locked ( tt, move (pd), @@ -6130,8 +6303,32 @@ namespace build2 // while we were preparing the prerequisite list. // if (p.second) + { bt.prerequisites (move (ps)); + // Add the p/coptions from our scope in case of a "direct" use. Take + // into account hbmi{} target-type/pattern values to allow specifying + // hbmi-specific options. + // + if (lt == nullptr) + { + auto set = [&bs, &as, &tt, &bt] (const variable& var) + { + // Avoid duplicating the options if they are from the same + // amalgamation as the sidebuild. + // + lookup l (bs.lookup (var, tt, bt.name, hbmi::static_type, bt.name)); + if (l.defined () && !l.belongs (as)) + bt.assign (var) = *l; + }; + + set (c_poptions); + set (x_poptions); + set (c_coptions); + set (x_coptions); + } + } + return bt; } diff --git a/libbuild2/cc/compile-rule.hxx b/libbuild2/cc/compile-rule.hxx index a5eb8e8..edff1d8 100644 --- a/libbuild2/cc/compile-rule.hxx +++ b/libbuild2/cc/compile-rule.hxx @@ -162,7 +162,7 @@ namespace build2 const target_type&, const file&, module_imports&, sha256&) const; - dir_path + pair<dir_path, const scope&> find_modules_sidebuild (const scope&) const; const file& @@ -170,7 +170,8 @@ namespace build2 const target&, const string&) const; const file& - make_header_sidebuild (action, const scope&, linfo, const file&) const; + make_header_sidebuild (action, const scope&, const file&, + linfo, const file&) const; void append_header_options (environment&, cstrings&, small_vector<string, 2>&, diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx index 9afa29e..1e0c77a 100644 --- a/libbuild2/cc/guess.cxx +++ b/libbuild2/cc/guess.cxx @@ -65,7 +65,6 @@ # include <libbuild2/filesystem.hxx> #endif -#include <map> #include <cstring> // strlen(), strchr(), strstr() #include <libbuild2/diagnostics.hxx> @@ -3218,5 +3217,180 @@ namespace build2 return r; } + + // Table 23 [tab:headers.cpp]. + // + // In the future we will probably have to maintain per-standard additions. + // + static const char* std_importable[] = { + "<algorithm>", + "<any>", + "<array>", + "<atomic>", + "<barrier>", + "<bit>", + "<bitset>", + "<charconv>", + "<chrono>", + "<codecvt>", + "<compare>", + "<complex>", + "<concepts>", + "<condition_variable>", + "<coroutine>", + "<deque>", + "<exception>", + "<execution>", + "<filesystem>", + "<format>", + "<forward_list>", + "<fstream>", + "<functional>", + "<future>", + "<initializer_list>", + "<iomanip>", + "<ios>", + "<iosfwd>", + "<iostream>", + "<istream>", + "<iterator>", + "<latch>", + "<limits>", + "<list>", + "<locale>", + "<map>", + "<memory>", + "<memory_resource>", + "<mutex>", + "<new>", + "<numbers>", + "<numeric>", + "<optional>", + "<ostream>", + "<queue>", + "<random>", + "<ranges>", + "<ratio>", + "<regex>", + "<scoped_allocator>", + "<semaphore>", + "<set>", + "<shared_mutex>", + "<source_location>", + "<span>", + "<sstream>", + "<stack>", + "<stdexcept>", + "<stop_token>", + "<streambuf>", + "<string>", + "<string_view>", + "<strstream>", + "<syncstream>", + "<system_error>", + "<thread>", + "<tuple>", + "<typeindex>", + "<typeinfo>", + "<type_traits>", + "<unordered_map>", + "<unordered_set>", + "<utility>", + "<valarray>", + "<variant>", + "<vector>", + "<version>" + }; + + // Table 24 ([tab:headers.cpp.c]) + // + static const char* std_non_importable[] = { + "<cassert>", + "<cctype>", + "<cerrno>", + "<cfenv>", + "<cfloat>", + "<cinttypes>", + "<climits>", + "<clocale>", + "<cmath>", + "<csetjmp>", + "<csignal>", + "<cstdarg>", + "<cstddef>", + "<cstdint>", + "<cstdio>", + "<cstdlib>", + "<cstring>", + "<ctime>", + "<cuchar>", + "<cwchar>", + "<cwctype>" + }; + + void + guess_std_importable_headers (const compiler_info& ci, + const dir_paths& sys_inc_dirs, + importable_headers& hs) + { + hs.group_map.emplace (header_group_std, 0); + hs.group_map.emplace (header_group_std_importable, 0); + + // For better performance we make compiler-specific assumptions. + // + // For example, we can assume that all these headers are found in the + // same header search directory. This is at least the case for GCC's + // libstdc++. + // + // Note also that some headers could be missing. For example, <format> + // is currently not provided by GCC. Though entering missing headers + // should be harmless. + // + pair<const path, importable_headers::groups>* p; + auto add_groups = [&p] (bool imp) + { + if (imp) + p->second.push_back (header_group_std_importable); // More specific. + + p->second.push_back (header_group_std); + }; + + if (ci.id.type != compiler_type::gcc) + { + for (const char* f: std_importable) + if ((p = hs.insert_angle (sys_inc_dirs, f)) != nullptr) + add_groups (true); + + for (const char* f: std_non_importable) + if ((p = hs.insert_angle (sys_inc_dirs, f)) != nullptr) + add_groups (false); + } + else + { + p = hs.insert_angle (sys_inc_dirs, std_importable[0]); + assert (p != nullptr); + + add_groups (true); + + dir_path d (p->first.directory ()); + + auto add_header = [&hs, &d, &p, add_groups] (const char* f, bool imp) + { + path fp (d); + fp.combine (f + 1, strlen (f) - 2, '\0'); // Assuming simple. + + p = &hs.insert_angle (move (fp), f); + add_groups (imp); + }; + + for (size_t i (1); + i != sizeof (std_importable) / sizeof (std_importable[0]); + ++i) + add_header (std_importable[i], true); + + for (const char* f: std_non_importable) + add_header (f, false); + } + } } } diff --git a/libbuild2/cc/guess.hxx b/libbuild2/cc/guess.hxx index 868e925..a141fa3 100644 --- a/libbuild2/cc/guess.hxx +++ b/libbuild2/cc/guess.hxx @@ -265,6 +265,16 @@ namespace build2 const string& cid, const string& pattern, const strings& mode); + + // Insert importable/non-importable C++ standard library headers + // ([headers]/4). + // + // Note that the importable_headers instance should be unique-locked. + // + void + guess_std_importable_headers (const compiler_info&, + const dir_paths& sys_inc_dirs, + importable_headers&); } } diff --git a/libbuild2/cc/init.cxx b/libbuild2/cc/init.cxx index bb50d07..e8b0e6f 100644 --- a/libbuild2/cc/init.cxx +++ b/libbuild2/cc/init.cxx @@ -146,6 +146,14 @@ namespace build2 // vp.insert<string> ("cc.module_name", v_t); + // Importable header marker (normally set via the x.importable alias). + // + // Note that while at first it might seem like a good idea to allow + // setting it on a scope, that will cause translation of inline/template + // includes which is something we definitely don't want. + // + vp.insert<bool> ("cc.importable", v_t); + // Ability to disable using preprocessed output for compilation. // vp.insert<bool> ("config.cc.reprocess"); diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index 9c0b018..2b3f22a 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -3,7 +3,6 @@ #include <libbuild2/cc/link-rule.hxx> -#include <map> #include <cstdlib> // exit() #include <cstring> // strlen() @@ -24,7 +23,6 @@ #include <libbuild2/cc/target.hxx> // c, pc* #include <libbuild2/cc/utility.hxx> -using std::map; using std::exit; using namespace butl; @@ -330,7 +328,7 @@ namespace build2 // string ver; bool verp (true); // Platform-specific. - using verion_map = map<string, string>; + using verion_map = map<optional<string>, string>; if (const verion_map* m = cast_null<verion_map> (t["bin.lib.version"])) { // First look for the target system. @@ -347,14 +345,20 @@ namespace build2 // say "all others -- no version". // if (i == m->end ()) - i = m->find ("*"); + i = m->find (string ("*")); // Finally look for the platform-independent version. // if (i == m->end ()) { verp = false; - i = m->find (""); + + i = m->find (nullopt); + + // For backwards-compatibility. + // + if (i == m->end ()) + i = m->find (string ()); } // If we didn't find anything, fail. If the bin.lib.version was @@ -852,7 +856,7 @@ namespace build2 // else { - if (!p.is_a<objx> () && !p.is_a<bmix> ()) + if (!p.is_a<objx> () && !p.is_a<bmix> () && !x_header (p, true)) { // @@ Temporary hack until we get the default outer operation // for update. This allows operations like test and install to @@ -1549,17 +1553,19 @@ namespace build2 compile_target_types tts; } d {ls, args, l, a, li, rel, compile_types (li.type)}; - auto imp = [] (const file&, bool la) + auto imp = [] (const target&, bool la) { return la; }; - auto lib = [&d, this] (const file* const* lc, + auto lib = [&d, this] (const target* const* lc, const string& p, lflags f, bool) { - const file* l (lc != nullptr ? *lc : nullptr); + // Note: see also make_header_sidebuild(). + + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); // Suppress duplicates. // @@ -1739,11 +1745,13 @@ namespace build2 al.end = d.args.size (); // Close. }; - auto opt = [&d, this] (const file& l, + auto opt = [&d, this] (const target& lt, const string& t, bool com, bool exp) { + const file& l (lt.as<file> ()); + // Don't try to pass any loptions when linking a static library. // // Note also that we used to pass non-export loptions but that didn't @@ -1800,17 +1808,17 @@ namespace build2 linfo li; } d {cs, bs.root_scope ()->out_path (), update, mt, li}; - auto imp = [] (const file&, bool la) + auto imp = [] (const target&, bool la) { return la; }; - auto lib = [&d, this] (const file* const* lc, + auto lib = [&d, this] (const target* const* lc, const string& p, lflags f, bool) { - const file* l (lc != nullptr ? *lc : nullptr); + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); if (l == nullptr) { @@ -1862,7 +1870,7 @@ namespace build2 } }; - auto opt = [&d, this] (const file& l, + auto opt = [&d, this] (const target& l, const string& t, bool com, bool exp) @@ -1902,7 +1910,7 @@ namespace build2 return; } - auto imp = [link] (const file& l, bool la) + auto imp = [link] (const target& l, bool la) { // If we are not rpath-link'ing, then we only need to rpath interface // libraries (they will include rpath's for their implementations) @@ -1931,12 +1939,12 @@ namespace build2 bool link; } d {ls, args, link}; - auto lib = [&d, this] (const file* const* lc, + auto lib = [&d, this] (const target* const* lc, const string& f, lflags, bool sys) { - const file* l (lc != nullptr ? *lc : nullptr); + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); // We don't rpath system libraries. Why, you may ask? There are many // good reasons and I have them written on a napkin somewhere... diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx index 8241a01..971f175 100644 --- a/libbuild2/cc/module.cxx +++ b/libbuild2/cc/module.cxx @@ -426,20 +426,21 @@ namespace build2 translate_std (xi, tt, rs, mode, v); } - // config.x.translatable_header + // config.x.translate_include // // It's still fuzzy whether specifying (or maybe tweaking) this list in // the configuration will be a common thing to do so for now we use - // omitted. It's also probably too early to think whether we should have - // the cc.* version and what the semantics should be. + // omitted. // - if (x_translatable_headers != nullptr) + if (x_translate_include != nullptr) { - lookup l (lookup_config (rs, *config_x_translatable_headers)); - - // @@ MODHDR: if(modules) ? - // - rs.assign (x_translatable_headers) += cast_null<strings> (l); + if (lookup l = lookup_config (rs, *config_x_translate_include)) + { + // @@ MODHDR: if(modules) ? Yes. + // + rs.assign (x_translate_include).prepend ( + cast<translatable_headers> (l)); + } } // Extract system header/library search paths from the compiler and @@ -718,8 +719,19 @@ namespace build2 } } + // Global cache of ad hoc importable headers. + // + // The key is a hash of the system header search directories + // (sys_inc_dirs) where we search for the headers. + // + static map<string, importable_headers> importable_headers_cache; + static mutex importable_headers_mutex; + void module:: - init (scope& rs, const location& loc, const variable_map&) + init (scope& rs, + const location& loc, + const variable_map&, + const compiler_info& xi) { tracer trace (x, "init"); @@ -740,71 +752,76 @@ namespace build2 // load_module (rs, rs, "cc.core", loc); - // Process, sort, and cache (in this->xlate_hdr) translatable headers. - // Keep the cache NULL if unused or empty. + // Search include translation headers and groups. // - // @@ MODHDR TODO: support exclusions entries (e.g., -<stdio.h>)? - // - if (modules && x_translatable_headers != nullptr) + if (modules) { - strings* ih (cast_null<strings> (rs.assign (x_translatable_headers))); + { + sha256 k; + for (const dir_path& d: sys_inc_dirs) + k.append (d.string ()); + + mlock l (importable_headers_mutex); + importable_headers = &importable_headers_cache[k.string ()]; + } + + auto& hs (*importable_headers); - if (ih != nullptr && !ih->empty ()) + ulock ul (hs.mutex); + + if (hs.group_map.find (header_group_std) == hs.group_map.end ()) + guess_std_importable_headers (xi, sys_inc_dirs, hs); + + // Process x.translate_include. + // + const variable& var (*x_translate_include); + if (auto* v = cast_null<translatable_headers> (rs[var])) { - // Translate <>-style header names to absolute paths using the - // compiler's include search paths. Otherwise complete and normalize - // since when searching in this list we always use the absolute and - // normalized header target path. - // - for (string& h: *ih) + for (const auto& p: *v) { - if (h.empty ()) - continue; + const string& k (p.first); - path f; - if (h.front () == '<' && h.back () == '>') + if (k.front () == '<' && k.back () == '>') { - h.pop_back (); - h.erase (0, 1); + if (path_pattern (k)) + { + size_t n (hs.insert_angle_pattern (sys_inc_dirs, k)); - for (const dir_path& d: sys_inc_dirs) + l5 ([&]{trace << "pattern " << k << " searched to " << n + << " headers";}); + } + else { - if (file_exists ((f = d, f /= h), - true /* follow_symlinks */, - true /* ignore_errors */)) - goto found; + // What should we do if not found? While we can fail, this + // could be too drastic if, for example, the header is + // "optional" and may or may not be present/used. So for now + // let's ignore (we could have also removed it from the map as + // an indication). + // + const auto* r (hs.insert_angle (sys_inc_dirs, k)); + + l5 ([&]{trace << "header " << k << " searched to " + << (r ? r->first.string ().c_str () : "<none>");}); } - - // What should we do if not found? While we can fail, this could - // be too drastic if, for example, the header is "optional" and - // may or may not be present/used. So for now let's restore the - // original form to aid debugging (it can't possibly match any - // absolute path). + } + else if (path_traits::find_separator (k) == string::npos) + { + // Group name. // - h.insert (0, 1, '<'); - h.push_back ('>'); - continue; - - found: - ; // Fall through. + if (k != header_group_all_importable && + k != header_group_std_importable && + k != header_group_all && + k != header_group_std) + fail (loc) << "unknown header group '" << k << "' in " << var; } else { - f = path (move (h)); - - if (f.relative ()) - f.complete (); + // Absolute and normalized header path. + // + if (!path_traits::absolute (k)) + fail (loc) << "relative header path '" << k << "' in " << var; } - - // @@ MODHDR: should we use the more elaborate but robust - // normalize/realize scheme so the we get the same - // path? Feels right. - f.normalize (); - h = move (f).string (); } - - sort (ih->begin (), ih->end ()); - xlate_hdr = ih; } } diff --git a/libbuild2/cc/module.hxx b/libbuild2/cc/module.hxx index 85b7158..f9d435d 100644 --- a/libbuild2/cc/module.hxx +++ b/libbuild2/cc/module.hxx @@ -45,8 +45,10 @@ namespace build2 // Translate the x.std value (if any) to the standard-selecting // option(s) (if any) and fold them (normally by pre-pending) into the - // compiler mode options. This function may also check/set x.features.* - // variables on the root scope. + // compiler mode options. + // + // This function may also check/set [config.]x.features.* variables on + // the root scope. // virtual void translate_std (const compiler_info&, @@ -103,7 +105,10 @@ namespace build2 libux_install_rule (move (d), *this) {} void - init (scope&, const location&, const variable_map&); + init (scope&, + const location&, + const variable_map&, + const compiler_info&); }; } } diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx index 75c7227..d44b0ec 100644 --- a/libbuild2/cc/pkgconfig.cxx +++ b/libbuild2/cc/pkgconfig.cxx @@ -739,12 +739,12 @@ namespace build2 // export stub and we shouldn't touch them. // if (p.second) - p.first.get () = move (pops); + p.first = move (pops); } }; // Parse --libs into loptions/libs (interface and implementation). If - // ps is not NULL, add each resolves library target as a prerequisite. + // ps is not NULL, add each resolved library target as a prerequisite. // auto parse_libs = [a, &s, top_sysd, this] (target& t, bool binless, @@ -1090,7 +1090,7 @@ namespace build2 auto p (t.vars.insert (c_export_loptions)); if (p.second) - p.first.get () = move (lops); + p.first = move (lops); } // Set even if empty (export override). @@ -1099,7 +1099,7 @@ namespace build2 auto p (t.vars.insert (la ? c_export_imp_libs : c_export_libs)); if (p.second) - p.first.get () = move (libs); + p.first = move (libs); } }; @@ -1142,15 +1142,17 @@ namespace build2 return r; }; - // Parse modules and add them to the prerequisites. + // Parse modules, enter them as targets, and add them to the + // prerequisites. // - auto parse_modules = [&trace, &next, &s, this] - (const pkgconf& pc, prerequisites& ps) + auto parse_modules = [&trace, this, + &next, &s, <] (const pkgconf& pc, + prerequisites& ps) { - string mstr (pc.variable ("cxx_modules")); + string val (pc.variable ("cxx_modules")); string m; - for (size_t b (0), e (0); !(m = next (mstr, b, e)).empty (); ) + for (size_t b (0), e (0); !(m = next (val, b, e)).empty (); ) { // The format is <name>=<path> with `..` used as a partition // separator (see pkgconfig_save() for details). @@ -1159,7 +1161,7 @@ namespace build2 if (p == string::npos || p == 0 || // Empty name. p == m.size () - 1) // Empty path. - fail << "invalid module information in '" << mstr << "'" << + fail << "invalid module information in '" << val << "'" << info << "while parsing pkg-config --variable=cxx_modules " << pc.path; @@ -1193,12 +1195,12 @@ namespace build2 // If the target already exists, then setting its variables is not // MT-safe. So currently we only do it if we have the lock (and thus - // nobody can see this target yet) assuming that this has already + // nobody can see this target yet) verifying that this has already // been done otherwise. // // @@ This is not quite correct, though: this target could already // exist but for a "different purpose" (e.g., it could be used as - // a header). + // a header). Well, maybe it shouldn't. // // @@ Could setting it in the rule-specific vars help? (But we // are not matching a rule for it.) Note that we are setting @@ -1211,7 +1213,7 @@ namespace build2 // Set module properties. Note that if unspecified we should still // set them to their default values since the hosting project may - // have them set to incompatible value. + // have them set to incompatible values. // { value& v (mt.vars.assign (x_preprocessed)); // NULL @@ -1224,11 +1226,72 @@ namespace build2 tl.second.unlock (); } + else + { + if (!mt.vars[c_module_name]) + fail << "unexpected metadata for module target " << mt << + info << "module is expected to have assigned name" << + info << "make sure this module is used via " << lt + << " prerequisite"; + } ps.push_back (prerequisite (mt)); } }; + // Parse importable headers, enter them as targets, and add them to + // the prerequisites. + // + auto parse_headers = [&trace, this, + &next, &s, <] (const pkgconf& pc, + const target_type& tt, + const char* lang, + prerequisites& ps) + { + string var (string (lang) + "_importable_headers"); + string val (pc.variable (var)); + + string h; + for (size_t b (0), e (0); !(h = next (val, b, e)).empty (); ) + { + path hp (move (h)); + path hf (hp.leaf ()); + + auto tl ( + s.ctx.targets.insert_locked ( + tt, + hp.directory (), + dir_path (), + hf.base ().string (), + hf.extension (), + target_decl::implied, + trace)); + + target& ht (tl.first); + + // If the target already exists, then setting its variables is not + // MT-safe. So currently we only do it if we have the lock (and thus + // nobody can see this target yet) verifying that this has already + // been done otherwise. + // + if (tl.second.owns_lock ()) + { + ht.vars.assign (c_importable) = true; + tl.second.unlock (); + } + else + { + if (!cast_false<bool> (ht.vars[c_importable])) + fail << "unexpected metadata for existing header target " << ht << + info << "header is expected to be marked importable" << + info << "make sure this header is used via " << lt + << " prerequisite"; + } + + ps.push_back (prerequisite (ht)); + } + }; + // For now we only populate prerequisites for lib{}. To do it for // liba{} would require weeding out duplicates that are already in // lib{}. @@ -1291,13 +1354,21 @@ namespace build2 parse_cflags (*st, spc, false); // For now we assume static and shared variants export the same set of - // modules. While technically possible, having a different set will most - // likely lead to all sorts of complications (at least for installed - // libraries) and life is short. + // modules/importable headers. While technically possible, having + // different sets will most likely lead to all sorts of complications + // (at least for installed libraries) and life is short. // if (modules) + { parse_modules (ipc, prs); + // We treat headers outside of any project as C headers (see + // enter_header() for details). + // + parse_headers (ipc, h::static_type /* **x_hdr */, x, prs); + parse_headers (ipc, h::static_type, "c", prs); + } + assert (!lt.has_prerequisites ()); if (!prs.empty ()) lt.prerequisites (move (prs)); @@ -1479,13 +1550,25 @@ namespace build2 } else { - // Derive -l-name from the file name in a fuzzy, platform-specific - // manner. - // - n = l.path ().leaf ().base ().string (); + const path& p (l.path ()); - if (cclass != compiler_class::msvc) - strip_lib (); + if (p.empty ()) // Binless. + { + // For a binless library the target name is all it can possibly + // be. + // + n = l.name; + } + else + { + // Derive -l-name from the file name in a fuzzy, platform- + // specific manner. + // + n = p.leaf ().base ().string (); + + if (cclass != compiler_class::msvc) + strip_lib (); + } } os << " -l" << n; @@ -1550,15 +1633,15 @@ namespace build2 // pretty similar to link_rule::append_libraries()). // bool priv (false); - auto imp = [&priv] (const file&, bool la) {return priv && la;}; + auto imp = [&priv] (const target&, bool la) {return priv && la;}; auto lib = [&save_library_target, - &save_library_name] (const file* const* c, + &save_library_name] (const target* const* lc, const string& p, lflags, bool) { - const file* l (c != nullptr ? *c : nullptr); + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); if (l != nullptr) { @@ -1569,9 +1652,7 @@ namespace build2 save_library_name (p); // Something "system'y", save as is. }; - auto opt = [] (const file&, - const string&, - bool, bool) + auto opt = [] (const target&, const string&, bool, bool) { //@@ TODO: should we filter -L similar to -I? //@@ TODO: how will the Libs/Libs.private work? @@ -1602,32 +1683,46 @@ namespace build2 } } - // If we have modules, list them in the modules variable. We also save - // some extra info about them (yes, the rabbit hole runs deep). This - // code is pretty similar to compiler::search_modules(). + // If we have modules and/or importable headers, list them in the + // respective variables. We also save some extra info about modules + // (yes, the rabbit hole runs deep). This code is pretty similar to + // compiler::search_modules(). + // + // Note that we want to convey the importable headers information even + // if modules are not enabled. // - if (modules) { struct module { string name; path file; - string pp; + string preprocessed; bool symexport; }; - vector<module> modules; + vector<module> mods; + + // If we were to ever support another C-based language (e.g., + // Objective-C) and libraries that can use a mix of languages (e.g., + // C++ and Objective-C), then we would need to somehow reverse- + // lookup header target type to language. Let's hope we don't. + // + vector<path> x_hdrs; + vector<path> c_hdrs; // Note that the prerequisite targets are in the member, not the - // group (for now we don't support different sets of modules for - // static/shared library; see load above for details). + // group (for now we don't support different sets of modules/headers + // for static/shared library; see load above for details). // for (const target* pt: l.prerequisite_targets[a]) { + if (pt == nullptr) + continue; + // @@ UTL: we need to (recursively) see through libu*{} (and // also in search_modules()). // - if (pt != nullptr && pt->is_a<bmix> ()) + if (modules && pt->is_a<bmix> ()) { // What we have is a binary module interface. What we need is // a module interface source it was built from. We assume it's @@ -1654,7 +1749,7 @@ namespace build2 if (const string* v = cast_null<string> ((*mt)[x_preprocessed])) pp = *v; - modules.push_back ( + mods.push_back ( module { cast<string> (pt->state[a].vars[c_module_name]), move (p), @@ -1662,9 +1757,21 @@ namespace build2 symexport }); } + else if (pt->is_a (**x_hdr) || pt->is_a<h> ()) + { + if (cast_false<bool> ((*pt)[c_importable])) + { + path p (install::resolve_file (pt->as<file> ())); + + if (p.empty ()) // Not installed. + continue; + + (pt->is_a<h> () ? c_hdrs : x_hdrs).push_back (move (p)); + } + } } - if (!modules.empty ()) + if (size_t n = mods.size ()) { os << endl << "cxx_modules ="; @@ -1676,7 +1783,7 @@ namespace build2 // for example hello.print..impl. While in the variable values we // can use `:`, for consistency we use `..` there as well. // - for (module& m: modules) + for (module& m: mods) { size_t p (m.name.find (':')); if (p != string::npos) @@ -1684,7 +1791,8 @@ namespace build2 // Module names shouldn't require escaping. // - os << ' ' << m.name << '=' << escape (m.file.string ()); + os << (n != 1 ? " \\\n" : " ") + << m.name << '=' << escape (m.file.string ()); } os << endl; @@ -1693,16 +1801,38 @@ namespace build2 // // <lang>_module_<property>.<module> = <value> // - for (const module& m: modules) + for (const module& m: mods) { - if (!m.pp.empty ()) - os << "cxx_module_preprocessed." << m.name << " = " << m.pp - << endl; + if (!m.preprocessed.empty ()) + os << "cxx_module_preprocessed." << m.name << " = " + << m.preprocessed << endl; if (m.symexport) os << "cxx_module_symexport." << m.name << " = true" << endl; } } + + if (size_t n = c_hdrs.size ()) + { + os << endl + << "c_importable_headers ="; + + for (const path& h: c_hdrs) + os << (n != 1 ? " \\\n" : " ") << escape (h.string ()); + + os << endl; + } + + if (size_t n = x_hdrs.size ()) + { + os << endl + << x << "_importable_headers ="; + + for (const path& h: x_hdrs) + os << (n != 1 ? " \\\n" : " ") << escape (h.string ()); + + os << endl; + } } os.close (); diff --git a/libbuild2/cc/types.cxx b/libbuild2/cc/types.cxx new file mode 100644 index 0000000..666b048 --- /dev/null +++ b/libbuild2/cc/types.cxx @@ -0,0 +1,189 @@ +// file : libbuild2/cc/types.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/cc/types.hxx> + +#include <libbuild2/cc/utility.hxx> + +using namespace std; + +namespace build2 +{ + namespace cc + { + const string header_group_all ("all"); + const string header_group_all_importable ("all-importable"); + const string header_group_std ("std"); + const string header_group_std_importable ("std-importable"); + + // Find the position where the group should be inserted unless the group + // is already there. + // + using groups = importable_headers::groups; + + static inline optional<groups::const_iterator> + find_angle (const groups& gs, const string& g) + { + for (auto i (gs.begin ()); i != gs.end (); ++i) + { + // After last angle-bracket file. + // + if (i->front () != '<' || i->back () != '>' || path_pattern (*i)) + return i; + + if (*i == g) + return nullopt; + } + + return gs.begin (); + } + + static inline optional<groups::const_iterator> + find_angle_pattern (const groups& gs, const string& g) + { + for (auto i (gs.begin ()); i != gs.end (); ++i) + { + // After last angle-bracket file pattern. + // + if (i->front () != '<' || i->back () != '>') + return i; + + if (*i == g) + return nullopt; + } + + return gs.begin (); + } + + auto importable_headers:: + insert_angle (const dir_paths& sys_inc_dirs, + const string& s) -> pair<const path, groups>* + { + assert (s.front () == '<' && s.back () == '>'); + + // First see if it has already been inserted. + // + auto i (group_map.find (s)); + if (i == group_map.end ()) + { + path f (s, 1, s.size () - 2); + + path p; + for (const dir_path& d: sys_inc_dirs) + { + if (file_exists ((p = d, p /= f), + true /* follow_symlinks */, + true /* ignore_errors */)) + goto found; + } + + return nullptr; + + found: + normalize_header (p); + + // Note that it's possible this header has already been entered as + // part of a different group. + // + auto j (header_map.emplace (move (p), groups {}).first); + + if (auto p = find_angle (j->second, s)) + j->second.insert (*p, s); + + i = group_map.emplace (s, reinterpret_cast<uintptr_t> (&*j)).first; + } + + return reinterpret_cast<pair<const path, groups>*> (i->second); + } + + auto importable_headers:: + insert_angle (path p, const string& s) -> pair<const path, groups>& + { + assert (s.front () == '<' && s.back () == '>'); + + // First see if it has already been inserted. + // + auto i (group_map.find (s)); + if (i == group_map.end ()) + { + // Note that it's possible this header has already been entered as + // part of a different group. + // + auto j (header_map.emplace (move (p), groups {}).first); + + if (auto p = find_angle (j->second, s)) + j->second.insert (*p, s); + + i = group_map.emplace (s, reinterpret_cast<uintptr_t> (&*j)).first; + } + + return *reinterpret_cast<pair<const path, groups>*> (i->second); + } + + size_t importable_headers:: + insert_angle_pattern (const dir_paths& sys_inc_dirs, const string& pat) + { + assert (pat.front () == '<' && pat.back () == '>' && path_pattern (pat)); + + // First see if it has already been inserted. + // + auto i (group_map.find (pat)); + if (i == group_map.end ()) + { + path f (pat, 1, pat.size () - 2); + + struct data + { + uintptr_t n; + const string& pat; + const dir_path* dir; + } d {0, pat, nullptr}; + + auto process = [&d, this] (path&& pe, const string&, bool interm) + { + if (interm) + return true; + + path p (*d.dir / pe); + normalize_header (p); + + string s (move (pe).string ()); + s.insert (0, 1, '<'); + s.push_back ('>'); + + // Note that it's possible this header has already been entered as + // part of a different group. + // + auto j (header_map.emplace (move (p), groups {}).first); + + if (auto p = find_angle (j->second, s)) + j->second.insert (*p, move (s)); + + if (auto p = find_angle_pattern (j->second, d.pat)) + j->second.insert (*p, d.pat); + + d.n++; + return true; + }; + + for (const dir_path& dir: sys_inc_dirs) + { + d.dir = &dir; + + try + { + path_search (f, process, dir); + } + catch (const system_error& e) + { + fail << "unable to scan " << dir << ": " << e; + } + } + + i = group_map.emplace (pat, d.n).first; + } + + return static_cast<size_t> (i->second); + } + } +} diff --git a/libbuild2/cc/types.hxx b/libbuild2/cc/types.hxx index 20b67c2..1297b7b 100644 --- a/libbuild2/cc/types.hxx +++ b/libbuild2/cc/types.hxx @@ -4,6 +4,8 @@ #ifndef LIBBUILD2_CC_TYPES_HXX #define LIBBUILD2_CC_TYPES_HXX +#include <unordered_map> + #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> @@ -81,6 +83,80 @@ namespace build2 build2::cc::module_info module_info; }; + // Ad hoc (as opposed to marked with x.importable) importable headers. + // + // Note that these are only searched for in the system header search + // directories (sys_inc_dirs). + // + struct importable_headers + { + mutable shared_mutex mutex; + + using groups = small_vector<string, 3>; + + // Map of groups (e.g., std, <vector>, <boost/*.hpp>) that have already + // been inserted. + // + // For angle-bracket file groups (e.g., <vector>), the value is a + // pointer to the corresponding header_map element. For angle-bracket + // file pattern groups (e.g., <boost/**.hpp>), the value is the number + // of files in the group. + // + // Note that while the case-sensitivity of header names in #include + // directives is implementation-defined, our group names are case- + // sensitive (playing loose with the case will lead to portability + // issues sooner or later so we don't bother with any more elborate + // solutions). + // + std::unordered_map<string, uintptr_t> group_map; + + // Map of absolute and normalized header paths to groups (e.g., std, + // <vector>, <boost/**.hpp>) to which they belong. The groups are + // ordered from the most to least specific (e.g., <vector> then std). + // + std::unordered_map<path, groups> header_map; + + // Note that all these functions assume the instance is unique-locked. + // + + // Search for and insert an angle-bracket file, for example <vector>, + // making it belong to the angle-bracket file group itself. Return the + // pointer to the corresponding header_map element if the file has been + // found and NULL otherwise (so can be used as bool). + // + pair<const path, groups>* + insert_angle (const dir_paths& sys_inc_dirs, const string& file); + + // As above but for a manually-searched absolute and normalized path. + // + pair<const path, groups>& + insert_angle (path, const string& file); + + // Search for and insert an angle-bracket file pattern, for example + // <boost/**.hpp>, making each header belong to the angle-bracket file + // group (e.g., <boost/any.hpp>) and the angle-bracket file pattern + // group itself. Return the number of files found that match the + // pattern. + // + size_t + insert_angle_pattern (const dir_paths& sys_inc_dirs, const string& pat); + }; + + // Headers and header groups whose inclusion should or should not be + // translated to the corresponding header unit imports. + // + // The key is either an absolute and normalized header path or a reference + // to an importable_headers group (e.g., <vector>, std). + // + using translatable_headers = map<string, optional<bool>>; + + // Special translatable header groups. + // + extern const string header_group_all; + extern const string header_group_all_importable; + extern const string header_group_std; + extern const string header_group_std_importable; + // Compiler language. // enum class lang {c, cxx}; diff --git a/libbuild2/cc/utility.cxx b/libbuild2/cc/utility.cxx index 283e1b4..ffe3e03 100644 --- a/libbuild2/cc/utility.cxx +++ b/libbuild2/cc/utility.cxx @@ -17,5 +17,58 @@ namespace build2 const dir_path module_build_dir (dir_path (module_dir) /= "build"); const dir_path module_build_modules_dir ( dir_path (module_build_dir) /= "modules"); + + void + normalize_header (path& f) + { + // Interestingly, on most paltforms and with most compilers (Clang on + // Linux being a notable exception) most system/compiler headers are + // already normalized. + // + path_abnormality a (f.abnormalities ()); + if (a != path_abnormality::none) + { + // While we can reasonably expect this path to exit, things do go + // south from time to time (like compiling under wine with file + // wlantypes.h included as WlanTypes.h). + // + try + { + // If we have any parent components, then we have to verify the + // normalized path matches realized. + // + path r; + if ((a & path_abnormality::parent) == path_abnormality::parent) + { + r = f; + r.realize (); + } + + try + { + f.normalize (); + + // Note that we might still need to resolve symlinks in the + // normalized path. + // + if (!r.empty () && f != r && path (f).realize () != r) + f = move (r); + } + catch (const invalid_path&) + { + assert (!r.empty ()); // Shouldn't have failed if no `..`. + f = move (r); // Fallback to realize. + } + } + catch (const invalid_path&) + { + fail << "invalid header path '" << f.string () << "'"; + } + catch (const system_error& e) + { + fail << "invalid header path '" << f.string () << "': " << e; + } + } + } } } diff --git a/libbuild2/cc/utility.hxx b/libbuild2/cc/utility.hxx index a856fd0..42e53e3 100644 --- a/libbuild2/cc/utility.hxx +++ b/libbuild2/cc/utility.hxx @@ -48,6 +48,32 @@ namespace build2 compile_target_types compile_types (otype); + + // Normalize an absolute path to an existing header. + // + // We used to just normalize the path but that could result in an invalid + // path (e.g., for some system/compiler headers on CentOS 7 with Clang + // 3.4) because of the symlinks (if a directory component is a symlink, + // then any following `..` are resolved relative to the target; see + // path::normalize() for background). + // + // Initially, to fix this, we realized (i.e., realpath(3)) it instead. + // But that turned out also not to be quite right since now we have all + // the symlinks resolved: conceptually it feels correct to keep the + // original header names since that's how the user chose to arrange things + // and practically this is how the compilers see/report them (e.g., the + // GCC module mapper). + // + // So now we have a pretty elaborate scheme where we try to use the + // normalized path if possible and fallback to realized. Normalized paths + // will work for situations where `..` does not cross symlink boundaries, + // which is the sane case. And for the insane case we only really care + // about out-of-project files (i.e., system/compiler headers). In other + // words, if you have the insane case inside your project, then you are on + // your own. + // + void + normalize_header (path&); } } diff --git a/libbuild2/cc/windows-rpath.cxx b/libbuild2/cc/windows-rpath.cxx index eddb9c4..70eef73 100644 --- a/libbuild2/cc/windows-rpath.cxx +++ b/libbuild2/cc/windows-rpath.cxx @@ -56,14 +56,14 @@ namespace build2 // We need to collect all the DLLs, so go into implementation of both // shared and static (in case they depend on shared). // - auto imp = [] (const file&, bool) {return true;}; + auto imp = [] (const target&, bool) {return true;}; - auto lib = [&r] (const file* const* lc, + auto lib = [&r] (const target* const* lc, const string& f, lflags, bool sys) { - const file* l (lc != nullptr ? *lc : nullptr); + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); // We don't rpath system libraries. // @@ -137,14 +137,14 @@ namespace build2 { windows_dlls r; - auto imp = [] (const file&, bool) {return true;}; + auto imp = [] (const target&, bool) {return true;}; - auto lib = [&r, &bs] (const file* const* lc, + auto lib = [&r, &bs] (const target* const* lc, const string& f, lflags, bool sys) { - const file* l (lc != nullptr ? *lc : nullptr); + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); if (sys) return; diff --git a/libbuild2/config/module.hxx b/libbuild2/config/module.hxx index 857a30c..3afe721 100644 --- a/libbuild2/config/module.hxx +++ b/libbuild2/config/module.hxx @@ -4,8 +4,6 @@ #ifndef LIBBUILD2_CONFIG_MODULE_HXX #define LIBBUILD2_CONFIG_MODULE_HXX -#include <map> - #include <libbutl/prefix-map.mxx> #include <libbuild2/types.hxx> @@ -55,7 +53,7 @@ namespace build2 // Priority order with INT32_MIN being the highest. Modules with the // same priority are saved in the order inserted. // - std::multimap<std::int32_t, const_iterator> order; + multimap<std::int32_t, const_iterator> order; pair<iterator, bool> insert (string name, int prio = 0) diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index b9856be..7490bc3 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -82,8 +82,6 @@ namespace build2 } } - using project_set = set<const scope*>; // Use pointers to get comparison. - // Return (first) whether an unused/inherited variable should be saved // according to the config.config.persist value and (second) whether the // user should be warned about it. @@ -722,7 +720,7 @@ namespace build2 // create_bootstrap_inner (rs); - if (rs.out_path () == rs.src_path ()) + if (rs.out_eq_src ()) fail (l) << "forwarding to source directory " << rs.src_path (); } else diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx index 3224dc1..df06aa8 100644 --- a/libbuild2/context.cxx +++ b/libbuild2/context.cxx @@ -35,7 +35,7 @@ namespace build2 create_global_scope (scope_map& m) { auto i (m.insert (dir_path ())); - scope& r (i->second); + scope& r (*i->second.scope); r.out_path_ = &i->first; return r; }; @@ -488,7 +488,7 @@ namespace build2 // if (c == '!' || (dir && dir->absolute ())) { - scope& s (c == '!' ? gs : sm.insert (*dir)->second); + scope& s (c == '!' ? gs : *sm.insert (*dir)->second.scope); auto p (s.vars.insert (*o)); assert (p.second); // Variable name is unique. diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx index e2d7343..077e3fd 100644 --- a/libbuild2/cxx/init.cxx +++ b/libbuild2/cxx/init.cxx @@ -6,6 +6,8 @@ #include <libbuild2/scope.hxx> #include <libbuild2/diagnostics.hxx> +#include <libbuild2/config/utility.hxx> + #include <libbuild2/cc/guess.hxx> #include <libbuild2/cc/module.hxx> @@ -93,11 +95,57 @@ namespace build2 // auto& vp (rs.var_pool ()); - //bool concepts (false); - //auto& v_c (vp.insert<bool> ("cxx.features.concepts")); + // Similar to config.cxx.std, config.cxx.features.* overrides + // cxx.features.*. + // + struct feature + { + optional<bool> value; // cxx.features.* value. + optional<bool> c_value; // config.cxx.features.* value. + bool result; // Calculated result value. + + feature& operator= (bool r) {result = r; return *this;} + + build2::value& value_; // cxx.features.* variable value. + const char* name_; // Feature name. + }; + + auto get_feature = [&rs, &vp] (const char* name) -> feature + { + auto& var (vp.insert<bool> (string ("cxx.features.") + name)); + auto& c_var (vp.insert<bool> (string ("config.cxx.features.") + name)); + + pair<value&, bool> val (rs.vars.insert (var)); + lookup l (config::lookup_config (rs, c_var)); + + optional<bool> v, c_v; + if (l.defined ()) + v = c_v = cast_false<bool> (*l); + else if (!val.second) + v = cast_false<bool> (val.first); + + return feature {v, c_v, false, val.first, name}; + }; + + auto set_feature = [&rs, &ci, v] (const feature& f) + { + if (f.c_value && *f.c_value != f.result) + { + fail << f.name_ << " cannot be " + << (*f.c_value ? "enabled" : "disabled") << " for " + << project (rs) << '@' << rs << + info << "C++ language standard is " + << (v != nullptr ? v->c_str () : "compiler-default") << + info << "C++ compiler is " << ci.signature << + info << f.name_ << " state requested with config.cxx.features." + << f.name_; + } + + f.value_ = f.result; + }; - bool modules (false); - auto& v_m (vp.insert<bool> ("cxx.features.modules")); + feature modules (get_feature ("modules")); + //feature concepts (get_feature ("concepts")); // NOTE: see also module sidebuild subproject if changing anything about // modules here. @@ -301,8 +349,7 @@ namespace build2 // Unless disabled by the user, try to enable C++ modules. // - lookup l; - if (!(l = rs[v_m]) || cast<bool> (l)) + if (!modules.value || *modules.value) { switch (ct) { @@ -315,7 +362,7 @@ namespace build2 // M;` syntax. And 16.4 (19.24) supports the global module // fragment. // - if (mj > 19 || (mj == 19 && mi >= (l ? 10 : 12))) + if (mj > 19 || (mj == 19 && mi >= (modules.value ? 10 : 12))) { prepend ( mj > 19 || mi >= 24 ? @@ -331,27 +378,19 @@ namespace build2 } case compiler_type::gcc: { - // We now use extended/experimental module mapper support which - // is currently only available in our c++-modules-ex branch. - // But let's allow forcing it to plain c++-modules in case - // things got merged or the user feels adventurous. + // We use the module mapper support which is only available + // since GCC 11. And since we are not yet capable of supporting + // generated headers via the mapper, we require the user to + // explicitly request modules. // - // @@ TMP: revise: for now must be forced (also in modules - // tests). - // - if (mj >= 11 && - l && - ci.version.build.find ("c++-modules") - /* - ci.version.build.find (l - ? "c++-modules" - : "c++-modules-ex")*/ != string::npos) + if (mj >= 11 && modules.value) { // Defines __cpp_modules=201907. @@ TMP: confirm. // prepend ("-fmodules-ts"); modules = true; } + break; } case compiler_type::clang: @@ -369,12 +408,13 @@ namespace build2 // // Also see Clang modules support hack in cc::compile. // - if (l) + if (modules.value) { prepend ("-D__cpp_modules=201704"); // p0629r0 mode.push_back ("-fmodules-ts"); // For the hack to work. modules = true; } + break; } case compiler_type::icc: @@ -383,8 +423,8 @@ namespace build2 } } - rs.assign (v_m) = modules; - //rs.assign (v_c) = concepts; + set_feature (modules); + //set_feature (concepts); } static const char* const hinters[] = {"c", nullptr}; @@ -440,16 +480,42 @@ namespace build2 vp.insert<strings> ("config.cxx.aoptions"), vp.insert<strings> ("config.cxx.libs"), - // List of translatable headers. Inclusions of such headers are + // Headers and header groups whose inclusion should or should not be // translated to the corresponding header unit imports. // // A header can be specified either as an absolute and normalized path - // or as a <>-style include name. The latter kind is automatically - // translated to the absolute form based on the compiler's system (as - // opposed to -I) header search paths. Note also that all entries must - // be specified before loading the cxx module. + // or as a <>-style include file or file pattern (for example, + // <vector>, <boost/**.hpp>). The latter kind is automatically + // resolved to the absolute form based on the compiler's system (as + // opposed to project's) header search paths. + // + // Currently recognized header groups are: + // + // std-importable -- translate importable standard library headers + // std -- translate all standard library headers + // all-importable -- translate all importable headers + // all -- translate all headers + // + // Note that a header may belong to multiple groups which are looked + // up from the most to least specific, for example: <vector>, + // std-importable, std, all-importable, all. + // + // A header or group can also be excluded from being translated, for + // example: + // + // std-importable <vector>@false + // + // The config.cxx.translate_include value is prepended (merged with + // override) into cxx.translate_include while loading the cxx.config + // module. The headers and header groups in cxx.translate_include are + // resolved while loading the cxx module. For example: + // + // cxx.translate_include = <map>@false # Can be overriden. + // using cxx.config + // cxx.translate_include =+ <set>@false # Cannot be overriden. + // using cxx // - &vp.insert<strings> ("config.cxx.translatable_headers"), + &vp.insert<cc::translatable_headers> ("config.cxx.translate_include"), vp.insert<process_path_ex> ("cxx.path"), vp.insert<strings> ("cxx.mode"), @@ -466,7 +532,7 @@ namespace build2 vp.insert<strings> ("cxx.aoptions"), vp.insert<strings> ("cxx.libs"), - &vp.insert<strings> ("cxx.translatable_headers"), + &vp.insert<cc::translatable_headers> ("cxx.translate_include"), vp["cc.poptions"], vp["cc.coptions"], @@ -494,6 +560,7 @@ namespace build2 vp["cc.type"], vp["cc.system"], vp["cc.module_name"], + vp["cc.importable"], vp["cc.reprocess"], // Ability to signal that source is already (partially) preprocessed. @@ -548,6 +615,7 @@ namespace build2 // vp.insert_alias (d.c_runtime, "cxx.runtime"); vp.insert_alias (d.c_module_name, "cxx.module_name"); + vp.insert_alias (d.c_importable, "cxx.importable"); auto& m (extra.set_module (new config_module (move (d)))); m.guess (rs, loc, extra.hints); @@ -671,7 +739,7 @@ namespace build2 }; auto& m (extra.set_module (new module (move (d)))); - m.init (rs, loc, extra.hints); + m.init (rs, loc, extra.hints, *cm.x_info); return true; } diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx index 2fa953d..ac76a62 100644 --- a/libbuild2/dist/operation.cxx +++ b/libbuild2/dist/operation.cxx @@ -6,8 +6,6 @@ #include <libbutl/sha1.mxx> #include <libbutl/sha256.mxx> -#include <libbutl/path-pattern.mxx> - #include <libbuild2/file.hxx> #include <libbuild2/dump.hxx> #include <libbuild2/scope.hxx> @@ -599,7 +597,7 @@ namespace build2 (rs->out_path () != t.dir && rs->src_path () != t.dir)) fail << "dist meta-operation target must be project root directory"; - if (rs->out_path () == rs->src_path ()) + if (rs->out_eq_src ()) fail << "in-tree distribution of target " << t << info << "distribution requires out-of-tree build"; diff --git a/libbuild2/dump.cxx b/libbuild2/dump.cxx index 95c7cae..7cd95dd 100644 --- a/libbuild2/dump.cxx +++ b/libbuild2/dump.cxx @@ -438,7 +438,7 @@ namespace build2 scope_map::const_iterator& i, bool rel) { - const scope& p (i->second); + const scope& p (*i->second.scope); const dir_path& d (i->first); ++i; @@ -484,7 +484,8 @@ namespace build2 vb = true; } - // Nested scopes of which we are an immediate parent. + // Nested scopes of which we are an immediate parent. Only consider the + // out hierarchy. // // Note that because we use the logical (rather than physical) parent, we // will be printing the logical scope hierarchy (i.e., a project with @@ -492,7 +493,7 @@ namespace build2 // scope). // for (auto e (p.ctx.scopes.end ()); - i != e && i->second.parent_scope () == &p; ) + i != e && i->second.out && i->second.scope->parent_scope () == &p; ) { if (vb) { @@ -541,8 +542,8 @@ namespace build2 void dump (const context& c, optional<action> a) { - auto i (c.scopes.cbegin ()); - assert (&i->second == &c.global_scope); + auto i (c.scopes.begin ()); + assert (i->second.scope == &c.global_scope); // We don't lock diag_stream here as dump() is supposed to be called from // the main thread prior/after to any other threads being spawned. @@ -556,9 +557,9 @@ namespace build2 void dump (const scope& s, const char* cind) { - const scope_map_base& m (s.ctx.scopes); // Iterator interface. - auto i (m.find (s.out_path ())); - assert (i != m.end () && &i->second == &s); + const scope_map& m (s.ctx.scopes); + auto i (m.find_exact (s.out_path ())); + assert (i != m.end () && i->second.scope == &s); string ind (cind); ostream& os (*diag_stream); diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index 5d1487f..87e21a6 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -399,7 +399,7 @@ namespace build2 const dir_path& src_root) { auto i (ctx.scopes.rw ().insert (out_root, true /* root */)); - scope& rs (i->second); + scope& rs (*i->second.scope); // Set out_path. Note that src_path is set in setup_root() below. // @@ -457,9 +457,17 @@ namespace build2 const dir_path& d (cast<dir_path> (v)); if (s.src_path_ == nullptr) - s.src_path_ = &d; + { + if (*s.out_path_ != d) + { + auto i (ctx.scopes.rw (s).insert (s, d)); + s.src_path_ = &i->first; + } + else + s.src_path_ = s.out_path_; + } else - assert (s.src_path_ == &d); + assert (*s.src_path_ == d); s.assign (ctx.var_forwarded) = forwarded; } @@ -469,7 +477,7 @@ namespace build2 const dir_path& out_base, const dir_path& src_base) { - scope& s (i->second); + scope& s (*i->second.scope); context& ctx (s.ctx); // Set src/out_base variables. @@ -496,7 +504,15 @@ namespace build2 assert (*s.out_path_ == out_base); if (s.src_path_ == nullptr) - s.src_path_ = &cast<dir_path> (sv); + { + if (out_base != src_base) + { + auto i (ctx.scopes.rw (s).insert (s, src_base)); + s.src_path_ = &i->first; + } + else + s.src_path_ = s.out_path_; + } else assert (*s.src_path_ == src_base); @@ -504,21 +520,22 @@ namespace build2 } pair<scope&, scope*> - switch_scope (scope& root, const dir_path& p, bool proj) + switch_scope (scope& root, const dir_path& out_base, bool proj) { // First, enter the scope into the map and see if it is in any project. If // it is not, then there is nothing else to do. // - auto i (root.ctx.scopes.rw (root).insert (p)); - scope& base (i->second); + auto i (root.ctx.scopes.rw (root).insert (out_base)); + scope& base (*i->second.scope); scope* rs (nullptr); if (proj && (rs = base.root_scope ()) != nullptr) { - // Path p can be src_base or out_base. Figure out which one it is. + // The path must be in the out (since we've inserted it as out into the + // scope map). // - dir_path out_base (p.sub (rs->out_path ()) ? p : out_src (p, *rs)); + assert (out_base.sub (rs->out_path ())); // Create and bootstrap root scope(s) of subproject(s) that this scope // may belong to. If any were created, load them. Note that we need to @@ -534,8 +551,7 @@ namespace build2 // Now we can figure out src_base and finish setting the scope. // - dir_path src_base (src_out (out_base, *rs)); - setup_base (i, move (out_base), move (src_base)); + setup_base (i, out_base, src_out (out_base, *rs)); } return pair<scope&, scope*> (base, rs); @@ -1316,7 +1332,7 @@ namespace build2 // probably be tried first since that src_root was explicitly configured // by the user. After that, #2 followed by #1 seems reasonable. // - scope& rs (create_root (ctx, out_root, dir_path ())->second); + scope& rs (*create_root (ctx, out_root, dir_path ())->second.scope); bool bstrapped (bootstrapped (rs)); @@ -1383,7 +1399,7 @@ namespace build2 // The same logic to src_root as in create_bootstrap_outer(). // - scope& rs (create_root (ctx, out_root, dir_path ())->second); + scope& rs (*create_root (ctx, out_root, dir_path ())->second.scope); optional<bool> altn; if (!bootstrapped (rs)) @@ -1618,7 +1634,7 @@ namespace build2 assert (!forwarded || out_root != src_root); auto i (create_root (ctx, out_root, src_root)); - scope& rs (i->second); + scope& rs (*i->second.scope); if (!bootstrapped (rs)) { @@ -2327,7 +2343,7 @@ namespace build2 { bool top (proot == nullptr); - root = &create_root (ctx, out_root, src_root)->second; + root = create_root (ctx, out_root, src_root)->second.scope; bool bstrapped (bootstrapped (*root)); diff --git a/libbuild2/file.hxx b/libbuild2/file.hxx index e0291fe..0c4ad62 100644 --- a/libbuild2/file.hxx +++ b/libbuild2/file.hxx @@ -146,7 +146,7 @@ namespace build2 // second. // LIBBUILD2_SYMEXPORT pair<scope&, scope*> - switch_scope (scope& root, const dir_path&, bool project = true); + switch_scope (scope& root, const dir_path& out_base, bool project = true); // Bootstrap and optionally load an ad hoc (sub)project (i.e., the kind that // is not discovered and loaded automatically by bootstrap/load functions diff --git a/libbuild2/function.hxx b/libbuild2/function.hxx index 8fdf8f4..6654257 100644 --- a/libbuild2/function.hxx +++ b/libbuild2/function.hxx @@ -4,7 +4,6 @@ #ifndef LIBBUILD2_FUNCTION_HXX #define LIBBUILD2_FUNCTION_HXX -#include <map> #include <utility> // index_sequence #include <type_traits> // aligned_storage @@ -196,7 +195,7 @@ namespace build2 class LIBBUILD2_SYMEXPORT function_map { public: - using map_type = std::map<string, function_overloads>; + using map_type = map<string, function_overloads>; using iterator = map_type::iterator; using const_iterator = map_type::const_iterator; diff --git a/libbuild2/install/utility.hxx b/libbuild2/install/utility.hxx index ee78e17..2c0ca56 100644 --- a/libbuild2/install/utility.hxx +++ b/libbuild2/install/utility.hxx @@ -28,7 +28,7 @@ namespace build2 *s.var_pool ().find ("install"))); if (r.second) // Already set by the user? - r.first.get () = path_cast<path> (move (d)); + r.first = path_cast<path> (move (d)); } template <typename T> @@ -46,7 +46,7 @@ namespace build2 *s.var_pool ().find ("install.mode"))); if (r.second) // Already set by the user? - r.first.get () = move (m); + r.first = move (m); } template <typename T> diff --git a/libbuild2/module.hxx b/libbuild2/module.hxx index 770e694..8223bae 100644 --- a/libbuild2/module.hxx +++ b/libbuild2/module.hxx @@ -4,8 +4,6 @@ #ifndef LIBBUILD2_MODULE_HXX #define LIBBUILD2_MODULE_HXX -#include <map> - #include <libbuild2/types.hxx> #include <libbuild2/forward.hxx> #include <libbuild2/utility.hxx> @@ -275,7 +273,7 @@ namespace build2 // A NULL entry for the main module indicates that a module library was not // found. // - using loaded_module_map = std::map<string, const module_functions*>; + using loaded_module_map = map<string, const module_functions*>; // The loaded_modules map is locked per top-level (as opposed to nested) // context (see context.hxx for details). diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index ab4fa48..def4654 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -7,7 +7,6 @@ #include <iostream> // cout #include <libbutl/filesystem.mxx> // path_search -#include <libbutl/path-pattern.mxx> #include <libbuild2/rule.hxx> #include <libbuild2/dump.hxx> @@ -4754,21 +4753,26 @@ namespace build2 // bool unique (r.empty () && path_pattern_recursive (path (p)) <= 1); - function<void (string&&, optional<string>&&)> appf; + struct data + { + const optional<string>& e; + const dir_path& sp; + function<void (string&&, optional<string>&&)> appf; + + } d {e, *sp, nullptr}; + if (unique) - appf = [a, &append] (string&& v, optional<string>&& e) + d.appf = [a, &append] (string&& v, optional<string>&& e) { append (move (v), move (e), a); }; else - appf = [a, &include_match] (string&& v, optional<string>&& e) + d.appf = [a, &include_match] (string&& v, optional<string>&& e) { include_match (move (v), move (e), a); }; - auto process = [this, &e, &appf, sp] (path&& m, - const string& p, - bool interm) + auto process = [&d, this] (path&& m, const string& p, bool interm) { // Ignore entries that start with a dot unless the pattern that // matched them also starts with a dot. Also ignore directories @@ -4780,14 +4784,14 @@ namespace build2 (root_ != nullptr && root_->root_extra != nullptr && m.to_directory () && - exists (*sp / m / root_->root_extra->buildignore_file))) + exists (d.sp / m / root_->root_extra->buildignore_file))) return !interm; // Note that we have to make copies of the extension since there will // multiple entries for each pattern. // if (!interm) - appf (move (m).representation (), optional<string> (e)); + d.appf (move (m).representation (), optional<string> (d.e)); return true; }; diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx index 1e924f0..3efb94a 100644 --- a/libbuild2/parser.hxx +++ b/libbuild2/parser.hxx @@ -531,7 +531,7 @@ namespace build2 // project. So both must be saved and restored. // void - switch_scope (const dir_path&); + switch_scope (const dir_path& out_base); void process_default_target (token&); diff --git a/libbuild2/rule-map.hxx b/libbuild2/rule-map.hxx index df59548..8014d02 100644 --- a/libbuild2/rule-map.hxx +++ b/libbuild2/rule-map.hxx @@ -4,8 +4,6 @@ #ifndef LIBBUILD2_RULE_MAP_HXX #define LIBBUILD2_RULE_MAP_HXX -#include <map> - #include <libbutl/prefix-map.mxx> #include <libbuild2/types.hxx> @@ -19,7 +17,7 @@ namespace build2 using hint_rule_map = butl::prefix_map<string, reference_wrapper<const rule>, '.'>; - using target_type_rule_map = std::map<const target_type*, hint_rule_map>; + using target_type_rule_map = map<const target_type*, hint_rule_map>; // This is an "indexed map" with operation_id being the index. Entry // with id 0 is a wildcard. diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx index 53c859f..29e0c55 100644 --- a/libbuild2/scope.cxx +++ b/libbuild2/scope.cxx @@ -944,10 +944,21 @@ namespace build2 auto scope_map:: insert (const dir_path& k, bool root) -> iterator { - scope_map_base& m (*this); + auto er (map_.emplace (k, true /* out */)); - auto er (m.emplace (k, scope (ctx, true /* global */))); - scope& s (er.first->second); + if (er.second) + { + er.first->second.scope = new scope (ctx, true /* global */); + } + else if (!er.first->second.out) + { + // This can potentially be triggered if we use the same directory as one + // project's out and another's src. + // + fail << "directory " << k << " is used as both src and out scope"; + } + + scope& s (*er.first->second.scope); // If this is a new scope, update the parent chain. // @@ -958,14 +969,14 @@ namespace build2 // Update scopes of which we are a new parent/root (unless this is the // global scope). Also find our parent while at it. // - if (m.size () > 1) + if (map_.size () > 1) { // The first entry is ourselves. // - auto r (m.find_sub (k)); + auto r (map_.find_sub (k)); for (++r.first; r.first != r.second; ++r.first) { - scope& c (r.first->second); + scope& c (*r.first->second.scope); // The first scope of which we are a parent is the least (shortest) // one which means there is no other scope between it and our @@ -995,10 +1006,10 @@ namespace build2 { // Upgrade to root scope. // - auto r (m.find_sub (k)); + auto r (map_.find_sub (k)); for (++r.first; r.first != r.second; ++r.first) { - scope& c (r.first->second); + scope& c (*r.first->second.scope); if (c.root_ == s.root_) // No intermediate root. c.root_ = &s; @@ -1010,14 +1021,36 @@ namespace build2 return er.first; } + auto scope_map:: + insert (scope& s, const dir_path& k) -> iterator + { + auto er (map_.emplace (k, false /* out */)); + + if (er.second) + { + er.first->second.scope = &s; + } + else if (!er.first->second.out) + { + assert (er.first->second.scope == &s); + } + else + { + // This can be triggered, for example, by specifying a variable override + // with src instead of out directory. + // + fail << "directory " << k << " is used as both src and out scope"; + } + + return er.first; + } + scope& scope_map:: find (const dir_path& k) { assert (k.normalized (false)); // Allow non-canonical dir separators. - - scope_map_base& m (*this); - auto i (m.find_sup (k)); - assert (i != m.end ()); // Should have global scope. - return i->second; + auto i (map_.find_sup (k)); + assert (i != map_.end ()); // Should have global scope. + return *i->second.scope; } } diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index d441267..11fdfe4 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -4,7 +4,6 @@ #ifndef LIBBUILD2_SCOPE_HXX #define LIBBUILD2_SCOPE_HXX -#include <map> #include <unordered_set> #include <libbuild2/types.hxx> @@ -27,7 +26,7 @@ namespace build2 { class dir; - using subprojects = std::map<project_name, dir_path>; + using subprojects = map<project_name, dir_path>; LIBBUILD2_SYMEXPORT ostream& operator<< (ostream&, const subprojects&); // Print as name@dir sequence. @@ -44,8 +43,10 @@ namespace build2 const dir_path& out_path () const {return *out_path_;} const dir_path& src_path () const {return *src_path_;} - // The first is a pointer to the key in scope_map. The second is a pointer - // to the src_root/base variable value, if any (i.e., it can be NULL). + bool out_eq_src () const {return out_path_ == src_path_;} + + // These are pointers to the keys in scope_map. The second can be NULL + // during bootstrap until initialized. // const dir_path* out_path_ = nullptr; const dir_path* src_path_ = nullptr; @@ -140,12 +141,20 @@ namespace build2 return lookup (var, &tt, &tn).first; } + lookup_type + lookup (const variable& var, + const target_type& tt, const string& tn, + const target_type& gt, const string& gn) const + { + return lookup (var, &tt, &tn, >, &gn).first; + } + pair<lookup_type, size_t> lookup (const variable& var, - const target_type* tt = nullptr, - const string* tn = nullptr) const + const target_type* tt = nullptr, const string* tn = nullptr, + const target_type* gt = nullptr, const string* gn = nullptr) const { - auto p (lookup_original (var, tt, tn)); + auto p (lookup_original (var, tt, tn, gt, gn)); return var.overrides == nullptr ? p : lookup_override (var, move (p)); } @@ -416,8 +425,7 @@ namespace build2 function<callback> post; }; - using operation_callback_map = std::multimap<action_id, - operation_callback>; + using operation_callback_map = multimap<action_id, operation_callback>; operation_callback_map operation_callbacks; @@ -621,21 +629,53 @@ namespace build2 } }; - // Scope map. + // Scope map. Protected by the phase mutex. // - // Protected by the phase mutex. Note that the scope map is only for paths - // from the out tree. + // While it contains both out and src paths, the latter is not available + // during bootstrap (see setup_root() and setup_base() for details). // - using scope_map_base = dir_path_map<scope>; - - class scope_map: public scope_map_base + class scope_map { public: + struct scope_ptr + { + using scope_type = build2::scope; + + scope_type* scope; + bool out; + + scope_ptr (bool o): scope (nullptr), out (o) {} + ~scope_ptr () {if (out) delete scope;} + + scope_ptr (scope_ptr&& x) // For GCC 4.9 + : scope (x.scope), out (x.out) + { + x.scope = nullptr; + } + + scope_ptr& operator= (scope_ptr&&) = delete; + + scope_ptr (const scope_ptr&) = delete; + scope_ptr& operator= (const scope_ptr&) = delete; + }; + + using map_type = dir_path_map<scope_ptr>; + + using iterator = map_type::iterator; + using const_iterator = map_type::const_iterator; + + // Insert a scope given its out path. + // // Note that we assume the first insertion into the map is always the // global scope with empty key. // LIBBUILD2_SYMEXPORT iterator - insert (const dir_path&, bool root = false); + insert (const dir_path& our_path, bool root = false); + + // Insert a shallow reference to the scope for its src path. + // + LIBBUILD2_SYMEXPORT iterator + insert (scope&, const dir_path& src_path); // Find the most qualified scope that encompasses this path. // @@ -661,6 +701,10 @@ namespace build2 return find (path_cast<dir_path> (p)); } + const_iterator begin () const {return map_.begin ();} + const_iterator end () const {return map_.end ();} + const_iterator find_exact (const dir_path& d) const {return map_.find (d);} + // RW access. // public: @@ -685,6 +729,7 @@ namespace build2 private: context& ctx; + map_type map_; }; } diff --git a/libbuild2/script/regex.hxx b/libbuild2/script/regex.hxx index 6d2c5c6..e043c99 100644 --- a/libbuild2/script/regex.hxx +++ b/libbuild2/script/regex.hxx @@ -25,10 +25,10 @@ namespace build2 enum class char_flags: uint16_t { icase = 0x1, // Case-insensitive match. - idot = 0x2, // Invert '.' escaping. + idot = 0x2, // Invert '.' escaping. - none = 0 - }; + none = 0 + }; // Restricts valid standard flags to just {icase}, extends with custom // flags {idot}. @@ -66,9 +66,9 @@ namespace build2 enum class line_type { special, - literal, - regex - }; + literal, + regex + }; struct line_char { @@ -625,7 +625,8 @@ namespace std // specialize the class template to behave as the __match_any<line_char> // instantiation does (that luckily has all the functions in place). // -#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 10000 +//#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 11000 +#ifdef _LIBCPP_VERSION template <> class __match_any_but_newline<build2::script::regex::line_char> : public __match_any<build2::script::regex::line_char> diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx index 58ba23d..0b49dea 100644 --- a/libbuild2/script/run.cxx +++ b/libbuild2/script/run.cxx @@ -15,7 +15,6 @@ #include <libbutl/builtin.mxx> #include <libbutl/fdstream.mxx> // fdopen_mode, fddup() #include <libbutl/filesystem.mxx> // path_search() -#include <libbutl/path-pattern.mxx> #include <libbuild2/filesystem.hxx> #include <libbuild2/diagnostics.hxx> diff --git a/libbuild2/search.cxx b/libbuild2/search.cxx index 25a4199..fca19ea 100644 --- a/libbuild2/search.cxx +++ b/libbuild2/search.cxx @@ -175,7 +175,7 @@ namespace build2 if (tk.out->empty ()) { - if (s->out_path () != s->src_path ()) + if (!s->out_eq_src ()) out = out_src (d, *s->root_scope ()); } else diff --git a/libbuild2/target-key.hxx b/libbuild2/target-key.hxx index 62bcc25..2cc58f9 100644 --- a/libbuild2/target-key.hxx +++ b/libbuild2/target-key.hxx @@ -4,7 +4,6 @@ #ifndef LIBBUILD2_TARGET_KEY_HXX #define LIBBUILD2_TARGET_KEY_HXX -#include <map> #include <cstring> // strcmp() #include <libbuild2/types.hxx> diff --git a/libbuild2/target-type.hxx b/libbuild2/target-type.hxx index 88171f5..913432e 100644 --- a/libbuild2/target-type.hxx +++ b/libbuild2/target-type.hxx @@ -4,8 +4,6 @@ #ifndef LIBBUILD2_TARGET_TYPE_HXX #define LIBBUILD2_TARGET_TYPE_HXX -#include <map> - #include <libbuild2/types.hxx> #include <libbuild2/forward.hxx> #include <libbuild2/utility.hxx> @@ -203,8 +201,8 @@ namespace build2 bool d_; }; - std::map<string, target_type_ref> type_map_; - std::map<string, reference_wrapper<const target_type>> file_map_; + map<string, target_type_ref> type_map_; + map<string, reference_wrapper<const target_type>> file_map_; }; } diff --git a/libbuild2/test/script/parser.hxx b/libbuild2/test/script/parser.hxx index f8c3f21..f118ad3 100644 --- a/libbuild2/test/script/parser.hxx +++ b/libbuild2/test/script/parser.hxx @@ -4,6 +4,7 @@ #ifndef LIBBUILD2_TEST_SCRIPT_PARSER_HXX #define LIBBUILD2_TEST_SCRIPT_PARSER_HXX +#include <set> #include <unordered_map> #include <libbuild2/types.hxx> diff --git a/libbuild2/types.hxx b/libbuild2/types.hxx index 60101b3..6877f92 100644 --- a/libbuild2/types.hxx +++ b/libbuild2/types.hxx @@ -13,6 +13,7 @@ # include <libbuild2/config.hxx> #endif +#include <map> #include <array> #include <tuple> #include <vector> @@ -92,6 +93,8 @@ namespace build2 using std::shared_ptr; using std::weak_ptr; + using std::map; + using std::multimap; using std::array; using std::vector; using butl::vector_view; // <libbutl/vector-view.mxx> diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx index a07d928..4cfdf15 100644 --- a/libbuild2/utility.hxx +++ b/libbuild2/utility.hxx @@ -4,7 +4,6 @@ #ifndef LIBBUILD2_UTILITY_HXX #define LIBBUILD2_UTILITY_HXX -#include <map> #include <tuple> // make_tuple() #include <memory> // make_shared() #include <string> // to_string() @@ -18,6 +17,7 @@ #include <libbutl/utility.mxx> // combine_hash(), reverse_iterate(), etc #include <libbutl/fdstream.mxx> +#include <libbutl/path-pattern.mxx> #include <libbuild2/types.hxx> #include <libbuild2/forward.hxx> @@ -94,6 +94,10 @@ namespace build2 using butl::open_file_or_stdin; using butl::open_file_or_stdout; + // <libbutl/path-pattern.mxx> + // + using butl::path_pattern; + // Diagnostics state (verbosity level, etc; see <libbuild2/diagnostics.hxx>). // // Note on naming of values (here and in the global state below) that come @@ -561,7 +565,7 @@ namespace build2 } private: - std::map<string, T> cache_; + map<string, T> cache_; mutable mutex mutex_; }; diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx index f5476b5..917b9e7 100644 --- a/libbuild2/variable.cxx +++ b/libbuild2/variable.cxx @@ -1736,7 +1736,7 @@ namespace build2 return pair<value_data*, const variable&> (r, p.second); } - pair<reference_wrapper<value>, bool> variable_map:: + pair<value&, bool> variable_map:: insert (const variable& var, bool typed) { assert (!global_ || ctx->phase == run_phase::load); @@ -1756,7 +1756,7 @@ namespace build2 r.version++; - return make_pair (reference_wrapper<value> (r), p.second); + return pair<value&, bool> (r, p.second); } // variable_type_map @@ -1832,8 +1832,26 @@ namespace build2 value_traits<vector<pair<string, string>>>; template struct LIBBUILD2_DEFEXPORT - value_traits<std::map<string, string>>; + value_traits<vector<pair<string, optional<string>>>>; template struct LIBBUILD2_DEFEXPORT - value_traits<std::map<project_name, dir_path>>; + value_traits<vector<pair<optional<string>, string>>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<vector<pair<string, optional<bool>>>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<string, string>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<string, optional<string>>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<optional<string>, string>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<string, optional<bool>>>; + + template struct LIBBUILD2_DEFEXPORT + value_traits<map<project_name, dir_path>>; } diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx index a671978..898648c 100644 --- a/libbuild2/variable.hxx +++ b/libbuild2/variable.hxx @@ -4,7 +4,6 @@ #ifndef LIBBUILD2_VARIABLE_HXX #define LIBBUILD2_VARIABLE_HXX -#include <map> #include <set> #include <type_traits> // aligned_storage #include <unordered_map> @@ -339,15 +338,17 @@ namespace build2 // Assign/Append/Prepend. // public: - // Assign/append a typed value. For assign, LHS should be either of the - // same type or untyped. For append, LHS should be either of the same type - // or untyped and NULL. + // Assign/append/prepend a typed value. For assign, LHS should be either + // of the same type or untyped. For append, LHS should be either of the + // same type or untyped and NULL. // template <typename T> value& operator= (T); template <typename T> value& operator+= (T); + template <typename T> value& prepend (T); value& operator= (names); value& operator+= (names); + //value& prepend (names); // See below. template <typename T> value& operator= (T* v) { return v != nullptr ? *this = *v : *this = nullptr;} @@ -355,8 +356,12 @@ namespace build2 template <typename T> value& operator+= (T* v) { return v != nullptr ? *this += *v : *this;} + template <typename T> value& prepend (T* v) { + return v != nullptr ? prepend (*v) : *this;} + value& operator= (const char* v) {return *this = string (v);} value& operator+= (const char* v) {return *this += string (v);} + value& prepend (const char* v) {return prepend (string (v));} // Assign/append/prepend raw data. Variable is optional and is only used // for diagnostics. @@ -610,6 +615,9 @@ namespace build2 // static const build2::value_type value_type; // }; + template <typename T> + struct value_traits<const T>: value_traits<T> {}; + // Convert name to a simple value. Throw invalid_argument (with a message) // if the name is not a valid representation of value (in which case the // name remains unchanged for diagnostics). The second version is called for @@ -897,6 +905,9 @@ namespace build2 // half of a pair). If both are empty then this is an empty value (and not a // pair of two empties). // + // @@ Maybe we should redo this with optional<> to signify which half can + // be missing? + // template <> struct LIBBUILD2_SYMEXPORT value_traits<name_pair> { @@ -1004,9 +1015,67 @@ namespace build2 static const build2::value_type value_type; }; + // optional<T> + // + // This is an incomplete implementation meant to provide enough support only + // to be usable as elements of containers. + // + template <typename T> + struct value_traits<optional<T>> + { + static int compare (const optional<T>&, const optional<T>&); + }; + + // pair<F, S> + // + // Either F or S can be optional<T> making the corresponding half of the + // pair optional. + // + // This is an incomplete implementation meant to provide enough support only + // to be usable as elements of containers. + // + template <typename F, typename S> + struct pair_value_traits + { + static pair<F, S> + convert (name&&, name*, const char*, const char*, const variable*); + + static void + reverse (const F&, const S&, names&); + }; + + template <typename F, typename S> + struct pair_value_traits<F, optional<S>> + { + static pair<F, optional<S>> + convert (name&&, name*, const char*, const char*, const variable*); + + static void + reverse (const F&, const optional<S>&, names&); + }; + + template <typename F, typename S> + struct pair_value_traits<optional<F>, S> + { + static pair<optional<F>, S> + convert (name&&, name*, const char*, const char*, const variable*); + + static void + reverse (const optional<F>&, const S&, names&); + }; + + template <typename F, typename S> + struct value_traits<pair<F, S>>: pair_value_traits<F, S> + { + static int compare (const pair<F, S>&, const pair<F, S>&); + }; + // vector<T> // template <typename T> + struct vector_value_type; + + template <typename T> struct value_traits<vector<T>> { static_assert (sizeof (vector<T>) <= value::size_, "insufficient space"); @@ -1018,20 +1087,17 @@ namespace build2 static bool empty (const vector<T>& x) {return x.empty ();} static const vector<T> empty_instance; - - // Make sure these are static-initialized together. Failed that VC will - // make sure it's done in the wrong order. - // - struct value_type_ex: build2::value_type - { - string type_name; - value_type_ex (value_type&&); - }; - static const value_type_ex value_type; + static const vector_value_type<T> value_type; }; // vector<pair<K, V>> // + // Either K or V can be optional<T> making the corresponding half of the + // pair optional. + // + template <typename K, typename V> + struct pair_vector_value_type; + template <typename K, typename V> struct value_traits<vector<pair<K, V>>> { @@ -1045,44 +1111,33 @@ namespace build2 static bool empty (const vector<pair<K, V>>& x) {return x.empty ();} static const vector<pair<K, V>> empty_instance; - - // Make sure these are static-initialized together. Failed that VC will - // make sure it's done in the wrong order. - // - struct value_type_ex: build2::value_type - { - string type_name; - value_type_ex (value_type&&); - }; - static const value_type_ex value_type; + static const pair_vector_value_type<K, V> value_type; }; // map<K, V> // + // Either K or V can be optional<T> making the key or value optional. + // + // Note that append/+= is non-overriding (like insert()) while prepend/=+ + // is (like insert_or_assign()). + // template <typename K, typename V> - struct value_traits<std::map<K, V>> + struct map_value_type; + + template <typename K, typename V> + struct value_traits<map<K, V>> { - template <typename K1, typename V1> using map = std::map<K1, V1>; + template <typename K1, typename V1> using map = map<K1, V1>; static_assert (sizeof (map<K, V>) <= value::size_, "insufficient space"); static void assign (value&, map<K, V>&&); static void append (value&, map<K, V>&&); - static void prepend (value& v, map<K, V>&& x) { - return append (v, move (x));} + static void prepend (value&, map<K, V>&&); static bool empty (const map<K, V>& x) {return x.empty ();} static const map<K, V> empty_instance; - - // Make sure these are static-initialized together. Failed that VC will - // make sure it's done in the wrong order. - // - struct value_type_ex: build2::value_type - { - string type_name; - value_type_ex (value_type&&); - }; - static const value_type_ex value_type; + static const map_value_type<K, V> value_type; }; // Explicitly pre-instantiate and export value_traits templates for @@ -1102,10 +1157,28 @@ namespace build2 value_traits<vector<pair<string, string>>>; extern template struct LIBBUILD2_DECEXPORT - value_traits<std::map<string, string>>; + value_traits<vector<pair<string, optional<string>>>>; + + extern template struct LIBBUILD2_DECEXPORT + value_traits<vector<pair<optional<string>, string>>>; + + extern template struct LIBBUILD2_DECEXPORT + value_traits<vector<pair<string, optional<bool>>>>; + + extern template struct LIBBUILD2_DECEXPORT + value_traits<map<string, string>>; + + extern template struct LIBBUILD2_DECEXPORT + value_traits<map<string, optional<string>>>; + + extern template struct LIBBUILD2_DECEXPORT + value_traits<map<optional<string>, string>>; + + extern template struct LIBBUILD2_DECEXPORT + value_traits<map<string, optional<bool>>>; extern template struct LIBBUILD2_DECEXPORT - value_traits<std::map<project_name, dir_path>>; + value_traits<map<project_name, dir_path>>; // var_subprojects // Project-wide (as opposed to global) variable overrides (see context ctor // for details). @@ -1523,7 +1596,7 @@ namespace build2 // will be NULL) was actually inserted. Similar to find(), if typed is // false, leave the value untyped even if the variable is. // - pair<reference_wrapper<value>, bool> + pair<value&, bool> insert (const variable&, bool typed = true); pair<const_iterator, const_iterator> @@ -1638,7 +1711,7 @@ namespace build2 stem_version (sver) {} }; - using map_type = std::map<K, entry_type>; + using map_type = map<K, entry_type>; map_type m_; }; @@ -1660,7 +1733,7 @@ namespace build2 class variable_pattern_map { public: - using map_type = std::map<string, variable_map>; + using map_type = map<string, variable_map>; using const_iterator = map_type::const_iterator; using const_reverse_iterator = map_type::const_reverse_iterator; @@ -1688,7 +1761,7 @@ namespace build2 class LIBBUILD2_SYMEXPORT variable_type_map { public: - using map_type = std::map<reference_wrapper<const target_type>, + using map_type = map<reference_wrapper<const target_type>, variable_pattern_map>; using const_iterator = map_type::const_iterator; diff --git a/libbuild2/variable.ixx b/libbuild2/variable.ixx index c8f9541..a84c012 100644 --- a/libbuild2/variable.ixx +++ b/libbuild2/variable.ixx @@ -114,6 +114,22 @@ namespace build2 return *this; } + template <typename T> + inline value& value:: + prepend (T v) + { + assert (type == &value_traits<T>::value_type || (type == nullptr && null)); + + // Prepare the receiving value. + // + if (type == nullptr) + type = &value_traits<T>::value_type; + + value_traits<T>::prepend (*this, move (v)); + null = false; + return *this; + } + inline value& value:: operator= (names v) { @@ -728,6 +744,31 @@ namespace build2 : s); } + // optional<T> + // + template <typename T> + inline int value_traits<optional<T>>:: + compare (const optional<T>& l, const optional<T>& r) + { + return l + ? (r ? value_traits<T>::compare (*l, *r) : 1) + : (r ? -1 : 0); + } + + // pair<F, S> value + // + template <typename F, typename S> + inline int value_traits<pair<F, S>>:: + compare (const pair<F, S>& l, const pair<F, S>& r) + { + int i (value_traits<F>::compare (l.first, r.first)); + + if (i == 0) + i = value_traits<S>::compare (l.second, r.second); + + return i; + } + // vector<T> value // template <typename T> @@ -812,7 +853,7 @@ namespace build2 // map<K, V> value // template <typename K, typename V> - inline void value_traits<std::map<K, V>>:: + inline void value_traits<map<K, V>>:: assign (value& v, map<K, V>&& x) { if (v) @@ -822,7 +863,7 @@ namespace build2 } template <typename K, typename V> - inline void value_traits<std::map<K, V>>:: + inline void value_traits<map<K, V>>:: append (value& v, map<K, V>&& x) { if (v) @@ -842,6 +883,26 @@ namespace build2 new (&v.data_) map<K, V> (move (x)); } + template <typename K, typename V> + inline void value_traits<map<K, V>>:: + prepend (value& v, map<K, V>&& x) + { + if (v) + { + map<K, V>& m (v.as<map<K, V>> ()); + + m.swap (x); + + // Note that this will only move values. Keys (being const) are still + // copied. + // + m.insert (make_move_iterator (x.begin ()), + make_move_iterator (x.end ())); + } + else + new (&v.data_) map<K, V> (move (x)); + } + // variable_pool // inline const variable& variable_pool:: diff --git a/libbuild2/variable.txx b/libbuild2/variable.txx index 8fa1d7c..3e4a9f3 100644 --- a/libbuild2/variable.txx +++ b/libbuild2/variable.txx @@ -241,9 +241,228 @@ namespace build2 return value_traits<T>::compare (l.as<T> (), r.as<T> ()); } - // vector<T> value + // pair<F, S> value // + template <typename F, typename S> + pair<F, S> pair_value_traits<F, S>:: + convert (name&& l, name* r, + const char* type, const char* what, const variable* var) + { + if (!l.pair) + { + diag_record dr (fail); + + dr << type << ' ' << what << (*what != '\0' ? " " : "") + << "pair expected instead of '" << l << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + } + + if (l.pair != '@') + { + diag_record dr (fail); + + dr << "unexpected pair style for " + << type << ' ' << what << (*what != '\0' ? " " : "") + << "key-value pair '" + << l << "'" << l.pair << "'" << *r << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + } + + try + { + F f (value_traits<F>::convert (move (l), nullptr)); + + try + { + S s (value_traits<S>::convert (move (*r), nullptr)); + + return pair<F, S> (move (f), move (s)); + } + catch (const invalid_argument&) + { + diag_record dr (fail); + + dr << "invalid " << value_traits<S>::value_type.name + << " second have of pair '" << *r << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << endf; + } + } + catch (const invalid_argument&) + { + diag_record dr (fail); + + dr << "invalid " << value_traits<F>::value_type.name + << " first have of pair '" << l << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << endf; + } + } + + template <typename F, typename S> + pair<F, optional<S>> pair_value_traits<F, optional<S>>:: + convert (name&& l, name* r, + const char* type, const char* what, const variable* var) + { + if (l.pair && l.pair != '@') + { + diag_record dr (fail); + + dr << "unexpected pair style for " + << type << ' ' << what << (*what != '\0' ? " " : "") + << "key-value pair '" + << l << "'" << l.pair << "'" << *r << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + } + + try + { + F f (value_traits<F>::convert (move (l), nullptr)); + + try + { + optional<S> s; + + if (l.pair) + s = value_traits<S>::convert (move (*r), nullptr); + + return pair<F, optional<S>> (move (f), move (s)); + } + catch (const invalid_argument&) + { + diag_record dr (fail); + + dr << "invalid " << value_traits<S>::value_type.name + << " second have of pair '" << *r << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << endf; + } + } + catch (const invalid_argument&) + { + diag_record dr (fail); + + dr << "invalid " << value_traits<F>::value_type.name + << " first have of pair '" << l << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << endf; + } + } + template <typename F, typename S> + pair<optional<F>, S> pair_value_traits<optional<F>, S>:: + convert (name&& l, name* r, + const char* type, const char* what, const variable* var) + { + if (l.pair && l.pair != '@') + { + diag_record dr (fail); + + dr << "unexpected pair style for " + << type << ' ' << what << (*what != '\0' ? " " : "") + << "key-value pair '" + << l << "'" << l.pair << "'" << *r << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + } + + try + { + optional<F> f; + + if (l.pair) + { + f = value_traits<F>::convert (move (l), nullptr); + l = move (*r); // Shift. + } + + try + { + S s (value_traits<S>::convert (move (l), nullptr)); + + return pair<optional<F>, S> (move (f), move (s)); + } + catch (const invalid_argument&) + { + diag_record dr (fail); + + dr << "invalid " << value_traits<S>::value_type.name + << " second have of pair '" << l << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << endf; + } + } + catch (const invalid_argument&) + { + diag_record dr (fail); + + dr << "invalid " << value_traits<F>::value_type.name + << " first have of pair '" << l << "'"; + + if (var != nullptr) + dr << " in variable " << var->name; + + dr << endf; + } + } + + template <typename F, typename S> + void pair_value_traits<F, S>:: + reverse (const F& f, const S& s, names& ns) + { + ns.push_back (value_traits<F>::reverse (f)); + ns.back ().pair = '@'; + ns.push_back (value_traits<S>::reverse (s)); + } + + template <typename F, typename S> + void pair_value_traits<F, optional<S>>:: + reverse (const F& f, const optional<S>& s, names& ns) + { + ns.push_back (value_traits<F>::reverse (f)); + if (s) + { + ns.back ().pair = '@'; + ns.push_back (value_traits<S>::reverse (*s)); + } + } + + template <typename F, typename S> + void pair_value_traits<optional<F>, S>:: + reverse (const optional<F>& f, const S& s, names& ns) + { + if (f) + { + ns.push_back (value_traits<F>::reverse (*f)); + ns.back ().pair = '@'; + } + ns.push_back (value_traits<S>::reverse (s)); + } + + // vector<T> value + // template <typename T> vector<T> value_traits<vector<T>>:: convert (names&& ns) @@ -396,21 +615,28 @@ namespace build2 return 0; } + // Make sure these are static-initialized together. Failed that VC will make + // sure it's done in the wrong order. + // template <typename T> - value_traits<vector<T>>::value_type_ex:: - value_type_ex (value_type&& v) - : value_type (move (v)) + struct vector_value_type: value_type { - type_name = value_traits<T>::type_name; - type_name += 's'; - name = type_name.c_str (); - } + string type_name; + + vector_value_type (value_type&& v) + : value_type (move (v)) + { + type_name = value_traits<T>::type_name; + type_name += 's'; + name = type_name.c_str (); + } + }; template <typename T> const vector<T> value_traits<vector<T>>::empty_instance; template <typename T> - const typename value_traits<vector<T>>::value_type_ex + const vector_value_type<T> value_traits<vector<T>>::value_type = build2::value_type // VC14 wants =. { nullptr, // Patched above. @@ -444,63 +670,14 @@ namespace build2 for (auto i (ns.begin ()); i != ns.end (); ++i) { name& l (*i); + name* r (l.pair ? &*++i : nullptr); - if (!l.pair) - { - diag_record dr (fail); - - dr << value_traits<vector<pair<K, V>>>::value_type.name - << " key-value pair expected instead of '" << l << "'"; + p.push_back (value_traits<pair<K, V>>::convert ( + move (l), r, + value_traits<vector<pair<K, V>>>::value_type.name, + "element", + var)); - if (var != nullptr) - dr << " in variable " << var->name; - } - - name& r (*++i); // Got to have the second half of the pair. - - if (l.pair != '@') - { - diag_record dr (fail); - - dr << "unexpected pair style for " - << value_traits<vector<pair<K, V>>>::value_type.name - << " key-value '" << l << "'" << l.pair << "'" << r << "'"; - - if (var != nullptr) - dr << " in variable " << var->name; - } - - try - { - K k (value_traits<K>::convert (move (l), nullptr)); - - try - { - V v (value_traits<V>::convert (move (r), nullptr)); - - p.emplace_back (move (k), move (v)); - } - catch (const invalid_argument&) - { - diag_record dr (fail); - - dr << "invalid " << value_traits<V>::value_type.name - << " element value '" << r << "'"; - - if (var != nullptr) - dr << " in variable " << var->name; - } - } - catch (const invalid_argument&) - { - diag_record dr (fail); - - dr << "invalid " << value_traits<K>::value_type.name - << " element key '" << l << "'"; - - if (var != nullptr) - dr << " in variable " << var->name; - } } } @@ -522,11 +699,7 @@ namespace build2 s.reserve (2 * vv.size ()); for (const auto& p: vv) - { - s.push_back (value_traits<K>::reverse (p.first)); - s.back ().pair = '@'; - s.push_back (value_traits<V>::reverse (p.second)); - } + value_traits<pair<K, V>>::reverse (p.first, p.second, s); return s; } @@ -543,9 +716,7 @@ namespace build2 for (; li != le && ri != re; ++li, ++ri) { - int r; - if ((r = value_traits<K>::compare (li->first, ri->first)) != 0 || - (r = value_traits<V>::compare (li->second, ri->second)) != 0) + if (int r = value_traits<pair<K, V>>::compare (*li, *ri)) return r; } @@ -558,23 +729,66 @@ namespace build2 return 0; } + // Make sure these are static-initialized together. Failed that VC will make + // sure it's done in the wrong order. + // template <typename K, typename V> - value_traits<vector<pair<K, V>>>::value_type_ex:: - value_type_ex (value_type&& v) - : value_type (move (v)) + struct pair_vector_value_type: value_type { - type_name = value_traits<K>::type_name; - type_name += '_'; - type_name += value_traits<V>::type_name; - type_name += "_pair_vector"; - name = type_name.c_str (); - } + string type_name; + + pair_vector_value_type (value_type&& v) + : value_type (move (v)) + { + type_name = value_traits<K>::type_name; + type_name += '_'; + type_name += value_traits<V>::type_name; + type_name += "_pair_vector"; + name = type_name.c_str (); + } + }; + + // This is beyond our static initialization order control skills, so we hack + // it up for now. + // + template <typename K, typename V> + struct pair_vector_value_type<K, optional<V>>: value_type + { + string type_name; + + pair_vector_value_type (value_type&& v) + : value_type (move (v)) + { + type_name = value_traits<K>::type_name; + type_name += "_optional_"; + type_name += value_traits<V>::type_name; + type_name += "_pair_vector"; + name = type_name.c_str (); + } + }; + + template <typename K, typename V> + struct pair_vector_value_type<optional<K>, V>: value_type + { + string type_name; + + pair_vector_value_type (value_type&& v) + : value_type (move (v)) + { + type_name = "optional_"; + type_name += value_traits<K>::type_name; + type_name += '_'; + type_name += value_traits<V>::type_name; + type_name += "_pair_vector"; + name = type_name.c_str (); + } + }; template <typename K, typename V> const vector<pair<K, V>> value_traits<vector<pair<K, V>>>::empty_instance; template <typename K, typename V> - const typename value_traits<std::vector<pair<K, V>>>::value_type_ex + const pair_vector_value_type<K, V> value_traits<vector<pair<K, V>>>::value_type = build2::value_type // VC14 wants = { nullptr, // Patched above. @@ -599,8 +813,6 @@ namespace build2 void map_append (value& v, names&& ns, const variable* var) { - using std::map; - map<K, V>& p (v ? v.as<map<K, V>> () : *new (&v.data_) map<K, V> ()); @@ -610,63 +822,42 @@ namespace build2 for (auto i (ns.begin ()); i != ns.end (); ++i) { name& l (*i); + name* r (l.pair ? &*++i : nullptr); - if (!l.pair) - { - diag_record dr (fail); - - dr << value_traits<map<K, V>>::value_type.name << " key-value " - << "pair expected instead of '" << l << "'"; - - if (var != nullptr) - dr << " in variable " << var->name; - } - - name& r (*++i); // Got to have the second half of the pair. - - if (l.pair != '@') - { - diag_record dr (fail); - - dr << "unexpected pair style for " - << value_traits<map<K, V>>::value_type.name << " key-value " - << "'" << l << "'" << l.pair << "'" << r << "'"; - - if (var != nullptr) - dr << " in variable " << var->name; - } - - try - { - K k (value_traits<K>::convert (move (l), nullptr)); - - try - { - V v (value_traits<V>::convert (move (r), nullptr)); + pair<K, V> v (value_traits<pair<K, V>>::convert ( + move (l), r, + value_traits<map<K, V>>::value_type.name, + "element", + var)); - p.emplace (move (k), move (v)); - } - catch (const invalid_argument&) - { - diag_record dr (fail); + p.emplace (move (v.first), move (v.second)); + } + } - dr << "invalid " << value_traits<V>::value_type.name - << " element value '" << r << "'"; + template <typename K, typename V> + void + map_prepend (value& v, names&& ns, const variable* var) + { + map<K, V>& p (v + ? v.as<map<K, V>> () + : *new (&v.data_) map<K, V> ()); - if (var != nullptr) - dr << " in variable " << var->name; - } - } - catch (const invalid_argument&) - { - diag_record dr (fail); + // Verify we have a sequence of pairs and convert each lhs/rhs to K/V. + // + for (auto i (ns.begin ()); i != ns.end (); ++i) + { + name& l (*i); + name* r (l.pair ? &*++i : nullptr); - dr << "invalid " << value_traits<K>::value_type.name - << " element key '" << l << "'"; + pair<K, V> v (value_traits<pair<K, V>>::convert ( + move (l), r, + value_traits<map<K, V>>::value_type.name, + "element", + var)); - if (var != nullptr) - dr << " in variable " << var->name; - } + // Poor man's emplace_or_assign(). + // + p.emplace (move (v.first), V ()).first->second = move (v.second); } } @@ -674,8 +865,6 @@ namespace build2 void map_assign (value& v, names&& ns, const variable* var) { - using std::map; - if (v) v.as<map<K, V>> ().clear (); @@ -686,17 +875,11 @@ namespace build2 static names_view map_reverse (const value& v, names& s) { - using std::map; - auto& vm (v.as<map<K, V>> ()); s.reserve (2 * vm.size ()); for (const auto& p: vm) - { - s.push_back (value_traits<K>::reverse (p.first)); - s.back ().pair = '@'; - s.push_back (value_traits<V>::reverse (p.second)); - } + value_traits<pair<K, V>>::reverse (p.first, p.second, s); return s; } @@ -705,8 +888,6 @@ namespace build2 static int map_compare (const value& l, const value& r) { - using std::map; - auto& lm (l.as<map<K, V>> ()); auto& rm (r.as<map<K, V>> ()); @@ -715,9 +896,7 @@ namespace build2 for (; li != le && ri != re; ++li, ++ri) { - int r; - if ((r = value_traits<K>::compare (li->first, ri->first)) != 0 || - (r = value_traits<V>::compare (li->second, ri->second)) != 0) + if (int r = value_traits<pair<const K, V>>::compare (*li, *ri)) return r; } @@ -730,24 +909,67 @@ namespace build2 return 0; } + // Make sure these are static-initialized together. Failed that VC will make + // sure it's done in the wrong order. + // template <typename K, typename V> - value_traits<std::map<K, V>>::value_type_ex:: - value_type_ex (value_type&& v) - : value_type (move (v)) + struct map_value_type: value_type { - type_name = value_traits<K>::type_name; - type_name += '_'; - type_name += value_traits<V>::type_name; - type_name += "_map"; - name = type_name.c_str (); - } + string type_name; + + map_value_type (value_type&& v) + : value_type (move (v)) + { + type_name = value_traits<K>::type_name; + type_name += '_'; + type_name += value_traits<V>::type_name; + type_name += "_map"; + name = type_name.c_str (); + } + }; + + // This is beyond our static initialization order control skills, so we hack + // it up for now. + // + template <typename K, typename V> + struct map_value_type<K, optional<V>>: value_type + { + string type_name; + + map_value_type (value_type&& v) + : value_type (move (v)) + { + type_name = value_traits<K>::type_name; + type_name += "_optional_"; + type_name += value_traits<V>::type_name; + type_name += "_map"; + name = type_name.c_str (); + } + }; + + template <typename K, typename V> + struct map_value_type<optional<K>, V>: value_type + { + string type_name; + + map_value_type (value_type&& v) + : value_type (move (v)) + { + type_name = "optional_"; + type_name += value_traits<K>::type_name; + type_name += '_'; + type_name += value_traits<V>::type_name; + type_name += "_map"; + name = type_name.c_str (); + } + }; template <typename K, typename V> - const std::map<K, V> value_traits<std::map<K, V>>::empty_instance; + const map<K, V> value_traits<map<K, V>>::empty_instance; template <typename K, typename V> - const typename value_traits<std::map<K, V>>::value_type_ex - value_traits<std::map<K, V>>::value_type = build2::value_type // VC14 wants = + const map_value_type<K, V> + value_traits<map<K, V>>::value_type = build2::value_type // VC14 wants = { nullptr, // Patched above. sizeof (map<K, V>), @@ -758,7 +980,7 @@ namespace build2 &default_copy_assign<map<K, V>>, &map_assign<K, V>, &map_append<K, V>, - &map_append<K, V>, // Prepend is the same as append. + &map_prepend<K, V>, &map_reverse<K, V>, nullptr, // No cast (cast data_ directly). &map_compare<K, V>, diff --git a/libbuild2/version/module.hxx b/libbuild2/version/module.hxx index 26bd48a..e80870e 100644 --- a/libbuild2/version/module.hxx +++ b/libbuild2/version/module.hxx @@ -4,8 +4,6 @@ #ifndef LIBBUILD2_VERSION_MODULE_HXX #define LIBBUILD2_VERSION_MODULE_HXX -#include <map> - #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> @@ -26,7 +24,7 @@ namespace build2 string constraint; }; - using dependencies = std::map<string, dependency>; + using dependencies = map<string, dependency>; struct module: build2::module { diff --git a/tests/cc/modules/headers.testscript b/tests/cc/modules/headers.testscript index 6fc2ba7..20f2a5f 100644 --- a/tests/cc/modules/headers.testscript +++ b/tests/cc/modules/headers.testscript @@ -69,7 +69,7 @@ cat <<EOI >=driver-inc.cxx; #endif int main () {return f () - CORE_OUT;} EOI -$* test clean config.cxx.translatable_headers="$~/core.hxx" <<EOI +$* test clean config.cxx.translate_include="$~/core.hxx" <<EOI ./: exe{test-imp}: cxx{driver-imp} hxx{core} ./: exe{test-inc}: cxx{driver-inc} hxx{core} EOI @@ -104,7 +104,7 @@ $* test clean config.cxx.translatable_headers="$~/core.hxx" <<EOI int main () {return g ();} EOI $* test clean config.cxx.poptions=-DBASE_INCLUDE \ - config.cxx.translatable_headers="$~/core.hxx" <<EOI + config.cxx.translate_include="$~/core.hxx" <<EOI exe{test}: cxx{driver} hxx{core} mxx{base} EOI #\ @@ -135,7 +135,7 @@ cat <<EOI >=driver-inc.cxx; #include <generated/core.hxx> int main () {return f ();} EOI -$* test clean config.cxx.translatable_headers="$~/core.hxx" <<EOI +$* test clean config.cxx.translate_include="$~/core.hxx" <<EOI ./: exe{test-imp}: cxx{driver-imp} hxx{core} ./: exe{test-inc}: cxx{driver-inc} hxx{core} hxx{core}: in{core} @@ -161,7 +161,7 @@ cat <<EOI >=driver-inc.cxx; # out = ../../headers-remapped-out; $* 'test:' ./@$out/remapped/ \ - config.cxx.translatable_headers=$out/remapped/core.hxx <<EOI; + config.cxx.translate_include=$out/remapped/core.hxx <<EOI; ./: exe{test-imp}: cxx{driver-imp} hxx{core} ./: exe{test-inc}: cxx{driver-inc} hxx{core} hxx{core}: in{core} diff --git a/tests/cc/modules/modules.testscript b/tests/cc/modules/modules.testscript index a43cc57..eb4c122 100644 --- a/tests/cc/modules/modules.testscript +++ b/tests/cc/modules/modules.testscript @@ -173,6 +173,9 @@ $* test clean <<EOI ln -s ../driver.cxx ./; $* test &*.d <'exe{test}: cxx{driver}' 2>>EOE != 0 driver.cxx: error: unable to resolve module foo.core + info: verify module interface is listed as a prerequisite, otherwise + info: consider adjusting module interface file names or + info: consider specifying module name with cxx.module_name EOE : misguessed |