From 3a8972b42f75e10e9a833bba58d65009e7bed7f3 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 24 Aug 2016 15:41:54 +0200 Subject: Handle *.export.libs, distinguish interface and implementation dependencies A library dependency on another libraries is either "interface" or "implementation". If it is interface, then everyone who links to this library should also link to the interface dependency, explicitly. A good example of an interface dependency is a library API that is called in inline functions. Interface dependencies of a library should be explicitly listed in the *.export.libs (where we can also list target names). So the typical usage will be along these lines: import int_libs = libfoo%lib{foo} import int_libs += ... import imp_libs = libbar%lib{bar} import imp_libs += ... lib{baz}: ... $int_libs $imp_libs lib{baz}: cxx.export.libs = $int_libs --- build2/c/init.cxx | 2 +- build2/cc/compile.cxx | 4 +- build2/cc/init.cxx | 2 +- build2/cc/link | 26 ++++- build2/cc/link.cxx | 276 ++++++++++++++++++++++++++++++++++++++++---------- build2/cc/msvc.cxx | 22 ++-- build2/cxx/init.cxx | 2 +- build2/prerequisite | 4 + build2/target-key | 4 + build2/utility | 14 +++ build2/utility.cxx | 19 ++++ build2/utility.ixx | 14 +++ 12 files changed, 321 insertions(+), 68 deletions(-) (limited to 'build2') diff --git a/build2/c/init.cxx b/build2/c/init.cxx index fa17227..4eab8a6 100644 --- a/build2/c/init.cxx +++ b/build2/c/init.cxx @@ -141,7 +141,7 @@ namespace build2 v.insert ("c.export.poptions"), v.insert ("c.export.coptions"), v.insert ("c.export.loptions"), - v.insert ("c.export.libs"), + v.insert ("c.export.libs"), v["cc.export.poptions"], v["cc.export.coptions"], diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx index 58dbe30..56122e8 100644 --- a/build2/cc/compile.cxx +++ b/build2/cc/compile.cxx @@ -882,8 +882,8 @@ namespace build2 else { // Note that we skip any target type-specific searches (like for - // an existing file) and go straight for the target make since we - // need to find the target explicitly spelled out. + // an existing file) and go straight for the target object since + // we need to find the target explicitly spelled out. // auto i (targets.find (*tt, d, out, n, e, trace)); r = i != targets.end () ? i->get () : nullptr; diff --git a/build2/cc/init.cxx b/build2/cc/init.cxx index 474db70..0c82169 100644 --- a/build2/cc/init.cxx +++ b/build2/cc/init.cxx @@ -52,7 +52,7 @@ namespace build2 v.insert ("cc.export.poptions"); v.insert ("cc.export.coptions"); v.insert ("cc.export.loptions"); - v.insert ("cc.export.libs"); + v.insert ("cc.export.libs"); // Hint variables (not overridable). // diff --git a/build2/cc/link b/build2/cc/link index f6a16c0..aaf1cd7 100644 --- a/build2/cc/link +++ b/build2/cc/link @@ -39,6 +39,15 @@ namespace build2 private: friend class compile; + void + append_libraries (strings&, file&, bool) const; + + void + hash_libraries (sha256&, file&, bool) const; + + file& + resolve_library (name, scope&, lorder, optional&) const; + // Extract system library search paths from GCC or compatible (Clang, // Intel) using the -print-search-dirs option. // @@ -58,15 +67,26 @@ namespace build2 bin::liba* msvc_search_static (const process_path&, const dir_path&, - prerequisite&) const; + const prerequisite_key&) const; bin::libs* msvc_search_shared (const process_path&, const dir_path&, - prerequisite&) const; + const prerequisite_key&) const; target* - search_library (optional&, prerequisite&) const; + search_library (optional& spc, prerequisite& p) const + { + if (p.target == nullptr) // First check the cache. + p.target = search_library (spc, p.key ()); + + return p.target; + } + + // Note that pk's scope should not be NULL (even if dir is absolute). + // + target* + search_library (optional&, const prerequisite_key&) const; // Windows-specific (windows-manifest.cxx). // diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index 7a6b059..ffa088f 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -9,6 +9,7 @@ #include +#include // import() #include #include #include @@ -201,24 +202,21 @@ namespace build2 } target* link:: - search_library (optional& spc, prerequisite& p) const + search_library (optional& spc, const prerequisite_key& p) const { tracer trace (x, "link::search_library"); + assert (p.scope != nullptr); + // @@ This is hairy enough to warrant a separate implementation for // Windows. // - - // First check the cache. - // - if (p.target != nullptr) - return p.target; - bool l (p.is_a ()); - const string* ext (l ? nullptr : p.ext); // Only for liba/libs. + const string* ext (l ? nullptr : p.tk.ext); // Only for liba/libs. // Then figure out what we need to search for. // + const string& name (*p.tk.name); // liba // @@ -244,12 +242,12 @@ namespace build2 if (cid == "msvc") { - an = path (p.name); + an = path (name); e = "lib"; } else { - an = path ("lib" + p.name); + an = path ("lib" + name); e = "a"; } @@ -275,12 +273,12 @@ namespace build2 if (cid == "msvc") { - sn = path (p.name); + sn = path (name); e = "dll.lib"; } else { - sn = path ("lib" + p.name); + sn = path ("lib" + name); if (tsys == "darwin") e = "dylib"; else if (tsys == "mingw32") e = "dll.a"; // See search code below. @@ -301,7 +299,7 @@ namespace build2 // Now search. // if (!spc) - spc = extract_library_paths (p.scope); + spc = extract_library_paths (*p.scope); liba* a (nullptr); libs* s (nullptr); @@ -331,14 +329,12 @@ namespace build2 // if (tclass == "windows") { - s = &targets.insert ( - d, dir_path (), p.name, nullptr, trace); + s = &targets.insert (d, dir_path (), name, nullptr, trace); if (s->member == nullptr) { libi& i ( - targets.insert ( - d, dir_path (), p.name, se, trace)); + targets.insert (d, dir_path (), name, se, trace)); if (i.path ().empty ()) i.path (move (f)); @@ -357,7 +353,7 @@ namespace build2 } else { - s = &targets.insert (d, dir_path (), p.name, se, trace); + s = &targets.insert (d, dir_path (), name, se, trace); if (s->path ().empty ()) s->path (move (f)); @@ -379,7 +375,7 @@ namespace build2 if (mt != timestamp_nonexistent) { - s = &targets.insert (d, dir_path (), p.name, se, trace); + s = &targets.insert (d, dir_path (), name, se, trace); if (s->path ().empty ()) s->path (move (f)); @@ -406,7 +402,7 @@ namespace build2 // Note that this target is outside any project which we treat // as out trees. // - a = &targets.insert (d, dir_path (), p.name, ae, trace); + a = &targets.insert (d, dir_path (), name, ae, trace); if (a->path ().empty ()) a->path (move (f)); @@ -419,7 +415,7 @@ namespace build2 // if (cid == "msvc") { - scope& rs (*p.scope.root_scope ()); + scope& rs (*p.scope->root_scope ()); const process_path& ld (cast (rs["bin.ld.path"])); if (s == nullptr && !sn.empty ()) @@ -492,7 +488,7 @@ namespace build2 { // Enter the target group. // - lib& l (targets.insert (*pd, dir_path (), p.name, p.ext, trace)); + lib& l (targets.insert (*pd, dir_path (), name, p.tk.ext, trace)); // It should automatically link-up to the members we have found. // @@ -506,12 +502,10 @@ namespace build2 : "shared"); l.assign ("bin.lib") = bl; - p.target = &l; + return &l; } else - p.target = p.is_a () ? static_cast (a) : s; - - return p.target; + return p.is_a () ? static_cast (a) : s; } match_result link:: @@ -1022,36 +1016,216 @@ namespace build2 } } - // Recursively append/hash prerequisite libraries of a static library. + // Recursively append/hash prerequisite libraries. Only interface + // (*.export.libs) for shared libraries, interface and implementation + // (both prerequisite and from *.libs) for static libraries. // - static void - append_libraries (strings& args, liba& a) + // Note that here we assume that an interface library is also an + // implementation (since we don't use *.export.libs in static link). We + // currently have this restriction to make sure the target in + // *.export.libs is up-to-date (which will happen automatically if it is + // listed as a prerequisite of this library). + // + void link:: + append_libraries (strings& args, file& l, bool la) const { - for (target* pt: a.prerequisite_targets) + for (target* p: l.prerequisite_targets) { - if (liba* pa = pt->is_a ()) + bool a; + file* f; + + if ((a = (f = p->is_a ()) != nullptr) + || (f = p->is_a ()) != nullptr) { - args.push_back (relative (pa->path ()).string ()); // string()&& - append_libraries (args, *pa); + if (la) + args.push_back (relative (f->path ()).string ()); // string()&& + + append_libraries (args, *f, a); } - else if (libs* ps = pt->is_a ()) - args.push_back (relative (ps->path ()).string ()); // string()&& + } + + if (la) + { + append_options (args, l, c_libs); + append_options (args, l, x_libs); + } + else + { + scope* bs (nullptr); // Resolve lazily. + optional lo; // Calculate lazily. + optional spc; // Extract lazily. + + auto append = [&args, &l, &bs, &lo, &spc, this] (const variable& var) + { + const names* ns (cast_null (l[var])); + if (ns == nullptr || ns->empty ()) + return; + + args.reserve (args.size () + ns->size ()); + + for (const name& n: *ns) + { + if (n.simple ()) + args.push_back (n.value); + else + { + if (bs == nullptr) + bs = &l.base_scope (); + + if (!lo) + lo = link_order (*bs, otype::s); // We know it's libs{}. + + file& t (resolve_library (n, *bs, *lo, spc)); + + // 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 (t.path ().empty ()) + fail << "target " << t << " is out of date" << + info << "mentioned in " << var.name << " of target " << l << + info << "is it a prerequisite of " << l << "?"; + + args.push_back (relative (t.path ()).string ()); + } + } + }; + + append (c_export_libs); + append (x_export_libs); } } - static void - hash_libraries (sha256& cs, liba& a) + void link:: + hash_libraries (sha256& cs, file& l, bool la) const { - for (target* pt: a.prerequisite_targets) + for (target* p: l.prerequisite_targets) { - if (liba* pa = pt->is_a ()) + bool a; + file* f; + + if ((a = (f = p->is_a ()) != nullptr) + || (f = p->is_a ()) != nullptr) { - cs.append (pa->path ().string ()); - hash_libraries (cs, *pa); + if (la) + cs.append (f->path ().string ()); + + hash_libraries (cs, *f, a); } - else if (libs* ps = pt->is_a ()) - cs.append (ps->path ().string ()); } + + if (la) + { + hash_options (cs, l, c_libs); + hash_options (cs, l, x_libs); + } + else + { + scope* bs (nullptr); // Resolve lazily. + optional lo; // Calculate lazily. + optional spc; // Extract lazily. + + auto hash = [&cs, &l, &bs, &lo, &spc, this] (const variable& var) + { + const names* ns (cast_null (l[var])); + if (ns == nullptr || ns->empty ()) + return; + + for (const name& n: *ns) + { + if (n.simple ()) + cs.append (n.value); + else + { + if (bs == nullptr) + bs = &l.base_scope (); + + if (!lo) + lo = link_order (*bs, otype::s); // We know it's libs{}. + + file& t (resolve_library (n, *bs, *lo, spc)); + + // 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 (t.path ().empty ()) + fail << "target " << t << " is out of date" << + info << "mentioned in " << var.name << " of target " << l << + info << "is it a prerequisite of " << l << "?"; + + cs.append (t.path ().string ()); + } + } + }; + + hash (c_export_libs); + hash (x_export_libs); + } + } + + // The name can be a simple value (e.g., -lpthread or shell32.lib), an + // absolute target name (e.g., /tmp/libfoo/lib{foo}) or a potentially + // project-qualified relative target name (e.g., libfoo%lib{foo}). + // + // Note that the scope, search paths, and the link order should all be + // derived from the library target that mentioned this name. This way we + // will select exactly the same target as the library's matched rule and + // that's the only way to guarantee it will be up-to-date. + // + file& link:: + resolve_library (name n, + scope& s, + lorder lo, + optional& spc) const + { + if (n.type != "lib" && n.type != "liba" && n.type != "libs") + fail << "target name " << n << " is not a library"; + + target* xt (nullptr); + + if (n.dir.absolute () && !n.qualified ()) + { + // Search for an existing target with this name "as if" it was a + // prerequisite. + // + xt = &search (move (n), s); + } + else + { + // This is import. + // + const string* ext; + const target_type* tt (s.find_target_type (n, ext)); // Changes name. + + if (tt == nullptr) + fail << "unknown target type " << n.type << " in library " << n; + + // @@ OUT: for now we assume out is undetermined, just like in + // search (name, scope). + // + dir_path out; + prerequisite_key pk {n.proj, {tt, &n.dir, &out, &n.value, ext}, &s}; + + xt = search_library (spc, pk); + + if (xt == nullptr) + { + if (n.qualified ()) + xt = &import (pk); + else + fail << "unable to find library " << pk; + } + } + + // If this is lib{}, pick appropriate member. + // + if (lib* l = xt->is_a ()) + xt = &link_member (*l, lo); + + return static_cast (*xt); // liba{} or libs{}. } static void @@ -1433,11 +1607,11 @@ namespace build2 cs.append (f->path ().string ()); - // If this is a static library, link all the libraries it depends - // on, recursively. + // Link all the dependent interface libraries (shared) or + // interface and implementation (static), recursively. // - if (a != nullptr) - hash_libraries (cs, *a); + if (a != nullptr || s != nullptr) + hash_libraries (cs, *f, a != nullptr); } } @@ -1669,11 +1843,11 @@ namespace build2 sargs.push_back (relative (f->path ()).string ()); // string()&& - // If this is a static library, link all the libraries it depends - // on, recursively. + // Link all the dependent interface libraries (shared) or interface + // and implementation (static), recursively. // - if (a != nullptr) - append_libraries (sargs, *a); + if (a != nullptr || s != nullptr) + append_libraries (sargs, *f, a != nullptr); } } diff --git a/build2/cc/msvc.cxx b/build2/cc/msvc.cxx index 84f5853..d088c5b 100644 --- a/build2/cc/msvc.cxx +++ b/build2/cc/msvc.cxx @@ -219,7 +219,7 @@ namespace build2 msvc_search_library (const char* mod, const process_path& ld, const dir_path& d, - prerequisite& p, + const prerequisite_key& p, otype lt, const char* pfx, const char* sfx) @@ -228,6 +228,9 @@ namespace build2 // tracer trace (mod, "msvc_search_library"); + const string* ext (p.tk.ext); + const string& name (*p.tk.name); + // Assemble the file path. // path f (d); @@ -235,18 +238,18 @@ namespace build2 if (*pfx != '\0') { f /= pfx; - f += p.name; + f += name; } else - f /= p.name; + f /= name; if (*sfx != '\0') f += sfx; const string& e ( - p.ext == nullptr || p.is_a () // Only for liba/libs. + ext == nullptr || p.is_a () // Only for liba/libs. ? extension_pool.find ("lib") - : *p.ext); + : *ext); if (!e.empty ()) { @@ -262,7 +265,7 @@ namespace build2 { // Enter the target. // - T& t (targets.insert (d, dir_path (), p.name, &e, trace)); + T& t (targets.insert (d, dir_path (), name, &e, trace)); if (t.path ().empty ()) t.path (move (f)); @@ -277,7 +280,7 @@ namespace build2 liba* link:: msvc_search_static (const process_path& ld, const dir_path& d, - prerequisite& p) const + const prerequisite_key& p) const { liba* r (nullptr); @@ -304,7 +307,7 @@ namespace build2 libs* link:: msvc_search_shared (const process_path& ld, const dir_path& d, - prerequisite& p) const + const prerequisite_key& p) const { tracer trace (x, "link::msvc_search_shared"); @@ -316,7 +319,8 @@ namespace build2 if (libi* i = msvc_search_library (x, ld, d, p, otype::s, pf, sf)) { - r = &targets.insert (d, dir_path (), p.name, nullptr, trace); + r = &targets.insert ( + d, dir_path (), *p.tk.name, nullptr, trace); if (r->member == nullptr) { diff --git a/build2/cxx/init.cxx b/build2/cxx/init.cxx index 71b4a5b..3dd6b1f 100644 --- a/build2/cxx/init.cxx +++ b/build2/cxx/init.cxx @@ -141,7 +141,7 @@ namespace build2 v.insert ("cxx.export.poptions"), v.insert ("cxx.export.coptions"), v.insert ("cxx.export.loptions"), - v.insert ("cxx.export.libs"), + v.insert ("cxx.export.libs"), v["cc.export.poptions"], v["cc.export.coptions"], diff --git a/build2/prerequisite b/build2/prerequisite index e53bb41..156a096 100644 --- a/build2/prerequisite +++ b/build2/prerequisite @@ -30,6 +30,10 @@ namespace build2 mutable const string* proj; // Can be NULL, from project_name_pool. target_key tk; // .dir and .out can be relative. mutable scope_type* scope; // Can be NULL if tk.dir is absolute. + + template + bool is_a () const {return tk.is_a ();} + bool is_a (const target_type& tt) const {return tk.is_a (tt);} }; inline bool diff --git a/build2/target-key b/build2/target-key index df589d7..ef59d63 100644 --- a/build2/target-key +++ b/build2/target-key @@ -28,6 +28,10 @@ namespace build2 const string* const name; const string* const& ext; + template + bool is_a () const {return type->is_a ();} + bool is_a (const target_type& tt) const {return type->is_a (tt);} + // The above references have to track the original objects so we cannot // have assignment. // diff --git a/build2/utility b/build2/utility index 3fb47b7..3ccca61 100644 --- a/build2/utility +++ b/build2/utility @@ -211,6 +211,14 @@ namespace build2 template void + append_options (strings&, T&, const variable&); + + template + void + append_options (strings&, T&, const char*); + + template + void hash_options (sha256&, T&, const variable&); template @@ -226,12 +234,18 @@ namespace build2 append_options (cstrings&, const lookup&); void + append_options (strings&, const lookup&); + + void hash_options (sha256&, const lookup&); void append_options (cstrings&, const strings&); void + append_options (strings&, const strings&); + + void hash_options (sha256&, const strings&); // Check if a specified option is present in the variable or value. T is diff --git a/build2/utility.cxx b/build2/utility.cxx index dc862c6..ef90084 100644 --- a/build2/utility.cxx +++ b/build2/utility.cxx @@ -173,6 +173,13 @@ namespace build2 } void + append_options (strings& args, const lookup& l) + { + if (l) + append_options (args, cast (l)); + } + + void hash_options (sha256& csum, const lookup& l) { if (l) @@ -192,6 +199,18 @@ namespace build2 } void + append_options (strings& args, const strings& sv) + { + if (!sv.empty ()) + { + args.reserve (args.size () + sv.size ()); + + for (const string& s: sv) + args.push_back (s); + } + } + + void hash_options (sha256& csum, const strings& sv) { for (const string& s: sv) diff --git a/build2/utility.ixx b/build2/utility.ixx index 956a726..36c7a7f 100644 --- a/build2/utility.ixx +++ b/build2/utility.ixx @@ -42,6 +42,13 @@ namespace build2 template inline void + append_options (strings& args, T& s, const variable& var) + { + append_options (args, s[var]); + } + + template + inline void append_options (cstrings& args, T& s, const char* var) { append_options (args, s[var]); @@ -49,6 +56,13 @@ namespace build2 template inline void + append_options (strings& args, T& s, const char* var) + { + append_options (args, s[var]); + } + + template + inline void hash_options (sha256& csum, T& s, const variable& var) { hash_options (csum, s[var]); -- cgit v1.1