From 32e04ad4b4a8dec07836b7c9fcf90fe72a006990 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sat, 25 Aug 2018 17:40:21 +0200 Subject: Implement missing pieces in utility libraries support In particular, we can now build static libraries out of utility libraries. --- build2/bin/init.cxx | 189 ++++++++++++++++---------------- build2/bin/rule.cxx | 24 +++-- build2/bin/rule.hxx | 12 +++ build2/bin/target.cxx | 108 ++++++++++++++----- build2/bin/target.hxx | 24 ++++- build2/cc/common.cxx | 257 ++++++++++++++++++++++++-------------------- build2/cc/common.hxx | 5 +- build2/cc/install-rule.cxx | 4 +- build2/cc/link-rule.cxx | 245 ++++++++++++++++++++++++++++++++--------- build2/cc/msvc.cxx | 18 +++- build2/cc/pkgconfig.cxx | 4 +- build2/cc/utility.cxx | 31 ++++-- build2/cc/utility.hxx | 2 +- build2/cc/windows-rpath.cxx | 14 ++- 14 files changed, 624 insertions(+), 313 deletions(-) diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index 7e022fda..6d99e26 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -38,7 +38,7 @@ namespace build2 static const strings libs_lib {"shared", "static"}; bool - vars_init (scope& r, + vars_init (scope& rs, scope&, const location&, unique_ptr&, @@ -47,7 +47,7 @@ namespace build2 const variable_map&) { tracer trace ("bin::vars_init"); - l5 ([&]{trace << "for " << r.out_path ();}); + l5 ([&]{trace << "for " << rs.out_path ();}); assert (first); @@ -56,7 +56,7 @@ namespace build2 // Target is a string and not target_triplet because it can be // specified by the user. // - auto& vp (var_pool.rw (r)); + auto& vp (var_pool.rw (rs)); vp.insert ("config.bin.target", true); vp.insert ("config.bin.pattern", true); @@ -91,7 +91,7 @@ namespace build2 // example: // // exe{test}: liba{foo} - // liba{foo}: libu{foo1 foo2} + // liba{foo}: libua{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{}. @@ -121,6 +121,11 @@ namespace build2 tracer trace ("bin::config_init"); l5 ([&]{trace << "for " << bs.out_path ();}); + // We only support root loading (which means there can only be one). + // + if (&rs != &bs) + fail (loc) << "bin.config module must be loaded in project root"; + // Load bin.vars. // if (!cast_false (rs["bin.vars.loaded"])) @@ -149,7 +154,7 @@ namespace build2 // config.bin.lib // { - value& v (bs.assign ("bin.lib")); + value& v (rs.assign ("bin.lib")); if (!v) v = *required (rs, "config.bin.lib", "both").first; } @@ -157,7 +162,7 @@ namespace build2 // config.bin.exe.lib // { - value& v (bs.assign ("bin.exe.lib")); + value& v (rs.assign ("bin.exe.lib")); if (!v) v = *required (rs, "config.bin.exe.lib", exe_lib).first; } @@ -165,7 +170,7 @@ namespace build2 // config.bin.liba.lib // { - value& v (bs.assign ("bin.liba.lib")); + value& v (rs.assign ("bin.liba.lib")); if (!v) v = *required (rs, "config.bin.liba.lib", liba_lib).first; } @@ -173,7 +178,7 @@ namespace build2 // config.bin.libs.lib // { - value& v (bs.assign ("bin.libs.lib")); + value& v (rs.assign ("bin.libs.lib")); if (!v) v = *required (rs, "config.bin.libs.lib", libs_lib).first; } @@ -183,7 +188,7 @@ namespace build2 // This one is optional and we merge it into bin.rpath, if any. // See the cxx module for details on merging. // - bs.assign ("bin.rpath") += cast_null ( + rs.assign ("bin.rpath") += cast_null ( optional (rs, "config.bin.rpath")); // config.bin.{lib,exe}.{prefix,suffix} @@ -196,13 +201,13 @@ namespace build2 lookup p (omitted (rs, "config.bin.prefix").first); lookup s (omitted (rs, "config.bin.suffix").first); - auto set = [&rs, &bs] (const char* bv, const char* cv, lookup l) + auto set = [&rs] (const char* bv, const char* cv, lookup l) { if (lookup o = omitted (rs, cv).first) l = o; if (l) - bs.assign (bv) = *l; + rs.assign (bv) = *l; }; set ("bin.lib.prefix", "config.bin.lib.prefix", p); @@ -364,8 +369,8 @@ namespace build2 // Load bin.config. // - if (!cast_false (bs["bin.config.loaded"])) - load_module (rs, bs, "bin.config", loc, false, hints); + if (!cast_false (rs["bin.config.loaded"])) + load_module (rs, rs, "bin.config", loc, false, hints); // Cache some config values we will be needing below. // @@ -391,6 +396,7 @@ namespace build2 t.insert (); t.insert (); + t.insert (); t.insert (); t.insert (); t.insert (); @@ -466,6 +472,9 @@ namespace build2 r.insert (perform_update_id, "bin.libu", fail_); r.insert (perform_clean_id, "bin.libu", fail_); + r.insert (perform_update_id, "bin.libul", fail_); + r.insert (perform_clean_id, "bin.libul", fail_); + // Similar to alias. // @@ -493,8 +502,8 @@ namespace build2 } bool - ar_config_init (scope& r, - scope& b, + ar_config_init (scope& rs, + scope& bs, const location& loc, unique_ptr&, bool first, @@ -502,18 +511,18 @@ namespace build2 const variable_map& hints) { tracer trace ("bin::ar_config_init"); - l5 ([&]{trace << "for " << b.out_path ();}); + l5 ([&]{trace << "for " << bs.out_path ();}); // Make sure bin.config is loaded. // - if (!cast_false (b["bin.config.loaded"])) - load_module (r, b, "bin.config", loc, false, hints); + if (!cast_false (rs["bin.config.loaded"])) + load_module (rs, bs, "bin.config", loc, false, hints); // Enter configuration variables. // if (first) { - auto& v (var_pool.rw (r)); + auto& v (var_pool.rw (rs)); v.insert ("bin.rc.path"); v.insert ("bin.ranlib.path"); @@ -543,12 +552,12 @@ namespace build2 // Use the target to decide on the default binutils program names. // - const string& tsys (cast (r["bin.target.system"])); + const string& tsys (cast (rs["bin.target.system"])); const char* ar_d (tsys == "win32-msvc" ? "lib" : "ar"); // This can be either a pattern or a fallback search directory. // - const string* pat (cast_null (r["bin.pattern"])); + const string* pat (cast_null (rs["bin.pattern"])); bool fb (pat != nullptr && path::traits::is_separator (pat->back ())); // Don't save the default value to config.build so that if the user @@ -557,7 +566,7 @@ namespace build2 // auto ap ( config::required ( - r, + rs, "config.bin.ar", path (apply_pattern (ar_d, fb ? nullptr : pat)), false, @@ -565,7 +574,7 @@ namespace build2 auto rp ( config::required ( - r, + rs, "config.bin.ranlib", nullptr, false, @@ -585,7 +594,7 @@ namespace build2 diag_record dr (text); { - dr << "bin.ar " << project (r) << '@' << r.out_path () << '\n' + dr << "bin.ar " << project (rs) << '@' << rs.out_path () << '\n' << " ar " << ari.ar_path << '\n' << " id " << ari.ar_id << '\n' << " version " << ari.ar_version.string () << '\n' @@ -614,28 +623,28 @@ namespace build2 } } - r.assign ("bin.ar.path") = move (ari.ar_path); - r.assign ("bin.ar.id") = move (ari.ar_id); - r.assign ("bin.ar.signature") = move (ari.ar_signature); - r.assign ("bin.ar.checksum") = move (ari.ar_checksum); + rs.assign ("bin.ar.path") = move (ari.ar_path); + rs.assign ("bin.ar.id") = move (ari.ar_id); + rs.assign ("bin.ar.signature") = move (ari.ar_signature); + rs.assign ("bin.ar.checksum") = move (ari.ar_checksum); { semantic_version& v (ari.ar_version); - r.assign ("bin.ar.version") = v.string (); - r.assign ("bin.ar.version.major") = v.major; - r.assign ("bin.ar.version.minor") = v.minor; - r.assign ("bin.ar.version.patch") = v.patch; - r.assign ("bin.ar.version.build") = move (v.build); + rs.assign ("bin.ar.version") = v.string (); + rs.assign ("bin.ar.version.major") = v.major; + rs.assign ("bin.ar.version.minor") = v.minor; + rs.assign ("bin.ar.version.patch") = v.patch; + rs.assign ("bin.ar.version.build") = move (v.build); } if (ranlib != nullptr) { - r.assign ("bin.ranlib.path") = move (ari.ranlib_path); - r.assign ("bin.ranlib.id") = move (ari.ranlib_id); - r.assign ("bin.ranlib.signature") = + rs.assign ("bin.ranlib.path") = move (ari.ranlib_path); + rs.assign ("bin.ranlib.id") = move (ari.ranlib_id); + rs.assign ("bin.ranlib.signature") = move (ari.ranlib_signature); - r.assign ("bin.ranlib.checksum") = + rs.assign ("bin.ranlib.checksum") = move (ari.ranlib_checksum); } } @@ -644,8 +653,8 @@ namespace build2 } bool - ar_init (scope& r, - scope& b, + ar_init (scope& rs, + scope& bs, const location& loc, unique_ptr&, bool, @@ -653,22 +662,22 @@ namespace build2 const variable_map& hints) { tracer trace ("bin::ar_init"); - l5 ([&]{trace << "for " << b.out_path ();}); + l5 ([&]{trace << "for " << bs.out_path ();}); // Make sure the bin core and ar.config are loaded. // - if (!cast_false (b["bin.loaded"])) - load_module (r, b, "bin", loc, false, hints); + if (!cast_false (bs["bin.loaded"])) + load_module (rs, bs, "bin", loc, false, hints); - if (!cast_false (b["bin.ar.config.loaded"])) - load_module (r, b, "bin.ar.config", loc, false, hints); + if (!cast_false (bs["bin.ar.config.loaded"])) + load_module (rs, bs, "bin.ar.config", loc, false, hints); return true; } bool - ld_config_init (scope& r, - scope& b, + ld_config_init (scope& rs, + scope& bs, const location& loc, unique_ptr&, bool first, @@ -676,18 +685,18 @@ namespace build2 const variable_map& hints) { tracer trace ("bin::ld_config_init"); - l5 ([&]{trace << "for " << b.out_path ();}); + l5 ([&]{trace << "for " << bs.out_path ();}); // Make sure bin.config is loaded. // - if (!cast_false (b["bin.config.loaded"])) - load_module (r, b, "bin.config", loc, false, hints); + if (!cast_false (rs["bin.config.loaded"])) + load_module (rs, rs, "bin.config", loc, false, hints); // Enter configuration variables. // if (first) { - auto& v (var_pool.rw (r)); + auto& v (var_pool.rw (rs)); v.insert ("bin.ld.path"); v.insert ("config.bin.ld", true); @@ -701,17 +710,17 @@ namespace build2 // // Use the target to decide on the default ld name. // - const string& tsys (cast (r["bin.target.system"])); + const string& tsys (cast (rs["bin.target.system"])); const char* ld_d (tsys == "win32-msvc" ? "link" : "ld"); // This can be either a pattern or a fallback search directory. // - const string* pat (cast_null (r["bin.pattern"])); + const string* pat (cast_null (rs["bin.pattern"])); bool fb (pat != nullptr && path::traits::is_separator (pat->back ())); auto p ( config::required ( - r, + rs, "config.bin.ld", path (apply_pattern (ld_d, fb ? nullptr : pat)), false, @@ -725,25 +734,25 @@ namespace build2 // if (verb >= (p.second ? 2 : 3)) { - text << "bin.ld " << project (r) << '@' << r.out_path () << '\n' + text << "bin.ld " << project (rs) << '@' << rs.out_path () << '\n' << " ld " << ldi.path << '\n' << " id " << ldi.id << '\n' << " signature " << ldi.signature << '\n' << " checksum " << ldi.checksum; } - r.assign ("bin.ld.path") = move (ldi.path); - r.assign ("bin.ld.id") = move (ldi.id); - r.assign ("bin.ld.signature") = move (ldi.signature); - r.assign ("bin.ld.checksum") = move (ldi.checksum); + rs.assign ("bin.ld.path") = move (ldi.path); + rs.assign ("bin.ld.id") = move (ldi.id); + rs.assign ("bin.ld.signature") = move (ldi.signature); + rs.assign ("bin.ld.checksum") = move (ldi.checksum); } return true; } bool - ld_init (scope& r, - scope& b, + ld_init (scope& rs, + scope& bs, const location& loc, unique_ptr&, bool, @@ -751,17 +760,17 @@ namespace build2 const variable_map& hints) { tracer trace ("bin::ld_init"); - l5 ([&]{trace << "for " << b.out_path ();}); + l5 ([&]{trace << "for " << bs.out_path ();}); // Make sure the bin core and ld.config are loaded. // - if (!cast_false (b["bin.loaded"])) - load_module (r, b, "bin", loc, false, hints); + if (!cast_false (bs["bin.loaded"])) + load_module (rs, bs, "bin", loc, false, hints); - if (!cast_false (b["bin.ld.config.loaded"])) - load_module (r, b, "bin.ld.config", loc, false, hints); + if (!cast_false (bs["bin.ld.config.loaded"])) + load_module (rs, bs, "bin.ld.config", loc, false, hints); - const string& lid (cast (r["bin.ld.id"])); + const string& lid (cast (rs["bin.ld.id"])); // Register the pdb{} target if using the VC toolchain. // @@ -769,17 +778,17 @@ namespace build2 if (lid == "msvc") { - const target_type& pdb (b.derive_target_type ("pdb").first); - install_path (b, pdb, dir_path ("bin")); // Goes to install.bin - install_mode (b, pdb, "644"); // But not executable. + const target_type& pdb (bs.derive_target_type ("pdb").first); + install_path (bs, pdb, dir_path ("bin")); // Goes to install.bin + install_mode (bs, pdb, "644"); // But not executable. } return true; } bool - rc_config_init (scope& r, - scope& b, + rc_config_init (scope& rs, + scope& bs, const location& loc, unique_ptr&, bool first, @@ -787,18 +796,18 @@ namespace build2 const variable_map& hints) { tracer trace ("bin::rc_config_init"); - l5 ([&]{trace << "for " << b.out_path ();}); + l5 ([&]{trace << "for " << bs.out_path ();}); // Make sure bin.config is loaded. // - if (!cast_false (b["bin.config.loaded"])) - load_module (r, b, "bin.config", loc, false, hints); + if (!cast_false (bs["bin.config.loaded"])) + load_module (rs, bs, "bin.config", loc, false, hints); // Enter configuration variables. // if (first) { - auto& v (var_pool.rw (r)); + auto& v (var_pool.rw (rs)); v.insert ("bin.rc.path"); v.insert ("config.bin.rc", true); @@ -812,17 +821,17 @@ namespace build2 // // Use the target to decide on the default rc name. // - const string& tsys (cast (r["bin.target.system"])); + const string& tsys (cast (rs["bin.target.system"])); const char* rc_d (tsys == "win32-msvc" ? "rc" : "windres"); // This can be either a pattern or a fallback search directory. // - const string* pat (cast_null (r["bin.pattern"])); + const string* pat (cast_null (rs["bin.pattern"])); bool fb (pat != nullptr && path::traits::is_separator (pat->back ())); auto p ( config::required ( - r, + rs, "config.bin.rc", path (apply_pattern (rc_d, fb ? nullptr : pat)), false, @@ -836,25 +845,25 @@ namespace build2 // if (verb >= (p.second ? 2 : 3)) { - text << "bin.rc " << project (r) << '@' << r.out_path () << '\n' + text << "bin.rc " << project (rs) << '@' << rs.out_path () << '\n' << " rc " << rci.path << '\n' << " id " << rci.id << '\n' << " signature " << rci.signature << '\n' << " checksum " << rci.checksum; } - r.assign ("bin.rc.path") = move (rci.path); - r.assign ("bin.rc.id") = move (rci.id); - r.assign ("bin.rc.signature") = move (rci.signature); - r.assign ("bin.rc.checksum") = move (rci.checksum); + rs.assign ("bin.rc.path") = move (rci.path); + rs.assign ("bin.rc.id") = move (rci.id); + rs.assign ("bin.rc.signature") = move (rci.signature); + rs.assign ("bin.rc.checksum") = move (rci.checksum); } return true; } bool - rc_init (scope& r, - scope& b, + rc_init (scope& rs, + scope& bs, const location& loc, unique_ptr&, bool, @@ -862,15 +871,15 @@ namespace build2 const variable_map& hints) { tracer trace ("bin::rc_init"); - l5 ([&]{trace << "for " << b.out_path ();}); + l5 ([&]{trace << "for " << bs.out_path ();}); // Make sure the bin core and rc.config are loaded. // - if (!cast_false (b["bin.loaded"])) - load_module (r, b, "bin", loc, false, hints); + if (!cast_false (bs["bin.loaded"])) + load_module (rs, bs, "bin", loc, false, hints); - if (!cast_false (b["bin.rc.config.loaded"])) - load_module (r, b, "bin.rc.config", loc, false, hints); + if (!cast_false (bs["bin.rc.config.loaded"])) + load_module (rs, bs, "bin.rc.config", loc, false, hints); return true; } diff --git a/build2/bin/rule.cxx b/build2/bin/rule.cxx index 9427697..b609c9a 100644 --- a/build2/bin/rule.cxx +++ b/build2/bin/rule.cxx @@ -37,15 +37,10 @@ namespace build2 // The whole logic is pretty much as if we had our two group members as // our prerequisites. // - bool lib_rule:: - match (action, target& xt, const string&) const + lib_rule::members lib_rule:: + build_members (const scope& rs) { - lib& t (xt.as ()); - - // Get the library type to build. If not set for a target, this should - // be configured at the project scope by init(). - // - const string& type (cast (t["bin.lib"])); + const string& type (cast (rs["bin.lib"])); bool a (type == "static" || type == "both"); bool s (type == "shared" || type == "both"); @@ -54,8 +49,17 @@ namespace build2 fail << "unknown library type: " << type << info << "'static', 'shared', or 'both' expected"; - t.a = a ? &search (t, t.dir, t.out, t.name) : nullptr; - t.s = s ? &search (t, t.dir, t.out, t.name) : nullptr; + return members {a, s}; + } + + bool lib_rule:: + match (action, target& xt, const string&) const + { + lib& t (xt.as ()); + + members bm (build_members (t.root_scope ())); + t.a = bm.a ? &search (t, t.dir, t.out, t.name) : nullptr; + t.s = bm.s ? &search (t, t.dir, t.out, t.name) : nullptr; return true; } diff --git a/build2/bin/rule.hxx b/build2/bin/rule.hxx index d656f62..ab8d64b 100644 --- a/build2/bin/rule.hxx +++ b/build2/bin/rule.hxx @@ -44,6 +44,18 @@ namespace build2 static target_state perform (action, const target&); + + // Return library types to build according to the bin.lib value (set + // on project's root scope by init()). + // + struct members + { + bool a; // static + bool s; // shared + }; + + static members + build_members (const scope&); }; } } diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx index 0560b8e..6c62258 100644 --- a/build2/bin/target.cxx +++ b/build2/bin/target.cxx @@ -71,7 +71,7 @@ namespace build2 // running serial. For the members it is also safe to set the group during // creation. - // obj*{}, bmi*{}, libu*{} member factory. + // obj*{} and bmi*{} member factory. // template static target* @@ -111,11 +111,11 @@ namespace build2 false }; - const target_type libue::static_type + const target_type obja::static_type { - "libue", - &libux::static_type, - &m_factory, + "obja", + &objx::static_type, + &m_factory, nullptr, /* fixed_extension */ &target_extension_var, &target_pattern_var, @@ -124,11 +124,11 @@ namespace build2 false }; - const target_type obja::static_type + const target_type bmia::static_type { - "obja", - &objx::static_type, - &m_factory, + "bmia", + &bmix::static_type, + &m_factory, nullptr, /* fixed_extension */ &target_extension_var, &target_pattern_var, @@ -137,11 +137,11 @@ namespace build2 false }; - const target_type bmia::static_type + const target_type objs::static_type { - "bmia", - &bmix::static_type, - &m_factory, + "objs", + &objx::static_type, + &m_factory, nullptr, /* fixed_extension */ &target_extension_var, &target_pattern_var, @@ -150,11 +150,11 @@ namespace build2 false }; - const target_type libua::static_type + const target_type bmis::static_type { - "libua", - &libux::static_type, - &m_factory, + "bmis", + &bmix::static_type, + &m_factory, nullptr, /* fixed_extension */ &target_extension_var, &target_pattern_var, @@ -163,11 +163,33 @@ namespace build2 false }; - const target_type objs::static_type + // libu*{} member factory. + // + template + static target* + libux_factory (const target_type&, dir_path dir, dir_path out, string n) { - "objs", - &objx::static_type, - &m_factory, + const target* g (targets.find (dir, out, n)); + + if (const target* g2 = targets.find (dir, out, n)) + { + if (g != 0) + fail << "both " << *g << " and " << g2 << " targets declared"; + + g = g2; + } + + M* m (new M (move (dir), move (out), move (n))); + m->group = g; + + return m; + } + + const target_type libue::static_type + { + "libue", + &libux::static_type, + &libux_factory, nullptr, /* fixed_extension */ &target_extension_var, &target_pattern_var, @@ -176,11 +198,12 @@ namespace build2 false }; - const target_type bmis::static_type + + const target_type libua::static_type { - "bmis", - &bmix::static_type, - &m_factory, + "libua", + &libux::static_type, + &libux_factory, nullptr, /* fixed_extension */ &target_extension_var, &target_pattern_var, @@ -193,7 +216,7 @@ namespace build2 { "libus", &libux::static_type, - &m_factory, + &libux_factory, nullptr, /* fixed_extension */ &target_extension_var, &target_pattern_var, @@ -255,6 +278,39 @@ namespace build2 false }; + // The same as g_factory() but without E. + // + static target* + libul_factory (const target_type&, dir_path dir, dir_path out, string n) + { + libua* a (phase == run_phase::load + ? const_cast (targets.find (dir, out, n)) + : nullptr); + libus* s (phase == run_phase::load + ? const_cast (targets.find (dir, out, n)) + : nullptr); + + libul* g (new libul (move (dir), move (out), move (n))); + + if (a != nullptr) a->group = g; + if (s != nullptr) s->group = g; + + return g; + } + + const target_type libul::static_type + { + "libul", + &libx::static_type, + &libul_factory, + nullptr, + nullptr, + nullptr, + nullptr, + &target_search, + false + }; + const target_type libu::static_type { "libu", diff --git a/build2/bin/target.hxx b/build2/bin/target.hxx index f0b427f..0bcdf90 100644 --- a/build2/bin/target.hxx +++ b/build2/bin/target.hxx @@ -135,7 +135,7 @@ namespace build2 virtual const target_type& dynamic_type () const {return static_type;} }; - // Common base for lib{} and libu{} groups. + // Common base for lib{} and libul{}/libu{} groups. // // We use mtime_target as a base for the "trust me it exists" functionality // which we use, for example, to have installed lib{} prerequisites that @@ -150,13 +150,21 @@ namespace build2 static const target_type static_type; }; - // The libu{} target group (utility library). + // The libul{}/libu{} target groups (utility library). // - // All the members are static libraries that differ base on the kind of + // All the members are static libraries that differ based on the kind of // object files they contains. Note that the group is more like obj{} // rather than lib{} in that one does not build the group directly rather // picking a suitable member. // + // libul{} is a "library utility library" in that the choice of members is + // libua{} or libus{}, even when linking an executable (normally a unit + // test). + // + // libu{} is a general utility library with all three types of members. It + // would normally be used when you want to build both a library from + // libua{}/libus{} and an executable from libue{}. + // class libux: public file // Common base of all libuX{} static libraries. { public: @@ -196,6 +204,16 @@ namespace build2 virtual const target_type& dynamic_type () const {return static_type;} }; + class libul: public libx + { + public: + using libx::libx; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + class libu: public libx { public: diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx index c6ef3bd..23c37ff 100644 --- a/build2/cc/common.cxx +++ b/build2/cc/common.cxx @@ -41,9 +41,14 @@ namespace build2 // // 1. options // 2. lib itself (if self is true) - // 3. dependency libs (prerequisite_targets) + // 3. dependency libs (prerequisite_targets, left to right, depth-first) // 4. dependency libs (*.libs variables). // + // The first argument to proc_lib is a pointer to the last element of an + // array that contains the current library dependency chain all the way to + // the library passes to process_libraries(). The first element of this + // array is NULL. + // void common:: process_libraries ( action a, @@ -55,7 +60,7 @@ namespace build2 lflags lf, const function& proc_impl, // Implementation? - const function& proc_lib, // True if system library. @@ -63,8 +68,16 @@ namespace build2 const string& type, // cc.type bool com, // cc. or x. bool exp)>& proc_opt, // *.export. - bool self /*= false*/) const // Call proc_lib on l? + bool self /*= false*/, // Call proc_lib on l? + small_vector* chain) const { + small_vector chain_storage; + if (chain == nullptr) + { + chain = &chain_storage; + chain->push_back (nullptr); + } + // See what type of library this is (C, C++, etc). Use it do decide // which x.libs variable name to use. If it's unknown, then we only // look into prerequisites. @@ -177,6 +190,8 @@ namespace build2 // if (self && proc_lib) { + chain->push_back (&l); + // Note that while normally the path is assigned, in case of an import // stub the path to the DLL may not be known and so the path will be // empty (but proc_lib() will use the import stub). @@ -187,7 +202,7 @@ namespace build2 ? cast_false (l.vars[c_system]) : !p.empty () && sys (top_sysd, p.string ())); - proc_lib (&l, p.string (), lf, s); + proc_lib (&chain->back (), p.string (), lf, s); } const scope& bs (t == nullptr || cc ? top_bs : l.base_scope ()); @@ -242,7 +257,7 @@ namespace build2 process_libraries (a, bs, *li, *sysd, *f, la, pt.data, - proc_impl, proc_lib, proc_opt, true); + proc_impl, proc_lib, proc_opt, true, chain); } } } @@ -251,148 +266,154 @@ namespace build2 // handling import, etc. // // If it is not a C-common library, then it probably doesn't have any of - // the *.libs and we are done. - // - if (t == nullptr) - return; - - optional usrd; // Extract lazily. - - // Determine if a "simple path" is a system library. + // the *.libs. // - auto sys_simple = [&sysd, &sys, &find_sysd] (const string& p) -> bool + if (t != nullptr) { - bool s (!path::traits::absolute (p)); + optional usrd; // Extract lazily. - if (!s) + // Determine if a "simple path" is a system library. + // + auto sys_simple = [&sysd, &sys, &find_sysd] (const string& p) -> bool { - if (sysd == nullptr) find_sysd (); + bool s (!path::traits::absolute (p)); - s = sys (*sysd, p); - } + if (!s) + { + if (sysd == nullptr) find_sysd (); - return s; - }; + s = sys (*sysd, p); + } - auto proc_int = [&l, - &proc_impl, &proc_lib, &proc_opt, - &sysd, &usrd, - &find_sysd, &find_linfo, &sys_simple, - &bs, a, &li, this] (const lookup& lu) - { - const vector* ns (cast_null> (lu)); - if (ns == nullptr || ns->empty ()) - return; + return s; + }; - for (const name& n: *ns) + auto proc_int = [&l, + &proc_impl, &proc_lib, &proc_opt, chain, + &sysd, &usrd, + &find_sysd, &find_linfo, &sys_simple, + &bs, a, &li, this] (const lookup& lu) { - if (n.simple ()) - { - // This is something like -lpthread or shell32.lib so should be a - // valid path. But it can also be an absolute library path (e.g., - // something that in the future will come from our -static/-shared - // .pc files. - // - if (proc_lib) - proc_lib (nullptr, n.value, 0, sys_simple (n.value)); - } - else + const vector* ns (cast_null> (lu)); + if (ns == nullptr || ns->empty ()) + return; + + for (const name& n: *ns) { - // This is a potentially project-qualified target. - // - if (sysd == nullptr) find_sysd (); - if (!li) find_linfo (); + if (n.simple ()) + { + // This is something like -lpthread or shell32.lib so should be + // a valid path. But it can also be an absolute library path + // (e.g., something that in the future will come from our + // -static/-shared .pc files. + // + if (proc_lib) + proc_lib (nullptr, n.value, 0, sys_simple (n.value)); + } + else + { + // This is a potentially project-qualified target. + // + if (sysd == nullptr) find_sysd (); + if (!li) find_linfo (); - const file& t (resolve_library (a, bs, n, *li, *sysd, usrd)); + const file& t (resolve_library (a, bs, n, *li, *sysd, usrd)); - if (proc_lib) - { - // This can happen if the target is mentioned in *.export.libs - // (i.e., it is an interface dependency) but not in the - // library's prerequisites (i.e., it is not an implementation - // dependency). + if (proc_lib) + { + // This can happen if the target is mentioned in *.export.libs + // (i.e., it is an interface dependency) but not in the + // library's prerequisites (i.e., it is not an implementation + // dependency). + // + // Note that we used to just check for path being assigned but + // on Windows import-installed DLLs may legally have empty + // paths. + // + if (t.mtime () == timestamp_unknown) + fail << "interface dependency " << t << " is out of date" << + info << "mentioned in *.export.libs of target " << l << + info << "is it a prerequisite of " << l << "?"; + } + + // Process it recursively. // - // Note that we used to just check for path being assigned but - // on Windows import-installed DLLs may legally have empty - // paths. + // @@ Where can we get the link flags? Should we try to find + // them in the library's prerequisites? What about installed + // stuff? // - if (t.mtime () == timestamp_unknown) - fail << "interface dependency " << t << " is out of date" << - info << "mentioned in *.export.libs of target " << l << - info << "is it a prerequisite of " << l << "?"; + process_libraries (a, bs, *li, *sysd, + t, t.is_a () || t.is_a (), 0, + proc_impl, proc_lib, proc_opt, true, chain); } + } + }; - // 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 from *.libs (of type strings). + // + auto proc_imp = [&proc_lib, &sys_simple] (const lookup& lu) + { + const strings* ns (cast_null (lu)); + if (ns == nullptr || ns->empty ()) + return; + + for (const string& n: *ns) + { + // This is something like -lpthread or shell32.lib so should be a + // valid path. // - process_libraries (a, bs, *li, *sysd, - t, t.is_a () || t.is_a (), 0, - proc_impl, proc_lib, proc_opt, true); + proc_lib (nullptr, n, 0, sys_simple (n)); } - } - }; - - // Process libraries from *.libs (of type strings). - // - auto proc_imp = [&proc_lib, &sys_simple] (const lookup& lu) - { - const strings* ns (cast_null (lu)); - if (ns == nullptr || ns->empty ()) - return; + }; - for (const string& n: *ns) + // Note: the same structure as when processing options above. + // + // If all we know is it's a C-common library, then in both cases we + // only look for cc.export.libs. + // + if (cc) { - // This is something like -lpthread or shell32.lib so should be a - // valid path. - // - proc_lib (nullptr, n, 0, sys_simple (n)); + if (c_e_libs) proc_int (c_e_libs); } - }; - - // Note: the same structure as when processing options above. - // - // If all we know is it's a C-common library, then in both cases we only - // look for cc.export.libs. - // - if (cc) - { - if (c_e_libs) proc_int (c_e_libs); - } - else - { - if (impl) + else { - // Interface and implementation: as discussed above, we can have two - // situations: overriden export or default export. - // - if (c_e_libs.defined () || x_e_libs.defined ()) + if (impl) { - if (c_e_libs) proc_int (c_e_libs); - if (x_e_libs) proc_int (x_e_libs); + // Interface and implementation: as discussed above, we can have + // two situations: overriden export or default export. + // + if (c_e_libs.defined () || x_e_libs.defined ()) + { + if (c_e_libs) proc_int (c_e_libs); + if (x_e_libs) proc_int (x_e_libs); + } + else + { + // For default export we use the same options/libs as were used + // to build the library. Since libraries in (non-export) *.libs + // are not targets, we don't need to recurse. + // + if (proc_lib) + { + proc_imp (l[c_libs]); + proc_imp (l[same ? x_libs : vp[*t + ".libs"]]); + } + } } else { - // For default export we use the same options/libs as were used to - // build the library. Since libraries in (non-export) *.libs are - // not targets, we don't need to recurse. + // Interface: only add *.export.* (interface dependencies). // - if (proc_lib) - { - proc_imp (l[c_libs]); - proc_imp (l[same ? x_libs : vp[*t + ".libs"]]); - } + if (c_e_libs) proc_int (c_e_libs); + if (x_e_libs) proc_int (x_e_libs); } } - else - { - // Interface: only add *.export.* (interface dependencies). - // - if (c_e_libs) proc_int (c_e_libs); - if (x_e_libs) proc_int (x_e_libs); - } } + + // Remove this library from the chain. + // + if (self && proc_lib) + chain->pop_back (); } // The name can be an absolute target name (e.g., /tmp/libfoo/lib{foo}) or @@ -456,7 +477,7 @@ namespace build2 fail << "unable to find library " << pk; } - // If this is lib{}/libu{}, pick appropriate member. + // If this is lib{}/libu*{}, pick appropriate member. // if (const libx* l = xt->is_a ()) xt = &link_member (*l, a, li); // Pick lib*{e,a,s}{}. diff --git a/build2/cc/common.hxx b/build2/cc/common.hxx index 18824cb..d809050 100644 --- a/build2/cc/common.hxx +++ b/build2/cc/common.hxx @@ -220,9 +220,10 @@ namespace build2 bool, lflags, const function&, - const function&, + const function&, const function&, - bool = false) const; + bool = false, + small_vector* = nullptr) const; const target* search_library (action a, diff --git a/build2/cc/install-rule.cxx b/build2/cc/install-rule.cxx index ee290e3..9e6b93f 100644 --- a/build2/cc/install-rule.cxx +++ b/build2/cc/install-rule.cxx @@ -53,8 +53,8 @@ namespace build2 { const target* pt (&search (t, p)); - // If this is the lib{}/libu{} group, pick a member which we would - // link. For libu{} we want the "see through" logic. + // If this is the lib{}/libu*{} group, pick a member which we would + // link. For libu*{} we want the "see through" logic. // if (const libx* l = pt->is_a ()) pt = &link_member (*l, a, link_info (t.base_scope (), ot)); diff --git a/build2/cc/link-rule.cxx b/build2/cc/link-rule.cxx index 0bfef09..5ebb97a 100644 --- a/build2/cc/link-rule.cxx +++ b/build2/cc/link-rule.cxx @@ -130,14 +130,19 @@ namespace build2 } if (!(seen_x || seen_c || seen_obj || seen_lib)) + { + l4 ([&]{trace << "no " << x_lang << ", C, or obj/lib prerequisite " + << "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 (seen_c && !seen_x && hint < x) { - l4 ([&]{trace << "C prerequisite without " << x_lang << " or hint";}); + l4 ([&]{trace << "C prerequisite without " << x_lang << " or hint " + << "for target " << t;}); return false; } @@ -1070,47 +1075,126 @@ namespace build2 const file& l, bool la, lflags lf, const scope& bs, action a, linfo li) const { - // Note: lack of the "small function object" optimization will really - // kill us here since we are called in a loop. - // + struct data + { + strings& args; + const file& l; + action a; + linfo li; + } d {args, l, a, li}; + auto imp = [] (const file&, bool la) {return la;}; - auto lib = [&args, this] (const file* l, const string& p, lflags f, bool) + auto lib = [&d, this] (const file* const* lc, + const string& p, + lflags f, + bool) { - if (l != nullptr) + const file* l (lc != nullptr ? *lc : 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 (l->member != nullptr && l->is_a () && tclass == "windows") - l = &l->member->as (); + d.args.push_back (p); + } + else + { + bool lu (l->is_a ()); - string p (relative (l->path ()).string ()); + // The utility/non-utility case is tricky. Consider these two + // scenarios: + // + // exe -> (libu1-e -> libu1-e) -> (liba) -> libu-a -> (liba1) + // exe -> (liba) -> libu1-a -> libu1-a -> (liba1) -> libu-a1 + // + // Libraries that should be linked are in '()'. That is, we need to + // link the initial sequence of utility libraries and then, after + // encountering a first non-utility, only link non-utilities + // (because they already contain their utility's object files). + // + if (lu) + { + for (ptrdiff_t i (-1); lc[i] != nullptr; --i) + if (!lc[i]->is_a ()) + return; + } - if (f & lflag_whole) + if (d.li.type == otype::a) { - if (tsys == "win32-msvc") - { - p.insert (0, "/WHOLEARCHIVE:"); // Only available from VC14U2. - } - else if (tsys == "darwin") + // Linking a utility library to a static library. + // + // Note that utility library prerequisites of utility libraries + // are automatically handled by process_libraries(). So all we + // have to do is implement the "thin archive" logic. + // + // 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. + // + if (!lu) + return; + + for (const target* pt: l->prerequisite_targets[d.a]) { - p.insert (0, "-Wl,-force_load,"); + if (pt == nullptr) + continue; + + if (modules) + { + if (pt->is_a ()) + pt = pt->member; + } + + // We could have dependency diamonds with utility libraries. + // Repeats will be handled by the linker (in fact, it could be + // required to repeat them to satisfy all the symbols) but here + // we have to suppress duplicates ourselves. + // + if (const file* f = pt->is_a ()) + { + string p (relative (f->path ()).string ()); + if (find (d.args.begin (), d.args.end (), p) == d.args.end ()) + d.args.push_back (move (p)); + } } - else + } + else + { + // Linking a library to a shared library or executable. + // + + // 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 (l->member != nullptr && + l->is_a () && + tclass == "windows") + l = &l->member->as (); + + string p (relative (l->path ()).string ()); + + if (f & lflag_whole) { - args.push_back ("-Wl,--whole-archive"); - args.push_back (move (p)); - args.push_back ("-Wl,--no-whole-archive"); - return; + if (tsys == "win32-msvc") + { + p.insert (0, "/WHOLEARCHIVE:"); // Only available from VC14U2. + } + else if (tsys == "darwin") + { + p.insert (0, "-Wl,-force_load,"); + } + else + { + d.args.push_back ("-Wl,--whole-archive"); + d.args.push_back (move (p)); + d.args.push_back ("-Wl,--no-whole-archive"); + return; + } } - } - args.push_back (move (p)); + d.args.push_back (move (p)); + } } - else - args.push_back (p); }; auto opt = [&args, this] ( @@ -1149,12 +1233,43 @@ namespace build2 const dir_path& out_root; bool& update; timestamp mt; - } d {cs, bs.root_scope ()->out_path (), update, mt}; + linfo li; + } d {cs, bs.root_scope ()->out_path (), update, mt, li}; - auto lib = [&d, this] (const file* l, const string& p, lflags f, bool) + auto lib = [&d, this] (const file* const* lc, + const string& p, + lflags f, + bool) { - if (l != nullptr) + const file* l (lc != nullptr ? *lc : nullptr); + + if (l == nullptr) { + d.cs.append (p); + } + else + { + bool lu (l->is_a ()); + + if (lu) + { + for (ptrdiff_t i (-1); lc[i] != nullptr; --i) + if (!lc[i]->is_a ()) + 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; + // Check if this library renders us out of date. // d.update = d.update || l->newer (d.mt); @@ -1169,8 +1284,6 @@ namespace build2 d.cs.append (f); hash_path (d.cs, l->path (), d.out_root); } - else - d.cs.append (p); }; auto opt = [&cs, this] ( @@ -1238,8 +1351,13 @@ namespace build2 bool for_install; } d {args, for_install}; - auto lib = [&d, this] (const file* l, const string& f, lflags, bool sys) + auto lib = [&d, this] (const file* const* lc, + const string& f, + lflags, + bool sys) { + const file* l (lc != nullptr ? *lc : 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... // @@ -1293,7 +1411,8 @@ namespace build2 // In case we don't have the "small function object" optimization. // const function impf (imp); - const function libf (lib); + const function< + void (const file* const*, const string&, lflags, bool)> libf (lib); for (const prerequisite_target& pt: t.prerequisite_targets[a]) { @@ -1544,7 +1663,10 @@ namespace build2 if (lt.static_library ()) { - if (tsys == "win32-msvc") ; + if (tsys == "win32-msvc") + { + // No options for lib.exe. + } else { // If the user asked for ranlib, don't try to do its function with @@ -1700,7 +1822,7 @@ namespace build2 // pinpoint exactly what is causing the update. On the other hand, the // checksum is faster and simpler. And we like simple. // - const file* def (nullptr); // Cache if present. + const file* def (nullptr); // Cached if present. { sha256 cs; @@ -1722,10 +1844,16 @@ namespace build2 const file* f; bool la (false), ls (false); - if ((f = pt->is_a ()) || - (!lt.static_library () && // @@ UTL: TODO libua to liba link. + // We link utility libraries to everything except other utility + // libraries. In case of linking to liba{} we follow the "thin + // archive" lead and "see through" to their object file + // prerequisites (recursively, until we encounter a non-utility). + // + if ((f = pt->is_a ()) || + (!lt.utility && + (la = (f = pt->is_a ()))) || + (!lt.static_library () && ((la = (f = pt->is_a ())) || - (la = (f = pt->is_a ())) || (ls = (f = pt->is_a ()))))) { // Link all the dependent interface libraries (shared) or interface @@ -1774,7 +1902,7 @@ namespace build2 if (!manifest.empty ()) hash_path (cs, manifest, rs.out_path ()); - // Treat .libs as inputs, not options. + // Treat *.libs variable values as inputs, not options. // if (!lt.static_library ()) { @@ -2020,6 +2148,8 @@ namespace build2 // The same logic as during hashing above. // + // See also a similar loop inside append_libraries(). + // for (const prerequisite_target& p: t.prerequisite_targets[a]) { const target* pt (p.target); @@ -2036,15 +2166,13 @@ namespace build2 const file* f; bool la (false), ls (false); - if ((f = pt->is_a ()) || - (!lt.static_library () && // @@ UTL: TODO libua to liba link. + if ((f = pt->is_a ()) || + (!lt.utility && + (la = (f = pt->is_a ()))) || + (!lt.static_library () && ((la = (f = pt->is_a ())) || - (la = (f = pt->is_a ())) || (ls = (f = pt->is_a ()))))) { - // Link all the dependent interface libraries (shared) or interface - // and implementation (static), recursively. - // if (la || ls) append_libraries (sargs, *f, la, p.data, bs, a, li); else @@ -2199,6 +2327,27 @@ namespace build2 throw failed (); } + // VC link.exe creates an import library and .exp file for an executable + // if any of its object files export any symbols (think a unit test + // linking libus{}). And, no, there is no way to suppress it. Well, + // there is a way: create a .def file with an empty EXPORTS section, + // pass it to lib.exe to create a dummy .exp (and .lib), and then pass + // this empty .exp to link.exe. Wanna go this way? Didn't think so. + // Having no way to disable this, the next simplest thing seems to be + // just cleaning the mess up. + // + // Note also that if at some point we decide to support such "shared + // executables" (-rdynamic, etc), then it will probably have to be a + // different target type (exes{}?) since it will need a different set + // of object files (-fPIC so probably objs{}), etc. + // + if (lt.executable () && tsys == "win32-msvc") + { + path b (relt.base ()); + try_rmfile (b + ".lib", true /* ignore_errors */); + try_rmfile (b + ".exp", true /* ignore_errors */); + } + if (ranlib) { const process_path& rl (cast (ranlib)); diff --git a/build2/cc/msvc.cxx b/build2/cc/msvc.cxx index 4346872..9eb3de0 100644 --- a/build2/cc/msvc.cxx +++ b/build2/cc/msvc.cxx @@ -65,12 +65,22 @@ namespace build2 { // " Creating library foo\foo.dll.lib and object foo\foo.dll.exp" // - if (lt == otype::s && l.compare (0, 3, " ") == 0) + // This can also appear when linking executables if any of the object + // files export any symbols. + // + if (l.compare (0, 3, " ") == 0) { - path imp (t.member->as ().path ().leaf ()); + // Use the actual import library name if this is a library (since we + // override this name) and the executable name otherwise (by default + // .lib/.exp are named by replacing the .exe extension). + // + path i ( + lt == otype::s + ? t.member->as ().path ().leaf () + : t.path ().leaf ().base () + ".lib"); - if (l.find (imp.string ()) != string::npos && - l.find (imp.base ().string () + ".exp") != string::npos) + if (l.find (i.string ()) != string::npos && + l.find (i.base ().string () + ".exp") != string::npos) continue; } diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx index 55c8b35..d331f7e 100644 --- a/build2/cc/pkgconfig.cxx +++ b/build2/cc/pkgconfig.cxx @@ -1265,11 +1265,13 @@ namespace build2 bool priv (false); auto imp = [&priv] (const file&, bool la) {return priv && la;}; - auto lib = [&os, &save_library] (const file* l, + auto lib = [&os, &save_library] (const file* const* c, const string& p, lflags, bool) { + const file* l (c != nullptr ? *c : nullptr); + if (l != nullptr) { if (l->is_a () || l->is_a ()) // See through libux. diff --git a/build2/cc/utility.cxx b/build2/cc/utility.cxx index 830a957..080578c 100644 --- a/build2/cc/utility.cxx +++ b/build2/cc/utility.cxx @@ -8,6 +8,7 @@ #include #include // search() +#include #include using namespace std; @@ -45,17 +46,35 @@ namespace build2 const target& link_member (const bin::libx& x, action a, linfo li) { - if (const libu* u = x.is_a ()) + bool ul; + + if ((ul = x.is_a ()) || x.is_a ()) { - const target_type& tt (li.type == otype::e ? libue::static_type : - li.type == otype::a ? libua::static_type : - libus::static_type); + // If this is a libul{} and we are linking an executable, then the + // member choice should be dictated by the members of lib{} this + // libul{} is "primarily" for. If both are being built, then it seems + // natural to prefer static over shared since it could be faster (but + // I am sure someone will probably want this configurable). + // + if (ul && li.type == otype::e) + { + // Utility libraries are project-local which means the primarily + // target should be in the same project as us. + // + li.type = lib_rule::build_members (x.root_scope ()).a + ? otype::a + : otype::s; + } + + const target_type& tt (li.type == otype::a ? libua::static_type : + li.type == otype::s ? libus::static_type : + libue::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); + ? search (x, tt, x.dir, x.out, x.name) + : *search_existing (tt, x.dir, x.out, x.name); } else { diff --git a/build2/cc/utility.hxx b/build2/cc/utility.hxx index 18de360..c378e74 100644 --- a/build2/cc/utility.hxx +++ b/build2/cc/utility.hxx @@ -54,7 +54,7 @@ namespace build2 } // Given the link order return the library member to link. That is, liba{} - // or libs{} for lib{} and libue{}, libua{} or libus{} for libu{}. + // or libs{} for lib{} and libue{}, libua{} or libus{} for libu*{}. // const target& link_member (const bin::libx&, action, linfo); diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx index c710e66..268bab2 100644 --- a/build2/cc/windows-rpath.cxx +++ b/build2/cc/windows-rpath.cxx @@ -59,8 +59,13 @@ namespace build2 // auto imp = [] (const file&, bool) {return true;}; - auto lib = [&r] (const file* l, const string& f, lflags, bool sys) + auto lib = [&r] (const file* const* lc, + const string& f, + lflags, + bool sys) { + const file* l (lc != nullptr ? *lc : nullptr); + // We don't rpath system libraries. // if (sys) @@ -135,8 +140,13 @@ namespace build2 auto imp = [] (const file&, bool) {return true;}; - auto lib = [&r, &bs] (const file* l, const string& f, lflags, bool sys) + auto lib = [&r, &bs] (const file* const* lc, + const string& f, + lflags, + bool sys) { + const file* l (lc != nullptr ? *lc : nullptr); + if (sys) return; -- cgit v1.1