aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/build/script/parser.test.cxx10
-rw-r--r--libbuild2/buildfile4
-rw-r--r--libbuild2/cc/common.cxx12
-rw-r--r--libbuild2/cc/compile-rule.cxx27
-rw-r--r--libbuild2/cc/guess.cxx54
-rw-r--r--libbuild2/cc/guess.hxx3
-rw-r--r--libbuild2/cc/link-rule.cxx483
-rw-r--r--libbuild2/cc/pkgconfig.cxx4
-rw-r--r--libbuild2/cli/rule.cxx3
-rw-r--r--libbuild2/cli/target.cxx6
-rw-r--r--libbuild2/config/operation.cxx3
-rw-r--r--libbuild2/context.hxx2
-rw-r--r--libbuild2/context.ixx14
-rw-r--r--libbuild2/dist/operation.cxx12
-rw-r--r--libbuild2/file.cxx26
-rw-r--r--libbuild2/functions-filesystem.cxx51
-rw-r--r--libbuild2/functions-string.cxx4
-rw-r--r--libbuild2/install/rule.cxx14
-rw-r--r--libbuild2/parser.cxx51
-rw-r--r--libbuild2/scope.cxx25
-rw-r--r--libbuild2/scope.hxx15
-rw-r--r--libbuild2/script/parser.cxx111
-rw-r--r--libbuild2/script/script.cxx45
-rw-r--r--libbuild2/target.cxx20
-rw-r--r--libbuild2/target.hxx121
-rw-r--r--libbuild2/test/script/parser.test.cxx21
-rw-r--r--libbuild2/test/script/script.cxx5
-rw-r--r--libbuild2/utility.cxx46
-rw-r--r--libbuild2/utility.hxx61
-rw-r--r--libbuild2/utility.txx21
-rw-r--r--libbuild2/variable.cxx15
-rw-r--r--libbuild2/variable.hxx18
-rw-r--r--libbuild2/variable.txx18
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