diff options
Diffstat (limited to 'libbuild2/cc/link-rule.cxx')
-rw-r--r-- | libbuild2/cc/link-rule.cxx | 1912 |
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; + } } } |