aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/cc/common.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/cc/common.cxx')
-rw-r--r--libbuild2/cc/common.cxx1464
1 files changed, 1093 insertions, 371 deletions
diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx
index 9cac1b0..2a8bc50 100644
--- a/libbuild2/cc/common.cxx
+++ b/libbuild2/cc/common.cxx
@@ -24,7 +24,7 @@ namespace build2
// Recursively process prerequisite libraries of the specified library. If
// proc_impl returns false, then only process interface (*.export.libs),
// otherwise -- interface and implementation (prerequisite and from
- // *.libs, unless overriden with *.export.imp_libs).
+ // *.libs, unless overriden with *.export.impl_libs).
//
// Note that here we assume that an interface library is also always an
// implementation (since we don't use *.export.libs for static linking).
@@ -32,10 +32,6 @@ namespace build2
// *.export.libs is up-to-date (which will happen automatically if it is
// listed as a prerequisite of this library).
//
- // Storing a reference to library path in proc_lib is legal (it comes
- // either from the target's path or from one of the *.libs variables
- // neither of which should change on this run).
- //
// Note that the order of processing is:
//
// 1. options (x.* then cc.* to be consistent with poptions/loptions)
@@ -43,289 +39,574 @@ namespace build2
// 3. dependency libs (prerequisite_targets, left to right, depth-first)
// 4. dependency libs (*.libs variables).
//
+ // If proc_opt_group is true, then pass to proc_opt the group rather than
+ // the member if a member was picked (according to linfo) form a group.
+ // This is useful when we only want to see the common options set on the
+ // group.
+ //
+ // If either proc_opt or proc_lib return false, then any further
+ // processing of this library or its dependencies is skipped. This can be
+ // used to "prune" the graph traversal in case of duplicates. Note that
+ // proc_opt is called twice for each library so carefully consider from
+ // which to return false.
+ //
// 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 passed to process_libraries(). The first element of this
- // array is NULL.
+ // array is NULL. If this argument is NULL, then this is a library without
+ // a target (e.g., -lm, -pthread, etc) and its name is in the second
+ // argument (which could be resolved to an absolute path or passed as an
+ // -l<name>/-pthread option). Otherwise, (the first argument is not NULL),
+ // the second argument contains the target path (which can be empty in
+ // case of the unknown DLL path).
+ //
+ // Initially, the second argument (library name) was a string (e.g., -lm)
+ // but there are cases where the library is identified with multiple
+ // options, such as -framework CoreServices (there are also cases like
+ // -Wl,--whole-archive -lfoo -lbar -Wl,--no-whole-archive). So now it is a
+ // vector_view that contains a fragment of options (from one of the *.libs
+ // variables) that corresponds to the library (or several libraries, as in
+ // the --whole-archive example above).
+ //
+ // Storing a reference to elements of library name in proc_lib is legal
+ // (they come either from the target's path or from one of the *.libs
+ // variables neither of which should change on this run).
+ //
+ // If proc_impl always returns false (that is, we are only interested in
+ // interfaces), then top_li can be absent. This makes process_libraries()
+ // not to pick the liba/libs{} member for installed libraries instead
+ // passing the lib{} group itself. This can be used to match the semantics
+ // of file_rule which, when matching prerequisites, does not pick the
+ // liba/libs{} member (naturally) but just matches the lib{} group. Note
+ // that currently this truly only works for installed lib{} since non-
+ // installed ones don't have cc.type set. See proc_opt_group for an
+ // alternative way to (potentially) achieve the desired semantics.
+ //
+ // Note that if top_li is present, then the target passed to proc_impl,
+ // proc_lib, and proc_opt (unless proc_opt_group is true) is always a
+ // file.
+ //
+ // The dedup argument is part of the interface dependency deduplication
+ // functionality, similar to $x.deduplicate_export_libs(). Note, however,
+ // that here we do it "properly" (i.e., using group members, etc).
//
void common::
process_libraries (
action a,
const scope& top_bs,
- linfo top_li,
+ optional<linfo> top_li,
const dir_paths& top_sysd,
- const file& l,
+ const mtime_target& l, // liba/libs{}, libux{}, or lib{}
bool la,
lflags lf,
- const function<bool (const file&,
- bool la)>& proc_impl, // Implementation?
- const function<void (const file* const*, // 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
- bool com, // cc. or x.
- bool exp)>& proc_opt, // *.export.
- bool self /*= false*/, // Call proc_lib on l?
- small_vector<const file*, 16>* chain) const
+ const function<bool (const target&,
+ bool la)>& proc_impl, // Implementation?
+ const function<bool (const target* const*, // Can be NULL.
+ const small_vector<reference_wrapper<
+ const string>, 2>&, // Library "name".
+ lflags, // Link flags.
+ const string* type, // whole cc.type
+ bool sys)>& proc_lib, // System library?
+ const function<bool (const target&,
+ const string& lang, // lang from cc.type
+ bool com, // cc. or x.
+ bool exp)>& proc_opt, // *.export.
+ bool self, // Call proc_lib on l?
+ bool proc_opt_group, // Call proc_opt on group instead of member?
+ library_cache* cache) const
{
- small_vector<const file*, 16> chain_storage;
- if (chain == nullptr)
- {
- chain = &chain_storage;
- chain->push_back (nullptr);
- }
+ library_cache cache_storage;
+ if (cache == nullptr)
+ cache = &cache_storage;
+
+ small_vector<const target*, 32> chain;
+
+ if (proc_lib)
+ chain.push_back (nullptr);
+
+ process_libraries_impl (a, top_bs, top_li, top_sysd,
+ nullptr, l, la, lf,
+ proc_impl, proc_lib, proc_opt,
+ self, proc_opt_group,
+ cache, &chain, 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. Note: lookup starting from rule-specific
- // variables (target should already be matched).
+ void common::
+ process_libraries_impl (
+ action a,
+ const scope& top_bs,
+ optional<linfo> top_li,
+ const dir_paths& top_sysd,
+ const target* lg,
+ const mtime_target& l,
+ bool la,
+ lflags lf,
+ const function<bool (const target&,
+ bool la)>& proc_impl,
+ const function<bool (const target* const*,
+ const small_vector<reference_wrapper<
+ const string>, 2>&,
+ lflags,
+ const string* type,
+ bool sys)>& proc_lib,
+ const function<bool (const target&,
+ const string& lang,
+ bool com,
+ bool exp)>& proc_opt,
+ bool self,
+ bool proc_opt_group,
+ library_cache* cache,
+ small_vector<const target*, 32>* chain,
+ small_vector<const target*, 32>* dedup) const
+ {
+ // Add the library to the chain.
//
- const string* t (cast_null<string> (l.state[a][c_type]));
+ if (self && proc_lib)
+ {
+ if (find (chain->begin (), chain->end (), &l) != chain->end ())
+ fail << "dependency cycle detected involving library " << l;
- bool impl (proc_impl && proc_impl (l, la));
- bool cc (false), same (false);
+ chain->push_back (&l);
+ }
+ // We only lookup public variables so go straight for the public
+ // variable pool.
+ //
auto& vp (top_bs.ctx.var_pool);
- lookup c_e_libs;
- lookup x_e_libs;
- if (t != nullptr)
+ do // Breakout loop.
{
- cc = (*t == "cc");
- same = (!cc && *t == x);
-
- // Note that we used to treat *.export.libs set on the liba/libs{}
- // members as *.libs overrides rather than as member-specific
- // interface dependencies. This difference in semantics proved to be
- // surprising so now we have separate *.export.imp_libs for that.
- // Note that in this case options come from *.export.* variables.
+ // 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. Note: lookup starting from rule-specific
+ // variables (target should already be matched). Note also that for
+ // performance we use lookup_original() directly and only look in the
+ // target (so no target type/pattern-specific).
+ //
+ const string* pt (
+ cast_null<string> (
+ l.state[a].lookup_original (c_type, true /* target_only */).first));
+
+ // cc.type value format is <lang>[,...].
//
- // Note also that we only check for *.*libs. If one doesn't have any
- // libraries but needs to set, say, *.loptions, then *.*libs should be
- // set to NULL or empty (this is why we check for the result being
- // defined).
+ size_t p;
+ const string& t (pt != nullptr
+ ? ((p = pt->find (',')) == string::npos
+ ? *pt
+ : string (*pt, 0, p))
+ : string ());
+
+ // Why are we bothering with impl for binless libraries since all
+ // their dependencies are by definition interface? Well, for one, it
+ // could be that it is dynamically-binless (e.g., binless on some
+ // platforms or in some configurations and binful on/in others). In
+ // this case it would be helpful to have a uniform semantics so that,
+ // for example, *.libs are used for liba{} regardless of whether it is
+ // binless or not. On the other hand, having to specify both
+ // *.export.libs=-lm and *.libs=-lm (or *.export.impl_libs) for an
+ // always-binless library is sure not very intuitive. Not sure if we
+ // can win here.
//
- c_e_libs = l[impl ? c_export_imp_libs : c_export_libs];
+ bool impl (proc_impl && proc_impl (l, la));
+ bool cc (false), same (false);
- if (!cc)
- x_e_libs = l[same
- ? (impl ? x_export_imp_libs : x_export_libs)
- : vp[*t + (impl ? ".export.imp_libs" : ".export.libs")]];
+ if (!t.empty ())
+ {
+ cc = (t == "cc");
+ same = (!cc && t == x);
+ }
- // Process options first.
- //
- if (proc_opt)
+ const scope& bs (t.empty () || cc ? top_bs : l.base_scope ());
+
+ lookup c_e_libs;
+ lookup x_e_libs;
+
+ if (!t.empty ())
{
- // If all we know is it's a C-common library, then in both cases we
- // only look for cc.export.*.
+ // Note that we used to treat *.export.libs set on the liba/libs{}
+ // members as *.libs overrides rather than as member-specific
+ // interface dependencies. This difference in semantics proved to be
+ // surprising so now we have separate *.export.impl_libs for that.
+ // Note that in this case options come from *.export.* variables.
+ //
+ // Note also that we only check for *.*libs. If one doesn't have any
+ // libraries but needs to set, say, *.loptions, then *.*libs should
+ // be set to NULL or empty (this is why we check for the result
+ // being defined).
+ //
+ // Note: for performance we call lookup_original() directly (we know
+ // these variables are not overridable) and pass the base scope we
+ // have already resolved.
+ //
+ // See also deduplicate_export_libs() if changing anything here.
//
- if (cc)
- proc_opt (l, *t, true, true);
- else
{
- if (impl)
+ const variable& v (impl ? c_export_impl_libs : c_export_libs);
+ c_e_libs = l.lookup_original (v, false, &bs).first;
+ }
+
+ if (!cc)
+ {
+ const variable& v (
+ same
+ ? (impl ? x_export_impl_libs : x_export_libs)
+ : vp[t + (impl ? ".export.impl_libs" : ".export.libs")]);
+ x_e_libs = l.lookup_original (v, false, &bs).first;
+ }
+
+ // Process options first.
+ //
+ if (proc_opt)
+ {
+ const target& ol (proc_opt_group && lg != nullptr ? *lg : l);
+
+ // If all we know is it's a C-common library, then in both cases
+ // we only look for cc.export.*.
+ //
+ if (cc)
{
- // 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 (!proc_opt (ol, t, true, true)) break;
+ }
+ else
+ {
+ if (impl)
{
- // NOTE: should this not be from l.vars rather than l? Or
- // perhaps we can assume non-common values will be set on
- // libs{}/liba{}.
+ // Interface and implementation: as discussed above, we can
+ // have two situations: overriden export or default export.
//
- // Note: options come from *.export.* variables.
- //
- proc_opt (l, *t, false, true);
- proc_opt (l, *t, true, true);
+ if (c_e_libs.defined () || x_e_libs.defined ())
+ {
+ // NOTE: should this not be from l.vars rather than l? Or
+ // perhaps we can assume non-common values will be set on
+ // libs{}/liba{}.
+ //
+ // Note: options come from *.export.* variables.
+ //
+ if (!proc_opt (ol, t, false, true) ||
+ !proc_opt (ol, t, true, true)) break;
+ }
+ else
+ {
+ // For default export we use the same options as were used
+ // to build the library.
+ //
+ if (!proc_opt (ol, t, false, false) ||
+ !proc_opt (ol, t, true, false)) break;
+ }
}
else
{
- // For default export we use the same options as were used to
- // build the library.
+ // Interface: only add *.export.* (interface dependencies).
//
- proc_opt (l, *t, false, false);
- proc_opt (l, *t, true, false);
+ if (!proc_opt (ol, t, false, true) ||
+ !proc_opt (ol, t, true, true)) break;
}
}
- else
- {
- // Interface: only add *.export.* (interface dependencies).
- //
- proc_opt (l, *t, false, true);
- proc_opt (l, *t, true, true);
- }
}
}
- }
-
- // Determine if an absolute path is to a system library. Note that
- // we assume both paths to be normalized.
- //
- auto sys = [] (const dir_paths& sysd, const string& p) -> bool
- {
- size_t pn (p.size ());
- for (const dir_path& d: sysd)
+ // Determine if an absolute path is to a system library. Note that
+ // we assume both paths to be normalized.
+ //
+ auto sys = [] (const dir_paths& sysd, const string& p) -> bool
{
- const string& ds (d.string ()); // Can be "/", otherwise no slash.
- size_t dn (ds.size ());
-
- if (pn > dn &&
- p.compare (0, dn, ds) == 0 &&
- (path::traits_type::is_separator (ds[dn - 1]) ||
- path::traits_type::is_separator (p[dn])))
- return true;
- }
+ size_t pn (p.size ());
- return false;
- };
+ for (const dir_path& d: sysd)
+ {
+ const string& ds (d.string ()); // Can be "/", otherwise no slash.
+ size_t dn (ds.size ());
+
+ if (pn > dn &&
+ p.compare (0, dn, ds) == 0 &&
+ (path::traits_type::is_separator (ds[dn - 1]) ||
+ path::traits_type::is_separator (p[dn])))
+ return true;
+ }
- // Next process the library itself if requested.
- //
- if (self && proc_lib)
- {
- chain->push_back (&l);
+ return false;
+ };
- // 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).
+ // Next process the library itself if requested.
//
- const path& p (l.path ());
-
- bool s (t != nullptr // If cc library (matched or imported).
- ? cast_false<bool> (l.vars[c_system])
- : !p.empty () && sys (top_sysd, p.string ()));
+ small_vector<reference_wrapper<const string>, 2> proc_lib_name;//Reuse.
- proc_lib (&chain->back (), p.string (), lf, s);
- }
+ if (self && proc_lib)
+ {
+ // 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).
+ //
+ const file* f;
+ const path& p ((f = l.is_a<file> ()) ? f->path () : empty_path);
- const scope& bs (t == nullptr || cc ? top_bs : l.base_scope ());
- optional<linfo> li; // Calculate lazily.
- const dir_paths* sysd (nullptr); // Resolve lazily.
+ bool s (pt != nullptr // If cc library (matched or imported).
+ ? cast_false<bool> (l.vars[c_system])
+ : !p.empty () && sys (top_sysd, p.string ()));
- // Find system search directories corresponding to this library, i.e.,
- // from its project and for its type (C, C++, etc).
- //
- auto find_sysd = [&top_sysd, t, cc, same, &bs, &sysd, this] ()
- {
- // Use the search dirs corresponding to this library scope/type.
- //
- sysd = (t == nullptr || cc)
- ? &top_sysd // Imported library, use importer's sysd.
- : &cast<dir_paths> (
- bs.root_scope ()->vars[same
- ? x_sys_lib_dirs
- : bs.ctx.var_pool[*t + ".sys_lib_dirs"]]);
- };
+ proc_lib_name = {p.string ()};
+ if (!proc_lib (&chain->back (), proc_lib_name, lf, pt, s))
+ break;
+ }
- auto find_linfo = [top_li, t, cc, &bs, &l, &li] ()
- {
- li = (t == nullptr || cc)
- ? top_li
- : link_info (bs, link_type (l).type);
- };
+ optional<optional<linfo>> li; // Calculate lazily.
+ const dir_paths* sysd (nullptr); // Resolve lazily.
- // Only go into prerequisites (implementation) if instructed and we are
- // not using explicit export. Otherwise, interface dependencies come
- // from the lib{}:*.export.imp_libs below.
- //
- if (impl && !c_e_libs.defined () && !x_e_libs.defined ())
- {
- for (const prerequisite_target& pt: l.prerequisite_targets[a])
+ // Find system search directories corresponding to this library, i.e.,
+ // from its project and for its type (C, C++, etc).
+ //
+ auto find_sysd = [&top_sysd, &vp, t, cc, same, &bs, &sysd, this] ()
{
- // Note: adhoc prerequisites are not part of the library metadata
- // protocol (and we should check for adhoc first to avoid races).
+ // Use the search dirs corresponding to this library scope/type.
//
- if (pt.adhoc || pt == nullptr)
- continue;
+ sysd = (t.empty () || cc)
+ ? &top_sysd // Imported library, use importer's sysd.
+ : &cast<dir_paths> (
+ bs.root_scope ()->vars[same
+ ? x_sys_lib_dirs
+ : vp[t + ".sys_lib_dirs"]]);
+ };
- bool la;
- const file* f;
+ auto find_linfo = [top_li, t, cc, &bs, &l, &li] ()
+ {
+ li = (t.empty () || cc)
+ ? top_li
+ : optional<linfo> (link_info (bs, link_type (l).type)); // @@ PERF
+ };
+
+ // Only go into prerequisites (implementation) if instructed and we
+ // are not using explicit export. Otherwise, interface dependencies
+ // come from the lib{}:*.export.impl_libs below.
+ //
+ if (impl && !c_e_libs.defined () && !x_e_libs.defined ())
+ {
+#if 0
+ assert (top_li); // Must pick a member if implementation (see above).
+#endif
- if ((la = (f = pt->is_a<liba> ())) ||
- (la = (f = pt->is_a<libux> ())) ||
- ( f = pt->is_a<libs> ()))
+ for (const prerequisite_target& pt: l.prerequisite_targets[a])
{
- if (sysd == nullptr) find_sysd ();
- if (!li) find_linfo ();
+ // Note: adhoc prerequisites are not part of the library metadata
+ // protocol (and we should check for adhoc first to avoid races
+ // during execute).
+ //
+ if (pt.adhoc () || pt == nullptr)
+ continue;
+
+ if (marked (pt))
+ fail << "implicit dependency cycle detected involving library "
+ << l;
+
+ bool la;
+ const file* f;
+
+ if ((la = (f = pt->is_a<liba> ())) ||
+ (la = (f = pt->is_a<libux> ())) ||
+ ( f = pt->is_a<libs> ()))
+ {
+ // See link_rule for details.
+ //
+ const target* g ((pt.include & include_group) != 0
+ ? f->group
+ : nullptr);
+
+ if (sysd == nullptr) find_sysd ();
+ if (!li) find_linfo ();
- process_libraries (a, bs, *li, *sysd,
- *f, la, pt.data,
- proc_impl, proc_lib, proc_opt, true, chain);
+ process_libraries_impl (a, bs, *li, *sysd,
+ g, *f, la, pt.data /* lflags */,
+ proc_impl, proc_lib, proc_opt,
+ true /* self */, proc_opt_group,
+ cache, chain, nullptr);
+ }
}
}
- }
-
- // Process libraries (recursively) from *.export.*libs (of type names)
- // handling import, etc.
- //
- // If it is not a C-common library, then it probably doesn't have any of
- // the *.libs.
- //
- if (t != nullptr)
- {
- optional<dir_paths> usrd; // Extract lazily.
- // Determine if a "simple path" is a system library.
+ // Process libraries (recursively) from *.export.*libs (of type names)
+ // handling import, etc.
+ //
+ // If it is not a C-common library, then it probably doesn't have any
+ // of the *.libs.
//
- auto sys_simple = [&sysd, &sys, &find_sysd] (const string& p) -> bool
+ if (!t.empty ())
{
- bool s (!path::traits_type::absolute (p));
+ optional<dir_paths> 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_type::absolute (p));
- s = sys (*sysd, p);
- }
-
- return s;
- };
+ if (!s)
+ {
+ if (sysd == nullptr) find_sysd ();
+ s = sys (*sysd, p);
+ }
- auto proc_int = [&l,
- &proc_impl, &proc_lib, &proc_opt, chain,
- &sysd, &usrd,
- &find_sysd, &find_linfo, &sys_simple,
- &bs, a, &li, impl, this] (const lookup& lu)
- {
- const vector<name>* ns (cast_null<vector<name>> (lu));
- if (ns == nullptr || ns->empty ())
- return;
+ return s;
+ };
- for (auto i (ns->begin ()), e (ns->end ()); i != e; ++i)
+ // Determine the length of the library name fragment as well as
+ // whether it is a system library. Possible length values are:
+ //
+ // 1 - just the argument itself (-lm, -pthread)
+ // 2 - argument and next element (-l m, -framework CoreServices)
+ // 0 - unrecognized/until the end (-Wl,--whole-archive ...)
+ //
+ // See similar code in find_system_library().
+ //
+ auto sense_fragment = [&sys_simple, this] (const string& l) ->
+ pair<size_t, bool>
{
- const name& n (*i);
+ size_t n;
+ bool s (true);
- if (n.simple ())
+ if (tsys == "win32-msvc")
{
- // 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 may come from our .static/shared.pc
- // files).
- //
- if (proc_lib)
- proc_lib (nullptr, n.value, 0, sys_simple (n.value));
+ if (l[0] == '/')
+ {
+ // Some other option (e.g., /WHOLEARCHIVE:<name>).
+ //
+ n = 0;
+ }
+ else
+ {
+ // Presumably a path.
+ //
+ n = 1;
+ s = sys_simple (l);
+ }
}
else
{
- // This is a potentially project-qualified target.
- //
- if (sysd == nullptr) find_sysd ();
- if (!li) find_linfo ();
+ if (l[0] == '-')
+ {
+ // -l<name>, -l <name>, -pthread
+ //
+ if (l[1] == 'l' || l == "-pthread")
+ {
+ n = l.size () == 2 ? 2 : 1;
+ }
+ // -framework <name> (Mac OS)
+ //
+ else if (tsys == "darwin" && l == "-framework")
+ {
+ n = 2;
+ }
+ // Some other option (e.g., -Wl,--whole-archive).
+ //
+ else
+ n = 0;
+ }
+ else
+ {
+ // Presumably a path.
+ //
+ n = 1;
+ s = sys_simple (l);
+ }
+ }
- const file& t (
- resolve_library (a,
- bs,
- n,
- (n.pair ? (++i)->dir : dir_path ()),
- *li,
- *sysd, usrd));
+ return make_pair (n, s);
+ };
+
+ auto proc_intf = [&l, proc_opt_group, cache, chain,
+ &proc_impl, &proc_lib, &proc_lib_name, &proc_opt,
+ &sysd, &usrd,
+ &find_sysd, &find_linfo, &sense_fragment,
+ &bs, a, &li, impl, this] (
+ const lookup& lu,
+ small_vector<const target*, 32>* dedup,
+ size_t dedup_start) // Start of our deps.
+ {
+ const vector<name>* ns (cast_null<vector<name>> (lu));
+ if (ns == nullptr || ns->empty ())
+ return;
+
+ for (auto i (ns->begin ()), e (ns->end ()); i != e; )
+ {
+ const name& n (*i);
- if (proc_lib)
+ // Note: see also recursively-binless logic in link_rule if
+ // changing anything in simple library handling.
+ //
+ if (n.simple ())
+ {
+ // This is something like -lm or shell32.lib so should be a
+ // valid path. But it can also be an absolute library path
+ // (e.g., something that may come from our .{static/shared}.pc
+ // files).
+ //
+ if (proc_lib)
+ {
+ pair<size_t, bool> r (sense_fragment (n.value));
+
+ proc_lib_name.clear ();
+ for (auto e1 (r.first != 0 ? i + r.first : e);
+ i != e && i != e1 && i->simple ();
+ ++i)
+ {
+ proc_lib_name.push_back (i->value);
+ }
+
+ proc_lib (nullptr, proc_lib_name, 0, nullptr, r.second);
+ continue;
+ }
+ }
+ else
{
+ // This is a potentially project-qualified target.
+ //
+ if (sysd == nullptr) find_sysd ();
+ if (!li) find_linfo ();
+
+ const mtime_target* t;
+ const target* g;
+
+ const char* w (nullptr);
+ try
+ {
+ pair<const mtime_target&, const target*> p (
+ resolve_library (a,
+ bs,
+ n,
+ (n.pair ? (++i)->dir : dir_path ()),
+ *li,
+ *sysd, usrd,
+ cache));
+
+ t = &p.first;
+ g = p.second;
+
+ // Deduplicate.
+ //
+ // Note that dedup_start makes sure we only consider our
+ // interface dependencies while maintaining the "through"
+ // list.
+ //
+ if (dedup != nullptr)
+ {
+ if (find (dedup->begin () + dedup_start,
+ dedup->end (),
+ t) != dedup->end ())
+ {
+ ++i;
+ continue;
+ }
+
+ dedup->push_back (t);
+ }
+ }
+ catch (const non_existent_library& e)
+ {
+ // This is another manifestation of the "mentioned in
+ // *.export.libs but not in prerequisites" case (see below).
+ //
+ t = &e.target;
+ w = "unknown";
+ }
+
// 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
@@ -335,87 +616,193 @@ namespace build2
// on Windows import-installed DLLs may legally have empty
// paths.
//
- if (t.mtime () == timestamp_unknown)
+ if (w != nullptr)
+ ; // See above.
+ else if (l.ctx.phase == run_phase::match)
+ {
+ // We allow not matching installed libraries if all we need
+ // is their options (see compile_rule::apply()).
+ //
+ if (proc_lib || t->base_scope ().root_scope () != nullptr)
+ {
+ if (!t->matched (a))
+ w = "not matched";
+ }
+ }
+ else
+ {
+ // Note that this check we only do if there is proc_lib
+ // (since it's valid to process library's options before
+ // updating it).
+ //
+ if (proc_lib)
+ {
+ if (t->mtime () == timestamp_unknown)
+ w = "out of date";
+ }
+ }
+
+ if (w != nullptr)
+ {
fail << (impl ? "implementation" : "interface")
- << " dependency " << t << " is out of date" <<
- info << "mentioned in *.export." << (impl ? "imp_" : "")
+ << " dependency " << *t << " is " << w <<
+ info << "mentioned in *.export." << (impl ? "impl_" : "")
<< "libs of target " << l <<
- info << "is it a prerequisite of " << l << "?";
+ info << "is it a prerequisite of " << l << "?" << endf;
+ }
+
+ // Process it recursively.
+ //
+ bool u;
+ bool la ((u = t->is_a<libux> ()) || t->is_a<liba> ());
+ lflags lf (0);
+
+ // If this is a static library, see if we need to link it
+ // whole.
+ //
+ if (la && proc_lib)
+ {
+ // Note: go straight for the public variable pool.
+ //
+ const variable& var (t->ctx.var_pool["bin.whole"]);
+
+ // See the link rule for the lookup semantics.
+ //
+ lookup l (
+ t->lookup_original (var, true /* target_only */).first);
+
+ if (l ? cast<bool> (*l) : u)
+ lf |= lflag_whole;
+ }
+
+ process_libraries_impl (
+ a, bs, *li, *sysd,
+ g, *t, la, lf,
+ proc_impl, proc_lib, proc_opt,
+ true /* self */, proc_opt_group,
+ cache, chain, dedup);
}
- // 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 (a, bs, *li, *sysd,
- t, t.is_a<liba> () || t.is_a<libux> (), 0,
- proc_impl, proc_lib, proc_opt, true, chain);
+ ++i;
}
- }
- };
+ };
- // Process libraries from *.libs (of type strings).
- //
- auto proc_imp = [&proc_lib, &sys_simple] (const lookup& lu)
- {
- const strings* ns (cast_null<strings> (lu));
- if (ns == nullptr || ns->empty ())
- return;
+ auto proc_intf_storage = [&proc_intf] (const lookup& lu1,
+ const lookup& lu2 = lookup ())
+ {
+ small_vector<const target*, 32> dedup_storage;
- for (const string& n: *ns)
+ if (lu1) proc_intf (lu1, &dedup_storage, 0);
+ if (lu2) proc_intf (lu2, &dedup_storage, 0);
+ };
+
+ // Process libraries from *.libs (of type strings).
+ //
+ auto proc_impl = [&proc_lib, &proc_lib_name,
+ &sense_fragment] (const lookup& lu)
{
- // This is something like -lpthread or shell32.lib so should be a
- // valid path.
- //
- proc_lib (nullptr, n, 0, sys_simple (n));
- }
- };
+ const strings* ns (cast_null<strings> (lu));
+ if (ns == nullptr || ns->empty ())
+ return;
- // 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)
+ for (auto i (ns->begin ()), e (ns->end ()); i != e; )
+ {
+ // This is something like -lm or shell32.lib so should be a
+ // valid path.
+ //
+ pair<size_t, bool> r (sense_fragment (*i));
+
+ proc_lib_name.clear ();
+ for (auto e1 (r.first != 0 ? i + r.first : e);
+ i != e && i != e1;
+ ++i)
+ {
+ proc_lib_name.push_back (*i);
+ }
+
+ proc_lib (nullptr, proc_lib_name, 0, nullptr, r.second);
+ }
+ };
+
+ // 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)
{
- // 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);
+ if (c_e_libs) proc_intf (c_e_libs, nullptr, 0);
}
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)
+ if (c_e_libs)
{
- proc_imp (l[c_libs]);
- proc_imp (l[same ? x_libs : vp[*t + ".libs"]]);
+ if (dedup != nullptr)
+ proc_intf (c_e_libs, dedup, dedup->size ());
+ else
+ proc_intf_storage (c_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);
+ // Note: see also recursively-binless logic in link_rule if
+ // changing anything here.
+
+ if (impl)
+ {
+ // 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 ())
+ {
+ // Why are we calling proc_intf() on *.impl_libs? Perhaps
+ // because proc_impl() expects strings, not names? Yes, and
+ // proc_intf() checks impl.
+ //
+ if (c_e_libs) proc_intf (c_e_libs, nullptr, 0);
+ if (x_e_libs) proc_intf (x_e_libs, nullptr, 0);
+ }
+ 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.
+ //
+ // Note: for performance we call lookup_original() directly
+ // (we know these variables are not overridable) and pass the
+ // base scope we have already resolved.
+ //
+ if (proc_lib)
+ {
+ const variable& v (same ? x_libs : vp[t + ".libs"]);
+ proc_impl (l.lookup_original (c_libs, false, &bs).first);
+ proc_impl (l.lookup_original (v, false, &bs).first);
+ }
+ }
+ }
+ else
+ {
+ // Interface: only add *.export.* (interface dependencies).
+ //
+ if (c_e_libs.defined () || x_e_libs.defined ())
+ {
+ if (dedup != nullptr)
+ {
+ size_t s (dedup->size ()); // Start of our interface deps.
+
+ if (c_e_libs) proc_intf (c_e_libs, dedup, s);
+ if (x_e_libs) proc_intf (x_e_libs, dedup, s);
+ }
+ else
+ proc_intf_storage (c_e_libs, x_e_libs);
+ }
+ }
}
}
- }
+ } while (false); // Breakout loop end.
// Remove this library from the chain.
//
@@ -436,21 +823,69 @@ namespace build2
// 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.
//
- const file& common::
+ // If li is absent, then don't pick the liba/libs{} member, returning the
+ // lib{} target itself. If li is present, then the returned target is
+ // always a file. The second half of the returned pair is the group, if
+ // the member was picked.
+ //
+ // Note: paths in sysd/usrd are expected to be absolute and normalized.
+ //
+ // Note: may throw non_existent_library.
+ //
+ pair<const mtime_target&, const target*> common::
resolve_library (action a,
const scope& s,
const name& cn,
const dir_path& out,
- linfo li,
+ optional<linfo> li,
const dir_paths& sysd,
- optional<dir_paths>& usrd) const
+ optional<dir_paths>& usrd,
+ library_cache* cache) const
{
+ bool q (cn.qualified ());
+ auto lo (li ? optional<lorder> (li->order) : nullopt);
+
+ // If this is an absolute and normalized unqualified name (which means
+ // the scope does not factor into the result), then first check the
+ // cache.
+ //
+ // Note that normally we will have a handful of libraries repeated a
+ // large number of times (see Boost for an extreme example of this).
+ //
+ // Note also that for non-utility libraries we know that only the link
+ // order from linfo is used. While not caching it and always picking an
+ // alternative could also work, we cache it to avoid the lookup.
+ //
+ if (cache != nullptr)
+ {
+ if (!q &&
+ (cn.dir.absolute () && cn.dir.normalized ()) &&
+ (out.empty () || (out.absolute () && out.normalized ())))
+ {
+ auto i (find_if (cache->begin (), cache->end (),
+ [lo, &cn, &out] (const library_cache_entry& e)
+ {
+ const target& t (e.lib);
+ return (e.lo == lo &&
+ e.value == cn.value &&
+ e.type == cn.type &&
+ t.dir == cn.dir &&
+ t.out == out);
+ }));
+
+ if (i != cache->end ())
+ return pair<const mtime_target&, const target*> {i->lib, i->group};
+ }
+ else
+ cache = nullptr; // Do not cache.
+ }
+
if (cn.type != "lib" && cn.type != "liba" && cn.type != "libs")
fail << "target name " << cn << " is not a library";
const target* xt (nullptr);
- if (!cn.qualified ())
+ if (!q)
{
// Search for an existing target with this name "as if" it was a
// prerequisite.
@@ -478,18 +913,36 @@ namespace build2
fail << "unable to find library " << pk;
}
- // If this is lib{}/libu*{}, pick appropriate member.
+ // If this is lib{}/libul{}, pick appropriate member unless we were
+ // instructed not to.
//
- if (const libx* l = xt->is_a<libx> ())
- xt = link_member (*l, a, li); // Pick lib*{e,a,s}{}.
+ const target* g (nullptr);
+ if (li)
+ {
+ if (const libx* l = xt->is_a<libx> ())
+ {
+ g = xt;
+ xt = link_member (*l, a, *li); // Pick lib*{e,a,s}{}.
+ }
+ }
+
+ auto& t (xt->as<mtime_target> ());
- return xt->as<file> ();
+ if (cache != nullptr)
+ cache->push_back (library_cache_entry {lo, cn.type, cn.value, t, g});
+
+ return pair<const mtime_target&, const target*> {t, g};
}
- // Note that pk's scope should not be NULL (even if dir is absolute).
+ // Action should be absent if called during the load phase. Note that pk's
+ // scope should not be NULL (even if dir is absolute).
+ //
+ // Note: paths in sysd/usrd are expected to be absolute and normalized.
+ //
+ // Note: see similar logic in find_system_library().
//
target* common::
- search_library (action act,
+ search_library (optional<action> act,
const dir_paths& sysd,
optional<dir_paths>& usrd,
const prerequisite_key& p,
@@ -497,7 +950,7 @@ namespace build2
{
tracer trace (x, "search_library");
- assert (p.scope != nullptr);
+ assert (p.scope != nullptr && (!exist || act));
context& ctx (p.scope->ctx);
const scope& rs (*p.scope->root_scope ());
@@ -614,6 +1067,21 @@ namespace build2
{
context& ctx (p.scope->ctx);
+ // Whether to look for a binless variant using the common .pc file
+ // (see below).
+ //
+ // Normally we look for a binless version if the binful one was not
+ // found. However, sometimes we may find what looks like a binful
+ // library but on a closer examination realize that there is something
+ // wrong with it (for example, it's not a Windows import library). In
+ // such cases we want to omit looking for a binless library using the
+ // common .pc file since it most likely corresponds to the binful
+ // library (and we may end up in a infinite loop trying to resolve
+ // itself).
+ //
+ bool ba (true);
+ bool bs (true);
+
timestamp mt;
// libs
@@ -685,6 +1153,31 @@ namespace build2
s->path_mtime (move (f), mt);
}
}
+ else if (!ext && tsys == "darwin")
+ {
+ // Besides .dylib, Mac OS now also has "text-based stub libraries"
+ // that use the .tbd extension. They appear to be similar to
+ // Windows import libraries and contain information such as the
+ // location of the .dylib library, its symbols, etc. For example,
+ // there is /Library/.../MacOSX13.3.sdk/usr/lib/libsqlite3.tbd
+ // which points to /usr/lib/libsqlite3.dylib (but which itself is
+ // invisible/inaccessible, presumably for security).
+ //
+ // Note that for now we are treating the .tbd library as the
+ // shared library but could probably do the more elaborate dance
+ // with ad hoc members like on Windows if really necessary.
+ //
+ se = string ("tbd");
+ f = f.base (); // Remove .dylib.
+ f += ".tbd";
+ mt = mtime (f);
+
+ if (mt != timestamp_nonexistent)
+ {
+ insert_library (ctx, s, name, d, ld, se, exist, trace);
+ s->path_mtime (move (f), mt);
+ }
+ }
}
// liba
@@ -714,10 +1207,24 @@ namespace build2
if (tsys == "win32-msvc")
{
if (s == nullptr && !sn.empty ())
- s = msvc_search_shared (ld, d, p, exist);
+ {
+ pair<libs*, bool> r (msvc_search_shared (ld, d, p, exist));
+
+ if (r.first != nullptr)
+ s = r.first;
+ else if (!r.second)
+ bs = false;
+ }
if (a == nullptr && !an.empty ())
- a = msvc_search_static (ld, d, p, exist);
+ {
+ pair<liba*, bool> r (msvc_search_static (ld, d, p, exist));
+
+ if (r.first != nullptr)
+ a = r.first;
+ else if (!r.second)
+ ba = false;
+ }
}
// Look for binary-less libraries via pkg-config .pc files. Note that
@@ -734,7 +1241,10 @@ namespace build2
// is no binful variant.
//
pair<path, path> r (
- pkgconfig_search (d, p.proj, name, na && ns /* common */));
+ pkgconfig_search (d,
+ p.proj,
+ name,
+ na && ns && ba && bs /* common */));
if (na && !r.first.empty ())
{
@@ -787,6 +1297,8 @@ namespace build2
// making it the only one to allow things to be overriden (e.g.,
// if build2 was moved or some such).
//
+ // Note: build_install_lib is already normalized.
+ //
usrd->insert (usrd->begin (), build_install_lib);
}
}
@@ -839,43 +1351,88 @@ namespace build2
if (exist)
return r;
- // If we cannot acquire the lock then this mean the target has already
- // been matched and we assume all of this has already been done.
+ // Try to extract library information from pkg-config. We only add the
+ // default macro if we could not extract more precise information. The
+ // idea is that in .pc files that we generate, we copy those macros (or
+ // custom ones) from *.export.poptions.
//
- auto lock = [act] (const target* t) -> target_lock
+ // @@ Should we add .pc files as ad hoc members so pkgconfig_save() can
+ // use their names when deriving -l-names (this would be especially
+ // helpful for binless libraries to get hold of prefix/suffix, etc).
+ //
+ auto load_pc = [this, &trace,
+ act, &p, &name,
+ &sysd, &usrd,
+ pd, &pc, lt, a, s] (pair<bool, bool> metaonly)
{
- auto l (t != nullptr ? build2::lock (act, *t, true) : target_lock ());
-
- if (l && l.offset == target::offset_matched)
+ l5 ([&]{trace << "loading pkg-config information during "
+ << (act ? "match" : "load") << " for "
+ << (a != nullptr ? "static " : "")
+ << (s != nullptr ? "shared " : "")
+ << "member(s) of " << *lt << "; metadata only: "
+ << metaonly.first << " " << metaonly.second;});
+
+ // Add the "using static/shared library" macro (used, for example, to
+ // handle DLL export). The absence of either of these macros would
+ // mean some other build system that cannot distinguish between the
+ // two (and no pkg-config information).
+ //
+ auto add_macro = [this] (target& t, const char* suffix)
{
- assert ((*t)[act].rule == &file_rule::rule_match);
- l.unlock ();
- }
-
- return l;
- };
+ // If there is already a value (either in cc.export or x.export),
+ // don't add anything: we don't want to be accumulating defines nor
+ // messing with custom values. And if we are adding, then use the
+ // generic cc.export.
+ //
+ // The only way we could already have this value is if this same
+ // library was also imported as a project (as opposed to installed).
+ // Unlikely but possible. In this case the values were set by the
+ // export stub and we shouldn't touch them.
+ //
+ if (!t.vars[x_export_poptions])
+ {
+ auto p (t.vars.insert (c_export_poptions));
- target_lock ll (lock (lt));
+ if (p.second)
+ {
+ // The "standard" macro name will be LIB<NAME>_{STATIC,SHARED},
+ // where <name> is the target name. Here we want to strike a
+ // balance between being unique and not too noisy.
+ //
+ string d ("-DLIB");
- // Set lib{} group members to indicate what's available. Note that we
- // must be careful here since its possible we have already imported some
- // of its members.
- //
- timestamp mt (timestamp_nonexistent);
- if (ll)
- {
- if (s != nullptr) {lt->s = s; mt = s->mtime ();}
- if (a != nullptr) {lt->a = a; mt = a->mtime ();}
- }
+ d += sanitize_identifier (
+ ucase (const_cast<const string&> (t.name)));
- target_lock al (lock (a));
- target_lock sl (lock (s));
+ d += '_';
+ d += suffix;
- if (!al) a = nullptr;
- if (!sl) s = nullptr;
+ strings o;
+ o.push_back (move (d));
+ p.first = move (o);
+ }
+ }
+ };
- if (a != nullptr) a->group = lt;
- if (s != nullptr) s->group = lt;
+ if (pc.first.empty () && pc.second.empty ())
+ {
+ if (!pkgconfig_load (act, *p.scope,
+ *lt, a, s,
+ p.proj, name,
+ *pd, sysd, *usrd,
+ metaonly))
+ {
+ if (a != nullptr && !metaonly.first) add_macro (*a, "STATIC");
+ if (s != nullptr && !metaonly.second) add_macro (*s, "SHARED");
+ }
+ }
+ else
+ pkgconfig_load (act, *p.scope,
+ *lt, a, s,
+ pc,
+ *pd, sysd, *usrd,
+ metaonly);
+ };
// Mark as a "cc" library (unless already marked) and set the system
// flag.
@@ -886,7 +1443,7 @@ namespace build2
if (p.second)
{
- p.first.get () = string ("cc");
+ p.first = string ("cc");
if (sys)
t.vars.assign (c_system) = true;
@@ -895,74 +1452,129 @@ namespace build2
return p.second;
};
- // If the library already has cc.type, then assume it was either
- // already imported or was matched by a rule.
+ // Deal with the load phase case. The rest is already hairy enough so
+ // let's not try to weave this logic into that.
//
- if (a != nullptr && !mark_cc (*a)) a = nullptr;
- if (s != nullptr && !mark_cc (*s)) s = nullptr;
-
- // Add the "using static/shared library" macro (used, for example, to
- // handle DLL export). The absence of either of these macros would
- // mean some other build system that cannot distinguish between the
- // two (and no pkg-config information).
- //
- auto add_macro = [this] (target& t, const char* suffix)
+ if (!act)
{
- // If there is already a value (either in cc.export or x.export),
- // don't add anything: we don't want to be accumulating defines nor
- // messing with custom values. And if we are adding, then use the
- // generic cc.export.
+ assert (ctx.phase == run_phase::load);
+
+ // The overall idea here is to set everything up so that the default
+ // file_rule matches the returned targets, the same way as it would if
+ // multiple operations were executed for the match phase (see below).
//
- // The only way we could already have this value is if this same
- // library was also imported as a project (as opposed to installed).
- // Unlikely but possible. In this case the values were set by the
- // export stub and we shouldn't touch them.
+ // Note however, that there is no guarantee that we won't end up in
+ // the match phase code below even after loading things here. For
+ // example, the same library could be searched from pkgconfig_load()
+ // if specified as -l. And if we try to re-assign group members, then
+ // that would be a race condition. So we use the cc mark to detect
+ // this.
//
- if (!t.vars[x_export_poptions])
+ timestamp mt (timestamp_nonexistent);
+ if (a != nullptr) {lt->a = a; a->group = lt; mt = a->mtime ();}
+ if (s != nullptr) {lt->s = s; s->group = lt; mt = s->mtime ();}
+
+ // @@ TODO: we currently always reload pkgconfig for lt (and below).
+ //
+ mark_cc (*lt);
+ lt->mtime (mt); // Note: problematic, see below for details.
+
+ // We can only load metadata from here since we can only do this
+ // during the load phase. But it's also possible that a racing match
+ // phase already found and loaded this library without metadata. So
+ // looks like the only way is to load the metadata incrementally. We
+ // can base this decision on the presense/absense of cc.type and
+ // export.metadata.
+ //
+ pair<bool, bool> metaonly {false, false};
+
+ if (a != nullptr && !mark_cc (*a))
{
- auto p (t.vars.insert (c_export_poptions));
+ if (a->vars[ctx.var_export_metadata])
+ a = nullptr;
+ else
+ metaonly.first = true;
+ }
- if (p.second)
- {
- // The "standard" macro name will be LIB<NAME>_{STATIC,SHARED},
- // where <name> is the target name. Here we want to strike a
- // balance between being unique and not too noisy.
- //
- string d ("-DLIB");
+ if (s != nullptr && !mark_cc (*s))
+ {
+ if (s->vars[ctx.var_export_metadata])
+ s = nullptr;
+ else
+ metaonly.second = true;
+ }
- d += sanitize_identifier (
- ucase (const_cast<const string&> (t.name)));
+ // Try to extract library information from pkg-config.
+ //
+ if (a != nullptr || s != nullptr)
+ load_pc (metaonly);
+
+ return r;
+ }
- d += '_';
- d += suffix;
+ // If we cannot acquire the lock then this mean the target has already
+ // been matched and we assume all of this has already been done.
+ //
+ auto lock = [a = *act] (const target* t) -> target_lock
+ {
+ auto l (t != nullptr ? build2::lock (a, *t, true) : target_lock ());
- strings o;
- o.push_back (move (d));
- p.first.get () = move (o);
- }
+ if (l && l.offset == target::offset_matched)
+ {
+ assert ((*t)[a].rule == &file_rule::rule_match);
+ l.unlock ();
}
+
+ return l;
};
- if (ll && (a != nullptr || s != nullptr))
+ target_lock al (lock (a));
+ target_lock sl (lock (s));
+
+ target_lock ll (lock (lt));
+
+ // Set lib{} group members to indicate what's available. Note that we
+ // must be careful here since its possible we have already imported some
+ // of its members.
+ //
+ timestamp mt (timestamp_nonexistent);
+ if (ll)
{
- // Try to extract library information from pkg-config. We only add the
- // default macro if we could not extract more precise information. The
- // idea is that in .pc files that we generate, we copy those macros
- // (or custom ones) from *.export.poptions.
+ // Mark the group since sometimes we use it itself instead of one of
+ // the liba/libs{} members (see process_libraries_impl() for details).
//
- if (pc.first.empty () && pc.second.empty ())
+ // If it's already marked, then it could have been imported during
+ // load (see above).
+ //
+ // @@ TODO: we currently always reload pkgconfig for lt (and above).
+ // Maybe pass NULL lt to pkgconfig_load() in this case?
+ //
+ if (mark_cc (*lt))
{
- if (!pkgconfig_load (act, *p.scope,
- *lt, a, s,
- p.proj, name,
- *pd, sysd, *usrd))
- {
- if (a != nullptr) add_macro (*a, "STATIC");
- if (s != nullptr) add_macro (*s, "SHARED");
- }
+ if (a != nullptr) {lt->a = a; mt = a->mtime ();}
+ if (s != nullptr) {lt->s = s; mt = s->mtime ();}
}
else
- pkgconfig_load (act, *p.scope, *lt, a, s, pc, *pd, sysd, *usrd);
+ ll.unlock ();
+ }
+
+ if (!al) a = nullptr;
+ if (!sl) s = nullptr;
+
+ // If the library already has cc.type, then assume it was either already
+ // imported (e.g., during load) or was matched by a rule.
+ //
+ if (a != nullptr && !mark_cc (*a)) a = nullptr;
+ if (s != nullptr && !mark_cc (*s)) s = nullptr;
+
+ if (a != nullptr) a->group = lt;
+ if (s != nullptr) s->group = lt;
+
+ if (ll && (a != nullptr || s != nullptr))
+ {
+ // Try to extract library information from pkg-config.
+ //
+ load_pc ({false, false} /* metaonly */);
}
// If we have the lock (meaning this is the first time), set the matched
@@ -975,10 +1587,38 @@ namespace build2
//
// Note also that these calls clear target data.
//
- if (al) match_rule (al, file_rule::rule_match);
- if (sl) match_rule (sl, file_rule::rule_match);
+ if (a != nullptr) match_rule (al, file_rule::rule_match);
+ if (s != nullptr) match_rule (sl, file_rule::rule_match);
if (ll)
{
+ // @@ Turns out this has a problem: file_rule won't match/execute
+ // group members. So what happens is that if we have two installed
+ // libraries, say lib{build2} that depends on lib{butl}, then
+ // lib{build2} will have lib{butl} as a prerequisite and file_rule
+ // that matches lib{build2} will update lib{butl} (also matched by
+ // file_rule), but not its members. Later, someone (for example,
+ // the newer() call in append_libraries()) will pick one of the
+ // members assuming it is executed and things will go sideways.
+ //
+ // For now we hacked around the issue but the long term solution is
+ // probably to add to the bin module a special rule that is
+ // registered on the global scope and matches the installed lib{}
+ // targets. This rule will have to both update prerequisites like
+ // the file_rule and group members like the lib_rule (or maybe it
+ // can skip prerequisites since one of the member will do that; in
+ // which case maybe we will be able to reuse lib_rule maybe with
+ // the "all members" flag or some such). A few additional
+ // notes/thoughts:
+ //
+ // - Will be able to stop inheriting lib{} from mtime_target.
+ //
+ // - Will need to register for perform_update/clean like in context
+ // as well as for configure as in the config module (feels like
+ // shouldn't need to register for dist).
+ //
+ // - Will need to test batches, immediate import thoroughly (this
+ // stuff is notoriously tricky to get right in all situations).
+ //
match_rule (ll, file_rule::rule_match);
// Also bless the library group with a "trust me it exists" timestamp.
@@ -987,6 +1627,8 @@ namespace build2
// won't match.
//
lt->mtime (mt);
+
+ ll.unlock (); // Unlock group before members, for good measure.
}
return r;
@@ -1028,5 +1670,85 @@ namespace build2
return r;
}
+
+ void common::
+ append_diag_color_options (cstrings& args) const
+ {
+ switch (cclass)
+ {
+ case compiler_class::msvc:
+ {
+ // MSVC has the /diagnostics: option which has an undocumented value
+ // `color`. It's unclear from which version of MSVC this value is
+ // supported, but it works in 17.0, so let's start from there.
+ //
+ // Note that there is currently no way to disable color in the MSVC
+ // diagnostics specifically (the /diagnostics:* option values are
+ // cumulative and there doesn't seem to be a `color-` value). This
+ // is probably not a big deal since one can just disable the color
+ // globally (--no-diag-color).
+ //
+ // Note that clang-cl appears to use -fansi-escape-codes. See GH
+ // issue #312 for background.
+ //
+ if (show_diag_color ())
+ {
+ if (cvariant.empty () &&
+ (cmaj > 19 || (cmaj == 19 && cmin >= 30)))
+ {
+ // Check for the prefix in case /diagnostics:color- gets added
+ // eventually.
+ //
+ if (!find_option_prefixes ({"/diagnostics:color",
+ "-diagnostics:color"}, args))
+ {
+ args.push_back ("/diagnostics:color");
+ }
+ }
+ }
+
+ break;
+ }
+ case compiler_class::gcc:
+ {
+ // Enable/disable diagnostics color unless a custom option is
+ // specified.
+ //
+ // Supported from GCC 4.9 (8.1 on Windows) and (at least) from Clang
+ // 3.5. Clang supports -f[no]color-diagnostics in addition to the
+ // GCC's spelling.
+ //
+ if (
+#ifndef _WIN32
+ ctype == compiler_type::gcc ? cmaj > 4 || (cmaj == 4 && cmin >= 9) :
+#else
+ ctype == compiler_type::gcc ? cmaj > 8 || (cmaj == 8 && cmin >= 1) :
+#endif
+ ctype == compiler_type::clang ? cmaj > 3 || (cmaj == 3 && cmin >= 5) :
+ false)
+ {
+ if (!(find_option_prefix ("-fdiagnostics-color", args) ||
+ find_option ("-fno-diagnostics-color", args) ||
+ find_option ("-fdiagnostics-plain-output", args) ||
+ (ctype == compiler_type::clang &&
+ (find_option ("-fcolor-diagnostics", args) ||
+ find_option ("-fno-color-diagnostics", args)))))
+ {
+ // Omit -fno-diagnostics-color if stderr is not a terminal (we
+ // know there will be no color in this case and the option will
+ // just add noise, for example, in build logs).
+ //
+ if (const char* o = (
+ show_diag_color () ? "-fdiagnostics-color" :
+ stderr_term ? "-fno-diagnostics-color" :
+ nullptr))
+ args.push_back (o);
+ }
+ }
+
+ break;
+ }
+ }
+ }
}
}