aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-08-24 15:41:54 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-08-24 15:41:54 +0200
commit3a8972b42f75e10e9a833bba58d65009e7bed7f3 (patch)
treecb350e533b967b828ece6a59ced5bb2caa932de7
parentfeb55ebfdc536c32de2c173d108df5ee0004da44 (diff)
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
-rw-r--r--build2/c/init.cxx2
-rw-r--r--build2/cc/compile.cxx4
-rw-r--r--build2/cc/init.cxx2
-rw-r--r--build2/cc/link26
-rw-r--r--build2/cc/link.cxx276
-rw-r--r--build2/cc/msvc.cxx22
-rw-r--r--build2/cxx/init.cxx2
-rw-r--r--build2/prerequisite4
-rw-r--r--build2/target-key4
-rw-r--r--build2/utility14
-rw-r--r--build2/utility.cxx19
-rw-r--r--build2/utility.ixx14
12 files changed, 321 insertions, 68 deletions
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<strings> ("c.export.poptions"),
v.insert<strings> ("c.export.coptions"),
v.insert<strings> ("c.export.loptions"),
- v.insert<strings> ("c.export.libs"),
+ v.insert<names> ("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<strings> ("cc.export.poptions");
v.insert<strings> ("cc.export.coptions");
v.insert<strings> ("cc.export.loptions");
- v.insert<strings> ("cc.export.libs");
+ v.insert<names> ("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<dir_paths>&) 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<dir_paths>&, prerequisite&) const;
+ search_library (optional<dir_paths>& 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<dir_paths>&, 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 <butl/path-map>
+#include <build2/file> // import()
#include <build2/depdb>
#include <build2/scope>
#include <build2/context>
@@ -201,24 +202,21 @@ namespace build2
}
target* link::
- search_library (optional<dir_paths>& spc, prerequisite& p) const
+ search_library (optional<dir_paths>& 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<lib> ());
- 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<libs> (
- d, dir_path (), p.name, nullptr, trace);
+ s = &targets.insert<libs> (d, dir_path (), name, nullptr, trace);
if (s->member == nullptr)
{
libi& i (
- targets.insert<libi> (
- d, dir_path (), p.name, se, trace));
+ targets.insert<libi> (d, dir_path (), name, se, trace));
if (i.path ().empty ())
i.path (move (f));
@@ -357,7 +353,7 @@ namespace build2
}
else
{
- s = &targets.insert<libs> (d, dir_path (), p.name, se, trace);
+ s = &targets.insert<libs> (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<libs> (d, dir_path (), p.name, se, trace);
+ s = &targets.insert<libs> (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<liba> (d, dir_path (), p.name, ae, trace);
+ a = &targets.insert<liba> (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<process_path> (rs["bin.ld.path"]));
if (s == nullptr && !sn.empty ())
@@ -492,7 +488,7 @@ namespace build2
{
// Enter the target group.
//
- lib& l (targets.insert<lib> (*pd, dir_path (), p.name, p.ext, trace));
+ lib& l (targets.insert<lib> (*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<liba> () ? static_cast<target*> (a) : s;
-
- return p.target;
+ return p.is_a<liba> () ? static_cast<target*> (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<liba> ())
+ bool a;
+ file* f;
+
+ if ((a = (f = p->is_a<liba> ()) != nullptr)
+ || (f = p->is_a<libs> ()) != 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<libs> ())
- 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<lorder> lo; // Calculate lazily.
+ optional<dir_paths> spc; // Extract lazily.
+
+ auto append = [&args, &l, &bs, &lo, &spc, this] (const variable& var)
+ {
+ const names* ns (cast_null<names> (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<liba> ())
+ bool a;
+ file* f;
+
+ if ((a = (f = p->is_a<liba> ()) != nullptr)
+ || (f = p->is_a<libs> ()) != 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<libs> ())
- 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<lorder> lo; // Calculate lazily.
+ optional<dir_paths> spc; // Extract lazily.
+
+ auto hash = [&cs, &l, &bs, &lo, &spc, this] (const variable& var)
+ {
+ const names* ns (cast_null<names> (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<dir_paths>& 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<lib> ())
+ xt = &link_member (*l, lo);
+
+ return static_cast<file&> (*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<lib> () // Only for liba/libs.
+ ext == nullptr || p.is_a<lib> () // 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<T> (d, dir_path (), p.name, &e, trace));
+ T& t (targets.insert<T> (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<libi> (x, ld, d, p, otype::s, pf, sf))
{
- r = &targets.insert<libs> (d, dir_path (), p.name, nullptr, trace);
+ r = &targets.insert<libs> (
+ 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<strings> ("cxx.export.poptions"),
v.insert<strings> ("cxx.export.coptions"),
v.insert<strings> ("cxx.export.loptions"),
- v.insert<strings> ("cxx.export.libs"),
+ v.insert<names> ("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 <typename T>
+ bool is_a () const {return tk.is_a<T> ();}
+ 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 <typename T>
+ bool is_a () const {return type->is_a<T> ();}
+ 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 <typename T>
void
+ append_options (strings&, T&, const variable&);
+
+ template <typename T>
+ void
+ append_options (strings&, T&, const char*);
+
+ template <typename T>
+ void
hash_options (sha256&, T&, const variable&);
template <typename T>
@@ -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<strings> (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 <typename T>
inline void
+ append_options (strings& args, T& s, const variable& var)
+ {
+ append_options (args, s[var]);
+ }
+
+ template <typename T>
+ inline void
append_options (cstrings& args, T& s, const char* var)
{
append_options (args, s[var]);
@@ -49,6 +56,13 @@ namespace build2
template <typename T>
inline void
+ append_options (strings& args, T& s, const char* var)
+ {
+ append_options (args, s[var]);
+ }
+
+ template <typename T>
+ inline void
hash_options (sha256& csum, T& s, const variable& var)
{
hash_options (csum, s[var]);