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.cxx695
1 files changed, 521 insertions, 174 deletions
diff --git a/libbuild2/cc/common.cxx b/libbuild2/cc/common.cxx
index cd89c79..2a8bc50 100644
--- a/libbuild2/cc/common.cxx
+++ b/libbuild2/cc/common.cxx
@@ -39,6 +39,11 @@ 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
@@ -72,10 +77,14 @@ namespace build2
// 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.
+ // 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 is always a file.
+ // 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,
@@ -87,7 +96,7 @@ namespace build2
const scope& top_bs,
optional<linfo> top_li,
const dir_paths& top_sysd,
- const mtime_target& l, // liba/libs{} or lib{}
+ const mtime_target& l, // liba/libs{}, libux{}, or lib{}
bool la,
lflags lf,
const function<bool (const target&,
@@ -102,7 +111,8 @@ namespace build2
const string& lang, // lang from cc.type
bool com, // cc. or x.
bool exp)>& proc_opt, // *.export.
- bool self /*= false*/, // Call proc_lib on l?
+ bool self, // Call proc_lib on l?
+ bool proc_opt_group, // Call proc_opt on group instead of member?
library_cache* cache) const
{
library_cache cache_storage;
@@ -115,8 +125,9 @@ namespace build2
chain.push_back (nullptr);
process_libraries_impl (a, top_bs, top_li, top_sysd,
- l, la, lf,
- proc_impl, proc_lib, proc_opt, self,
+ nullptr, l, la, lf,
+ proc_impl, proc_lib, proc_opt,
+ self, proc_opt_group,
cache, &chain, nullptr);
}
@@ -126,6 +137,7 @@ namespace build2
const scope& top_bs,
optional<linfo> top_li,
const dir_paths& top_sysd,
+ const target* lg,
const mtime_target& l,
bool la,
lflags lf,
@@ -142,6 +154,7 @@ namespace build2
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
@@ -149,8 +162,16 @@ namespace build2
// Add the library to the chain.
//
if (self && proc_lib)
+ {
+ if (find (chain->begin (), chain->end (), &l) != chain->end ())
+ fail << "dependency cycle detected involving library " << l;
+
chain->push_back (&l);
+ }
+ // We only lookup public variables so go straight for the public
+ // variable pool.
+ //
auto& vp (top_bs.ctx.var_pool);
do // Breakout loop.
@@ -237,12 +258,14 @@ namespace build2
//
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)
{
- if (!proc_opt (l, t, true, true)) break;
+ if (!proc_opt (ol, t, true, true)) break;
}
else
{
@@ -259,24 +282,24 @@ namespace build2
//
// Note: options come from *.export.* variables.
//
- if (!proc_opt (l, t, false, true) ||
- !proc_opt (l, t, true, true)) break;
+ 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 (l, t, false, false) ||
- !proc_opt (l, t, true, false)) break;
+ if (!proc_opt (ol, t, false, false) ||
+ !proc_opt (ol, t, true, false)) break;
}
}
else
{
// Interface: only add *.export.* (interface dependencies).
//
- if (!proc_opt (l, t, false, true) ||
- !proc_opt (l, t, true, true)) break;
+ if (!proc_opt (ol, t, false, true) ||
+ !proc_opt (ol, t, true, true)) break;
}
}
}
@@ -332,7 +355,7 @@ namespace build2
// 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] ()
+ auto find_sysd = [&top_sysd, &vp, t, cc, same, &bs, &sysd, this] ()
{
// Use the search dirs corresponding to this library scope/type.
//
@@ -341,7 +364,7 @@ namespace build2
: &cast<dir_paths> (
bs.root_scope ()->vars[same
? x_sys_lib_dirs
- : bs.ctx.var_pool[t + ".sys_lib_dirs"]]);
+ : vp[t + ".sys_lib_dirs"]]);
};
auto find_linfo = [top_li, t, cc, &bs, &l, &li] ()
@@ -364,9 +387,10 @@ namespace build2
for (const prerequisite_target& pt: l.prerequisite_targets[a])
{
// Note: adhoc prerequisites are not part of the library metadata
- // protocol (and we should check for adhoc first to avoid races).
+ // protocol (and we should check for adhoc first to avoid races
+ // during execute).
//
- if (pt == nullptr || pt.adhoc ())
+ if (pt.adhoc () || pt == nullptr)
continue;
if (marked (pt))
@@ -380,12 +404,19 @@ namespace build2
(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_impl (a, bs, *li, *sysd,
- *f, la, pt.data,
- proc_impl, proc_lib, proc_opt, true,
+ g, *f, la, pt.data /* lflags */,
+ proc_impl, proc_lib, proc_opt,
+ true /* self */, proc_opt_group,
cache, chain, nullptr);
}
}
@@ -480,7 +511,7 @@ namespace build2
return make_pair (n, s);
};
- auto proc_intf = [&l, cache, chain,
+ 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,
@@ -530,77 +561,125 @@ namespace build2
if (sysd == nullptr) find_sysd ();
if (!li) find_linfo ();
- const mtime_target& t (
- resolve_library (a,
+ 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));
+ 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";
+ }
- // Deduplicate.
+ // 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 dedup_start makes sure we only consider our
- // interface dependencies while maintaining the "through"
- // list.
+ // Note that we used to just check for path being assigned but
+ // on Windows import-installed DLLs may legally have empty
+ // paths.
//
- if (dedup != nullptr)
+ if (w != nullptr)
+ ; // See above.
+ else if (l.ctx.phase == run_phase::match)
{
- if (find (dedup->begin () + dedup_start,
- dedup->end (),
- &t) != dedup->end ())
+ // 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)
{
- ++i;
- continue;
+ if (!t->matched (a))
+ w = "not matched";
}
-
- dedup->push_back (&t);
}
-
- if (proc_lib)
+ else
{
- // 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.
+ // Note that this check we only do if there is proc_lib
+ // (since it's valid to process library's options before
+ // updating it).
//
- const char* w (nullptr);
- if (t.ctx.phase == run_phase::match)
+ if (proc_lib)
{
- size_t o (
- t.state[a].task_count.load (memory_order_consume) -
- t.ctx.count_base ());
-
- if (o != target::offset_applied &&
- o != target::offset_executed)
- w = "not matched";
+ if (t->mtime () == timestamp_unknown)
+ w = "out of date";
}
- else if (t.mtime () == timestamp_unknown)
- w = "out of date";
-
- if (w != nullptr)
- fail << (impl ? "implementation" : "interface")
- << " dependency " << t << " is " << w <<
- info << "mentioned in *.export." << (impl ? "impl_" : "")
- << "libs of target " << l <<
- info << "is it a prerequisite of " << l << "?";
+ }
+
+ if (w != nullptr)
+ {
+ fail << (impl ? "implementation" : "interface")
+ << " dependency " << *t << " is " << w <<
+ info << "mentioned in *.export." << (impl ? "impl_" : "")
+ << "libs of target " << l <<
+ info << "is it a prerequisite of " << l << "?" << endf;
}
// 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?
+ 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,
- t, t.is_a<liba> () || t.is_a<libux> (), 0,
- proc_impl, proc_lib, proc_opt, true,
+ g, *t, la, lf,
+ proc_impl, proc_lib, proc_opt,
+ true /* self */, proc_opt_group,
cache, chain, dedup);
}
@@ -746,9 +825,14 @@ namespace build2
//
// 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.
+ // 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.
//
- const mtime_target& common::
+ // Note: may throw non_existent_library.
+ //
+ pair<const mtime_target&, const target*> common::
resolve_library (action a,
const scope& s,
const name& cn,
@@ -769,7 +853,8 @@ namespace build2
// 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.
+ // 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)
{
@@ -789,7 +874,7 @@ namespace build2
}));
if (i != cache->end ())
- return i->lib;
+ return pair<const mtime_target&, const target*> {i->lib, i->group};
}
else
cache = nullptr; // Do not cache.
@@ -831,26 +916,33 @@ namespace build2
// If this is lib{}/libul{}, pick appropriate member unless we were
// instructed not to.
//
+ 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> ());
if (cache != nullptr)
- cache->push_back (library_cache_entry {lo, cn.type, cn.value, t});
+ cache->push_back (library_cache_entry {lo, cn.type, cn.value, t, g});
- return t;
+ 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,
@@ -858,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 ());
@@ -975,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
@@ -1046,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
@@ -1075,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
@@ -1095,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 ())
{
@@ -1148,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);
}
}
@@ -1200,20 +1351,87 @@ 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.
+ //
+ // @@ 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 lock = [act] (const target* t) -> target_lock
+ 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 ());
+ 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)
+ {
+ // 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));
- if (l && l.offset == target::offset_matched)
+ 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");
+
+ d += sanitize_identifier (
+ ucase (const_cast<const string&> (t.name)));
+
+ d += '_';
+ d += suffix;
+
+ strings o;
+ o.push_back (move (d));
+ p.first = move (o);
+ }
+ }
+ };
+
+ if (pc.first.empty () && pc.second.empty ())
{
- assert ((*t)[act].rule == &file_rule::rule_match);
- l.unlock ();
+ 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");
+ }
}
-
- return l;
+ 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
@@ -1234,6 +1452,85 @@ namespace build2
return p.second;
};
+ // Deal with the load phase case. The rest is already hairy enough so
+ // let's not try to weave this logic into that.
+ //
+ if (!act)
+ {
+ 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).
+ //
+ // 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.
+ //
+ 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))
+ {
+ if (a->vars[ctx.var_export_metadata])
+ a = nullptr;
+ else
+ metaonly.first = true;
+ }
+
+ if (s != nullptr && !mark_cc (*s))
+ {
+ if (s->vars[ctx.var_export_metadata])
+ s = nullptr;
+ else
+ metaonly.second = true;
+ }
+
+ // Try to extract library information from pkg-config.
+ //
+ if (a != nullptr || s != nullptr)
+ load_pc (metaonly);
+
+ 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.
+ //
+ auto lock = [a = *act] (const target* t) -> target_lock
+ {
+ auto l (t != nullptr ? build2::lock (a, *t, true) : target_lock ());
+
+ if (l && l.offset == target::offset_matched)
+ {
+ assert ((*t)[a].rule == &file_rule::rule_match);
+ l.unlock ();
+ }
+
+ return l;
+ };
+
+ 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
@@ -1243,101 +1540,41 @@ namespace build2
timestamp mt (timestamp_nonexistent);
if (ll)
{
- if (s != nullptr) {lt->s = s; mt = s->mtime ();}
- if (a != nullptr) {lt->a = a; mt = a->mtime ();}
-
// Mark the group since sometimes we use it itself instead of one of
// the liba/libs{} members (see process_libraries_impl() for details).
//
- mark_cc (*lt);
+ // 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 (a != nullptr) {lt->a = a; mt = a->mtime ();}
+ if (s != nullptr) {lt->s = s; mt = s->mtime ();}
+ }
+ else
+ ll.unlock ();
}
- target_lock al (lock (a));
- target_lock sl (lock (s));
-
if (!al) a = nullptr;
if (!sl) s = nullptr;
- if (a != nullptr) a->group = lt;
- if (s != nullptr) s->group = lt;
-
- // If the library already has cc.type, then assume it was either
- // already imported or was matched by a rule.
+ // 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;
- // 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 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));
-
- 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");
-
- d += sanitize_identifier (
- ucase (const_cast<const string&> (t.name)));
-
- d += '_';
- d += suffix;
-
- strings o;
- o.push_back (move (d));
- p.first = move (o);
- }
- }
- };
+ 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. 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.
+ // Try to extract library information from pkg-config.
//
- // @@ Should we add .pc files as ad hoc members so pkconfig_save() can
- // use their names when deriving -l-names (this would be especially
- // helpful for binless libraries to get hold of prefix/suffix, etc).
- //
- if (pc.first.empty () && pc.second.empty ())
- {
- if (!pkgconfig_load (act, *p.scope,
- *lt, a, s,
- p.proj, name,
- *pd, sysd, *usrd,
- false /* metadata */))
- {
- if (a != nullptr) add_macro (*a, "STATIC");
- if (s != nullptr) add_macro (*s, "SHARED");
- }
- }
- else
- pkgconfig_load (act, *p.scope,
- *lt, a, s,
- pc,
- *pd, sysd, *usrd,
- false /* metadata */);
+ load_pc ({false, false} /* metaonly */);
}
// If we have the lock (meaning this is the first time), set the matched
@@ -1350,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.
@@ -1362,6 +1627,8 @@ namespace build2
// won't match.
//
lt->mtime (mt);
+
+ ll.unlock (); // Unlock group before members, for good measure.
}
return r;
@@ -1403,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;
+ }
+ }
+ }
}
}