aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/cc/link-rule.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/cc/link-rule.cxx')
-rw-r--r--libbuild2/cc/link-rule.cxx1912
1 files changed, 1355 insertions, 557 deletions
diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx
index 9c0b018..08a60b9 100644
--- a/libbuild2/cc/link-rule.cxx
+++ b/libbuild2/cc/link-rule.cxx
@@ -3,11 +3,10 @@
#include <libbuild2/cc/link-rule.hxx>
-#include <map>
#include <cstdlib> // exit()
#include <cstring> // strlen()
-#include <libbutl/filesystem.mxx> // file_exists(), path_search()
+#include <libbutl/filesystem.hxx> // file_exists(), path_search()
#include <libbuild2/depdb.hxx>
#include <libbuild2/scope.hxx>
@@ -21,10 +20,11 @@
#include <libbuild2/bin/target.hxx>
#include <libbuild2/bin/utility.hxx>
+#include <libbuild2/install/utility.hxx>
+
#include <libbuild2/cc/target.hxx> // c, pc*
#include <libbuild2/cc/utility.hxx>
-using std::map;
using std::exit;
using namespace butl;
@@ -36,13 +36,228 @@ namespace build2
using namespace bin;
using build2::to_string;
+ bool link_rule::
+ deduplicate_export_libs (const scope& bs,
+ const vector<name>& ns,
+ names& r,
+ vector<reference_wrapper<const name>>* seen) const
+ {
+ bool top (seen == nullptr);
+
+ vector<reference_wrapper<const name>> seen_storage;
+ if (top)
+ seen = &seen_storage;
+
+ // The plan is as follows: resolve the target names in ns into targets
+ // and then traverse their interface dependencies recursively removing
+ // duplicates from the list r.
+ //
+ for (auto i (ns.begin ()), e (ns.end ()); i != e; ++i)
+ {
+ if (i->pair)
+ {
+ ++i;
+ continue;
+ }
+
+ const name& n (*i);
+
+ if (n.qualified () ||
+ !(n.dir.absolute () && n.dir.normalized ()) ||
+ !(n.type == "lib" || n.type == "liba" || n.type != "libs"))
+ continue;
+
+ if (!top)
+ {
+ // Check if we have already seen this library among interface
+ // dependencies of our interface dependencies.
+ //
+ if (find (seen->begin (), seen->end (), n) != seen->end ())
+ continue;
+
+ // Remove duplicates. Because we only consider absolute/normalized
+ // target names, we can just compare their names.
+ //
+ for (auto i (r.begin ()); i != r.end (); )
+ {
+ if (i->pair)
+ i += 2;
+ else if (*i == n)
+ i = r.erase (i);
+ else
+ ++i;
+ }
+
+ // @@ TODO: we could optimize this further by returning false if
+ // there are no viable candidates (e.g., only pairs/qualified/etc
+ // left).
+ //
+ if (r.empty ())
+ return false;
+ }
+
+ if (const target* t = search_existing (n, bs))
+ {
+ // The same logic as in process_libraries().
+ //
+ const scope& bs (t->base_scope ());
+
+ if (lookup l = t->lookup_original (c_export_libs, false, &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 (!deduplicate_export_libs (bs, cast<vector<name>> (l), r, seen))
+ return false;
+ }
+ }
+
+ if (!top)
+ seen->push_back (n);
+ }
+
+ return true;
+ }
+
+ optional<path> link_rule::
+ find_system_library (const strings& l) const
+ {
+ assert (!l.empty ());
+
+ // Figure out what we are looking for.
+ //
+ // See similar code in process_libraries().
+ //
+ // @@ TODO: should we take the link order into account (but do we do
+ // this when we link system libraries)?
+ //
+ string n1, n2;
+ {
+ auto i (l.begin ()), e (l.end ());
+
+ string s (*i);
+
+ if (tsys == "win32-msvc")
+ {
+ if (s[0] == '/')
+ {
+ // Some option (e.g., /WHOLEARCHIVE:<name>). Fall through to fail.
+ }
+ else
+ {
+ // Presumably a complete name.
+ //
+ n1 = move (s);
+ i++;
+ }
+ }
+ else
+ {
+ if (s[0] == '-')
+ {
+ // -l<name>, -l <name> (Note: not -pthread, which is system)
+ //
+ if (s[1] == 'l')
+ {
+ if (s.size () == 2) // -l <name>
+ {
+ if (i + 1 != e)
+ s = *++i;
+ else
+ s.clear ();
+ }
+ else // -l<name>
+ s.erase (0, 2);
+
+ if (!s.empty ())
+ {
+ i++;
+
+ // Here we need to be consistent with search_library(). Maybe
+ // one day we should generalize it to be usable here (though
+ // here we don't need library name guessing).
+ //
+ const char* p ("");
+ const char* e1 (nullptr);
+ const char* e2 (nullptr);
+
+ if (tclass == "windows")
+ {
+ if (tsys == "mingw32")
+ {
+ p = "lib";
+ e1 = ".dll.a";
+ e2 = ".a";
+ }
+ else
+ {
+ e1 = ".dll.lib";
+ e2 = ".lib";
+ }
+ }
+ else
+ {
+ p = "lib";
+ e1 = (tclass == "macos" ? ".dylib" : ".so");
+ e2 = ".a";
+ }
+
+ n1 = p + s + e1;
+ n2 = e2 != nullptr ? p + s + e2 : string ();
+ }
+ }
+#if 0
+ // -framework <name> (Mac OS)
+ //
+ else if (tsys == "darwin" && l == "-framework")
+ {
+ // @@ TODO: maybe one day.
+ }
+#endif
+ else
+ {
+ // Some other option (e.g., -Wl,--whole-archive). Fall through
+ // to fail.
+ }
+ }
+ else
+ {
+ // Presumably a complete name.
+ //
+ n1 = move (s);
+ i++;
+ }
+ }
+
+ if (i != e)
+ fail << "unexpected library name '" << *i << "'";
+ }
+
+ path p; // Reuse the buffer.
+ for (const dir_path& d: sys_lib_dirs)
+ {
+ auto exists = [&p, &d] (const string& n)
+ {
+ return file_exists ((p = d, p /= n),
+ true /* follow_symlinks */,
+ true /* ignore_errors */);
+ };
+
+ if (exists (n1) || (!n2.empty () && exists (n2)))
+ return p;
+ }
+
+ return nullopt;
+ }
+
link_rule::
link_rule (data&& d)
: common (move (d)),
- rule_id (string (x) += ".link 2")
+ rule_id (string (x) += ".link 3")
{
- static_assert (sizeof (match_data) <= target::data_size,
- "insufficient space");
}
link_rule::match_result link_rule::
@@ -67,25 +282,33 @@ namespace build2
{
// If excluded or ad hoc, then don't factor it into our tests.
//
- if (include (a, t, p) != include_type::normal)
+ // Note that here we don't validate the update operation override
+ // value (since we may not match). Instead we do this in apply().
+ //
+ lookup l;
+ if (include (a, t, p, a.operation () == update_id ? &l : nullptr) !=
+ include_type::normal)
continue;
if (p.is_a (x_src) ||
(x_mod != nullptr && p.is_a (*x_mod)) ||
+ (x_asp != nullptr && p.is_a (*x_asp)) ||
+ (x_obj != nullptr && p.is_a (*x_obj)) ||
// Header-only X library (or library with C source and X header).
(library && x_header (p, false /* c_hdr */)))
{
- r.seen_x = r.seen_x || true;
+ r.seen_x = true;
}
- else if (p.is_a<c> () ||
+ else if (p.is_a<c> () || p.is_a<S> () ||
+ (x_obj != nullptr && p.is_a<m> ()) ||
// Header-only C library.
(library && p.is_a<h> ()))
{
- r.seen_c = r.seen_c || true;
+ r.seen_c = true;
}
else if (p.is_a<obj> () || p.is_a<bmi> ())
{
- r.seen_obj = r.seen_obj || true;
+ r.seen_obj = true;
}
else if (p.is_a<obje> () || p.is_a<bmie> ())
{
@@ -94,121 +317,131 @@ namespace build2
if (ot != otype::e)
fail << p.type ().name << "{} as prerequisite of " << t;
- r.seen_obj = r.seen_obj || true;
+ r.seen_obj = true;
}
else if (p.is_a<obja> () || p.is_a<bmia> ())
{
if (ot != otype::a)
fail << p.type ().name << "{} as prerequisite of " << t;
- r.seen_obj = r.seen_obj || true;
+ r.seen_obj = true;
}
else if (p.is_a<objs> () || p.is_a<bmis> ())
{
if (ot != otype::s)
fail << p.type ().name << "{} as prerequisite of " << t;
- r.seen_obj = r.seen_obj || true;
+ r.seen_obj = true;
}
else if (p.is_a<libul> () || p.is_a<libux> ())
{
// For a unility library we look at its prerequisites, recursively.
- // Since these checks are not exactly light-weight, only do them if
- // we haven't already seen any X prerequisites.
- //
- if (!r.seen_x)
- {
- // This is a bit iffy: in our model a rule can only search a
- // target's prerequisites if it matches. But we don't yet know
- // whether we match. However, it seems correct to assume that any
- // rule-specific search will always resolve to an existing target
- // if there is one. So perhaps it's time to relax this restriction
- // a little? Note that this fits particularly well with what we
- // doing here since if there is no existing target, then there can
- // be no prerequisites.
- //
- // Note, however, that we cannot link-up a prerequisite target
- // member to its group since we are not matching this target. As
- // result we have to do all the steps except for setting t.group
- // and pass both member and group (we also cannot query t.group
- // since it's racy).
- //
- const target* pg (nullptr);
- const target* pt (p.search_existing ());
-
- if (p.is_a<libul> ())
+ //
+ // This is a bit iffy: in our model a rule can only search a
+ // target's prerequisites if it matches. But we don't yet know
+ // whether we match. However, it seems correct to assume that any
+ // rule-specific search will always resolve to an existing target if
+ // there is one. So perhaps it's time to relax this restriction a
+ // little? Note that this fits particularly well with what we are
+ // doing here since if there is no existing target, then there can
+ // be no prerequisites.
+ //
+ // Note, however, that we cannot link-up a prerequisite target
+ // member to its group since we are not matching this target. As
+ // result we have to do all the steps except for setting t.group and
+ // pass both member and group (we also cannot query t.group since
+ // it's racy).
+ //
+ const target* pg (nullptr);
+ const target* pt (p.search_existing ());
+
+ auto search = [&t, &p] (const target_type& tt)
+ {
+ return search_existing (t.ctx, p.prerequisite.key (tt));
+ };
+
+ if (p.is_a<libul> ())
+ {
+ if (pt != nullptr)
{
- if (pt != nullptr)
+ // If this is a group then try to pick (again, if exists) a
+ // suitable member. If it doesn't exist, then we will only be
+ // considering the group's prerequisites.
+ //
+ if (const target* pm =
+ link_member (pt->as<libul> (),
+ a,
+ linfo {ot, lorder::a /* unused */},
+ true /* existing */))
{
- // If this is a group then try to pick (again, if exists) a
- // suitable member. If it doesn't exist, then we will only be
- // considering the group's prerequisites.
- //
- if (const target* pm =
- link_member (pt->as<libul> (),
- a,
- linfo {ot, lorder::a /* unused */},
- true /* existing */))
- {
- pg = pt;
- pt = pm;
- }
+ pg = pt;
+ pt = pm;
}
- else
+ }
+ else
+ {
+ // It's possible we have no group but have a member so try that.
+ //
+ if (ot != otype::e)
{
- // It's possible we have no group but have a member so try
- // that.
- //
- const target_type& tt (ot == otype::a ? libua::static_type :
- ot == otype::s ? libus::static_type :
- libue::static_type);
-
// We know this prerequisite member is a prerequisite since
// otherwise the above search would have returned the member
// target.
//
- pt = search_existing (t.ctx, p.prerequisite.key (tt));
+ pt = search (ot == otype::a
+ ? libua::static_type
+ : libus::static_type);
}
- }
- else if (!p.is_a<libue> ())
- {
- // See if we also/instead have a group.
- //
- pg = search_existing (t.ctx,
- p.prerequisite.key (libul::static_type));
+ else
+ {
+ // Similar semantics to bin::link_member(): prefer static over
+ // shared.
+ //
+ pt = search (libua::static_type);
- if (pt == nullptr)
- swap (pt, pg);
+ if (pt == nullptr)
+ pt = search (libus::static_type);
+ }
}
+ }
+ else if (!p.is_a<libue> ())
+ {
+ // See if we also/instead have a group.
+ //
+ pg = search (libul::static_type);
- if (pt != nullptr)
- {
- // If we are matching a target, use the original output type
- // since that would be the member that we pick.
- //
- otype pot (pt->is_a<libul> () ? ot : link_type (*pt).type);
- match_result pr (match (a, *pt, pg, pot, true /* lib */));
+ if (pt == nullptr)
+ swap (pt, pg);
+ }
- // Do we need to propagate any other seen_* values? Hm, that
- // would in fact match with the "see-through" semantics of
- // utility libraries we have in other places.
- //
- r.seen_x = pr.seen_x;
- }
- else
- r.seen_lib = r.seen_lib || true; // Consider as just a library.
+ if (pt != nullptr)
+ {
+ // If we are matching a target, use the original output type since
+ // that would be the member that we pick.
+ //
+ otype pot (pt->is_a<libul> () ? ot : link_type (*pt).type);
+
+ // Propagate values according to the "see-through" semantics of
+ // utility libraries.
+ //
+ r |= match (a, *pt, pg, pot, true /* lib */);
}
+ else
+ r.seen_lib = true; // Consider as just a library.
}
else if (p.is_a<lib> () ||
p.is_a<liba> () ||
p.is_a<libs> ())
{
- r.seen_lib = r.seen_lib || true;
+ r.seen_lib = true;
}
// Some other c-common header/source (say C++ in a C rule) other than
- // a C header (we assume everyone can hanle that).
+ // a C header (we assume everyone can hanle that) or some other
+ // #include'able target.
//
- else if (p.is_a<cc> () && !(x_header (p, true /* c_hdr */)))
+ else if (p.is_a<cc> () &&
+ !(x_header (p, true /* c_hdr */)) &&
+ !p.is_a (x_inc) && !p.is_a<c_inc> ())
{
r.seen_cc = true;
break;
@@ -219,7 +452,7 @@ namespace build2
}
bool link_rule::
- match (action a, target& t, const string& hint) const
+ match (action a, target& t, const string& hint, match_extra&) const
{
// NOTE: may be called multiple times and for both inner and outer
// operations (see the install rules).
@@ -258,17 +491,22 @@ namespace build2
return false;
}
- if (!(r.seen_x || r.seen_c || r.seen_obj || r.seen_lib))
+ // Sometimes we may need to have a binless library whose only purpose is
+ // to export dependencies on other libraries (potentially in a platform-
+ // specific manner; think the whole -pthread mess). So allow a library
+ // without any sources with a hint.
+ //
+ if (!(r.seen_x || r.seen_c || r.seen_obj || r.seen_lib || !hint.empty ()))
{
- l4 ([&]{trace << "no " << x_lang << ", C, or obj/lib prerequisite "
- << "for target " << t;});
+ l4 ([&]{trace << "no " << x_lang << ", C, obj/lib prerequisite or "
+ << "hint for target " << t;});
return false;
}
// We will only chain a C source if there is also an X source or we were
// explicitly told to.
//
- if (r.seen_c && !r.seen_x && hint < x)
+ if (r.seen_c && !r.seen_x && hint.empty ())
{
l4 ([&]{trace << "C prerequisite without " << x_lang << " or hint "
<< "for target " << t;});
@@ -330,7 +568,7 @@ namespace build2
//
string ver;
bool verp (true); // Platform-specific.
- using verion_map = map<string, string>;
+ using verion_map = map<optional<string>, string>;
if (const verion_map* m = cast_null<verion_map> (t["bin.lib.version"]))
{
// First look for the target system.
@@ -347,14 +585,20 @@ namespace build2
// say "all others -- no version".
//
if (i == m->end ())
- i = m->find ("*");
+ i = m->find (string ("*"));
// Finally look for the platform-independent version.
//
if (i == m->end ())
{
verp = false;
- i = m->find ("");
+
+ i = m->find (nullopt);
+
+ // For backwards-compatibility.
+ //
+ if (i == m->end ())
+ i = m->find (string ());
}
// If we didn't find anything, fail. If the bin.lib.version was
@@ -600,6 +844,15 @@ namespace build2
//
if (const libul* ul = pt->is_a<libul> ())
{
+ // @@ Isn't libul{} member already picked or am I missing something?
+ // If not, then we may need the same in recursive-binless logic.
+ //
+#if 0
+ // @@ TMP hm, this hasn't actually been enabled. So may actually
+ // enable and see if it trips up (do git-blame for good measure).
+ //
+ assert (false); // @@ TMP (remove before 0.16.0 release)
+#endif
ux = &link_member (*ul, a, li)->as<libux> ();
}
else if ((ux = pt->is_a<libue> ()) ||
@@ -616,8 +869,20 @@ namespace build2
return nullptr;
};
+ // Given the cc.type value return true if the library is recursively
+ // binless.
+ //
+ static inline bool
+ recursively_binless (const string& type)
+ {
+ size_t p (type.find ("recursively-binless"));
+ return (p != string::npos &&
+ type[p - 1] == ',' && // <lang> is first.
+ (type[p += 19] == '\0' || type[p] == ','));
+ }
+
recipe link_rule::
- apply (action a, target& xt) const
+ apply (action a, target& xt, match_extra&) const
{
tracer trace (x, "link_rule::apply");
@@ -627,7 +892,11 @@ namespace build2
// Note that for_install is signalled by install_rule and therefore
// can only be relied upon during execute.
//
- match_data& md (t.data (match_data ()));
+ // Note that we don't really need to set it as target data: while there
+ // are calls to get it, they should only happen after the target has
+ // been matched.
+ //
+ match_data md (*this);
const scope& bs (t.base_scope ());
const scope& rs (*bs.root_scope ());
@@ -636,11 +905,6 @@ namespace build2
otype ot (lt.type);
linfo li (link_info (bs, ot));
- // Set the library type (C, C++, etc) as rule-specific variable.
- //
- if (lt.library ())
- t.state[a].assign (c_type) = string (x);
-
bool binless (lt.library ()); // Binary-less until proven otherwise.
bool user_binless (lt.library () && cast_false<bool> (t[b_binless]));
@@ -648,7 +912,7 @@ namespace build2
// for binless libraries since there could be other output (e.g., .pc
// files).
//
- inject_fsdir (a, t);
+ 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
@@ -662,7 +926,7 @@ namespace build2
// We do libraries first in order to indicate that we will execute these
// targets before matching any of the obj/bmi{}. This makes it safe for
// compile::apply() to unmatch them and therefore not to hinder
- // parallelism.
+ // parallelism (or mess up for-install'ness).
//
// We also create obj/bmi{} chain targets because we need to add
// (similar to lib{}) all the bmi{} as prerequisites to all the other
@@ -686,33 +950,98 @@ namespace build2
return a.operation () == clean_id && !pt.dir.sub (rs.out_path ());
};
+ bool update_match (false); // Have update during match.
+
auto& pts (t.prerequisite_targets[a]);
size_t start (pts.size ());
for (prerequisite_member p: group_prerequisite_members (a, t))
{
- include_type pi (include (a, t, p));
+ // Note that we have to recognize update=match for *(update), not just
+ // perform(update). But only actually update for perform(update).
+ //
+ lookup l; // The `update` variable value, if any.
+ include_type pi (
+ include (a, t, p, a.operation () == update_id ? &l : nullptr));
// We pre-allocate a NULL slot for each (potential; see clean)
// prerequisite target.
//
pts.push_back (prerequisite_target (nullptr, pi));
- const target*& pt (pts.back ());
+ auto& pto (pts.back ());
+
+ // Use bit 2 of prerequisite_target::include to signal update during
+ // match.
+ //
+ // Not that for now we only allow updating during match ad hoc and
+ // mark 3 (headers, etc; see below) prerequisites.
+ //
+ // By default we update during match headers and ad hoc sources (which
+ // are commonly marked as such because they are #include'ed).
+ //
+ optional<bool> um;
+
+ if (l)
+ {
+ const string& v (cast<string> (l));
+
+ if (v == "match")
+ um = true;
+ else if (v == "execute")
+ um = false;
+ else if (v != "false" && v != "true")
+ {
+ fail << "unrecognized update variable value '" << v
+ << "' specified for prerequisite " << p.prerequisite;
+ }
+ }
+
+ // Skip excluded and ad hoc (unless updated during match) on this
+ // pass.
+ //
+ if (pi != include_type::normal)
+ {
+ if (a == perform_update_id && pi == include_type::adhoc)
+ {
+ // By default update ad hoc headers/sources during match (see
+ // above).
+ //
+#if 1
+ if (!um)
+ um = (p.is_a (x_src) || p.is_a<c> () || p.is_a<S> () ||
+ (x_mod != nullptr && p.is_a (*x_mod)) ||
+ (x_obj != nullptr && (p.is_a (*x_obj) || p.is_a<m> ())) ||
+ x_header (p, true));
+#endif
+
+ if (*um)
+ {
+ pto.target = &p.search (t); // mark 0
+ pto.include |= prerequisite_target::include_udm;
+ update_match = true;
+ }
+ }
- if (pi != include_type::normal) // Skip excluded and ad hoc.
continue;
+ }
+
+ const target*& pt (pto);
- // Mark:
- // 0 - lib
+ // Mark (2 bits):
+ //
+ // 0 - lib or update during match
// 1 - src
// 2 - mod
- // 3 - obj/bmi and also lib not to be cleaned
+ // 3 - obj/bmi and also lib not to be cleaned (and other stuff)
//
- uint8_t m (0);
+ uint8_t mk (0);
bool mod (x_mod != nullptr && p.is_a (*x_mod));
+ bool hdr (false);
- if (mod || p.is_a (x_src) || p.is_a<c> ())
+ if (mod ||
+ p.is_a (x_src) || p.is_a<c> () || p.is_a<S> () ||
+ (x_obj != nullptr && (p.is_a (*x_obj) || p.is_a<m> ())))
{
binless = binless && (mod ? user_binless : false);
@@ -763,8 +1092,8 @@ namespace build2
// be the group -- we will pick a member in part 2 below.
//
pair<target&, ulock> r (
- search_locked (
- t, rtt, d, dir_path (), *cp.tk.name, nullptr, cp.scope));
+ 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
@@ -800,7 +1129,7 @@ namespace build2
}
pt = &r.first;
- m = mod ? 2 : 1;
+ mk = mod ? 2 : 1;
}
else if (p.is_a<libx> () ||
p.is_a<liba> () ||
@@ -809,12 +1138,8 @@ namespace build2
{
// Handle imported libraries.
//
- // Note that since the search is rule-specific, we don't cache the
- // target in the prerequisite.
- //
if (p.proj ())
- pt = search_library (
- a, sys_lib_dirs, usr_lib_dirs, p.prerequisite);
+ pt = search_library (a, sys_lib_dirs, usr_lib_dirs, p.prerequisite);
// The rest is the same basic logic as in search_and_match().
//
@@ -822,13 +1147,17 @@ namespace build2
pt = &p.search (t);
if (skip (*pt))
- m = 3; // Mark so it is not matched.
+ mk = 3; // Mark so it is not matched.
// If this is the lib{}/libul{} group, then pick the appropriate
- // member.
+ // member. Also note this in prerequisite_target::include (used
+ // by process_libraries()).
//
if (const libx* l = pt->is_a<libx> ())
+ {
pt = link_member (*l, a, li);
+ pto.include |= include_group;
+ }
}
else
{
@@ -841,8 +1170,11 @@ namespace build2
// Windows module definition (.def). For other platforms (and for
// static libraries) treat it as an ordinary prerequisite.
//
- else if (p.is_a<def> () && tclass == "windows" && ot != otype::a)
+ else if (p.is_a<def> ())
{
+ if (tclass != "windows" || ot == otype::a)
+ continue;
+
pt = &p.search (t);
}
//
@@ -852,11 +1184,14 @@ namespace build2
//
else
{
- if (!p.is_a<objx> () && !p.is_a<bmix> ())
+ if (!p.is_a<objx> () &&
+ !p.is_a<bmix> () &&
+ !(hdr = x_header (p, true)))
{
// @@ Temporary hack until we get the default outer operation
// for update. This allows operations like test and install to
- // skip such tacked on stuff.
+ // skip such tacked on stuff. @@ This doesn't feel temporary
+ // anymore...
//
// Note that ad hoc inputs have to be explicitly marked with the
// include=adhoc prerequisite-specific variable.
@@ -866,6 +1201,12 @@ namespace build2
}
pt = &p.search (t);
+
+ if (pt == dir)
+ {
+ pt = nullptr;
+ continue;
+ }
}
if (skip (*pt))
@@ -884,21 +1225,58 @@ namespace build2
!pt->is_a<hbmix> () &&
cast_false<bool> ((*pt)[b_binless])));
- m = 3;
+ mk = 3;
}
if (user_binless && !binless)
fail << t << " cannot be binless due to " << p << " prerequisite";
- mark (pt, m);
+ // Upgrade update during match prerequisites to mark 0 (see above for
+ // details).
+ //
+ if (a == perform_update_id)
+ {
+ // By default update headers during match (see above).
+ //
+#if 1
+ if (!um)
+ um = hdr;
+#endif
+
+ if (*um)
+ {
+ if (mk != 3)
+ fail << "unable to update during match prerequisite " << p <<
+ info << "updating this type of prerequisites during match is "
+ << "not supported by this rule";
+
+ mk = 0;
+ pto.include |= prerequisite_target::include_udm;
+ update_match = true;
+ }
+ }
+
+ mark (pt, mk);
}
- // Match lib{} (the only unmarked) in parallel and wait for completion.
+ // Match lib{} first and then update during match (the only unmarked) in
+ // parallel and wait for completion. We need to match libraries first
+ // because matching generated headers/sources may lead to matching some
+ // of the libraries (for example, if generation requires some of the
+ // metadata; think poptions needed by Qt moc).
//
- match_members (a, t, pts, start);
+ {
+ auto mask (prerequisite_target::include_udm);
+
+ match_members (a, t, pts, start, {mask, 0});
+
+ if (update_match)
+ match_members (a, t, pts, start, {mask, mask});
+ }
// Check if we have any binful utility libraries.
//
+ bool rec_binless (false); // Recursively-binless.
if (binless)
{
if (const libux* l = find_binful (a, t, li))
@@ -909,8 +1287,128 @@ namespace build2
fail << t << " cannot be binless due to binful " << *l
<< " prerequisite";
}
+
+ // See if we are recursively-binless.
+ //
+ if (binless)
+ {
+ rec_binless = true;
+
+ for (const target* pt: t.prerequisite_targets[a])
+ {
+ if (pt == nullptr || unmark (pt) != 0) // See above.
+ continue;
+
+ const file* ft;
+ if ((ft = pt->is_a<libs> ()) ||
+ (ft = pt->is_a<liba> ()) ||
+ (ft = pt->is_a<libux> ()))
+ {
+ if (ft->path ().empty ()) // Binless.
+ {
+ // The same lookup as in process_libraries().
+ //
+ if (const string* t = cast_null<string> (
+ ft->state[a].lookup_original (
+ c_type, true /* target_only */).first))
+ {
+ if (recursively_binless (*t))
+ continue;
+ }
+ }
+
+ rec_binless = false;
+ break;
+ }
+ }
+
+ // Another thing we must check is for the presence of any simple
+ // libraries (-lm, shell32.lib, etc) in *.export.libs. See
+ // process_libraries() for details.
+ //
+ if (rec_binless)
+ {
+ auto find = [&t, &bs] (const variable& v) -> lookup
+ {
+ return t.lookup_original (v, false, &bs).first;
+ };
+
+ auto has_simple = [] (lookup l)
+ {
+ if (const auto* ns = cast_null<vector<name>> (l))
+ {
+ for (auto i (ns->begin ()), e (ns->end ()); i != e; ++i)
+ {
+ if (i->pair)
+ ++i;
+ else if (i->simple ()) // -l<name>, etc.
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ if (lt.shared_library ()) // process_libraries()::impl == false
+ {
+ if (has_simple (find (x_export_libs)) ||
+ has_simple (find (c_export_libs)))
+ rec_binless = false;
+ }
+ else // process_libraries()::impl == true
+ {
+ lookup x (find (x_export_impl_libs));
+ lookup c (find (c_export_impl_libs));
+
+ if (x.defined () || c.defined ())
+ {
+ if (has_simple (x) || has_simple (c))
+ rec_binless = false;
+ }
+ else
+ {
+ // These are strings and we assume if either is defined and
+ // not empty, then we have simple libraries.
+ //
+ if (((x = find (x_libs)) && !x->empty ()) ||
+ ((c = find (c_libs)) && !c->empty ()))
+ rec_binless = false;
+ }
+ }
+ }
+ }
}
+ // Set the library type (C, C++, binless) as rule-specific variable.
+ //
+ if (lt.library ())
+ {
+ string v (x);
+
+ if (rec_binless)
+ v += ",recursively-binless";
+ else if (binless)
+ v += ",binless";
+
+ t.state[a].assign (c_type) = move (v);
+ }
+
+ // If we have any update during match prerequisites, now is the time to
+ // update them. Note that we have to do it before any further matches
+ // since they may rely on these prerequisites already being updated (for
+ // example, object file matches may need the headers to be already
+ // updated). We also must do it after matching all our prerequisite
+ // libraries since they may generate headers that we depend upon.
+ //
+ // Note that we ignore the result and whether it renders us out of date,
+ // leaving it to the common execute logic in perform_update().
+ //
+ // Note also that update_during_match_prerequisites() spoils
+ // prerequisite_target::data.
+ //
+ if (update_match)
+ update_during_match_prerequisites (trace, a, t);
+
// Now that we know for sure whether we are binless, derive file name(s)
// and add ad hoc group members. Note that for binless we still need the
// .pc member (whose name depends on the libray prefix) so we take care
@@ -1053,6 +1551,41 @@ 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
+ // next to the main .js file.
+ //
+ // @@ Note that our recommendation is to pass -pthread in *.libs
+ // but checking that is not straightforward (it could come from
+ // one of the libraries that we are linking). We could have called
+ // append_libraries() (similar to $x.lib_libs()) and then looked
+ // there. But this is quite heavy handed and it's not clear this
+ // is worth the trouble since the -pthread support in Emscripten
+ // is quite high-touch (i.e., it's not like we can write a library
+ // that starts some threads and then run its test as on any other
+ // POSIX platform).
+ //
+ if (find_option ("-pthread", cmode) ||
+ find_option ("-pthread", t, c_loptions) ||
+ find_option ("-pthread", t, x_loptions))
+ {
+ exe& worker (add_adhoc_member<exe> (t, "worker.js"));
+
+ 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")};
+ }
}
// Add VC's .pdb. Note that we are looking for the link.exe /DEBUG
@@ -1060,22 +1593,31 @@ namespace build2
//
if (!binless && ot != otype::a && tsys == "win32-msvc")
{
- if (find_option ("/DEBUG", t, c_loptions, true) ||
- find_option ("/DEBUG", t, x_loptions, true))
+ const string* o;
+ if ((o = find_option_prefix ("/DEBUG", t, c_loptions, true)) != nullptr ||
+ (o = find_option_prefix ("/DEBUG", t, x_loptions, true)) != nullptr)
{
- const target_type& tt (*bs.find_target_type ("pdb"));
+ if (icasecmp (*o, "/DEBUG:NONE") != 0)
+ {
+ const target_type& tt (*bs.find_target_type ("pdb"));
- // We call the target foo.{exe,dll}.pdb rather than just foo.pdb
- // because we can have both foo.exe and foo.dll in the same
- // directory.
- //
- file& pdb (add_adhoc_member<file> (t, tt, e));
+ // We call the target foo.{exe,dll}.pdb rather than just
+ // foo.pdb because we can have both foo.exe and foo.dll in the
+ // same directory.
+ //
+ file& pdb (add_adhoc_member<file> (t, tt, e));
- // Note that the path is derived from the exe/dll path (so it
- // will include the version in case of a dll).
- //
- if (pdb.path ().empty ())
- pdb.derive_path (t.path ());
+ // Note that the path is derived from the exe/dll path (so it
+ // will include the version in case of a dll).
+ //
+ 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")};
+ }
}
}
@@ -1097,6 +1639,13 @@ namespace build2
// we will use its bin.lib to decide what will be installed and in
// perform_update() we will confirm that it is actually installed.
//
+ // This, of course, works only if we actually have explicit lib{}.
+ // But the user could only have liba{} (common in testing frameworks
+ // that provide main()) or only libs{} (e.g., plugin that can also
+ // be linked). It's also theoretically possible to have both liba{}
+ // and libs{} but no lib{}, in which case it feels correct not to
+ // generate the common file at all.
+ //
if (ot != otype::e)
{
// Note that here we always use the lib name prefix, even on
@@ -1108,7 +1657,13 @@ namespace build2
// Note also that the order in which we are adding these members
// is important (see add_addhoc_member() for details).
//
- if (ot == otype::a || !link_members (rs).a)
+ if (operator>= (t.group->decl, target_decl::implied) // @@ VC14
+ ? ot == (link_members (rs).a ? otype::a : otype::s)
+ : search_existing (ctx,
+ ot == otype::a
+ ? libs::static_type
+ : liba::static_type,
+ t.dir, t.out, t.name) == nullptr)
{
auto& pc (add_adhoc_member<pc> (t));
@@ -1141,14 +1696,13 @@ 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 */,
+ nullopt /* ext */));
// By default our backlinking logic will try to symlink the
// directory and it can even be done on Windows using junctions.
@@ -1162,9 +1716,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
+ };
}
}
}
@@ -1186,23 +1746,24 @@ namespace build2
continue;
// New mark:
+ // 0 - already matched
// 1 - completion
// 2 - verification
//
- uint8_t m (unmark (pt));
+ uint8_t mk (unmark (pt));
- if (m == 3) // obj/bmi or lib not to be cleaned
+ if (mk == 3) // obj/bmi or lib not to be cleaned
{
- m = 1; // Just completion.
+ mk = 1; // Just completion.
// Note that if this is a library not to be cleaned, we keep it
// marked for completion (see the next phase).
}
- else if (m == 1 || m == 2) // Source/module chain.
+ else if (mk == 1 || mk == 2) // Source/module chain.
{
- bool mod (m == 2);
+ bool mod (mk == 2); // p is_a x_mod
- m = 1;
+ mk = 1;
const target& rt (*pt);
bool group (!p.prerequisite.belongs (t)); // Group's prerequisite.
@@ -1234,7 +1795,21 @@ namespace build2
if (!pt->has_prerequisites () &&
(!group || !rt.has_prerequisites ()))
{
- prerequisites ps {p.as_prerequisite ()}; // Source.
+ prerequisites ps;
+
+ // Add source.
+ //
+ // Remove the update variable (we may have stray update=execute
+ // that was specified together with the header).
+ //
+ {
+ prerequisite pc (p.as_prerequisite ());
+
+ if (!pc.vars.empty ())
+ pc.vars.erase (*ctx.var_update);
+
+ ps.push_back (move (pc));
+ }
// Add our lib*{} (see the export.* machinery for details) and
// bmi*{} (both original and chained; see module search logic)
@@ -1253,7 +1828,7 @@ namespace build2
// might depend on the imported one(s) which we will never "see"
// unless we start with this library.
//
- // Note: have similar logic in make_module_sidebuild().
+ // Note: have similar logic in make_{module,header}_sidebuild().
//
size_t j (start);
for (prerequisite_member p: group_prerequisite_members (a, t))
@@ -1339,7 +1914,10 @@ namespace build2
// Most of the time we will have just a single source so fast-
// path that case.
//
- if (p1.is_a (mod ? *x_mod : x_src) || p1.is_a<c> ())
+ if (mod
+ ? p1.is_a (*x_mod)
+ : (p1.is_a (x_src) || p1.is_a<c> () || p1.is_a<S> () ||
+ (x_obj != nullptr && (p1.is_a (*x_obj) || p1.is_a<m> ()))))
{
src = true;
continue; // Check the rest of the prerequisites.
@@ -1352,8 +1930,12 @@ namespace build2
p1.is_a<libx> () ||
p1.is_a<liba> () || p1.is_a<libs> () || p1.is_a<libux> () ||
p1.is_a<bmi> () || p1.is_a<bmix> () ||
- (p.is_a (mod ? *x_mod : x_src) && x_header (p1)) ||
- (p.is_a<c> () && p1.is_a<h> ()))
+ ((mod ||
+ p.is_a (x_src) ||
+ (x_asp != nullptr && p.is_a (*x_asp)) ||
+ (x_obj != nullptr && p.is_a (*x_obj))) && x_header (p1)) ||
+ ((p.is_a<c> () || p.is_a<S> () ||
+ (x_obj != nullptr && p.is_a<m> ())) && p1.is_a<h> ()))
continue;
fail << "synthesized dependency for prerequisite " << p
@@ -1366,14 +1948,14 @@ namespace build2
if (!src)
fail << "synthesized dependency for prerequisite " << p
<< " would be incompatible with existing target " << *pt <<
- info << "no existing c/" << x_name << " source prerequisite" <<
+ info << "no existing C/" << x_lang << " source prerequisite" <<
info << "specify corresponding " << rtt.name << "{} "
<< "dependency explicitly";
- m = 2; // Needs verification.
+ mk = 2; // Needs verification.
}
}
- else // lib*{}
+ else // lib*{} or update during match
{
// If this is a static library, see if we need to link it whole.
// Note that we have to do it after match since we rely on the
@@ -1382,6 +1964,8 @@ namespace build2
bool u;
if ((u = pt->is_a<libux> ()) || pt->is_a<liba> ())
{
+ // Note: go straight for the public variable pool.
+ //
const variable& var (ctx.var_pool["bin.whole"]); // @@ Cache.
// See the bin module for the lookup semantics discussion. Note
@@ -1391,16 +1975,18 @@ namespace build2
lookup l (p.prerequisite.vars[var]);
if (!l.defined ())
- l = pt->lookup_original (var, true).first;
+ l = pt->lookup_original (var, true /* target_only */).first;
if (!l.defined ())
{
- bool g (pt->group != nullptr);
+ const target* g (pt->group);
+
+ target_key tk (pt->key ());
+ target_key gk (g != nullptr ? g->key () : target_key {});
+
l = bs.lookup_original (var,
- &pt->type (),
- &pt->name,
- (g ? &pt->group->type () : nullptr),
- (g ? &pt->group->name : nullptr)).first;
+ &tk,
+ g != nullptr ? &gk : nullptr).first;
}
if (l ? cast<bool> (*l) : u)
@@ -1408,7 +1994,7 @@ namespace build2
}
}
- mark (pt, m);
+ mark (pt, mk);
}
// Process prerequisites, pass 3: match everything and verify chains.
@@ -1421,10 +2007,10 @@ namespace build2
i = start;
for (prerequisite_member p: group_prerequisite_members (a, t))
{
- bool adhoc (pts[i].adhoc);
+ bool adhoc (pts[i].adhoc ());
const target*& pt (pts[i++]);
- uint8_t m;
+ uint8_t mk;
if (pt == nullptr)
{
@@ -1434,10 +2020,15 @@ namespace build2
continue;
pt = &p.search (t);
- m = 1; // Mark for completion.
+ mk = 1; // Mark for completion.
}
- else if ((m = unmark (pt)) != 0)
+ else
{
+ mk = unmark (pt);
+
+ if (mk == 0)
+ continue; // Already matched.
+
// If this is a library not to be cleaned, we can finally blank it
// out.
//
@@ -1449,7 +2040,7 @@ namespace build2
}
match_async (a, *pt, ctx.count_busy (), t[a].task_count);
- mark (pt, m);
+ mark (pt, mk);
}
wg.wait ();
@@ -1464,15 +2055,15 @@ namespace build2
// Skipped or not marked for completion.
//
- uint8_t m;
- if (pt == nullptr || (m = unmark (pt)) == 0)
+ uint8_t mk;
+ if (pt == nullptr || (mk = unmark (pt)) == 0)
continue;
- build2::match (a, *pt);
+ match_complete (a, *pt);
// Nothing else to do if not marked for verification.
//
- if (m == 1)
+ if (mk == 1)
continue;
// Finish verifying the existing dependency (which is now matched)
@@ -1484,7 +2075,10 @@ namespace build2
for (prerequisite_member p1: group_prerequisite_members (a, *pt))
{
- if (p1.is_a (mod ? *x_mod : x_src) || p1.is_a<c> ())
+ if (mod
+ ? p1.is_a (*x_mod)
+ : (p1.is_a (x_src) || p1.is_a<c> () || p1.is_a<S> () ||
+ (x_obj != nullptr && (p1.is_a (*x_obj) || p1.is_a<m> ()))))
{
// Searching our own prerequisite is ok, p1 must already be
// resolved.
@@ -1520,46 +2114,63 @@ namespace build2
switch (a)
{
- case perform_update_id: return [this] (action a, const target& t)
- {
- return perform_update (a, t);
- };
- case perform_clean_id: return [this] (action a, const target& t)
- {
- return perform_clean (a, t);
- };
+ // Keep the recipe (which is match_data) after execution to allow the
+ // install rule to examine it.
+ //
+ case perform_update_id: t.keep_data (a); // Fall through.
+ case perform_clean_id: return md;
default: return noop_recipe; // Configure update.
}
}
+ // Append (and optionally hash and detect if rendered out of data)
+ // libraries to link, recursively.
+ //
void link_rule::
append_libraries (appended_libraries& ls, strings& args,
+ sha256* cs, bool* update, timestamp mt,
const scope& bs, action a,
const file& l, bool la, lflags lf, linfo li,
- bool self, bool rel) const
+ optional<bool> for_install, bool self, bool rel,
+ library_cache* lib_cache) const
{
struct data
{
appended_libraries& ls;
strings& args;
+
+ sha256* cs;
+ const dir_path* out_root;
+
+ bool* update;
+ timestamp mt;
+
const file& l;
action a;
linfo li;
+ optional<bool> for_install;
bool rel;
compile_target_types tts;
- } d {ls, args, l, a, li, rel, compile_types (li.type)};
+ } d {ls, args,
+ cs, cs != nullptr ? &bs.root_scope ()->out_path () : nullptr,
+ update, mt,
+ l, a, li, for_install, rel, compile_types (li.type)};
- auto imp = [] (const file&, bool la)
+ auto imp = [] (const target&, bool la)
{
return la;
};
- auto lib = [&d, this] (const file* const* lc,
- const string& p,
- lflags f,
- bool)
+ auto lib = [&d, this] (
+ const target* const* lc,
+ const small_vector<reference_wrapper<const string>, 2>& ns,
+ lflags f,
+ const string* type, // Whole cc.type in the <lang>[,...] form.
+ bool)
{
- const file* l (lc != nullptr ? *lc : nullptr);
+ // Note: see also make_header_sidebuild().
+
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
// Suppress duplicates.
//
@@ -1575,45 +2186,33 @@ namespace build2
// that range of elements to the end of args. See GitHub issue #114
// for details.
//
+ // One case where we can prune the graph is if the library is
+ // recursively-binless. It's tempting to wish that we can do the same
+ // just for binless, but alas that's not the case: we have to hoist
+ // its binful interface dependency because, for example, it must
+ // appear after the preceding static library of which this binless
+ // library is a dependency.
+ //
// 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 (p, d.args.size ()));
+ appended_library* al (l != nullptr
+ ? &d.ls.append (*l, d.args.size ())
+ : d.ls.append (ns, d.args.size ()));
- if (al.end != appended_library::npos) // Closed.
+ if (al != nullptr && al->end != appended_library::npos) // Closed.
{
// Hoist the elements corresponding to this library to the end.
+ // Note that we cannot prune the traversal since we need to see the
+ // last occurrence of each library, unless the library is
+ // recursively-binless (in which case there will be no need to
+ // hoist since there can be no libraries among the elements).
//
- if (al.begin != al.end)
- {
- // Rotate to the left the subrange starting from the first element
- // of this library and until the end so that the element after the
- // last element of this library becomes the first element of this
- // subrange. We also need to adjust begin/end of libraries
- // affected by the rotation.
- //
- rotate (d.args.begin () + al.begin,
- d.args.begin () + al.end,
- d.args.end ());
-
- size_t n (al.end - al.begin);
+ if (type != nullptr && recursively_binless (*type))
+ return false;
- for (appended_library& al1: d.ls)
- {
- if (al1.begin >= al.end)
- {
- al1.begin -= n;
- al1.end -= n;
- }
- }
-
- al.end = d.args.size ();
- al.begin = al.end - n;
- }
-
- return;
+ d.ls.hoist (d.args, *al);
+ return true;
}
if (l == nullptr)
@@ -1622,7 +2221,15 @@ namespace build2
// static library.
//
if (d.li.type != otype::a)
- d.args.push_back (p);
+ {
+ for (const string& n: ns)
+ {
+ d.args.push_back (n);
+
+ if (d.cs != nullptr)
+ d.cs->append (n);
+ }
+ }
}
else
{
@@ -1645,6 +2252,55 @@ namespace build2
if (!lc[i]->is_a<libux> ())
goto done;
}
+ // If requested, verify the target and the library are both for
+ // install or both not. We can only do this if the library is build
+ // by our link_rule.
+ //
+ else if (d.for_install &&
+ type != nullptr &&
+ *type != "cc" &&
+ type->compare (0, 3, "cc,") != 0)
+ {
+ auto* md (l->try_data<link_rule::match_data> (d.a));
+
+ if (md == nullptr)
+ fail << "library " << *l << " is not built with cc module-based "
+ << "link rule" <<
+ info << "mark it as generic with cc.type=cc target-specific "
+ << "variable";
+
+ assert (md->for_install); // Must have been executed.
+
+ // The user will get the target name from the context info.
+ //
+ if (*md->for_install != *d.for_install)
+ fail << "incompatible " << *l << " build" <<
+ info << "library is built " << (*md->for_install ? "" : "not ")
+ << "for install";
+ }
+
+ auto newer = [&d, l] ()
+ {
+ // @@ Work around the unexecuted member for installed libraries
+ // issue (see search_library() for details).
+ //
+ // Note that the member may not even be matched, let alone
+ // executed, so we have to go through the group to detect this
+ // case (if the group is not matched, then the member got to be).
+ //
+#if 0
+ return l->newer (d.mt);
+#else
+ const target* g (l->group);
+ target_state s (g != nullptr &&
+ g->matched (d.a, memory_order_acquire) &&
+ g->state[d.a].rule == &file_rule::rule_match
+ ? target_state::unchanged
+ : l->executed_state (d.a));
+
+ return l->newer (d.mt, s);
+#endif
+ };
if (d.li.type == otype::a)
{
@@ -1654,6 +2310,12 @@ namespace build2
// are automatically handled by process_libraries(). So all we
// have to do is implement the "thin archive" logic.
//
+ // We also don't need to do anything special for the out-of-date
+ // logic: If any of its object files (or the set of its object
+ // files) changes, then the library will have to be updated as
+ // well. In other words, we use the library timestamp as a proxy
+ // for all of its member's timestamps.
+ //
// We may also end up trying to link a non-utility library to a
// static library via a utility library (direct linking is taken
// care of by perform_update()). So we cut it off here.
@@ -1664,6 +2326,11 @@ namespace build2
if (l->mtime () == timestamp_unreal) // Binless.
goto done;
+ // Check if this library renders us out of date.
+ //
+ if (d.update != nullptr)
+ *d.update = *d.update || newer ();
+
for (const target* pt: l->prerequisite_targets[d.a])
{
if (pt == nullptr)
@@ -1698,6 +2365,11 @@ namespace build2
if (l->mtime () == timestamp_unreal) // Binless.
goto done;
+ // Check if this library renders us out of date.
+ //
+ if (d.update != nullptr)
+ *d.update = *d.update || newer ();
+
// On Windows a shared library is a DLL with the import library as
// an ad hoc group member. MinGW though can link directly to DLLs
// (see search_library() for details).
@@ -1733,17 +2405,28 @@ namespace build2
d.args.push_back (move (p));
}
+
+ if (d.cs != nullptr)
+ {
+ d.cs->append (f);
+ hash_path (*d.cs, l->path (), *d.out_root);
+ }
}
done:
- al.end = d.args.size (); // Close.
+ if (al != nullptr)
+ al->end = d.args.size (); // Close.
+
+ return true;
};
- auto opt = [&d, this] (const file& l,
+ auto opt = [&d, this] (const target& lt,
const string& t,
bool com,
bool exp)
{
+ const file& l (lt.as<file> ());
+
// Don't try to pass any loptions when linking a static library.
//
// Note also that we used to pass non-export loptions but that didn't
@@ -1755,17 +2438,19 @@ namespace build2
// the exp checks below.
//
if (d.li.type == otype::a || !exp)
- return;
+ return true;
// Suppress duplicates.
//
if (d.ls.append (l, d.args.size ()).end != appended_library::npos)
- return;
+ return true;
// If we need an interface value, then use the group (lib{}).
//
if (const target* g = exp && l.is_a<libs> () ? l.group : &l)
{
+ // Note: go straight for the public variable pool.
+ //
const variable& var (
com
? (exp ? c_export_loptions : c_loptions)
@@ -1774,135 +2459,44 @@ namespace build2
: l.ctx.var_pool[t + (exp ? ".export.loptions" : ".loptions")]));
append_options (d.args, *g, var);
- }
- };
-
- process_libraries (
- a, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, self);
- }
-
- void link_rule::
- append_libraries (sha256& cs, bool& update, timestamp mt,
- const scope& bs, action a,
- const file& l, bool la, lflags lf, linfo li) const
- {
- // Note that we don't do any duplicate suppression here: there is no way
- // to "hoist" things once they are hashed and hashing only the first
- // occurrence could miss changes to the command line (e.g., due to
- // "hoisting").
-
- struct data
- {
- sha256& cs;
- const dir_path& out_root;
- bool& update;
- timestamp mt;
- linfo li;
- } d {cs, bs.root_scope ()->out_path (), update, mt, li};
-
- auto imp = [] (const file&, bool la)
- {
- return la;
- };
- auto lib = [&d, this] (const file* const* lc,
- const string& p,
- lflags f,
- bool)
- {
- const file* l (lc != nullptr ? *lc : nullptr);
-
- if (l == nullptr)
- {
- if (d.li.type != otype::a)
- d.cs.append (p);
+ if (d.cs != nullptr)
+ append_options (*d.cs, *g, var);
}
- else
- {
- bool lu (l->is_a<libux> ());
-
- if (lu)
- {
- for (ptrdiff_t i (-1); lc[i] != nullptr; --i)
- if (!lc[i]->is_a<libux> ())
- return;
- }
- // We also don't need to do anything special for linking a utility
- // library to a static library. If any of its object files (or the
- // set of its object files) changes, then the library will have to
- // be updated as well. In other words, we use the library timestamp
- // as a proxy for all of its member's timestamps.
- //
- // We do need to cut of the static to static linking, just as in
- // append_libraries().
- //
- if (d.li.type == otype::a && !lu)
- return;
-
- if (l->mtime () == timestamp_unreal) // Binless.
- return;
-
- // Check if this library renders us out of date.
- //
- d.update = d.update || l->newer (d.mt);
-
- // On Windows a shared library is a DLL with the import library as
- // an ad hoc group member. MinGW though can link directly to DLLs
- // (see search_library() for details).
- //
- if (tclass == "windows" && l->is_a<libs> ())
- {
- if (const libi* li = find_adhoc_member<libi> (*l))
- l = li;
- }
-
- d.cs.append (f);
- hash_path (d.cs, l->path (), d.out_root);
- }
+ return true;
};
- auto opt = [&d, this] (const file& l,
- const string& t,
- bool com,
- bool exp)
- {
- if (d.li.type == otype::a || !exp)
- return;
-
- if (const target* g = exp && l.is_a<libs> () ? l.group : &l)
- {
- const variable& var (
- com
- ? (exp ? c_export_loptions : c_loptions)
- : (t == x
- ? (exp ? x_export_loptions : x_loptions)
- : l.ctx.var_pool[t + (exp ? ".export.loptions" : ".loptions")]));
-
- append_options (d.cs, *g, var);
- }
- };
-
- process_libraries (
- a, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true);
+ process_libraries (a, bs, li, sys_lib_dirs,
+ l, la,
+ lf, imp, lib, opt,
+ self,
+ false /* proc_opt_group */,
+ lib_cache);
}
void link_rule::
rpath_libraries (rpathed_libraries& ls, strings& args,
const scope& bs,
action a, const file& l, bool la,
- linfo li, bool link, bool self) const
+ linfo li, bool link, bool self,
+ library_cache* lib_cache) const
{
// Use -rpath-link only on targets that support it (Linux, *BSD). Note
// that we don't really need it for top-level libraries.
//
+ // Note that more recent versions of FreeBSD are using LLVM lld without
+ // any mentioning of -rpath-link in the man pages.
+ //
+ auto have_link = [this] () {return tclass == "linux" || tclass == "bsd";};
+
if (link)
{
- if (tclass != "linux" && tclass != "bsd")
+ if (!have_link ())
return;
}
- auto imp = [link] (const file& l, bool la)
+ auto imp = [link] (const target& l, bool la)
{
// If we are not rpath-link'ing, then we only need to rpath interface
// libraries (they will include rpath's for their implementations)
@@ -1928,38 +2522,116 @@ namespace build2
{
rpathed_libraries& ls;
strings& args;
- bool link;
- } d {ls, args, link};
+ bool rpath;
+ bool rpath_link;
+ } d {ls, args, false, false};
- auto lib = [&d, this] (const file* const* lc,
- const string& f,
- lflags,
- bool sys)
+ if (link)
+ d.rpath_link = true;
+ else
{
- const file* l (lc != nullptr ? *lc : nullptr);
+ // While one would naturally expect -rpath to be a superset of
+ // -rpath-link, according to GNU ld:
+ //
+ // "The -rpath option is also used when locating shared objects which
+ // are needed by shared objects explicitly included in the link; see
+ // the description of the -rpath-link option. Searching -rpath in
+ // this way is only supported by native linkers and cross linkers
+ // which have been configured with the --with-sysroot option."
+ //
+ // So we check if this is cross-compilation and request both options
+ // if that's the case (we have no easy way of detecting whether the
+ // linker has been configured with the --with-sysroot option, whatever
+ // that means, so we will just assume the worst case).
+ //
+ d.rpath = true;
+
+ if (have_link ())
+ {
+ // Detecting cross-compilation is not as easy as it seems. Comparing
+ // complete target triplets proved too strict. For example, we may be
+ // running on x86_64-apple-darwin17.7.0 while the compiler is
+ // targeting x86_64-apple-darwin17.3.0. Also, there is the whole i?86
+ // family of CPUs which, at least for linking, should probably be
+ // considered the same.
+ //
+ const target_triplet& h (*bs.ctx.build_host);
+ const target_triplet& t (ctgt);
+
+ auto x86 = [] (const string& c)
+ {
+ return (c.size () == 4 &&
+ c[0] == 'i' &&
+ (c[1] >= '3' && c[1] <= '6') &&
+ c[2] == '8' &&
+ c[3] == '6');
+ };
+
+ if (t.system != h.system ||
+ (t.cpu != h.cpu && !(x86 (t.cpu) && x86 (h.cpu))))
+ d.rpath_link = true;
+ }
+ }
+
+ auto lib = [&d, this] (
+ const target* const* lc,
+ const small_vector<reference_wrapper<const string>, 2>& ns,
+ lflags,
+ const string*,
+ bool sys)
+ {
+ const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr);
// We don't rpath system libraries. Why, you may ask? There are many
// good reasons and I have them written on a napkin somewhere...
//
+ // We also assume system libraries can only depend on other system
+ // libraries and so can prune the traversal.
+ //
if (sys)
- return;
+ return false;
- if (l != nullptr)
+ auto append = [&d] (const string& f)
{
- if (!l->is_a<libs> ())
- return;
+ size_t p (path::traits_type::rfind_separator (f));
+ assert (p != string::npos);
- if (l->mtime () == timestamp_unreal) // Binless.
- return;
+ 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 (d.rpath_link)
+ {
+ string o ("-Wl,-rpath-link,");
+ o.append (f, 0, (p != 0 ? p : 1));
+ d.args.push_back (move (o));
+ }
+ };
+
+ if (l != nullptr)
+ {
// Suppress duplicates.
//
// We handle rpath similar to the compilation case by adding the
- // options on the first occurrence and ignoring all the subsequent.
+ // options on the first occurrence and ignoring (and pruning) all
+ // the subsequent.
//
if (find (d.ls.begin (), d.ls.end (), l) != d.ls.end ())
- return;
+ return false;
+
+ // Note that these checks are fairly expensive so we do them after
+ // duplicate suppression.
+ //
+ if (!l->is_a<libs> ())
+ return true;
+ if (l->mtime () == timestamp_unreal) // Binless.
+ return true;
+
+ append (ns[0]);
d.ls.push_back (l);
}
else
@@ -1969,39 +2641,32 @@ namespace build2
// better than checking for a platform-specific extension (maybe
// we should cache it somewhere).
//
- size_t p (path::traits_type::find_extension (f));
+ for (const string& f: ns)
+ {
+ size_t p (path::traits_type::find_extension (f));
- if (p == string::npos)
- return;
+ if (p == string::npos)
+ break;
- ++p; // Skip dot.
+ ++p; // Skip dot.
- bool c (true);
- const char* e;
+ bool c (true);
+ const char* e;
- if (tclass == "windows") {e = "dll"; c = false;}
- else if (tsys == "darwin") e = "dylib";
- else e = "so";
+ if (tclass == "windows") {e = "dll"; c = false;}
+ else if (tsys == "darwin") e = "dylib";
+ else e = "so";
- if ((c
- ? f.compare (p, string::npos, e)
- : icasecmp (f.c_str () + p, e)) != 0)
- return;
+ if ((c
+ ? f.compare (p, string::npos, e)
+ : icasecmp (f.c_str () + p, e)) == 0)
+ append (f);
+ }
}
- // Ok, if we are here then it means we have a non-system, shared
- // library and its absolute path is in f.
- //
- string o (d.link ? "-Wl,-rpath-link," : "-Wl,-rpath,");
-
- size_t p (path::traits_type::rfind_separator (f));
- assert (p != string::npos);
-
- o.append (f, 0, (p != 0 ? p : 1)); // Don't include trailing slash.
- d.args.push_back (move (o));
+ return true;
};
-
if (self && !link && !la)
{
// Top-level shared library dependency.
@@ -2020,7 +2685,10 @@ namespace build2
process_libraries (a, bs, li, sys_lib_dirs,
l, la, 0 /* lflags */,
- imp, lib, nullptr);
+ imp, lib, nullptr,
+ false /* self */,
+ false /* proc_opt_group */,
+ lib_cache);
}
void link_rule::
@@ -2029,6 +2697,7 @@ namespace build2
const target& t, linfo li, bool link) const
{
rpathed_libraries ls;
+ library_cache lc;
for (const prerequisite_target& pt: t.prerequisite_targets[a])
{
@@ -2042,16 +2711,16 @@ namespace build2
(la = (f = pt->is_a<libux> ())) ||
( f = pt->is_a<libs> ()))
{
- rpath_libraries (ls, args, bs, a, *f, la, li, link, true);
+ rpath_libraries (ls, args, bs, a, *f, la, li, link, true, &lc);
}
}
}
- // Append object files of bmi{} prerequisites that belong to binless
- // libraries.
+ // Append (and optionally hash while at it) object files of bmi{}
+ // prerequisites that belong to binless libraries.
//
void link_rule::
- append_binless_modules (strings& args,
+ append_binless_modules (strings& args, sha256* cs,
const scope& bs, action a, const file& t) const
{
// Note that here we don't need to hoist anything on duplicate detection
@@ -2068,25 +2737,12 @@ namespace build2
if (find (args.begin (), args.end (), p) == args.end ())
{
args.push_back (move (p));
- append_binless_modules (args, bs, a, o);
- }
- }
- }
- }
- void link_rule::
- append_binless_modules (sha256& cs,
- const scope& bs, action a, const file& t) const
- {
- for (const target* pt: t.prerequisite_targets[a])
- {
- if (pt != nullptr &&
- pt->is_a<bmix> () &&
- cast_false<bool> ((*pt)[b_binless]))
- {
- const objx& o (*find_adhoc_member<objx> (*pt));
- hash_path (cs, o.path (), bs.root_scope ()->out_path ());
- append_binless_modules (cs, bs, a, o);
+ if (cs != nullptr)
+ hash_path (*cs, o.path (), bs.root_scope ()->out_path ());
+
+ append_binless_modules (args, cs, bs, a, o);
+ }
}
}
}
@@ -2094,7 +2750,7 @@ namespace build2
// Filter link.exe noise (msvc.cxx).
//
void
- msvc_filter_link (ifdstream&, const file&, otype);
+ msvc_filter_link (diag_buffer&, const file&, otype);
// Translate target CPU to the link.exe/lib.exe /MACHINE option.
//
@@ -2102,7 +2758,7 @@ namespace build2
msvc_machine (const string& cpu); // msvc.cxx
target_state link_rule::
- perform_update (action a, const target& xt) const
+ perform_update (action a, const target& xt, match_data& md) const
{
tracer trace (x, "link_rule::perform_update");
@@ -2114,8 +2770,6 @@ namespace build2
const scope& bs (t.base_scope ());
const scope& rs (*bs.root_scope ());
- match_data& md (t.data<match_data> ());
-
// Unless the outer install rule signalled that this is update for
// install, signal back that we've performed plain update.
//
@@ -2144,14 +2798,33 @@ namespace build2
// Note that execute_prerequisites() blanks out all the ad hoc
// prerequisites so we don't need to worry about them from now on.
//
+ // There is an interesting trade-off between the straight and reverse
+ // execution. With straight we may end up with inaccurate progress if
+ // most of our library prerequisites (typically specified last) are
+ // already up to date. In this case, the progress will first increase
+ // slowly as we compile this target's source files and then jump
+ // straight to 100% as we "realize" that all the libraries (and all
+ // their prerequisites) are already up to date.
+ //
+ // Switching to reverse fixes this but messes up incremental building:
+ // now instead of starting to compile source files right away, we will
+ // first spend some time making sure all the libraries are up to date
+ // (which, in case of an error in the source code, will be a complete
+ // waste).
+ //
+ // There doesn't seem to be an easy way to distinguish between
+ // incremental and from-scratch builds and on balance fast incremental
+ // builds feel more important.
+ //
target_state ts;
- if (optional<target_state> s =
- execute_prerequisites (a,
- t,
- mt,
- [] (const target&, size_t) {return false;}))
+ if (optional<target_state> s = execute_prerequisites (
+ a, t,
+ mt,
+ [] (const target&, size_t) {return false;}))
+ {
ts = *s;
+ }
else
{
// An ad hoc prerequisite renders us out-of-date. Let's update from
@@ -2165,7 +2838,7 @@ namespace build2
// those that don't match. Note that we have to do it after updating
// prerequisites to keep the dependency counts straight.
//
- if (const variable* var_fi = ctx.var_pool.find ("for_install"))
+ if (const variable* var_fi = rs.var_pool ().find ("for_install"))
{
// Parallel prerequisites/prerequisite_targets loop.
//
@@ -2191,12 +2864,20 @@ namespace build2
// (Re)generate pkg-config's .pc file. While the target itself might be
// up-to-date from a previous run, there is no guarantee that .pc exists
// or also up-to-date. So to keep things simple we just regenerate it
- // unconditionally.
+ // unconditionally (and avoid doing so on uninstall; see pkgconfig_save()
+ // for details).
//
// Also, if you are wondering why don't we just always produce this .pc,
// install or no install, the reason is unless and until we are updating
// for install, we have no idea where-to things will be installed.
//
+ // There is a further complication: we may have no intention of
+ // installing the library but still need to update it for install (see
+ // install_scope() for background). In which case we may still not have
+ // the installation directories. We handle this in pkgconfig_save() by
+ // skipping the generation of .pc files (and letting the install rule
+ // complain if we do end up trying to install them).
+ //
if (for_install && lt.library () && !lt.utility)
{
bool la (lt.static_library ());
@@ -2210,8 +2891,12 @@ namespace build2
if (!m->is_a (la ? pca::static_type : pcs::static_type))
{
- if (t.group->matched (a))
+ if (operator>= (t.group->decl, target_decl::implied) // @@ VC14
+ ? t.group->matched (a)
+ : true)
+ {
pkgconfig_save (a, t, la, true /* common */, binless);
+ }
else
// Mark as non-existent not to confuse the install rule.
//
@@ -2323,14 +3008,19 @@ namespace build2
try
{
+ // We assume that what we write to stdin is small enough to
+ // fit into the pipe's buffer without blocking.
+ //
process pr (rc,
args,
- -1 /* stdin */,
- 1 /* stdout */,
- 2 /* stderr */,
- nullptr /* cwd */,
+ -1 /* stdin */,
+ 1 /* stdout */,
+ diag_buffer::pipe (ctx) /* stderr */,
+ nullptr /* cwd */,
env_ptrs.empty () ? nullptr : env_ptrs.data ());
+ diag_buffer dbuf (ctx, args[0], pr);
+
try
{
ofdstream os (move (pr.out_fd));
@@ -2354,7 +3044,8 @@ namespace build2
// was caused by that and let run_finish() deal with it.
}
- run_finish (args, pr);
+ dbuf.read ();
+ run_finish (dbuf, args, pr, 2 /* verbosity */);
}
catch (const process_error& e)
{
@@ -2409,6 +3100,8 @@ namespace build2
{
// For VC we use link.exe directly.
//
+ // Note: go straight for the public variable pool.
+ //
const string& cs (
cast<string> (
rs[tsys == "win32-msvc"
@@ -2419,10 +3112,13 @@ namespace build2
l4 ([&]{trace << "linker mismatch forcing update of " << t;});
}
- // Hash and compare any changes to the environment.
+ // Then the linker environment checksum (original and our modifications).
//
- if (dd.expect (env_cs.string ()) != nullptr)
- l4 ([&]{trace << "environment mismatch forcing update of " << t;});
+ {
+ bool e (dd.expect (env_checksum) != nullptr);
+ if (dd.expect (env_cs.string ()) != nullptr || e)
+ l4 ([&]{trace << "environment mismatch forcing update of " << t;});
+ }
// Next check the target. While it might be incorporated into the linker
// checksum, it also might not (e.g., VC link.exe).
@@ -2435,8 +3131,8 @@ namespace build2
// are to either replicate the exact process twice, first for hashing
// then for building or to go ahead and start building and hash the
// result. The first approach is probably more efficient while the
- // second is simpler. Let's got with the simpler for now (actually it's
- // kind of a hybrid).
+ // second is simpler. Let's got with the simpler for now (also see a
+ // note on the cost of library dependency graph traversal below).
//
cstrings args {nullptr}; // Reserve one for config.bin.ar/config.x.
strings sargs; // Argument tail with storage.
@@ -2499,6 +3195,9 @@ namespace build2
// probably safe to assume that the two came from the same version
// of binutils/LLVM.
//
+ // @@ Note also that GNU ar deprecated -T in favor of --thin in
+ // version 2.38.
+ //
if (lt.utility)
{
const string& id (cast<string> (rs["bin.ar.id"]));
@@ -2612,10 +3311,72 @@ namespace build2
rpath_libraries (sargs, bs, a, t, li, for_install /* link */);
lookup l;
-
if ((l = t["bin.rpath"]) && !l->empty ())
+ {
+ // See if we need to make the specified paths relative using the
+ // $ORIGIN (Linux, BSD) or @loader_path (Mac OS) mechanisms.
+ //
+ optional<dir_path> origin;
+ if (for_install && cast_false<bool> (rs["install.relocatable"]))
+ {
+ // Note that both $ORIGIN and @loader_path will be expanded to
+ // the path of the binary that we are building (executable or
+ // shared library) as opposed to top-level executable.
+ //
+ path p (install::resolve_file (t));
+
+ // If the file is not installable then the install.relocatable
+ // semantics does not apply, naturally.
+ //
+ if (!p.empty ())
+ origin = p.directory ();
+ }
+
+ bool origin_used (false);
for (const dir_path& p: cast<dir_paths> (l))
- sargs.push_back ("-Wl,-rpath," + p.string ());
+ {
+ string o ("-Wl,-rpath,");
+
+ // Note that we only rewrite absolute paths so if the user
+ // specified $ORIGIN or @loader_path manually, we will pass it
+ // through as is.
+ //
+ if (origin && p.absolute ())
+ {
+ dir_path l;
+ try
+ {
+ l = p.relative (*origin);
+ }
+ catch (const invalid_path&)
+ {
+ fail << "unable to make rpath " << p << " relative to "
+ << *origin <<
+ info << "required for relocatable installation";
+ }
+
+ o += (tclass == "macos" ? "@loader_path" : "$ORIGIN");
+
+ if (!l.empty ())
+ {
+ o += path_traits::directory_separator;
+ o += l.string ();
+ }
+
+ origin_used = true;
+ }
+ else
+ o += p.string ();
+
+ sargs.push_back (move (o));
+ }
+
+ // According to the Internet, `-Wl,-z,origin` is not needed except
+ // potentially for older BSDs.
+ //
+ if (origin_used && tclass == "bsd")
+ sargs.push_back ("-Wl,-z,origin");
+ }
if ((l = t["bin.rpath_link"]) && !l->empty ())
{
@@ -2649,25 +3410,24 @@ namespace build2
// Extra system library dirs (last).
//
- assert (sys_lib_dirs_extra <= sys_lib_dirs.size ());
+ assert (sys_lib_dirs_mode + sys_lib_dirs_extra <= sys_lib_dirs.size ());
+
+ // Note that the mode options are added as part of cmode.
+ //
+ auto b (sys_lib_dirs.begin () + sys_lib_dirs_mode);
+ auto x (b + sys_lib_dirs_extra);
if (tsys == "win32-msvc")
{
// If we have no LIB environment variable set, then we add all of
// them. But we want extras to come first.
//
- // Note that the mode options are added as part of cmode.
- //
- auto b (sys_lib_dirs.begin () + sys_lib_dirs_mode);
- auto m (sys_lib_dirs.begin () + sys_lib_dirs_extra);
- auto e (sys_lib_dirs.end ());
-
- for (auto i (m); i != e; ++i)
+ for (auto i (b); i != x; ++i)
sargs1.push_back ("/LIBPATH:" + i->string ());
if (!getenv ("LIB"))
{
- for (auto i (b); i != m; ++i)
+ for (auto i (x), e (sys_lib_dirs.end ()); i != e; ++i)
sargs1.push_back ("/LIBPATH:" + i->string ());
}
@@ -2678,7 +3438,7 @@ namespace build2
append_option_values (
args,
"-L",
- sys_lib_dirs.begin () + sys_lib_dirs_extra, sys_lib_dirs.end (),
+ b, x,
[] (const dir_path& d) {return d.string ().c_str ();});
}
}
@@ -2708,8 +3468,20 @@ namespace build2
// pinpoint exactly what is causing the update. On the other hand, the
// checksum is faster and simpler. And we like simple.
//
+ // Note that originally we only hashed inputs here and then re-collected
+ // them below. But the double traversal of the library graph proved to
+ // be way more expensive on libraries with lots of dependencies (like
+ // Boost) than both collecting and hashing in a single pass. So that's
+ // what we do now. @@ TODO: it would be beneficial to also merge the
+ // rpath pass above into this.
+ //
+ // See also a similar loop inside append_libraries().
+ //
+ bool seen_obj (false);
const file* def (nullptr); // Cached if present.
{
+ appended_libraries als;
+ library_cache lc;
sha256 cs;
for (const prerequisite_target& p: t.prerequisite_targets[a])
@@ -2747,8 +3519,8 @@ namespace build2
((la = (f = pt->is_a<liba> ())) ||
(ls = (f = pt->is_a<libs> ())))))
{
- // Link all the dependent interface libraries (shared) or interface
- // and implementation (static), recursively.
+ // Link all the dependent interface libraries (shared) or
+ // interface and implementation (static), recursively.
//
// Also check if any of them render us out of date. The tricky
// case is, say, a utility library (static) that depends on a
@@ -2758,12 +3530,22 @@ namespace build2
//
if (la || ls)
{
- append_libraries (cs, update, mt, bs, a, *f, la, p.data, li);
- f = nullptr; // Timestamp checked by hash_libraries().
+ append_libraries (als, sargs,
+ &cs, &update, mt,
+ bs, a, *f, la, p.data, li,
+ for_install, true, true, &lc);
+ f = nullptr; // Timestamp checked by append_libraries().
}
else
{
- hash_path (cs, f->path (), rs.out_path ());
+ // Do not hoist libraries over object files since such object
+ // files might satisfy symbols in the preceding libraries.
+ //
+ als.clear ();
+
+ const path& p (f->path ());
+ sargs.push_back (relative (p).string ());
+ hash_path (cs, p, rs.out_path ());
// @@ Do we actually need to hash this? I don't believe this set
// can change without rendering the object file itself out of
@@ -2771,7 +3553,9 @@ namespace build2
// marked with bin.binless manually?
//
if (modules)
- append_binless_modules (cs, rs, a, *f);
+ append_binless_modules (sargs, &cs, bs, a, *f);
+
+ seen_obj = true;
}
}
else if ((f = pt->is_a<bin::def> ()))
@@ -2839,6 +3623,10 @@ namespace build2
//
path relt (relative (tp));
+ path reli; // Import library.
+ if (lt.shared_library () && (tsys == "win32-msvc" || tsys == "mingw32"))
+ reli = relative (find_adhoc_member<libi> (t)->path ());
+
const process_path* ld (nullptr);
if (lt.static_library ())
{
@@ -2970,7 +3758,7 @@ namespace build2
// derived from the import library by changing the extension.
// Lucky for us -- there is no option to name it.
//
- out2 += relative (find_adhoc_member<libi> (t)->path ()).string ();
+ out2 += reli.string ();
}
else
{
@@ -2983,14 +3771,17 @@ namespace build2
// If we have /DEBUG then name the .pdb file. It is an ad hoc group
// member.
//
- if (find_option ("/DEBUG", args, true))
+ if (const char* o = find_option_prefix ("/DEBUG", args, true))
{
- const file& pdb (
- *find_adhoc_member<file> (t, *bs.find_target_type ("pdb")));
+ if (icasecmp (o, "/DEBUG:NONE") != 0)
+ {
+ const file& pdb (
+ *find_adhoc_member<file> (t, *bs.find_target_type ("pdb")));
- out1 = "/PDB:";
- out1 += relative (pdb.path ()).string ();
- args.push_back (out1.c_str ());
+ out1 = "/PDB:";
+ out1 += relative (pdb.path ()).string ();
+ args.push_back (out1.c_str ());
+ }
}
out = "/OUT:" + relt.string ();
@@ -3004,6 +3795,8 @@ namespace build2
{
ld = &cpath;
+ append_diag_color_options (args);
+
// Add the option that triggers building a shared library and
// take care of any extras (e.g., import library).
//
@@ -3019,8 +3812,7 @@ namespace build2
// On Windows libs{} is the DLL and an ad hoc group member
// is the import library.
//
- const file& imp (*find_adhoc_member<libi> (t));
- out = "-Wl,--out-implib=" + relative (imp.path ()).string ();
+ out = "-Wl,--out-implib=" + reli.string ();
args.push_back (out.c_str ());
}
}
@@ -3051,60 +3843,6 @@ namespace build2
size_t args_input (args.size ());
#endif
- // The same logic as during hashing above. See also a similar loop
- // inside append_libraries().
- //
- bool seen_obj (false);
- {
- appended_libraries als;
- for (const prerequisite_target& p: t.prerequisite_targets[a])
- {
- const target* pt (p.target);
-
- if (pt == nullptr)
- continue;
-
- if (modules)
- {
- if (pt->is_a<bmix> ())
- {
- pt = find_adhoc_member (*pt, tts.obj);
-
- if (pt == nullptr) // Header BMIs have no object file.
- continue;
- }
- }
-
- const file* f;
- bool la (false), ls (false);
-
- if ((f = pt->is_a<objx> ()) ||
- (!lt.utility &&
- (la = (f = pt->is_a<libux> ()))) ||
- (!lt.static_library () &&
- ((la = (f = pt->is_a<liba> ())) ||
- (ls = (f = pt->is_a<libs> ())))))
- {
- if (la || ls)
- append_libraries (als, sargs, bs, a, *f, la, p.data, li);
- else
- {
- // Do not hoist libraries over object files since such object
- // files might satisfy symbols in the preceding libraries.
- //
- als.clear ();
-
- sargs.push_back (relative (f->path ()).string ());
-
- if (modules)
- append_binless_modules (sargs, bs, a, *f);
-
- seen_obj = true;
- }
- }
- }
- }
-
// For MinGW manifest is an object file.
//
if (!manifest.empty () && tsys == "mingw32")
@@ -3231,17 +3969,43 @@ namespace build2
try_rmfile (relt, true);
}
+ // We have no choice but to serialize early if we want the command line
+ // printed shortly before actually executing the linker. Failed that, it
+ // may look like we are still executing in parallel.
+ //
+ scheduler::alloc_guard jobs_ag;
+ if (!ctx.dry_run && cast_false<bool> (t[c_serialize]))
+ jobs_ag = scheduler::alloc_guard (*ctx.sched, phase_unlock (nullptr));
+
if (verb == 1)
- text << (lt.static_library () ? "ar " : "ld ") << t;
+ print_diag (lt.static_library () ? "ar" : "ld", t);
else if (verb == 2)
print_process (args);
+ // Do any necessary fixups to the command line to make it runnable.
+ //
+ // Notice the split in the diagnostics: at verbosity level 1 we print
+ // the "logical" command line while at level 2 and above -- what we are
+ // actually executing.
+ //
+ // We also need to save the original for the diag_buffer::close() call
+ // below if at verbosity level 1.
+ //
+ cstrings oargs;
+
// Adjust linker parallelism.
//
+ // Note that we are not going to bother with oargs for this.
+ //
+ // Note also that we now have scheduler::serialize() which allows us to
+ // block until full parallelism is available (this mode can currently
+ // be forced with cc.serialize=true; maybe we should invent something
+ // like config.cc.link_serialize or some such which can be used when
+ // LTO is enabled).
+ //
string jobs_arg;
- scheduler::alloc_guard jobs_extra;
- if (!lt.static_library ())
+ if (!ctx.dry_run && !lt.static_library ())
{
switch (ctype)
{
@@ -3257,8 +4021,10 @@ namespace build2
auto i (find_option_prefix ("-flto", args.rbegin (), args.rend ()));
if (i != args.rend () && strcmp (*i, "-flto=auto") == 0)
{
- jobs_extra = scheduler::alloc_guard (ctx.sched, 0);
- jobs_arg = "-flto=" + to_string (1 + jobs_extra.n);
+ if (jobs_ag.n == 0) // Might already have (see above).
+ jobs_ag = scheduler::alloc_guard (*ctx.sched, 0);
+
+ jobs_arg = "-flto=" + to_string (1 + jobs_ag.n);
*i = jobs_arg.c_str ();
}
break;
@@ -3276,8 +4042,10 @@ namespace build2
strcmp (*i, "-flto=thin") == 0 &&
!find_option_prefix ("-flto-jobs=", args))
{
- jobs_extra = scheduler::alloc_guard (ctx.sched, 0);
- jobs_arg = "-flto-jobs=" + to_string (1 + jobs_extra.n);
+ if (jobs_ag.n == 0) // Might already have (see above).
+ jobs_ag = scheduler::alloc_guard (*ctx.sched, 0);
+
+ jobs_arg = "-flto-jobs=" + to_string (1 + jobs_ag.n);
args.insert (i.base (), jobs_arg.c_str ()); // After -flto=thin.
}
break;
@@ -3288,12 +4056,6 @@ namespace build2
}
}
- // Do any necessary fixups to the command line to make it runnable.
- //
- // Notice the split in the diagnostics: at verbosity level 1 we print
- // the "logical" command line while at level 2 and above -- what we are
- // actually executing.
- //
// On Windows we need to deal with the command line length limit. The
// best workaround seems to be passing (part of) the command line in an
// "options file" ("response file" in Microsoft's terminology). Both
@@ -3314,7 +4076,7 @@ namespace build2
{
auto quote = [s = string ()] (const char* a) mutable -> const char*
{
- return process::quote_argument (a, s);
+ return process::quote_argument (a, s, false /* batch */);
};
// Calculate the would-be command line length similar to how process'
@@ -3379,19 +4141,20 @@ namespace build2
fail << "unable to write to " << f << ": " << e;
}
+ if (verb == 1)
+ oargs = args;
+
// Replace input arguments with @file.
//
targ = '@' + f.string ();
args.resize (args_input);
args.push_back (targ.c_str());
args.push_back (nullptr);
-
- //@@ TODO: leave .t file if linker failed and verb > 2?
}
}
#endif
- if (verb > 2)
+ if (verb >= 3)
print_process (args);
// Remove the target file if any of the subsequent (after the linker)
@@ -3409,52 +4172,51 @@ namespace build2
{
// VC tools (both lib.exe and link.exe) send diagnostics to stdout.
// Also, link.exe likes to print various gratuitous messages. So for
- // link.exe we redirect stdout to a pipe, filter that noise out, and
- // send the rest to stderr.
+ // link.exe we filter that noise out.
//
// For lib.exe (and any other insane linker that may try to pull off
// something like this) we are going to redirect stdout to stderr.
// For sane compilers this should be harmless.
//
// Note that we don't need this for LLD's link.exe replacement which
- // is quiet.
+ // is thankfully quiet.
//
bool filter (tsys == "win32-msvc" &&
!lt.static_library () &&
cast<string> (rs["bin.ld.id"]) != "msvc-lld");
process pr (*ld,
- args.data (),
- 0 /* stdin */,
- (filter ? -1 : 2) /* stdout */,
- 2 /* stderr */,
- nullptr /* cwd */,
+ args,
+ 0 /* stdin */,
+ 2 /* stdout */,
+ diag_buffer::pipe (ctx, filter /* force */) /* stderr */,
+ nullptr /* cwd */,
env_ptrs.empty () ? nullptr : env_ptrs.data ());
+ diag_buffer dbuf (ctx, args[0], pr);
+
if (filter)
+ msvc_filter_link (dbuf, t, ot);
+
+ dbuf.read ();
+
{
- try
- {
- ifdstream is (
- move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit);
+ bool e (pr.wait ());
- msvc_filter_link (is, t, ot);
+#ifdef _WIN32
+ // Keep the options file if we have shown it.
+ //
+ if (!e && verb >= 3)
+ trm.cancel ();
+#endif
- // If anything remains in the stream, send it all to stderr.
- // Note that the eof check is important: if the stream is at
- // eof, this and all subsequent writes to the diagnostics stream
- // will fail (and you won't see a thing).
- //
- if (is.peek () != ifdstream::traits_type::eof ())
- diag_stream_lock () << is.rdbuf ();
+ dbuf.close (oargs.empty () ? args : oargs,
+ *pr.exit,
+ 1 /* verbosity */);
- is.close ();
- }
- catch (const io_error&) {} // Assume exits with error.
+ if (!e)
+ throw failed ();
}
-
- run_finish (args, pr);
- jobs_extra.deallocate ();
}
catch (const process_error& e)
{
@@ -3476,12 +4238,24 @@ namespace build2
throw failed ();
}
- // Clean up executable's import library (see above for details).
+ // Clean up executable's import library (see above for details). And
+ // make sure we have an import library for a shared library.
//
- if (lt.executable () && tsys == "win32-msvc")
+ if (tsys == "win32-msvc")
{
- try_rmfile (relt + ".lib", true /* ignore_errors */);
- try_rmfile (relt + ".exp", true /* ignore_errors */);
+ if (lt.executable ())
+ {
+ try_rmfile (relt + ".lib", true /* ignore_errors */);
+ try_rmfile (relt + ".exp", true /* ignore_errors */);
+ }
+ else if (lt.shared_library ())
+ {
+ if (!file_exists (reli,
+ false /* follow_symlinks */,
+ true /* ignore_error */))
+ fail << "linker did not produce import library " << reli <<
+ info << "perhaps this library does not export any symbols?";
+ }
}
// Set executable bit on the .js file so that it can be run with a
@@ -3513,12 +4287,17 @@ namespace build2
print_process (args);
if (!ctx.dry_run)
- run (rl,
+ {
+ run (ctx,
+ rl,
args,
- dir_path () /* cwd */,
+ 1 /* finish_verbosity */,
env_ptrs.empty () ? nullptr : env_ptrs.data ());
+ }
}
+ jobs_ag.deallocate ();
+
// For Windows generate (or clean up) rpath-emulating assembly.
//
if (tclass == "windows")
@@ -3621,12 +4400,11 @@ namespace build2
}
target_state link_rule::
- perform_clean (action a, const target& xt) const
+ perform_clean (action a, const target& xt, match_data& md) const
{
const file& t (xt.as<file> ());
ltype lt (link_type (t));
- const match_data& md (t.data<match_data> ());
clean_extras extras;
clean_adhoc_extras adhoc_extras;
@@ -3699,5 +4477,25 @@ namespace build2
return perform_clean_extra (a, t, extras, adhoc_extras);
}
+
+ const target* link_rule::
+ import (const prerequisite_key& pk,
+ const optional<string>&,
+ const location&) const
+ {
+ tracer trace (x, "link_rule::import");
+
+ // @@ TODO: do we want to make metadata loading optional?
+ //
+ optional<dir_paths> usr_lib_dirs;
+ const target* r (search_library (nullopt /* action */,
+ sys_lib_dirs, usr_lib_dirs,
+ pk));
+
+ if (r == nullptr)
+ l4 ([&]{trace << "unable to find installed library " << pk;});
+
+ return r;
+ }
}
}