diff options
Diffstat (limited to 'libbuild2')
33 files changed, 996 insertions, 329 deletions
diff --git a/libbuild2/build/script/parser.test.cxx b/libbuild2/build/script/parser.test.cxx index 97eac22..1b28ec3 100644 --- a/libbuild2/build/script/parser.test.cxx +++ b/libbuild2/build/script/parser.test.cxx @@ -247,11 +247,11 @@ namespace build2 // really care. // file& tt ( - ctx.targets.insert<file> (work, - dir_path (), - "driver", - string (), - trace)); + ctx.targets.insert_implied<file> (work, + dir_path (), + "driver", + string (), + trace)); tt.path (path ("driver")); diff --git a/libbuild2/buildfile b/libbuild2/buildfile index 3518d93..d9711e6 100644 --- a/libbuild2/buildfile +++ b/libbuild2/buildfile @@ -293,7 +293,7 @@ if ($install.root != [null]) if ($cxx.target.class != 'windows') { - libul{build2}: cxx.libs += -lpthread + libul{build2}: cxx.libs += -pthread # Note: only linking libdl in shared build. # @@ -323,7 +323,7 @@ lib{build2}: # needed for some std::thread implementations (like libstdc++). # if ($cxx.target.class != 'windows') - lib{build2}: cxx.export.libs += -lpthread + lib{build2}: cxx.export.libs += -pthread liba{build2}: cxx.export.poptions += -DLIBBUILD2_STATIC libs{build2}: cxx.export.poptions += -DLIBBUILD2_SHARED diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx index 9a4a07c..fcc8961 100644 --- a/libbuild2/cc/common.cxx +++ b/libbuild2/cc/common.cxx @@ -185,7 +185,7 @@ namespace build2 // const string* pt ( cast_null<string> ( - l.state[a].lookup_original (c_type, true /* target_only */).first)); + l.state[a].lookup_original (c_type, lookup_limit::target).first)); // cc.type value format is <lang>[,...]. // @@ -242,7 +242,7 @@ namespace build2 // { const variable& v (impl ? c_export_impl_libs : c_export_libs); - c_e_libs = l.lookup_original (v, false, &bs).first; + c_e_libs = l.lookup_original (v, &bs).first; } if (!cc) @@ -251,7 +251,7 @@ namespace build2 same ? (impl ? x_export_impl_libs : x_export_libs) : vp[t + (impl ? ".export.impl_libs" : ".export.libs")]); - x_e_libs = l.lookup_original (v, false, &bs).first; + x_e_libs = l.lookup_original (v, &bs).first; } // Process options first. @@ -669,7 +669,7 @@ namespace build2 // See the link rule for the lookup semantics. // lookup l ( - t->lookup_original (var, true /* target_only */).first); + t->lookup_original (var, lookup_limit::target).first); if (l ? cast<bool> (*l) : u) lf |= lflag_whole; @@ -778,8 +778,8 @@ namespace build2 if (proc_lib) { const variable& v (same ? x_libs : vp[t + ".libs"]); - proc_impl (l.lookup_original (c_libs, false, &bs).first); - proc_impl (l.lookup_original (v, false, &bs).first); + proc_impl (l.lookup_original (c_libs, &bs).first); + proc_impl (l.lookup_original (v, &bs).first); } } } diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index 7629ed5..cebd244 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -1950,8 +1950,8 @@ namespace build2 // // @@ Should we print the pid we are talking to? It gets hard to // follow once things get nested. But if all our diag will include - // some kind of id (chain, thread?), then this will not be strictly - // necessary. + // some kind of id (dependency chain, thread?), then this will not + // be strictly necessary. // diag_record dr (text); for (size_t i (0); i != batch_n; ++i) @@ -2628,8 +2628,8 @@ namespace build2 // @@ MODHDR: Should we print the pid we are talking to? It gets hard to // follow once things get nested. But if all our diag will - // include some kind of id (chain, thread?), then this will - // not be strictly necessary. + // include some kind of id (dependency chain, thread?), then + // this will not be strictly necessary. // if (verb >= 3) text << " > " << rq; @@ -3213,7 +3213,21 @@ namespace build2 if (f != nullptr) { //cache_cls.fetch_add (1, memory_order_relaxed); + +#if 0 assert (r.first == f); +#else + if (r.first != f) + { + info << "inconsistent header cache content" << + info << "encountered: " << *f << + info << "expected: " << *r.first << + info << "please report at " + << "https://github.com/build2/build2/issues/390"; + + assert (r.first == f); + } +#endif } } @@ -4153,9 +4167,10 @@ namespace build2 if (l->empty ()) // Done, nothing changed. { // If modules are enabled, then we keep the preprocessed output - // around (see apply() for details). + // around (see apply() for details). Unless reprocessing was + // requested. // - if (modules) + if (modules && !reprocess) { result.first = ctx.fcache->create_existing (t.path () + pext); result.second = true; diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx index d7e9c63..f092933 100644 --- a/libbuild2/cc/guess.cxx +++ b/libbuild2/cc/guess.cxx @@ -1878,7 +1878,7 @@ namespace build2 move (ver), nullopt, move (gr.signature), - "", + "", // Checksum to be calculated from signature. move (t), move (ot), move (cpat), @@ -2128,7 +2128,7 @@ namespace build2 move (ver), nullopt, move (gr.signature), - move (gr.checksum), // Calculated on whole -v output. + "", // Checksum calculated on whole -v output. move (t), move (ot), move (pat), @@ -2580,27 +2580,29 @@ namespace build2 // // Note that this is Apple Clang version and not XCode version. // - // 4.2 -> 3.2svn - // 5.0 -> 3.3svn - // 5.1 -> 3.4svn - // 6.0 -> 3.5svn - // 6.1.0 -> 3.6svn - // 7.0.0 -> 3.7 - // 7.3.0 -> 3.8 - // 8.0.0 -> 3.9 - // 8.1.0 -> ? - // 9.0.0 -> 4.0 - // 9.1.0 -> 5.0 - // 10.0.0 -> 6.0 - // 11.0.0 -> 7.0 - // 11.0.3 -> 8.0 (yes, seriously!) - // 12.0.0 -> 9.0 - // 12.0.5 -> 10.0 (yes, seriously!) - // 13.0.0 -> 11.0 - // 13.1.6 -> 12.0 - // 14.0.0 -> 12.0 (_LIBCPP_VERSION=130000) - // 14.0.3 -> 15.0 (_LIBCPP_VERSION=150006) - // 15.0.0 -> 16.0 (_LIBCPP_VERSION=160002) + // 4.2 -> 3.2svn + // 5.0 -> 3.3svn + // 5.1 -> 3.4svn + // 6.0 -> 3.5svn + // 6.1.0 -> 3.6svn + // 7.0.0 -> 3.7 + // 7.3.0 -> 3.8 + // 8.0.0 -> 3.9 + // 8.1.0 -> ? + // 9.0.0 -> 4.0 + // 9.1.0 -> 5.0 + // 10.0.0 -> 6.0 + // 11.0.0 -> 7.0 + // 11.0.3 -> 8.0 (yes, seriously!) + // 12.0.0 -> 9.0 + // 12.0.5 -> 10.0 (yes, seriously!) + // 13.0.0 -> 11.0 + // 13.1.6 -> 12.0 + // 14.0.0 -> 12.0 (_LIBCPP_VERSION=130000) + // 14.0.3 -> 15.0 (_LIBCPP_VERSION=150006) + // 15.0.0.0 -> 16.0 (_LIBCPP_VERSION=160002) + // 15.0.0.1 -> 16.0 (_LIBCPP_VERSION=160006) + // 15.0.0.3 -> 16.0 (_LIBCPP_VERSION=170006) // uint64_t mj (var_ver->major); uint64_t mi (var_ver->minor); @@ -2896,7 +2898,7 @@ namespace build2 move (ver), move (var_ver), move (gr.signature), - move (gr.checksum), // Calculated on whole -v output. + "", // Checksum calculated on whole -v output. move (t), move (ot), move (cpat), @@ -3214,7 +3216,7 @@ namespace build2 move (ver), nullopt, move (gr.signature), - "", + "", // Checksum to be calculated from signature. move (t), move (ot), move (pat), @@ -3368,6 +3370,8 @@ namespace build2 cs.append (gr.type_signature); } + cs.append (r.path.effect_string ()); + r.checksum = cs.string (); // Derive binutils pattern unless this has already been done by the diff --git a/libbuild2/cc/guess.hxx b/libbuild2/cc/guess.hxx index 7cbbd87..dfa8aa2 100644 --- a/libbuild2/cc/guess.hxx +++ b/libbuild2/cc/guess.hxx @@ -160,6 +160,9 @@ namespace build2 // checksum will still change. This is currently the case for all the // compilers that we support. // + // And we assume that the checksum incorporates the absolute compiler + // path. This is used to detect compilation database changes. + // // The target is the compiler's traget architecture triplet. Note that // unlike all the preceding fields, this one takes into account the // compile options (e.g., -m32). diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index 08a60b9..d9fbbea 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -102,13 +102,13 @@ namespace build2 // const scope& bs (t->base_scope ()); - if (lookup l = t->lookup_original (c_export_libs, false, &bs).first) + if (lookup l = t->lookup_original (c_export_libs, &bs).first) { if (!deduplicate_export_libs (bs, cast<vector<name>> (l), r, seen)) return false; } - if (lookup l = t->lookup_original (x_export_libs, false, &bs).first) + if (lookup l = t->lookup_original (x_export_libs, &bs).first) { if (!deduplicate_export_libs (bs, cast<vector<name>> (l), r, seen)) return false; @@ -503,8 +503,8 @@ namespace build2 return false; } - // We will only chain a C source if there is also an X source or we were - // explicitly told to. + // We will only synthesize a dependency for a C source if there is also + // an X source or we were explicitly told to. // if (r.seen_c && !r.seen_x && hint.empty ()) { @@ -915,8 +915,8 @@ namespace build2 const fsdir* dir (inject_fsdir (a, t)); // Process prerequisites, pass 1: search and match prerequisite - // libraries, search obj/bmi{} targets, and search targets we do rule - // chaining for. + // libraries, search obj/bmi{} targets, and search targets we do + // dependency synthesis for. // // Also clear the binless flag if we see any source or object files. // Note that if we don't see any this still doesn't mean the library is @@ -928,7 +928,7 @@ namespace build2 // compile::apply() to unmatch them and therefore not to hinder // parallelism (or mess up for-install'ness). // - // We also create obj/bmi{} chain targets because we need to add + // We also synthesize obj/bmi{} dependencies because we need to add // (similar to lib{}) all the bmi{} as prerequisites to all the other // obj/bmi{} that we are creating. Note that this doesn't mean that the // compile rule will actually treat them all as prerequisite targets. @@ -955,6 +955,20 @@ namespace build2 auto& pts (t.prerequisite_targets[a]); size_t start (pts.size ()); + // Compile rule options specified on lib/exe{} to propagate to obj/bmi{} + // during dependency synthesis (see below). + // + struct cr_options + { + const strings* c_p; // cc.poptions + const strings* x_p; // x.poptions + const strings* c_c; // cc.coptions + const strings* x_c; // x.coptions + + bool member; // Any value came from member as opposed to group. + }; + optional<cr_options> cr_ops; // Lookup lazily. + for (prerequisite_member p: group_prerequisite_members (a, t)) { // Note that we have to recognize update=match for *(update), not just @@ -1025,7 +1039,14 @@ namespace build2 continue; } - const target*& pt (pto); + const target*& pt (pto.target); + + // Auxiliary data in prerequisite_target: + // + // - for libraries it stores link flags (lflag_whole) + // - for synthesized obj/bmi{} it strores the group flag + // + uintptr_t& pd (pto.data); // Mark (2 bits): // @@ -1045,7 +1066,7 @@ namespace build2 { binless = binless && (mod ? user_binless : false); - // Rule chaining, part 1. + // Dependency synthesis, part 1. // // Which scope shall we use to resolve the root? Unlikely, but // possible, the prerequisite is from a different project @@ -1058,6 +1079,62 @@ namespace build2 // bool group (!p.prerequisite.belongs (t)); // Group's prerequisite. + // Lookup all the relevant binary-specific compile option values if + // this hasn't already been done. + // + if (!cr_ops) + { + bool m (false); + + auto lookup = [&bs, &t, &m] (const variable& var) -> const strings* + { + // We don't want to pick anything beyond target-specific + // values (but including target type/pattern-specific) since + // they will also be in effect for obj/bmi{} and setting them + // to the same values is a waste (and, in fact, it could be + // that when this feature is not used, different obj/bmi{} are + // compiled with different options). + // + // Note that we need to take into account target type/pattern- + // specific append/prepend since it could modify the scope + // value but only be applicable to lib/exe{}. Something like + // this: + // + // cc.poptions += -DFOO + // lib{*}: cc.poptions += -DBAR + // + // Then there is this nuance: if any value came from the member + // and not from the group, then we need to override the above + // group semantics. In particular, this allows us to do: + // + // liba{hello}: cxx.poptions += -DLIBHELLO_STATIC_BUILD + // libs{hello}: cxx.poptions += -DLIBHELLO_SHARED_BUILD + // + // Note also that the variables we are dealing with are not + // overridable. + // + pair<build2::lookup, size_t> p ( + t.lookup_original (var, + lookup_limit::target_type, + &bs)); + + const strings* r (cast_null<strings> (p.first)); + + if (r != nullptr) + m = m || p.second == 1; // Found in the target itself. + + return r; + }; + + cr_ops = cr_options {lookup (c_poptions), lookup (x_poptions), + lookup (c_coptions), lookup (x_coptions), + false}; + cr_ops->member = m; + } + + if (group && cr_ops->member) + group = false; + const target_type& rtt (mod ? (group ? bmi::static_type : tts.bmi) : (group ? obj::static_type : tts.obj)); @@ -1091,44 +1168,278 @@ namespace build2 // obj/bmi{} is always in the out tree. Note that currently it could // be the group -- we will pick a member in part 2 below. // + // Note: d is used below. + // pair<target&, ulock> r ( search_new_locked ( ctx, rtt, d, dir_path (), *cp.tk.name, nullptr, cp.scope)); - // If we shouldn't clean obj{}, then it is fair to assume we - // shouldn't clean the source either (generated source will be in - // the same directory as obj{} and if not, well, go find yourself - // another build system ;-)). - // - if (skip (r.first)) - { - pt = nullptr; - continue; - } + const target& cpt (r.first); + bool locked (r.second.owns_lock ()); - // Either set of verify the bin.binless value on this bmi*{} target + // Either set or verify the bin.binless value on this bmi*{} target // (see config_data::b_binless for semantics). // if (mod) { - if (r.second.owns_lock ()) + if (locked) { if (user_binless) r.first.assign (b_binless) = true; } else { - lookup l (r.first[b_binless]); + lookup l (cpt[b_binless]); if (user_binless ? !cast_false<bool> (l) : l.defined ()) fail << "synthesized dependency for prerequisite " << p << " would be incompatible with existing target " - << r.first << + << cpt << info << "incompatible bin.binless value"; } } - pt = &r.first; + // Binary-specific compile options. + // + // Propagate compile rule options (*.poptions and *.coptions) + // specified on the binary to the obj/bmi{} targets that we + // synthesize. + // + // The semantics we are aiming for is as-if they were set on the + // obj/bmi{} target at the end of the buildfile (to be precise, at + // the end of loading all buildfiles for the scope). + // + // While ideally we would like to prevent sharing such obj/bmi{} + // between multiple binaries or for the user to specify any compile + // options explicitly, this is not easy to do (since once the + // options are set, we don't know who set them: same binary on the + // previous operation batch, another binary, or user). Instead, we + // are going to approximate this by making sure the options (or + // their absence) match. + // + // Note also that we don't touch user-specified obj/bmi{} + // prerequisites (neither set nor verify). In particular, this + // allows customizing compile options for specific translation + // units. + // + // NOTE: keep last since unlocks the lock. + // + { + // If we have any value, then either set them (locked) or make + // sure they all match (unlocked). + // + // Note that the case where one lib/exe{} specifies a value while + // the other doesn't specify any (and they share a synthesized + // dependency) will be racy to verify. Getting rid of this race + // will be difficult because in the verify case we can't say who + // set the value on obj/bmi{}. We could set some sort of a marker + // variable but then it means we would need to look it up for + // every lib/exe{} (whether they use this feature or not, and most + // won't). However, we can handle the common case based on the + // target being newly created (as opposed to being mentioned in + // the buildfile; see target_decl). + // + auto check = [&bs, &p, &d] (const variable& var, + const target& et, + const strings* e, + const target& st) + { + // If the expected value is NULL, then just make sure this + // variable is not set on the target. Otherwise, compare the + // result of the normal lookup and if it matches, then we assume + // it's good regardless of where it comes from (this covers all + // the corner cases which we cannot verify precisely; see above + // for details). + // + const strings* v; + size_t vd (0); + + if (e == nullptr) + { + v = cast_null<strings> (st.vars[var]); + + if (v == nullptr) + return; + } + else + { + // Optimize the lookup for the common case. + // + pair<lookup, size_t> p ( + st.lookup_original ( + var, + d.empty () ? &bs : nullptr)); + + v = cast_null<strings> (p.first); + + if (v != nullptr && *v == *e) + return; + + vd = p.second; + } + + diag_record dr (fail); + + dr << "synthesized dependency for prerequisite " << p + << " would be incompatible with existing target " << st << + info << "variable " << var << " value mismatch"; + + if (e == nullptr) // Expected to be absent. + { + dr << info << st << " value: "; to_stream_quoted (dr.os, *v); + dr << info << et << " value is absent"; + } + else if (v == nullptr) // Expected to be present. + { + dr << info << st << " value is absent"; + dr << info << et << " value: "; to_stream_quoted (dr.os, *e); + } + else // Expected to match. + { + dr << info << st << " value: "; to_stream_quoted (dr.os, *v); + dr << info << et << " value: "; to_stream_quoted (dr.os, *e); + + if (vd > 1) + { + if (const target* g = st.group) + dr << info << st << " value came from group " << *g; + } + } + }; + + auto set = [&r] (const variable& var, const strings& v) + { + // One nuance here is that target type/pattern-specific + // append/prepend/assign specified for obj/bmi{} will not be + // in effect for options specified on lib/exe{}. For example: + // + // obj{*}: cc.poptions = -DFOO + // lib{bar}: cc.poptions += -DBAR + // + // It doesn't seem there is anything sensible we can do about + // it automatically other than documenting this nuance and + // suggesting the user adds lib/exe{} to such a pattern. + // + r.first.assign (var) = v; + }; + + bool absent (false); + if (cr_ops->c_p != nullptr || cr_ops->x_p != nullptr || + cr_ops->c_c != nullptr || cr_ops->x_c != nullptr || + (absent = !operator>= (cpt.decl, target_decl::implied))) // VC14 + { + if (locked) + { + if (!absent) + { + if (cr_ops->c_p != nullptr) set (c_poptions, *cr_ops->c_p); + if (cr_ops->x_p != nullptr) set (x_poptions, *cr_ops->x_p); + if (cr_ops->c_c != nullptr) set (c_coptions, *cr_ops->c_c); + if (cr_ops->x_c != nullptr) set (x_coptions, *cr_ops->x_c); + } + + // @@ PERF: maybe pass the lock to search_new_locked() below? + // + r.second.unlock (); + locked = false; + } + else + { + if (absent && cpt.vars.empty ()) + ; // Optimize for the common case. + else + { + // If the values came from the group, then use the group in + // diagnostics. + // + const target& et (group ? *t.group : t); + + check (c_poptions, et, cr_ops->c_p, cpt); + check (x_poptions, et, cr_ops->x_p, cpt); + check (c_coptions, et, cr_ops->c_c, cpt); + check (x_coptions, et, cr_ops->x_c, cpt); + } + } + + // Verify member/group consistency. + // + // The check above doesn't quite work for libraries where the + // one that specified any options will likely end up + // synthesizing obja/objs{} targets (see the group logic above) + // while the one that didn't -- obj{}. So we also need to check + // obj{} vs obj[as]{} consistency if both are synthesized. + // + // (This is actually even hairier than that: sometimes, the + // obj{} library will race ahead in its matching and manage to + // create the obja/objs{} prerequisite. In which case we will + // end up failing the above check, which will be quite confusing + // and which is the reason we have added the "value came from + // group" info above). + // + // Implementing this verification in this racing environment is + // challanging, to put it mildly. So what we are going to do is, + // in case of a group, also enter the member (which we will be + // doing anyway shortly). If we were the ones who created it, + // then we have "staked out" our view of its variables and any + // subsequent attempts to enter it will trigger the above + // check. If, however, this target was already there, then we + // verify that it's consistent with the values we expect. + // + if (group) + { + const target_type& tt (mod ? tts.bmi : tts.obj); + + // @@ PERF: maybe we should stash the member in pd and avoid + // another search when we pick the member? (Though obj{} + // might also not be synthesized.) + + pair<target&, ulock> r ( + search_new_locked ( + ctx, tt, d, dir_path (), *cp.tk.name, nullptr, cp.scope)); + + if (r.second.owns_lock ()) + r.second.unlock (); + else + { + const target& cmt (r.first); + + if (!absent || + !operator>= (cmt.decl, target_decl::implied)) // VC14 + { + if (absent && cmt.vars.empty ()) + ; // Optimize for the common case. + else + { + check (c_poptions, cpt, cr_ops->c_p, cmt); + check (x_poptions, cpt, cr_ops->x_p, cmt); + check (c_coptions, cpt, cr_ops->c_c, cmt); + check (x_coptions, cpt, cr_ops->x_c, cmt); + } + } + } + } + } + } + + if (locked) + r.second.unlock (); + + // If we shouldn't clean obj{}, then it is fair to assume we + // shouldn't clean the source either (generated source will be in + // the same directory as obj{} and if not, well, go find yourself + // another build system ;-)). + // + // Note: should be done after setting/verifying variables since can + // be an operation batch. + // + if (skip (cpt)) + { + pt = nullptr; + continue; + } + + pt = &cpt; + pd = group ? 1 : 0; mk = mod ? 2 : 1; } else if (p.is_a<libx> () || @@ -1310,7 +1621,7 @@ namespace build2 // if (const string* t = cast_null<string> ( ft->state[a].lookup_original ( - c_type, true /* target_only */).first)) + c_type, lookup_limit::target).first)) { if (recursively_binless (*t)) continue; @@ -1330,7 +1641,7 @@ namespace build2 { auto find = [&t, &bs] (const variable& v) -> lookup { - return t.lookup_original (v, false, &bs).first; + return t.lookup_original (v, &bs).first; }; auto has_simple = [] (lookup l) @@ -1729,9 +2040,9 @@ namespace build2 } } - // Process prerequisites, pass 2: finish rule chaining but don't start - // matching anything yet since that may trigger recursive matching of - // bmi{} targets we haven't completed yet. Hairy, I know. + // Process prerequisites, pass 2: finish dependency synthesis but don't + // start matching anything yet since that may trigger recursive matching + // of bmi{} targets we haven't completed yet. Hairy, I know. // // Parallel prerequisites/prerequisite_targets loop. @@ -1759,14 +2070,14 @@ namespace build2 // Note that if this is a library not to be cleaned, we keep it // marked for completion (see the next phase). } - else if (mk == 1 || mk == 2) // Source/module chain. + else if (mk == 1 || mk == 2) // Synthesized source/module dependency. { bool mod (mk == 2); // p is_a x_mod mk = 1; const target& rt (*pt); - bool group (!p.prerequisite.belongs (t)); // Group's prerequisite. + bool group (pd != 0); // Group's prerequisite (see pass 1). // If we have created a obj/bmi{} target group, pick one of its // members; the rest would be primarily concerned with it. @@ -1812,7 +2123,7 @@ namespace build2 } // Add our lib*{} (see the export.* machinery for details) and - // bmi*{} (both original and chained; see module search logic) + // bmi*{} (both original and synthesized; see module search logic) // prerequisites. // // Note that we don't resolve lib{} to liba{}/libs{} here @@ -1833,7 +2144,8 @@ namespace build2 size_t j (start); for (prerequisite_member p: group_prerequisite_members (a, t)) { - const target* pt (pts[j++]); + const target* pt (pts[j].target); + uintptr_t& pd (pts[j++].data); if (pt == nullptr) // Note: ad hoc is taken care of. continue; @@ -1847,7 +2159,8 @@ namespace build2 { ps.push_back (p.as_prerequisite ()); } - else if (x_mod != nullptr && p.is_a (*x_mod)) // Chained module. + else if (x_mod != nullptr && p.is_a (*x_mod)) // Synthesized + // module dependency. { // Searched during pass 1 but can be NULL or marked. // @@ -1857,7 +2170,7 @@ namespace build2 // was a group, then we would have picked up a member. So // here we may have to "unpick" it. // - bool group (j < i && !p.prerequisite.belongs (t)); + bool group (j < i && pd != 0); unmark (pt); ps.push_back (prerequisite (group ? *pt->group : *pt)); @@ -1975,7 +2288,7 @@ namespace build2 lookup l (p.prerequisite.vars[var]); if (!l.defined ()) - l = pt->lookup_original (var, true /* target_only */).first; + l = pt->lookup_original (var, lookup_limit::target).first; if (!l.defined ()) { @@ -1997,7 +2310,8 @@ namespace build2 mark (pt, mk); } - // Process prerequisites, pass 3: match everything and verify chains. + // Process prerequisites, pass 3: match everything and verify synthesized + // dependencies. // // Wait with unlocked phase to allow phase switching. @@ -2051,7 +2365,9 @@ namespace build2 i = start; for (prerequisite_member p: group_prerequisite_members (a, t)) { - const target*& pt (pts[i++]); + const target*& pt (pts[i].target); + uintptr_t& pd (pts[i++].data); + // Skipped or not marked for completion. // @@ -2088,7 +2404,7 @@ namespace build2 if (&tp != &tp1) { - bool group (!p.prerequisite.belongs (t)); + bool group (pd != 0); // See pass 1. const target_type& rtt (mod ? (group ? bmi::static_type : tts.bmi) @@ -2193,12 +2509,19 @@ namespace build2 // appear after the preceding static library of which this binless // library is a dependency. // + // Note that we omit the duplicate suppression if we are linking the + // whole archive since the previous instance may not necessarily do + // the same (see GH issue #411; we could have complicated things and + // stored the flag in appended_libraries but it doesn't feel + // worthwhile in this case). + // // From the process_libraries() semantics we know that this callback // is always called and always after the options callbacks. // - appended_library* al (l != nullptr - ? &d.ls.append (*l, d.args.size ()) - : d.ls.append (ns, d.args.size ())); + appended_library* al ( + f & lflag_whole ? nullptr : + l != nullptr ? &d.ls.append (*l, d.args.size ()) : + d.ls.append (ns, d.args.size ())); if (al != nullptr && al->end != appended_library::npos) // Closed. { @@ -2397,13 +2720,13 @@ namespace build2 else { d.args.push_back ("-Wl,--whole-archive"); - d.args.push_back (move (p)); + d.args.push_back (move (p)); p.clear (); d.args.push_back ("-Wl,--no-whole-archive"); - goto done; } } - d.args.push_back (move (p)); + if (!p.empty ()) + d.args.push_back (move (p)); } if (d.cs != nullptr) @@ -2585,6 +2908,24 @@ namespace build2 // We don't rpath system libraries. Why, you may ask? There are many // good reasons and I have them written on a napkin somewhere... // + // Well, the main reason is that we naturally assume the dynamic + // linker searches there by default and so there is no need for rpath. + // Plus, rpath would prevent "overriding" distribution-system + // (/usr/lib) libraries with user-system (/usr/local/lib). + // + // Note, however, that some operating systems don't search in + // /usr/local/lib by default (for example, Fedora, RHEL, Mac OS since + // version 13). In a sense, on these platforms /usr/local is + // "half-system" in that the system compiler by default searches in + // /usr/local/include and/or /usr/local/lib (see config_module::init() + // for background) but the dynamic linker does not. While we could + // hack this test for such platforms and add rpath for /usr/local/lib, + // this is still feels wrong (the user can always "fix" such an + // operating system by instructing the dynamic linker to search in + // /usr/local/lib, as many, including ourselves, do). So for now we + // are not going to do anything. In the end, the user can always add + // an rpath for /usr/local/lib manually. + // // We also assume system libraries can only depend on other system // libraries and so can prune the traversal. // @@ -2596,18 +2937,26 @@ namespace build2 size_t p (path::traits_type::rfind_separator (f)); assert (p != string::npos); + // For good measure, also suppress duplicates at the options level. + // This will take care of different libraries built in the same + // directory, system-installed, etc. + if (d.rpath) { string o ("-Wl,-rpath,"); o.append (f, 0, (p != 0 ? p : 1)); // Don't include trailing slash. - d.args.push_back (move (o)); + + if (find (d.args.begin (), d.args.end (), o) == d.args.end ()) + d.args.push_back (move (o)); } if (d.rpath_link) { string o ("-Wl,-rpath-link,"); o.append (f, 0, (p != 0 ? p : 1)); - d.args.push_back (move (o)); + + if (find (d.args.begin (), d.args.end (), o) == d.args.end ()) + d.args.push_back (move (o)); } }; @@ -2660,7 +3009,9 @@ namespace build2 if ((c ? f.compare (p, string::npos, e) : icasecmp (f.c_str () + p, e)) == 0) + { append (f); + } } } @@ -2671,13 +3022,22 @@ namespace build2 { // Top-level shared library dependency. // + // As above, suppress duplicates. + // + if (find (d.ls.begin (), d.ls.end (), &l) != d.ls.end ()) + return; + if (!l.path ().empty ()) // Not binless. { // It is either matched or imported so should be a cc library. // if (!cast_false<bool> (l.vars[c_system])) { - args.push_back ("-Wl,-rpath," + l.path ().directory ().string ()); + string o ("-Wl,-rpath," + l.path ().directory ().string ()); + + if (find (args.begin (), args.end (), o) == args.end ()) + args.push_back (move (o)); + ls.push_back (&l); } } @@ -3332,6 +3692,9 @@ namespace build2 origin = p.directory (); } + // Note: suppress duplicates at the options level, similar to + // rpath_libraries(). + bool origin_used (false); for (const dir_path& p: cast<dir_paths> (l)) { @@ -3368,7 +3731,8 @@ namespace build2 else o += p.string (); - sargs.push_back (move (o)); + if (find (sargs.begin (), sargs.end (), o) == sargs.end ()) + sargs.push_back (move (o)); } // According to the Internet, `-Wl,-z,origin` is not needed except @@ -3386,7 +3750,12 @@ namespace build2 fail << ctgt << " does not support rpath-link"; for (const dir_path& p: cast<dir_paths> (l)) - sargs.push_back ("-Wl,-rpath-link," + p.string ()); + { + string o ("-Wl,-rpath-link," + p.string ()); + + if (find (sargs.begin (), sargs.end (), o) == sargs.end ()) + sargs.push_back (move (o)); + } } } @@ -3433,13 +3802,19 @@ namespace build2 append_args (sargs1); } - else + else if (b != x) { - append_option_values ( - args, + // Use the more canonical combined form (-L/usr/local/lib) even + // though it's less efficient (the split one is just too much of an + // eye-sore in the logs). + // + append_combined_option_values ( + sargs1, "-L", b, x, - [] (const dir_path& d) {return d.string ().c_str ();}); + [] (const dir_path& d) -> const string& {return d.string ();}); + + append_args (sargs1); } } diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx index 046fbc8..7e47534 100644 --- a/libbuild2/cc/pkgconfig.cxx +++ b/libbuild2/cc/pkgconfig.cxx @@ -2256,7 +2256,7 @@ namespace build2 const string& t ( cast<string> ( l.state[a].lookup_original ( - c_type, true /* target_only */).first)); + c_type, lookup_limit::target).first)); // If common, then only save the language (the rest could be // static/shared-specific; strictly speaking even the language could @@ -2276,7 +2276,7 @@ namespace build2 // if (cast_false<bool> (l.lookup_original ( ctx.var_pool["bin.whole"], - true /* target_only */).first)) + lookup_limit::target).first)) { os << endl << "bin.whole = true" << endl; diff --git a/libbuild2/cli/rule.cxx b/libbuild2/cli/rule.cxx index 996ca51..7c571d4 100644 --- a/libbuild2/cli/rule.cxx +++ b/libbuild2/cli/rule.cxx @@ -120,7 +120,8 @@ namespace build2 prerequisite_members (a, t))) { if (g == nullptr) - g = &t.ctx.targets.insert<cli_cxx> (t.dir, t.out, t.name, trace); + g = &t.ctx.targets.insert_implied<cli_cxx> ( + t.dir, t.out, t.name, trace); prerequisites ps; ps.push_back (p->as_prerequisite ()); diff --git a/libbuild2/cli/target.cxx b/libbuild2/cli/target.cxx index 22ae75c..6e9601b 100644 --- a/libbuild2/cli/target.cxx +++ b/libbuild2/cli/target.cxx @@ -52,9 +52,9 @@ namespace build2 // // Also required for the src-out remapping logic. // - ctx.targets.insert<cxx::hxx> (d, o, n, trace); - ctx.targets.insert<cxx::cxx> (d, o, n, trace); - ctx.targets.insert<cxx::ixx> (d, o, n, trace); + ctx.targets.insert_implied<cxx::hxx> (d, o, n, trace); + ctx.targets.insert_implied<cxx::cxx> (d, o, n, trace); + ctx.targets.insert_implied<cxx::ixx> (d, o, n, trace); return new cli_cxx (ctx, move (d), move (o), move (n)); } diff --git a/libbuild2/config/operation.cxx b/libbuild2/config/operation.cxx index 150bf1a..b1716cf 100644 --- a/libbuild2/config/operation.cxx +++ b/libbuild2/config/operation.cxx @@ -1062,6 +1062,9 @@ namespace build2 static bool disfigure_project (action a, const scope& rs, project_set& projects) { + // NOTE: if changing anything here, see also code in bpkg that saves the + // configuration files on reconfiguration. + tracer trace ("disfigure_project"); context& ctx (rs.ctx); diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx index 33fc892..828c41e 100644 --- a/libbuild2/context.hxx +++ b/libbuild2/context.hxx @@ -904,7 +904,7 @@ namespace build2 bool unlock_phase = false); void - wait (); + wait (bool work_queue = true); // Note: move-assignable to empty only. // diff --git a/libbuild2/context.ixx b/libbuild2/context.ixx index 6c8c428..2c62a45 100644 --- a/libbuild2/context.ixx +++ b/libbuild2/context.ixx @@ -26,8 +26,11 @@ namespace build2 inline wait_guard:: ~wait_guard () noexcept (false) { + // Don't work our own queue since we are most likely in stack unwinding + // causes by an exception. + // if (task_count != nullptr) - wait (); + wait (false); } inline wait_guard:: @@ -54,10 +57,15 @@ namespace build2 } inline void wait_guard:: - wait () + wait (bool wq) { phase_unlock u (phase ? ctx : nullptr, true /* delay */); - ctx->sched->wait (start_count, *task_count, u); + ctx->sched->wait (start_count, + *task_count, + u, + (wq + ? scheduler::work_queue::work_all + : scheduler::work_queue::work_none)); task_count = nullptr; } } diff --git a/libbuild2/dist/operation.cxx b/libbuild2/dist/operation.cxx index cfc90cf..6dc830b 100644 --- a/libbuild2/dist/operation.cxx +++ b/libbuild2/dist/operation.cxx @@ -112,7 +112,7 @@ namespace build2 // dir_path out (!rs.out_eq_src () ? out_src (d, rs) : dir_path ()); - const T& t (rs.ctx.targets.insert<T> ( + const T& t (rs.ctx.targets.insert_implied<T> ( move (d), move (out), p.leaf ().base ().string (), @@ -155,8 +155,12 @@ namespace build2 for (const dir_entry& e: dir_iterator (d, dir_iterator::no_follow)) { const path& n (e.path ()); + const string& s (n.string ()); - if (!n.empty () && n.string ().front () != '.') + if (s.compare (0, 4, ".git") != 0 && + s != ".bdep" && + s != ".bpkg" && + s != ".build2") try { if (e.type () == entry_type::directory) // Can throw. @@ -643,8 +647,8 @@ namespace build2 { l5 ([&]{trace << "bootstrap dist " << rs;}); - // Recursively enter/collect file targets in src_root ignoring those - // that start with a dot. + // Recursively enter/collect file targets in src_root ignoring the + // following ones: .git*, .bdep, .bpkg, and .build2. // // Note that, in particular, we also collect the symlinks which point // outside src_root (think of third-party project packaging with the diff --git a/libbuild2/file.cxx b/libbuild2/file.cxx index 18147a2..f834d8c 100644 --- a/libbuild2/file.cxx +++ b/libbuild2/file.cxx @@ -759,8 +759,9 @@ namespace build2 { if (!altn) altn = s.root_extra->altn; - else - assert (*altn == s.root_extra->altn); + else if (*altn != s.root_extra->altn) + fail << "naming scheme mismatch for " << out_root << " and " + << s.src_path (); if (s.root_extra->project) { @@ -2199,8 +2200,8 @@ namespace build2 const project_name& pn (project (iroot)); if (pn.empty ()) - fail (loc) << "project-local importation of target " << tgt - << " from an unnamed project"; + fail (loc) << "project-local importation of target '" << tgt + << "' from an unnamed project"; tgt.proj = pn; // Reduce to normal import. @@ -2218,7 +2219,7 @@ namespace build2 // maybe project-less does not make sense. // if (tgt.absolute ()) - fail (loc) << "absolute directory in imported target " << tgt; + fail (loc) << "absolute directory in imported target '" << tgt << "'"; // Get the project name and convert the target to unqualified. // @@ -2583,7 +2584,7 @@ namespace build2 src = p.second; if (out_root.empty ()) - fail (loc) << "no project for imported target " << tgt; + fail (loc) << "no project for imported target '" << tgt << "'"; } if (src) @@ -2854,7 +2855,8 @@ namespace build2 auto df = make_diag_frame ( [&tgt, &loc] (const diag_record& dr) { - dr << info (loc) << "while loading export stub for " << tgt; + dr << info (loc) << "while loading export stub for imported " + << "target '" << tgt << "'"; }); parser p (ctx); @@ -2865,7 +2867,7 @@ namespace build2 // assume the target is not exported. // if (v.empty () && !tgt.empty ()) - fail (loc) << "target " << tgt << " is not exported by project " + fail (loc) << "target '" << tgt << "' is not exported by project " << *proj; pair<names, const scope&> r (move (v), *root); @@ -2879,7 +2881,9 @@ namespace build2 } catch (const io_error& e) { - fail (loc) << "unable to read buildfile " << es << ": " << e << endf; + fail (loc) << "unable to read buildfile " << es << ": " << e << + info << "while loading export stub for imported target '" + << tgt << "'" << endf; } } else @@ -2902,7 +2906,7 @@ namespace build2 altn); if (!bf) fail << "no buildfile in " << src_base << " or parent directories " - << "for imported target " << tgt; + << "for imported target '" << tgt << "'"; if (!bf->empty ()) src_base = bf->directory (); @@ -2981,7 +2985,7 @@ namespace build2 // Validate the name. // if (tgt.qualified () && tgt.empty ()) - fail (loc) << "project-qualified empty name " << tgt; + fail (loc) << "importing project-qualified empty name '" << tgt << "'"; // If metadata is requested, delegate to import_direct() which will lookup // the target and verify the metadata was loaded. diff --git a/libbuild2/functions-filesystem.cxx b/libbuild2/functions-filesystem.cxx index 665a0f3..340c2bc 100644 --- a/libbuild2/functions-filesystem.cxx +++ b/libbuild2/functions-filesystem.cxx @@ -5,6 +5,7 @@ #include <libbuild2/function.hxx> #include <libbuild2/variable.hxx> +#include <libbuild2/filesystem.hxx> using namespace std; using namespace butl; @@ -95,14 +96,60 @@ namespace build2 return r; } + static bool + file_exists (path&& f) + { + if (f.relative () && path_traits::thread_current_directory () != nullptr) + f.complete (); + + return exists (f); + } + + static bool + directory_exists (dir_path&& d) + { + if (d.relative () && path_traits::thread_current_directory () != nullptr) + d.complete (); + + return exists (d); + } + void filesystem_functions (function_map& m) { - // @@ Maybe we should have the ability to mark the whole family as not - // pure? + // NOTE: anything that depends on relative path must handle the + // thread-specific curren directory override explicitly. function_family f (m, "filesystem"); + // $file_exists(<path>) + // + // Return true if a filesystem entry at the specified path exists and is a + // regular file (or is a symlink to a regular file) and false otherwise. + // + // Note that this function is not pure. + // + { + auto e (f.insert ("file_exists", false)); + + e += [](path f) {return file_exists (move (f));}; + e += [](names ns) {return file_exists (convert<path> (move (ns)));}; + } + + // $directory_exists(<path>) + // + // Return true if a filesystem entry at the specified path exists and is a + // directory (or is a symlink to a directory) and false otherwise. + // + // Note that this function is not pure. + // + { + auto e (f.insert ("directory_exists", false)); + + e += [](path f) {return directory_exists (path_cast<dir_path> (move (f)));}; + e += [](names ns) {return directory_exists (convert<dir_path> (move (ns)));}; + } + // $path_search(<pattern>[, <start-dir>]) // // Return filesystem paths that match the shell-like wildcard pattern. If diff --git a/libbuild2/functions-string.cxx b/libbuild2/functions-string.cxx index eccc6c7..58c17d7 100644 --- a/libbuild2/functions-string.cxx +++ b/libbuild2/functions-string.cxx @@ -38,14 +38,14 @@ namespace build2 { n -= sn; // Don't consider characters out of range. - for (size_t p (n);; ) + for (size_t p (n);; --p) { if ((ic ? icasecmp (ss, s.c_str () + p, sn) : s.compare (p, sn, ss)) == 0) return p; - if (--p == 0) + if (p == 0) break; } } diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index 873b2e9..1aa21d0 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -871,16 +871,16 @@ namespace build2 r->sudo = cast_null<string> (s["config.install.sudo"]); if (r->cmd == nullptr) - r->cmd = &cast<path> (s["config.install.cmd"]); + r->cmd = cast_null<path> (s["config.install.cmd"]); if (r->options == nullptr) r->options = cast_null<strings> (s["config.install.options"]); if (r->mode == nullptr) - r->mode = &cast<string> (s["config.install.mode"]); + r->mode = cast_null<string> (s["config.install.mode"]); if (r->dir_mode == nullptr) - r->dir_mode = &cast<string> (s["config.install.dir_mode"]); + r->dir_mode = cast_null<string> (s["config.install.dir_mode"]); return rs; } @@ -1064,6 +1064,10 @@ namespace build2 if (base.sudo != nullptr) args.push_back (base.sudo->c_str ()); + // Wouldn't be here otherwise. + // + assert (base.cmd != nullptr && base.dir_mode != nullptr); + args.push_back (base.cmd->string ().c_str ()); args.push_back ("-d"); @@ -1129,6 +1133,10 @@ namespace build2 if (base.sudo != nullptr) args.push_back (base.sudo->c_str ()); + // Wouldn't be here otherwise. + // + assert (base.cmd != nullptr && base.mode != nullptr); + args.push_back (base.cmd->string ().c_str ()); if (base.options != nullptr) diff --git a/libbuild2/parser.cxx b/libbuild2/parser.cxx index 5321cd5..53f808c 100644 --- a/libbuild2/parser.cxx +++ b/libbuild2/parser.cxx @@ -1757,11 +1757,18 @@ namespace build2 } else { - // Note that p cannot point to the last character since then it - // would have been a directory, not a simple name. - // - d = dir_path (ns[0].value, 0, p + 1); - ns[0].value.erase (0, p + 1); + try + { + // Note that p cannot point to the last character since then it + // would have been a directory, not a simple name. + // + d = dir_path (ns[0].value, 0, p + 1); + ns[0].value.erase (0, p + 1); + } + catch (const invalid_path& e) + { + fail (nloc) << "invalid scope path '" << e.path << "'"; + } } } @@ -5600,7 +5607,7 @@ namespace build2 } d {var, val_attrs, line, block, lhs, is}; - function<void (value&&, bool first)> iteration = + function<bool (value&&, bool first)> iteration = [this, &d] (value&& v, bool first) { // Rewind the stream. @@ -5638,12 +5645,15 @@ namespace build2 << "instead of " << t; lexer_ = ol; + return true; }; if (!iterate) { for (auto b (ns->begin ()), i (b), e (ns->end ()); i != e; ++i) { + bool first (i == b); + // Set the variable value. // bool pair (i->pair); @@ -5655,7 +5665,7 @@ namespace build2 if (etype != nullptr) typify (v, *etype, &var); - iteration (move (v), i == b); + iteration (move (v), first); } } else @@ -8802,15 +8812,22 @@ namespace build2 (p = path_traits::rfind_separator (ns[0].value)) != string::npos) { - // Note that p cannot point to the last character since - // then it would have been a directory, not a simple name. - // - string& s (ns[0].value); - - name = string (s, p + 1); - s.resize (p + 1); - qual.push_back (name_type (dir_path (move (s)))); - qual.back ().pair = '/'; + try + { + // Note that p cannot point to the last character since + // then it would have been a directory, not a simple name. + // + string& s (ns[0].value); + + name = string (s, p + 1); + s.resize (p + 1); + qual.push_back (name_type (dir_path (move (s)))); + qual.back ().pair = '/'; + } + catch (const invalid_path& e) + { + fail (loc) << "invalid scope path '" << e.path << "'"; + } } else name = move (ns[n - 1].value); @@ -10086,7 +10103,7 @@ namespace build2 o = out_src (d, *root_); } - return ctx->targets.insert<T> ( + return ctx->targets.insert_implied<T> ( move (d), move (o), p.leaf ().base ().string (), diff --git a/libbuild2/scope.cxx b/libbuild2/scope.cxx index 23781a8..8710a3d 100644 --- a/libbuild2/scope.cxx +++ b/libbuild2/scope.cxx @@ -48,10 +48,13 @@ namespace build2 const target_key* tk, const target_key* g1k, const target_key* g2k, + lookup_limit limit, size_t start_d) const { - assert (tk != nullptr || var.visibility != variable_visibility::target); - assert (g2k == nullptr || g1k != nullptr); + assert ((tk != nullptr || var.visibility != variable_visibility::target) && + (g2k == nullptr || g1k != nullptr) && + (limit == lookup_limit::none || + (limit == lookup_limit::target_type || tk != nullptr))); size_t d (0); @@ -79,7 +82,9 @@ namespace build2 // group, then we shouldn't be looking for stem in the target's // variables. In other words, once we "jump" to group, we stay there. // - lookup_type stem (s->lookup_original (var, tk, g1k, g2k, 2).first); + lookup_type stem ( + s->lookup_original ( + var, tk, g1k, g2k, lookup_limit::none, 2).first); // Check the cache. // @@ -115,7 +120,7 @@ namespace build2 cv = *stem; // Typify the cache value in case there is no stem (we still want to - // prepend/append things in type-aware way). + // prepend/append things in a type-aware way). // if (cv.type == nullptr && var.type != nullptr) typify (cv, *var.type, &var); @@ -152,7 +157,7 @@ namespace build2 { bool f (!s->target_vars.empty ()); - // Target. + // Target type/pattern-specific for the target. // if (++d >= start_d) { @@ -170,7 +175,7 @@ namespace build2 } } - // Group. + // Target type/pattern-specific for the group. // if (++d >= start_d) { @@ -205,7 +210,9 @@ namespace build2 // Note that we still increment the lookup depth so that we can compare // depths of variables with different visibilities. // - if (++d >= start_d && var.visibility != variable_visibility::target) + if (++d >= start_d && + limit != lookup_limit::target_type && + var.visibility != variable_visibility::target) { auto p (s->vars.lookup (var)); if (p.first != nullptr) @@ -232,11 +239,11 @@ namespace build2 return make_pair (lookup_type (), size_t (~0)); } - auto scope:: + scope::override_info scope:: lookup_override_info (const variable& var, const pair<lookup_type, size_t> original, bool target, - bool rule) const -> override_info + bool rule) const { assert (!rule || target); // Rule-specific is target-specific. diff --git a/libbuild2/scope.hxx b/libbuild2/scope.hxx index 09d61e9..ece78b7 100644 --- a/libbuild2/scope.hxx +++ b/libbuild2/scope.hxx @@ -186,14 +186,23 @@ namespace build2 return var.overrides == nullptr ? p : lookup_override (var, move (p)); } - // Implementation details (used by scope target lookup). The start_depth - // can be used to skip a number of initial lookups. + // Implementation details (used by scope and target lookup). + // + // The only valid values for limit are none and target_type and in the + // latter case the target key should not be NULL. If it is target_type, + // then only look in target type/pattern-specific variables. Note that if + // a target type/pattern-specific append/prepend modifies a scope + // variable, then the resulting value is considered target type/pattern- + // specific. + // + // The start_depth can be used to skip a number of initial lookups. // pair<lookup_type, size_t> - lookup_original (const variable&, + lookup_original (const variable& var, const target_key* tk = nullptr, const target_key* g1k = nullptr, const target_key* g2k = nullptr, + lookup_limit limit = lookup_limit::none, size_t start_depth = 1) const; pair<lookup_type, size_t> diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx index 84d2afc..a82ccb8 100644 --- a/libbuild2/script/parser.cxx +++ b/libbuild2/script/parser.cxx @@ -2460,19 +2460,19 @@ namespace build2 // struct loop_data { - lines::const_iterator i; - lines::const_iterator e; const function<exec_set_function>& exec_set; const function<exec_cmd_function>& exec_cmd; const function<exec_cond_function>& exec_cond; const function<exec_for_function>& exec_for; + lines::const_iterator i; + lines::const_iterator e; const iteration_index* ii; size_t& li; variable_pool* var_pool; decltype (fcend)& fce; lines::const_iterator& fe; - } ld {i, e, - exec_set, exec_cmd, exec_cond, exec_for, + } ld {exec_set, exec_cmd, exec_cond, exec_for, + i, e, ii, li, var_pool, fcend, @@ -2558,7 +2558,6 @@ namespace build2 const location& ll; size_t fli; iteration_index& fi; - } d {ld, env, vname, attrs, ll, fli, fi}; function<void (string&&)> f ( @@ -2676,12 +2675,19 @@ namespace build2 if (val) { - // If this value is a vector, then save its element type so + // If the value type provides custom iterate function, then + // use that (see value_type::iterate for details). + // + auto iterate (val.type != nullptr + ? val.type->iterate + : nullptr); + + // If this value is a container, then save its element type so // that we can typify each element below. // const value_type* etype (nullptr); - if (val.type != nullptr) + if (!iterate && val.type != nullptr) { etype = val.type->element_type; @@ -2693,37 +2699,84 @@ namespace build2 size_t fli (li); iteration_index fi {1, ii}; - names& ns (val.as<names> ()); - for (auto ni (ns.begin ()), ne (ns.end ()); ni != ne; ++ni) + names* ns (!iterate ? &val.as<names> () : nullptr); + + // Similar to above. + // + struct loop_data + { + const function<exec_set_function>& exec_set; + const function<exec_cmd_function>& exec_cmd; + const function<exec_cond_function>& exec_cond; + const function<exec_for_function>& exec_for; + lines::const_iterator i; + lines::const_iterator e; + const location& ll; + size_t& li; + variable_pool* var_pool; + const variable& var; + const attributes& val_attrs; + decltype (fcend)& fce; + lines::const_iterator& fe; + iteration_index& fi; + + } ld {exec_set, exec_cmd, exec_cond, exec_for, + i, e, + ll, li, + var_pool, *var, val_attrs, + fcend, fe, fi}; + + function<bool (value&&, bool first)> iteration = + [this, &ld] (value&& v, bool) { - li = fli; + ld.exec_for (ld.var, move (v), ld.val_attrs, ld.ll); - // Set the variable value. + // Find the construct end, if it is not found yet. // - bool pair (ni->pair); - names n; - n.push_back (move (*ni)); - if (pair) n.push_back (move (*++ni)); - value v (move (n)); // Untyped. + if (ld.fe == ld.e) + ld.fe = ld.fce (ld.i, true, false); + + if (!exec_lines ( + ld.i + 1, ld.fe, + ld.exec_set, ld.exec_cmd, ld.exec_cond, ld.exec_for, + &ld.fi, ld.li, + ld.var_pool)) + return false; + + ld.fi.index++; + return true; + }; - if (etype != nullptr) - typify (v, *etype, var); + if (!iterate) + { + for (auto nb (ns->begin ()), ni (nb), ne (ns->end ()); + ni != ne; + ++ni) + { + bool first (ni == nb); - exec_for (*var, move (v), val_attrs, ll); + li = fli; - // Find the construct end, if it is not found yet. - // - if (fe == e) - fe = fcend (i, true, false); + // Set the variable value. + // + bool pair (ni->pair); + names n; + n.push_back (move (*ni)); + if (pair) n.push_back (move (*++ni)); + value v (move (n)); // Untyped. - if (!exec_lines (i + 1, fe, - exec_set, exec_cmd, exec_cond, exec_for, - &fi, li, - var_pool)) - return false; + if (etype != nullptr) + typify (v, *etype, var); - fi.index++; + if (!iteration (move (v), first)) + return false; + } + } + else + { + if (!iterate (val, iteration)) + return false; } } diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx index b53fc23..ee2c9aa 100644 --- a/libbuild2/script/script.cxx +++ b/libbuild2/script/script.cxx @@ -237,56 +237,15 @@ namespace build2 } } - // Quote a string unconditionally, assuming it contains some special - // characters. - // - // If the quote character is present in the string then it is double - // quoted rather than single quoted. In this case the following characters - // are escaped: - // - // \" - // - static void - to_stream_quoted (ostream& o, const char* s) - { - if (strchr (s, '\'') != nullptr) - { - o << '"'; - - for (; *s != '\0'; ++s) - { - // Escape characters special inside double quotes. - // - if (strchr ("\\\"", *s) != nullptr) - o << '\\'; - - o << *s; - } - - o << '"'; - } - else - o << '\'' << s << '\''; - } - - static inline void - to_stream_quoted (ostream& o, const string& s) - { - to_stream_quoted (o, s.c_str ()); - } - // Quote if empty or contains spaces or any of the command line special // characters. // - static void + static inline void to_stream_q (ostream& o, const string& s) { // NOTE: update dump(line) if adding any new special character. // - if (s.empty () || s.find_first_of (" |&<>=\\\"'") != string::npos) - to_stream_quoted (o, s); - else - o << s; + to_stream_quoted (o, s, " |&<>=\\\"'"); } void diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx index 2a134a4..65e18d3 100644 --- a/libbuild2/target.cxx +++ b/libbuild2/target.cxx @@ -142,7 +142,7 @@ namespace build2 pair<lookup, size_t> target:: lookup_original (const variable& var, - bool target_only, + lookup_limit limit, const scope* bs, bool locked) const { @@ -188,9 +188,10 @@ namespace build2 // Skip looking up in the ad hoc group, which is semantically the // first/primary member. // - if ((g1 = group == nullptr + const target* g (group); // Atomic. + if ((g1 = g == nullptr ? nullptr - : group->adhoc_group () ? group->group : group)) + : g->adhoc_group () ? static_cast<const target*> (g->group) : g)) { auto p (g1->vars.lookup (var)); if (p.first != nullptr) @@ -203,7 +204,7 @@ namespace build2 // if (!r.first) { - if (!target_only) + if (limit != lookup_limit::target) { auto key = [locked] (const target* t) { @@ -220,7 +221,8 @@ namespace build2 auto p (bs->lookup_original (var, &tk, g1 != nullptr ? &g1k : nullptr, - g2 != nullptr ? &g2k : nullptr)); + g2 != nullptr ? &g2k : nullptr, + limit)); r.first = move (p.first); r.second = r.first ? r.second + p.second : p.second; @@ -240,7 +242,7 @@ namespace build2 // Note that here we want the original value without any overrides // applied. // - auto l (lookup_original (var, false, bs).first); + auto l (lookup_original (var, bs).first); if (l.defined () && l.belongs (*this)) // Existing var in this target. return vars.modify (l); // Ok since this is original. @@ -256,7 +258,7 @@ namespace build2 value& target:: append_locked (const variable& var, const scope* bs) { - auto l (lookup_original (var, false, bs, true /* locked */).first); + auto l (lookup_original (var, bs, true /* locked */).first); if (l.defined () && l.belongs (*this)) // Existing var in this target. return vars.modify (l); // Ok since this is original. @@ -270,7 +272,7 @@ namespace build2 } pair<lookup, size_t> target::opstate:: - lookup_original (const variable& var, bool target_only) const + lookup_original (const variable& var, lookup_limit limit) const { pair<lookup_type, size_t> r (lookup_type (), 0); @@ -285,7 +287,7 @@ namespace build2 // if (!r.first) { - auto p (target_->lookup_original (var, target_only)); + auto p (target_->lookup_original (var, limit)); r.first = move (p.first); r.second = r.first ? r.second + p.second : p.second; diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index 20cd32d..b008347 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -382,25 +382,28 @@ namespace build2 // // A target can be entered for several reasons that are useful to - // distinguish for diagnostics, when considering as the default - // target, etc. + // distinguish for diagnostics, when considering as the default target, etc. // - // Note that the order of the enumerators is arranged so that their - // integral values indicate whether one "overrides" the other. + // Note that the order of the enumerators is arranged so that their integral + // values indicate whether one "overrides" the other. // - // We refer to the targets other than real and implied as - // dynamically-created or just dynamic. + // We refer to the targets other than real and implied as dynamically- + // created or just dynamic. // - // @@ We have cases (like pkg-config extraction) where it should probably be - // prereq_file rather than implied (also audit targets.insert<> calls). + // Implied means the target's existence is implied by the presence of + // another entity in the buildfile or the environment. This can range from a + // target-specific variable assignment, to a group target (which may imply + // the presence of member), to targets implied by the presence of installed + // artifacts. // - // @@ Also, synthesized dependency declarations (e.g., in cc::link_rule) are - // fuzzy: they feel more `real` than `implied`. Maybe introduce - // `synthesized` in-between? - // - // @@ There are also now dynamically-discovered targets (ad hoc group - // members; see depdb-dyndep --dyn-target) which currently end up - // with prereq_new. + // Note that it's often tempting to add another class of declarations for + // what we refer to as synthesized dependencies (see cc::link_rule for an + // example). However, this is probably an orthogonal notion (maybe a + // sub-state) to declaration since we can synthesize a dependency based on + // any declaration, including real. And saying that synthesized is stronger + // than real feels wrong. To put it another way, synthesized dependencies + // are more about the target-prerequisite relationship rather that the + // target. // enum class target_decl: uint8_t { @@ -770,15 +773,19 @@ namespace build2 // As above but also return the depth at which the value is found. The // depth is calculated by adding 1 for each test performed. So a value // that is from the target will have depth 1. That from the group -- 2. - // From the innermost scope's target type/patter-specific variables -- - // 3. From the innermost scope's variables -- 4. And so on. The idea is - // that given two lookups from the same target, we can say which one came - // earlier. If no value is found, then the depth is set to ~0. + // From the innermost scope's target type/patter-specific variables for + // the target -- 3. From the innermost scope's target type/patter-specific + // variables for the group -- 4. From the innermost scope's variables -- + // 5. And so on. Note that if a target type/patter-specific append/prepend + // was applied, then the returned depth is of the innermost such + // append/prepend. The idea is that given two lookups from the same + // target, we can say which one came earlier. If no value is found, then + // the depth is set to ~0. // pair<lookup_type, size_t> lookup (const variable& var, const scope* bs = nullptr) const { - auto p (lookup_original (var, false, bs)); + auto p (lookup_original (var, bs)); return var.overrides == nullptr ? p : (bs != nullptr @@ -786,17 +793,31 @@ namespace build2 : base_scope ()).lookup_override (var, move (p), true); } - // If target_only is true, then only look in target and its target group - // without continuing in scopes. As an optimization, the caller can also - // pass the base scope of the target, if already known. If locked is true, - // assume the targets mutex is locked. + // If limit is target, then only look in target and its target group + // without continuing in scopes. If it is target_type, then additionally + // look in target type/pattern-specific variables in scopes (see + // scope::lookup_original() for the exact semantics). + // + // As an optimization, the caller can also pass the base scope of the + // target, if already known. + // + // If locked is true, assume the targets mutex is locked (not relevant if + // limit is target). // pair<lookup_type, size_t> lookup_original (const variable&, - bool target_only = false, + lookup_limit = lookup_limit::none, const scope* bs = nullptr, bool locked = false) const; + pair<lookup_type, size_t> + lookup_original (const variable& var, + const scope* bs, + bool locked = false) const + { + return lookup_original (var, lookup_limit::none, bs, locked); + } + // Return a value suitable for assignment. See scope for details. // value& @@ -989,11 +1010,11 @@ namespace build2 : target_->base_scope ().lookup_override (var, move (p), true, true); } - // If target_only is true, then only look in target and its target group - // without continuing in scopes. + // The limit semantics is the same as in target::lookup_original(). // pair<lookup_type, size_t> - lookup_original (const variable&, bool target_only = false) const; + lookup_original (const variable&, + lookup_limit = lookup_limit::none) const; // Return a value suitable for assignment. See target for details. // @@ -2012,17 +2033,18 @@ namespace build2 return pair<target&, bool> (p.first, p.second.mutex () != nullptr); } - // Note that the following versions always enter implied targets. + // The following versions always enter implied targets and are primarily + // meant for entering members when there is a group or vice versa. // template <typename T> T& - insert (const target_type& tt, - dir_path dir, - dir_path out, - string name, - optional<string> ext, - tracer& t, - bool skip_find = false) + insert_implied (const target_type& tt, + dir_path dir, + dir_path out, + string name, + optional<string> ext, + tracer& t, + bool skip_find = false) { return insert (tt, move (dir), @@ -2036,25 +2058,26 @@ namespace build2 template <typename T> T& - insert (const dir_path& dir, - const dir_path& out, - const string& name, - const optional<string>& ext, - tracer& t, - bool skip_find = false) + insert_implied (const dir_path& dir, + const dir_path& out, + const string& name, + const optional<string>& ext, + tracer& t, + bool skip_find = false) { - return insert<T> (T::static_type, dir, out, name, ext, t, skip_find); + return insert_implied<T> ( + T::static_type, dir, out, name, ext, t, skip_find); } template <typename T> T& - insert (const dir_path& dir, - const dir_path& out, - const string& name, - tracer& t, - bool skip_find = false) + insert_implied (const dir_path& dir, + const dir_path& out, + const string& name, + tracer& t, + bool skip_find = false) { - return insert<T> (dir, out, name, nullopt, t, skip_find); + return insert_implied<T> (dir, out, name, nullopt, t, skip_find); } // Note: not MT-safe so can only be used during serial execution. diff --git a/libbuild2/test/script/parser.test.cxx b/libbuild2/test/script/parser.test.cxx index 6838e47..7d63e7d 100644 --- a/libbuild2/test/script/parser.test.cxx +++ b/libbuild2/test/script/parser.test.cxx @@ -255,11 +255,11 @@ namespace build2 // really care. // file& tt ( - ctx.targets.insert<file> (work, - dir_path (), - "driver", - string (), - trace)); + ctx.targets.insert_implied<file> (work, + dir_path (), + "driver", + string (), + trace)); value& v ( tt.assign ( @@ -268,11 +268,12 @@ namespace build2 v = *ctx.build_host; testscript& st ( - ctx.targets.insert<testscript> (work, - dir_path (), - name.leaf ().base ().string (), - name.leaf ().extension (), - trace)); + ctx.targets.insert_implied<testscript> ( + work, + dir_path (), + name.leaf ().base ().string (), + name.leaf ().extension (), + trace)); tt.path (path ("driver")); st.path (name); diff --git a/libbuild2/test/script/script.cxx b/libbuild2/test/script/script.cxx index f7827f6..7862120 100644 --- a/libbuild2/test/script/script.cxx +++ b/libbuild2/test/script/script.cxx @@ -369,7 +369,10 @@ namespace build2 // value. In this case, presumably the override also affects the // script target and we will pick it up there. A bit fuzzy. // - auto p (root.test_target.lookup_original (var, target_only)); + auto p ( + root.test_target.lookup_original ( + var, + target_only ? lookup_limit::target : lookup_limit::none)); if (p.first) { diff --git a/libbuild2/utility.cxx b/libbuild2/utility.cxx index 1135851..ae7c9b0 100644 --- a/libbuild2/utility.cxx +++ b/libbuild2/utility.cxx @@ -16,7 +16,7 @@ #endif #include <cerrno> // ENOENT -#include <cstring> // strlen(), str[n]cmp() +#include <cstring> // strlen(), str[n]cmp(), strchr() #include <iostream> // cerr #include <libbuild2/target.hxx> @@ -1011,4 +1011,48 @@ namespace build2 return r; } + + void + to_stream_quoted (ostream& o, const char* s) + { + if (strchr (s, '\'') != nullptr) + { + o << '"'; + + for (; *s != '\0'; ++s) + { + // Escape characters special inside double quotes. + // + if (strchr ("\\\"", *s) != nullptr) + o << '\\'; + + o << *s; + } + + o << '"'; + } + else + o << '\'' << s << '\''; + } + + void + to_stream_quoted (ostream& o, const string& s, const char* special, bool e) + { + if ((e && s.empty ()) || s.find_first_of (special) != string::npos) + to_stream_quoted (o, s); + else + o << s; + } + + void + to_stream_quoted (ostream& o, const strings& ss, const char* special) + { + for (auto b (ss.begin ()), i (b), e (ss.end ()); i != e; ++i) + { + if (i != b) + o << ' '; + + to_stream_quoted (o, *i, special); + } + } } diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx index b534f41..f4fd7bc 100644 --- a/libbuild2/utility.hxx +++ b/libbuild2/utility.hxx @@ -877,17 +877,29 @@ namespace build2 // template <typename I, typename F> void - append_option_values (cstrings&, - const char* opt, - I begin, I end, - F&& get = [] (const string& s) {return s.c_str ();}); + append_option_values ( + cstrings&, + const char* opt, + I begin, I end, + F&& get = [] (const string& s) {return s.c_str ();}); template <typename I, typename F> void - append_option_values (sha256&, - const char* opt, - I begin, I end, - F&& get = [] (const string& s) {return s;}); + append_option_values ( + sha256&, + const char* opt, + I begin, I end, + F&& get = [] (const string& s) -> const string& {return s;}); + + // As above but in a combined form (e.g., -L/usr/local/lib). + // + template <typename I, typename F> + void + append_combined_option_values ( + strings&, + const char* opt, + I begin, I end, + F&& get = [] (const string& s) -> const string& {return s;}); // As above but append a single option (used for append/hash uniformity). // @@ -1102,6 +1114,39 @@ namespace build2 // optional<uint64_t> parse_number (const string&, uint64_t max = UINT64_MAX); + + + // Write a string to a stream, single or double-quoting it unconditionally. + // + // If no quote characters are present in the string, then it is single- + // quoted. Otherwise, it is double quoted and the `\"` characters (backslach + // and double quote) inside the string are escaped. + // + LIBBUILD2_SYMEXPORT void + to_stream_quoted (ostream&, const char*); + + inline void + to_stream_quoted (ostream& o, const string& s) + { + to_stream_quoted (o, s.c_str ()); + } + + // As above but only quote the string if it contains any of the specified + // special characters (which should include backslash as well as single and + // double quotes) or is empty (unless empty is false). + // + LIBBUILD2_SYMEXPORT void + to_stream_quoted (ostream&, const string&, + const char* special, bool empty = true); + + // Write a vector of strings to a stream separating elements with a space + // and quoting each element if it is empty or contains any of the specified + // special characters (which should include space and backslash as well as + // single and double quotes; this is also the default which is suitable for + // displaying command line options, etc). + // + LIBBUILD2_SYMEXPORT void + to_stream_quoted (ostream&, const strings&, const char* special = " \\\"'"); } #include <libbuild2/utility.ixx> diff --git a/libbuild2/utility.txx b/libbuild2/utility.txx index d2fc29c..cdf510f 100644 --- a/libbuild2/utility.txx +++ b/libbuild2/utility.txx @@ -5,16 +5,16 @@ namespace build2 { template <typename I, typename F> void - append_option_values (cstrings& args, const char* o, I b, I e, F&& get) + append_option_values (cstrings& ss, const char* o, I b, I e, F&& get) { if (b != e) { - args.reserve (args.size () + (e - b)); + ss.reserve (ss.size () + (e - b)); for (; b != e; ++b) { - args.push_back (o); - args.push_back (get (*b)); + ss.push_back (o); + ss.push_back (get (*b)); } } } @@ -30,6 +30,19 @@ namespace build2 } } + template <typename I, typename F> + void + append_combined_option_values (strings& ss, const char* o, I b, I e, F&& get) + { + if (b != e) + { + ss.reserve (ss.size () + (e - b)); + + for (; b != e; ++b) + ss.push_back (string (o) += get (*b)); + } + } + template <typename K> basic_path<char, K> relative (const basic_path<char, K>& p) diff --git a/libbuild2/variable.cxx b/libbuild2/variable.cxx index 078c13a..0ec23d3 100644 --- a/libbuild2/variable.cxx +++ b/libbuild2/variable.cxx @@ -510,7 +510,7 @@ namespace build2 &simple_append<bool>, // Prepend same as append. &simple_reverse<bool>, nullptr, // No cast (cast data_ directly). - nullptr, // No compare (compare as POD). + &simple_compare<bool>, nullptr, // Never empty. nullptr, // Subscript. nullptr // Iterate. @@ -570,7 +570,7 @@ namespace build2 &simple_append<int64_t>, // Prepend same as append. &simple_reverse<int64_t>, nullptr, // No cast (cast data_ directly). - nullptr, // No compare (compare as POD). + &simple_compare<int64_t>, nullptr, // Never empty. nullptr, // Subscript. nullptr // Iterate. @@ -632,7 +632,7 @@ namespace build2 &simple_append<uint64_t>, // Prepend same as append. &simple_reverse<uint64_t>, nullptr, // No cast (cast data_ directly). - nullptr, // No compare (compare as POD). + &simple_compare<uint64_t>, nullptr, // Never empty. nullptr, // Subscript. nullptr // Iterate. @@ -2122,9 +2122,9 @@ namespace build2 return r; } - static void + static bool json_iterate (const value& val, - const function<void (value&&, bool first)>& f) + const function<bool (value&&, bool first)>& f) { // Implement in terms of subscript for consistency (in particular, // iterating over simple values like number, string). @@ -2136,8 +2136,11 @@ namespace build2 if (!e.second) break; - f (move (e.first), i == 0); + if (!f (move (e.first), i == 0)) + return false; } + + return true; } const json_value value_traits<json_value>::empty_instance; diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx index 6dfbbc6..a14c52b 100644 --- a/libbuild2/variable.hxx +++ b/libbuild2/variable.hxx @@ -124,12 +124,13 @@ namespace build2 const location& sloc, const location& bloc); - // Custom iteration function. It should invoked the specified function for + // Custom iteration function. It should invoke the specified function for // each element in order. If NULL, then the generic implementation is - // used. The passed value is never NULL. + // used. The passed value is never NULL. If the specified function returns + // false, then stop the iteration and return false. Otherwise return true. // - void (*const iterate) (const value&, - const function<void (value&&, bool first)>&); + bool (*const iterate) (const value&, + const function<bool (value&&, bool first)>&); }; // The order of the enumerators is arranged so that their integral values @@ -612,6 +613,15 @@ namespace build2 vars (v != nullptr ? m : nullptr) {} }; + // Variable lookup limit (see {scope,target}::lookup_original()). + // + enum class lookup_limit + { + none, + target_type, + target + }; + // Two lookups are equal if they point to the same variable. // inline bool diff --git a/libbuild2/variable.txx b/libbuild2/variable.txx index a1ee340..6e00f89 100644 --- a/libbuild2/variable.txx +++ b/libbuild2/variable.txx @@ -686,16 +686,19 @@ namespace build2 // Provide iterate for vector<T> for efficiency. // template <typename T> - void + bool vector_iterate (const value& val, - const function<void (value&&, bool first)>& f) + const function<bool (value&&, bool first)>& f) { const auto& v (val.as<vector<T>> ()); // Never NULL. for (auto b (v.begin ()), i (b), e (v.end ()); i != e; ++i) { - f (value (*i), i == b); + if (!f (value (*i), i == b)) + return false; } + + return true; } // Make sure these are static-initialized together. Failed that VC will make @@ -1071,16 +1074,19 @@ namespace build2 // Provide iterate for set<T> for efficiency. // template <typename T> - void + bool set_iterate (const value& val, - const function<void (value&&, bool first)>& f) + const function<bool (value&&, bool first)>& f) { const auto& v (val.as<set<T>> ()); // Never NULL. for (auto b (v.begin ()), i (b), e (v.end ()); i != e; ++i) { - f (value (*i), i == b); + if (!f (value (*i), i == b)) + return false; } + + return true; } // Make sure these are static-initialized together. Failed that VC will make |