From 1c7cbb302b1c6e41eb0c5cecfc655532f1919cba Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 28 Jul 2017 13:46:26 +0200 Subject: Implement support for linking whole archive --- build2/algorithm.cxx | 34 ++++---------- build2/algorithm.hxx | 14 +++++- build2/algorithm.ixx | 49 ++++++++++++++++++-- build2/bin/init.cxx | 18 ++++++++ build2/cc/common.cxx | 23 ++++++---- build2/cc/common.hxx | 3 +- build2/cc/compile.cxx | 6 +-- build2/cc/link.cxx | 109 +++++++++++++++++++++++++++++++++----------- build2/cc/link.hxx | 4 +- build2/cc/types.hxx | 6 +++ build2/cc/utility.cxx | 15 +++--- build2/cc/windows-rpath.cxx | 12 ++--- tests/cc/libu/testscript | 34 +++++++++++--- 13 files changed, 237 insertions(+), 90 deletions(-) diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index fa9787b..5e18441 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -19,32 +19,6 @@ using namespace butl; namespace build2 { - const target* - search_existing (const prerequisite& p) - { - assert (phase == run_phase::match); // Could be relaxed. - - const target* r (p.target.load (memory_order_consume)); - - if (r == nullptr) - { - const prerequisite_key& pk (p.key ()); - r = pk.proj ? import_existing (pk) : search_existing_target (pk); - - if (r != nullptr) - { - const target* e (nullptr); - if (!p.target.compare_exchange_strong ( - e, r, - memory_order_release, - memory_order_consume)) - assert (e == r); - } - } - - return r; - } - const target& search (const target& t, const prerequisite_key& pk) { @@ -62,6 +36,14 @@ namespace build2 return create_new_target (pk); } + const target* + search_existing (const prerequisite_key& pk) + { + assert (phase == run_phase::match || phase == run_phase::execute); + + return pk.proj ? import_existing (pk) : search_existing_target (pk); + } + const target& search (const target& t, name n, const scope& s) { diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx index 58bdf2f..2006e2a 100644 --- a/build2/algorithm.hxx +++ b/build2/algorithm.hxx @@ -27,13 +27,16 @@ namespace build2 // As above but only search for an already existing target. // const target* - search_existing (const target&, const prerequisite&); + search_existing (const prerequisite&); // As above but specify the prerequisite to search as a key. // const target& search (const target&, const prerequisite_key&); + const target* + search_existing (const prerequisite_key&); + // Uniform search interface for prerequisite/prerequisite_member. // inline const target& @@ -59,6 +62,15 @@ namespace build2 const scope* = nullptr, // NULL means dir is absolute. const optional& proj = nullopt); + const target* + search_existing (const target_type& type, + const dir_path& dir, + const dir_path& out, + const string& name, + const string* ext = nullptr, + const scope* = nullptr, + const optional& proj = nullopt); + // As above but specify the target type as template argument. // template diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx index 53501d3..892c832 100644 --- a/build2/algorithm.ixx +++ b/build2/algorithm.ixx @@ -29,6 +29,31 @@ namespace build2 return *r; } + inline const target* + search_existing (const prerequisite& p) + { + assert (phase == run_phase::match || phase == run_phase::execute); + + const target* r (p.target.load (memory_order_consume)); + + if (r == nullptr) + { + r = search_existing (p.key ()); + + if (r != nullptr) + { + const target* e (nullptr); + if (!p.target.compare_exchange_strong ( + e, r, + memory_order_release, + memory_order_consume)) + assert (e == r); + } + } + + return r; + } + inline const target& search (const target& t, const target_type& tt, const prerequisite_key& k) { @@ -54,9 +79,27 @@ namespace build2 proj, { &type, - &dir, - &out, - &name, + &dir, &out, &name, + ext != nullptr ? optional (*ext) : nullopt + }, + scope}); + } + + inline const target* + search_existing (const target_type& type, + const dir_path& dir, + const dir_path& out, + const string& name, + const string* ext, + const scope* scope, + const optional& proj) + { + return search_existing ( + prerequisite_key { + proj, + { + &type, + &dir, &out, &name, ext != nullptr ? optional (*ext) : nullopt }, scope}); diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index fb9dd71..e145a23 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -76,6 +76,24 @@ namespace build2 vp.insert ("bin.libs.lib"); vp.insert ("bin.rpath"); + // Link whole archive. Note: non-overridable with target visibility. + // + // The lookup semantics is as follows: we first look for a prerequisite- + // specific value, then for a target-specific value in the library being + // linked, and then for target type/pattern-specific value starting from + // the scope of the target being linked-to. In that final lookup we do + // not look in the target being linked-to itself since that is used to + // indicate how this target should be linked to other targets. For + // example: + // + // exe{test}: liba{foo} + // liba{foo}: libu{foo1 foo2} + // liba{foo}: bin.whole = false # Affects test but not foo1 and foo2. + // + // If unspecified, defaults to false for liba{} and to true for libux{}. + // + vp.insert ("bin.whole", false, variable_visibility::target); + vp.insert ("bin.lib.prefix"); vp.insert ("bin.lib.suffix"); vp.insert ("bin.exe.prefix"); diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx index 8dabb07..195c3b7 100644 --- a/build2/cc/common.cxx +++ b/build2/cc/common.cxx @@ -52,10 +52,12 @@ namespace build2 const dir_paths& top_sysd, const file& l, bool la, + lflags lf, const function& proc_impl, // Implementation? const function& proc_lib, // True if system library. const function (l.vars[c_system]) : !p.empty () && sys (top_sysd, p.string ())); - proc_lib (&l, p.string (), s); + proc_lib (&l, p.string (), lf, s); } const scope& bs (t == nullptr || cc ? top_bs : l.base_scope ()); @@ -220,20 +222,20 @@ namespace build2 // if (impl && !c_e_libs.defined () && !x_e_libs.defined ()) { - for (const target* p: l.prerequisite_targets) + for (auto pt: l.prerequisite_targets) { bool a; const file* f; - if ((a = (f = p->is_a ())) || - (a = (f = p->is_a ())) || - ( f = p->is_a ())) + if ((a = (f = pt->is_a ())) || + (a = (f = pt->is_a ())) || + ( f = pt->is_a ())) { if (sysd == nullptr) find_sysd (); if (!li) find_linfo (); process_libraries (act, bs, *li, *sysd, - *f, a, + *f, a, pt.data, proc_impl, proc_lib, proc_opt, true); } } @@ -286,7 +288,7 @@ namespace build2 // .pc files. // if (proc_lib) - proc_lib (nullptr, n.value, sys_simple (n.value)); + proc_lib (nullptr, n.value, 0, sys_simple (n.value)); } else { @@ -316,8 +318,11 @@ namespace build2 // Process it recursively. // + // @@ Where can we get the link flags? Should we try to find them + // in the library's prerequisites? What about installed stuff? + // process_libraries (act, bs, *li, *sysd, - t, t.is_a () || t.is_a (), + t, t.is_a () || t.is_a (), 0, proc_impl, proc_lib, proc_opt, true); } } @@ -336,7 +341,7 @@ namespace build2 // This is something like -lpthread or shell32.lib so should be a // valid path. // - proc_lib (nullptr, n, sys_simple (n)); + proc_lib (nullptr, n, 0, sys_simple (n)); } }; diff --git a/build2/cc/common.hxx b/build2/cc/common.hxx index 6e1d8b9..951863a 100644 --- a/build2/cc/common.hxx +++ b/build2/cc/common.hxx @@ -198,8 +198,9 @@ namespace build2 const dir_paths&, const file&, bool, + lflags, const function&, - const function&, + const function&, const function&, bool = false) const; diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx index 8fa3296..fbf6a19 100644 --- a/build2/cc/compile.cxx +++ b/build2/cc/compile.cxx @@ -294,7 +294,7 @@ namespace build2 continue; process_libraries (act, bs, li, sys_lib_dirs, - pt->as (), a, + pt->as (), a, 0, // Hack: lflags unused. nullptr, nullptr, optf); } } @@ -338,7 +338,7 @@ namespace build2 continue; process_libraries (act, bs, li, sys_lib_dirs, - pt->as (), a, + pt->as (), a, 0, // Hack: lflags unused. nullptr, nullptr, optf); } } @@ -385,7 +385,7 @@ namespace build2 continue; process_libraries (act, bs, li, sys_lib_dirs, - pt->as (), a, + pt->as (), a, 0, // Hack: lflags unused. nullptr, nullptr, optf); } } diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index 7b14621..babbbed 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -632,7 +632,8 @@ namespace build2 size_t i (start), n (t.prerequisite_targets.size ()); for (prerequisite_member p: group_prerequisite_members (act, t)) { - const target*& pt (t.prerequisite_targets[i++]); + const target*& pt (t.prerequisite_targets[i].target); + uintptr_t& pd (t.prerequisite_targets[i++].data); if (pt == nullptr) continue; @@ -778,6 +779,38 @@ namespace build2 m = 2; // Needs verification. } } + else // lib*{} + { + // 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 + // group link-up. + // + bool u; + if ((u = pt->is_a ()) || pt->is_a ()) + { + const variable& var (var_pool["bin.whole"]); // @@ Cache. + + // See the bin module for the lookup semantics discussion. Note + // that the variable is not overridable so we omit find_override() + // calls. + // + //@@ TODO: prerequisite-specific lookup. + // + lookup l (pt->find_original (var, true).first); + if (!l.defined ()) + { + bool g (pt->group != nullptr); + l = bs.find_original (var, + &pt->type (), + &pt->name, + (g ? &pt->group->type () : nullptr), + (g ? &pt->group->name : nullptr)).first; + } + + if (l ? cast (*l) : u) + pd |= lflag_whole; + } + } mark (pt, m); } @@ -875,28 +908,47 @@ namespace build2 void link:: append_libraries (strings& args, - const file& l, bool la, + const file& l, bool la, lflags lf, const scope& bs, action act, linfo li) const { // Note: lack of the "small function object" optimization will really // kill us here since we are called in a loop. // - bool win (tclass == "windows"); - auto imp = [] (const file&, bool la) {return la;}; - auto lib = [&args, win] (const file* f, const string& p, bool) + auto lib = [&args, this] (const file* l, const string& p, lflags f, bool) { - if (f != nullptr) + if (l != nullptr) { // On Windows a shared library is a DLL with the import library as a // first ad hoc group member. MinGW though can link directly to DLLs // (see search_library() for details). // - if (win && f->member != nullptr && f->is_a ()) - f = &f->member->as (); + if (l->member != nullptr && l->is_a () && tclass == "windows") + l = &l->member->as (); + + string p (relative (l->path ()).string ()); - args.push_back (relative (f->path ()).string ()); + if (f & lflag_whole) + { + if (tsys == "win32-msvc") + { + p.insert (0, "/WHOLEARCHIVE:"); // Only available from VC14U2. + } + else if (tsys == "darwin") + { + p.insert (0, "-Wl,-force_load,"); + } + else + { + args.push_back ("-Wl,--whole-archive"); + args.push_back (move (p)); + args.push_back ("-Wl,--no-whole-archive"); + return; + } + } + + args.push_back (move (p)); } else args.push_back (p); @@ -921,30 +973,29 @@ namespace build2 }; process_libraries ( - act, bs, li, sys_lib_dirs, l, la, imp, lib, opt, true); + act, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true); } void link:: hash_libraries (sha256& cs, - const file& l, bool la, + const file& l, bool la, lflags lf, const scope& bs, action act, linfo li) const { - bool win (tclass == "windows"); - auto imp = [] (const file&, bool la) {return la;}; - auto lib = [&cs, win] (const file* f, const string& p, bool) + auto lib = [&cs, this] (const file* l, const string& p, lflags f, bool) { - if (f != nullptr) + if (l != nullptr) { // On Windows a shared library is a DLL with the import library as a // first ad hoc group member. MinGW though can link directly to DLLs // (see search_library() for details). // - if (win && f->member != nullptr && f->is_a ()) - f = &f->member->as (); + if (l->member != nullptr && l->is_a () && tclass == "windows") + l = &l->member->as (); - cs.append (f->path ().string ()); + cs.append (f); + cs.append (l->path ().string ()); } else cs.append (p); @@ -967,7 +1018,7 @@ namespace build2 }; process_libraries ( - act, bs, li, sys_lib_dirs, l, la, imp, lib, opt, true); + act, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true); } void link:: @@ -1013,7 +1064,7 @@ namespace build2 bool for_install; } d {args, for_install}; - auto lib = [&d, this] (const file* l, const string& f, bool sys) + auto lib = [&d, this] (const file* l, const string& f, lflags, bool sys) { // We don't rpath system libraries. Why, you may ask? There are many // good reasons and I have them written on a napkin somewhere... @@ -1068,9 +1119,9 @@ namespace build2 // In case we don't have the "small function object" optimization. // const function impf (imp); - const function libf (lib); + const function libf (lib); - for (const target* pt: t.prerequisite_targets) + for (auto pt: t.prerequisite_targets) { bool a; const file* f; @@ -1090,7 +1141,7 @@ namespace build2 } process_libraries (act, bs, li, sys_lib_dirs, - *f, a, + *f, a, pt.data, impf, libf, nullptr); } } @@ -1421,8 +1472,10 @@ namespace build2 { sha256 cs; - for (const target* pt: t.prerequisite_targets) + for (auto p: t.prerequisite_targets) { + const target* pt (p.target); + // If this is bmi*{}, then obj*{} is its ad hoc member. // if (modules) @@ -1446,7 +1499,7 @@ namespace build2 // and implementation (static), recursively. // if (a || s) - hash_libraries (cs, *f, a, bs, act, li); + hash_libraries (cs, *f, a, p.data, bs, act, li); else cs.append (f->path ().string ()); } @@ -1669,8 +1722,10 @@ namespace build2 // The same logic as during hashing above. // - for (const target* pt: t.prerequisite_targets) + for (auto p: t.prerequisite_targets) { + const target* pt (p.target); + if (modules) { if (pt->is_a () || pt->is_a () || pt->is_a ()) @@ -1692,7 +1747,7 @@ namespace build2 // and implementation (static), recursively. // if (a || s) - append_libraries (sargs, *f, a, bs, act, li); + append_libraries (sargs, *f, a, p.data, bs, act, li); else sargs.push_back (relative (f->path ()).string ()); // string()&& } diff --git a/build2/cc/link.hxx b/build2/cc/link.hxx index 0256774..ed28eca 100644 --- a/build2/cc/link.hxx +++ b/build2/cc/link.hxx @@ -75,12 +75,12 @@ namespace build2 // void append_libraries (strings&, - const file&, bool, + const file&, bool, lflags, const scope&, action, linfo) const; void hash_libraries (sha256&, - const file&, bool, + const file&, bool, lflags, const scope&, action, linfo) const; void diff --git a/build2/cc/types.hxx b/build2/cc/types.hxx index b575dc7..48ffa05 100644 --- a/build2/cc/types.hxx +++ b/build2/cc/types.hxx @@ -91,6 +91,12 @@ namespace build2 otype type; lorder order; }; + + // Prerequisite link flags. + // + using lflags = uintptr_t; // To match prerequisite_target::data. + + const lflags lflag_whole = 0x00000001U; // Link whole liba{}/libux{}. } } diff --git a/build2/cc/utility.cxx b/build2/cc/utility.cxx index fa5061e..7a2b7fe 100644 --- a/build2/cc/utility.cxx +++ b/build2/cc/utility.cxx @@ -43,12 +43,15 @@ namespace build2 { if (const libu* u = x.is_a ()) { - otype ot (li.type); - return search (*u, - ot == otype::e ? libue::static_type : - ot == otype::a ? libua::static_type : - libus::static_type, - u->dir, u->out, u->name); + const target_type& tt (li.type == otype::e ? libue::static_type : + li.type == otype::a ? libua::static_type : + libus::static_type); + + // Called by the compile rule during execute. + // + return phase == run_phase::match + ? search (*u, tt, u->dir, u->out, u->name) + : *search_existing (tt, u->dir, u->out, u->name); } else { diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx index 1fc195a..2f4f31f 100644 --- a/build2/cc/windows-rpath.cxx +++ b/build2/cc/windows-rpath.cxx @@ -58,7 +58,7 @@ namespace build2 // auto imp = [] (const file&, bool) {return true;}; - auto lib = [&r] (const file* l, const string& f, bool sys) + auto lib = [&r] (const file* l, const string& f, lflags, bool sys) { // We don't rpath system libraries. // @@ -102,7 +102,7 @@ namespace build2 r = t; }; - for (const target* pt: t.prerequisite_targets) + for (auto pt: t.prerequisite_targets) { const file* f; const liba* a; @@ -110,7 +110,7 @@ namespace build2 if ((f = a = pt->is_a ()) || (f = pt->is_a ())) process_libraries (act, bs, li, sys_lib_dirs, - *f, a != nullptr, + *f, a != nullptr, pt.data, imp, lib, nullptr, true); } @@ -130,7 +130,7 @@ namespace build2 auto imp = [] (const file&, bool) {return true;}; - auto lib = [&r] (const file* l, const string& f, bool sys) + auto lib = [&r] (const file* l, const string& f, lflags, bool sys) { if (sys) return; @@ -184,7 +184,7 @@ namespace build2 } }; - for (const target* pt: t.prerequisite_targets) + for (auto pt: t.prerequisite_targets) { const file* f; const liba* a; @@ -192,7 +192,7 @@ namespace build2 if ((f = a = pt->is_a ()) || (f = pt->is_a ())) process_libraries (act, bs, li, sys_lib_dirs, - *f, a != nullptr, + *f, a != nullptr, pt.data, imp, lib, nullptr, true); } diff --git a/tests/cc/libu/testscript b/tests/cc/libu/testscript index 454a443..78a3eb3 100644 --- a/tests/cc/libu/testscript +++ b/tests/cc/libu/testscript @@ -7,6 +7,10 @@ test.arguments = config.cxx="$recall($cxx.path)" .include ../../common.test ++cat <+build/bootstrap.build +using test +EOI + +cat <=build/root.build cxx.std = latest @@ -14,6 +18,8 @@ using cxx hxx{*}: extension = hxx cxx{*}: extension = cxx + +exe{*}: test = true EOI # Common source files that are symlinked in the test directories if used. @@ -23,24 +29,40 @@ EOI # define LIBFOO_EXPORT #endif - LIBFOO_EXPORT void f (); + LIBFOO_EXPORT extern int f; EOI +cat <=foo.cxx - void f () {} + #include + int f; + EOI + ++cat <=bar.cxx + #include + struct b { b () {++f;} } b_; EOI +cat <=driver.cxx + #include #include - int main () {f ();} + int main () {assert (f != 0);} + EOI + +: basic +: +ln -s ../foo.hxx ../foo.cxx ../bar.cxx ../driver.cxx ./; +$* test clean <