diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2017-07-28 13:46:26 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2017-07-28 13:46:26 +0200 |
commit | 1c7cbb302b1c6e41eb0c5cecfc655532f1919cba (patch) | |
tree | 84375e8be2bfe00b2387f02cab8dcca396019299 | |
parent | 24402ed431c1780914576f72350f8796308cb59b (diff) |
Implement support for linking whole archive
-rw-r--r-- | build2/algorithm.cxx | 34 | ||||
-rw-r--r-- | build2/algorithm.hxx | 14 | ||||
-rw-r--r-- | build2/algorithm.ixx | 49 | ||||
-rw-r--r-- | build2/bin/init.cxx | 18 | ||||
-rw-r--r-- | build2/cc/common.cxx | 23 | ||||
-rw-r--r-- | build2/cc/common.hxx | 3 | ||||
-rw-r--r-- | build2/cc/compile.cxx | 6 | ||||
-rw-r--r-- | build2/cc/link.cxx | 109 | ||||
-rw-r--r-- | build2/cc/link.hxx | 4 | ||||
-rw-r--r-- | build2/cc/types.hxx | 6 | ||||
-rw-r--r-- | build2/cc/utility.cxx | 15 | ||||
-rw-r--r-- | build2/cc/windows-rpath.cxx | 12 | ||||
-rw-r--r-- | 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<string>& 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<string>& proj = nullopt); + // As above but specify the target type as template argument. // template <typename T> 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<string> (*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<string>& proj) + { + return search_existing ( + prerequisite_key { + proj, + { + &type, + &dir, &out, &name, ext != nullptr ? optional<string> (*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<strings> ("bin.libs.lib"); vp.insert<dir_paths> ("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<bool> ("bin.whole", false, variable_visibility::target); + vp.insert<string> ("bin.lib.prefix"); vp.insert<string> ("bin.lib.suffix"); vp.insert<string> ("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<bool (const file&, bool la)>& proc_impl, // Implementation? const function<void (const file*, // Can be NULL. const string& path, // Library path. + lflags, // Link flags. bool sys)>& proc_lib, // True if system library. const function<void (const file&, const string& type, // cc.type @@ -185,7 +187,7 @@ namespace build2 ? cast_false<bool> (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<liba> ())) || - (a = (f = p->is_a<libux> ())) || - ( f = p->is_a<libs> ())) + if ((a = (f = pt->is_a<liba> ())) || + (a = (f = pt->is_a<libux> ())) || + ( f = pt->is_a<libs> ())) { 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<liba> () || t.is_a<libux> (), + t, t.is_a<liba> () || t.is_a<libux> (), 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<bool (const file&, bool)>&, - const function<void (const file*, const string&, bool)>&, + const function<void (const file*, const string&, lflags, bool)>&, const function<void (const file&, const string&, bool, bool)>&, 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<file> (), a, + pt->as<file> (), 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<file> (), a, + pt->as<file> (), 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<file> (), a, + pt->as<file> (), 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<libux> ()) || pt->is_a<liba> ()) + { + 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<bool> (*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<libs> ()) - f = &f->member->as<file> (); + if (l->member != nullptr && l->is_a<libs> () && tclass == "windows") + l = &l->member->as<file> (); + + 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<libs> ()) - f = &f->member->as<file> (); + if (l->member != nullptr && l->is_a<libs> () && tclass == "windows") + l = &l->member->as<file> (); - 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<bool (const file&, bool)> impf (imp); - const function<void (const file*, const string&, bool)> libf (lib); + const function<void (const file*, const string&, lflags, bool)> 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<bmie> () || pt->is_a<bmia> () || pt->is_a<bmis> ()) @@ -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<libu> ()) { - 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<liba> ()) || (f = pt->is_a<libs> ())) 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<liba> ()) || (f = pt->is_a<libs> ())) 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 <<EOI >+build/bootstrap.build +using test +EOI + +cat <<EOI >=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 <<EOI >=foo.cxx - void f () {} + #include <foo.hxx> + int f; + EOI + ++cat <<EOI >=bar.cxx + #include <foo.hxx> + struct b { b () {++f;} } b_; EOI +cat <<EOI >=driver.cxx + #include <cassert> #include <foo.hxx> - int main () {f ();} + int main () {assert (f != 0);} + EOI + +: basic +: +ln -s ../foo.hxx ../foo.cxx ../bar.cxx ../driver.cxx ./; +$* test clean <<EOI + cc.poptions += "-I$src_base" + exe{foo}: cxx{driver} libu{foo} + libu{foo}: cxx{foo bar} EOI : members : : Test building individual libuX{} members. : -ln -s ../foo.hxx ../foo.cxx ../driver.cxx ./; -$* update clean <<EOI +ln -s ../foo.hxx ../foo.cxx ../bar.cxx ../driver.cxx ./; +$* test clean <<EOI cc.poptions += "-I$src_base" # {exe liba libs}{foo} @@ -50,5 +72,5 @@ $* update clean <<EOI liba{foo}: libua{foo} libs{foo}: libus{foo} - libu{foo}: cxx{foo} + libu{foo}: cxx{foo bar} EOI |