diff options
Diffstat (limited to 'libbuild2/cc/common.cxx')
-rw-r--r-- | libbuild2/cc/common.cxx | 695 |
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; + } + } + } } } |