aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2022-11-29 08:02:51 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2022-11-29 08:02:51 +0200
commit05afdd8ca16c7066d12510a27e2fc08743bb2e95 (patch)
tree90c7a43a08c47786df3869d68d7b625bf93174d3
parent472d6d0e49c0114f46ff31267d09acdbf9fba421 (diff)
Fix backlink logic for target groups
We used to backlink ad hoc group members both via the group and as individual members. And for explicit groups it was done only via individual members, which means it only works correctly if every member is individually updated. Now both types of groups are backlinked from the group target.
-rw-r--r--libbuild2/algorithm.cxx373
-rw-r--r--libbuild2/algorithm.hxx4
-rw-r--r--libbuild2/cc/link-rule.cxx29
-rw-r--r--libbuild2/context.cxx8
-rw-r--r--libbuild2/context.hxx17
-rw-r--r--libbuild2/diagnostics.cxx195
-rw-r--r--libbuild2/diagnostics.hxx15
-rw-r--r--libbuild2/target.cxx3
8 files changed, 469 insertions, 175 deletions
diff --git a/libbuild2/algorithm.cxx b/libbuild2/algorithm.cxx
index 0326839..cc48a38 100644
--- a/libbuild2/algorithm.cxx
+++ b/libbuild2/algorithm.cxx
@@ -1613,11 +1613,26 @@ namespace build2
return ts;
}
- void
- update_backlink (const file& f, const path& l, bool changed, backlink_mode m)
+ static inline const char*
+ update_backlink_name (backlink_mode m, bool to_dir)
{
using mode = backlink_mode;
+ const char* r (nullptr);
+ switch (m)
+ {
+ case mode::link:
+ case mode::symbolic: r = verb >= 3 ? "ln -sf" : verb >= 2 ? "ln -s" : "ln"; break;
+ case mode::hard: r = verb >= 3 ? "ln -f" : "ln"; break;
+ case mode::copy:
+ case mode::overwrite: r = to_dir ? "cp -r" : "cp"; break;
+ }
+ return r;
+ }
+
+ void
+ update_backlink (const file& f, const path& l, bool changed, backlink_mode m)
+ {
const path& p (f.path ());
dir_path d (l.directory ());
@@ -1629,25 +1644,17 @@ namespace build2
// actually updated to signal to the user that the updated out target is
// now available in src.
//
- if (verb <= 2)
+ if (verb == 1 || verb == 2)
{
if (changed || !butl::entry_exists (l,
false /* follow_symlinks */,
true /* ignore_errors */))
{
- const char* c (nullptr);
- switch (m)
- {
- case mode::link:
- case mode::symbolic: c = verb >= 2 ? "ln -s" : "ln"; break;
- case mode::hard: c = "ln"; break;
- case mode::copy:
- case mode::overwrite: c = l.to_directory () ? "cp -r" : "cp"; break;
- }
+ const char* c (update_backlink_name (m, l.to_directory ()));
// Note: 'ln foo/ bar/' means a different thing (and below).
//
- if (verb >= 2)
+ if (verb == 2)
text << c << ' ' << p.string () << ' ' << l.string ();
else
print_diag (c, f, d);
@@ -1670,25 +1677,15 @@ namespace build2
{
// As above but with a slightly different diagnostics.
- using mode = backlink_mode;
-
dir_path d (l.directory ());
- if (verb <= 2)
+ if (verb == 1 || verb == 2)
{
if (changed || !butl::entry_exists (l,
false /* follow_symlinks */,
true /* ignore_errors */))
{
- const char* c (nullptr);
- switch (m)
- {
- case mode::link:
- case mode::symbolic: c = verb >= 2 ? "ln -s" : "ln"; break;
- case mode::hard: c = "ln"; break;
- case mode::copy:
- case mode::overwrite: c = l.to_directory () ? "cp -r" : "cp"; break;
- }
+ const char* c (update_backlink_name (m, l.to_directory ()));
// Note: 'ln foo/ bar/' means a different thing (and above) so strip
// trailing directory separator (but keep as path for relative).
@@ -1761,17 +1758,8 @@ namespace build2
{
if (verb >= verbosity)
{
- const char* c (nullptr);
- switch (m)
- {
- case mode::link:
- case mode::symbolic: c = "ln -sf"; break;
- case mode::hard: c = "ln -f"; break;
- case mode::copy:
- case mode::overwrite: c = d ? "cp -r" : "cp"; break;
- }
-
- text << c << ' ' << p.string () << ' ' << l.string ();
+ text << update_backlink_name (m, d) << ' ' << p.string () << ' '
+ << l.string ();
}
};
@@ -1926,9 +1914,15 @@ namespace build2
struct backlink: auto_rm<path>
{
using path_type = build2::path;
+ using target_type = build2::target;
reference_wrapper<const path_type> target;
- backlink_mode mode;
+ backlink_mode mode;
+
+ // Ad hoc group-specific information for diagnostics (see below).
+ //
+ const target_type* member = nullptr;
+ bool print = true;
backlink (const path_type& t, path_type&& l, backlink_mode m, bool active)
: auto_rm<path_type> (move (l), active), target (t), mode (m)
@@ -1950,33 +1944,65 @@ namespace build2
};
// Normally (i.e., on sane platforms that don't have things like PDBs, etc)
- // there will be just one backlink so optimize for that.
+ // there will be just one or two backlinks so optimize for that.
//
- using backlinks = small_vector<backlink, 1>;
+ using backlinks = small_vector<backlink, 2>;
- static optional<backlink_mode>
- backlink_test (const target& t, const lookup& l)
+ static optional<pair<backlink_mode, bool>>
+ backlink_test (const target& t, const lookup& l, optional<backlink_mode> gm)
{
using mode = backlink_mode;
- optional<mode> r;
- const string& v (cast<string> (l));
+ const names& ns (cast<names> (l));
- if (v == "true") r = mode::link;
- else if (v == "symbolic") r = mode::symbolic;
- else if (v == "hard") r = mode::hard;
- else if (v == "copy") r = mode::copy;
- else if (v == "overwrite") r = mode::overwrite;
- else if (v != "false")
- fail << "invalid backlink variable value '" << v << "' "
+ if (ns.size () != 1 && ns.size () != 2)
+ {
+ fail << "invalid backlink variable value '" << ns << "' "
<< "specified for target " << t;
+ }
- return r;
+ optional<mode> m;
+ for (;;) // Breakout loop.
+ {
+ const name& n (ns.front ());
+
+ if (n.simple ())
+ {
+ const string& v (n.value);
+
+ if (v == "true") {m = mode::link; break;}
+ else if (v == "symbolic") {m = mode::symbolic; break;}
+ else if (v == "hard") {m = mode::hard; break;}
+ else if (v == "copy") {m = mode::copy; break;}
+ else if (v == "overwrite") {m = mode::overwrite; break;}
+ else if (v == "false") { break;}
+ else if (v == "group") {if ((m = gm)) break;}
+ }
+
+ fail << "invalid backlink variable value mode component '" << n << "' "
+ << "specified for target " << t << endf;
+ }
+
+ bool np (false); // "not print"
+ if (ns.size () == 2)
+ {
+ const name& n (ns.back ());
+
+ if (n.simple () && (n.value == "true" || (np = (n.value == "false"))))
+ ;
+ else
+ fail << "invalid backlink variable value print component '" << n
+ << "' specified for target " << t;
+ }
+
+ return m ? optional<pair<mode, bool>> (make_pair (*m, !np)) : nullopt;
}
static optional<backlink_mode>
backlink_test (action a, target& t)
{
+ using mode = backlink_mode;
+
context& ctx (t.ctx);
// Note: the order of these checks is from the least to most expensive.
@@ -1986,9 +2012,20 @@ namespace build2
if (a.outer () || (a != perform_update_id && a != perform_clean_id))
return nullopt;
- // Only file-based targets in the out tree can be backlinked.
+ // Only targets in the out tree can be backlinked.
//
- if (!t.out.empty () || !t.is_a<file> ())
+ if (!t.out.empty ())
+ return nullopt;
+
+ // Only file-based targets or groups containing file-based targets can be
+ // backlinked. Note that we don't do the "file-based" check of the latter
+ // case here since they can still be execluded. So instead we are prepared
+ // to handle the empty backlinks list.
+ //
+ // @@ Potentially members could only be resolved in execute. I guess we
+ // don't support backlink for such groups at the moment.
+ //
+ if (!t.is_a<file> () && t.group_members (a).members == nullptr)
return nullopt;
// Neither an out-of-project nor in-src configuration can be forwarded.
@@ -2012,7 +2049,13 @@ namespace build2
if (!l.defined ())
l = ctx.global_scope.lookup (*ctx.var_backlink, t.key ());
- return l ? backlink_test (t, l) : nullopt;
+ optional<pair<mode, bool>> r (l ? backlink_test (t, l, nullopt) : nullopt);
+
+ if (r && !r->second)
+ fail << "backlink variable value print component cannot be false "
+ << "for primary target " << t;
+
+ return r ? optional<mode> (r->first) : nullopt;
}
static backlinks
@@ -2024,56 +2067,100 @@ namespace build2
const scope& s (t.base_scope ());
backlinks bls;
- auto add = [&bls, &s] (const path& p, mode m)
+ auto add = [&bls, &s] (const path& p,
+ mode m,
+ const target* mt = nullptr,
+ bool print = true)
{
bls.emplace_back (p,
s.src_path () / p.leaf (s.out_path ()),
m,
!s.ctx.dry_run /* active */);
+
+ if (mt != nullptr)
+ {
+ backlink& bl (bls.back ());
+ bl.member = mt;
+ bl.print = print;
+ }
};
- // First the target itself.
+ // Check for a custom backlink mode for this member. If none, then
+ // inherit the one from the group (so if the user asked to copy
+ // .exe, we will also copy .pdb).
//
- add (t.as<file> ().path (), m);
-
- // Then ad hoc group file/fsdir members, if any.
+ // Note that we want to avoid group or tt/patter-spec lookup. And
+ // since this is an ad hoc member (which means it was either declared
+ // in the buildfile or added by the rule), we assume that the value,
+ // if any, will be set as a target or rule-specific variable.
//
- for (const target* mt (t.adhoc_member);
- mt != nullptr;
- mt = mt->adhoc_member)
+ auto member_mode = [a, m, &ctx] (const target& mt)
+ -> optional<pair<mode, bool>>
{
- const path* p (nullptr);
+ lookup l (mt.state[a].vars[ctx.var_backlink]);
- if (const file* f = mt->is_a<file> ())
- {
- p = &f->path ();
+ if (!l)
+ l = mt.vars[ctx.var_backlink];
- if (p->empty ()) // The "trust me, it's somewhere" case.
- p = nullptr;
- }
- else if (const fsdir* d = mt->is_a<fsdir> ())
- p = &d->dir;
+ return l ? backlink_test (mt, l, m) : make_pair (m, true);
+ };
+
+ // @@ Currently we don't handle the following cases:
+ //
+ // 1. File-based explicit groups.
+ //
+ // 2. Ad hoc subgroups in explicit groups.
+ //
+ // Note: see also the corresponding code in backlink_update_post().
+ //
+ if (file* f = t.is_a<file> ())
+ {
+ // First the target itself.
+ //
+ add (f->path (), m, f, true); // Note: always printed.
- if (p != nullptr)
+ // Then ad hoc group file/fsdir members, if any.
+ //
+ for (const target* mt (t.adhoc_member);
+ mt != nullptr;
+ mt = mt->adhoc_member)
{
- // Check for a custom backlink mode for this member. If none, then
- // inherit the one from the group (so if the user asked to copy .exe,
- // we will also copy .pdb).
- //
- // Note that we want to avoid group or tt/patter-spec lookup. And
- // since this is an ad hoc member (which means it was either declared
- // in the buildfile or added by the rule), we assume that the value,
- // if any, will be set as a target or rule-specific variable.
- //
- lookup l (mt->state[a].vars[ctx.var_backlink]);
+ const path* p (nullptr);
+
+ if (const file* f = mt->is_a<file> ())
+ {
+ p = &f->path ();
- if (!l)
- l = mt->vars[ctx.var_backlink];
+ if (p->empty ()) // The "trust me, it's somewhere" case.
+ p = nullptr;
+ }
+ else if (const fsdir* d = mt->is_a<fsdir> ())
+ p = &d->dir;
- optional<mode> bm (l ? backlink_test (*mt, l) : m);
+ if (p != nullptr)
+ {
+ if (optional<pair<mode, bool>> m = member_mode (*mt))
+ add (*p, m->first, mt, m->second);
+ }
+ }
+ }
+ else
+ {
+ // Explicit group.
+ //
+ group_view gv (t.group_members (a));
+ assert (gv.members != nullptr);
- if (bm)
- add (*p, *bm);
+ for (size_t i (0); i != gv.count; ++i)
+ {
+ if (const target* mt = gv.members[i])
+ {
+ if (const file* f = mt->is_a<file> ())
+ {
+ if (optional<pair<mode, bool>> m = member_mode (*mt))
+ add (f->path (), m->first);
+ }
+ }
}
}
@@ -2087,29 +2174,89 @@ namespace build2
}
static void
- backlink_update_post (target& t, target_state ts, backlinks& bls)
+ backlink_update_post (target& t, target_state ts,
+ backlink_mode m, backlinks& bls)
{
if (ts == target_state::failed)
return; // Let auto rm clean things up.
- // Make backlinks.
- //
- for (auto b (bls.begin ()), i (b); i != bls.end (); ++i)
+ context& ctx (t.ctx);
+
+ file* ft (t.is_a<file> ());
+
+ if (ft != nullptr && bls.size () == 1)
{
- const backlink& bl (*i);
+ // Single file-based target.
+ //
+ const backlink& bl (bls.front ());
- if (i == b)
- update_backlink (t.as<file> (),
- bl.path,
- ts == target_state::changed,
- bl.mode);
- else
- update_backlink (t.ctx, bl.target, bl.path, bl.mode);
+ update_backlink (*ft,
+ bl.path,
+ ts == target_state::changed,
+ bl.mode);
+ }
+ else
+ {
+ // Explicit or ad hoc group.
+ //
+ // What we have below is a custom variant of update_backlink(file).
+ //
+ dir_path d (bls.front ().path.directory ());
+
+ // First print the verbosity level 1 diagnostics. Level 2 and higher are
+ // handled by the update_backlink() calls below.
+ //
+ if (verb == 1)
+ {
+ bool changed (ts == target_state::changed);
+
+ if (!changed)
+ {
+ for (const backlink& bl: bls)
+ {
+ changed = !butl::entry_exists (bl.path,
+ false /* follow_symlinks */,
+ true /* ignore_errors */);
+ if (changed)
+ break;
+ }
+ }
+
+ if (changed)
+ {
+ const char* c (update_backlink_name (m, false /* to_dir */));
+
+ // For explicit groups we only print the group target. For ad hoc
+ // groups we print all the members except those explicitly excluded.
+ //
+ if (ft == nullptr)
+ print_diag (c, t, d);
+ else
+ {
+ vector<target_key> tks;
+ tks.reserve (bls.size ());
+
+ for (const backlink& bl: bls)
+ if (bl.print)
+ tks.push_back (bl.member->key ());
+
+ print_diag (c, move (tks), d);
+ }
+ }
+ }
+
+ if (!exists (d))
+ mkdir_p (d, 2 /* verbosity */);
+
+ // Make backlinks.
+ //
+ for (const backlink& bl: bls)
+ update_backlink (ctx, bl.target, bl.path, bl.mode, 2 /* verbosity */);
}
// Cancel removal.
//
- if (!t.ctx.dry_run)
+ if (!ctx.dry_run)
{
for (backlink& bl: bls)
bl.cancel ();
@@ -2150,15 +2297,27 @@ namespace build2
// which is ok since such targets are probably not interesting for
// backlinking.
//
+ // Note also that for group members (both ad hoc and non) backlinking
+ // is handled when updating/cleaning the group.
+ //
backlinks bls;
- optional<backlink_mode> blm (backlink_test (a, t));
+ optional<backlink_mode> blm;
- if (blm)
+ if (t.group == nullptr) // Matched so must be already resolved.
{
- if (a == perform_update_id)
- bls = backlink_update_pre (a, t, *blm);
- else
- backlink_clean_pre (a, t, *blm);
+ blm = backlink_test (a, t);
+
+ if (blm)
+ {
+ if (a == perform_update_id)
+ {
+ bls = backlink_update_pre (a, t, *blm);
+ if (bls.empty ())
+ blm = nullopt;
+ }
+ else
+ backlink_clean_pre (a, t, *blm);
+ }
}
// Note: see similar code in set_rule_trace() for match.
@@ -2196,7 +2355,7 @@ namespace build2
if (blm)
{
if (a == perform_update_id)
- backlink_update_post (t, ts, bls);
+ backlink_update_post (t, ts, *blm, bls);
}
}
catch (const failed&)
diff --git a/libbuild2/algorithm.hxx b/libbuild2/algorithm.hxx
index aae59ca..9a6a56b 100644
--- a/libbuild2/algorithm.hxx
+++ b/libbuild2/algorithm.hxx
@@ -964,7 +964,7 @@ namespace build2
bool changed,
backlink_mode = backlink_mode::link);
- // Note: verbosite should be 2 or greater.
+ // Note: verbosity should be 2 or greater.
//
LIBBUILD2_SYMEXPORT void
update_backlink (context&,
@@ -973,7 +973,7 @@ namespace build2
backlink_mode = backlink_mode::link,
uint16_t verbosity = 3);
- // Note: verbosite should be 2 or greater.
+ // Note: verbosity should be 2 or greater.
//
LIBBUILD2_SYMEXPORT void
clean_backlink (context&,
diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx
index 20c6c62..39f6f54 100644
--- a/libbuild2/cc/link-rule.cxx
+++ b/libbuild2/cc/link-rule.cxx
@@ -1537,6 +1537,11 @@ namespace build2
if (wasm.path ().empty ())
wasm.derive_path ();
+ // We don't want to print this member at level 1 diagnostics.
+ //
+ wasm.state[a].assign (ctx.var_backlink) = names {
+ name ("group"), name ("false")};
+
// If we have -pthread then we get additional .worker.js file
// which is used for thread startup. In a somewhat hackish way we
// represent it as an exe{} member to make sure it gets installed
@@ -1560,6 +1565,11 @@ namespace build2
if (worker.path ().empty ())
worker.derive_path ();
+
+ // We don't want to print this member at level 1 diagnostics.
+ //
+ worker.state[a].assign (ctx.var_backlink) = names {
+ name ("group"), name ("false")};
}
}
@@ -1587,6 +1597,11 @@ namespace build2
//
if (pdb.path ().empty ())
pdb.derive_path (t.path ());
+
+ // We don't want to print this member at level 1 diagnostics.
+ //
+ pdb.state[a].assign (ctx.var_backlink) = names {
+ name ("group"), name ("false")};
}
}
}
@@ -1666,14 +1681,12 @@ namespace build2
// exists (windows_rpath_assembly() does take care to clean it up
// if not used).
//
-#ifdef _WIN32
- target& dir =
-#endif
+ target& dir (
add_adhoc_member (t,
fsdir::static_type,
path_cast<dir_path> (t.path () + ".dlls"),
t.out,
- string () /* name */);
+ string () /* name */));
// By default our backlinking logic will try to symlink the
// directory and it can even be done on Windows using junctions.
@@ -1687,9 +1700,15 @@ namespace build2
// Wine. So we only resort to copy-link'ing if we are running on
// Windows.
//
+ // We also don't want to print this member at level 1 diagnostics.
+ //
+ dir.state[a].assign (ctx.var_backlink) = names {
#ifdef _WIN32
- dir.state[a].assign (ctx.var_backlink) = "copy";
+ name ("copy"), name ("false")
+#else
+ name ("group"), name ("false")
#endif
+ };
}
}
}
diff --git a/libbuild2/context.cxx b/libbuild2/context.cxx
index 4858c4c..1da6fd3 100644
--- a/libbuild2/context.cxx
+++ b/libbuild2/context.cxx
@@ -614,13 +614,15 @@ namespace build2
var_extension = &vp.insert<string> ("extension", v_t);
var_update = &vp.insert<string> ("update", v_q);
var_clean = &vp.insert<bool> ("clean", v_t);
- var_backlink = &vp.insert<string> ("backlink", v_t);
+ var_backlink = &vp.insert ("backlink", v_t); // Untyped.
var_include = &vp.insert<string> ("include", v_q);
// Backlink executables and (generated) documentation by default.
//
- gs.target_vars[exe::static_type]["*"].assign (var_backlink) = "true";
- gs.target_vars[doc::static_type]["*"].assign (var_backlink) = "true";
+ gs.target_vars[exe::static_type]["*"].assign (var_backlink) =
+ names {name ("true")};
+ gs.target_vars[doc::static_type]["*"].assign (var_backlink) =
+ names {name ("true")};
// Register builtin rules.
//
diff --git a/libbuild2/context.hxx b/libbuild2/context.hxx
index c16181f..b71a068 100644
--- a/libbuild2/context.hxx
+++ b/libbuild2/context.hxx
@@ -505,7 +505,12 @@ namespace build2
//
const variable* var_clean;
- // Forwarded configuration backlink mode. Valid values are:
+ // Forwarded configuration backlink mode. The value has two components
+ // in the form:
+ //
+ // <mode> [<print>]
+ //
+ // Valid <mode> values are:
//
// false - no link.
// true - make a link using appropriate mechanism.
@@ -513,8 +518,14 @@ namespace build2
// hard - make a hard link.
// copy - make a copy.
// overwrite - copy over but don't remove on clean.
+ // group - inherit the group mode (only valid for group members).
//
- // Note that it can be set by a matching rule as a rule-specific variable.
+ // While the <print> component should be either true or false and can be
+ // used to suppress printing of specific ad hoc group members at verbosity
+ // level 1. Note that it cannot be false for the primary member.
+ //
+ // Note that this value can be set by a matching rule as a rule-specific
+ // variable.
//
// Note also that the overwrite mode was originally meant for handling
// pregenerated source code. But in the end this did not pan out for
@@ -538,7 +549,7 @@ namespace build2
// just expose a mechanism to delegate to a different rule, which we
// already have).
//
- // [string] target visibility
+ // [names] target visibility
//
const variable* var_backlink;
diff --git a/libbuild2/diagnostics.cxx b/libbuild2/diagnostics.cxx
index bc74db3..d91150b 100644
--- a/libbuild2/diagnostics.cxx
+++ b/libbuild2/diagnostics.cxx
@@ -82,39 +82,26 @@ namespace build2
dr << r;
}
- template <typename L> // L can be target_key, path, or string.
- static void
- print_diag_impl (const char* p,
- const L* l, bool lempty,
- vector<target_key>&& rs,
- const char* c)
- {
- assert (rs.size () > 1);
- // The overall plan is as follows:
- //
- // 1. Collect the printed names for all the group members.
- //
- // Note if the printed representation is irregular (see
- // to_stream(target_key) for details). We will print such members each
- // on a separate line.
- //
- // 2. Move the names so that we end up with contiguous partitions of
- // targets with the same name.
- //
- // 3. Print the partitions, one per line.
- //
- vector<pair<optional<string>, const target_key*>> ns;
- ns.reserve (rs.size ());
+ static inline bool
+ print_diag_cmp (const pair<optional<string>, const target_key*>& x,
+ const pair<optional<string>, const target_key*>& y)
+ {
+ return (x.second->dir->compare (*y.second->dir) == 0 &&
+ x.first->compare (*y.first) == 0);
+ }
- // Use the diag_record's ostringstream so that we get the appropriate
- // stream verbosity, etc.
- //
- diag_record dr (text);
- ostringstream& os (dr.os);
- stream_verbosity sv (stream_verb (os));
+ // Return true if we have multiple partitions (see below for details).
+ //
+ static bool
+ print_diag_collect (const vector<target_key>& tks,
+ ostringstream& os,
+ stream_verbosity sv,
+ vector<pair<optional<string>, const target_key*>>& ns)
+ {
+ ns.reserve (tks.size ());
- for (const target_key& k: rs)
+ for (const target_key& k: tks)
{
bool r;
if (auto p = k.type->print)
@@ -130,16 +117,9 @@ namespace build2
// Partition.
//
- auto cmp = [] (const pair<optional<string>, const target_key*>& x,
- const pair<optional<string>, const target_key*>& y)
- {
- return (x.second->dir->compare (*y.second->dir) == 0 &&
- x.first->compare (*y.first) == 0);
- };
-
// While at it also determine whether we have multiple partitions.
//
- optional<string> ml;
+ bool ml (false);
for (auto b (ns.begin ()), e (ns.end ()); b != e; )
{
const pair<optional<string>, const target_key*>& x (*b++);
@@ -149,25 +129,24 @@ namespace build2
//
b = stable_partition (
b, e,
- [&cmp, &x] (const pair<optional<string>, const target_key*>& y)
+ [&x] (const pair<optional<string>, const target_key*>& y)
{
- return (x.first && y.first && cmp (x, y));
+ return (x.first && y.first && print_diag_cmp (x, y));
});
if (!ml && b != e)
- ml = string ();
+ ml = true;
}
- // Print.
- //
- os << p << ' ';
-
- if (l != nullptr)
- os << *l << (lempty ? "" : " ") << (c == nullptr ? "->" : c) << ' ';
-
- if (ml)
- ml = string (os.str ().size (), ' '); // Indentation.
+ return ml;
+ }
+ static void
+ print_diag_print (const vector<pair<optional<string>, const target_key*>>& ns,
+ ostringstream& os,
+ stream_verbosity sv,
+ const optional<string>& ml)
+ {
for (auto b (ns.begin ()), i (b), e (ns.end ()); i != e; )
{
if (i != b)
@@ -185,7 +164,7 @@ namespace build2
// Calculate the number of members in this partition.
//
size_t n (1);
- for (auto j (i + 1); j != e && j->first && cmp (*i, *j); ++j)
+ for (auto j (i + 1); j != e && j->first && print_diag_cmp (*i, *j); ++j)
++n;
// Similar code to to_stream(target_key).
@@ -232,6 +211,95 @@ namespace build2
}
}
+ template <typename L> // L can be target_key, path, or string.
+ static void
+ print_diag_impl (const char* p,
+ const L* l, bool lempty,
+ vector<target_key>&& rs,
+ const char* c)
+ {
+ assert (rs.size () > 1);
+
+ // The overall plan is as follows:
+ //
+ // 1. Collect the printed names for all the group members.
+ //
+ // Note if the printed representation is irregular (see
+ // to_stream(target_key) for details). We will print such members each
+ // on a separate line.
+ //
+ // 2. Move the names around so that we end up with contiguous partitions
+ // of targets with the same name.
+ //
+ // 3. Print the partitions, one per line.
+ //
+ // The steps 1-2 are performed by print_diag_impl_common() above.
+ //
+ vector<pair<optional<string>, const target_key*>> ns;
+
+ // Use the diag_record's ostringstream so that we get the appropriate
+ // stream verbosity, etc.
+ //
+ diag_record dr (text);
+ ostringstream& os (dr.os);
+ stream_verbosity sv (stream_verb (os));
+
+ optional<string> ml;
+ if (print_diag_collect (rs, os, sv, ns))
+ ml = string ();
+
+ // Print.
+ //
+ os << p << ' ';
+
+ if (l != nullptr)
+ os << *l << (lempty ? "" : " ") << (c == nullptr ? "->" : c) << ' ';
+
+ if (ml)
+ ml = string (os.str ().size (), ' '); // Indentation.
+
+ print_diag_print (ns, os, sv, ml);
+ }
+
+ template <typename R> // R can be target_key, path, or string.
+ static void
+ print_diag_impl (const char* p,
+ vector<target_key>&& ls, const R& r,
+ const char* c)
+ {
+ assert (ls.size () > 1);
+
+ // As above but for the group on the LHS.
+ //
+ vector<pair<optional<string>, const target_key*>> ns;
+
+ diag_record dr (text);
+ ostringstream& os (dr.os);
+ stream_verbosity sv (stream_verb (os));
+
+ optional<string> ml;
+ if (print_diag_collect (ls, os, sv, ns))
+ ml = string ();
+
+ // Print.
+ //
+ os << p << ' ';
+
+ if (ml)
+ ml = string (os.str ().size (), ' '); // Indentation.
+
+ print_diag_print (ns, os, sv, ml);
+
+ // @@ TODO: make sure `->` is aligned with longest line printed by
+ // print_diag_print(). Currently it can look like this:
+ //
+ // ln /tmp/hello-gcc/hello/hello/{hxx cxx}{hello-types}
+ // /tmp/hello-gcc/hello/hello/{hxx cxx}{hello-stubs}
+ // /tmp/hello-gcc/hello/hello/cxx{hello-ext} -> ./
+ //
+ os << ' ' << (c == nullptr ? "->" : c) << ' ' << r;
+ }
+
void
print_diag_impl (const char* p,
target_key* l, vector<target_key>&& rs,
@@ -361,7 +429,9 @@ namespace build2
}
void
- print_diag (const char* p, const target& l, const dir_path& r, const char* c)
+ print_diag (const char* p,
+ const target& l, const path_name_view& r,
+ const char* c)
{
// @@ TODO: out qualification stripping: only do if p.out is subdir of t
// (also below)?
@@ -370,11 +440,28 @@ namespace build2
}
void
+ print_diag (const char* p, const target& l, const dir_path& r, const char* c)
+ {
+ print_diag (p, l.key (), r, c);
+ }
+
+ void
+ print_diag (const char* p, target_key&& l, const dir_path& r, const char* c)
+ {
+ text << p << ' ' << l << ' ' << (c == nullptr ? "->" : c) << ' ' << r;
+ }
+
+ void
print_diag (const char* p,
- const target& l, const path_name_view& r,
+ vector<target_key>&& ls, const dir_path& r,
const char* c)
{
- text << p << ' ' << l << ' ' << (c == nullptr ? "->" : c) << ' ' << r;
+ assert (!ls.empty ());
+
+ if (ls.size () == 1)
+ print_diag (p, move (ls.front ()), r, c);
+ else
+ print_diag_impl<dir_path> (p, move (ls), r, c);
}
void
diff --git a/libbuild2/diagnostics.hxx b/libbuild2/diagnostics.hxx
index 397d3c0..c048d5b 100644
--- a/libbuild2/diagnostics.hxx
+++ b/libbuild2/diagnostics.hxx
@@ -69,6 +69,9 @@ namespace build2
// {hxx cxx}{foo-stubs} -> {hxx cxx}{foo-insts}
// {hxx cxx}{foo-impls}
//
+ // Currently we only support this for the `group -> dir_path` form (used
+ // by the backlink machinery).
+ //
// See also the `diag` Buildscript pseudo-builtin which is reduced to one of
// the print_diag() calls (adhoc_buildscript_rule::print_custom_diag()). In
// particular, if you are adding a new overload, also consider if/how it
@@ -196,6 +199,18 @@ namespace build2
const target& l, const path_name_view& r,
const char* comb = nullptr);
+ LIBBUILD2_SYMEXPORT void
+ print_diag (const char* prog,
+ target_key&& l, const dir_path& r,
+ const char* comb = nullptr);
+
+ // prog group -> dir_path
+ //
+ LIBBUILD2_SYMEXPORT void
+ print_diag (const char* prog,
+ vector<target_key>&& l, const dir_path& r,
+ const char* comb = nullptr);
+
// prog path -> path
//
void
diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx
index 34b131f..9f26eee 100644
--- a/libbuild2/target.cxx
+++ b/libbuild2/target.cxx
@@ -109,7 +109,8 @@ namespace build2
group_view target::
group_members (action) const
{
- assert (false); // Not a group or doesn't expose its members.
+ // Not a group or doesn't expose its members.
+ //
return group_view {nullptr, 0};
}