diff options
Diffstat (limited to 'libbuild2/cc')
42 files changed, 17976 insertions, 4374 deletions
diff --git a/libbuild2/cc/buildfile b/libbuild2/cc/buildfile index fc8809d..7dcd811 100644 --- a/libbuild2/cc/buildfile +++ b/libbuild2/cc/buildfile @@ -4,16 +4,35 @@ # NOTE: shared imports should go into root.build. # include ../ -imp_libs = ../lib{build2} # Implied interface dependency. +impl_libs = ../lib{build2} # Implied interface dependency. -import imp_libs += libpkgconf%lib{pkgconf} +libpkgconf = $config.build2.libpkgconf + +if $libpkgconf + import impl_libs += libpkgconf%lib{pkgconf} +else + import impl_libs += libpkg-config%lib{pkg-config} include ../bin/ -int_libs = ../bin/lib{build2-bin} +intf_libs = ../bin/lib{build2-bin} + +./: lib{build2-cc}: libul{build2-cc}: \ + {hxx ixx txx cxx}{** -pkgconfig-lib* -**.test...} \ + h{msvc-setup} + +libul{build2-cc}: cxx{pkgconfig-libpkgconf}: include = $libpkgconf +libul{build2-cc}: cxx{pkgconfig-libpkg-config}: include = (!$libpkgconf) + +libul{build2-cc}: $intf_libs $impl_libs -./: lib{build2-cc}: libul{build2-cc}: {hxx ixx txx cxx}{** -**.test...} \ - h{msvc-setup} \ - $int_libs $imp_libs +# libc++ std module interface translation unit. +# +# Hopefully temporary, see llvm-project GH issues #73089. +# +# @@ TMP: make sure sync'ed with upstream before release (keep this note). +# +lib{build2-cc}: file{std.cppm} +file{std.cppm}@./: install = data/libbuild2/cc/ # Unit tests. # @@ -38,6 +57,9 @@ for t: cxx{**.test...} obja{*}: cxx.poptions += -DLIBBUILD2_CC_STATIC_BUILD objs{*}: cxx.poptions += -DLIBBUILD2_CC_SHARED_BUILD +if $libpkgconf + cxx.poptions += -DBUILD2_LIBPKGCONF + if ($cxx.target.class == 'windows') cxx.libs += $regex.apply(advapi32 ole32 oleaut32, \ '(.+)', \ @@ -50,7 +72,7 @@ if ($cxx.target.class == 'windows') lib{build2-cc}: { cxx.export.poptions = "-I$out_root" "-I$src_root" - cxx.export.libs = $int_libs + cxx.export.libs = $intf_libs } liba{build2-cc}: cxx.export.poptions += -DLIBBUILD2_CC_STATIC 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; + } + } + } } } diff --git a/libbuild2/cc/common.hxx b/libbuild2/cc/common.hxx index f072591..cb85632 100644 --- a/libbuild2/cc/common.hxx +++ b/libbuild2/cc/common.hxx @@ -32,10 +32,12 @@ namespace build2 { lang x_lang; - const char* x; // Module name ("c", "cxx"). - const char* x_name; // Compiler name ("c", "c++"). - const char* x_default; // Compiler default ("gcc", "g++"). - const char* x_pext; // Preprocessed source extension (".i", ".ii"). + const char* x; // Module name ("c", "cxx"). + const char* x_name; // Compiler name ("c", "c++"). + const char* x_obj_name; // Same for Objective-X ("obj-c", "obj-c++"). + const char* x_default; // Compiler default ("gcc", "g++"). + const char* x_pext; // Preprocessed source extension (".i", ".ii"). + const char* x_obj_pext; // Same for Objective-X (".mi", ".mii"). // Array of modules that can hint us the toolchain, terminate with // NULL. @@ -64,14 +66,15 @@ namespace build2 const variable& config_x_loptions; const variable& config_x_aoptions; const variable& config_x_libs; - const variable* config_x_translatable_headers; + const variable& config_x_internal_scope; + const variable* config_x_translate_include; const variable& x_path; // Compiler process path. const variable& x_mode; // Compiler mode options. const variable& x_c_path; // Compiler path as configured. const variable& x_c_mode; // Compiler mode as configured. const variable& x_sys_lib_dirs; // System library search directories. - const variable& x_sys_inc_dirs; // System header search directories. + const variable& x_sys_hdr_dirs; // System header search directories. const variable& x_std; const variable& x_poptions; @@ -79,7 +82,9 @@ namespace build2 const variable& x_loptions; const variable& x_aoptions; const variable& x_libs; - const variable* x_translatable_headers; + const variable& x_internal_scope; + const variable& x_internal_libs; + const variable* x_translate_include; const variable& c_poptions; // cc.* const variable& c_coptions; @@ -91,13 +96,16 @@ namespace build2 const variable& x_export_coptions; const variable& x_export_loptions; const variable& x_export_libs; - const variable& x_export_imp_libs; + const variable& x_export_impl_libs; const variable& c_export_poptions; // cc.export.* const variable& c_export_coptions; const variable& c_export_loptions; const variable& c_export_libs; - const variable& c_export_imp_libs; + const variable& c_export_impl_libs; + + const variable& c_pkgconfig_include; + const variable& c_pkgconfig_lib; const variable& x_stdlib; // x.stdlib @@ -107,7 +115,9 @@ namespace build2 const variable& c_type; // cc.type const variable& c_system; // cc.system const variable& c_module_name; // cc.module_name + const variable& c_importable; // cc.importable const variable& c_reprocess; // cc.reprocess + const variable& c_serialize; // cc.serialize const variable& x_preprocessed; // x.preprocessed const variable* x_symexport; // x.features.symexport @@ -149,19 +159,21 @@ namespace build2 struct data: config_data { - const char* x_compile; // Rule names. - const char* x_link; - const char* x_install; - const char* x_uninstall; + string x_compile; // Rule names. + string x_link; + string x_install; // Cached values for some commonly-used variables/values. // + const compiler_id& cid; // x.id compiler_type ctype; // x.id.type const string& cvariant; // x.id.variant compiler_class cclass; // x.class uint64_t cmaj; // x.version.major uint64_t cmin; // x.version.minor + uint64_t cvmaj; // x.variant_version.major (0 if no variant) + uint64_t cvmin; // x.variant_version.minor (0 if no variant) const process_path& cpath; // x.path const strings& cmode; // x.mode (options) @@ -169,41 +181,87 @@ namespace build2 const string& tsys; // x.target.system const string& tclass; // x.target.class + const string& env_checksum; // config_module::env_checksum + bool modules; // x.features.modules bool symexport; // x.features.symexport - const strings* xlate_hdr; // x.translatable_headers (NULL if - // unused/empty). + enum class internal_scope {current, base, root, bundle, strong, weak}; + + optional<internal_scope> iscope; // x.internal.scope + const scope* iscope_current; + + const scope* + effective_iscope (const scope& bs) const; + + const strings* c_ilibs; // cc.internal.libs + const strings* x_ilibs; // x.internal.libs + + build2::cc::importable_headers* importable_headers; // The order of sys_*_dirs is the mode entries first, followed by the - // compiler built-in entries, and finished off with any extra entries - // (e.g., fallback directories such as /usr/local/*). + // extra entries (e.g., /usr/local/*), followed by the compiler built-in + // entries. + // + // Note that even if we wanted to, we wouldn't be able to support extra + // trailing (after built-in) directories since we would need a portable + // equivalent of -idirafter for both headers and libraries. // const dir_paths& sys_lib_dirs; // x.sys_lib_dirs - const dir_paths& sys_inc_dirs; // x.sys_inc_dirs + const dir_paths& sys_hdr_dirs; // x.sys_hdr_dirs const dir_paths* sys_mod_dirs; // compiler_info::sys_mod_dirs - size_t sys_lib_dirs_mode; // Number of leading mode entries (0 if none). - size_t sys_inc_dirs_mode; + size_t sys_lib_dirs_mode; // Number of mode entries (0 if none). + size_t sys_hdr_dirs_mode; size_t sys_mod_dirs_mode; - size_t sys_lib_dirs_extra; // First trailing extra entry (size if none). - size_t sys_inc_dirs_extra; + size_t sys_lib_dirs_extra; // Number of extra entries (0 if none). + size_t sys_hdr_dirs_extra; + // Note that x_obj is patched in by the x.objx module. So it stays NULL + // if Objective-X compilation is not enabled. Similarly for x_asp except + // here we don't have duality and it's purely to signal (by the c.as-cpp + // module) that it's enabled. + // const target_type& x_src; // Source target type (c{}, cxx{}). const target_type* x_mod; // Module target type (mxx{}), if any. + const target_type& x_inc; // Includable base target type (e.g., c_inc{}). + const target_type* x_obj; // Objective-X target type (m{}, mm{}). + const target_type* x_asp; // Assembler with CPP target type (S{}). + + // Check if an object (target, prerequisite, etc) is an Objective-X + // source. + // + template <typename T> + bool + x_objective (const T& t) const + { + return x_obj != nullptr && t.is_a (*x_obj); + } + + // Check if an object (target, prerequisite, etc) is an Assembler with + // C preprocessor source. + // + template <typename T> + bool + x_assembler_cpp (const T& t) const + { + return x_asp != nullptr && t.is_a (*x_asp); + } // Array of target types that are considered the X-language headers // (excluding h{} except for C). Keep them in the most likely to appear // order with the "real header" first and terminated with NULL. // - const target_type* const* x_hdr; + const target_type* const* x_hdrs; + // Check if an object (target, prerequisite, etc) is a header. + // template <typename T> bool x_header (const T& t, bool c_hdr = true) const { - for (const target_type* const* ht (x_hdr); *ht != nullptr; ++ht) + for (const target_type* const* ht (x_hdrs); *ht != nullptr; ++ht) if (t.is_a (**ht)) return true; @@ -214,7 +272,7 @@ namespace build2 // extensions to target types. Keep them in the most likely to appear // order and terminate with NULL. // - const target_type* const* x_inc; + const target_type* const* x_incs; // Aggregate-like constructor with from-base support. // @@ -222,42 +280,50 @@ namespace build2 const char* compile, const char* link, const char* install, - const char* uninstall, - compiler_type ct, - const string& cv, + const compiler_id& ci, compiler_class cl, uint64_t mj, uint64_t mi, + uint64_t vmj, uint64_t vmi, const process_path& path, const strings& mode, const target_triplet& tgt, + const string& env_cs, bool fm, bool fs, + optional<internal_scope> is, const scope* isc, + const strings* cils, const strings* xils, const dir_paths& sld, - const dir_paths& sid, + const dir_paths& shd, const dir_paths* smd, - size_t slm, size_t sim, size_t smm, - size_t sle, size_t sie, + size_t slm, size_t shm, size_t smm, + size_t sle, size_t she, const target_type& src, const target_type* mod, - const target_type* const* hdr, - const target_type* const* inc) + const target_type& inc, + const target_type* const* hdrs, + const target_type* const* incs) : config_data (cd), x_compile (compile), x_link (link), x_install (install), - x_uninstall (uninstall), - ctype (ct), cvariant (cv), cclass (cl), + cid (ci), ctype (ci.type), cvariant (ci.variant), cclass (cl), cmaj (mj), cmin (mi), + cvmaj (vmj), cvmin (vmi), cpath (path), cmode (mode), ctgt (tgt), tsys (ctgt.system), tclass (ctgt.class_), + env_checksum (env_cs), modules (fm), symexport (fs), - xlate_hdr (nullptr), - sys_lib_dirs (sld), sys_inc_dirs (sid), sys_mod_dirs (smd), - sys_lib_dirs_mode (slm), sys_inc_dirs_mode (sim), + iscope (is), iscope_current (isc), + c_ilibs (cils), x_ilibs (xils), + importable_headers (nullptr), + sys_lib_dirs (sld), sys_hdr_dirs (shd), sys_mod_dirs (smd), + sys_lib_dirs_mode (slm), sys_hdr_dirs_mode (shm), sys_mod_dirs_mode (smm), - sys_lib_dirs_extra (sle), sys_inc_dirs_extra (sie), - x_src (src), x_mod (mod), x_hdr (hdr), x_inc (inc) {} + sys_lib_dirs_extra (sle), sys_hdr_dirs_extra (she), + x_src (src), x_mod (mod), x_inc (inc), + x_obj (nullptr), x_asp (nullptr), + x_hdrs (hdrs), x_incs (incs) {} }; class LIBBUILD2_CC_SYMEXPORT common: public data @@ -268,20 +334,60 @@ namespace build2 // Library handling. // public: + struct library_cache_entry + { + optional<lorder> lo; + string type; // name::type + string value; // name::value + reference_wrapper<const mtime_target> lib; + const target* group; + }; + + using library_cache = small_vector<library_cache_entry, 32>; + + // The prerequisite_target::include bit that indicates a library + // member has been picked from the group. + // + static const uintptr_t include_group = 0x100; + void process_libraries ( action, const scope&, - linfo, + optional<linfo>, const dir_paths&, - const file&, + const mtime_target&, bool, lflags, - const function<bool (const file&, bool)>&, - const function<void (const file* const*, const string&, lflags, bool)>&, - const function<void (const file&, const string&, bool, bool)>&, + const function<bool (const target&, bool)>&, + const function<bool (const target* const*, + const small_vector<reference_wrapper<const string>, 2>&, + lflags, const string*, bool)>&, + const function<bool (const target&, const string&, bool, bool)>&, + bool = false, bool = false, - small_vector<const file*, 16>* = nullptr) const; + library_cache* = nullptr) const; + + void + process_libraries_impl ( + action, + const scope&, + optional<linfo>, + const dir_paths&, + const target*, + const mtime_target&, + bool, + lflags, + const function<bool (const target&, bool)>&, + const function<bool (const target* const*, + const small_vector<reference_wrapper<const string>, 2>&, + lflags, const string*, bool)>&, + const function<bool (const target&, const string&, bool, bool)>&, + bool, + bool, + library_cache*, + small_vector<const target*, 32>*, + small_vector<const target*, 32>*) const; const target* search_library (action a, @@ -308,14 +414,20 @@ namespace build2 } public: - const file& + pair<const mtime_target&, const target*> resolve_library (action, const scope&, const name&, const dir_path&, - linfo, + optional<linfo>, const dir_paths&, - optional<dir_paths>&) const; + optional<dir_paths>&, + library_cache* = nullptr) const; + + struct non_existent_library + { + const mtime_target& target; + }; template <typename T> static ulock @@ -329,7 +441,7 @@ namespace build2 tracer&); target* - search_library (action, + search_library (optional<action>, const dir_paths&, optional<dir_paths>&, const prerequisite_key&, @@ -349,13 +461,16 @@ namespace build2 // Alternative search logic for VC (msvc.cxx). // - bin::liba* + // The second half is false if we should poison the binless search via + // the common .pc file. + // + pair<bin::liba*, bool> msvc_search_static (const process_path&, const dir_path&, const prerequisite_key&, bool existing) const; - bin::libs* + pair<bin::libs*, bool> msvc_search_shared (const process_path&, const dir_path&, const prerequisite_key&, @@ -375,25 +490,33 @@ namespace build2 bool) const; void - pkgconfig_load (action, const scope&, + pkgconfig_load (optional<action>, const scope&, bin::lib&, bin::liba*, bin::libs*, const pair<path, path>&, const dir_path&, const dir_paths&, - const dir_paths&) const; + const dir_paths&, + pair<bool, bool>) const; bool - pkgconfig_load (action, const scope&, + pkgconfig_load (optional<action>, const scope&, bin::lib&, bin::liba*, bin::libs*, const optional<project_name>&, const string&, const dir_path&, const dir_paths&, - const dir_paths&) const; + const dir_paths&, + pair<bool, bool>) const; + + // Append compiler-specific diagnostics color options as necessary. + // + void + append_diag_color_options (cstrings&) const; }; } } +#include <libbuild2/cc/common.ixx> #include <libbuild2/cc/common.txx> #endif // LIBBUILD2_CC_COMMON_HXX diff --git a/libbuild2/cc/common.ixx b/libbuild2/cc/common.ixx new file mode 100644 index 0000000..417efbd --- /dev/null +++ b/libbuild2/cc/common.ixx @@ -0,0 +1,27 @@ +// file : libbuild2/cc/common.ixx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +namespace build2 +{ + namespace cc + { + inline const scope* data:: + effective_iscope (const scope& bs) const + { + if (iscope) + { + switch (*iscope) + { + case internal_scope::current: return iscope_current; + case internal_scope::base: return &bs; + case internal_scope::root: return bs.root_scope (); + case internal_scope::bundle: return bs.bundle_scope (); + case internal_scope::strong: return bs.strong_scope (); + case internal_scope::weak: return bs.weak_scope (); + } + } + + return nullptr; + } + } +} diff --git a/libbuild2/cc/common.txx b/libbuild2/cc/common.txx index d14f966..8c80686 100644 --- a/libbuild2/cc/common.txx +++ b/libbuild2/cc/common.txx @@ -19,15 +19,18 @@ namespace build2 bool exist, tracer& trace) { - auto p (ctx.targets.insert_locked (T::static_type, - move (dir), - path_cast<dir_path> (out.effect), - name, - move (ext), - target_decl::implied, - trace)); + auto p (ctx.targets.insert_locked ( + T::static_type, + move (dir), + dir_path (out.effect_string ()).normalize (), + name, + move (ext), + target_decl::implied, + trace)); + + if (exist && p.second) + throw non_existent_library {p.first.template as<mtime_target> ()}; - assert (!exist || !p.second); r = &p.first.template as<T> (); return move (p.second); } diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index b96c39d..2e4775e 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -3,8 +3,11 @@ #include <libbuild2/cc/compile-rule.hxx> +#include <cerrno> #include <cstdlib> // exit() -#include <cstring> // strlen(), strchr() +#include <cstring> // strlen(), strchr(), strncmp() + +#include <libbutl/path-pattern.hxx> #include <libbuild2/file.hxx> #include <libbuild2/depdb.hxx> @@ -14,6 +17,7 @@ #include <libbuild2/algorithm.hxx> #include <libbuild2/filesystem.hxx> // mtime() #include <libbuild2/diagnostics.hxx> +#include <libbuild2/make-parser.hxx> #include <libbuild2/bin/target.hxx> @@ -172,14 +176,71 @@ namespace build2 if (s == "includes") return preprocessed::includes; if (s == "modules") return preprocessed::modules; if (s == "all") return preprocessed::all; - throw invalid_argument ("invalid preprocessed value '" + s + "'"); + throw invalid_argument ("invalid preprocessed value '" + s + '\''); + } + + // Return true if the compiler supports -isystem (GCC class) or + // /external:I (MSVC class). + // + static inline bool + isystem (const data& d) + { + switch (d.cclass) + { + case compiler_class::gcc: + { + return true; + } + case compiler_class::msvc: + { + if (d.cvariant.empty ()) + { + // While /external:I is available since 15.6, it required + // /experimental:external (and was rather buggy) until 16.10. + // + return d.cmaj > 19 || (d.cmaj == 19 && d.cmin >= 29); + } + else if (d.cvariant != "clang") + { + // clang-cl added support for /external:I (by translating it to + // -isystem) in version 13. + // + return d.cvmaj >= 13; + } + else + return false; + } + } + + return false; } + optional<path> compile_rule:: + find_system_header (const path& f) const + { + path p; // Reuse the buffer. + for (const dir_path& d: sys_hdr_dirs) + { + if (file_exists ((p = d, p /= f), + true /* follow_symlinks */, + true /* ignore_errors */)) + return p; + } + + return nullopt; + } + + // Note that we don't really need this for clean (where we only need + // unrefined unit type) so we could make this update-only. But let's keep + // it simple for now. Note that now we do need the source prerequisite + // type in clean to deal with Objective-X. + // struct compile_rule::match_data { - explicit - match_data (unit_type t, const prerequisite_member& s) - : type (t), src (s) {} + match_data (const compile_rule& r, + unit_type t, + const prerequisite_member& s) + : type (t), src (s), rule (r) {} unit_type type; preprocessed pp = preprocessed::none; @@ -188,54 +249,87 @@ namespace build2 bool touch = false; // Target needs to be touched. timestamp mt = timestamp_unknown; // Target timestamp. prerequisite_member src; - auto_rmfile psrc; // Preprocessed source, if any. + file_cache::entry psrc; // Preprocessed source, if any. path dd; // Dependency database path. size_t header_units = 0; // Number of imported header units. module_positions modules = {0, 0, 0}; // Positions of imported modules. + + const compile_rule& rule; + + target_state + operator() (action a, const target& t) + { + return rule.perform_update (a, t, *this); + } }; compile_rule:: - compile_rule (data&& d) + compile_rule (data&& d, const scope& rs) : common (move (d)), - rule_id (string (x) += ".compile 4") + rule_id (string (x) += ".compile 6") { - static_assert (sizeof (match_data) <= target::data_size, - "insufficient space"); + // Locate the header cache (see enter_header() for details). + // + { + string mn (string (x) + ".config"); + + header_cache_ = rs.find_module<config_module> (mn); // Must be there. + + const scope* ws (rs.weak_scope ()); + if (ws != &rs) + { + const scope* s (&rs); + do + { + s = s->parent_scope ()->root_scope (); + + if (const auto* m = s->find_module<config_module> (mn)) + header_cache_ = m; + + } while (s != ws); + } + } } template <typename T> void compile_rule:: - append_sys_inc_options (T& args) const + append_sys_hdr_options (T& args) const { - assert (sys_inc_dirs_extra <= sys_inc_dirs.size ()); + assert (sys_hdr_dirs_mode + sys_hdr_dirs_extra <= sys_hdr_dirs.size ()); // Note that the mode options are added as part of cmode. // - auto b (sys_inc_dirs.begin () + sys_inc_dirs_mode); - auto m (sys_inc_dirs.begin () + sys_inc_dirs_extra); - auto e (sys_inc_dirs.end ()); + auto b (sys_hdr_dirs.begin () + sys_hdr_dirs_mode); + auto x (b + sys_hdr_dirs_extra); - // Note: starting from 15.6, MSVC gained /external:I option though it + // Add extras. + // + // Note: starting from 16.10, MSVC gained /external:I option though it // doesn't seem to affect the order, only "system-ness". // append_option_values ( args, - cclass == compiler_class::gcc ? "-idirafter" : - cclass == compiler_class::msvc ? "/I" : "-I", - m, e, + cclass == compiler_class::gcc ? "-isystem" : + cclass == compiler_class::msvc ? (isystem (*this) + ? "/external:I" + : "/I") : "-I", + b, x, [] (const dir_path& d) {return d.string ().c_str ();}); // For MSVC if we have no INCLUDE environment variable set, then we // add all of them. But we want extras to come first. Note also that // clang-cl takes care of this itself. // + // Note also that we don't use /external:I to have consistent semantics + // with when INCLUDE is set (there is separate /external:env for that). + // if (ctype == compiler_type::msvc && cvariant != "clang") { if (!getenv ("INCLUDE")) { append_option_values ( args, "/I", - b, m, + x, sys_hdr_dirs.end (), [] (const dir_path& d) {return d.string ().c_str ();}); } } @@ -260,6 +354,35 @@ namespace build2 case lang::c: o1 = "/TC"; break; case lang::cxx: o1 = "/TP"; break; } + + // Note: /interface and /internalPartition are in addition to /TP. + // + switch (md.type) + { + case unit_type::non_modular: + case unit_type::module_impl: + { + break; + } + case unit_type::module_intf: + case unit_type::module_intf_part: + { + o2 = "/interface"; + break; + } + case unit_type::module_impl_part: + { + o2 = "/internalPartition"; + break; + } + case unit_type::module_header: + { + //@@ MODHDR TODO: /exportHeader + assert (false); + break; + } + } + break; } case compiler_class::gcc: @@ -278,11 +401,20 @@ namespace build2 case unit_type::module_impl: { o1 = "-x"; - switch (x_lang) + + if (x_assembler_cpp (md.src)) + o2 = "assembler-with-cpp"; + else { - case lang::c: o2 = "c"; break; - case lang::cxx: o2 = "c++"; break; + bool obj (x_objective (md.src)); + + switch (x_lang) + { + case lang::c: o2 = obj ? "objective-c" : "c"; break; + case lang::cxx: o2 = obj ? "objective-c++" : "c++"; break; + } } + break; } case unit_type::module_intf: @@ -322,9 +454,11 @@ namespace build2 default: assert (false); } + break; } } + break; } } @@ -348,7 +482,7 @@ namespace build2 } bool compile_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { tracer trace (x, "compile_rule::match"); @@ -381,13 +515,15 @@ namespace build2 // For a header unit we check the "real header" plus the C header. // - if (ut == unit_type::module_header ? p.is_a (**x_hdr) || p.is_a<h> () : - ut == unit_type::module_intf ? p.is_a (*x_mod) : - p.is_a (x_src)) + if (ut == unit_type::module_header ? p.is_a (**x_hdrs) || p.is_a<h> () : + ut == unit_type::module_intf ? p.is_a (*x_mod) : + p.is_a (x_src) || + (x_asp != nullptr && p.is_a (*x_asp)) || + (x_obj != nullptr && p.is_a (*x_obj))) { // Save in the target's auxiliary storage. // - t.data (match_data (ut, p)); + t.data (a, match_data (*this, ut, p)); return true; } } @@ -398,41 +534,49 @@ namespace build2 // Append or hash library options from a pair of *.export.* variables // (first is x.* then cc.*) recursively, prerequisite libraries first. + // If common is true, then only append common options from the lib{} + // groups. // template <typename T> void compile_rule:: append_library_options (appended_libraries& ls, T& args, const scope& bs, - action a, const file& l, bool la, linfo li) const + const scope* is, // Internal scope. + action a, const file& l, bool la, + linfo li, bool common, + library_cache* lib_cache) const { struct data { appended_libraries& ls; T& args; - } d {ls, args}; + const scope* is; + } d {ls, args, is}; // See through utility libraries. // - auto imp = [] (const file& l, bool la) {return la && l.is_a<libux> ();}; + auto imp = [] (const target& l, bool la) {return la && l.is_a<libux> ();}; - auto opt = [&d, this] (const file& l, + auto opt = [&d, this] (const target& l, // Note: could be lib{} const string& t, bool com, bool exp) { // Note that in our model *.export.poptions are always "interface", // even if set on liba{}/libs{}, unlike loptions. // if (!exp) // Ignore libux. - return; + return true; // Suppress duplicates. // // Compilation is the simple case: we can add the options on the first - // occurrence of the library and ignore all subsequent occurrences. - // See GitHub issue #114 for details. + // occurrence of the library and ignore (and prune) all subsequent + // occurrences. See GitHub issue #114 for details. // if (find (d.ls.begin (), d.ls.end (), &l) != d.ls.end ()) - return; + return false; + // Note: go straight for the public variable pool. + // const variable& var ( com ? c_export_poptions @@ -440,26 +584,166 @@ namespace build2 ? x_export_poptions : l.ctx.var_pool[t + ".export.poptions"])); - append_options (d.args, l, var); + if (const strings* ops = cast_null<strings> (l[var])) + { + // If enabled, remap -I to -isystem or /external:I for paths that + // are outside of the internal scope provided the library is not + // whitelisted. + // + auto whitelist = [&l] (const strings& pats) + { + return find_if (pats.begin (), pats.end (), + [&l] (const string& pat) + { + return path_match (l.name, pat); + }) != pats.end (); + }; + + const scope* is (d.is); + + if (is != nullptr && c_ilibs != nullptr && whitelist (*c_ilibs)) + is = nullptr; + + if (is != nullptr && x_ilibs != nullptr && whitelist (*x_ilibs)) + is = nullptr; + + for (auto i (ops->begin ()), e (ops->end ()); i != e; ++i) + { + const string& o (*i); + + if (is != nullptr) + { + // See if this is -I<dir> or -I <dir> (or /I... for MSVC). + // + // While strictly speaking we can only attempt to recognize + // options until we hit something unknown (after that, we don't + // know what's an option and what's a value), it doesn't seem + // likely to cause issues here, where we only expect to see -I, + // -D, and -U. + // + bool msvc (cclass == compiler_class::msvc); + + if ((o[0] == '-' || (msvc && o[0] == '/')) && o[1] == 'I') + { + bool sep (o.size () == 2); // -I<dir> vs -I <dir> + + const char* v (nullptr); + size_t vn (0); + if (sep) + { + if (i + 1 == e) + ; // Append as is and let the compiler complain. + else + { + ++i; + v = i->c_str (); + vn = i->size (); + } + } + else + { + v = o.c_str () + 2; + vn = o.size () - 2; + } + + if (v != nullptr) + { + // See if we need to translate the option for this path. We + // only do this for absolute paths and try to optimize for + // the already normalized ones. + // + if (path_traits::absolute (v)) + { + const char* p (nullptr); + size_t pn (0); + + dir_path nd; + if (path_traits::normalized (v, vn, true /* separators */)) + { + p = v; + pn = vn; + } + else + try + { + nd = dir_path (v, vn); + nd.normalize (); + p = nd.string ().c_str (); + pn = nd.string ().size (); + } + catch (const invalid_path&) + { + // Ignore this path. + } + + if (p != nullptr) + { + auto sub = [p, pn] (const dir_path& d) + { + return path_traits::sub ( + p, pn, + d.string ().c_str (), d.string ().size ()); + }; + + // Translate if it's neither in src nor in out of the + // internal scope. + // + if (!sub (is->src_path ()) && + (is->out_eq_src () || !sub (is->out_path ()))) + { + // Note: must use original value (path is temporary). + // + append_option (d.args, + msvc ? "/external:I" : "-isystem"); + append_option (d.args, v); + continue; + } + } + } + + // If not translated, preserve the original form. + // + append_option (d.args, o.c_str ()); + if (sep) append_option (d.args, v); + + continue; + } + } + } + + append_option (d.args, o.c_str ()); + } + } // From the process_libraries() semantics we know that the final call // is always for the common options. // if (com) d.ls.push_back (&l); + + return true; }; process_libraries (a, bs, li, sys_lib_dirs, - l, la, 0, // Hack: lflags unused. - imp, nullptr, opt); + l, la, 0, // lflags unused. + imp, nullptr, opt, + false /* self */, + common /* proc_opt_group */, + lib_cache); } void compile_rule:: append_library_options (appended_libraries& ls, strings& args, const scope& bs, - action a, const file& l, bool la, linfo li) const + action a, const file& l, bool la, + linfo li, + bool common, + bool original) const { - append_library_options<strings> (ls, args, bs, a, l, la, li); + const scope* is (!original && isystem (*this) + ? effective_iscope (bs) + : nullptr); + append_library_options (ls, args, bs, is, a, l, la, li, common, nullptr); } template <typename T> @@ -468,7 +752,16 @@ namespace build2 const scope& bs, action a, const target& t, linfo li) const { + auto iscope = [this, &bs, is = optional<const scope*> ()] () mutable + { + if (!is) + is = isystem (*this) ? effective_iscope (bs) : nullptr; + + return *is; + }; + appended_libraries ls; + library_cache lc; for (prerequisite_member p: group_prerequisite_members (a, t)) { @@ -488,7 +781,13 @@ namespace build2 (la = (f = pt->is_a<libux> ())) || ( (f = pt->is_a<libs> ()))) { - append_library_options (ls, args, bs, a, *f, la, li); + append_library_options (ls, + args, + bs, iscope (), + a, *f, la, + li, + false /* common */, + &lc); } } } @@ -498,35 +797,61 @@ namespace build2 // recursively, prerequisite libraries first. // void compile_rule:: - append_library_prefixes (prefix_map& m, + append_library_prefixes (appended_libraries& ls, prefix_map& pm, const scope& bs, - action a, - target& t, - linfo li) const + action a, const target& t, linfo li) const { - auto imp = [] (const file& l, bool la) {return la && l.is_a<libux> ();}; + struct data + { + appended_libraries& ls; + prefix_map& pm; + } d {ls, pm}; + + auto imp = [] (const target& l, bool la) {return la && l.is_a<libux> ();}; - auto opt = [&m, this] ( - const file& l, const string& t, bool com, bool exp) + auto opt = [&d, this] (const target& lt, + const string& t, bool com, bool exp) { if (!exp) - return; + return true; - const variable& var ( - com - ? c_export_poptions - : (t == x - ? x_export_poptions - : l.ctx.var_pool[t + ".export.poptions"])); + const file& l (lt.as<file> ()); + + // Suppress duplicates like in append_library_options(). + // + if (find (d.ls.begin (), d.ls.end (), &l) != d.ls.end ()) + return false; + + // If this target does not belong to any project (e.g, an "imported as + // installed" library), then it can't possibly generate any headers + // for us. + // + if (const scope* rs = l.base_scope ().root_scope ()) + { + // Note: go straight for the public variable pool. + // + const variable& var ( + com + ? c_export_poptions + : (t == x + ? x_export_poptions + : l.ctx.var_pool[t + ".export.poptions"])); + + append_prefixes (d.pm, *rs, l, var); + } - append_prefixes (m, l, var); + if (com) + d.ls.push_back (&l); + + return true; }; // The same logic as in append_library_options(). // - const function<bool (const file&, bool)> impf (imp); - const function<void (const file&, const string&, bool, bool)> optf (opt); + const function<bool (const target&, bool)> impf (imp); + const function<bool (const target&, const string&, bool, bool)> optf (opt); + library_cache lib_cache; for (prerequisite_member p: group_prerequisite_members (a, t)) { if (include (a, t, p) != include_type::normal) // Excluded/ad hoc. @@ -544,75 +869,15 @@ namespace build2 continue; process_libraries (a, bs, li, sys_lib_dirs, - pt->as<file> (), la, 0, // Hack: lflags unused. - impf, nullptr, optf); + pt->as<file> (), la, 0, // lflags unused. + impf, nullptr, optf, + false /* self */, + false /* proc_opt_group */, + &lib_cache); } } } - // Update the target during the match phase. Return true if it has changed - // or if the passed timestamp is not timestamp_unknown and is older than - // the target. - // - // This function is used to make sure header dependencies are up to date. - // - // There would normally be a lot of headers for every source file (think - // all the system headers) and just calling execute_direct() on all of - // them can get expensive. At the same time, most of these headers are - // existing files that we will never be updating (again, system headers, - // for example) and the rule that will match them is the fallback - // file_rule. That rule has an optimization: it returns noop_recipe (which - // causes the target state to be automatically set to unchanged) if the - // file is known to be up to date. So we do the update "smartly". - // - static bool - update (tracer& trace, action a, const target& t, timestamp ts) - { - const path_target* pt (t.is_a<path_target> ()); - - if (pt == nullptr) - ts = timestamp_unknown; - - target_state os (t.matched_state (a)); - - if (os == target_state::unchanged) - { - if (ts == timestamp_unknown) - return false; - else - { - // We expect the timestamp to be known (i.e., existing file). - // - timestamp mt (pt->mtime ()); - assert (mt != timestamp_unknown); - return mt > ts; - } - } - else - { - // We only want to return true if our call to execute() actually - // caused an update. In particular, the target could already have been - // in target_state::changed because of a dependency extraction run for - // some other source file. - // - // @@ MT perf: so we are going to switch the phase and execute for - // any generated header. - // - phase_switch ps (t.ctx, run_phase::execute); - target_state ns (execute_direct (a, t)); - - if (ns != os && ns != target_state::unchanged) - { - l6 ([&]{trace << "updated " << t - << "; old state " << os - << "; new state " << ns;}); - return true; - } - else - return ts != timestamp_unknown ? pt->newer (ts, ns) : false; - } - } - recipe compile_rule:: apply (action a, target& xt) const { @@ -620,7 +885,7 @@ namespace build2 file& t (xt.as<file> ()); // Either obj*{} or bmi*{}. - match_data& md (t.data<match_data> ()); + match_data& md (t.data<match_data> (a)); context& ctx (t.ctx); @@ -713,7 +978,9 @@ namespace build2 // // Note: ut is still unrefined. // - if (ut == unit_type::module_intf && cast_true<bool> (t[b_binless])) + if ((ut == unit_type::module_intf || + ut == unit_type::module_intf_part || + ut == unit_type::module_impl_part) && cast_true<bool> (t[b_binless])) { // The module interface unit can be the same as an implementation // (e.g., foo.mxx and foo.cxx) which means obj*{} targets could @@ -746,8 +1013,6 @@ namespace build2 // wait_guard wg (ctx, ctx.count_busy (), t[a].task_count, true); - target_state src_ts1 (target_state::unknown), src_ts2 (src_ts1); - size_t src_i (~0); // Index of src target. size_t start (pts.size ()); // Index of the first to be added. for (prerequisite_member p: group_prerequisite_members (a, t)) @@ -759,8 +1024,8 @@ namespace build2 continue; // A dependency on a library is there so that we can get its - // *.export.poptions, modules, etc. This is the library metadata - // protocol. See also append_library_options(). + // *.export.poptions, modules, importable headers, etc. This is the + // library metadata protocol. See also append_library_options(). // if (pi == include_type::normal && (p.is_a<libx> () || @@ -773,8 +1038,14 @@ namespace build2 // Handle (phase two) imported libraries. We know that for such // libraries we don't need to do match() in order to get options // (if any, they would be set by search_library()). But we do need - // to match it if we may need its modules (see search_modules() - // for details). + // to match it if we may need its modules or importable headers + // (see search_modules(), make_header_sidebuild() for details). + // + // Well, that was the case until we've added support for immediate + // importation of libraries, which happens during the load phase + // and natually leaves the library unmatched. While we could have + // returned from search_library() an indication of whether the + // library has been matched, this doesn't seem worth the trouble. // if (p.proj ()) { @@ -783,8 +1054,10 @@ namespace build2 usr_lib_dirs, p.prerequisite); +#if 0 if (pt != nullptr && !modules) continue; +#endif } if (pt == nullptr) @@ -812,28 +1085,21 @@ namespace build2 { pt = &p.search (t); - if (a.operation () == clean_id && !pt->dir.sub (rs.out_path ())) + if (pt == dir || + (a.operation () == clean_id && !pt->dir.sub (rs.out_path ()))) continue; } - target_state ts ( - match_async (a, *pt, ctx.count_busy (), t[a].task_count)); + match_async (a, *pt, ctx.count_busy (), t[a].task_count); if (p == md.src) - { src_i = pts.size (); - src_ts1 = ts; - } pts.push_back (prerequisite_target (pt, pi)); } - size_t src_tc1 (t[a].task_count.load (memory_order_consume)); - wg.wait (); - size_t src_tc2 (t[a].task_count.load (memory_order_consume)); - // Finish matching all the targets that we have started. // for (size_t i (start), n (pts.size ()); i != n; ++i) @@ -847,8 +1113,13 @@ namespace build2 // match in link::apply() it will be safe unless someone is building // an obj?{} target directly. // + // @@ If for some reason unmatch fails, this messes up the for_install + // logic because we will update this library during match. Perhaps + // we should postpone updating them until execute if we failed to + // unmatch. See how we do this in ad hoc rule. + // pair<bool, target_state> mr ( - build2::match ( + match_complete ( a, *pt, pt->is_a<liba> () || pt->is_a<libs> () || pt->is_a<libux> () @@ -857,8 +1128,6 @@ namespace build2 if (mr.first) pt = nullptr; // Ignore in execute. - else if (i == src_i) - src_ts2 = mr.second; } // Inject additional prerequisites. We only do it when performing update @@ -884,6 +1153,8 @@ namespace build2 md.symexport = l ? cast<bool> (l) : symexport; } + // NOTE: see similar code in adhoc_buildscript_rule::apply(). + // Make sure the output directory exists. // // Is this the right thing to do? It does smell a bit, but then we do @@ -907,12 +1178,14 @@ namespace build2 // this can very well be happening in parallel. But that's not a // problem since fsdir{}'s update is idempotent. // - fsdir_rule::perform_update_direct (a, t); + fsdir_rule::perform_update_direct (a, *dir); } // Note: the leading '@' is reserved for the module map prefix (see // extract_modules()) and no other line must start with it. // + // NOTE: see also the predefs rule if changing anything here. + // depdb dd (tp + ".d"); // First should come the rule name/version. @@ -927,6 +1200,11 @@ namespace build2 if (dd.expect (cast<string> (rs[x_checksum])) != nullptr) l4 ([&]{trace << "compiler mismatch forcing update of " << t;}); + // Then the compiler environment checksum. + // + if (dd.expect (env_checksum) != nullptr) + l4 ([&]{trace << "environment mismatch forcing update of " << t;}); + // Then the options checksum. // // The idea is to keep them exactly as they are passed to the compiler @@ -943,9 +1221,30 @@ namespace build2 if (ut == unit_type::module_intf) // Note: still unrefined. cs.append (&md.symexport, sizeof (md.symexport)); - if (xlate_hdr != nullptr) - append_options (cs, *xlate_hdr); - + // If we track translate_include then we should probably also track + // the cc.importable flag for each header we include, which would be + // quite heavy-handed indeed. Or maybe we shouldn't bother with this + // at all: after all include translation is an optimization so why + // rebuild an otherwise up-to-date target? + // +#if 0 + if (modules) + { + // While there is also the companion importable_headers map, it's + // unlikely to change in a way that affects us without changes to + // other things that we track (e.g., compiler version, etc). + // + if (const auto* v = cast_null<translatable_headers> ( + t[x_translate_include])) + { + for (const auto& p: *v) + { + cs.append (p.first); + cs.append (!p.second || *p.second); + } + } + } +#endif if (md.pp != preprocessed::all) { append_options (cs, t, x_poptions); @@ -958,19 +1257,10 @@ namespace build2 append_options (cs, t, c_coptions); append_options (cs, t, x_coptions); - - if (ot == otype::s) - { - // On Darwin, Win32 -fPIC is the default. - // - if (tclass == "linux" || tclass == "bsd") - cs.append ("-fPIC"); - } - append_options (cs, cmode); if (md.pp != preprocessed::all) - append_sys_inc_options (cs); // Extra system header dirs (last). + append_sys_hdr_options (cs); // Extra system header dirs (last). if (dd.expect (cs.string ()) != nullptr) l4 ([&]{trace << "options mismatch forcing update of " << t;}); @@ -980,32 +1270,8 @@ namespace build2 // { const path& p (src.path ()); - - // @@ TMP: we seem to have a race condition here but can't quite put - // our finger on it. - // - // NOTE: remember to get rid of src_ts*, etc., once done. - // -#if 0 assert (!p.empty ()); // Sanity check. -#else - if (p.empty ()) - { - target_state src_ts3 (src.matched_state (a, false)); - - info << "unassigned path for target " << src << - info << "is empty_path: " << (&p == &empty_path) << - info << "target state 1: " << src_ts1 << - info << "target state 2: " << src_ts2 << - info << "target state 3: " << src_ts3 << - info << "target count 1: " << src_tc1 << - info << "target count 2: " << src_tc2 << - info << "please report at " - << "https://github.com/build2/build2/issues/89"; - - assert (!p.empty ()); - } -#endif + if (dd.expect (p) != nullptr) l4 ([&]{trace << "source file mismatch forcing update of " << t;}); } @@ -1077,14 +1343,14 @@ namespace build2 // If we have no #include directives (or header unit imports), then // skip header dependency extraction. // - pair<auto_rmfile, bool> psrc (auto_rmfile (), false); + pair<file_cache::entry, bool> psrc (file_cache::entry (), false); if (md.pp < preprocessed::includes) { // Note: trace is used in a test. // l5 ([&]{trace << "extracting headers from " << src;}); auto& is (tu.module_info.imports); - psrc = extract_headers (a, bs, t, li, src, md, dd, u, mt, is); + extract_headers (a, bs, t, li, src, md, dd, u, mt, is, psrc); is.clear (); // No longer needed. } @@ -1139,6 +1405,10 @@ namespace build2 // if (mt != timestamp_nonexistent) { + // Appended to by to_module_info() below. + // + tu.module_info.imports.clear (); + u = false; md.touch = true; } @@ -1223,21 +1493,6 @@ namespace build2 extract_modules (a, bs, t, li, tts, src, md, move (tu.module_info), dd, u); - - // Currently in VC module interface units must be compiled from - // the original source (something to do with having to detect and - // store header boundaries in the .ifc files). - // - // @@ MODHDR MSVC: should we do the same for header units? I guess - // we will figure it out when MSVC supports header units. - // - // @@ TMP: probably outdated. Probably the same for partitions. - // - if (ctype == compiler_type::msvc) - { - if (ut == unit_type::module_intf) - psrc.second = false; - } } } @@ -1256,10 +1511,10 @@ namespace build2 // to keep re-validating the file on every subsequent dry-run as well // on the real run). // - if (u && dd.reading () && !ctx.dry_run) - dd.touch = true; + if (u && dd.reading () && !ctx.dry_run_option) + dd.touch = timestamp_unknown; - dd.close (); + dd.close (false /* mtime_check */); md.dd = move (dd.path); // If the preprocessed output is suitable for compilation, then pass @@ -1269,22 +1524,33 @@ namespace build2 { md.psrc = move (psrc.first); + // Now is also the right time to unpin the cache entry (we don't do + // it earlier because parse_unit() may need to read it). + // + md.psrc.unpin (); + // Without modules keeping the (partially) preprocessed output // around doesn't buy us much: if the source/headers haven't changed // then neither will the object file. Modules make things more // interesting: now we may have to recompile an otherwise unchanged - // translation unit because a BMI it depends on has changed. In this - // case re-processing the translation unit would be a waste and - // compiling the original source would break distributed + // translation unit because a named module BMI it depends on has + // changed. In this case re-processing the translation unit would be + // a waste and compiling the original source would break distributed // compilation. // // Note also that the long term trend will (hopefully) be for // modularized projects to get rid of #include's which means the // need for producing this partially preprocessed output will - // (hopefully) gradually disappear. + // (hopefully) gradually disappear. Or not, most C headers will stay + // headers, and probably not importable. + // + // @@ TODO: no use keeping it if there are no named module imports + // (but see also file_cache::create() hint, and + // extract_headers() the cache case: there we just assume + // it exists if modules is true). // if (modules) - md.psrc.active = false; // Keep. + md.psrc.temporary = false; // Keep. } // Above we may have ignored changes to the translation unit. The @@ -1306,79 +1572,42 @@ namespace build2 switch (a) { - case perform_update_id: return [this] (action a, const target& t) - { - return perform_update (a, t); - }; - case perform_clean_id: return [this] (action a, const target& t) + case perform_update_id: return move (md); + case perform_clean_id: { - return perform_clean (a, t); - }; + return [this, srct = &md.src.type ()] (action a, const target& t) + { + return perform_clean (a, t, *srct); + }; + } default: return noop_recipe; // Configure update. } } - // Reverse-lookup target type(s) from extension. - // - small_vector<const target_type*, 2> compile_rule:: - map_extension (const scope& s, const string& n, const string& e) const - { - // We will just have to try all of the possible ones, in the "most - // likely to match" order. - // - auto test = [&s, &n, &e] (const target_type& tt) -> bool - { - // Call the extension derivation function. Here we know that it will - // only use the target type and name from the target key so we can - // pass bogus values for the rest. - // - target_key tk {&tt, nullptr, nullptr, &n, nullopt}; - - // This is like prerequisite search. - // - optional<string> de (tt.default_extension (tk, s, nullptr, true)); - - return de && *de == e; - }; - - small_vector<const target_type*, 2> r; - - for (const target_type* const* p (x_inc); *p != nullptr; ++p) - if (test (**p)) - r.push_back (*p); - - return r; - } - void compile_rule:: - append_prefixes (prefix_map& m, const target& t, const variable& var) const + append_prefixes (prefix_map& m, + const scope& rs, const target& t, + const variable& var) const { tracer trace (x, "compile_rule::append_prefixes"); - // If this target does not belong to any project (e.g, an "imported as - // installed" library), then it can't possibly generate any headers for - // us. - // - const scope& bs (t.base_scope ()); - const scope* rs (bs.root_scope ()); - if (rs == nullptr) - return; - - const dir_path& out_base (t.dir); - const dir_path& out_root (rs->out_path ()); - if (auto l = t[var]) { const auto& v (cast<strings> (l)); for (auto i (v.begin ()), e (v.end ()); i != e; ++i) { - // -I can either be in the "-Ifoo" or "-I foo" form. For VC it can - // also be /I. - // const string& o (*i); - if (o.size () < 2 || (o[0] != '-' && o[0] != '/') || o[1] != 'I') + // -I can either be in the "-Ifoo" or "-I foo" form. For MSVC it + // can also be /I. + // + // Note that we naturally assume that -isystem, /external:I, etc., + // are not relevant here. + // + bool msvc (cclass == compiler_class::msvc); + + if (!((o[0] == '-' || (msvc && o[0] == '/')) && o[1] == 'I')) continue; dir_path d; @@ -1419,113 +1648,8 @@ namespace build2 // If we are not inside our project root, then ignore. // - if (!d.sub (out_root)) - continue; - - // If the target directory is a sub-directory of the include - // directory, then the prefix is the difference between the - // two. Otherwise, leave it empty. - // - // The idea here is to make this "canonical" setup work auto- - // magically: - // - // 1. We include all files with a prefix, e.g., <foo/bar>. - // 2. The library target is in the foo/ sub-directory, e.g., - // /tmp/foo/. - // 3. The poptions variable contains -I/tmp. - // - dir_path p (out_base.sub (d) ? out_base.leaf (d) : dir_path ()); - - // We use the target's directory as out_base but that doesn't work - // well for targets that are stashed in subdirectories. So as a - // heuristics we are going to also enter the outer directories of - // the original prefix. It is, however, possible, that another -I - // option after this one will produce one of these outer prefixes as - // its original prefix in which case we should override it. - // - // So we are going to assign the original prefix priority value 0 - // (highest) and then increment it for each outer prefix. - // - auto enter = [&trace, &m] (dir_path p, dir_path d, size_t prio) - { - auto j (m.find (p)); - - if (j != m.end ()) - { - prefix_value& v (j->second); - - // We used to reject duplicates but it seems this can be - // reasonably expected to work according to the order of the - // -I options. - // - // Seeing that we normally have more "specific" -I paths first, - // (so that we don't pick up installed headers, etc), we ignore - // it. - // - if (v.directory == d) - { - if (v.priority > prio) - v.priority = prio; - } - else if (v.priority <= prio) - { - if (verb >= 4) - trace << "ignoring mapping for prefix '" << p << "'\n" - << " existing mapping to " << v.directory - << " priority " << v.priority << '\n' - << " another mapping to " << d - << " priority " << prio; - } - else - { - if (verb >= 4) - trace << "overriding mapping for prefix '" << p << "'\n" - << " existing mapping to " << v.directory - << " priority " << v.priority << '\n' - << " new mapping to " << d - << " priority " << prio; - - v.directory = move (d); - v.priority = prio; - } - } - else - { - l6 ([&]{trace << "'" << p << "' -> " << d << " priority " - << prio;}); - m.emplace (move (p), prefix_value {move (d), prio}); - } - }; - -#if 1 - // Enter all outer prefixes, including prefixless. - // - // The prefixless part is fuzzy but seems to be doing the right - // thing ignoring/overriding-wise, at least in cases where one of - // the competing -I paths is a subdirectory of another. But the - // proper solution will be to keep all the prefixless entries (by - // changing prefix_map to a multimap) since for them we have an - // extra check (target must be explicitly spelled out in a - // buildfile). - // - for (size_t prio (0);; ++prio) - { - bool e (p.empty ()); - enter ((e ? move (p) : p), (e ? move (d) : d), prio); - if (e) - break; - p = p.directory (); - } -#else - size_t prio (0); - for (bool e (false); !e; ++prio) - { - dir_path n (p.directory ()); - e = n.empty (); - enter ((e ? move (p) : p), (e ? move (d) : d), prio); - p = move (n); - } -#endif + if (d.sub (rs.out_path ())) + append_prefix (trace, m, t, move (d)); } } } @@ -1533,83 +1657,23 @@ namespace build2 auto compile_rule:: build_prefix_map (const scope& bs, action a, - target& t, + const target& t, linfo li) const -> prefix_map { - prefix_map m; + prefix_map pm; // First process our own. // - append_prefixes (m, t, x_poptions); - append_prefixes (m, t, c_poptions); + const scope& rs (*bs.root_scope ()); + append_prefixes (pm, rs, t, x_poptions); + append_prefixes (pm, rs, t, c_poptions); // Then process the include directories from prerequisite libraries. // - append_library_prefixes (m, bs, a, t, li); - - return m; - } - - // Return the next make prerequisite starting from the specified - // position and update position to point to the start of the - // following prerequisite or l.size() if there are none left. - // - static string - next_make (const string& l, size_t& p) - { - size_t n (l.size ()); - - // Skip leading spaces. - // - for (; p != n && l[p] == ' '; p++) ; - - // Lines containing multiple prerequisites are 80 characters max. - // - string r; - r.reserve (n); - - // Scan the next prerequisite while watching out for escape sequences. - // - for (; p != n && l[p] != ' '; p++) - { - char c (l[p]); - - if (p + 1 != n) - { - if (c == '$') - { - // Got to be another (escaped) '$'. - // - if (l[p + 1] == '$') - ++p; - } - else if (c == '\\') - { - // This may or may not be an escape sequence depending on whether - // what follows is "escapable". - // - switch (c = l[++p]) - { - case '\\': break; - case ' ': break; - default: c = '\\'; --p; // Restore. - } - } - } - - r += c; - } - - // Skip trailing spaces. - // - for (; p != n && l[p] == ' '; p++) ; - - // Skip final '\'. - // - if (p == n - 1 && l[p] == '\\') - p++; + appended_libraries ls; + append_library_prefixes (ls, pm, bs, a, t, li); - return r; + return pm; } // VC /showIncludes output. The first line is the file being compiled @@ -1807,22 +1871,31 @@ namespace build2 // Any unhandled io_error is handled by the caller as a generic module // mapper io error. Returning false terminates the communication. // - struct compile_rule::module_mapper_state //@@ gcc_module_mapper_state + struct compile_rule::gcc_module_mapper_state { size_t skip; // Number of depdb entries to skip. size_t header_units = 0; // Number of header units imported. module_imports& imports; // Unused (potentially duplicate suppression). + // Include translation (looked up lazily). + // + optional<const build2::cc::translatable_headers*> translatable_headers; + small_vector<string, 2> batch; // Reuse buffers. + size_t batch_n = 0; - module_mapper_state (size_t s, module_imports& i) + gcc_module_mapper_state (size_t s, module_imports& i) : skip (s), imports (i) {} }; - bool compile_rule:: - gcc_module_mapper (module_mapper_state& st, + // The module mapper is called on one line of input at a time. It should + // return nullopt if another line is expected (batch), false if the mapper + // interaction should be terminated, and true if it should be continued. + // + optional<bool> compile_rule:: + gcc_module_mapper (gcc_module_mapper_state& st, action a, const scope& bs, file& t, linfo li, - ifdstream& is, + const string& l, ofdstream& os, depdb& dd, bool& update, bool& bad_error, optional<prefix_map>& pfx_map, srcout_map& so_map) const @@ -1838,35 +1911,40 @@ namespace build2 // Read in the entire batch trying hard to reuse the buffers. // - auto& batch (st.batch); - size_t batch_n (0); + small_vector<string, 2>& batch (st.batch); + size_t& batch_n (st.batch_n); - for (;;) + // Add the next line. + // { if (batch.size () == batch_n) - batch.push_back (string ()); - - string& r (batch[batch_n]); - - if (eof (getline (is, r))) - break; + batch.push_back (l); + else + batch[batch_n] = l; batch_n++; + } - if (r.back () != ';') - break; + // Check if more is expected in this batch. + // + { + string& r (batch[batch_n - 1]); - // Strip the trailing `;` word. - // - r.pop_back (); - r.pop_back (); - } + if (r.back () == ';') + { + // Strip the trailing `;` word. + // + r.pop_back (); + r.pop_back (); - if (batch_n == 0) // EOF - return false; + return nullopt; + } + } if (verb >= 3) { + // It doesn't feel like buffering this would be useful. + // // Note that we show `;` in requests/responses so that the result // could be replayed. // @@ -1888,23 +1966,211 @@ namespace build2 for (size_t i (0); i != batch_n; ++i) { string& r (batch[i]); + size_t rn (r.size ()); - // @@ TODO: quoting and escaping. + // The protocol uses a peculiar quoting/escaping scheme that can be + // summarized as follows (see the libcody documentation for details): + // + // - Words are seperated with spaces and/or tabs. + // + // - Words need not be quoted if they only containing characters from + // the [-+_/%.A-Za-z0-9] set. // - size_t b (0), e (0), n; // Next word. + // - Otherwise words need to be single-quoted. + // + // - Inside single-quoted words, the \n \t \' and \\ escape sequences + // are recognized. + // + // Note that we currently don't treat abutted quotes (as in a' 'b) as + // a single word (it doesn't seem plausible that we will ever receive + // something like this). + // + size_t b (0), e (0), n; bool q; // Next word. - auto next = [&r, &b, &e, &n] () -> size_t + auto next = [&r, rn, &b, &e, &n, &q] () -> size_t { - return (n = next_word (r, b, e, ' ', '\t')); + if (b != e) + b = e; + + // Skip leading whitespaces. + // + for (; b != rn && (r[b] == ' ' || r[b] == '\t'); ++b) ; + + if (b != rn) + { + q = (r[b] == '\''); + + // Find first trailing whitespace or closing quote. + // + for (e = b + 1; e != rn; ++e) + { + // Note that we deal with invalid quoting/escaping in unquote(). + // + switch (r[e]) + { + case ' ': + case '\t': + if (q) + continue; + else + break; + case '\'': + if (q) + { + ++e; // Include closing quote (hopefully). + break; + } + else + { + assert (false); // Abutted quote. + break; + } + case '\\': + if (++e != rn) // Skip next character (hopefully). + continue; + else + break; + default: + continue; + } + + break; + } + + n = e - b; + } + else + { + q = false; + e = rn; + n = 0; + } + + return n; }; + // Unquote into tmp the current word returning false if malformed. + // + auto unquote = [&r, &b, &n, &q, &tmp] (bool clear = true) -> bool + { + if (q && n > 1) + { + size_t e (b + n - 1); + + if (r[b] == '\'' && r[e] == '\'') + { + if (clear) + tmp.clear (); + + size_t i (b + 1); + for (; i != e; ++i) + { + char c (r[i]); + if (c == '\\') + { + if (++i == e) + { + i = 0; + break; + } + + c = r[i]; + if (c == 'n') c = '\n'; + else if (c == 't') c = '\t'; + } + tmp += c; + } + + if (i == e) + return true; + } + } + + return false; + }; + +#if 0 +#define UNQUOTE(x, y) \ + r = x; rn = r.size (); b = e = 0; \ + assert (next () && unquote () && tmp == y) + + UNQUOTE ("'foo bar'", "foo bar"); + UNQUOTE (" 'foo bar' ", "foo bar"); + UNQUOTE ("'foo\\\\bar'", "foo\\bar"); + UNQUOTE ("'\\'foo bar'", "'foo bar"); + UNQUOTE ("'foo bar\\''", "foo bar'"); + UNQUOTE ("'\\'foo\\\\bar\\''", "'foo\\bar'"); + + fail << "all good"; +#endif + + // Escape if necessary the specified string and append to r. + // + auto escape = [&r] (const string& s) + { + size_t b (0), e, n (s.size ()); + while (b != n && (e = s.find_first_of ("\\'\n\t", b)) != string::npos) + { + r.append (s, b, e - b); // Preceding chunk. + + char c (s[e]); + r += '\\'; + r += (c == '\n' ? 'n' : c == '\t' ? 't' : c); + b = e + 1; + } + + if (b != n) + r.append (s, b, e); // Final chunk. + }; + + // Quote and escape if necessary the specified string and append to r. + // + auto quote = [&r, &escape] (const string& s) + { + if (find_if (s.begin (), s.end (), + [] (char c) + { + return !((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + c == '-' || c == '_' || c == '/' || + c == '.' || c == '+' || c == '%'); + }) == s.end ()) + { + r += s; + } + else + { + r += '\''; + escape (s); + r += '\''; + } + }; + +#if 0 +#define QUOTE(x, y) \ + r.clear (); quote (x); \ + assert (r == y) + + QUOTE ("foo/Bar-7.h", "foo/Bar-7.h"); + + QUOTE ("foo bar", "'foo bar'"); + QUOTE ("foo\\bar", "'foo\\\\bar'"); + QUOTE ("'foo bar", "'\\'foo bar'"); + QUOTE ("foo bar'", "'foo bar\\''"); + QUOTE ("'foo\\bar'", "'\\'foo\\\\bar\\''"); + + fail << "all good"; +#endif + next (); // Request name. - auto name = [&r, b, n] (const char* c) -> bool + auto name = [&r, b, n, q] (const char* c) -> bool { // We can reasonably assume a command will never be quoted. // - return (r.compare (b, n, c) == 0 && + return (!q && + r.compare (b, n, c) == 0 && (r[n] == ' ' || r[n] == '\t' || r[n] == '\0')); }; @@ -1953,7 +2219,17 @@ namespace build2 if (next ()) { - path f (r, b, n); + path f; + if (!q) + f = path (r, b, n); + else if (unquote ()) + f = path (tmp); + else + { + r = "ERROR 'malformed quoting/escaping in request'"; + continue; + } + bool exists (true); // The TU path we pass to the compiler is always absolute so any @@ -1964,8 +2240,9 @@ namespace build2 // if (exists && f.relative ()) { - tmp.assign (r, b, n); - r = "ERROR relative header path '"; r += tmp; r += '\''; + r = "ERROR 'relative header path "; + escape (f.string ()); + r += '\''; continue; } @@ -2002,16 +2279,17 @@ namespace build2 try { pair<const file*, bool> er ( - enter_header (a, bs, t, li, - move (f), false /* cache */, false /* norm */, - pfx_map, so_map)); + enter_header ( + a, bs, t, li, + move (f), false /* cache */, false /* normalized */, + pfx_map, so_map)); ht = er.first; remapped = er.second; if (remapped) { - r = "ERROR remapping of headers not supported"; + r = "ERROR 'remapping of headers not supported'"; continue; } @@ -2021,14 +2299,14 @@ namespace build2 // diagnostics won't really add anything to the compiler's. So // let's only print it at -V or higher. // - if (ht == nullptr) + if (ht == nullptr) // f is still valid. { assert (!exists); // Sanity check. if (verb > 2) { diag_record dr; - dr << error << "header '" << f << "' not found and no " + dr << error << "header " << f << " not found and no " << "rule to generate it"; if (verb < 4) @@ -2069,8 +2347,10 @@ namespace build2 // messy, let's keep both (it would have been nicer to print // ours after the compiler's but that isn't easy). // - r = "ERROR unable to update header '"; - r += (ht != nullptr ? ht->path () : f).string (); + // Note: if ht is NULL, f is still valid. + // + r = "ERROR 'unable to update header "; + escape ((ht != nullptr ? ht->path () : f).string ()); r += '\''; continue; } @@ -2094,21 +2374,84 @@ namespace build2 // Now handle INCLUDE and IMPORT differences. // - const string& hp (ht->path ().string ()); + const path& hp (ht->path ()); + const string& hs (hp.string ()); // Reduce include translation to the import case. // - if (!imp && xlate_hdr != nullptr) + if (!imp) { - auto i (lower_bound ( - xlate_hdr->begin (), xlate_hdr->end (), - hp, - [] (const string& x, const string& y) - { - return path_traits::compare (x, y) < 0; - })); + if (!st.translatable_headers) + st.translatable_headers = + cast_null<translatable_headers> (t[x_translate_include]); + + if (*st.translatable_headers != nullptr) + { + auto& ths (**st.translatable_headers); + + // First look for the header path in the translatable headers + // itself. + // + auto i (ths.find (hs)), ie (ths.end ()); + + // Next look it up in the importable headers and then look up + // the associated groups in the translatable headers. + // + if (i == ie) + { + slock l (importable_headers->mutex); + auto& ihs (importable_headers->header_map); + + auto j (ihs.find (hp)), je (ihs.end ()); + + if (j != je) + { + // The groups are ordered from the most to least specific. + // + for (const string& g: j->second) + if ((i = ths.find (g)) != ie) + break; + } + + // Finally look for the `all` groups. + // + if (i == ie) + { + i = ths.find (header_group_all_importable); + + if (i != ie) + { + // See if this header is marked as importable. + // + if (lookup l = (*ht)[c_importable]) + { + if (!cast<bool> (l)) + i = ie; + } + else if (j != je) + { + // See if this is one of ad hoc *-importable groups + // (currently only std-importable). + // + const auto& gs (j->second); + if (find (gs.begin (), + gs.end (), + header_group_std_importable) == gs.end ()) + i = ie; + } + else + i = ie; + } + + if (i == ie) + i = ths.find (header_group_all); + } + } - imp = (i != xlate_hdr->end () && *i == hp); + // Translate if we found an entry and it's not false. + // + imp = (i != ie && (!i->second || *i->second)); + } } if (imp) @@ -2118,7 +2461,7 @@ namespace build2 // Synthesize the BMI dependency then update and add the BMI // target as a prerequisite. // - const file& bt (make_header_sidebuild (a, bs, li, *ht)); + const file& bt (make_header_sidebuild (a, bs, t, li, *ht)); if (!skip) { @@ -2142,16 +2485,27 @@ namespace build2 // original (which we may need to normalize when we read // this mapping in extract_headers()). // - tmp = "@ "; tmp.append (r, b, n); tmp += ' '; tmp += bp; + // @@ This still breaks if the header path contains spaces. + // GCC bug 110153. + // + tmp = "@ "; + if (!q) tmp.append (r, b, n); + else unquote (false /* clear */); // Can't fail. + tmp += ' '; + tmp += bp; + dd.expect (tmp); st.header_units++; } - r = "PATHNAME "; r += bp; + r = "PATHNAME "; + quote (bp); } catch (const failed&) { - r = "ERROR 'unable to update header unit "; r += hp; r += '\''; + r = "ERROR 'unable to update header unit for "; + escape (hs); + r += '\''; continue; } } @@ -2160,7 +2514,7 @@ namespace build2 if (skip) st.skip--; else - dd.expect (hp); + dd.expect (hs); // Confusingly, TRUE means include textually and FALSE means we // don't know. @@ -2177,7 +2531,7 @@ namespace build2 // Truncate the response batch and terminate the communication (see // also libcody issue #22). // - tmp.assign (r, b, n); + tmp.assign (r, b, n); // Request name (unquoted). r = "ERROR '"; r += w; r += ' '; r += tmp; r += '\''; batch_n = i + 1; term = true; @@ -2193,6 +2547,9 @@ namespace build2 // Write the response batch. // + // @@ It's theoretically possible that we get blocked writing the + // response while the compiler gets blocked writing the diagnostics. + // for (size_t i (0);; ) { string& r (batch[i]); @@ -2213,6 +2570,8 @@ namespace build2 os.flush (); + batch_n = 0; // Start a new batch. + return !term; } @@ -2466,9 +2825,10 @@ namespace build2 if (exists) { pair<const file*, bool> r ( - enter_header (a, bs, t, li, - move (f), false /* cache */, false /* norm */, - pfx_map, so_map)); + enter_header ( + a, bs, t, li, + move (f), false /* cache */, false /* normalized */, + pfx_map, so_map)); if (!r.second) // Shouldn't be remapped. ht = r.first; @@ -2476,7 +2836,7 @@ namespace build2 if (ht != pts.back ()) { - ht = static_cast<const file*> (pts.back ().target); + ht = &pts.back ().target->as<file> (); rs = "ERROR expected header '" + ht->path ().string () + "' to be found instead"; bad_error = true; // We expect an error from the compiler. @@ -2493,9 +2853,10 @@ namespace build2 try { pair<const file*, bool> er ( - enter_header (a, bs, t, li, - move (f), false /* cache */, false /* norm */, - pfx_map, so_map)); + enter_header ( + a, bs, t, li, + move (f), false /* cache */, false /* normalized */, + pfx_map, so_map)); ht = er.first; remapped = er.second; @@ -2513,7 +2874,7 @@ namespace build2 // diagnostics won't really add anything to the compiler's. So // let's only print it at -V or higher. // - if (ht == nullptr) + if (ht == nullptr) // f is still valid. { assert (!exists); // Sanity check. @@ -2560,10 +2921,12 @@ namespace build2 // messy, let's keep both (it would have been nicer to print // ours after the compiler's but that isn't easy). // + // Note: if ht is NULL, f is still valid. + // rs = !exists ? string ("INCLUDE") : ("ERROR unable to update header '" + - (ht != nullptr ? ht->path () : f).string () + "'"); + (ht != nullptr ? ht->path () : f).string () + '\''); bad_error = true; break; @@ -2611,7 +2974,7 @@ namespace build2 // Synthesize the BMI dependency then update and add the BMI // target as a prerequisite. // - const file& bt (make_header_sidebuild (a, bs, li, *ht)); + const file& bt (make_header_sidebuild (a, bs, t, li, *ht)); if (!skip) { @@ -2641,7 +3004,7 @@ namespace build2 } catch (const failed&) { - rs = "ERROR unable to update header unit '" + hp + "'"; + rs = "ERROR unable to update header unit '" + hp + '\''; bad_error = true; break; } @@ -2683,419 +3046,204 @@ namespace build2 } #endif - // Enter as a target a header file. Depending on the cache flag, the file - // is assumed to either have come from the depdb cache or from the - // compiler run. - // - // Return the header target and an indication of whether it was remapped - // or NULL if the header does not exist and cannot be generated. In the - // latter case the passed header path is guaranteed to be still valid but - // might have been adjusted (e.g., normalized, etc). + //atomic_count cache_hit {0}; + //atomic_count cache_mis {0}; + //atomic_count cache_cls {0}; + + // The fp path is only moved from on success. // // Note: this used to be a lambda inside extract_headers() so refer to the // body of that function for the overall picture. // pair<const file*, bool> compile_rule:: enter_header (action a, const scope& bs, file& t, linfo li, - path&& f, bool cache, bool norm, - optional<prefix_map>& pfx_map, srcout_map& so_map) const + path&& fp, bool cache, bool norm, + optional<prefix_map>& pfx_map, + const srcout_map& so_map) const { tracer trace (x, "compile_rule::enter_header"); - // Find or maybe insert the target. The directory is only moved from if - // insert is true. Note that it must be normalized. - // - auto find = [&trace, &t, this] (dir_path&& d, - path&& f, - bool insert) -> const file* + // It's reasonable to expect the same header to be included by multiple + // translation units, which means we will be re-doing this work over and + // over again. And it's not exactly cheap, taking up to 50% of an + // up-to-date check time on some projects. So we are going to cache the + // header path to target mapping. + // + // While we pass quite a bit of specific "context" (target, base scope) + // to enter_file(), here is the analysis why the result will not depend + // on this context for the non-absent header (fp is absolute): + // + // 1. Let's start with the base scope (bs). Firstly, the base scope + // passed to map_extension() is the scope of the header (i.e., it is + // the scope of fp.directory()). Other than that, the target base + // scope is only passed to build_prefix_map() which is only called + // for the absent header (linfo is also only used here). + // + // 2. Next is the target (t). It is passed to build_prefix_map() but + // that doesn't matter for the same reason as in (1). Other than + // that, it is only passed to build2::search() which in turn passes + // it to target type-specific prerequisite search callback (see + // target_type::search) if one is not NULL. The target type in + // question here is one of the headers and we know all of them use + // the standard file_search() which ignores the passed target. + // + // 3. Finally, so_map could be used for an absolute fp. While we could + // simply not cache the result if it was used (second half of the + // result pair is true), there doesn't seem to be any harm in caching + // the remapped path->target mapping. In fact, if to think about it, + // there is no harm in caching the generated file mapping since it + // will be immediately generated and any subsequent inclusions we + // will "see" with an absolute path, which we can resolve from the + // cache. + // + // To put it another way, all we need to do is make sure that if we were + // to not return an existing cache entry, the call to enter_file() would + // have returned exactly the same path/target. + // + // @@ Could it be that the header is re-mapped in one config but not the + // other (e.g., when we do both in src and in out builds and we pick + // the generated header in src)? If so, that would lead to a + // divergence. I.e., we would cache the no-remap case first and then + // return it even though the re-map is necessary? Why can't we just + // check for re-mapping ourselves? A: the remapping logic in + // enter_file() is not exactly trivial. + // + // But on the other hand, I think we can assume that different + // configurations will end up with different caches. In other words, + // we can assume that for the same "cc amalgamation" we use only a + // single "version" of a header. Seems reasonable. + // + // Note also that while it would have been nice to have a unified cc + // cache, the map_extension() call is passed x_incs which is module- + // specific. In other words, we may end up mapping the same header to + // two different targets depending on whether it is included from, say, + // C or C++ translation unit. We could have used a unified cache for + // headers that were mapped using the fallback target type, which would + // cover the installed headers. Maybe, one day (it's also possible that + // separate caches reduce contention). + // + // Another related question is where we want to keep the cache: project, + // strong amalgamation, or weak amalgamation (like module sidebuilds). + // Some experimentation showed that weak has the best performance (which + // suggest that a unified cache will probably be a win). + // + // Note also that we don't need to clear this cache since we never clear + // the targets set. In other words, the only time targets are + // invalidated is when we destroy the build context, which also destroys + // the cache. + // + const config_module& hc (*header_cache_); + + // First check the cache. + // + config_module::header_key hk; + + bool e (fp.absolute ()); + if (e) { - // Split the file into its name part and extension. Here we can assume - // the name part is a valid filesystem name. - // - // Note that if the file has no extension, we record an empty - // extension rather than NULL (which would signify that the default - // extension should be added). - // - string e (f.extension ()); - string n (move (f).string ()); - - if (!e.empty ()) - n.resize (n.size () - e.size () - 1); // One for the dot. - - // See if this directory is part of any project out_root hierarchy and - // if so determine the target type. - // - // Note that this will miss all the headers that come from src_root - // (so they will be treated as generic C headers below). Generally, we - // don't have the ability to determine that some file belongs to - // src_root of some project. But that's not a problem for our - // purposes: it is only important for us to accurately determine - // target types for headers that could be auto-generated. - // - // While at it also try to determine if this target is from the src or - // out tree of said project. - // - dir_path out; - - // It's possible the extension-to-target type mapping is ambiguous - // (usually because both C and X-language headers use the same .h - // extension). In this case we will first try to find one that matches - // an explicit target (similar logic to when insert is false). - // - small_vector<const target_type*, 2> tts; - - const scope& bs (t.ctx.scopes.find (d)); - if (const scope* rs = bs.root_scope ()) + if (!norm) { - tts = map_extension (bs, n, e); - - if (bs.out_path () != bs.src_path () && d.sub (bs.src_path ())) - out = out_src (d, *rs); + normalize_external (fp, "header"); + norm = true; } - // If it is outside any project, or the project doesn't have such an - // extension, assume it is a plain old C header. - // - if (tts.empty ()) - { - // If the project doesn't "know" this extension then we can't - // possibly find an explicit target of this type. - // - if (!insert) - return nullptr; + hk.file = move (fp); + hk.hash = hash<path> () (hk.file); - tts.push_back (&h::static_type); - } - - // Find or insert target. - // - // Note that in case of the target type ambiguity we first try to find - // an explicit target that resolves this ambiguity. - // - const target* r (nullptr); - - if (!insert || tts.size () > 1) + slock l (hc.header_map_mutex); + auto i (hc.header_map.find (hk)); + if (i != hc.header_map.end ()) { - // Note that we skip any target type-specific searches (like for an - // existing file) and go straight for the target object since we - // need to find the target explicitly spelled out. - // - // Also, it doesn't feel like we should be able to resolve an - // absolute path with a spelled-out extension to multiple targets. - // - for (const target_type* tt: tts) - if ((r = t.ctx.targets.find (*tt, d, out, n, e, trace)) != nullptr) - break; - - // Note: we can't do this because of the in-source builds where - // there won't be explicit targets for non-generated headers. - // - // This should be harmless, however, since in our world generated - // headers are normally spelled-out as explicit targets. And if not, - // we will still get an error, just a bit less specific. - // -#if 0 - if (r == nullptr && insert) - { - f = d / n; - if (!e.empty ()) - { - f += '.'; - f += e; - } - - diag_record dr (fail); - dr << "mapping of header " << f << " to target type is ambiguous"; - for (const target_type* tt: tts) - dr << info << "could be " << tt->name << "{}"; - dr << info << "spell-out its target to resolve this ambiguity"; - } -#endif + //cache_hit.fetch_add (1, memory_order_relaxed); + return make_pair (i->second, false); } - // @@ OPT: move d, out, n - // - if (r == nullptr && insert) - r = &search (t, *tts[0], d, out, n, &e, nullptr); + fp = move (hk.file); - return static_cast<const file*> (r); - }; + //cache_mis.fetch_add (1, memory_order_relaxed); + } - // If it's not absolute then it either does not (yet) exist or is a - // relative ""-include (see init_args() for details). Reduce the second - // case to absolute. - // - // Note: we now always use absolute path to the translation unit so this - // no longer applies. But let's keep it for posterity. - // -#if 0 - if (f.relative () && rels.relative ()) + struct data { - // If the relative source path has a directory component, make sure - // it matches since ""-include will always start with that (none of - // the compilers we support try to normalize this path). Failed that - // we may end up searching for a generated header in a random - // (working) directory. - // - const string& fs (f.string ()); - const string& ss (rels.string ()); - - size_t p (path::traits::rfind_separator (ss)); - - if (p == string::npos || // No directory. - (fs.size () > p + 1 && - path::traits::compare (fs.c_str (), p, ss.c_str (), p) == 0)) - { - path t (work / f); // The rels path is relative to work. - - if (exists (t)) - f = move (t); - } - } -#endif + linfo li; + optional<prefix_map>& pfx_map; + } d {li, pfx_map}; + + // If it is outside any project, or the project doesn't have such an + // extension, assume it is a plain old C header. + // + auto r (enter_file ( + trace, "header", + a, bs, t, + fp, cache, norm, + [this] (const scope& bs, const string& n, const string& e) + { + return map_extension (bs, n, e, x_incs); + }, + h::static_type, + [this, &d] (action a, const scope& bs, const target& t) + -> const prefix_map& + { + if (!d.pfx_map) + d.pfx_map = build_prefix_map (bs, a, t, d.li); - const file* pt (nullptr); - bool remapped (false); + return *d.pfx_map; + }, + so_map)); - // If still relative then it does not exist. + // Cache. // - if (f.relative ()) + if (r.first != nullptr) { - // This is probably as often an error as an auto-generated file, so - // trace at level 4. - // - l4 ([&]{trace << "non-existent header '" << f << "'";}); - - f.normalize (); + hk.file = move (fp); - // The relative path might still contain '..' (e.g., ../foo.hxx; - // presumably ""-include'ed). We don't attempt to support auto- - // generated headers with such inclusion styles. + // Calculate the hash if we haven't yet and re-calculate it if the + // path has changed (header has been remapped). // - if (f.normalized ()) - { - if (!pfx_map) - pfx_map = build_prefix_map (bs, a, t, li); - - // First try the whole file. Then just the directory. - // - // @@ Has to be a separate map since the prefix can be the same as - // the file name. - // - // auto i (pfx_map->find (f)); - - // Find the most qualified prefix of which we are a sub-path. - // - if (!pfx_map->empty ()) - { - dir_path d (f.directory ()); - auto i (pfx_map->find_sup (d)); - - if (i != pfx_map->end ()) - { - // Note: value in pfx_map is not necessarily canonical. - // - dir_path pd (i->second.directory); - pd.canonicalize (); - - l4 ([&]{trace << "prefix '" << d << "' mapped to " << pd;}); + if (!e || r.second) + hk.hash = hash<path> () (hk.file); - // If this is a prefixless mapping, then only use it if we can - // resolve it to an existing target (i.e., it is explicitly - // spelled out in a buildfile). - // - // Note that at some point we will probably have a list of - // directories. - // - pt = find (pd / d, f.leaf (), !i->first.empty ()); - if (pt != nullptr) - { - f = pd / f; - l4 ([&]{trace << "mapped as auto-generated " << f;}); - } - else - l4 ([&]{trace << "no explicit target in " << pd;}); - } - else - l4 ([&]{trace << "no prefix map entry for '" << d << "'";}); - } - else - l4 ([&]{trace << "prefix map is empty";}); - } - } - else - { - // We used to just normalize the path but that could result in an - // invalid path (e.g., for some system/compiler headers on CentOS 7 - // with Clang 3.4) because of the symlinks (if a directory component - // is a symlink, then any following `..` are resolved relative to the - // target; see path::normalize() for background). - // - // Initially, to fix this, we realized (i.e., realpath(3)) it instead. - // But that turned out also not to be quite right since now we have - // all the symlinks resolved: conceptually it feels correct to keep - // the original header names since that's how the user chose to - // arrange things and practically this is how the compilers see/report - // them (e.g., the GCC module mapper). - // - // So now we have a pretty elaborate scheme where we try to use the - // normalized path if possible and fallback to realized. Normalized - // paths will work for situations where `..` does not cross symlink - // boundaries, which is the sane case. And for the insane case we only - // really care about out-of-project files (i.e., system/compiler - // headers). In other words, if you have the insane case inside your - // project, then you are on your own. - // - // All of this is unless the path comes from the depdb, in which case - // we've already done that (normally). This is also where we handle - // src-out remap (again, not needed if cached). - // - if (!cache || norm) + const file* f; { - // Interestingly, on most paltforms and with most compilers (Clang - // on Linux being a notable exception) most system/compiler headers - // are already normalized. - // - path_abnormality a (f.abnormalities ()); - if (a != path_abnormality::none) - { - // While we can reasonably expect this path to exit, things do go - // south from time to time (like compiling under wine with file - // wlantypes.h included as WlanTypes.h). - // - try - { - // If we have any parent components, then we have to verify the - // normalized path matches realized. - // - path r; - if ((a & path_abnormality::parent) == path_abnormality::parent) - { - r = f; - r.realize (); - } - - try - { - f.normalize (); - - // Note that we might still need to resolve symlinks in the - // normalized path. - // - if (!r.empty () && f != r && path (f).realize () != r) - f = move (r); - } - catch (const invalid_path&) - { - assert (!r.empty ()); // Shouldn't have failed if no `..`. - f = move (r); // Fallback to realize. - } - } - catch (const invalid_path&) - { - fail << "invalid header path '" << f.string () << "'"; - } - catch (const system_error& e) - { - fail << "invalid header path '" << f.string () << "': " << e; - } - } + ulock l (hc.header_map_mutex); + auto p (hc.header_map.emplace (move (hk), r.first)); + f = p.second ? nullptr : p.first->second; } - if (!cache) + if (f != nullptr) { - if (!so_map.empty ()) - { - // Find the most qualified prefix of which we are a sub-path. - // - auto i (so_map.find_sup (f)); - if (i != so_map.end ()) - { - // Ok, there is an out tree for this headers. Remap to a path - // from the out tree and see if there is a target for it. Note - // that the value in so_map is not necessarily canonical. - // - dir_path d (i->second); - d /= f.leaf (i->first).directory (); - d.canonicalize (); - - pt = find (move (d), f.leaf (), false); // d is not moved from. - - if (pt != nullptr) - { - path p (d / f.leaf ()); - l4 ([&]{trace << "remapping " << f << " to " << p;}); - f = move (p); - remapped = true; - } - } - } - } - - if (pt == nullptr) - { - l6 ([&]{trace << "entering " << f;}); - pt = find (f.directory (), f.leaf (), true); + //cache_cls.fetch_add (1, memory_order_relaxed); + assert (r.first == f); } } - return make_pair (pt, remapped); + return r; } - // Update and add to the list of prerequisite targets a header or header - // unit target. - // - // Return the indication of whether it has changed or, if the passed - // timestamp is not timestamp_unknown, is older than the target. If the - // header does not exists nor can be generated (no rule), then issue - // diagnostics and fail if the fail argument is true and return nullopt - // otherwise. - // // Note: this used to be a lambda inside extract_headers() so refer to the // body of that function for the overall picture. // optional<bool> compile_rule:: inject_header (action a, file& t, - const file& pt, timestamp mt, bool f /* fail */) const + const file& pt, timestamp mt, bool fail) const { tracer trace (x, "compile_rule::inject_header"); - // Even if failing we still use try_match() in order to issue consistent - // (with extract_headers() below) diagnostics (rather than the generic - // "not rule to update ..."). - // - if (!try_match (a, pt).first) - { - if (!f) - return nullopt; - - diag_record dr; - dr << fail << "header " << pt << " not found and no rule to " - << "generate it"; - - if (verb < 4) - dr << info << "re-run with --verbose=4 for more information"; - } - - bool r (update (trace, a, pt, mt)); - - // Add to our prerequisite target list. - // - t.prerequisite_targets[a].push_back (&pt); - - return r; + return inject_file (trace, "header", a, t, pt, mt, fail); } - // Extract and inject header dependencies. Return the preprocessed source - // file as well as an indication if it is usable for compilation (see - // below for details). + // Extract and inject header dependencies. Return (in result) the + // preprocessed source file as well as an indication if it is usable for + // compilation (see below for details). Note that result is expected to + // be initialized to {entry (), false}. Not using return type due to + // GCC bug #107555. // // This is also the place where we handle header units which are a lot // more like auto-generated headers than modules. In particular, if a // header unit BMI is out-of-date, then we have to re-preprocess this // translation unit. // - pair<auto_rmfile, bool> compile_rule:: + void compile_rule:: extract_headers (action a, const scope& bs, file& t, @@ -3105,30 +3253,30 @@ namespace build2 depdb& dd, bool& update, timestamp mt, - module_imports& imports) const + module_imports& imports, + pair<file_cache::entry, bool>& result) const { tracer trace (x, "compile_rule::extract_headers"); + context& ctx (t.ctx); + otype ot (li.type); bool reprocess (cast_false<bool> (t[c_reprocess])); - auto_rmfile psrc; + file_cache::entry psrc; bool puse (true); - // If things go wrong (and they often do in this area), give the user a - // bit extra context. + // Preprocessed file extension. // - auto df = make_diag_frame ( - [&src](const diag_record& dr) - { - if (verb != 0) - dr << info << "while extracting header dependencies from " << src; - }); + const char* pext (x_assembler_cpp (src) ? ".Si" : + x_objective (src) ? x_obj_pext : + x_pext); // Preprocesor mode that preserves as much information as possible while // still performing inclusions. Also serves as a flag indicating whether - // this compiler uses the separate preprocess and compile setup. + // this (non-MSVC) compiler uses the separate preprocess and compile + // setup. // const char* pp (nullptr); @@ -3139,7 +3287,16 @@ namespace build2 // -fdirectives-only is available since GCC 4.3.0. // if (cmaj > 4 || (cmaj == 4 && cmin >= 3)) - pp = "-fdirectives-only"; + { + // Note that for assembler-with-cpp GCC currently forces full + // preprocessing in (what appears to be) an attempt to paper over + // a deeper issue (see GCC bug 109534). If/when that bug gets + // fixed, we can enable this on our side. Note that Clang's + // -frewrite-includes also has issues (see below). + // + if (!x_assembler_cpp (src)) + pp = "-fdirectives-only"; + } break; } @@ -3148,7 +3305,16 @@ namespace build2 // -frewrite-includes is available since Clang 3.2.0. // if (cmaj > 3 || (cmaj == 3 && cmin >= 2)) - pp = "-frewrite-includes"; + { + // While Clang's -frewrite-includes appears to work, there are + // some issues with correctly tracking location information + // (manifests itself as wrong line numbers in debug info, for + // example). The result also appears to reference the .Si file + // instead of the original source file for some reason. + // + if (!x_assembler_cpp (src)) + pp = "-frewrite-includes"; + } break; } @@ -3229,7 +3395,7 @@ namespace build2 // // GCC's -fdirective-only, on the other hand, processes all the // directives so they are gone from the preprocessed source. Here is - // what we are going to do to work around this: we will detect if any + // what we are going to do to work around this: we will sense if any // diagnostics has been written to stderr on the -E run. If that's the // case (but the compiler indicated success) then we assume they are // warnings and disable the use of the preprocessed output for @@ -3258,6 +3424,8 @@ namespace build2 // // So seeing that it is hard to trigger a legitimate VC preprocessor // warning, for now, we will just treat them as errors by adding /WX. + // BTW, another example of a plausible preprocessor warnings are C4819 + // and C4828 (character unrepresentable in source charset). // // Finally, if we are using the module mapper, then all this mess falls // away: we only run the compiler once, we let the diagnostics through, @@ -3265,7 +3433,9 @@ namespace build2 // not found, and there is no problem with outdated generated headers // since we update/remap them before the compiler has a chance to read // them. Overall, this "dependency mapper" approach is how it should - // have been done from the beginning. + // have been done from the beginning. Note: that's the ideal world, + // the reality is that the required mapper extensions are not (yet) + // in libcody/GCC. // Note: diagnostics sensing is currently only supported if dependency // info is written to a file (see above). @@ -3275,15 +3445,15 @@ namespace build2 // And here is another problem: if we have an already generated header // in src and the one in out does not yet exist, then the compiler will // pick the one in src and we won't even notice. Note that this is not - // only an issue with mixing in- and out-of-tree builds (which does feel + // only an issue with mixing in and out of source builds (which does feel // wrong but is oh so convenient): this is also a problem with // pre-generated headers, a technique we use to make installing the // generator by end-users optional by shipping pre-generated headers. // // This is a nasty problem that doesn't seem to have a perfect solution - // (except, perhaps, C++ modules). So what we are going to do is try to - // rectify the situation by detecting and automatically remapping such - // mis-inclusions. It works as follows. + // (except, perhaps, C++ modules and/or module mapper). So what we are + // going to do is try to rectify the situation by detecting and + // automatically remapping such mis-inclusions. It works as follows. // // First we will build a map of src/out pairs that were specified with // -I. Here, for performance and simplicity, we will assume that they @@ -3296,10 +3466,7 @@ namespace build2 // case, then we calculate a corresponding header in the out tree and, // (this is the most important part), check if there is a target for // this header in the out tree. This should be fairly accurate and not - // require anything explicit from the user except perhaps for a case - // where the header is generated out of nothing (so there is no need to - // explicitly mention its target in the buildfile). But this probably - // won't be very common. + // require anything explicit from the user. // // One tricky area in this setup are target groups: if the generated // sources are mentioned in the buildfile as a group, then there might @@ -3309,10 +3476,7 @@ namespace build2 // generated depending on the options (e.g., inline files might be // suppressed), headers are usually non-optional. // - // Note that we use path_map instead of dir_path_map to allow searching - // using path (file path). - // - srcout_map so_map; // path_map<dir_path> + srcout_map so_map; // Dynamic module mapper. // @@ -3320,18 +3484,20 @@ namespace build2 // The gen argument to init_args() is in/out. The caller signals whether // to force the generated header support and on return it signals - // whether this support is enabled. The first call to init_args is - // expected to have gen false. + // whether this support is enabled. If gen is false, then stderr is + // expected to be either discarded or merged with sdtout. // // Return NULL if the dependency information goes to stdout and a // pointer to the temporary file path otherwise. // - auto init_args = [a, &t, ot, li, reprocess, + auto init_args = [a, &t, ot, li, reprocess, pext, &src, &md, &psrc, &sense_diag, &mod_mapper, &bs, pp, &env, &args, &args_gen, &args_i, &out, &drm, &so_map, this] (bool& gen) -> const path* { + context& ctx (t.ctx); + const path* r (nullptr); if (args.empty ()) // First call. @@ -3390,40 +3556,52 @@ namespace build2 // Populate the src-out with the -I$out_base -I$src_base pairs. // { + srcout_builder builder (ctx, so_map); + // Try to be fast and efficient by reusing buffers as much as // possible. // string ds; - // Previous -I innermost scope if out_base plus the difference - // between the scope path and the -I path (normally empty). - // - const scope* s (nullptr); - dir_path p; - for (auto i (args.begin ()), e (args.end ()); i != e; ++i) { + const char* o (*i); + // -I can either be in the "-Ifoo" or "-I foo" form. For VC it // can also be /I. // - const char* o (*i); - size_t n (strlen (o)); - - if (n < 2 || (o[0] != '-' && o[0] != '/') || o[1] != 'I') + // Note also that append_library_options() may have translated + // -I to -isystem or /external:I so we have to recognize those + // as well. + // { - s = nullptr; - continue; - } + bool msvc (cclass == compiler_class::msvc); - if (n == 2) - { - if (++i == e) - break; // Let the compiler complain. + size_t p (0); + if (o[0] == '-' || (msvc && o[0] == '/')) + { + p = (o[1] == 'I' ? 2 : + !msvc && strncmp (o + 1, "isystem", 7) == 0 ? 8 : + msvc && strncmp (o + 1, "external:I", 10) == 0 ? 11 : 0); + } + + if (p == 0) + { + builder.skip (); + continue; + } + + size_t n (strlen (o)); + if (n == p) + { + if (++i == e) + break; // Let the compiler complain. - ds = *i; + ds = *i; + } + else + ds.assign (o + p, n - p); } - else - ds.assign (o + 2, n - 2); if (!ds.empty ()) { @@ -3442,67 +3620,14 @@ namespace build2 // if (!d.empty ()) { - // Ignore any paths containing '.', '..' components. Allow - // any directory separators thought (think -I$src_root/foo - // on Windows). - // - if (d.absolute () && d.normalized (false)) - { - // If we have a candidate out_base, see if this is its - // src_base. - // - if (s != nullptr) - { - const dir_path& bp (s->src_path ()); - - if (d.sub (bp)) - { - if (p.empty () || d.leaf (bp) == p) - { - // We've got a pair. - // - so_map.emplace (move (d), s->out_path () / p); - s = nullptr; // Taken. - continue; - } - } - - // Not a pair. Fall through to consider as out_base. - // - s = nullptr; - } - - // See if this path is inside a project with an out-of- - // tree build and is in the out directory tree. - // - const scope& bs (t.ctx.scopes.find (d)); - if (bs.root_scope () != nullptr) - { - const dir_path& bp (bs.out_path ()); - if (bp != bs.src_path ()) - { - bool e; - if ((e = (d == bp)) || d.sub (bp)) - { - s = &bs; - if (e) - p.clear (); - else - p = d.leaf (bp); - } - } - } - } - else - s = nullptr; - - ds = move (d).string (); // Move the buffer out. + if (!builder.next (move (d))) + ds = move (d).string (); // Move the buffer back out. } else - s = nullptr; + builder.skip (); } else - s = nullptr; + builder.skip (); } } @@ -3511,16 +3636,6 @@ namespace build2 // Some compile options (e.g., -std, -m) affect the preprocessor. // - // Currently Clang supports importing "header modules" even when in - // the TS mode. And "header modules" support macros which means - // imports have to be resolved during preprocessing. Which poses a - // bit of a chicken and egg problem for us. For now, the workaround - // is to remove the -fmodules-ts option when preprocessing. Hopefully - // there will be a "pure modules" mode at some point. - // - // @@ MODHDR Clang: should be solved with the dynamic module mapper - // if/when Clang supports it? - // // Don't treat warnings as errors. // @@ -3547,15 +3662,47 @@ namespace build2 args.push_back ("/nologo"); append_options (args, cmode); - append_sys_inc_options (args); // Extra system header dirs (last). + append_sys_hdr_options (args); // Extra system header dirs (last). + + // Note that for MSVC stderr is merged with stdout and is then + // parsed, so no append_diag_color_options() call. - // See perform_update() for details on overriding the default - // exceptions and runtime. + // See perform_update() for details on the choice of options. // - if (x_lang == lang::cxx && !find_option_prefix ("/EH", args)) + // NOTE: see also the predefs rule if adding anything here. + // + { + bool sc (find_option_prefixes ( + {"/source-charset:", "-source-charset:"}, args)); + bool ec (find_option_prefixes ( + {"/execution-charset:", "-execution-charset:"}, args)); + + if (!sc && !ec) + args.push_back ("/utf-8"); + else + { + if (!sc) + args.push_back ("/source-charset:UTF-8"); + + if (!ec) + args.push_back ("/execution-charset:UTF-8"); + } + } + + if (cvariant != "clang" && isystem (*this)) + { + if (find_option_prefixes ({"/external:I", "-external:I"}, args) && + !find_option_prefixes ({"/external:W", "-external:W"}, args)) + args.push_back ("/external:W0"); + } + + if (x_lang == lang::cxx && + !find_option_prefixes ({"/EH", "-EH"}, args)) args.push_back ("/EHsc"); - if (!find_option_prefixes ({"/MD", "/MT"}, args)) + // NOTE: see similar code in search_modules(). + // + if (!find_option_prefixes ({"/MD", "/MT", "-MD", "-MT"}, args)) args.push_back ("/MD"); args.push_back ("/P"); // Preprocess to file. @@ -3566,16 +3713,16 @@ namespace build2 msvc_sanitize_cl (args); - psrc = auto_rmfile (t.path () + x_pext); + psrc = ctx.fcache->create (t.path () + pext, !modules); if (fc) { args.push_back ("/Fi:"); - args.push_back (psrc.path.string ().c_str ()); + args.push_back (psrc.path ().string ().c_str ()); } else { - out = "/Fi" + psrc.path.string (); + out = "/Fi" + psrc.path ().string (); args.push_back (out.c_str ()); } @@ -3585,8 +3732,21 @@ namespace build2 } case compiler_class::gcc: { + append_options (args, cmode); + append_sys_hdr_options (args); // Extra system header dirs (last). + + // If not gen, then stderr is discarded. + // + if (gen) + append_diag_color_options (args); + // See perform_update() for details on the choice of options. // + // NOTE: see also the predefs rule if adding anything here. + // + if (!find_option_prefix ("-finput-charset=", args)) + args.push_back ("-finput-charset=UTF-8"); + if (ot == otype::s) { if (tclass == "linux" || tclass == "bsd") @@ -3595,8 +3755,7 @@ namespace build2 if (ctype == compiler_type::clang && tsys == "win32-msvc") { - initializer_list<const char*> os {"-nostdlib", "-nostartfiles"}; - if (!find_options (os, cmode) && !find_options (os, args)) + if (!find_options ({"-nostdlib", "-nostartfiles"}, args)) { args.push_back ("-D_MT"); args.push_back ("-D_DLL"); @@ -3615,10 +3774,6 @@ namespace build2 } } - append_options (args, cmode, - cmode.size () - (modules && clang ? 1 : 0)); - append_sys_inc_options (args); // Extra system header dirs (last). - // Setup the dynamic module mapper if needed. // // Note that it's plausible in the future we will use it even if @@ -3710,9 +3865,9 @@ namespace build2 // Preprocessor output. // - psrc = auto_rmfile (t.path () + x_pext); + psrc = ctx.fcache->create (t.path () + pext, !modules); args.push_back ("-o"); - args.push_back (psrc.path.string ().c_str ()); + args.push_back (psrc.path ().string ().c_str ()); } else { @@ -3836,15 +3991,12 @@ namespace build2 // to be inconvenient: some users like to re-run a failed build with // -s not to get "swamped" with errors. // - bool df (!ctx.match_only && !ctx.dry_run_option); - - const file* ht (enter_header (a, bs, t, li, - move (hp), cache, false /* norm */, - pfx_map, so_map).first); - if (ht == nullptr) + auto fail = [&ctx] (const auto& h) -> optional<bool> { + bool df (!ctx.match_only && !ctx.dry_run_option); + diag_record dr; - dr << error << "header '" << hp << "' not found and no rule to " + dr << error << "header " << h << " not found and no rule to " << "generate it"; if (df) @@ -3853,41 +4005,44 @@ namespace build2 if (verb < 4) dr << info << "re-run with --verbose=4 for more information"; - if (df) return nullopt; else dr << endf; - } + if (df) + return nullopt; + else + dr << endf; + }; - // If we are reading the cache, then it is possible the file has since - // been removed (think of a header in /usr/local/include that has been - // uninstalled and now we need to use one from /usr/include). This - // will lead to the match failure which we translate to a restart. - // - if (optional<bool> u = inject_header (a, t, *ht, mt, false /* fail */)) + if (const file* ht = enter_header ( + a, bs, t, li, + move (hp), cache, cache /* normalized */, + pfx_map, so_map).first) { - // Verify/add it to the dependency database. + // If we are reading the cache, then it is possible the file has + // since been removed (think of a header in /usr/local/include that + // has been uninstalled and now we need to use one from + // /usr/include). This will lead to the match failure which we + // translate to a restart. And, yes, this case will trip up + // inject_header(), not enter_header(). // - if (!cache) - dd.expect (ht->path ()); - - skip_count++; - return *u; - } - else if (!cache) - { - diag_record dr; - dr << error << "header " << *ht << " not found and no rule to " - << "generate it"; - - if (df) - dr << info << "failure deferred to compiler diagnostics"; - - if (verb < 4) - dr << info << "re-run with --verbose=4 for more information"; + if (optional<bool> u = inject_header (a, t, *ht, mt, false /*fail*/)) + { + // Verify/add it to the dependency database. + // + if (!cache) + dd.expect (ht->path ()); - if (df) return nullopt; else dr << endf; + skip_count++; + return *u; + } + else if (cache) + { + dd.write (); // Invalidate this line. + return true; + } + else + return fail (*ht); } - - dd.write (); // Invalidate this line. - return true; + else + return fail (hp); // hp is still valid. }; // As above but for a header unit. Note that currently it is only used @@ -3904,13 +4059,13 @@ namespace build2 const file* ht ( enter_header (a, bs, t, li, - move (hp), true /* cache */, true /* norm */, + move (hp), true /* cache */, false /* normalized */, pfx_map, so_map).first); - if (ht == nullptr) + if (ht == nullptr) // hp is still valid. { diag_record dr; - dr << error << "header '" << hp << "' not found and no rule to " + dr << error << "header " << hp << " not found and no rule to " << "generate it"; if (df) @@ -3927,7 +4082,7 @@ namespace build2 // if (inject_header (a, t, *ht, mt, false /* fail */)) { - const file& bt (make_header_sidebuild (a, bs, li, *ht)); + const file& bt (make_header_sidebuild (a, bs, t, li, *ht)); // It doesn't look like we need the cache semantics here since given // the header, we should be able to build its BMI. In other words, a @@ -3956,6 +4111,16 @@ namespace build2 const path* drmp (nullptr); // Points to drm.path () if active. + // If things go wrong (and they often do in this area), give the user a + // bit extra context. + // + auto df = make_diag_frame ( + [&src](const diag_record& dr) + { + if (verb != 0) + dr << info << "while extracting header dependencies from " << src; + }); + // If nothing so far has invalidated the dependency database, then try // the cached data before running the compiler. // @@ -3990,9 +4155,13 @@ namespace build2 // If modules are enabled, then we keep the preprocessed output // around (see apply() for details). // - return modules - ? make_pair (auto_rmfile (t.path () + x_pext, false), true) - : make_pair (auto_rmfile (), false); + if (modules) + { + result.first = ctx.fcache->create_existing (t.path () + pext); + result.second = true; + } + + return; } // This can be a header or a header unit (mapping). @@ -4045,7 +4214,7 @@ namespace build2 // Bail out early if we have deferred a failure. // - return make_pair (auto_rmfile (), false); + return; } } } @@ -4059,11 +4228,24 @@ namespace build2 if (args.empty () || gen != args_gen) drmp = init_args (gen); + // If we are producing the preprocessed output, get its write + // handle. + // + file_cache::write psrcw (psrc + ? psrc.init_new () + : file_cache::write ()); + if (verb >= 3) print_process (args.data ()); // Disable pipe mode. process pr; + // We use the fdstream_mode::skip mode on stdout (cannot be used + // on both) and so dbuf must be destroyed (closed) first. + // + ifdstream is (ifdstream::badbit); + diag_buffer dbuf (ctx); + try { // Assume the preprocessed output (if produced) is usable @@ -4084,217 +4266,229 @@ namespace build2 // bool good_error (false), bad_error (false); - // If we have no generated header support, then suppress all - // diagnostics (if things go badly we will restart with this - // support). - // - if (drmp == nullptr) // Dependency info goes to stdout. + if (mod_mapper) // Dependency info is implied by mapper requests. { - assert (!sense_diag); // Note: could support with fdselect(). + assert (gen && !sense_diag); // Not used in this mode. - // For VC with /P the dependency info and diagnostics all go - // to stderr so redirect it to stdout. + // Note that here we use the skip mode on the diagnostics + // stream which means we have to use own instance of stdout + // stream for the correct destruction order (see below). // - pr = process ( - cpath, - args.data (), - 0, - -1, - cclass == compiler_class::msvc ? 1 : gen ? 2 : -2, - nullptr, // CWD - env.empty () ? nullptr : env.data ()); - } - else // Dependency info goes to a temporary file. - { pr = process (cpath, - args.data (), - mod_mapper ? -1 : 0, - mod_mapper ? -1 : 2, // Send stdout to stderr. - gen ? 2 : sense_diag ? -1 : -2, + args, + -1, + -1, + diag_buffer::pipe (ctx), nullptr, // CWD env.empty () ? nullptr : env.data ()); - // Monitor for module mapper requests and/or diagnostics. If - // diagnostics is detected, mark the preprocessed output as - // unusable for compilation. - // - if (mod_mapper || sense_diag) + dbuf.open (args[0], + move (pr.in_efd), + fdstream_mode::non_blocking | + fdstream_mode::skip); + try { - module_mapper_state mm_state (skip_count, imports); + gcc_module_mapper_state mm_state (skip_count, imports); + + // Note that while we read both streams until eof in normal + // circumstances, we cannot use fdstream_mode::skip for the + // exception case on both of them: we may end up being + // blocked trying to read one stream while the process may + // be blocked writing to the other. So in case of an + // exception we only skip the diagnostics and close the + // mapper stream hard. The latter (together with closing of + // the stdin stream) should happen first so the order of + // the following variable is important. + // + // Note also that we open the stdin stream in the blocking + // mode. + // + ifdstream is (move (pr.in_ofd), + fdstream_mode::non_blocking, + ifdstream::badbit); // stdout + ofdstream os (move (pr.out_fd)); // stdin (badbit|failbit) + + // Read until we reach EOF on all streams. + // + // Note that if dbuf is not opened, then we automatically + // get an inactive nullfd entry. + // + fdselect_set fds {is.fd (), dbuf.is.fd ()}; + fdselect_state& ist (fds[0]); + fdselect_state& dst (fds[1]); - const char* w (nullptr); - try + bool more (false); + for (string l; ist.fd != nullfd || dst.fd != nullfd; ) { - // For now we don't need to do both so let's use a simpler - // blocking implementation. Note that the module mapper - // also needs to be adjusted when switching to the - // non-blocking version. + // @@ Currently we will accept a (potentially truncated) + // line that ends with EOF rather than newline. // -#if 1 - assert (mod_mapper != sense_diag); - - if (mod_mapper) + if (ist.fd != nullfd && getline_non_blocking (is, l)) { - w = "module mapper request"; - - // Note: the order is important (see the non-blocking - // verison for details). - // - ifdstream is (move (pr.in_ofd), - fdstream_mode::skip, - ifdstream::badbit); - ofdstream os (move (pr.out_fd)); - - do + if (eof (is)) { - if (!gcc_module_mapper (mm_state, - a, bs, t, li, - is, os, - dd, update, bad_error, - pfx_map, so_map)) - break; + os.close (); + is.close (); - } while (!is.eof ()); - - os.close (); - is.close (); - } - - if (sense_diag) - { - w = "diagnostics"; - ifdstream is (move (pr.in_efd), fdstream_mode::skip); - puse = puse && (is.peek () == ifdstream::traits_type::eof ()); - is.close (); - } -#else - fdselect_set fds; - auto add = [&fds] (const auto_fd& afd) -> fdselect_state* - { - int fd (afd.get ()); - fdmode (fd, fdstream_mode::non_blocking); - fds.push_back (fd); - return &fds.back (); - }; - - // Note that while we read both streams until eof in - // normal circumstances, we cannot use fdstream_mode::skip - // for the exception case on both of them: we may end up - // being blocked trying to read one stream while the - // process may be blocked writing to the other. So in case - // of an exception we only skip the diagnostics and close - // the mapper stream hard. The latter should happen first - // so the order of the following variable is important. - // - ifdstream es; - ofdstream os; - ifdstream is; - - fdselect_state* ds (nullptr); - if (sense_diag) - { - w = "diagnostics"; - ds = add (pr.in_efd); - es.open (move (pr.in_efd), fdstream_mode::skip); - } - - fdselect_state* ms (nullptr); - if (mod_mapper) - { - w = "module mapper request"; - ms = add (pr.in_ofd); - is.open (move (pr.in_ofd)); - os.open (move (pr.out_fd)); // Note: blocking. - } - - // Set each state pointer to NULL when the respective - // stream reaches eof. - // - while (ds != nullptr || ms != nullptr) - { - w = "output"; - ifdselect (fds); + if (more) + throw_generic_ios_failure (EIO, "unexpected EOF"); - // First read out the diagnostics in case the mapper - // interaction produces more. To make sure we don't get - // blocked by full stderr, the mapper should only handle - // one request at a time. - // - if (ds != nullptr && ds->ready) + ist.fd = nullfd; + } + else { - w = "diagnostics"; - - for (char buf[4096];;) - { - streamsize c (sizeof (buf)); - streamsize n (es.readsome (buf, c)); + optional<bool> r ( + gcc_module_mapper (mm_state, + a, bs, t, li, + l, os, + dd, update, bad_error, + pfx_map, so_map)); - if (puse && n > 0) - puse = false; + more = !r.has_value (); - if (n < c) - break; - } - - if (es.eof ()) - { - es.close (); - ds->fd = nullfd; - ds = nullptr; - } - } - - if (ms != nullptr && ms->ready) - { - w = "module mapper request"; - - gcc_module_mapper (mm_state, - a, bs, t, li, - is, os, - dd, update, bad_error, - pfx_map, so_map); - if (is.eof ()) + if (more || *r) + l.clear (); + else { os.close (); is.close (); - ms->fd = nullfd; - ms = nullptr; + ist.fd = nullfd; } } + + continue; } -#endif - } - catch (const io_error& e) - { - if (pr.wait ()) - fail << "io error handling " << x_lang << " compiler " - << w << ": " << e; - // Fall through. + ifdselect (fds); + + if (dst.ready) + { + if (!dbuf.read ()) + dst.fd = nullfd; + } } - if (mod_mapper) - md.header_units += mm_state.header_units; + md.header_units += mm_state.header_units; + } + catch (const io_error& e) + { + // Note that diag_buffer handles its own io errors so this + // is about mapper stdin/stdout. + // + if (pr.wait ()) + fail << "io error handling " << x_lang << " compiler " + << "module mapper request: " << e; + + // Fall through. } // The idea is to reduce this to the stdout case. // - pr.wait (); - - // With -MG we want to read dependency info even if there is - // an error (in case an outdated header file caused it). But - // with the GCC module mapper an error is non-negotiable, so - // to speak, and so we want to skip all of that. In fact, we - // now write directly to depdb without generating and then + // We now write directly to depdb without generating and then // parsing an intermadiate dependency makefile. // - pr.in_ofd = (ctype == compiler_type::gcc && mod_mapper) - ? auto_fd (nullfd) - : fdopen (*drmp, fdopen_mode::in); + pr.wait (); + pr.in_ofd = nullfd; + } + else + { + // If we have no generated header support, then suppress all + // diagnostics (if things go badly we will restart with this + // support). + // + if (drmp == nullptr) // Dependency info goes to stdout. + { + assert (!sense_diag); // Note: could support if necessary. + + // For VC with /P the dependency info and diagnostics all go + // to stderr so redirect it to stdout. + // + int err ( + cclass == compiler_class::msvc ? 1 : // stdout + !gen ? -2 : // /dev/null + diag_buffer::pipe (ctx, sense_diag /* force */)); + + pr = process ( + cpath, + args, + 0, + -1, + err, + nullptr, // CWD + env.empty () ? nullptr : env.data ()); + + if (cclass != compiler_class::msvc && gen) + { + dbuf.open (args[0], + move (pr.in_efd), + fdstream_mode::non_blocking); // Skip on stdout. + } + } + else // Dependency info goes to temporary file. + { + // Since we only need to read from one stream (dbuf) let's + // use the simpler blocking setup. + // + int err ( + !gen && !sense_diag ? -2 : // /dev/null + diag_buffer::pipe (ctx, sense_diag /* force */)); + + pr = process (cpath, + args, + 0, + 2, // Send stdout to stderr. + err, + nullptr, // CWD + env.empty () ? nullptr : env.data ()); + + if (gen || sense_diag) + { + dbuf.open (args[0], move (pr.in_efd)); + dbuf.read (sense_diag /* force */); + } + + if (sense_diag) + { + if (!dbuf.buf.empty ()) + { + puse = false; + dbuf.buf.clear (); // Discard. + } + } + + // The idea is to reduce this to the stdout case. + // + // Note that with -MG we want to read dependency info even + // if there is an error (in case an outdated header file + // caused it). + // + pr.wait (); + pr.in_ofd = fdopen (*drmp, fdopen_mode::in); + } } + // Read and process dependency information, if any. + // if (pr.in_ofd != nullfd) { + // We have two cases here: reading from stdout and potentially + // stderr (dbuf) or reading from file (see the process startup + // code above for details). If we have to read from two + // streams, then we have to use the non-blocking setup. But we + // cannot use the non-blocking setup uniformly because on + // Windows it's only suppored for pipes. So things are going + // to get a bit hairy. + // + // And there is another twist to this: for MSVC we redirect + // stderr to stdout since the header dependency information is + // part of the diagnostics. If, however, there is some real + // diagnostics, we need to pass it through, potentially with + // buffering. The way we achieve this is by later opening dbuf + // in the EOF state and using it to buffer or stream the + // diagnostics. + // + bool nb (dbuf.is.is_open ()); + // We may not read all the output (e.g., due to a restart). // Before we used to just close the file descriptor to signal // to the other end that we are not interested in the rest. @@ -4302,16 +4496,70 @@ namespace build2 // impolite and complains, loudly (broken pipe). So now we are // going to skip until the end. // - ifdstream is (move (pr.in_ofd), - fdstream_mode::text | fdstream_mode::skip, - ifdstream::badbit); + // Note that this means we are not using skip on dbuf (see + // above for the destruction order details). + // + { + fdstream_mode m (fdstream_mode::text | + fdstream_mode::skip); + + if (nb) + m |= fdstream_mode::non_blocking; + + is.open (move (pr.in_ofd), m); + } + + fdselect_set fds; + if (nb) + fds = {is.fd (), dbuf.is.fd ()}; size_t skip (skip_count); - string l; // Reuse. + string l, l2; // Reuse. for (bool first (true), second (false); !restart; ) { - if (eof (getline (is, l))) - break; + if (nb) + { + fdselect_state& ist (fds[0]); + fdselect_state& dst (fds[1]); + + // We read until we reach EOF on both streams. + // + if (ist.fd == nullfd && dst.fd == nullfd) + break; + + if (ist.fd != nullfd && getline_non_blocking (is, l)) + { + if (eof (is)) + { + ist.fd = nullfd; + continue; + } + + // Fall through to parse (and clear) the line. + } + else + { + ifdselect (fds); + + if (dst.ready) + { + if (!dbuf.read ()) + dst.fd = nullfd; + } + + continue; + } + } + else + { + if (eof (getline (is, l))) + { + if (bad_error && !l2.empty ()) // MSVC only (see below). + dbuf.write (l2, true /* newline */); + + break; + } + } l6 ([&]{trace << "header dependency line '" << l << "'";}); @@ -4322,20 +4570,31 @@ namespace build2 case compiler_class::msvc: { // The first line should be the file we are compiling, - // unless this is clang-cl. If it is not, then something - // went wrong even before we could compile anything - // (e.g., file does not exist). In this case the first - // line (and everything after it) is presumably - // diagnostics. + // unless this is clang-cl. + // + // If it is not, then we have several possibilities: // - // It can, however, be a command line warning, for - // example: + // First, it can be a command line warning, for example: // // cl : Command line warning D9025 : overriding '/W3' with '/W4' // // So we try to detect and skip them assuming they will // also show up during the compilation proper. // + // Another possibility is a mis-spelled option that is + // treated as another file to compile, for example: + // + // cl junk /nologo /P /showIncluses /TP foo.cxx + // junk + // foo.cxx + // c1xx: fatal error C1083: Cannot open source file: 'junk': No such file or directory + // + // Yet another possibility is that something went wrong + // even before we could compile anything. + // + // So the plan is to keep going (in the hope of C1083) + // but print the last line if there is no more input. + // if (first) { if (cvariant != "clang") @@ -4346,16 +4605,29 @@ namespace build2 // size_t p (msvc_sense_diag (l, 'D').first); if (p != string::npos && l[p] == '9') - continue; - - text << l; - bad_error = true; - break; + ; // Skip. + else + { + l2 = l; + + if (!bad_error) + { + dbuf.open_eof (args[0]); + bad_error = true; + } + } + + l.clear (); + continue; } + + l2.clear (); + // Fall through. } first = false; + l.clear (); continue; } @@ -4363,8 +4635,13 @@ namespace build2 if (f.empty ()) // Some other diagnostics. { - text << l; - bad_error = true; + if (!bad_error) + { + dbuf.open_eof (args[0]); + bad_error = true; + } + + dbuf.write (l, true /* newline */); break; } @@ -4458,12 +4735,9 @@ namespace build2 if (l.empty () || l[0] != '^' || l[1] != ':' || l[2] != ' ') { - // @@ Hm, we don't seem to redirect stderr to stdout - // for this class of compilers so I wonder why - // we are doing this? - // if (!l.empty ()) - text << l; + l5 ([&]{trace << "invalid header dependency line '" + << l << "'";}); bad_error = true; break; @@ -4478,22 +4752,37 @@ namespace build2 // "^: \". // if (l.size () == 4 && l[3] == '\\') + { + l.clear (); continue; + } else pos = 3; // Skip "^: ". // Fall through to the 'second' block. } - if (second) - { - second = false; - next_make (l, pos); // Skip the source file. - } - while (pos != l.size ()) { - string f (next_make (l, pos)); + string f ( + make_parser::next ( + l, pos, make_parser::type::prereq).first); + + if (pos != l.size () && l[pos] == ':') + { + l5 ([&]{trace << "invalid header dependency line '" + << l << "'";}); + bad_error = true; + break; + } + + // Skip the source file. + // + if (second) + { + second = false; + continue; + } // Skip until where we left off. // @@ -4537,19 +4826,56 @@ namespace build2 } if (bad_error || md.deferred_failure) + { + // Note that it may be tempting to finish reading out the + // diagnostics before bailing out. But that may end up in + // a deadlock if the process gets blocked trying to write + // to stdout. + // break; + } + + l.clear (); + } + + // We may bail out early from the above loop in case of a + // restart or error. Which means the stderr stream (dbuf) may + // still be open and we need to close it before closing the + // stdout stream (which may try to skip). + // + // In this case we may also end up with incomplete diagnostics + // so discard it. + // + // Generally, it may be tempting to start thinking if we + // should discard buffered diagnostics in other cases, such as + // restart. But remember that during serial execution it will + // go straight to stderr so for consistency (and simplicity) + // we should just print it unless there are good reasons not + // to (also remember that in the restartable modes we normally + // redirect stderr to /dev/null; see the process startup code + // for details). + // + if (dbuf.is.is_open ()) + { + dbuf.is.close (); + dbuf.buf.clear (); } // Bail out early if we have deferred a failure. // + // Let's ignore any buffered diagnostics in this case since + // it would appear after the deferred failure note. + // if (md.deferred_failure) { is.close (); - return make_pair (auto_rmfile (), false); + return; } - // In case of VC, we are parsing stderr and if things go - // south, we need to copy the diagnostics for the user to see. + // In case of VC, we are parsing redirected stderr and if + // things go south, we need to copy the diagnostics for the + // user to see. Note that we should have already opened dbuf + // at EOF above. // if (bad_error && cclass == compiler_class::msvc) { @@ -4564,7 +4890,7 @@ namespace build2 l.compare (p.first, 4, "1083") != 0 && msvc_header_c1083 (l, p)) { - diag_stream_lock () << l << endl; + dbuf.write (l, true /* newline */); } } } @@ -4587,22 +4913,42 @@ namespace build2 if (pr.wait ()) { - if (!bad_error) // Ignore expected successes (we are done). - continue; + { + diag_record dr; + + if (bad_error) + dr << fail << "expected error exit status from " + << x_lang << " compiler"; - fail << "expected error exit status from " << x_lang - << " compiler"; + if (dbuf.is_open ()) + dbuf.close (move (dr)); // Throws if error. + } + + // Ignore expected successes (we are done). + // + if (!restart && psrc) + psrcw.close (); + + continue; } else if (pr.exit->normal ()) { if (good_error) // Ignore expected errors (restart). + { + if (dbuf.is_open ()) + dbuf.close (); + continue; + } } // Fall through. } catch (const io_error& e) { + // Ignore buffered diagnostics (since reading it could be the + // cause of this failure). + // if (pr.wait ()) fail << "unable to read " << x_lang << " compiler header " << "dependency output: " << e; @@ -4611,18 +4957,23 @@ namespace build2 } assert (pr.exit && !*pr.exit); - const process_exit& e (*pr.exit); + const process_exit& pe (*pr.exit); // For normal exit we assume the child process issued some // diagnostics. // - if (e.normal ()) + if (pe.normal ()) { - // If this run was with the generated header support then we - // have issued diagnostics and it's time to give up. + // If this run was with the generated header support then it's + // time to give up. // if (gen) + { + if (dbuf.is_open ()) + dbuf.close (args, pe, 2 /* verbosity */); + throw failed (); + } // Just to recap, being here means something is wrong with the // source: it can be a missing generated header, it can be an @@ -4640,7 +4991,12 @@ namespace build2 // or will issue diagnostics. // if (restart) + { + if (dbuf.is_open ()) + dbuf.close (); + l6 ([&]{trace << "trying again without generated headers";}); + } else { // In some pathological situations we may end up switching @@ -4665,19 +5021,24 @@ namespace build2 // example, because we have removed all the partially // preprocessed source files). // - if (force_gen_skip && *force_gen_skip == skip_count) { - diag_record dr (fail); + diag_record dr; + if (force_gen_skip && *force_gen_skip == skip_count) + { + dr << + fail << "inconsistent " << x_lang << " compiler behavior" << + info << "run the following two commands to investigate"; - dr << "inconsistent " << x_lang << " compiler behavior" << - info << "run the following two commands to investigate"; + dr << info; + print_process (dr, args.data ()); // No pipes. - dr << info; - print_process (dr, args.data ()); // No pipes. + init_args ((gen = true)); + dr << info << ""; + print_process (dr, args.data ()); // No pipes. + } - init_args ((gen = true)); - dr << info << ""; - print_process (dr, args.data ()); // No pipes. + if (dbuf.is_open ()) + dbuf.close (move (dr)); // Throws if error. } restart = true; @@ -4688,7 +5049,15 @@ namespace build2 continue; } else - run_finish (args, pr); // Throws. + { + if (dbuf.is_open ()) + { + dbuf.close (args, pe, 2 /* verbosity */); + throw failed (); + } + else + run_finish (args, pr, 2 /* verbosity */); + } } catch (const process_error& e) { @@ -4713,8 +5082,10 @@ namespace build2 // dd.expect (""); - puse = puse && !reprocess && !psrc.path.empty (); - return make_pair (move (psrc), puse); + puse = puse && !reprocess && psrc; + + result.first = move (psrc); + result.second = puse; } // Return the translation unit information (last argument) and its @@ -4726,13 +5097,25 @@ namespace build2 file& t, linfo li, const file& src, - auto_rmfile& psrc, + file_cache::entry& psrc, const match_data& md, const path& dd, unit& tu) const { tracer trace (x, "compile_rule::parse_unit"); + // Scanning .S files with our parser is hazardous since such files + // sometimes use `#`-style comments. Presumably real compilers just + // ignore them in some way, but it doesn't seem worth it to bother in + // our case. Also, the checksum calculation over assembler tokens feels + // iffy. + // + if (x_assembler_cpp (src)) + { + tu.type = unit_type::non_modular; + return ""; + } + otype ot (li.type); // If things go wrong give the user a bit extra context. Let's call it @@ -4777,8 +5160,8 @@ namespace build2 // may extend cc.reprocess to allow specifying where reprocessing is // needed). // - ps = !psrc.path.empty () && !reprocess; - sp = &(ps ? psrc.path : src.path ()); + ps = psrc && !reprocess; + sp = &(ps ? psrc.path () : src.path ()); // VC's preprocessed output, if present, is fully preprocessed. // @@ -4811,8 +5194,6 @@ namespace build2 case compiler_class::msvc: werror = "/WX"; break; } - bool clang (ctype == compiler_type::clang); - append_options (args, t, c_coptions, werror); append_options (args, t, x_coptions, werror); @@ -4825,12 +5206,43 @@ namespace build2 args.push_back ("/nologo"); append_options (args, cmode); - append_sys_inc_options (args); + append_sys_hdr_options (args); + + // Note: no append_diag_color_options() call since the + // diagnostics is discarded. + + // See perform_update() for details on the choice of options. + // + { + bool sc (find_option_prefixes ( + {"/source-charset:", "-source-charset:"}, args)); + bool ec (find_option_prefixes ( + {"/execution-charset:", "-execution-charset:"}, args)); + + if (!sc && !ec) + args.push_back ("/utf-8"); + else + { + if (!sc) + args.push_back ("/source-charset:UTF-8"); - if (x_lang == lang::cxx && !find_option_prefix ("/EH", args)) + if (!ec) + args.push_back ("/execution-charset:UTF-8"); + } + } + + if (cvariant != "clang" && isystem (*this)) + { + if (find_option_prefixes ({"/external:I", "-external:I"}, args) && + !find_option_prefixes ({"/external:W", "-external:W"}, args)) + args.push_back ("/external:W0"); + } + + if (x_lang == lang::cxx && + !find_option_prefixes ({"/EH", "-EH"}, args)) args.push_back ("/EHsc"); - if (!find_option_prefixes ({"/MD", "/MT"}, args)) + if (!find_option_prefixes ({"/MD", "/MT", "-MD", "-MT"}, args)) args.push_back ("/MD"); args.push_back ("/E"); @@ -4844,6 +5256,17 @@ namespace build2 } case compiler_class::gcc: { + append_options (args, cmode); + append_sys_hdr_options (args); + + // Note: no append_diag_color_options() call since the + // diagnostics is discarded. + + // See perform_update() for details on the choice of options. + // + if (!find_option_prefix ("-finput-charset=", args)) + args.push_back ("-finput-charset=UTF-8"); + if (ot == otype::s) { if (tclass == "linux" || tclass == "bsd") @@ -4852,8 +5275,7 @@ namespace build2 if (ctype == compiler_type::clang && tsys == "win32-msvc") { - initializer_list<const char*> os {"-nostdlib", "-nostartfiles"}; - if (!find_options (os, cmode) && !find_options (os, args)) + if (!find_options ({"-nostdlib", "-nostartfiles"}, args)) { args.push_back ("-D_MT"); args.push_back ("-D_DLL"); @@ -4872,10 +5294,6 @@ namespace build2 } } - append_options (args, cmode, - cmode.size () - (modules && clang ? 1 : 0)); - append_sys_inc_options (args); - args.push_back ("-E"); append_lang_options (args, md); @@ -4884,12 +5302,36 @@ namespace build2 // if (ps) { - if (ctype == compiler_type::gcc) + switch (ctype) { - // Note that only these two *plus* -x do the trick. - // - args.push_back ("-fpreprocessed"); - args.push_back ("-fdirectives-only"); + case compiler_type::gcc: + { + // Note that only these two *plus* -x do the trick. + // + args.push_back ("-fpreprocessed"); + args.push_back ("-fdirectives-only"); + break; + } + case compiler_type::clang: + { + // See below for details. + // + if (ctype == compiler_type::clang && + cmaj >= (cvariant != "apple" ? 15 : 16)) + { + if (find_options ({"-pedantic", "-pedantic-errors", + "-Wpedantic", "-Werror=pedantic"}, + args)) + { + args.push_back ("-Wno-gnu-line-marker"); + } + } + + break; + } + case compiler_type::msvc: + case compiler_type::icc: + assert (false); } } @@ -4917,11 +5359,16 @@ namespace build2 for (;;) // Breakout loop. try { - // Disarm the removal of the preprocessed file in case of an error. - // We re-arm it below. + // If we are compiling the preprocessed output, get its read handle. // - if (ps) - psrc.active = false; + file_cache::read psrcr (ps ? psrc.open () : file_cache::read ()); + + // Temporarily disable the removal of the preprocessed file in case of + // an error. We re-enable it below. + // + bool ptmp (ps && psrc.temporary); + if (ptmp) + psrc.temporary = false; process pr; @@ -4938,10 +5385,10 @@ namespace build2 print_process (args); // We don't want to see warnings multiple times so ignore all - // diagnostics. + // diagnostics (thus no need for diag_buffer). // pr = process (cpath, - args.data (), + args, 0, -1, -2, nullptr, // CWD env.empty () ? nullptr : env.data ()); @@ -4953,14 +5400,14 @@ namespace build2 fdstream_mode::binary | fdstream_mode::skip); parser p; - p.parse (is, path_name (*sp), tu); + p.parse (is, path_name (*sp), tu, cid); is.close (); if (pr.wait ()) { - if (ps) - psrc.active = true; // Re-arm. + if (ptmp) + psrc.temporary = true; // Re-enable. unit_type& ut (tu.type); module_info& mi (tu.module_info); @@ -4968,7 +5415,9 @@ namespace build2 if (!modules) { if (ut != unit_type::non_modular || !mi.imports.empty ()) - fail << "modules support required by " << src; + fail << "modules support required by " << src << + info << "consider enabling modules with " + << x << ".features.modules=true in root.build"; } else { @@ -4993,25 +5442,21 @@ namespace build2 ut = md.type; mi.name = src.path ().string (); } - - // Prior to 15.5 (19.12) VC was not using the 'export module M;' - // syntax so we use the preprequisite type to distinguish - // between interface and implementation units. - // - // @@ TMP: probably outdated. - // - if (ctype == compiler_type::msvc && cmaj == 19 && cmin <= 11) - { - if (ut == unit_type::module_impl && src.is_a (*x_mod)) - ut = unit_type::module_intf; - } } // If we were forced to reprocess, assume the checksum is not // accurate (parts of the translation unit could have been // #ifdef'ed out; see __build2_preprocess). // - return reprocess ? string () : move (p.checksum); + // Also, don't use the checksum for header units since it ignores + // preprocessor directives and may therefore cause us to ignore a + // change to an exported macro. @@ TODO: maybe we should add a + // flag to the parser not to waste time calculating the checksum + // in these cases. + // + return reprocess || ut == unit_type::module_header + ? string () + : move (p.checksum); } // Fall through. @@ -5042,7 +5487,7 @@ namespace build2 info << "then run failing command to display compiler diagnostics"; } else - run_finish (args, pr); // Throws. + run_finish (args, pr, 2 /* verbosity */); // Throws. } catch (const process_error& e) { @@ -5211,6 +5656,9 @@ namespace build2 { tracer trace (x, "compile_rule::search_modules"); + context& ctx (bs.ctx); + const scope& rs (*bs.root_scope ()); + // NOTE: currently we don't see header unit imports (they are handled by // extract_headers() and are not in imports). @@ -5234,7 +5682,7 @@ namespace build2 // // In the above examples one common theme about all the file names is // that they contain, in one form or another, the "tail" of the module - // name ('core'). So what we are going to do is require that, within a + // name (`core`). So what we are going to do is require that, within a // pool (library, executable), the interface file names contain enough // of the module name tail to unambiguously resolve all the module // imports. On our side we are going to implement a "fuzzy" module name @@ -5246,14 +5694,14 @@ namespace build2 // So, the fuzzy match: the idea is that each match gets a score, the // number of characters in the module name that got matched. A match // with the highest score is used. And we use the (length + 1) for a - // match against an actual module name. + // match against an actual (extracted) module name. // // Actually, the scoring system is a bit more elaborate than that. // Consider module name core.window and two files, window.mxx and // abstract-window.mxx: which one is likely to define this module? // Clearly the first, but in the above-described scheme they will get // the same score. More generally, consider these "obvious" (to the - // human) situations: + // human, that is) situations: // // window.mxx vs abstract-window.mxx // details/window.mxx vs abstract-window.mxx @@ -5262,18 +5710,22 @@ namespace build2 // To handle such cases we are going to combine the above primary score // with the following secondary scores (in that order): // - // a) Strength of separation between matched and unmatched parts: + // A) Strength of separation between matched and unmatched parts: // // '\0' > directory separator > other separator > unseparated // // Here '\0' signifies nothing to separate (unmatched part is empty). // - // b) Shortness of the unmatched part. + // B) Shortness of the unmatched part. // - // For std.* modules we only accept non-fuzzy matches (think std.core vs - // some core.mxx). And if such a module is unresolved, then we assume it - // is pre-built and will be found by some other means (e.g., VC's - // IFCPATH). + // Finally, for the fuzzy match we require a complete match of the last + // module (or partition) component. Failed that, we will match `format` + // to `print` because the last character (`t`) is the same. + // + // For std.* modules we only accept non-fuzzy matches (think std.compat + // vs some compat.mxx). And if such a module is unresolved, then we + // assume it is pre-built and will be found by some other means (e.g., + // VC's IFCPATH). // // Note also that we handle module partitions the same as submodules. In // other words, for matching, `.` and `:` are treated the same. @@ -5285,8 +5737,12 @@ namespace build2 // // PPPPABBBB // - // We use decimal instead of binary packing to make it easier to - // separate fields in the trace messages, during debugging, etc. + // Where PPPP is the primary score, A is the A) score, and BBBB is + // the B) score described above. Zero signifies no match. + // + // We use decimal instead of binary packing to make it easier for the + // human to separate fields in the trace messages, during debugging, + // etc. // return m.size () * 100000 + 99999; // Maximum match score. }; @@ -5309,6 +5765,8 @@ namespace build2 (ucase (c1) == c1) != (ucase (c2) == c2)); }; + auto mod_sep = [] (char c) {return c == '.' || c == ':';}; + size_t fn (f.size ()), fi (fn); size_t mn (m.size ()), mi (mn); @@ -5318,6 +5776,10 @@ namespace build2 bool fsep (false); bool msep (false); + // We require complete match of at least last module component. + // + bool match (false); + // Scan backwards for as long as we match. Keep track of the previous // character for case change detection. // @@ -5343,11 +5805,12 @@ namespace build2 // FOObar // bool fs (char_sep (fc)); - bool ms (mc == '_' || mc == '.' || mc == ':'); + bool ms (mod_sep (mc) || mc == '_'); if (fs && ms) { fsep = msep = true; + match = match || mod_sep (mc); continue; } @@ -5365,6 +5828,7 @@ namespace build2 if (fa) {++fi; msep = true;} if (ma) {++mi; fsep = true;} + match = match || mod_sep (mc); continue; } } @@ -5372,6 +5836,39 @@ namespace build2 break; // No match. } + // Deal with edge cases: complete module match and complete file + // match. + // + match = match || mi == 0 || (fi == 0 && mod_sep (m[mi - 1])); + + if (!match) + return 0; + + // Here is another corner case, the module is async_simple:IOExecutor + // and the file names are: + // + // IOExecutor.mxx + // SimpleIOExecutor.mxx + // + // The above implementation treats the latter as better because + // `Simple` in SimpleIOExecutor matches `simple` in async_simple. It's + // unclear what we can do about it without potentially breaking other + // legitimate cases (think Boost_Simple:IOExecutor). Maybe we could + // boost the exact partition name match score, similar to the exact + // module match, as some sort of a heuristics? Let's try. + // + if (fi == 0 && mi != 0 && m[mi - 1] == ':') + { + // Pretend we matched one short of the next module component. This + // way AsyncSimpleIOExecutor.mxx would still be a better match. + // + while (--mi != 0 && m[mi - 1] != '.') + ; + + msep = (mi != 0); // For uncount logic below. + mi++; // One short. + } + // "Uncount" real separators. // if (fsep) fi++; @@ -5400,6 +5897,20 @@ namespace build2 return ps * 100000 + as * 10000 + bs; }; +#if 0 + assert (match ("IOExecutor", "async_simple:IOExecutor") > + match ("SimpleIOExecutor", "async_simple:IOExecutor")); + + assert (match ("IOExecutor", "async_simple:IOExecutor") < + match ("AsyncSimpleIOExecutor", "async_simple:IOExecutor")); + + assert (match ("IOExecutor", "x.async_simple:IOExecutor") > + match ("SimpleIOExecutor", "x.async_simple:IOExecutor")); + + assert (match ("IOExecutor", "x.async_simple:IOExecutor") < + match ("AsyncSimpleIOExecutor", "x.async_simple:IOExecutor")); +#endif + auto& pts (t.prerequisite_targets[a]); size_t start (pts.size ()); // Index of the first to be added. @@ -5414,7 +5925,7 @@ namespace build2 // promise. It has to do with module re-exporting (export import M;). // In this case (currently) all implementations simply treat it as a // shallow (from the BMI's point of view) reference to the module (or an - // implicit import, if you will). Do you see where it's going? Nowever + // implicit import, if you will). Do you see where it's going? Nowhere // good, that's right. This shallow reference means that the compiler // should be able to find BMIs for all the re-exported modules, // recursively. The good news is we are actually in a pretty good shape @@ -5431,10 +5942,11 @@ namespace build2 // 1. There is no good place in prerequisite_targets to store the // exported flag (no, using the marking facility across match/execute // is a bad idea). So what we are going to do is put re-exported - // bmi{}s at the back and store (in the target's data pad) the start - // position. One bad aspect about this part is that we assume those - // bmi{}s have been matched by the same rule. But let's not kid - // ourselves, there will be no other rule that matches bmi{}s. + // bmi{}s at the back and store (in the target's auxiliary data + // storage) the start position. One bad aspect about this part is + // that we assume those bmi{}s have been matched by the same + // rule. But let's not kid ourselves, there will be no other rule + // that matches bmi{}s. // // @@ I think now we could use prerequisite_targets::data for this? // @@ -5459,6 +5971,7 @@ namespace build2 // so we actually don't need to pass any extra options (unless things // get moved) but they still need access to the BMIs (and things will // most likely have to be done differenly for distributed compilation). + // @@ Note: no longer the case for Clang either. // // So the revised plan: on the off chance that some implementation will // do it differently we will continue maintaing the imported/re-exported @@ -5552,6 +6065,8 @@ namespace build2 continue; // Scan the rest to detect if all done. } } + else + assert (name != m.name); // No duplicates. done = false; } @@ -5559,159 +6074,372 @@ namespace build2 return r; }; - for (prerequisite_member p: group_prerequisite_members (a, t)) + // Find the module in prerequisite targets of a library (recursively) + // seeing through libu*{}. Note: sets the `done` flag. See similar + // logic in pkgconfig_save(). + // + auto find = [a, &bs, this, + &check_exact, &done] (const file& l, + const auto& find) -> void { - if (include (a, t, p) != include_type::normal) // Excluded/ad hoc. - continue; - - const target* pt (p.load ()); // Should be cached for libraries. - - if (pt != nullptr) + for (const target* pt: l.prerequisite_targets[a]) { - const file* lt (nullptr); - - if (const libx* l = pt->is_a<libx> ()) - lt = link_member (*l, a, li); - else if (pt->is_a<liba> () || pt->is_a<libs> () || pt->is_a<libux> ()) - lt = &pt->as<file> (); + if (pt == nullptr) + continue; - // If this is a library, check its bmi{}s and mxx{}s. + // Note that here we (try) to use whatever flavor of bmi*{} is + // available. // - if (lt != nullptr) + // @@ MOD: BMI compatibility check. + // + if (pt->is_a<bmix> ()) { - for (const target* bt: lt->prerequisite_targets[a]) + // If the extraction of the module information for this BMI failed + // and we have deferred failure to compiler diagnostics, then + // there will be no module name assigned. It would have been + // better to make sure that's the cause, but that won't be easy. + // + const string* n (cast_null<string> ( + pt->state[a].vars[c_module_name])); + if (n != nullptr) { - if (bt == nullptr) - continue; + if (const target** p = check_exact (*n)) + *p = pt; + } + } + else if (pt->is_a (*x_mod)) + { + // This is an installed library with a list of module sources (the + // source are specified as prerequisites but the fallback file + // rule puts them into prerequisite_targets for us). + // + // The module names should be specified but if not assume + // something else is going on (like a deferred failure) and + // ignore. + // + // Note also that besides modules, prerequisite_targets may + // contain libraries which are interface dependencies of this + // library and which may be called to resolve its module + // dependencies. + // + const string* n (cast_null<string> (pt->vars[c_module_name])); - // Note that here we (try) to use whatever flavor of bmi*{} is - // available. - // - // @@ MOD: BMI compatibility check. - // @@ UTL: we need to (recursively) see through libu*{} (and - // also in pkgconfig_save()). + if (n == nullptr) + continue; + + if (const target** p = check_exact (*n)) + { + // It seems natural to build a BMI type that corresponds to the + // library type. After all, this is where the object file part + // of the BMI is going to come from (unless it's a module + // interface-only library). // - if (bt->is_a<bmix> ()) + *p = &this->make_module_sidebuild ( + a, bs, &l, link_type (l).type, *pt, *n).first; // GCC 4.9 + } + } + // Note that in prerequisite targets we will have the libux{} + // members, not the group. + // + else if (const libux* pl = pt->is_a<libux> ()) + find (*pl, find); + else + continue; + + if (done) + break; + } + }; + + // Pre-resolve std modules in an ad hoc way for certain compilers. + // + // @@ TODO: cache x_stdlib value. + // + if ((ctype == compiler_type::msvc) || + (ctype == compiler_type::clang && + cmaj >= 17 && + cast<string> (rs[x_stdlib]) == "libc++")) + { + // Similar logic to check_exact() above. + // + done = true; + + for (size_t i (0); i != n; ++i) + { + module_import& m (imports[i]); + + if (m.name == "std" || m.name == "std.compat") + { + otype ot (otype::e); + const target* mt (nullptr); + + switch (ctype) + { + case compiler_type::clang: { - const string& n ( - cast<string> (bt->state[a].vars[c_module_name])); + if (m.name != "std") + fail << "module " << m.name << " not yet provided by libc++"; - if (const target** p = check_exact (n)) - *p = bt; + // Find or insert std.cppm (similar code to pkgconfig.cxx). + // + // Note: build_install_data is absolute and normalized. + // + mt = &ctx.targets.insert_locked ( + *x_mod, + (dir_path (build_install_data) /= "libbuild2") /= "cc", + dir_path (), + "std", + string ("cppm"), // For C++14 during bootstrap. + target_decl::implied, + trace).first; + + // Which output type should we use, static or shared? The + // correct way would be to detect whether static or shared + // version of libc++ is to be linked and use the corresponding + // type. And we could do that by looking for -static-libstdc++ + // in loption (and no, it's not -static-libc++). + // + // But, looking at the object file produced from std.cppm, it + // only contains one symbol, the static object initializer. + // And this is unlikely to change since all other non-inline + // or template symbols should be in libc++. So feels like it's + // not worth the trouble and one variant should be good enough + // for both cases. Let's use the shared one for less + // surprising diagnostics (as in, "why are you linking obje{} + // to a shared library?") + // + // (Of course, theoretically, std.cppm could detect via a + // macro whether it's being compiled with -fPIC or not and do + // things differently, but this seems far-fetched). + // + ot = otype::s; + + break; } - else if (bt->is_a (*x_mod)) + case compiler_type::msvc: { - // This is an installed library with a list of module sources - // (the source are specified as prerequisites but the fallback - // file rule puts them into prerequisite_targets for us). + // For MSVC, the source files std.ixx and std.compat.ixx are + // found in the modules/ subdirectory which is a sibling of + // include/ in the MSVC toolset (and "that is a contract with + // customers" to quote one of the developers). // - // The module names should be specified but if not assume - // something else is going on and ignore. + // The problem of course is that there are multiple system + // header search directories (for example, as specified in the + // INCLUDE environment variable) and which one of them is for + // the MSVC toolset is not specified. So what we are going to + // do is search for one of the well-known standard C++ headers + // and assume that the directory where we found it is the one + // we are looking for. Or we could look for something + // MSVC-specific like vcruntime.h. + // + dir_path modules; + if (optional<path> p = find_system_header (path ("vcruntime.h"))) + { + p->make_directory (); // Strip vcruntime.h. + if (p->leaf () == path ("include")) // Sanity check. + { + modules = path_cast<dir_path> (move (p->make_directory ())); + modules /= "modules"; + } + } + + if (modules.empty ()) + fail << "unable to locate MSVC standard modules directory"; + + mt = &ctx.targets.insert_locked ( + *x_mod, + move (modules), + dir_path (), + m.name, + string ("ixx"), // For C++14 during bootstrap. + target_decl::implied, + trace).first; + + // For MSVC it's easier to detect the runtime being used since + // it's specified with the compile options (/MT[d], /MD[d]). // - // Note also that besides modules, prerequisite_targets may - // contain libraries which are interface dependencies of this - // library and which may be called to resolve its module - // dependencies. + // Similar semantics as in extract_headers() except here we + // use options visible from the root scope. Note that + // find_option_prefixes() looks in reverse, so look in the + // cmode, x_coptions, c_coptions order. // - const string* n (cast_null<string> (bt->vars[c_module_name])); + initializer_list<const char*> os {"/MD", "/MT", "-MD", "-MT"}; - if (n == nullptr) - continue; + const string* o; + if ((o = find_option_prefixes (os, cmode)) != nullptr || + (o = find_option_prefixes (os, rs, x_coptions)) != nullptr || + (o = find_option_prefixes (os, rs, c_coptions)) != nullptr) + { + ot = (*o)[2] == 'D' ? otype::s : otype::a; + } + else + ot = otype::s; // The default is /MD. - if (const target** p = check_exact (*n)) - *p = &make_module_sidebuild (a, bs, *lt, *bt, *n); + break; } - else - continue; + case compiler_type::gcc: + case compiler_type::icc: + assert (false); + }; - if (done) - break; - } + pair<target&, ulock> tl ( + this->make_module_sidebuild ( // GCC 4.9 + a, bs, nullptr, ot, *mt, m.name)); - if (done) - break; + if (tl.second.owns_lock ()) + { + // Special compile options for the std modules. + // + if (ctype == compiler_type::clang) + { + value& v (tl.first.append_locked (x_coptions)); - continue; - } + if (v.null) + v = strings {}; - // Fall through. - } + strings& cops (v.as<strings> ()); - // While it would have been even better not to search for a target, we - // need to get hold of the corresponding mxx{} (unlikely but possible - // for bmi{} to have a different name). - // - // While we want to use group_prerequisite_members() below, we cannot - // call resolve_group() since we will be doing it "speculatively" for - // modules that we may use but also for modules that may use us. This - // quickly leads to deadlocks. So instead we are going to perform an - // ad hoc group resolution. - // - const target* pg; - if (p.is_a<bmi> ()) - { - pg = pt != nullptr ? pt : &p.search (t); - pt = &search (t, btt, p.key ()); // Same logic as in picking obj*{}. - } - else if (p.is_a (btt)) - { - pg = &search (t, bmi::static_type, p.key ()); - if (pt == nullptr) pt = &p.search (t); + switch (ctype) + { + case compiler_type::clang: + { + cops.push_back ("-Wno-reserved-module-identifier"); + break; + } + case compiler_type::msvc: + // It appears nothing special is needed to compile MSVC + // standard modules. + case compiler_type::gcc: + case compiler_type::icc: + assert (false); + }; + } + + tl.second.unlock (); + } + + pts[start + i].target = &tl.first; + m.score = match_max (m.name) + 1; + continue; // Scan the rest to detect if all done. + } + + done = false; } - else - continue; + } - // Find the mxx{} prerequisite and extract its "file name" for the - // fuzzy match unless the user specified the module name explicitly. - // - for (prerequisite_member p: - prerequisite_members (a, t, group_prerequisites (*pt, pg))) + // Go over prerequisites and try to resolve imported modules with them. + // + if (!done) + { + for (prerequisite_member p: group_prerequisite_members (a, t)) { if (include (a, t, p) != include_type::normal) // Excluded/ad hoc. continue; - if (p.is_a (*x_mod)) + const target* pt (p.load ()); // Should be cached for libraries. + + if (pt != nullptr) { - // Check for an explicit module name. Only look for an existing - // target (which means the name can only be specified on the - // target itself, not target type/pattern-spec). + const file* lt (nullptr); + + if (const libx* l = pt->is_a<libx> ()) + lt = link_member (*l, a, li); + else if (pt->is_a<liba> () || + pt->is_a<libs> () || + pt->is_a<libux> ()) + lt = &pt->as<file> (); + + // If this is a library, check its bmi{}s and mxx{}s. // - const target* t (p.search_existing ()); - const string* n (t != nullptr - ? cast_null<string> (t->vars[c_module_name]) - : nullptr); - if (n != nullptr) + if (lt != nullptr) { - if (const target** p = check_exact (*n)) - *p = pt; + find (*lt, find); + + if (done) + break; + + continue; } - else + + // Fall through. + } + + // While it would have been even better not to search for a target, + // we need to get hold of the corresponding mxx{} (unlikely but + // possible for bmi{} to have a different name). + // + // While we want to use group_prerequisite_members() below, we + // cannot call resolve_group() since we will be doing it + // "speculatively" for modules that we may use but also for modules + // that may use us. This quickly leads to deadlocks. So instead we + // are going to perform an ad hoc group resolution. + // + const target* pg; + if (p.is_a<bmi> ()) + { + pg = pt != nullptr ? pt : &p.search (t); + pt = &search (t, btt, p.key ()); // Same logic as in picking obj*{}. + } + else if (p.is_a (btt)) + { + pg = &search (t, bmi::static_type, p.key ()); + if (pt == nullptr) pt = &p.search (t); + } + else + continue; + + // Find the mxx{} prerequisite and extract its "file name" for the + // fuzzy match unless the user specified the module name explicitly. + // + for (prerequisite_member p: + prerequisite_members (a, t, group_prerequisites (*pt, pg))) + { + if (include (a, t, p) != include_type::normal) // Excluded/ad hoc. + continue; + + if (p.is_a (*x_mod)) { - // Fuzzy match. + // Check for an explicit module name. Only look for an existing + // target (which means the name can only be specified on the + // target itself, not target type/pattern-spec). // - string f; + const target* mt (p.search_existing ()); + const string* n (mt != nullptr + ? cast_null<string> (mt->vars[c_module_name]) + : nullptr); + if (n != nullptr) + { + if (const target** p = check_exact (*n)) + *p = pt; + } + else + { + // Fuzzy match. + // + string f; - // Add the directory part if it is relative. The idea is to - // include it into the module match, say hello.core vs - // hello/mxx{core}. - // - // @@ MOD: Why not for absolute? Good question. What if it - // contains special components, say, ../mxx{core}? - // - const dir_path& d (p.dir ()); + // Add the directory part if it is relative. The idea is to + // include it into the module match, say hello.core vs + // hello/mxx{core}. + // + // @@ MOD: Why not for absolute? Good question. What if it + // contains special components, say, ../mxx{core}? + // + const dir_path& d (p.dir ()); - if (!d.empty () && d.relative ()) - f = d.representation (); // Includes trailing slash. + if (!d.empty () && d.relative ()) + f = d.representation (); // Includes trailing slash. - f += p.name (); - check_fuzzy (pt, f); + f += p.name (); + check_fuzzy (pt, f); + } + break; } - break; } - } - if (done) - break; + if (done) + break; + } } // Diagnose unresolved modules. @@ -5739,8 +6467,13 @@ namespace build2 // // But at this stage this doesn't seem worth the trouble. // - fail (relative (src)) << "unable to resolve module " - << imports[i].name; + fail (relative (src)) + << "unable to resolve module " << imports[i].name << + info << "verify module interface is listed as a prerequisite, " + << "otherwise" << + info << "consider adjusting module interface file names or" << + info << "consider specifying module name with " << x + << ".module_name"; } } } @@ -5776,9 +6509,12 @@ namespace build2 if (m.score <= match_max (in)) { - const string& mn (cast<string> (bt->state[a].vars[c_module_name])); + // As above (deffered failure). + // + const string* mn ( + cast_null<string> (bt->state[a].vars[c_module_name])); - if (in != mn) + if (mn != nullptr && in != *mn) { // Note: matched, so the group should be resolved. // @@ -5792,7 +6528,7 @@ namespace build2 fail (relative (src)) << "failed to correctly guess module name from " << p << info << "guessed: " << in << - info << "actual: " << mn << + info << "actual: " << *mn << info << "consider adjusting module interface file names or" << info << "consider specifying module name with " << x << ".module_name"; @@ -5803,11 +6539,11 @@ namespace build2 // Hash (we know it's a file). // - cs.append (static_cast<const file&> (*bt).path ().string ()); + cs.append (bt->as<file> ().path ().string ()); // Copy over bmi{}s from our prerequisites weeding out duplicates. // - if (size_t j = bt->data<match_data> ().modules.start) + if (size_t j = bt->data<match_data> (a).modules.start) { // Hard to say whether we should reserve or not. We will probably // get quite a bit of duplications. @@ -5820,26 +6556,29 @@ namespace build2 if (et == nullptr) continue; // Unresolved (std.*). - const string& mn (cast<string> (et->state[a].vars[c_module_name])); + // As above (deferred failure). + // + const string* mn (cast_null<string> (et->state[a].vars[c_module_name])); - if (find_if (imports.begin (), imports.end (), - [&mn] (const module_import& i) + if (mn != nullptr && + find_if (imports.begin (), imports.end (), + [mn] (const module_import& i) { - return i.name == mn; + return i.name == *mn; }) == imports.end ()) { pts.push_back (et); - cs.append (static_cast<const file&> (*et).path ().string ()); + cs.append (et->as<file> ().path ().string ()); // Add to the list of imports for further duplicate suppression. // We could have stored reference to the name (e.g., in score) // but it's probably not worth it if we have a small string // optimization. // - import_type t (mn.find (':') != string::npos + import_type t (mn->find (':') != string::npos ? import_type::module_part : import_type::module_intf); - imports.push_back (module_import {t, mn, true, 0}); + imports.push_back (module_import {t, *mn, true, 0}); } } } @@ -5859,7 +6598,11 @@ namespace build2 // Find or create a modules sidebuild subproject returning its root // directory. // - dir_path compile_rule:: + // @@ Could we omit creating a subproject if the sidebuild scope is the + // project scope itself? This would speed up simple examples (and + // potentially direct compilation that we may support). + // + pair<dir_path, const scope&> compile_rule:: find_modules_sidebuild (const scope& rs) const { context& ctx (rs.ctx); @@ -5869,6 +6612,9 @@ namespace build2 // cc.config module and that is within our amalgmantion seems like a // good place. // + // @@ TODO: maybe we should cache this in compile_rule ctor like we + // do for the header cache? + // const scope* as (&rs); { const scope* ws (as->weak_scope ()); @@ -5884,7 +6630,7 @@ namespace build2 // This is also the module that registers the scope operation // callback that cleans up the subproject. // - if (cast_false<bool> ((*s)["cc.core.vars.loaded"])) + if (cast_false<bool> (s->vars["cc.core.vars.loaded"])) as = s; } while (s != ws); @@ -5901,7 +6647,7 @@ namespace build2 module_build_modules_dir /= x); - const scope* ps (&ctx.scopes.find (pd)); + const scope* ps (&ctx.scopes.find_out (pd)); if (ps->out_path () != pd) { @@ -5912,7 +6658,7 @@ namespace build2 // Re-test again now that we are in exclusive phase (another thread // could have already created and loaded the subproject). // - ps = &ctx.scopes.find (pd); + ps = &ctx.scopes.find_out (pd); if (ps->out_path () != pd) { @@ -5957,16 +6703,21 @@ namespace build2 assert (m != nullptr && m->modules); #endif - return pd; + return pair<dir_path, const scope&> (move (pd), *as); } - // Synthesize a dependency for building a module binary interface on - // the side. + // Synthesize a dependency for building a module binary interface of a + // library on the side. If library is missing, then assume it's some + // ad hoc/system library case (in which case we assume it's binless, + // for now). // - const file& compile_rule:: + // The return value semantics is as in target_set::insert_locked(). + // + pair<target&, ulock> compile_rule:: make_module_sidebuild (action a, const scope& bs, - const file& lt, + const file* lt, + otype ot, const target& mt, const string& mn) const { @@ -5974,7 +6725,7 @@ namespace build2 // Note: see also make_header_sidebuild() below. - dir_path pd (find_modules_sidebuild (*bs.root_scope ())); + dir_path pd (find_modules_sidebuild (*bs.root_scope ()).first); // We need to come up with a file/target name that will be unique enough // not to conflict with other modules. If we assume that within an @@ -5987,24 +6738,20 @@ namespace build2 back_inserter (mf), [] (char c) {return c == '.' ? '-' : c == ':' ? '+' : c;}); - // It seems natural to build a BMI type that corresponds to the library - // type. After all, this is where the object file part of the BMI is - // going to come from (unless it's a module interface-only library). - // - const target_type& tt (compile_types (link_type (lt).type).bmi); + const target_type& tt (compile_types (ot).bmi); // Store the BMI target in the subproject root. If the target already // exists then we assume all this is already done (otherwise why would // someone have created such a target). // - if (const file* bt = bs.ctx.targets.find<file> ( + if (const target* bt = bs.ctx.targets.find ( tt, pd, dir_path (), // Always in the out tree. mf, nullopt, // Use default extension. trace)) - return *bt; + return pair<target&, ulock> (const_cast<target&> (*bt), ulock ()); prerequisites ps; ps.push_back (prerequisite (mt)); @@ -6017,24 +6764,22 @@ namespace build2 // // Note: lt is matched and so the group is resolved. // - ps.push_back (prerequisite (lt)); - for (prerequisite_member p: group_prerequisite_members (a, lt)) + if (lt != nullptr) { - if (include (a, lt, p) != include_type::normal) // Excluded/ad hoc. - continue; - - // @@ TODO: will probably need revision if using sidebuild for - // non-installed libraries (e.g., direct BMI dependencies - // will probably have to be translated to mxx{} or some such). - // Hm, don't think we want it this way: we want BMIs of binless - // library to be built in the library rather than on the side - // (so they can be potentially re-used by multiple independent - // importers). - // - if (p.is_a<libx> () || - p.is_a<liba> () || p.is_a<libs> () || p.is_a<libux> ()) + ps.push_back (prerequisite (*lt)); + for (prerequisite_member p: group_prerequisite_members (a, *lt)) { - ps.push_back (p.as_prerequisite ()); + // Ignore update=match. + // + lookup l; + if (include (a, *lt, p, &l) != include_type::normal) // Excluded/ad hoc. + continue; + + if (p.is_a<libx> () || + p.is_a<liba> () || p.is_a<libs> () || p.is_a<libux> ()) + { + ps.push_back (p.as_prerequisite ()); + } } } @@ -6045,31 +6790,33 @@ namespace build2 move (mf), nullopt, // Use default extension. target_decl::implied, - trace)); - file& bt (static_cast<file&> (p.first)); + trace, + true /* skip_find */)); // Note that this is racy and someone might have created this target // while we were preparing the prerequisite list. // if (p.second) { - bt.prerequisites (move (ps)); + p.first.prerequisites (move (ps)); // Unless this is a binless library, we don't need the object file // (see config_data::b_binless for details). // - bt.vars.assign (b_binless) = (lt.mtime () == timestamp_unreal); + p.first.vars.assign (b_binless) = (lt == nullptr || + lt->mtime () == timestamp_unreal); } - return bt; + return p; } // Synthesize a dependency for building a header unit binary interface on // the side. // const file& compile_rule:: - make_header_sidebuild (action, + make_header_sidebuild (action a, const scope& bs, + const file& t, linfo li, const file& ht) const { @@ -6077,7 +6824,116 @@ namespace build2 // Note: similar to make_module_sidebuild() above. - dir_path pd (find_modules_sidebuild (*bs.root_scope ())); + auto sb (find_modules_sidebuild (*bs.root_scope ())); + dir_path pd (move (sb.first)); + const scope& as (sb.second); + + // Determine if this header belongs to one of the libraries we depend + // on. + // + // Note that because libraries are not in prerequisite_targets, we have + // to go through prerequisites, similar to append_library_options(). + // + const target* lt (nullptr); // Can be lib{}. + { + // Note that any such library would necessarily be an interface + // dependency so we never need to go into implementations. + // + auto imp = [] (const target&, bool) { return false; }; + + // The same logic as in append_libraries(). + // + appended_libraries ls; + struct data + { + action a; + const file& ht; + const target*& lt; + appended_libraries& ls; + } d {a, ht, lt, ls}; + + auto lib = [&d] ( + const target* const* lc, + const small_vector<reference_wrapper<const string>, 2>&, + lflags, + const string*, + bool) + { + // Prune any further traversal if we already found it. + // + if (d.lt != nullptr) + return false; + + const target* l (lc != nullptr ? *lc : nullptr); // Can be lib{}. + + if (l == nullptr) + return true; + + // Suppress duplicates. + // + if (find (d.ls.begin (), d.ls.end (), l) != d.ls.end ()) + return false; + + // Feels like we should only consider non-utility libraries with + // utilities being treated as "direct" use. + // + if (l->is_a<libux> ()) + return true; + + // Since the library is searched and matched, all the headers should + // be in prerequisite_targets. + // + const auto& pts (l->prerequisite_targets[d.a]); + if (find (pts.begin (), pts.end (), &d.ht) != pts.end ()) + { + d.lt = l; + return false; + } + + d.ls.push_back (l); + return true; + }; + + library_cache lib_cache; + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + if (include (a, t, p) != include_type::normal) // Excluded/ad hoc. + continue; + + // Should be already searched and matched for libraries. + // + if (const target* pt = p.load ()) + { + if (const libx* l = pt->is_a<libx> ()) + pt = link_member (*l, a, li); + + bool la; + const file* f; + if ((la = (f = pt->is_a<liba> ())) || + (la = (f = pt->is_a<libux> ())) || + ( (f = pt->is_a<libs> ()))) + { + // Note that we are requesting process_libraries() to not pick + // the liba/libs{} member of the installed libraries and return + // the lib{} group itself instead. This is because, for the + // installed case, the library prerequisites (both headers and + // interface dependency libraries) are matched by file_rule + // which won't pick the liba/libs{} member (naturally) but will + // just match the lib{} group. + // + process_libraries (a, bs, nullopt, sys_lib_dirs, + *f, la, 0, // lflags unused. + imp, lib, nullptr, + true /* self */, + false /* proc_opt_group */, + &lib_cache); + + if (lt != nullptr) + break; + } + } + } + } // What should we use as a file/target name? On one hand we want it // unique enough so that <stdio.h> and <custom/stdio.h> don't end up @@ -6102,7 +6958,14 @@ namespace build2 mf += sha256 (hp.string ()).abbreviated_string (12); } - const target_type& tt (compile_types (li.type).hbmi); + // If the header comes from the library, use its hbmi?{} type to + // maximize reuse. + // + const target_type& tt ( + compile_types ( + lt != nullptr && !lt->is_a<lib> () + ? link_type (*lt).type + : li.type).hbmi); if (const file* bt = bs.ctx.targets.find<file> ( tt, @@ -6116,6 +6979,51 @@ namespace build2 prerequisites ps; ps.push_back (prerequisite (ht)); + // Similar story as for modules: the header may need poptions from its + // library (e.g., -I to find other headers that it includes). + // + if (lt != nullptr) + ps.push_back (prerequisite (*lt)); + else + { + // If the header does not belong to a library then this is a "direct" + // use, for example, by an exe{} target. In this case we need to add + // all the prerequisite libraries as well as scope p/coptions (in a + // sense, we are trying to approximate how all the sources that would + // typically include such a header are build). + // + // Note that this is also the case when we build the library's own + // sources (in a way it would have been cleaner to always build + // library's headers with only its "interface" options/prerequisites + // but that won't be easy to achieve). + // + // Note also that at first it might seem like a good idea to + // incorporate this information into the hash we use to form the BMI + // name. But that would reduce sharing of the BMI. For example, that + // would mean we will build the library header twice, once with the + // implementation options/prerequisites and once -- with interface. + // On the other hand, importable headers are expected to be "modular" + // and should probably not depend on any of the implementation + // options/prerequisites (though one could conceivably build a + // "richer" BMI if it is also to be used to build the library + // implementation -- interesting idea). + // + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + // Ignore update=match. + // + lookup l; + if (include (a, t, p, &l) != include_type::normal) // Excluded/ad hoc. + continue; + + if (p.is_a<libx> () || + p.is_a<liba> () || p.is_a<libs> () || p.is_a<libux> ()) + { + ps.push_back (p.as_prerequisite ()); + } + } + } + auto p (bs.ctx.targets.insert_locked ( tt, move (pd), @@ -6123,22 +7031,47 @@ namespace build2 move (mf), nullopt, // Use default extension. target_decl::implied, - trace)); - file& bt (static_cast<file&> (p.first)); + trace, + true /* skip_find */)); + file& bt (p.first.as<file> ()); // Note that this is racy and someone might have created this target // while we were preparing the prerequisite list. // if (p.second) + { bt.prerequisites (move (ps)); + // Add the p/coptions from our scope in case of a "direct" use. Take + // into account hbmi{} target-type/pattern values to allow specifying + // hbmi-specific options. + // + if (lt == nullptr) + { + auto set = [&bs, &as, &tt, &bt] (const variable& var) + { + // Avoid duplicating the options if they are from the same + // amalgamation as the sidebuild. + // + lookup l (bs.lookup (var, tt, bt.name, hbmi::static_type, bt.name)); + if (l.defined () && !l.belongs (as)) + bt.assign (var) = *l; + }; + + set (c_poptions); + set (x_poptions); + set (c_coptions); + set (x_coptions); + } + } + return bt; } // Filter cl.exe noise (msvc.cxx). // void - msvc_filter_cl (ifdstream&, const path& src); + msvc_filter_cl (diag_buffer&, const path& src); // Append header unit-related options. // @@ -6189,7 +7122,7 @@ namespace build2 // options). // void compile_rule:: - append_module_options (environment& env, + append_module_options (environment&, cstrings& args, small_vector<string, 2>& stor, action a, @@ -6200,8 +7133,6 @@ namespace build2 unit_type ut (md.type); const module_positions& ms (md.modules); - dir_path stdifc; // See the VC case below. - switch (ctype) { case compiler_type::gcc: @@ -6230,15 +7161,12 @@ namespace build2 if (ms.start == 0) return; - // Clang embeds module file references so we only need to specify - // our direct imports. - // - // If/when we get the ability to specify the mapping in a file, we - // will pass the whole list. + // If/when we get the ability to specify the mapping in a file. // #if 0 // In Clang the module implementation's unit .pcm is special and - // must be "loaded". + // must be "loaded". Note: not anymore, not from Clang 16 and is + // deprecated in 17. // if (ut == unit_type::module_impl) { @@ -6255,10 +7183,7 @@ namespace build2 stor.push_back (move (s)); #else auto& pts (t.prerequisite_targets[a]); - for (size_t i (ms.start), - n (ms.copied != 0 ? ms.copied : pts.size ()); - i != n; - ++i) + for (size_t i (ms.start), n (pts.size ()); i != n; ++i) { const target* pt (pts[i]); @@ -6271,17 +7196,9 @@ namespace build2 const file& f (pt->as<file> ()); string s (relative (f.path ()).string ()); - // In Clang the module implementation's unit .pcm is special and - // must be "loaded". - // - if (ut == unit_type::module_impl && i == ms.start) - s.insert (0, "-fmodule-file="); - else - { - s.insert (0, 1, '='); - s.insert (0, cast<string> (f.state[a].vars[c_module_name])); - s.insert (0, "-fmodule-file="); - } + s.insert (0, 1, '='); + s.insert (0, cast<string> (f.state[a].vars[c_module_name])); + s.insert (0, "-fmodule-file="); stor.push_back (move (s)); } @@ -6293,10 +7210,11 @@ namespace build2 if (ms.start == 0) return; + // MSVC requires a transitive set of interfaces, including + // implementation partitions. + // auto& pts (t.prerequisite_targets[a]); - for (size_t i (ms.start), n (pts.size ()); - i != n; - ++i) + for (size_t i (ms.start), n (pts.size ()); i != n; ++i) { const target* pt (pts[i]); @@ -6307,34 +7225,14 @@ namespace build2 // of these are bmi's. // const file& f (pt->as<file> ()); + string s (relative (f.path ()).string ()); - // In VC std.* modules can only come from a single directory - // specified with the IFCPATH environment variable or the - // /module:stdIfcDir option. - // - if (std_module (cast<string> (f.state[a].vars[c_module_name]))) - { - dir_path d (f.path ().directory ()); + s.insert (0, 1, '='); + s.insert (0, cast<string> (f.state[a].vars[c_module_name])); - if (stdifc.empty ()) - { - // Go one directory up since /module:stdIfcDir will look in - // either Release or Debug subdirectories. Keeping the result - // absolute feels right. - // - stor.push_back ("/module:stdIfcDir"); - stor.push_back (d.directory ().string ()); - stdifc = move (d); - } - else if (d != stdifc) // Absolute and normalized. - fail << "multiple std.* modules in different directories"; - } - else - { - stor.push_back ("/module:reference"); - stor.push_back (relative (f.path ()).string ()); - } + stor.push_back (move (s)); } + break; } case compiler_type::icc: @@ -6345,35 +7243,20 @@ namespace build2 // into storage? Because of potential reallocations. // for (const string& a: stor) - args.push_back (a.c_str ()); - - if (getenv ("IFCPATH")) - { - // VC's IFCPATH takes precedence over /module:stdIfcDir so unset it if - // we are using our own std modules. - // - if (!stdifc.empty ()) - env.push_back ("IFCPATH"); - } - else if (stdifc.empty ()) { - // Add the VC's default directory (should be only one). - // - if (sys_mod_dirs != nullptr && !sys_mod_dirs->empty ()) - { - args.push_back ("/module:stdIfcDir"); - args.push_back (sys_mod_dirs->front ().string ().c_str ()); - } + if (ctype == compiler_type::msvc) + args.push_back ("/reference"); + + args.push_back (a.c_str ()); } } target_state compile_rule:: - perform_update (action a, const target& xt) const + perform_update (action a, const target& xt, match_data& md) const { const file& t (xt.as<file> ()); const path& tp (t.path ()); - match_data md (move (t.data<match_data> ())); unit_type ut (md.type); context& ctx (t.ctx); @@ -6396,9 +7279,6 @@ namespace build2 }, md.modules.copied)); // See search_modules() for details. - const file& s (pr.second); - const path* sp (&s.path ()); - // Force recompilation in case of a deferred failure even if nothing // changed. // @@ -6415,11 +7295,14 @@ namespace build2 return *pr.first; } + const file& s (pr.second); + const path* sp (&s.path ()); + // Make sure depdb is no older than any of our prerequisites (see md.mt // logic description above for details). Also save the sequence start // time if doing mtime checks (see the depdb::check_mtime() call below). // - timestamp start (depdb::mtime_check () + timestamp start (!ctx.dry_run && depdb::mtime_check () ? system_clock::now () : timestamp_unknown); @@ -6437,7 +7320,8 @@ namespace build2 // If we are building a module interface or partition, then the target // is bmi*{} and it may have an ad hoc obj*{} member. For header units // there is no obj*{} (see the corresponding add_adhoc_member() call in - // apply()). + // apply()). For named modules there may be no obj*{} if this is a + // sidebuild (obj*{} is already in the library binary). // path relm; path relo; @@ -6485,9 +7369,6 @@ namespace build2 small_vector<string, 2> header_args; // Header unit options storage. small_vector<string, 2> module_args; // Module options storage. - size_t out_i (0); // Index of the -o option. - size_t lang_n (0); // Number of lang options. - switch (cclass) { case compiler_class::msvc: @@ -6505,7 +7386,44 @@ namespace build2 append_options (args, cmode); if (md.pp != preprocessed::all) - append_sys_inc_options (args); // Extra system header dirs (last). + append_sys_hdr_options (args); // Extra system header dirs (last). + + // Note: could be overridden in mode. + // + append_diag_color_options (args); + + // Set source/execution charsets to UTF-8 unless a custom charset + // is specified. + // + // Note that clang-cl supports /utf-8 and /*-charset. + // + { + bool sc (find_option_prefixes ( + {"/source-charset:", "-source-charset:"}, args)); + bool ec (find_option_prefixes ( + {"/execution-charset:", "-execution-charset:"}, args)); + + if (!sc && !ec) + args.push_back ("/utf-8"); + else + { + if (!sc) + args.push_back ("/source-charset:UTF-8"); + + if (!ec) + args.push_back ("/execution-charset:UTF-8"); + } + } + + // If we have any /external:I options but no /external:Wn, then add + // /external:W0 to emulate the -isystem semantics. + // + if (cvariant != "clang" && isystem (*this)) + { + if (find_option_prefixes ({"/external:I", "-external:I"}, args) && + !find_option_prefixes ({"/external:W", "-external:W"}, args)) + args.push_back ("/external:W0"); + } // While we want to keep the low-level build as "pure" as possible, // the two misguided defaults, C++ exceptions and runtime, just have @@ -6517,7 +7435,9 @@ namespace build2 // For C looks like no /EH* (exceptions supported but no C++ objects // destroyed) is a reasonable default. // - if (x_lang == lang::cxx && !find_option_prefix ("/EH", args)) + + if (x_lang == lang::cxx && + !find_option_prefixes ({"/EH", "-EH"}, args)) args.push_back ("/EHsc"); // The runtime is a bit more interesting. At first it may seem like @@ -6539,7 +7459,7 @@ namespace build2 // unreasonable thing to do). So by default we will always use the // release runtime. // - if (!find_option_prefixes ({"/MD", "/MT"}, args)) + if (!find_option_prefixes ({"/MD", "/MT", "-MD", "-MT"}, args)) args.push_back ("/MD"); msvc_sanitize_cl (args); @@ -6562,9 +7482,8 @@ namespace build2 // Note also that what we are doing here appears to be incompatible // with PCH (/Y* options) and /Gm (minimal rebuild). // - // @@ MOD: TODO deal with absent relo. - // - if (find_options ({"/Zi", "/ZI"}, args)) + if (!relo.empty () && + find_options ({"/Zi", "/ZI", "-Zi", "-ZI"}, args)) { if (fc) args.push_back ("/Fd:"); @@ -6577,27 +7496,38 @@ namespace build2 args.push_back (out1.c_str ()); } - if (fc) - { - args.push_back ("/Fo:"); - args.push_back (relo.string ().c_str ()); - } - else + if (ut == unit_type::module_intf || + ut == unit_type::module_intf_part || + ut == unit_type::module_impl_part || + ut == unit_type::module_header) { - out = "/Fo" + relo.string (); - args.push_back (out.c_str ()); - } + assert (ut != unit_type::module_header); // @@ MODHDR - // @@ MODHDR MSVC - // @@ MODPART MSVC - // - if (ut == unit_type::module_intf) - { relm = relative (tp); - args.push_back ("/module:interface"); - args.push_back ("/module:output"); + args.push_back ("/ifcOutput"); args.push_back (relm.string ().c_str ()); + + if (relo.empty ()) + args.push_back ("/ifcOnly"); + else + { + args.push_back ("/Fo:"); + args.push_back (relo.string ().c_str ()); + } + } + else + { + if (fc) + { + args.push_back ("/Fo:"); + args.push_back (relo.string ().c_str ()); + } + else + { + out = "/Fo" + relo.string (); + args.push_back (out.c_str ()); + } } // Note: no way to indicate that the source if already preprocessed. @@ -6610,6 +7540,65 @@ namespace build2 } case compiler_class::gcc: { + append_options (args, cmode); + + // Clang 15 introduced the unqualified-std-cast-call warning which + // warns about unqualified calls to std::move() and std::forward() + // (because they can be "hijacked" via ADL). Surprisingly, this + // warning is enabled by default, as opposed to with -Wextra or at + // least -Wall. It has also proven to be quite disruptive, causing a + // large number of warnings in a large number of packages. So we are + // going to "remap" it to -Wextra for now and in the future may + // "relax" it to -Wall and potentially to being enabled by default. + // See GitHub issue #259 for background and details. + // + if (x_lang == lang::cxx && + ctype == compiler_type::clang && + cmaj >= 15) + { + bool w (false); // Seen -W[no-]unqualified-std-cast-call + optional<bool> extra; // Seen -W[no-]extra + + for (const char* s: reverse_iterate (args)) + { + if (s != nullptr) + { + if (strcmp (s, "-Wunqualified-std-cast-call") == 0 || + strcmp (s, "-Wno-unqualified-std-cast-call") == 0) + { + w = true; + break; + } + + if (!extra) // Last seen option wins. + { + if (strcmp (s, "-Wextra") == 0) extra = true; + else if (strcmp (s, "-Wno-extra") == 0) extra = false; + } + } + } + + if (!w && (!extra || !*extra)) + args.push_back ("-Wno-unqualified-std-cast-call"); + } + + if (md.pp != preprocessed::all) + append_sys_hdr_options (args); // Extra system header dirs (last). + + // Note: could be overridden in mode. + // + append_diag_color_options (args); + + // Set the input charset to UTF-8 unless a custom one is specified. + // + // Note that the execution charset (-fexec-charset) is UTF-8 by + // default. + // + // Note that early versions of Clang only recognize uppercase UTF-8. + // + if (!find_option_prefix ("-finput-charset=", args)) + args.push_back ("-finput-charset=UTF-8"); + if (ot == otype::s) { // On Darwin, Win32 -fPIC is the default. @@ -6653,8 +7642,7 @@ namespace build2 // either -nostdlib or -nostartfiles is specified. Let's do // the same. // - initializer_list<const char*> os {"-nostdlib", "-nostartfiles"}; - if (!find_options (os, cmode) && !find_options (os, args)) + if (!find_options ({"-nostdlib", "-nostartfiles"}, args)) { args.push_back ("-D_MT"); args.push_back ("-D_DLL"); @@ -6713,18 +7701,9 @@ namespace build2 } } - append_options (args, cmode); - - if (md.pp != preprocessed::all) - append_sys_inc_options (args); // Extra system header dirs (last). - append_header_options (env, args, header_args, a, t, md, md.dd); append_module_options (env, args, module_args, a, t, md, md.dd); - // Note: the order of the following options is relied upon below. - // - out_i = args.size (); // Index of the -o option. - if (ut == unit_type::module_intf || ut == unit_type::module_intf_part || ut == unit_type::module_impl_part || @@ -6763,21 +7742,35 @@ namespace build2 } case compiler_type::clang: { - // @@ MOD TODO: deal with absent relo. + assert (ut != unit_type::module_header); // @@ MODHDR relm = relative (tp); - args.push_back ("-o"); - args.push_back (relm.string ().c_str ()); - args.push_back ("--precompile"); - // Without this option Clang's .pcm will reference source - // files. In our case this file may be transient (.ii). Plus, + // files. In our case this file may be transient (.ii). Plus, // it won't play nice with distributed compilation. // + // Note that this sort of appears to be the default from Clang + // 17, but not quite, see llvm-project issued #72383. + // args.push_back ("-Xclang"); args.push_back ("-fmodules-embed-all-files"); + if (relo.empty ()) + { + args.push_back ("-o"); + args.push_back (relm.string ().c_str ()); + args.push_back ("--precompile"); + } + else + { + out1 = "-fmodule-output=" + relm.string (); + args.push_back (out1.c_str ()); + args.push_back ("-o"); + args.push_back (relo.string ().c_str ()); + args.push_back ("-c"); + } + break; } case compiler_type::msvc: @@ -6792,7 +7785,7 @@ namespace build2 args.push_back ("-c"); } - lang_n = append_lang_options (args, md); + append_lang_options (args, md); if (md.pp == preprocessed::all) { @@ -6837,25 +7830,46 @@ namespace build2 if (!env.empty ()) env.push_back (nullptr); + // We have no choice but to serialize early if we want the command line + // printed shortly before actually executing the compiler. Failed that, + // it may look like we are still executing in parallel. + // + scheduler::alloc_guard jobs_ag; + if (!ctx.dry_run && cast_false<bool> (t[c_serialize])) + jobs_ag = scheduler::alloc_guard (*ctx.sched, phase_unlock (nullptr)); + // With verbosity level 2 print the command line as if we are compiling // the source file, not its preprocessed version (so that it's easy to // copy and re-run, etc). Only at level 3 and above print the real deal. // + // @@ TODO: why don't we print env (here and/or below)? Also link rule. + // if (verb == 1) - text << x_name << ' ' << s; + { + const char* name (x_assembler_cpp (s) ? "as-cpp" : + x_objective (s) ? x_obj_name : + x_name); + + print_diag (name, s, t); + } else if (verb == 2) print_process (args); // If we have the (partially) preprocessed output, switch to that. // - bool psrc (!md.psrc.path.empty ()); - bool pact (md.psrc.active); + // But we remember the original source/position to restore later. + // + bool psrc (md.psrc); // Note: false if cc.reprocess. + bool ptmp (psrc && md.psrc.temporary); + pair<size_t, const char*> osrc; if (psrc) { args.pop_back (); // nullptr + osrc.second = args.back (); args.pop_back (); // sp + osrc.first = args.size (); - sp = &md.psrc.path; + sp = &md.psrc.path (); // This should match with how we setup preprocessing. // @@ -6863,25 +7877,40 @@ namespace build2 { case compiler_type::gcc: { - // The -fpreprocessed is implied by .i/.ii. But not when compiling - // a header unit (there is no .hi/.hii). + // -fpreprocessed is implied by .i/.ii unless compiling a header + // unit (there is no .hi/.hii). Also, we would need to pop -x + // since it takes precedence over the extension, which would mess + // up our osrc logic. So in the end it feels like always passing + // explicit -fpreprocessed is the way to go. // - if (ut == unit_type::module_header) - args.push_back ("-fpreprocessed"); - else - // Pop -x since it takes precedence over the extension. - // - // @@ I wonder why bother and not just add -fpreprocessed? Are - // we trying to save an option or does something break? - // - for (; lang_n != 0; --lang_n) - args.pop_back (); - + // Also note that similarly there is no .Si for .S files. + // + args.push_back ("-fpreprocessed"); args.push_back ("-fdirectives-only"); break; } case compiler_type::clang: { + // Clang 15 and later with -pedantic warns about GNU-style line + // markers that it wrote itself in the -frewrite-includes output + // (llvm-project issue 63284). So we suppress this warning unless + // compiling from source. + // + // In Apple Clang this warning/option are absent in 14.0.3 (which + // is said to be based on vanilla Clang 15.0.5) for some reason + // (let's hope it's because they patched it out rather than due to + // a misleading _LIBCPP_VERSION value). + // + if (ctype == compiler_type::clang && + cmaj >= (cvariant != "apple" ? 15 : 16)) + { + if (find_options ({"-pedantic", "-pedantic-errors", + "-Wpedantic", "-Werror=pedantic"}, args)) + { + args.push_back ("-Wno-gnu-line-marker"); + } + } + // Note that without -x Clang will treat .i/.ii as fully // preprocessed. // @@ -6902,10 +7931,11 @@ namespace build2 // Let's keep the preprocessed file in case of an error but only at // verbosity level 3 and up (when one actually sees it mentioned on - // the command line). We also have to re-arm on success (see below). + // the command line). We also have to re-enable on success (see + // below). // - if (pact && verb >= 3) - md.psrc.active = false; + if (ptmp && verb >= 3) + md.psrc.temporary = false; } if (verb >= 3) @@ -6924,46 +7954,43 @@ namespace build2 { try { + // If we are compiling the preprocessed output, get its read handle. + // + file_cache::read psrcr (psrc ? md.psrc.open () : file_cache::read ()); + // VC cl.exe sends diagnostics to stdout. It also prints the file - // name being compiled as the first line. So for cl.exe we redirect - // stdout to a pipe, filter that noise out, and send the rest to - // stderr. + // name being compiled as the first line. So for cl.exe we filter + // that noise out. // - // For other compilers redirect stdout to stderr, in case any of - // them tries to pull off something similar. For sane compilers this - // should be harmless. + // For other compilers also redirect stdout to stderr, in case any + // of them tries to pull off something similar. For sane compilers + // this should be harmless. // bool filter (ctype == compiler_type::msvc); process pr (cpath, - args.data (), - 0, (filter ? -1 : 2), 2, + args, + 0, 2, diag_buffer::pipe (ctx, filter /* force */), nullptr, // CWD env.empty () ? nullptr : env.data ()); - if (filter) - { - try - { - ifdstream is ( - move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit); + diag_buffer dbuf (ctx, args[0], pr); - msvc_filter_cl (is, *sp); + if (filter) + msvc_filter_cl (dbuf, *sp); - // If anything remains in the stream, send it all to stderr. - // Note that the eof check is important: if the stream is at - // eof, this and all subsequent writes to the diagnostics stream - // will fail (and you won't see a thing). - // - if (is.peek () != ifdstream::traits_type::eof ()) - diag_stream_lock () << is.rdbuf (); + dbuf.read (); - is.close (); - } - catch (const io_error&) {} // Assume exits with error. + // Restore the original source if we switched to preprocessed. + // + if (psrc) + { + args.resize (osrc.first); + args.push_back (osrc.second); + args.push_back (nullptr); } - run_finish (args, pr); + run_finish (dbuf, args, pr, 1 /* verbosity */); } catch (const process_error& e) { @@ -6975,65 +8002,16 @@ namespace build2 throw failed (); } + jobs_ag.deallocate (); + if (md.deferred_failure) fail << "expected error exit status from " << x_lang << " compiler"; } // Remove preprocessed file (see above). // - if (pact && verb >= 3) - md.psrc.active = true; - - // Clang's module compilation requires two separate compiler - // invocations. - // - // @@ MODPART: Clang (all of this is probably outdated). - // - if (ctype == compiler_type::clang && ut == unit_type::module_intf) - { - // Adjust the command line. First discard everything after -o then - // build the new "tail". - // - args.resize (out_i + 1); - args.push_back (relo.string ().c_str ()); // Produce .o. - args.push_back ("-c"); // By compiling .pcm. - args.push_back ("-Wno-unused-command-line-argument"); - args.push_back (relm.string ().c_str ()); - args.push_back (nullptr); - - if (verb >= 2) - print_process (args); - - if (!ctx.dry_run) - { - // Remove the target file if this fails. If we don't do that, we - // will end up with a broken build that is up-to-date. - // - auto_rmfile rm (relm); - - try - { - process pr (cpath, - args.data (), - 0, 2, 2, - nullptr, // CWD - env.empty () ? nullptr : env.data ()); - - run_finish (args, pr); - } - catch (const process_error& e) - { - error << "unable to execute " << args[0] << ": " << e; - - if (e.child) - exit (1); - - throw failed (); - } - - rm.cancel (); - } - } + if (ptmp && verb >= 3) + md.psrc.temporary = true; timestamp now (system_clock::now ()); @@ -7050,18 +8028,27 @@ namespace build2 } target_state compile_rule:: - perform_clean (action a, const target& xt) const + perform_clean (action a, const target& xt, const target_type& srct) const { const file& t (xt.as<file> ()); - clean_extras extras; + // Preprocessed file extension. + // + const char* pext (x_assembler_cpp (srct) ? ".Si" : + x_objective (srct) ? x_obj_pext : + x_pext); + // Compressed preprocessed file extension. + // + string cpext (t.ctx.fcache->compressed_extension (pext)); + + clean_extras extras; switch (ctype) { - case compiler_type::gcc: extras = {".d", x_pext, ".t"}; break; - case compiler_type::clang: extras = {".d", x_pext}; break; - case compiler_type::msvc: extras = {".d", x_pext, ".idb", ".pdb"};break; - case compiler_type::icc: extras = {".d"}; break; + case compiler_type::gcc: extras = {".d", pext, cpext.c_str (), ".t"}; break; + case compiler_type::clang: extras = {".d", pext, cpext.c_str ()}; break; + case compiler_type::msvc: extras = {".d", pext, cpext.c_str (), ".idb", ".pdb"}; break; + case compiler_type::icc: extras = {".d"}; break; } return perform_clean_extra (a, t, extras); diff --git a/libbuild2/cc/compile-rule.hxx b/libbuild2/cc/compile-rule.hxx index a5eb8e8..0886b4b 100644 --- a/libbuild2/cc/compile-rule.hxx +++ b/libbuild2/cc/compile-rule.hxx @@ -8,7 +8,8 @@ #include <libbuild2/utility.hxx> #include <libbuild2/rule.hxx> -#include <libbuild2/filesystem.hxx> // auto_rmfile +#include <libbuild2/dyndep.hxx> +#include <libbuild2/file-cache.hxx> #include <libbuild2/cc/types.hxx> #include <libbuild2/cc/common.hxx> @@ -21,6 +22,8 @@ namespace build2 namespace cc { + class config_module; + // The order is arranged so that their integral values indicate whether // one is a "stronger" than another. // @@ -36,47 +39,57 @@ namespace build2 size_t copied; // First copied-over bmi*{}, 0 if none. }; - class LIBBUILD2_CC_SYMEXPORT compile_rule: public simple_rule, virtual common + class LIBBUILD2_CC_SYMEXPORT compile_rule: public simple_rule, + virtual common, + dyndep_rule { public: - compile_rule (data&&); + struct match_data; + + compile_rule (data&&, const scope&); virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; target_state - perform_update (action, const target&) const; + perform_update (action, const target&, match_data&) const; target_state - perform_clean (action, const target&) const; + perform_clean (action, const target&, const target_type&) const; public: - using appended_libraries = small_vector<const file*, 256>; + using appended_libraries = small_vector<const target*, 256>; void append_library_options (appended_libraries&, strings&, const scope&, - action, const file&, bool, linfo) const; + action, const file&, bool, linfo, + bool, bool) const; + + optional<path> + find_system_header (const path&) const; + protected: static void functions (function_family&, const char*); // functions.cxx private: - struct match_data; using environment = small_vector<const char*, 2>; template <typename T> void - append_sys_inc_options (T&) const; + append_sys_hdr_options (T&) const; template <typename T> void append_library_options (appended_libraries&, T&, const scope&, - action, const file&, bool, linfo) const; + const scope*, + action, const file&, bool, linfo, bool, + library_cache*) const; template <typename T> void @@ -84,70 +97,48 @@ namespace build2 const scope&, action, const target&, linfo) const; - // Mapping of include prefixes (e.g., foo in <foo/bar>) for auto- - // generated headers to directories where they will be generated. - // - // We are using a prefix map of directories (dir_path_map) instead of - // just a map in order to also cover sub-paths (e.g., <foo/more/bar> if - // we continue with the example). Specifically, we need to make sure we - // don't treat foobar as a sub-directory of foo. - // - // The priority is used to decide who should override whom. Lesser - // values are considered higher priority. See append_prefixes() for - // details. - // - // @@ The keys should be normalized. - // - struct prefix_value - { - dir_path directory; - size_t priority; - }; - using prefix_map = dir_path_map<prefix_value>; + using prefix_map = dyndep_rule::prefix_map; + using srcout_map = dyndep_rule::srcout_map; void - append_prefixes (prefix_map&, const target&, const variable&) const; + append_prefixes (prefix_map&, + const scope&, const target&, + const variable&) const; void - append_library_prefixes (prefix_map&, + append_library_prefixes (appended_libraries&, prefix_map&, const scope&, - action, target&, linfo) const; + action, const target&, linfo) const; prefix_map - build_prefix_map (const scope&, action, target&, linfo) const; - - small_vector<const target_type*, 2> - map_extension (const scope&, const string&, const string&) const; - - // Src-to-out re-mapping. See extract_headers() for details. - // - using srcout_map = path_map<dir_path>; + build_prefix_map (const scope&, action, const target&, linfo) const; - struct module_mapper_state; + struct gcc_module_mapper_state; - bool - gcc_module_mapper (module_mapper_state&, + optional<bool> + gcc_module_mapper (gcc_module_mapper_state&, action, const scope&, file&, linfo, - ifdstream&, ofdstream&, + const string&, ofdstream&, depdb&, bool&, bool&, optional<prefix_map>&, srcout_map&) const; pair<const file*, bool> enter_header (action, const scope&, file&, linfo, path&&, bool, bool, - optional<prefix_map>&, srcout_map&) const; + optional<prefix_map>&, const srcout_map&) const; optional<bool> inject_header (action, file&, const file&, timestamp, bool) const; - pair<auto_rmfile, bool> + void extract_headers (action, const scope&, file&, linfo, const file&, match_data&, - depdb&, bool&, timestamp, module_imports&) const; + depdb&, bool&, timestamp, module_imports&, + pair<file_cache::entry, bool>&) const; string parse_unit (action, file&, linfo, - const file&, auto_rmfile&, + const file&, file_cache::entry&, const match_data&, const path&, unit&) const; @@ -162,15 +153,17 @@ namespace build2 const target_type&, const file&, module_imports&, sha256&) const; - dir_path + pair<dir_path, const scope&> find_modules_sidebuild (const scope&) const; - const file& - make_module_sidebuild (action, const scope&, const file&, + pair<target&, ulock> + make_module_sidebuild (action, const scope&, + const file*, otype, const target&, const string&) const; const file& - make_header_sidebuild (action, const scope&, linfo, const file&) const; + make_header_sidebuild (action, const scope&, const file&, + linfo, const file&) const; void append_header_options (environment&, cstrings&, small_vector<string, 2>&, @@ -193,6 +186,7 @@ namespace build2 private: const string rule_id; + const config_module* header_cache_; }; } } diff --git a/libbuild2/cc/functions.cxx b/libbuild2/cc/functions.cxx index 760a332..9d408af 100644 --- a/libbuild2/cc/functions.cxx +++ b/libbuild2/cc/functions.cxx @@ -13,11 +13,10 @@ #include <libbuild2/cc/module.hxx> #include <libbuild2/cc/utility.hxx> +#include <libbuild2/functions-name.hxx> // to_target() + namespace build2 { - const target& - to_target (const scope&, name&&, name&&); // libbuild2/functions-name.cxx - namespace cc { using namespace bin; @@ -47,16 +46,24 @@ namespace build2 if (rs == nullptr) fail << f.name << " called out of project"; - if (bs->ctx.phase != run_phase::execute) - fail << f.name << " can only be called during execution"; + // Note that we also allow calling this during match since an ad hoc + // recipe with dynamic dependency extraction (depdb-dyndep) executes its + // depdb preamble during match (after matching all the prerequisites). + // + if (bs->ctx.phase != run_phase::match && + bs->ctx.phase != run_phase::execute) + fail << f.name << " can only be called from recipe"; const module* m (rs->find_module<module> (d.x)); if (m == nullptr) - fail << f.name << " called without " << d.x << " module being loaded"; + fail << f.name << " called without " << d.x << " module loaded"; // We can assume these are present due to function's types signature. // + if (vs[0].null) + throw invalid_argument ("null value"); + names& ts_ns (vs[0].as<names> ()); // <targets> // In a somewhat hackish way strip the outer operation to match how we @@ -70,20 +77,40 @@ namespace build2 { name& n (*i), o; const target& t (to_target (*bs, move (n), move (n.pair ? *++i : o))); + + if (!t.matched (a)) + fail << t << " is not matched" << + info << "make sure this target is listed as prerequisite"; + d.f (r, vs, *m, *bs, a, t); } return value (move (r)); } - // Common thunk for $x.lib_*(<targets>, <otype> [, ...]) functions. + // Common thunk for $x.lib_*(...) functions. + // + // The two supported function signatures are: + // + // $x.lib_*(<targets>, <otype> [, ...]]) + // + // $x.lib_*(<targets>) + // + // For the first signature, the passed targets cannot be library groups + // (so they are always file-based) and linfo is always present. + // + // For the second signature, targets can only be utility libraries + // (including the libul{} group). + // + // If <otype> in the first signature is NULL, then it is treated as + // the second signature. // struct lib_thunk_data { const char* x; void (*f) (void*, strings&, const vector_view<value>&, const module&, const scope&, - action, const file&, bool, linfo); + action, const target&, bool, optional<linfo>); }; static value @@ -102,21 +129,27 @@ namespace build2 if (rs == nullptr) fail << f.name << " called out of project"; - if (bs->ctx.phase != run_phase::execute) - fail << f.name << " can only be called during execution"; + if (bs->ctx.phase != run_phase::match && // See above. + bs->ctx.phase != run_phase::execute) + fail << f.name << " can only be called from recipe"; const module* m (rs->find_module<module> (d.x)); if (m == nullptr) - fail << f.name << " called without " << d.x << " module being loaded"; + fail << f.name << " called without " << d.x << " module loaded"; - // We can assume these are present due to function's types signature. + // We can assume this is present due to function's types signature. // + if (vs[0].null) + throw invalid_argument ("null value"); + names& ts_ns (vs[0].as<names> ()); // <targets> - names& ot_ns (vs[1].as<names> ()); // <otype> - linfo li; + optional<linfo> li; + if (vs.size () > 1 && !vs[1].null) { + names& ot_ns (vs[1].as<names> ()); // <otype> + string t (convert<string> (move (ot_ns))); const target_type* tt (bs->find_target_type (t)); @@ -162,17 +195,22 @@ namespace build2 name& n (*i), o; const target& t (to_target (*bs, move (n), move (n.pair ? *++i : o))); - const file* f; bool la (false); - - if ((la = (f = t.is_a<libux> ())) || - (la = (f = t.is_a<liba> ())) || - ( (f = t.is_a<libs> ()))) + if (li + ? ((la = t.is_a<libux> ()) || + (la = t.is_a<liba> ()) || + ( t.is_a<libs> ())) + : ((la = t.is_a<libux> ()) || + ( t.is_a<libul> ()))) { - d.f (ls, r, vs, *m, *bs, a, *f, la, li); + if (!t.matched (a)) + fail << t << " is not matched" << + info << "make sure this target is listed as prerequisite"; + + d.f (ls, r, vs, *m, *bs, a, t, la, li); } else - fail << t << " is not a library target"; + fail << t << " is not a library of expected type"; } return value (move (r)); @@ -199,33 +237,101 @@ namespace build2 void compile_rule:: functions (function_family& f, const char* x) { - // $<module>.lib_poptions(<lib-targets>, <otype>) + // $<module>.lib_poptions(<lib-targets>[, <otype>[, <original>]]) // // Return the preprocessor options that should be passed when compiling // sources that depend on the specified libraries. The second argument // is the output target type (obje, objs, etc). // + // The output target type may be omitted for utility libraries (libul{} + // or libu[eas]{}). In this case, only "common interface" options will + // be returned for lib{} dependencies. This is primarily useful for + // obtaining poptions to be passed to tools other than C/C++ compilers + // (for example, Qt moc). + // + // If <original> is true, then return the original -I options without + // performing any translation (for example, to -isystem or /external:I). + // This is the default if <otype> is omitted. To get the translation for + // the common interface options, pass [null] for <otype> and true for + // <original>. + // // Note that passing multiple targets at once is not a mere convenience: // this also allows for more effective duplicate suppression. // - // Note also that this function can only be called during execution - // after all the specified library targets have been matched. Normally - // it is used in ad hoc recipes to implement custom compilation. + // Note also that this function can only be called during execution (or, + // carefully, during match) after all the specified library targets have + // been matched. Normally it is used in ad hoc recipes to implement + // custom compilation. // // Note that this function is not pure. // f.insert (".lib_poptions", false). - insert<lib_thunk_data, names, names> ( + insert<lib_thunk_data, names, optional<names*>, optional<names>> ( &lib_thunk<appended_libraries>, lib_thunk_data { x, [] (void* ls, strings& r, - const vector_view<value>&, const module& m, const scope& bs, - action a, const file& l, bool la, linfo li) + const vector_view<value>& vs, const module& m, const scope& bs, + action a, const target& l, bool la, optional<linfo> li) { + // If this is libul{}, get the matched member (see bin::libul_rule + // for details). + // + const file& f ( + la || li + ? l.as<file> () + : (la = true, + l.prerequisite_targets[a].back ().target->as<file> ())); + + bool common (!li); + bool original (vs.size () > 2 ? convert<bool> (vs[2]) : !li); + + if (!li) + li = link_info (bs, link_type (f).type); + m.append_library_options ( - *static_cast<appended_libraries*> (ls), r, bs, a, l, la, li); + *static_cast<appended_libraries*> (ls), r, + bs, a, f, la, *li, common, original); }}); + + // $<module>.find_system_header(<name>) + // + // Return the header path if the specified header exists in one of the + // system header search directories and NULL otherwise. System header + // search directories are those that the compiler searches by default + // plus directories specified as part of the compiler mode options (but + // not *.poptions). + // + // Note that this function is not pure. + // + f.insert (".find_system_header", false). + insert<const char*, names> ( + [] (const scope* bs, + vector_view<value> vs, + const function_overload& f) -> value + { + const char* x (*reinterpret_cast<const char* const*> (&f.data)); + + if (bs == nullptr) + fail << f.name << " called out of scope"; + + const scope* rs (bs->root_scope ()); + + if (rs == nullptr) + fail << f.name << " called out of project"; + + const module* m (rs->find_module<module> (x)); + + if (m == nullptr) + fail << f.name << " called without " << x << " module loaded"; + + // We can assume the argument is present due to function's types + // signature. + // + auto r (m->find_system_header (convert<path> (move (vs[0])))); + return r ? value (move (*r)) : value (nullptr); + }, + x); } void link_rule:: @@ -249,9 +355,10 @@ namespace build2 // Note that passing multiple targets at once is not a mere convenience: // this also allows for more effective duplicate suppression. // - // Note also that this function can only be called during execution - // after all the specified library targets have been matched. Normally - // it is used in ad hoc recipes to implement custom linking. + // Note also that this function can only be called during execution (or, + // carefully, during match) after all the specified library targets have + // been matched. Normally it is used in ad hoc recipes to implement + // custom linking. // // Note that this function is not pure. // @@ -262,12 +369,15 @@ namespace build2 x, [] (void* ls, strings& r, const vector_view<value>& vs, const module& m, const scope& bs, - action a, const file& l, bool la, linfo li) + action a, const target& l, bool la, optional<linfo> li) { lflags lf (0); bool rel (true); if (vs.size () > 2) { + if (vs[2].null) + throw invalid_argument ("null value"); + for (const name& f: vs[2].as<names> ()) { string s (convert<string> (name (f))); @@ -283,9 +393,11 @@ namespace build2 bool self (vs.size () > 3 ? convert<bool> (vs[3]) : true); - m.append_libraries (*static_cast<appended_libraries*> (ls), r, - bs, - a, l, la, lf, li, self, rel); + m.append_libraries ( + *static_cast<appended_libraries*> (ls), r, + nullptr /* sha256 */, nullptr /* update */, timestamp_unknown, + bs, a, l.as<file> (), la, lf, *li, + nullopt /* for_install */, self, rel); }}); // $<module>.lib_rpaths(<lib-targets>, <otype> [, <link> [, <self>]]) @@ -317,13 +429,12 @@ namespace build2 x, [] (void* ls, strings& r, const vector_view<value>& vs, const module& m, const scope& bs, - action a, const file& l, bool la, linfo li) + action a, const target& l, bool la, optional<linfo> li) { bool link (vs.size () > 2 ? convert<bool> (vs[2]) : false); bool self (vs.size () > 3 ? convert<bool> (vs[3]) : true); m.rpath_libraries (*static_cast<rpathed_libraries*> (ls), r, - bs, - a, l, la, li, link, self); + bs, a, l.as<file> (), la, *li, link, self); }}); // $cxx.obj_modules(<obj-targets>) @@ -352,11 +463,123 @@ namespace build2 if (const file* f = t.is_a<objx> ()) { if (m.modules) - m.append_binless_modules (r, bs, a, *f); + m.append_binless_modules (r, nullptr /* sha256 */, bs, a, *f); } else fail << t << " is not an object file target"; }}); + + // $<module>.deduplicate_export_libs(<names>) + // + // Deduplicate interface library dependencies by removing libraries that + // are also interface dependencies of the specified libraries. This can + // result in significantly better build performance for heavily + // interface-interdependent library families (for example, like Boost). + // Typical usage: + // + // import intf_libs = ... + // import intf_libs += ... + // ... + // import intf_libs += ... + // intf_libs = $cxx.deduplicate_export_libs($intf_libs) + // + // Notes: + // + // 1. We only consider unqualified absolute/normalized target names (the + // idea is that the installed case will already be deduplicated). + // + // 2. We assume all the libraries listed are of the module type and only + // look for cc.export.libs and <module>.export.libs. + // + // 3. No member/group selection/linkup: we resolve *.export.libs on + // whatever is listed (so no liba{}/libs{} overrides will be + // considered). + // + // Because of (2) and (3), this functionality should only be used on a + // controlled list of libraries (usually libraries that belong to the + // same family as this library). + // + // Note that a similar deduplication is also performed when processing + // the libraries. However, it may still make sense to do it once at the + // source for really severe cases (like Boost). + // + // Note that this function is not pure. + // + f.insert (".deduplicate_export_libs", false). + insert<const char*, names> ( + [] (const scope* bs, + vector_view<value> vs, + const function_overload& f) -> value + { + const char* x (*reinterpret_cast<const char* const*> (&f.data)); + + if (bs == nullptr) + fail << f.name << " called out of scope"; + + const scope* rs (bs->root_scope ()); + + if (rs == nullptr) + fail << f.name << " called out of project"; + + const module* m (rs->find_module<module> (x)); + + if (m == nullptr) + fail << f.name << " called without " << x << " module loaded"; + + // We can assume the argument is present due to function's types + // signature. + // + if (vs[0].null) + throw invalid_argument ("null value"); + + names& r (vs[0].as<names> ()); + m->deduplicate_export_libs (*bs, + vector<name> (r.begin (), r.end ()), + r); + return value (move (r)); + }, + x); + + // $<module>.find_system_library(<name>) + // + // Return the library path if the specified library exists in one of the + // system library search directories. System library search directories + // are those that the compiler searches by default plus directories + // specified as part of the compiler mode options (but not *.loptions). + // + // The library can be specified in the same form as expected by the + // linker (-lfoo for POSIX, foo.lib for MSVC) or as a complete name. + // + // Note that this function is not pure. + // + f.insert (".find_system_library", false). + insert<const char*, names> ( + [] (const scope* bs, + vector_view<value> vs, + const function_overload& f) -> value + { + const char* x (*reinterpret_cast<const char* const*> (&f.data)); + + if (bs == nullptr) + fail << f.name << " called out of scope"; + + const scope* rs (bs->root_scope ()); + + if (rs == nullptr) + fail << f.name << " called out of project"; + + const module* m (rs->find_module<module> (x)); + + if (m == nullptr) + fail << f.name << " called without " << x << " module loaded"; + + // We can assume the argument is present due to function's types + // signature. + // + auto r (m->find_system_library (convert<strings> (move (vs[0])))); + return r ? value (move (*r)) : value (nullptr); + }, + x); } } } diff --git a/libbuild2/cc/gcc.cxx b/libbuild2/cc/gcc.cxx index 30f2092..286ba10 100644 --- a/libbuild2/cc/gcc.cxx +++ b/libbuild2/cc/gcc.cxx @@ -45,6 +45,13 @@ namespace build2 d = dir_path (o, 2, string::npos); else continue; + + // Ignore relative paths. Or maybe we should warn? + // + if (d.relative ()) + continue; + + d.normalize (); } catch (const invalid_path& e) { @@ -52,10 +59,91 @@ namespace build2 << o << "'"; } - // Ignore relative paths. Or maybe we should warn? - // - if (!d.relative ()) + r.push_back (move (d)); + } + } + +#ifdef _WIN32 + // Some misconfigured MinGW GCC builds add absolute POSIX directories to + // their built-in search paths (e.g., /mingw/{include,lib}) which GCC then + // interprets as absolute paths relative to the current drive (so the set + // of built-in search paths starts depending on where we run things from). + // + // While that's definitely misguided, life is short and we don't want to + // waste it explaining this in long mailing list threads and telling + // people to complain to whomever built their GCC. So we will just + // recreate the behavior in a way that's consistent with GCC and let + // people discover this on their own. + // + static inline void + add_current_drive (string& s) + { + s.insert (0, work.string (), 0, 2); // Add e.g., `c:`. + } +#endif + + // Parse color/semicolon-separated list of search directories (from + // -print-search-dirs output, environment variables). + // + static void + parse_search_dirs (const string& v, dir_paths& r, + const char* what, const char* what2 = "") + { + // Now the fun part: figuring out which delimiter is used. Normally it + // is ':' but on Windows it is ';' (or can be; who knows for sure). Also + // note that these paths are absolute (or should be). So here is what we + // are going to do: first look for ';'. If found, then that's the + // delimiter. If not found, then there are two cases: it is either a + // single Windows path or the delimiter is ':'. To distinguish these two + // cases we check if the path starts with a Windows drive. + // + char d (';'); + string::size_type e (v.find (d)); + + if (e == string::npos && + (v.size () < 2 || v[0] == '/' || v[1] != ':')) + { + d = ':'; + e = v.find (d); + } + + // Now chop it up. We already have the position of the first delimiter + // (if any). + // + for (string::size_type b (0);; e = v.find (d, (b = e + 1))) + { + dir_path d; + try + { + string ds (v, b, (e != string::npos ? e - b : e)); + + // Skip empty entries (sometimes found in random MinGW toolchains). + // + if (!ds.empty ()) + { +#ifdef _WIN32 + if (path_traits::is_separator (ds[0])) + add_current_drive (ds); +#endif + d = dir_path (move (ds)); + + if (d.relative ()) + throw invalid_path (move (d).string ()); + + d.normalize (); + } + } + catch (const invalid_path& e) + { + fail << "invalid directory '" << e.path << "'" << " in " + << what << what2; + } + + if (!d.empty () && find (r.begin (), r.end (), d) == r.end ()) r.push_back (move (d)); + + if (e == string::npos) + break; } } @@ -69,14 +157,15 @@ namespace build2 // do this is to run the compiler twice. // pair<dir_paths, size_t> config_module:: - gcc_header_search_dirs (const process_path& xc, scope& rs) const + gcc_header_search_dirs (const compiler_info& xi, scope& rs) const { dir_paths r; // Note also that any -I and similar that we may specify on the command - // line are factored into the output. + // line are factored into the output. As well as the CPATH, etc., + // environment variable values. // - cstrings args {xc.recall_string ()}; + cstrings args {xi.path.recall_string ()}; append_options (args, rs, x_mode); // Compile as. @@ -100,7 +189,7 @@ namespace build2 args.push_back ("-"); args.push_back (nullptr); - process_env env (xc); + process_env env (xi.path); // For now let's assume that all the platforms other than Windows // recognize LC_ALL. @@ -113,107 +202,109 @@ namespace build2 if (verb >= 3) print_process (env, args); + bool found_q (false); // Found `#include "..." ...` marker. + bool found_b (false); // Found `#include <...> ...` marker. + + // Open pipe to stderr, redirect stdin and stdout to /dev/null. + // + process pr (run_start ( + env, + args, + -2, /* stdin */ + -2, /* stdout */ + -1 /* stderr */)); try { - //@@ TODO: why don't we use run_start() here? Because it's unable to - // open pipe for stderr and we need to change it first, for example, - // making the err parameter a file descriptor rather than a flag. - // + ifdstream is ( + move (pr.in_efd), fdstream_mode::skip, ifdstream::badbit); - // Open pipe to stderr, redirect stdin and stdout to /dev/null. + // Normally the system header paths appear between the following + // lines: // - process pr (xc, - args.data (), - -2, /* stdin */ - -2, /* stdout */ - -1, /* stderr */ - nullptr /* cwd */, - env.vars); - - try + // #include <...> search starts here: + // End of search list. + // + // The exact text depends on the current locale. What we can rely on + // is the presence of the "#include <...>" marker in the "opening" + // line and the fact that the paths are indented with a single space + // character, unlike the "closing" line. + // + // Note that on Mac OS we will also see some framework paths among + // system header paths, followed with a comment. For example: + // + // /Library/Frameworks (framework directory) + // + // For now we ignore framework paths and to filter them out we will + // only consider valid paths to existing directories, skipping those + // which we fail to normalize or stat. @@ Maybe this is a bit too + // loose, especially compared to gcc_library_search_dirs()? + // + // Note that when there are no paths (e.g., because of -nostdinc), + // then GCC prints both #include markers while Clang -- only "...". + // + for (string s; getline (is, s); ) { - ifdstream is ( - move (pr.in_efd), fdstream_mode::skip, ifdstream::badbit); - - // Normally the system header paths appear between the following - // lines: - // - // #include <...> search starts here: - // End of search list. - // - // The exact text depends on the current locale. What we can rely on - // is the presence of the "#include <...>" substring in the - // "opening" line and the fact that the paths are indented with a - // single space character, unlike the "closing" line. - // - // Note that on Mac OS we will also see some framework paths among - // system header paths, followed with a comment. For example: - // - // /Library/Frameworks (framework directory) - // - // For now we ignore framework paths and to filter them out we will - // only consider valid paths to existing directories, skipping those - // which we fail to normalize or stat. - // - string s; - for (bool found (false); getline (is, s); ) + if (!found_q) + found_q = s.find ("#include \"...\"") != string::npos; + else if (!found_b) + found_b = s.find ("#include <...>") != string::npos; + else { - if (!found) - found = s.find ("#include <...>") != string::npos; - else + if (s[0] != ' ') + break; + + dir_path d; + try { - if (s[0] != ' ') - break; - - try - { - dir_path d (s, 1, s.size () - 1); - - if (d.absolute () && exists (d, true) && - find (r.begin (), r.end (), d.normalize ()) == r.end ()) - r.emplace_back (move (d)); - } - catch (const invalid_path&) - { - // Skip this path. - } - } - } + string ds (s, 1, s.size () - 1); - is.close (); // Don't block. +#ifdef _WIN32 + if (path_traits::is_separator (ds[0])) + add_current_drive (ds); +#endif + d = dir_path (move (ds)); - if (!pr.wait ()) - { - // We have read stderr so better print some diagnostics. - // - diag_record dr (fail); + if (d.relative () || !exists (d, true)) + continue; - dr << "failed to extract " << x_lang << " header search paths" << - info << "command line: "; + d.normalize (); + } + catch (const invalid_path&) + { + continue; + } - print_process (dr, args); + if (find (r.begin (), r.end (), d) == r.end ()) + r.emplace_back (move (d)); } } - catch (const io_error&) + + is.close (); // Don't block. + + if (!run_wait (args, pr)) { - pr.wait (); - fail << "error reading " << x_lang << " compiler -v -E output"; + // We have read stderr so better print some diagnostics. + // + diag_record dr (fail); + + dr << "failed to extract " << x_lang << " header search paths" << + info << "command line: "; + + print_process (dr, args); } } - catch (const process_error& e) + catch (const io_error&) { - error << "unable to execute " << args[0] << ": " << e; - - if (e.child) - exit (1); - - throw failed (); + run_wait (args, pr); + fail << "error reading " << x_lang << " compiler -v -E output"; } - // It's highly unlikely not to have any system directories. More likely - // we misinterpreted the compiler output. + // Note that it's possible that we will have no system directories, for + // example, if the user specified -nostdinc. But we must have still seen + // at least one marker. Failed that we assume we misinterpreted the + // compiler output. // - if (r.empty ()) + if (!found_b && !found_q) fail << "unable to extract " << x_lang << " compiler system header " << "search paths"; @@ -224,7 +315,7 @@ namespace build2 // (Clang, Intel) using the -print-search-dirs option. // pair<dir_paths, size_t> config_module:: - gcc_library_search_dirs (const process_path& xc, scope& rs) const + gcc_library_search_dirs (const compiler_info& xi, scope& rs) const { // The output of -print-search-dirs are a bunch of lines that start with // "<name>: =" where name can be "install", "programs", or "libraries". @@ -251,12 +342,12 @@ namespace build2 gcc_extract_library_search_dirs (cast<strings> (rs[x_mode]), r); size_t rn (r.size ()); - cstrings args {xc.recall_string ()}; + cstrings args {xi.path.recall_string ()}; append_options (args, rs, x_mode); args.push_back ("-print-search-dirs"); args.push_back (nullptr); - process_env env (xc); + process_env env (xi.path); // For now let's assume that all the platforms other than Windows // recognize LC_ALL. @@ -271,6 +362,9 @@ namespace build2 // Open pipe to stdout. // + // Note: this function is called in the serial load phase and so no + // diagnostics buffering is needed. + // process pr (run_start (env, args, 0, /* stdin */ @@ -305,42 +399,22 @@ namespace build2 // by that and let run_finish() deal with it. } - run_finish (args, pr); + run_finish (args, pr, 2 /* verbosity */); if (l.empty ()) fail << "unable to extract " << x_lang << " compiler system library " << "search paths"; - // Now the fun part: figuring out which delimiter is used. Normally it - // is ':' but on Windows it is ';' (or can be; who knows for sure). Also - // note that these paths are absolute (or should be). So here is what we - // are going to do: first look for ';'. If found, then that's the - // delimiter. If not found, then there are two cases: it is either a - // single Windows path or the delimiter is ':'. To distinguish these two - // cases we check if the path starts with a Windows drive. - // - char d (';'); - string::size_type e (l.find (d)); - - if (e == string::npos && - (l.size () < 2 || l[0] == '/' || l[1] != ':')) - { - d = ':'; - e = l.find (d); - } + parse_search_dirs (l, r, args[0], " -print-search-dirs output"); - // Now chop it up. We already have the position of the first delimiter - // (if any). + // While GCC incorporates the LIBRARY_PATH environment variable value + // into the -print-search-dirs output, Clang does not. Also, unlike GCC, + // it appears to consider such paths last. // - for (string::size_type b (0);; e = l.find (d, (b = e + 1))) + if (xi.id.type == compiler_type::clang) { - dir_path d (l, b, (e != string::npos ? e - b : e)); - - if (find (r.begin (), r.end (), d.normalize ()) == r.end ()) - r.emplace_back (move (d)); - - if (e == string::npos) - break; + if (optional<string> v = getenv ("LIBRARY_PATH")) + parse_search_dirs (*v, r, "LIBRARY_PATH environment variable"); } return make_pair (move (r), rn); diff --git a/libbuild2/cc/guess.cxx b/libbuild2/cc/guess.cxx index 9afa29e..d7e9c63 100644 --- a/libbuild2/cc/guess.cxx +++ b/libbuild2/cc/guess.cxx @@ -65,7 +65,6 @@ # include <libbuild2/filesystem.hxx> #endif -#include <map> #include <cstring> // strlen(), strchr(), strstr() #include <libbuild2/diagnostics.hxx> @@ -107,7 +106,7 @@ namespace build2 else if (id.compare (0, p, "icc" ) == 0) type = compiler_type::icc; else throw invalid_argument ( - "invalid compiler type '" + string (id, 0, p) + "'"); + "invalid compiler type '" + string (id, 0, p) + '\''); if (p != string::npos) { @@ -182,12 +181,12 @@ namespace build2 // could also be because there is something wrong with the compiler or // options but that we simply leave to blow up later). // - process pr (run_start (3 /* verbosity */, + process pr (run_start (3 /* verbosity */, xp, args, - -1 /* stdin */, - -1 /* stdout */, - false /* error */)); + -1 /* stdin */, + -1 /* stdout */, + 1 /* stderr (to stdout) */)); string l, r; try { @@ -223,7 +222,7 @@ namespace build2 // that. } - if (!run_finish_code (args.data (), pr, l)) + if (!run_finish_code (args.data (), pr, l, 2 /* verbosity */)) r = "none"; if (r.empty ()) @@ -263,6 +262,8 @@ namespace build2 " stdlib:=\"freebsd\" \n" "# elif defined(__NetBSD__) \n" " stdlib:=\"netbsd\" \n" +"# elif defined(__OpenBSD__) \n" +" stdlib:=\"openbsd\" \n" "# elif defined(__APPLE__) \n" " stdlib:=\"apple\" \n" "# elif defined(__EMSCRIPTEN__) \n" @@ -411,11 +412,13 @@ namespace build2 // // Note that Visual Studio versions prior to 15.0 are not supported. // + // Note also the directories are absolute and normalized. + // struct msvc_info { - dir_path msvc_dir; // VC directory (...\Tools\MSVC\<ver>\). - dir_path psdk_dir; // Platfor SDK version (under Include/, Lib/, etc). - string psdk_ver; // Platfor SDK directory (...\Windows Kits\<ver>\). + dir_path msvc_dir; // VC tools directory (...\Tools\MSVC\<ver>\). + dir_path psdk_dir; // Platform SDK directory (...\Windows Kits\<ver>\). + string psdk_ver; // Platform SDK version (under Include/, Lib/, etc). }; #if defined(_WIN32) && !defined(BUILD2_BOOTSTRAP) @@ -457,13 +460,16 @@ namespace build2 {0x87, 0xBF, 0xD5, 0x77, 0x83, 0x8F, 0x1D, 0x5C}}; // If cl is not empty, then find an installation that contains this cl.exe - // path. + // path. In this case the path must be absolute and normalized. // static optional<msvc_info> - find_msvc (const path& cl = path ()) + find_msvc (const path& cl = path ()) { using namespace butl; + assert (cl.empty () || + (cl.absolute () && cl.normalized (false /* sep */))); + msvc_info r; // Try to obtain the MSVC directory. @@ -529,7 +535,7 @@ namespace build2 // Note: we cannot use bstr_t due to the Clang 9.0 bug #42842. // BSTR p; - if (vs->ResolvePath (L"VC", &p) != S_OK) + if (vs->ResolvePath (L"VC", &p) != S_OK) return dir_path (); unique_ptr<wchar_t, bstr_deleter> deleter (p); @@ -635,36 +641,73 @@ namespace build2 return nullopt; } - // Read the VC version from the file and bail out on error. + // If cl.exe path is not specified, then deduce the default VC tools + // directory for this Visual Studio instance. Otherwise, extract the + // tools directory from this path. // - string vc_ver; // For example, 14.23.28105. + // Note that in the latter case we could potentially avoid the above + // iterating over the VS instances, but let's make sure that the + // specified cl.exe path actually belongs to one of them as a sanity + // check. + // + if (cl.empty ()) + { + // Read the VC version from the file and bail out on error. + // + string vc_ver; // For example, 14.23.28105. - path vp ( - r.msvc_dir / - path ("Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt")); + path vp ( + r.msvc_dir / + path ("Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt")); - try - { - ifdstream is (vp); - vc_ver = trim (is.read_text ()); - } - catch (const io_error&) {} + try + { + ifdstream is (vp); + vc_ver = trim (is.read_text ()); + } + catch (const io_error&) {} - // Make sure that the VC version directory exists. - // - if (!vc_ver.empty ()) - try - { - ((r.msvc_dir /= "Tools") /= "MSVC") /= vc_ver; + if (vc_ver.empty ()) + return nullopt; - if (!dir_exists (r.msvc_dir)) - r.msvc_dir.clear (); + // Make sure that the VC version directory exists. + // + try + { + ((r.msvc_dir /= "Tools") /= "MSVC") /= vc_ver; + + if (!dir_exists (r.msvc_dir)) + return nullopt; + } + catch (const invalid_path&) {return nullopt;} + catch (const system_error&) {return nullopt;} } - catch (const invalid_path&) {} - catch (const system_error&) {} + else + { + (r.msvc_dir /= "Tools") /= "MSVC"; - if (r.msvc_dir.empty ()) - return nullopt; + // Extract the VC tools version from the cl.exe path and append it + // to r.msvc_dir. + // + if (!cl.sub (r.msvc_dir)) + return nullopt; + + // For example, 14.23.28105\bin\Hostx64\x64\cl.exe. + // + path p (cl.leaf (r.msvc_dir)); // Can't throw. + + auto i (p.begin ()); // Tools version. + if (i == p.end ()) + return nullopt; + + r.msvc_dir /= *i; // Can't throw. + + // For good measure, make sure that the tools version is not the + // last component in the cl.exe path. + // + if (++i == p.end ()) + return nullopt; + } } // Try to obtain the latest Platform SDK directory and version. @@ -718,7 +761,7 @@ namespace build2 // for (const dir_entry& de: dir_iterator (r.psdk_dir / dir_path ("Include"), - false /* ignore_dangling */)) + dir_iterator::no_follow)) { if (de.type () == entry_type::directory) { @@ -736,6 +779,16 @@ namespace build2 return nullopt; } + try + { + r.msvc_dir.normalize (); + r.psdk_dir.normalize (); + } + catch (const invalid_path&) + { + return nullopt; + } + return r; } #endif @@ -776,7 +829,8 @@ namespace build2 // Note: allowed to change pre if succeeds. // static guess_result - guess (const char* xm, + guess (context& ctx, + const char* xm, lang xl, const path& xc, const strings& x_mo, @@ -830,7 +884,9 @@ namespace build2 // // The main drawback of the latter, of course, is that the commands we // print are no longer re-runnable (even though we may have supplied - // the rest of the "environment" explicitly on the command line). + // the rest of the "environment" explicitly on the command line). Plus + // we would need to save whatever environment variables we used to + // form the fallback path in case of hermetic configuration. // // An alternative strategy is to try and obtain the corresponding // "environment" in case of the effective (absolute) path similar to @@ -925,10 +981,12 @@ namespace build2 // We try to find the matching installation only for MSVC (for Clang // we extract this information from the compiler). // - if (xc.absolute () && - (pt == type::msvc && !pv)) + if (xc.absolute () && (pt == type::msvc && !pv)) { - if (optional<msvc_info> mi = find_msvc (xc)) + path cl (xc); // Absolute but may not be normalized. + cl.normalize (); // Can't throw since this is an existing path. + + if (optional<msvc_info> mi = find_msvc (cl)) { search_info = info_ptr ( new msvc_info (move (*mi)), msvc_info_deleter); @@ -964,7 +1022,7 @@ namespace build2 #endif string cache; - auto run = [&cs, &env, &args, &cache] ( + auto run = [&ctx, &cs, &env, &args, &cache] ( const char* o, auto&& f, bool checksum = false) -> guess_result @@ -972,10 +1030,11 @@ namespace build2 args[args.size () - 2] = o; cache.clear (); return build2::run<guess_result> ( + ctx, 3 /* verbosity */, env, - args.data (), - forward<decltype(f)> (f), + args, + forward<decltype (f)> (f), false /* error */, false /* ignore_exit */, checksum ? &cs : nullptr); @@ -1017,9 +1076,11 @@ namespace build2 : guess_result ()); } + size_t p; + // The gcc -v output will have a last line in the form: // - // "gcc version X.Y.Z ..." + // "gcc version X[.Y[.Z]][...] ..." // // The "version" word can probably be translated. For example: // @@ -1030,6 +1091,8 @@ namespace build2 // gcc version 4.9.2 (Ubuntu 4.9.2-0ubuntu1~14.04) // gcc version 5.1.0 (Ubuntu 5.1.0-0ubuntu11~14.04.1) // gcc version 6.0.0 20160131 (experimental) (GCC) + // gcc version 9.3-win32 20200320 (GCC) + // gcc version 10-win32 20220324 (GCC) // if (cache.empty ()) { @@ -1112,7 +1175,12 @@ namespace build2 // The clang-cl output is exactly the same, which means the only way // to distinguish it is based on the executable name. // - if (l.find ("clang ") != string::npos) + // We must also watch out for potential misdetections, for example: + // + // Configured with: ../gcc/configure CC=clang CXX=clang++ ... + // + if ((p = l.find ("clang ")) != string::npos && + (p == 0 || l[p - 1] == ' ')) { if (cache.empty ()) { @@ -1264,7 +1332,11 @@ namespace build2 // const char* evars[] = {"CL=", "_CL_=", nullptr}; - r = build2::run<guess_result> (3, process_env (xp, evars), f, false); + r = build2::run<guess_result> (ctx, + 3, + process_env (xp, evars), + f, + false); if (r.empty ()) { @@ -1415,10 +1487,12 @@ namespace build2 // And VC 16 seems to have the runtime version 14.1 (and not 14.2, as // one might expect; DLLs are still *140.dll but there are now _1 and _2 // variants for, say, msvcp140.dll). We will, however, call it 14.2 - // (which is the version of the "toolset") in our target triplet. + // (which is the version of the "toolset") in our target triplet. And we + // will call VC 17 14.3 (which is also the version of the "toolset"). // // year ver cl crt/dll toolset // + // 2022 17.X 19.3X 14.?/140 14.3X // 2019 16.X 19.2X 14.2/140 14.2X // 2017 15.9 19.16 14.1/140 14.16 // 2017 15.8 19.15 14.1/140 @@ -1437,7 +1511,8 @@ namespace build2 // // _MSC_VER is the numeric cl version, e.g., 1921 for 19.21. // - /**/ if (v.major == 19 && v.minor >= 20) return "14.2"; + /**/ if (v.major == 19 && v.minor >= 30) return "14.3"; + else if (v.major == 19 && v.minor >= 20) return "14.2"; else if (v.major == 19 && v.minor >= 10) return "14.1"; else if (v.major == 19 && v.minor == 0) return "14.0"; else if (v.major == 18 && v.minor == 0) return "12.0"; @@ -1461,19 +1536,21 @@ namespace build2 // Studio command prompt puts into INCLUDE) including any paths from the // compiler mode and their count. // - // Note that currently we don't add any ATL/MFC or WinRT paths (but could - // do that probably first checking if they exist/empty). + // Note that currently we don't add any ATL/MFC paths (but could do that + // probably first checking if they exist/empty). // static pair<dir_paths, size_t> - msvc_inc (const msvc_info& mi, const strings& mo) + msvc_hdr (const msvc_info& mi, const strings& mo) { dir_paths r; - // Extract /I paths from the compiler mode. + // Extract /I paths and similar from the compiler mode. // msvc_extract_header_search_dirs (mo, r); size_t rn (r.size ()); + // Note: the resulting directories are normalized by construction. + // r.push_back (dir_path (mi.msvc_dir) /= "include"); // This path structure only appeared in Platform SDK 10 (if anyone wants @@ -1487,6 +1564,7 @@ namespace build2 r.push_back (dir_path (d) /= "ucrt" ); r.push_back (dir_path (d) /= "shared"); r.push_back (dir_path (d) /= "um" ); + r.push_back (dir_path (d) /= "winrt" ); } return make_pair (move (r), rn); @@ -1522,6 +1600,8 @@ namespace build2 msvc_extract_library_search_dirs (mo, r); size_t rn (r.size ()); + // Note: the resulting directories are normalized by construction. + // r.push_back ((dir_path (mi.msvc_dir) /= "lib") /= cpu); // This path structure only appeared in Platform SDK 10 (if anyone wants @@ -1565,8 +1645,19 @@ namespace build2 const char* msvc_cpu (const string&); // msvc.cxx + // Note that LIB, LINK, and _LINK_ are technically link.exe's variables + // but we include them in case linking is done via the compiler without + // loading bin.ld. BTW, the same applies to rc.exe INCLUDE. + // + // See also the note on environment and caching below if adding any new + // variables. + // + static const char* msvc_env[] = {"INCLUDE", "IFCPATH", "CL", "_CL_", + "LIB", "LINK", "_LINK_", nullptr}; + static compiler_info - guess_msvc (const char* xm, + guess_msvc (context&, + const char* xm, lang xl, const path& xc, const string* xv, @@ -1589,6 +1680,7 @@ namespace build2 // "x86" // "x64" // "ARM" + // "ARM64" // compiler_version ver; { @@ -1652,9 +1744,10 @@ namespace build2 for (size_t b (0), e (0), n; (n = next_word (s, b, e, ' ', ',')) != 0; ) { - if (s.compare (b, n, "x64", 3) == 0 || - s.compare (b, n, "x86", 3) == 0 || - s.compare (b, n, "ARM", 3) == 0 || + if (s.compare (b, n, "x64", 3) == 0 || + s.compare (b, n, "x86", 3) == 0 || + s.compare (b, n, "ARM64", 5) == 0 || + s.compare (b, n, "ARM", 3) == 0 || s.compare (b, n, "80x86", 5) == 0) { cpu.assign (s, b, n); @@ -1665,15 +1758,15 @@ namespace build2 if (cpu.empty ()) fail << "unable to extract MSVC target CPU from " << "'" << s << "'"; - // Now we need to map x86, x64, and ARM to the target triplets. The - // problem is, there aren't any established ones so we got to invent - // them ourselves. Based on the discussion in - // <libbutl/target-triplet.mxx>, we need something in the + // Now we need to map x86, x64, ARM, and ARM64 to the target + // triplets. The problem is, there aren't any established ones so we + // got to invent them ourselves. Based on the discussion in + // <libbutl/target-triplet.hxx>, we need something in the // CPU-VENDOR-OS-ABI form. // // The CPU part is fairly straightforward with x86 mapped to 'i386' - // (or maybe 'i686'), x64 to 'x86_64', and ARM to 'arm' (it could also - // include the version, e.g., 'amrv8'). + // (or maybe 'i686'), x64 to 'x86_64', ARM to 'arm' (it could also + // include the version, e.g., 'amrv8'), and ARM64 to 'aarch64'. // // The (toolchain) VENDOR is also straightforward: 'microsoft'. Why // not omit it? Two reasons: firstly, there are other compilers with @@ -1683,7 +1776,7 @@ namespace build2 // // OS-ABI is where things are not as clear cut. The OS part shouldn't // probably be just 'windows' since we have Win32 and WinCE. And - // WinRT. And Universal Windows Platform (UWP). So perhaps the + // WinRT. And Universal Windows Platform (UWP). So perhaps the // following values for OS: 'win32', 'wince', 'winrt', 'winup'. // // For 'win32' the ABI part could signal the Microsoft C/C++ runtime @@ -1708,9 +1801,10 @@ namespace build2 // Putting it all together, Visual Studio 2015 will then have the // following target triplets: // - // x86 i386-microsoft-win32-msvc14.0 - // x64 x86_64-microsoft-win32-msvc14.0 - // ARM arm-microsoft-winup-??? + // x86 i386-microsoft-win32-msvc14.0 + // x64 x86_64-microsoft-win32-msvc14.0 + // ARM arm-microsoft-winup-??? + // ARM64 aarch64-microsoft-win32-msvc14.0 // if (cpu == "ARM") fail << "cl.exe ARM/WinRT/UWP target is not yet supported"; @@ -1720,6 +1814,8 @@ namespace build2 t = "x86_64-microsoft-win32-msvc"; else if (cpu == "x86" || cpu == "80x86") t = "i386-microsoft-win32-msvc"; + else if (cpu == "ARM64") + t = "aarch64-microsoft-win32-msvc"; else assert (false); @@ -1731,21 +1827,23 @@ namespace build2 else ot = t = *xt; + target_triplet tt (t); // Shouldn't fail. + // If we have the MSVC installation information, then this means we are // running out of the Visual Studio command prompt and will have to // supply PATH/INCLUDE/LIB/IFCPATH equivalents ourselves. // optional<pair<dir_paths, size_t>> lib_dirs; - optional<pair<dir_paths, size_t>> inc_dirs; + optional<pair<dir_paths, size_t>> hdr_dirs; optional<pair<dir_paths, size_t>> mod_dirs; string bpat; if (const msvc_info* mi = static_cast<msvc_info*> (gr.info.get ())) { - const char* cpu (msvc_cpu (target_triplet (t).cpu)); + const char* cpu (msvc_cpu (tt.cpu)); lib_dirs = msvc_lib (*mi, x_mo, cpu); - inc_dirs = msvc_inc (*mi, x_mo); + hdr_dirs = msvc_hdr (*mi, x_mo); mod_dirs = msvc_mod (*mi, x_mo, cpu); bpat = msvc_bin (*mi, cpu); @@ -1789,12 +1887,49 @@ namespace build2 move (csl), move (xsl), move (lib_dirs), - move (inc_dirs), - move (mod_dirs)}; + move (hdr_dirs), + move (mod_dirs), + msvc_env, + nullptr}; } + // See "Environment Variables Affecting GCC". + // + // Note that we also check below that the following variables are not set + // since they would interfere with what we are doing. + // + // DEPENDENCIES_OUTPUT + // SUNPRO_DEPENDENCIES + // + // Note also that we include (some) linker's variables in case linking is + // done via the compiler without loading bin.ld (to do this precisely we + // would need to detect which linker is being used at which point we might + // as well load bin.ld). + // + // See also the note on environment and caching below if adding any new + // variables. + // + static const char* gcc_c_env[] = { + "CPATH", "C_INCLUDE_PATH", + "LIBRARY_PATH", "LD_RUN_PATH", + "SOURCE_DATE_EPOCH", "GCC_EXEC_PREFIX", "COMPILER_PATH", + nullptr}; + + static const char* gcc_cxx_env[] = { + "CPATH", "CPLUS_INCLUDE_PATH", + "LIBRARY_PATH", "LD_RUN_PATH", + "SOURCE_DATE_EPOCH", "GCC_EXEC_PREFIX", "COMPILER_PATH", + nullptr}; + + // Note that Clang recognizes a whole family of *_DEPLOYMENT_TARGET + // variables (as does ld64). + // + static const char* macos_env[] = { + "SDKROOT", "MACOSX_DEPLOYMENT_TARGET", nullptr}; + static compiler_info - guess_gcc (const char* xm, + guess_gcc (context& ctx, + const char* xm, lang xl, const path& xc, const string* xv, @@ -1813,7 +1948,7 @@ namespace build2 // though language words can be translated and even rearranged (see // examples above). // - // "gcc version A.B.C[ ...]" + // "gcc version X[.Y[.Z]][...]" // compiler_version ver; { @@ -1839,38 +1974,34 @@ namespace build2 // end of the word position (first space). In fact, we can just // check if it is >= e. // - if (s.find_first_not_of ("1234567890.", b, 11) >= e) + size_t p (s.find_first_not_of ("1234567890.", b, 11)); + if (p >= e || (p > b && (s[p] == '-' || s[p] == '+'))) break; } if (b == e) fail << "unable to extract GCC version from '" << s << "'"; - ver.string.assign (s, b, string::npos); - - // Split the version into components. + // Split the version into components by parsing it as semantic-like + // version. // - size_t vb (b), ve (b); - auto next = [&s, b, e, &vb, &ve] (const char* m) -> uint64_t + try { - try - { - if (next_word (s, e, vb, ve, '.')) - return stoull (string (s, vb, ve - vb)); - } - catch (const invalid_argument&) {} - catch (const out_of_range&) {} - - fail << "unable to extract GCC " << m << " version from '" - << string (s, b, e - b) << "'" << endf; - }; - - ver.major = next ("major"); - ver.minor = next ("minor"); - ver.patch = next ("patch"); + semantic_version v (string (s, b, e - b), + semantic_version::allow_omit_minor | + semantic_version::allow_build, + ".-+"); + ver.major = v.major; + ver.minor = v.minor; + ver.patch = v.patch; + ver.build = move (v.build); + } + catch (const invalid_argument& e) + { + fail << "unable to extract GCC version from '" << s << "': " << e; + } - if (e != s.size ()) - ver.build.assign (s, e + 1, string::npos); + ver.string.assign (s, b, string::npos); } // Figure out the target architecture. This is actually a lot trickier @@ -1911,7 +2042,7 @@ namespace build2 // auto f = [] (string& l, bool) {return move (l);}; - t = run<string> (3, xp, args.data (), f, false); + t = run<string> (ctx, 3, xp, args, f, false); if (t.empty ()) { @@ -1919,7 +2050,7 @@ namespace build2 << "falling back to -dumpmachine";}); args[args.size () - 2] = "-dumpmachine"; - t = run<string> (3, xp, args.data (), f, false); + t = run<string> (ctx, 3, xp, args, f, false); } if (t.empty ()) @@ -1973,6 +2104,23 @@ namespace build2 } } + // Environment. + // + if (getenv ("DEPENDENCIES_OUTPUT")) + fail << "GCC DEPENDENCIES_OUTPUT environment variable is set"; + + if (getenv ("SUNPRO_DEPENDENCIES")) + fail << "GCC SUNPRO_DEPENDENCIES environment variable is set"; + + const char* const* c_env (nullptr); + switch (xl) + { + case lang::c: c_env = gcc_c_env; break; + case lang::cxx: c_env = gcc_cxx_env; break; + } + + const char* const* p_env (tt.system == "darwin" ? macos_env : nullptr); + return compiler_info { move (gr.path), move (gr.id), @@ -1990,7 +2138,9 @@ namespace build2 move (xsl), nullopt, nullopt, - nullopt}; + nullopt, + c_env, + p_env}; } struct clang_msvc_info: msvc_info @@ -2043,9 +2193,9 @@ namespace build2 process pr (run_start (3 /* verbosity */, xp, args, - -2 /* stdin (/dev/null) */, - -1 /* stdout */, - false /* error (2>&1) */)); + -2 /* stdin (to /dev/null) */, + -1 /* stdout */, + 1 /* stderr (to stdout) */)); clang_msvc_info r; @@ -2197,7 +2347,7 @@ namespace build2 // that. } - if (!run_finish_code (args.data (), pr, l)) + if (!run_finish_code (args.data (), pr, l, 2 /* verbosity */)) fail << "unable to extract MSVC information from " << xp; if (const char* w = ( @@ -2212,8 +2362,30 @@ namespace build2 return r; } + // These are derived from gcc_* plus the sparse documentation (clang(1)) + // and source code. + // + // Note that for now for Clang targeting MSVC we use msvc_env but should + // probably use a combined list. + // + // See also the note on environment and caching below if adding any new + // variables. + // + static const char* clang_c_env[] = { + "CPATH", "C_INCLUDE_PATH", "CCC_OVERRIDE_OPTIONS", + "LIBRARY_PATH", "LD_RUN_PATH", + "COMPILER_PATH", + nullptr}; + + static const char* clang_cxx_env[] = { + "CPATH", "CPLUS_INCLUDE_PATH", "CCC_OVERRIDE_OPTIONS", + "LIBRARY_PATH", "LD_RUN_PATH", + "COMPILER_PATH", + nullptr}; + static compiler_info - guess_clang (const char* xm, + guess_clang (context& ctx, + const char* xm, lang xl, const path& xc, const string* xv, @@ -2224,7 +2396,7 @@ namespace build2 const strings* c_lo, const strings* x_lo, guess_result&& gr, sha256& cs) { - // This function handles vanialla Clang, including its clang-cl variant, + // This function handles vanilla Clang, including its clang-cl variant, // as well as Apple and Emscripten variants. // // The clang-cl variant appears to be a very thin wrapper over the @@ -2252,6 +2424,12 @@ namespace build2 // // emcc (...) 2.0.8 // + // Pre-releases of the vanilla Clang append `rc` or `git` to the + // version, unfortunately without a separator. So we will handle these + // ad hoc. For example: + // + // FreeBSD clang version 18.1.0rc (https://github.com/llvm/llvm-project.git llvmorg-18-init-18361-g22683463740e) + // auto extract_version = [] (const string& s, bool patch, const char* what) -> compiler_version { @@ -2266,8 +2444,28 @@ namespace build2 // end of the word position (first space). In fact, we can just // check if it is >= e. // - if (s.find_first_not_of ("1234567890.", b, 11) >= e) + size_t p (s.find_first_not_of ("1234567890.", b, 11)); + if (p >= e) break; + + // Handle the unseparated `rc` and `git` suffixes. + // + if (p != string::npos) + { + if (p + 2 == e && (e - b) > 2 && + s[p] == 'r' && s[p + 1] == 'c') + { + e -= 2; + break; + } + + if (p + 3 == e && (e - b) > 3 && + s[p] == 'g' && s[p + 1] == 'i' && s[p + 2] == 't') + { + e -= 3; + break; + } + } } if (b == e) @@ -2303,7 +2501,14 @@ namespace build2 ver.patch = next ("patch", patch); if (e != s.size ()) - ver.build.assign (s, e + 1, string::npos); + { + // Skip the separator (it could also be unseparated `rc` or `git`). + // + if (s[e] == ' ' || s[e] == '-') + e++; + + ver.build.assign (s, e, string::npos); + } return ver; }; @@ -2327,7 +2532,10 @@ namespace build2 // Some overrides for testing. // + //string s (xv != nullptr ? *xv : ""); + // //s = "clang version 3.7.0 (tags/RELEASE_370/final)"; + //s = "FreeBSD clang version 18.1.0rc (https://github.com/llvm/llvm-project.git llvmorg-18-init-18361-g22683463740e)"; // //gr.id.variant = "apple"; //s = "Apple LLVM version 7.3.0 (clang-703.0.16.1)"; @@ -2355,7 +2563,20 @@ namespace build2 // // Specifically, we now look in the libc++'s __config file for the // _LIBCPP_VERSION and use the previous version as a conservative - // estimate. + // estimate (NOTE: that there could be multiple __config files with + // potentially different versions so compile with -v to see which one + // gets picked up). + // + // Also, lately, we started seeing _LIBCPP_VERSION values like 15.0.6 + // or 16.0.2 which would suggest the base is 15.0.5 or 16.0.1. But + // that assumption did not check out with the actual usage. For + // example, vanilla Clang 16 should no longer require -fmodules-ts but + // the Apple's version (that is presumably based on it) still does. So + // the theory here is that Apple upgrades to newer libc++ while + // keeping the old compiler. Which means we must be more conservative + // and assume something like 15.0.6 is still 14-based. But then you + // get -Wunqualified-std-cast-call in 14, which was supposedly only + // introduced in Clang 15. So maybe not. // // Note that this is Apple Clang version and not XCode version. // @@ -2372,34 +2593,45 @@ namespace build2 // 9.1.0 -> 5.0 // 10.0.0 -> 6.0 // 11.0.0 -> 7.0 - // 11.0.3 -> 8.0 + // 11.0.3 -> 8.0 (yes, seriously!) // 12.0.0 -> 9.0 + // 12.0.5 -> 10.0 (yes, seriously!) + // 13.0.0 -> 11.0 + // 13.1.6 -> 12.0 + // 14.0.0 -> 12.0 (_LIBCPP_VERSION=130000) + // 14.0.3 -> 15.0 (_LIBCPP_VERSION=150006) + // 15.0.0 -> 16.0 (_LIBCPP_VERSION=160002) // uint64_t mj (var_ver->major); uint64_t mi (var_ver->minor); uint64_t pa (var_ver->patch); - if (mj >= 12) {mj = 9; mi = 0;} - else if (mj == 11 && (mi > 0 || pa >= 3)) {mj = 8; mi = 0;} - else if (mj == 11) {mj = 7; mi = 0;} - else if (mj == 10) {mj = 6; mi = 0;} - else if (mj == 9 && mi >= 1) {mj = 5; mi = 0;} - else if (mj == 9) {mj = 4; mi = 0;} - else if (mj == 8) {mj = 3; mi = 9;} - else if (mj == 7 && mi >= 3) {mj = 3; mi = 8;} - else if (mj == 7) {mj = 3; mi = 7;} - else if (mj == 6 && mi >= 1) {mj = 3; mi = 5;} - else if (mj == 6) {mj = 3; mi = 4;} - else if (mj == 5 && mi >= 1) {mj = 3; mi = 3;} - else if (mj == 5) {mj = 3; mi = 2;} - else if (mj == 4 && mi >= 2) {mj = 3; mi = 1;} - else {mj = 3; mi = 0;} + if (mj >= 15) {mj = 16; mi = 0; pa = 0;} + else if (mj == 14 && (mi > 0 || pa >= 3)) {mj = 15; mi = 0; pa = 0;} + else if (mj == 14 || (mj == 13 && mi >= 1)) {mj = 12; mi = 0; pa = 0;} + else if (mj == 13) {mj = 11; mi = 0; pa = 0;} + else if (mj == 12 && (mi > 0 || pa >= 5)) {mj = 10; mi = 0; pa = 0;} + else if (mj == 12) {mj = 9; mi = 0; pa = 0;} + else if (mj == 11 && (mi > 0 || pa >= 3)) {mj = 8; mi = 0; pa = 0;} + else if (mj == 11) {mj = 7; mi = 0; pa = 0;} + else if (mj == 10) {mj = 6; mi = 0; pa = 0;} + else if (mj == 9 && mi >= 1) {mj = 5; mi = 0; pa = 0;} + else if (mj == 9) {mj = 4; mi = 0; pa = 0;} + else if (mj == 8) {mj = 3; mi = 9; pa = 0;} + else if (mj == 7 && mi >= 3) {mj = 3; mi = 8; pa = 0;} + else if (mj == 7) {mj = 3; mi = 7; pa = 0;} + else if (mj == 6 && mi >= 1) {mj = 3; mi = 5; pa = 0;} + else if (mj == 6) {mj = 3; mi = 4; pa = 0;} + else if (mj == 5 && mi >= 1) {mj = 3; mi = 3; pa = 0;} + else if (mj == 5) {mj = 3; mi = 2; pa = 0;} + else if (mj == 4 && mi >= 2) {mj = 3; mi = 1; pa = 0;} + else {mj = 3; mi = 0; pa = 0;} ver = compiler_version { - to_string (mj) + '.' + to_string (mi) + ".0", + to_string (mj) + '.' + to_string (mi) + '.' + to_string (pa), mj, mi, - 0, + pa, ""}; } else if (emscr) @@ -2413,6 +2645,22 @@ namespace build2 }); var_ver = extract_version (gr.signature, false, "Emscripten"); + + // The official Emscripten distributions routinely use unreleased + // Clang snapshots which nevertheless have the next release version + // (which means it's actually somewhere between the previous release + // and the next release). On the other hand, distributions like Debian + // package it to use their Clang package which normally has the + // accurate version. So here we will try to detect the former and + // similar to the Apple case we will conservatively adjust it to the + // previous release. + // + if (gr.type_signature.find ("googlesource") != string::npos) + { + if (ver.patch != 0) ver.patch--; + else if (ver.minor != 0) ver.minor--; + else ver.major--; + } } // Figure out the target architecture. @@ -2436,7 +2684,7 @@ namespace build2 // for LC_ALL. // auto f = [] (string& l, bool) {return move (l);}; - t = run<string> (3, xp, args.data (), f, false); + t = run<string> (ctx, 3, xp, args, f, false); if (t.empty ()) fail << "unable to extract target architecture from " << xc @@ -2496,7 +2744,7 @@ namespace build2 const char* cpu (msvc_cpu (tt.cpu)); // Come up with the system library search paths. Ideally we would want - // to extract this from Clang and -print-search-paths would have been + // to extract this from Clang and -print-search-dirs would have been // the natural way for Clang to report it. But no luck. // lib_dirs = msvc_lib (mi, x_mo, cpu); @@ -2618,6 +2866,29 @@ namespace build2 } } + // Environment. + // + // Note that "Emscripten Compiler Frontend (emcc)" has a long list of + // environment variables with little explanation. So someone will need + // to figure out what's important (some of them are clearly for + // debugging of emcc itself). + // + const char* const* c_env (nullptr); + const char* const* p_env (nullptr); + if (tt.system == "win32-msvc") + c_env = msvc_env; + else + { + switch (xl) + { + case lang::c: c_env = clang_c_env; break; + case lang::cxx: c_env = clang_cxx_env; break; + } + + if (tt.system == "darwin") + p_env = macos_env; + } + return compiler_info { move (gr.path), move (gr.id), @@ -2635,11 +2906,14 @@ namespace build2 move (xsl), move (lib_dirs), nullopt, - nullopt}; + nullopt, + c_env, + p_env}; } static compiler_info - guess_icc (const char* xm, + guess_icc (context& ctx, + const char* xm, lang xl, const path& xc, const string* xv, @@ -2703,7 +2977,7 @@ namespace build2 // // @@ TODO: running without the mode options. // - s = run<string> (3, env, "-V", f, false); + s = run<string> (ctx, 3, env, "-V", f, false); if (s.empty ()) fail << "unable to extract signature from " << xc << " -V output"; @@ -2829,7 +3103,7 @@ namespace build2 // The -V output is sent to STDERR. // - t = run<string> (3, env, args.data (), f, false); + t = run<string> (ctx, 3, env, args, f, false); if (t.empty ()) fail << "unable to extract target architecture from " << xc @@ -2880,7 +3154,7 @@ namespace build2 // { auto f = [] (string& l, bool) {return move (l);}; - t = run<string> (3, xp, "-dumpmachine", f); + t = run<string> (ctx, 3, xp, "-dumpmachine", f); } if (t.empty ()) @@ -2950,7 +3224,9 @@ namespace build2 move (xsl), nullopt, nullopt, - nullopt}; + nullopt, + nullptr, /* TODO */ + nullptr}; } // Compiler checks can be expensive (we often need to run the compiler @@ -2959,8 +3235,10 @@ namespace build2 static global_cache<compiler_info> cache; const compiler_info& - guess (const char* xm, + guess (context& ctx, + const char* xm, lang xl, + const string& ec, const path& xc, const string* xis, const string* xv, @@ -2972,6 +3250,13 @@ namespace build2 { // First check the cache. // + // Note that in case of MSVC (and Clang targeting MSVC) sys_*_dirs can + // be affected by the environment (INCLUDE, LIB, and IFCPATH) which is + // project-specific. So we have to include those into the key. But we + // don't know yet know whether it's those compilers/targets. So it seems + // we have no better choice than to include the project environment if + // overridden. + // // @@ We currently include config.{cc,x}.[pc]options into the key which // means any project-specific tweaks to these result in a different // key. Perhaps we should assume that any options that can affect the @@ -2984,6 +3269,7 @@ namespace build2 sha256 cs; cs.append (static_cast<size_t> (xl)); cs.append (xc.string ()); + if (!ec.empty ()) cs.append (ec); if (xis != nullptr) cs.append (*xis); append_options (cs, x_mo); if (c_po != nullptr) append_options (cs, *c_po); @@ -3024,7 +3310,7 @@ namespace build2 if (pre.type != invalid_compiler_type) { - gr = guess (xm, xl, xc, x_mo, xi, pre, cs); + gr = guess (ctx, xm, xl, xc, x_mo, xi, pre, cs); if (gr.empty ()) { @@ -3040,13 +3326,14 @@ namespace build2 } if (gr.empty ()) - gr = guess (xm, xl, xc, x_mo, xi, pre, cs); + gr = guess (ctx, xm, xl, xc, x_mo, xi, pre, cs); if (gr.empty ()) fail << "unable to guess " << xl << " compiler type of " << xc << info << "use config." << xm << ".id to specify explicitly"; compiler_info (*gf) ( + context&, const char*, lang, const path&, const string*, const string*, const strings&, const strings*, const strings*, @@ -3066,7 +3353,8 @@ namespace build2 case compiler_type::icc: gf = &guess_icc; break; } - compiler_info r (gf (xm, xl, xc, xv, xt, + compiler_info r (gf (ctx, + xm, xl, xc, xv, xt, x_mo, c_po, x_po, c_co, x_co, c_lo, x_lo, move (gr), cs)); @@ -3196,7 +3484,7 @@ namespace build2 if (id.variant == "emscripten") s = "em++"; else - s = "clang"; + s = "clang++"; break; } case type::icc: s = "icpc"; break; @@ -3218,5 +3506,193 @@ namespace build2 return r; } + + // Table 23 [tab:headers.cpp]. + // + // In the future we will probably have to maintain per-standard additions. + // + static const char* std_importable[] = { + "<initializer_list>", // Note: keep first (present in freestanding). + "<algorithm>", + "<any>", + "<array>", + "<atomic>", + "<barrier>", + "<bit>", + "<bitset>", + "<charconv>", + "<chrono>", + "<codecvt>", + "<compare>", + "<complex>", + "<concepts>", + "<condition_variable>", + "<coroutine>", + "<deque>", + "<exception>", + "<execution>", + "<filesystem>", + "<format>", + "<forward_list>", + "<fstream>", + "<functional>", + "<future>", + "<iomanip>", + "<ios>", + "<iosfwd>", + "<iostream>", + "<istream>", + "<iterator>", + "<latch>", + "<limits>", + "<list>", + "<locale>", + "<map>", + "<memory>", + "<memory_resource>", + "<mutex>", + "<new>", + "<numbers>", + "<numeric>", + "<optional>", + "<ostream>", + "<queue>", + "<random>", + "<ranges>", + "<ratio>", + "<regex>", + "<scoped_allocator>", + "<semaphore>", + "<set>", + "<shared_mutex>", + "<source_location>", + "<span>", + "<sstream>", + "<stack>", + "<stdexcept>", + "<stop_token>", + "<streambuf>", + "<string>", + "<string_view>", + "<strstream>", + "<syncstream>", + "<system_error>", + "<thread>", + "<tuple>", + "<typeindex>", + "<typeinfo>", + "<type_traits>", + "<unordered_map>", + "<unordered_set>", + "<utility>", + "<valarray>", + "<variant>", + "<vector>", + "<version>" + }; + + // Table 24 ([tab:headers.cpp.c]) + // + static const char* std_non_importable[] = { + "<cassert>", + "<cctype>", + "<cerrno>", + "<cfenv>", + "<cfloat>", + "<cinttypes>", + "<climits>", + "<clocale>", + "<cmath>", + "<csetjmp>", + "<csignal>", + "<cstdarg>", + "<cstddef>", + "<cstdint>", + "<cstdio>", + "<cstdlib>", + "<cstring>", + "<ctime>", + "<cuchar>", + "<cwchar>", + "<cwctype>" + }; + + void + guess_std_importable_headers (const compiler_info& ci, + const dir_paths& sys_hdr_dirs, + importable_headers& hs) + { + hs.group_map.emplace (header_group_std, 0); + hs.group_map.emplace (header_group_std_importable, 0); + + // For better performance we make compiler-specific assumptions. + // + // For example, we can assume that all these headers are found in the + // same header search directory. This is at least the case for GCC's + // libstdc++. + // + // Note also that some headers could be missing. For example, <format> + // is currently not provided by GCC. Though entering missing headers + // should be harmless. + // + // Plus, a freestanding implementation may only have a subset of such + // headers (see [compliance]). + // + pair<const path, importable_headers::groups>* p; + auto add_groups = [&p] (bool imp) + { + if (imp) + p->second.push_back (header_group_std_importable); // More specific. + + p->second.push_back (header_group_std); + }; + + if (ci.id.type != compiler_type::gcc) + { + for (const char* f: std_importable) + if ((p = hs.insert_angle (sys_hdr_dirs, f)) != nullptr) + add_groups (true); + + for (const char* f: std_non_importable) + if ((p = hs.insert_angle (sys_hdr_dirs, f)) != nullptr) + add_groups (false); + } + else + { + // While according to [compliance] a freestanding implementation + // should provide a subset of headers, including <initializer_list>, + // there seem to be cases where no headers are provided at all (see GH + // issue #219). So if we cannot find <initializer_list>, we just skip + // the whole thing. + // + p = hs.insert_angle (sys_hdr_dirs, std_importable[0]); + + if (p != nullptr) + { + assert (p != nullptr); + + add_groups (true); + + dir_path d (p->first.directory ()); + + auto add_header = [&hs, &d, &p, add_groups] (const char* f, bool imp) + { + path fp (d); + fp.combine (f + 1, strlen (f) - 2, '\0'); // Assuming simple. + + p = &hs.insert_angle (move (fp), f); + add_groups (imp); + }; + + for (size_t i (1); + i != sizeof (std_importable) / sizeof (std_importable[0]); + ++i) + add_header (std_importable[i], true); + + for (const char* f: std_non_importable) + add_header (f, false); + } + } + } } } diff --git a/libbuild2/cc/guess.hxx b/libbuild2/cc/guess.hxx index 868e925..7cbbd87 100644 --- a/libbuild2/cc/guess.hxx +++ b/libbuild2/cc/guess.hxx @@ -173,6 +173,9 @@ namespace build2 // search paths (similar to the PATH environment variable), in which case // it will end with a directory separator but will not contain '*'. // + // Watch out for the environment variables affecting any of the extracted + // information (like sys_*_dirs) since we cache it. + // struct compiler_info { process_path path; @@ -234,8 +237,14 @@ namespace build2 // entries, if extracted at the guess stage. // optional<pair<dir_paths, size_t>> sys_lib_dirs; - optional<pair<dir_paths, size_t>> sys_inc_dirs; + optional<pair<dir_paths, size_t>> sys_hdr_dirs; optional<pair<dir_paths, size_t>> sys_mod_dirs; + + // Optional list of environment variables that affect the compiler and + // its target platform. + // + const char* const* compiler_environment; + const char* const* platform_environment; }; // In a sense this is analagous to the language standard which we handle @@ -244,8 +253,10 @@ namespace build2 // that most of it will be the same, at least for C and C++. // const compiler_info& - guess (const char* xm, // Module (for var names in diagnostics). + guess (context&, + const char* xm, // Module (for var names in diagnostics). lang xl, // Language. + const string& ec, // Environment checksum. const path& xc, // Compiler path. const string* xi, // Compiler id (optional). const string* xv, // Compiler version (optional). @@ -265,6 +276,16 @@ namespace build2 const string& cid, const string& pattern, const strings& mode); + + // Insert importable/non-importable C++ standard library headers + // ([headers]/4). + // + // Note that the importable_headers instance should be unique-locked. + // + void + guess_std_importable_headers (const compiler_info&, + const dir_paths& sys_hdr_dirs, + importable_headers&); } } diff --git a/libbuild2/cc/init.cxx b/libbuild2/cc/init.cxx index bb50d07..e124450 100644 --- a/libbuild2/cc/init.cxx +++ b/libbuild2/cc/init.cxx @@ -86,7 +86,10 @@ namespace build2 // Enter variables. // - auto& vp (rs.var_pool ()); + // All the variables we enter are qualified so go straight for the + // public variable pool. + // + auto& vp (rs.var_pool (true /* public */)); auto v_t (variable_visibility::target); @@ -98,17 +101,33 @@ namespace build2 vp.insert<strings> ("config.cc.aoptions"); vp.insert<strings> ("config.cc.libs"); + vp.insert<string> ("config.cc.internal.scope"); + + vp.insert<bool> ("config.cc.reprocess"); // See cc.preprocess below. + + vp.insert<abs_dir_path> ("config.cc.pkgconfig.sysroot"); + vp.insert<strings> ("cc.poptions"); vp.insert<strings> ("cc.coptions"); vp.insert<strings> ("cc.loptions"); vp.insert<strings> ("cc.aoptions"); vp.insert<strings> ("cc.libs"); + vp.insert<string> ("cc.internal.scope"); + vp.insert<strings> ("cc.internal.libs"); + vp.insert<strings> ("cc.export.poptions"); vp.insert<strings> ("cc.export.coptions"); vp.insert<strings> ("cc.export.loptions"); vp.insert<vector<name>> ("cc.export.libs"); - vp.insert<vector<name>> ("cc.export.imp_libs"); + vp.insert<vector<name>> ("cc.export.impl_libs"); + + // Header (-I) and library (-L) search paths to use in the generated .pc + // files instead of the default install.{include,lib}. Relative paths + // are resolved as install paths. + // + vp.insert<dir_paths> ("cc.pkgconfig.include"); + vp.insert<dir_paths> ("cc.pkgconfig.lib"); // Hint variables (not overridable). // @@ -123,15 +142,23 @@ namespace build2 vp.insert<string> ("cc.runtime"); vp.insert<string> ("cc.stdlib"); - // Target type, for example, "C library" or "C++ library". Should be set - // on the target as a rule-specific variable by the matching rule to the - // name of the module (e.g., "c", "cxx"). Currenly only set for - // libraries and is used to decide which *.libs to use during static - // linking. + // Library target type in the <lang>[,<type>...] form where <lang> is + // "c" (C library), "cxx" (C++ library), or "cc" (C-common library but + // the specific language is not known). Currently recognized <type> + // values are "binless" (library is binless) and "recursively-binless" + // (library and all its prerequisite libraries are binless). Note that + // another indication of a binless library is an empty path, which could + // be easier/faster to check. Note also that there should be no + // whitespaces of any kind and <lang> is always first. // - // It can also be the special "cc" value which means a C-common library - // but specific language is not known. Used in the import installed - // logic. + // This value should be set on the library target as a rule-specific + // variable by the matching rule. It is also saved in the generated + // pkg-config files. Currently <lang> is used to decide which *.libs to + // use during static linking. The "cc" language is used in the import + // installed logic. + // + // Note that this variable cannot be set via the target type/pattern- + // specific mechanism (see process_libraries()). // vp.insert<string> ("cc.type", v_t); @@ -146,11 +173,25 @@ namespace build2 // vp.insert<string> ("cc.module_name", v_t); + // Importable header marker (normally set via the x.importable alias). + // + // Note that while at first it might seem like a good idea to allow + // setting it on a scope, that will cause translation of inline/template + // includes which is something we definitely don't want. + // + vp.insert<bool> ("cc.importable", v_t); + // Ability to disable using preprocessed output for compilation. // - vp.insert<bool> ("config.cc.reprocess"); vp.insert<bool> ("cc.reprocess"); + // Execute serially with regards to any other recipe. This is primarily + // useful when compiling large translation units or linking large + // binaries that require so much memory that doing that in parallel with + // other compilation/linking jobs is likely to summon the OOM killer. + // + vp.insert<bool> ("cc.serialize"); + // Register scope operation callback. // // It feels natural to clean up sidebuilds as a post operation but that @@ -287,17 +328,45 @@ namespace build2 rs.assign ("cc.libs") += cast_null<strings> ( lookup_config (rs, "config.cc.libs", nullptr)); + // config.cc.internal.scope + // + // Note: save omitted. + // + if (lookup l = lookup_config (rs, "config.cc.internal.scope")) + { + if (cast<string> (l) == "current") + fail << "'current' value in config.cc.internal.scope"; + + // This is necessary in case we are acting as bundle amalgamation. + // + rs.assign ("cc.internal.scope") = *l; + } + + // config.cc.reprocess + // + // Note: save omitted. + // if (lookup l = lookup_config (rs, "config.cc.reprocess")) rs.assign ("cc.reprocess") = *l; + // config.cc.pkgconfig.sysroot + // + // Let's look it up instead of just marking for saving to make sure the + // path is valid. + // + // Note: save omitted. + // + lookup_config (rs, "config.cc.pkgconfig.sysroot"); + // Load the bin.config module. // if (!cast_false<bool> (rs["bin.config.loaded"])) { - // Prepare configuration hints. They are only used on the first load - // of bin.config so we only populate them on our first load. + // Prepare configuration hints (pretend it belongs to root scope). + // They are only used on the first load of bin.config so we only + // populate them on our first load. // - variable_map h (rs.ctx); + variable_map h (rs); if (first) { @@ -332,15 +401,17 @@ namespace build2 } } - // Load bin.*.config for bin.* modules we may need (see core_init() - // below). + // Load bin.* modules we may need (see core_init() below). // const string& tsys (cast<string> (rs["cc.target.system"])); load_module (rs, rs, "bin.ar.config", loc); if (tsys == "win32-msvc") + { load_module (rs, rs, "bin.ld.config", loc); + load_module (rs, rs, "bin.def", loc); + } if (tsys == "mingw32") load_module (rs, rs, "bin.rc.config", loc); diff --git a/libbuild2/cc/install-rule.cxx b/libbuild2/cc/install-rule.cxx index dc10543..6758e03 100644 --- a/libbuild2/cc/install-rule.cxx +++ b/libbuild2/cc/install-rule.cxx @@ -18,21 +18,69 @@ namespace build2 { using namespace bin; + using posthoc_prerequisite_target = + context::posthoc_target::prerequisite_target; + // install_rule // install_rule:: install_rule (data&& d, const link_rule& l) : common (move (d)), link_ (l) {} - const target* install_rule:: - filter (action a, const target& t, prerequisite_iterator& i) const + // Wrap the file_rule's recipe into a data-carrying recipe. + // + struct install_match_data + { + build2::recipe recipe; + uint64_t options; // Match options. + link_rule::libs_paths libs_paths; + + target_state + operator() (action a, const target& t) + { + return recipe (a, t); + } + }; + + bool install_rule:: + filter (action a, const target& t, const target& m) const + { + if (!t.is_a<exe> ()) + { + // If runtime-only, filter out all known buildtime target types. + // + const auto& md (t.data<install_match_data> (a)); + + if ((md.options & lib::option_install_buildtime) == 0) + { + if (m.is_a<liba> () || // Staic library. + m.is_a<pc> () || // pkg-config file. + m.is_a<libi> ()) // Import library. + return false; + } + } + + return true; + } + + pair<const target*, uint64_t> install_rule:: + filter (const scope* is, + action a, const target& t, prerequisite_iterator& i, + match_extra& me) const { // NOTE: see libux_install_rule::filter() if changing anything here. const prerequisite& p (i->prerequisite); - // If this is a shared library prerequisite, install it as long as it - // is in the same amalgamation as we are. + uint64_t options (match_extra::all_options); + + otype ot (link_type (t).type); + + // @@ TMP: drop eventually. + // +#if 0 + // If this is a shared library prerequisite, install it as long as it is + // in the installation scope. // // Less obvious: we also want to install a static library prerequisite // of a library (since it could be referenced from its .pc file, etc). @@ -42,10 +90,14 @@ namespace build2 // // Note: we install ad hoc prerequisites by default. // - otype ot (link_type (t).type); + // Note: at least one must be true since we only register this rule for + // exe{}, and lib[as]{} (this makes sure the following if-condition will + // always be true for libx{}). + // bool st (t.is_a<exe> () || t.is_a<libs> ()); // Target needs shared. bool at (t.is_a<liba> () || t.is_a<libs> ()); // Target needs static. + assert (st || at); if ((st && (p.is_a<libx> () || p.is_a<libs> ())) || (at && (p.is_a<libx> () || p.is_a<liba> ()))) @@ -58,26 +110,115 @@ namespace build2 if (const libx* l = pt->is_a<libx> ()) pt = link_member (*l, a, link_info (t.base_scope (), ot)); - // Note: not redundant since we are returning a member. + // Note: not redundant since we could be returning a member. // if ((st && pt->is_a<libs> ()) || (at && pt->is_a<liba> ())) - return pt->in (t.weak_scope ()) ? pt : nullptr; + { + // Adjust match options. + // + if (a.operation () != update_id) + { + if (t.is_a<exe> ()) + options = lib::option_install_runtime; + else + { + // This is a library prerequisite of a library target and + // runtime-only begets runtime-only. + // + if (me.cur_options == lib::option_install_runtime) + options = lib::option_install_runtime; + } + } + + return make_pair (is == nullptr || pt->in (*is) ? pt : nullptr, + options); + } // See through to libu*{} members. Note that we are always in the same // project (and thus amalgamation). // if (pt->is_a<libux> ()) - return pt; + { + // Adjust match options (similar to above). + // + if (a.operation () != update_id && !pt->is_a<libue> ()) + { + if (t.is_a<exe> ()) + options = lib::option_install_runtime; + else + { + if (me.cur_options == lib::option_install_runtime) + options = lib::option_install_runtime; + } + } + + return make_pair (pt, options); + } + } +#else + // Note that at first it may seem like we don't need to install static + // library prerequisites of executables. But such libraries may still + // have prerequisites that are needed at runtime (say, some data files). + // So we install all libraries as long as they are in the installation + // scope and deal with runtime vs buildtime distiction using match + // options. + // + // Note: for now we assume these prerequisites never come from see- + // through groups. + // + // Note: we install ad hoc prerequisites by default. + // + if (p.is_a<libx> () || p.is_a<libs> () || p.is_a<liba> ()) + { + const target* pt (&search (t, p)); + + // If this is the lib{}/libu*{} group, pick a member which we would + // link. For libu*{} we want the "see through" logic. + // + if (const libx* l = pt->is_a<libx> ()) + pt = link_member (*l, a, link_info (t.base_scope (), ot)); + + // Adjust match options. + // + if (a.operation () != update_id) + { + if (t.is_a<exe> ()) + options = lib::option_install_runtime; + else + { + // This is a library prerequisite of a library target and + // runtime-only begets runtime-only. + // + if (me.cur_options == lib::option_install_runtime) + options = lib::option_install_runtime; + } + } + + // Note: not redundant since we could be returning a member. + // + if (pt->is_a<libs> () || pt->is_a<liba> ()) + { + return make_pair (is == nullptr || pt->in (*is) ? pt : nullptr, + options); + } + else // libua{} or libus{} + { + // See through to libu*{} members. Note that we are always in the + // same project (and thus amalgamation). + // + return make_pair (pt, options); + } } +#endif // The rest of the tests only succeed if the base filter() succeeds. // - const target* pt (file_rule::filter (a, t, p)); + const target* pt (file_rule::filter (is, a, t, p, me).first); if (pt == nullptr) - return pt; + return make_pair (pt, options); - // Don't install executable's prerequisite headers and module - // interfaces. + // Don't install executable's or runtime-only library's prerequisite + // headers and module interfaces. // // Note that if they come from a group, then we assume the entire // group is not to be installed. @@ -87,16 +228,22 @@ namespace build2 // auto header_source = [this] (const auto& p) { - return (x_header (p) || - p.is_a (x_src) || - (x_mod != nullptr && p.is_a (*x_mod))); + return (x_header (p) || + p.is_a (x_src) || + p.is_a (c::static_type) || + p.is_a (S::static_type) || + (x_mod != nullptr && p.is_a (*x_mod)) || + (x_obj != nullptr && (p.is_a (*x_obj) || + p.is_a (m::static_type)))); }; - if (t.is_a<exe> ()) + if (t.is_a<exe> () || + (a.operation () != update_id && + me.cur_options == lib::option_install_runtime)) { if (header_source (p)) pt = nullptr; - else if (p.type.see_through) + else if (p.type.see_through ()) { for (i.enter_group (); i.group (); ) { @@ -107,7 +254,7 @@ namespace build2 } if (pt == nullptr) - return pt; + return make_pair (pt, options); } // Here is a problem: if the user spells the obj*/bmi*{} targets @@ -137,76 +284,180 @@ namespace build2 { pt = t.is_a<exe> () ? nullptr - : file_rule::filter (a, *pt, pm.prerequisite); + : file_rule::filter (is, a, *pt, pm.prerequisite, me).first; break; } } if (pt == nullptr) - return pt; + return make_pair (pt, options); } - return pt; + return make_pair (pt, options); } bool install_rule:: - match (action a, target& t, const string& hint) const + match (action a, target& t, const string&, match_extra& me) const { - // @@ How do we split the hint between the two? - // - // We only want to handle installation if we are also the ones building // this target. So first run link's match(). // - return link_.match (a, t, hint) && file_rule::match (a, t, ""); + return link_.sub_match (x_link, update_id, a, t, me) && + file_rule::match (a, t); } recipe install_rule:: - apply (action a, target& t) const + apply (action a, target& t, match_extra& me) const { - recipe r (file_rule::apply (a, t)); + // Handle match options. + // + // Do it before calling apply_impl() since we need this information + // in the filter() callbacks. + // + if (a.operation () != update_id) + { + if (!t.is_a<exe> ()) + { + if (me.new_options == 0) + me.new_options = lib::option_install_runtime; // Minimum we can do. + + me.cur_options = me.new_options; + } + } + + recipe r (file_rule::apply_impl ( + a, t, me, + me.cur_options != match_extra::all_options /* reapply */)); + + if (r == nullptr) + { + me.cur_options = match_extra::all_options; // Noop for all options. + return noop_recipe; + } if (a.operation () == update_id) { // Signal to the link rule that this is update for install. And if the // update has already been executed, verify it was done for install. // - auto& md (t.data<link_rule::match_data> ()); + auto& md (t.data<link_rule::match_data> (a.inner_action ())); if (md.for_install) { + // Note: see also append_libraries() for the other half. + // if (!*md.for_install) - fail << "target " << t << " already updated but not for install"; + fail << "incompatible " << t << " build" << + info << "target already built not for install"; } else md.for_install = true; } else // install or uninstall { - // Derive shared library paths and cache them in the target's aux - // storage if we are un/installing (used in the *_extra() functions - // below). - // - static_assert (sizeof (link_rule::libs_paths) <= target::data_size, - "insufficient space"); - - if (file* f = t.is_a<libs> ()) + file* ls; + if ((ls = t.is_a<libs> ()) || t.is_a<liba> ()) { - if (!f->path ().empty ()) // Not binless. + // Derive shared library paths and cache them in the target's aux + // storage if we are un/installing (used in the *_extra() functions + // below). + // + link_rule::libs_paths lsp; + if (ls != nullptr && !ls->path ().empty ()) // Not binless. { const string* p (cast_null<string> (t["bin.lib.prefix"])); const string* s (cast_null<string> (t["bin.lib.suffix"])); - t.data ( - link_.derive_libs_paths (*f, - p != nullptr ? p->c_str (): nullptr, - s != nullptr ? s->c_str (): nullptr)); + + lsp = link_.derive_libs_paths (*ls, + p != nullptr ? p->c_str (): nullptr, + s != nullptr ? s->c_str (): nullptr); } + + return install_match_data {move (r), me.cur_options, move (lsp)}; } } return r; } + void install_rule:: + apply_posthoc (action a, target& t, match_extra& me) const + { + // Similar semantics to filter() above for shared libraries specified as + // post hoc prerequisites (e.g., plugins). + // + if (a.operation () != update_id) + { + for (posthoc_prerequisite_target& p: *me.posthoc_prerequisite_targets) + { + if (p.target != nullptr && p.target->is_a<libs> ()) + { + if (t.is_a<exe> ()) + p.match_options = lib::option_install_runtime; + else + { + if (me.cur_options == lib::option_install_runtime) + p.match_options = lib::option_install_runtime; + } + } + } + } + } + + void install_rule:: + reapply (action a, target& t, match_extra& me) const + { + tracer trace ("cc::install_rule::reapply"); + + assert (a.operation () != update_id && !t.is_a<exe> ()); + + l6 ([&]{trace << "rematching " << t + << ", current options " << me.cur_options + << ", new options " << me.new_options;}); + + me.cur_options |= me.new_options; + + // We also need to update options in install_match_data. + // + t.data<install_match_data> (a).options = me.cur_options; + + if ((me.new_options & lib::option_install_buildtime) != 0) + { + // If we are rematched with the buildtime option, propagate it to our + // prerequisite libraries. + // + for (const target* pt: t.prerequisite_targets[a]) + { + if (pt != nullptr && (pt->is_a<liba> () || pt->is_a<libs> () || + pt->is_a<libua> () || pt->is_a<libus> ())) + { + // Go for all options instead of just install_buildtime to avoid + // any further relocking/reapply (we only support runtime-only or + // everything). + // + rematch_sync (a, *pt, match_extra::all_options); + } + } + + // Also to post hoc. + // + if (me.posthoc_prerequisite_targets != nullptr) + { + for (posthoc_prerequisite_target& p: *me.posthoc_prerequisite_targets) + { + if (p.target != nullptr && p.target->is_a<libs> ()) + { + p.match_options = match_extra::all_options; + } + } + } + + // Also match any additional prerequisites (e.g., headers). + // + file_rule::reapply_impl (a, t, me); + } + } + bool install_rule:: install_extra (const file& t, const install_dir& id) const { @@ -214,14 +465,19 @@ namespace build2 if (t.is_a<libs> ()) { + const auto& md (t.data<install_match_data> (perform_install_id)); + // Here we may have a bunch of symlinks that we need to install. // + // Note that for runtime-only install we only omit the name that is + // used for linking (e.g., libfoo.so). + // const scope& rs (t.root_scope ()); - auto& lp (t.data<link_rule::libs_paths> ()); + const link_rule::libs_paths& lp (md.libs_paths); - auto ln = [&rs, &id] (const path& f, const path& l) + auto ln = [&t, &rs, &id] (const path& f, const path& l) { - install_l (rs, id, f.leaf (), l.leaf (), 2 /* verbosity */); + install_l (rs, id, l.leaf (), t, f.leaf (), 2 /* verbosity */); return true; }; @@ -235,7 +491,10 @@ namespace build2 if (!in.empty ()) {r = ln (*f, in) || r; f = ∈} if (!so.empty ()) {r = ln (*f, so) || r; f = &so;} if (!ld.empty ()) {r = ln (*f, ld) || r; f = &ld;} - if (!lk.empty ()) {r = ln (*f, lk) || r; } + if ((md.options & lib::option_install_buildtime) != 0) + { + if (!lk.empty ()) {r = ln (*f, lk) || r;} + } } return r; @@ -248,14 +507,16 @@ namespace build2 if (t.is_a<libs> ()) { + const auto& md (t.data<install_match_data> (perform_uninstall_id)); + // Here we may have a bunch of symlinks that we need to uninstall. // const scope& rs (t.root_scope ()); - auto& lp (t.data<link_rule::libs_paths> ()); + const link_rule::libs_paths& lp (md.libs_paths); - auto rm = [&rs, &id] (const path& l) + auto rm = [&rs, &id] (const path& f, const path& l) { - return uninstall_f (rs, id, nullptr, l.leaf (), 2 /* verbosity */); + return uninstall_l (rs, id, l.leaf (), f.leaf (), 2 /* verbosity */); }; const path& lk (lp.link); @@ -263,10 +524,15 @@ namespace build2 const path& so (lp.soname); const path& in (lp.interm); - if (!lk.empty ()) r = rm (lk) || r; - if (!ld.empty ()) r = rm (ld) || r; - if (!so.empty ()) r = rm (so) || r; - if (!in.empty ()) r = rm (in) || r; + const path* f (lp.real); + + if (!in.empty ()) {r = rm (*f, in) || r; f = ∈} + if (!so.empty ()) {r = rm (*f, so) || r; f = &so;} + if (!ld.empty ()) {r = rm (*f, ld) || r; f = &ld;} + if ((md.options & lib::option_install_buildtime) != 0) + { + if (!lk.empty ()) {r = rm (*f, lk) || r;} + } } return r; @@ -278,19 +544,30 @@ namespace build2 libux_install_rule (data&& d, const link_rule& l) : common (move (d)), link_ (l) {} - const target* libux_install_rule:: - filter (action a, const target& t, prerequisite_iterator& i) const + pair<const target*, uint64_t> libux_install_rule:: + filter (const scope* is, + action a, const target& t, prerequisite_iterator& i, + match_extra& me) const { + using file_rule = install::file_rule; + const prerequisite& p (i->prerequisite); + uint64_t options (match_extra::all_options); + + otype ot (link_type (t).type); + // The "see through" semantics that should be parallel to install_rule // above. In particular, here we use libue/libua/libus{} as proxies for // exe/liba/libs{} there. // - otype ot (link_type (t).type); + // @@ TMP: drop eventually. + // +#if 0 bool st (t.is_a<libue> () || t.is_a<libus> ()); // Target needs shared. bool at (t.is_a<libua> () || t.is_a<libus> ()); // Target needs static. + assert (st || at); if ((st && (p.is_a<libx> () || p.is_a<libs> ())) || (at && (p.is_a<libx> () || p.is_a<liba> ()))) @@ -301,28 +578,89 @@ namespace build2 pt = link_member (*l, a, link_info (t.base_scope (), ot)); if ((st && pt->is_a<libs> ()) || (at && pt->is_a<liba> ())) - return pt->in (t.weak_scope ()) ? pt : nullptr; + { + if (a.operation () != update_id) + { + if (t.is_a<libue> ()) + options = lib::option_install_runtime; + else + { + if (me.cur_options == lib::option_install_runtime) + options = lib::option_install_runtime; + } + } + + return make_pair (is == nullptr || pt->in (*is) ? pt : nullptr, + options); + } if (pt->is_a<libux> ()) - return pt; + { + if (a.operation () != update_id && !pt->is_a<libue> ()) + { + if (t.is_a<libue> ()) + options = lib::option_install_runtime; + else + { + if (me.cur_options == lib::option_install_runtime) + options = lib::option_install_runtime; + } + } + + return make_pair (pt, options); + } + } +#else + if (p.is_a<libx> () || p.is_a<libs> () || p.is_a<liba> ()) + { + const target* pt (&search (t, p)); + + if (const libx* l = pt->is_a<libx> ()) + pt = link_member (*l, a, link_info (t.base_scope (), ot)); + + if (a.operation () != update_id) + { + if (t.is_a<libue> ()) + options = lib::option_install_runtime; + else + { + if (me.cur_options == lib::option_install_runtime) + options = lib::option_install_runtime; + } + } + + if (pt->is_a<libs> () || pt->is_a<liba> ()) + { + return make_pair (is == nullptr || pt->in (*is) ? pt : nullptr, + options); + } + else + return make_pair (pt, options); } +#endif - const target* pt (install::file_rule::instance.filter (a, t, p)); + const target* pt (file_rule::instance.filter (is, a, t, p, me).first); if (pt == nullptr) - return pt; + return make_pair (pt, options); auto header_source = [this] (const auto& p) { - return (x_header (p) || - p.is_a (x_src) || - (x_mod != nullptr && p.is_a (*x_mod))); + return (x_header (p) || + p.is_a (x_src) || + p.is_a (c::static_type) || + p.is_a (S::static_type) || + (x_mod != nullptr && p.is_a (*x_mod)) || + (x_obj != nullptr && (p.is_a (*x_obj) || + p.is_a (m::static_type)))); }; - if (t.is_a<libue> ()) + if (t.is_a<libue> () || + (a.operation () != update_id && + me.cur_options == lib::option_install_runtime)) { if (header_source (p)) pt = nullptr; - else if (p.type.see_through) + else if (p.type.see_through ()) { for (i.enter_group (); i.group (); ) { @@ -333,7 +671,7 @@ namespace build2 } if (pt == nullptr) - return pt; + return make_pair (pt, options); } bool g (false); @@ -349,25 +687,103 @@ namespace build2 { pt = t.is_a<libue> () ? nullptr - : install::file_rule::instance.filter (a, *pt, pm.prerequisite); + : file_rule::instance.filter ( + is, a, *pt, pm.prerequisite, me).first; break; } } if (pt == nullptr) - return pt; + return make_pair (pt, options); } - return pt; + return make_pair (pt, options); } bool libux_install_rule:: - match (action a, target& t, const string& hint) const + match (action a, target& t, const string&, match_extra& me) const { // We only want to handle installation if we are also the ones building // this target. So first run link's match(). // - return link_.match (a, t, hint) && alias_rule::match (a, t, ""); + return link_.sub_match (x_link, update_id, a, t, me) && + alias_rule::match (a, t); + } + + recipe libux_install_rule:: + apply (action a, target& t, match_extra& me) const + { + if (a.operation () != update_id) + { + if (!t.is_a<libue> ()) + { + if (me.new_options == 0) + me.new_options = lib::option_install_runtime; + + me.cur_options = me.new_options; + } + } + + return alias_rule::apply_impl ( + a, t, me, me.cur_options != match_extra::all_options /* reapply */); + } + + void libux_install_rule:: + apply_posthoc (action a, target& t, match_extra& me) const + { + if (a.operation () != update_id) + { + for (posthoc_prerequisite_target& p: *me.posthoc_prerequisite_targets) + { + if (p.target != nullptr && p.target->is_a<libs> ()) + { + if (t.is_a<libue> ()) + p.match_options = lib::option_install_runtime; + else + { + if (me.cur_options == lib::option_install_runtime) + p.match_options = lib::option_install_runtime; + } + } + } + } + } + + void libux_install_rule:: + reapply (action a, target& t, match_extra& me) const + { + tracer trace ("cc::linux_install_rule::reapply"); + + assert (a.operation () != update_id && !t.is_a<libue> ()); + + l6 ([&]{trace << "rematching " << t + << ", current options " << me.cur_options + << ", new options " << me.new_options;}); + + me.cur_options |= me.new_options; + + if ((me.new_options & lib::option_install_buildtime) != 0) + { + for (const target* pt: t.prerequisite_targets[a]) + { + if (pt != nullptr && (pt->is_a<liba> () || pt->is_a<libs> () || + pt->is_a<libua> () || pt->is_a<libus> ())) + rematch_sync (a, *pt, match_extra::all_options); + } + + if (me.posthoc_prerequisite_targets != nullptr) + { + for (posthoc_prerequisite_target& p: *me.posthoc_prerequisite_targets) + { + if (p.target != nullptr && p.target->is_a<libs> ()) + { + p.match_options = match_extra::all_options; + } + } + } + + alias_rule::reapply_impl (a, t, me); + } } } } diff --git a/libbuild2/cc/install-rule.hxx b/libbuild2/cc/install-rule.hxx index 5856352..771c33b 100644 --- a/libbuild2/cc/install-rule.hxx +++ b/libbuild2/cc/install-rule.hxx @@ -20,7 +20,7 @@ namespace build2 { class link_rule; - // Installation rule for exe{} and lib*{}. Here we do: + // Installation rule for exe{} and lib[as]{}. Here we do: // // 1. Signal to the link rule that this is update for install. // @@ -28,20 +28,37 @@ namespace build2 // // 3. Extra un/installation (e.g., libs{} symlinks). // + // 4. Handling runtime/buildtime match options for lib[as]{}. + // class LIBBUILD2_CC_SYMEXPORT install_rule: public install::file_rule, virtual common { public: install_rule (data&&, const link_rule&); - virtual const target* - filter (action, const target&, prerequisite_iterator&) const override; + virtual bool + filter (action, const target&, const target&) const override; + + virtual pair<const target*, uint64_t> + filter (const scope*, + action, const target&, prerequisite_iterator&, + match_extra&) const override; + // Note: rule::match() override (with hint and match_extra). + // virtual bool - match (action, target&, const string&) const override; + match (action, target&, const string&, match_extra&) const override; + + using file_rule::match; // Make Clang happy. virtual recipe - apply (action, target&) const override; + apply (action, target&, match_extra&) const override; + + virtual void + apply_posthoc (action, target&, match_extra&) const override; + + virtual void + reapply (action, target&, match_extra&) const override; virtual bool install_extra (const file&, const install_dir&) const override; @@ -53,24 +70,40 @@ namespace build2 const link_rule& link_; }; - // Installation rule for libu*{}. + // Installation rule for libu[eas]{}. // // While libu*{} members themselves are not installable, we need to see // through them in case they depend on stuff that we need to install // (e.g., headers). Note that we use the alias_rule as a base. // - class LIBBUILD2_CC_SYMEXPORT libux_install_rule: - public install::alias_rule, - virtual common + class LIBBUILD2_CC_SYMEXPORT libux_install_rule: public install::alias_rule, + virtual common { public: libux_install_rule (data&&, const link_rule&); - virtual const target* - filter (action, const target&, prerequisite_iterator&) const override; + // Note: utility libraries currently have no ad hoc members. + virtual pair<const target*, uint64_t> + filter (const scope*, + action, const target&, prerequisite_iterator&, + match_extra&) const override; + + // Note: rule::match() override. + // virtual bool - match (action, target&, const string&) const override; + match (action, target&, const string&, match_extra&) const override; + + using alias_rule::match; // Make Clang happy. + + virtual recipe + apply (action, target&, match_extra&) const override; + + virtual void + apply_posthoc (action, target&, match_extra&) const override; + + virtual void + reapply (action, target&, match_extra&) const override; private: const link_rule& link_; diff --git a/libbuild2/cc/lexer+comment.test.testscript b/libbuild2/cc/lexer+comment.test.testscript index 358865c..381e479 100644 --- a/libbuild2/cc/lexer+comment.test.testscript +++ b/libbuild2/cc/lexer+comment.test.testscript @@ -16,6 +16,11 @@ four /** six /* */ +/* */ +/* + +*/ +/**/ EOI : cxx-comment diff --git a/libbuild2/cc/lexer+raw-string-literal.test.testscript b/libbuild2/cc/lexer+raw-string-literal.test.testscript index bca489a..a6455eb 100644 --- a/libbuild2/cc/lexer+raw-string-literal.test.testscript +++ b/libbuild2/cc/lexer+raw-string-literal.test.testscript @@ -16,6 +16,7 @@ R"X(a b)X" R"X(a\ b)X" +R""(a)"" EOI <string literal> <string literal> @@ -24,6 +25,7 @@ EOI <string literal> <string literal> <string literal> +<string literal> EOO : prefix diff --git a/libbuild2/cc/lexer.cxx b/libbuild2/cc/lexer.cxx index beeb970..d20e0dc 100644 --- a/libbuild2/cc/lexer.cxx +++ b/libbuild2/cc/lexer.cxx @@ -214,7 +214,7 @@ namespace build2 // #line <integer> [<string literal>] ... // # <integer> [<string literal>] ... // - // Also diagnose #include while at it. + // Also diagnose #include while at it if preprocessed. // if (!(c >= '0' && c <= '9')) { @@ -222,10 +222,13 @@ namespace build2 if (t.type == type::identifier) { - if (t.value == "include") - fail (l) << "unexpected #include directive"; - else if (t.value != "line") + if (t.value != "line") + { + if (preprocessed_ && t.value == "include") + fail (l) << "unexpected #include directive"; + continue; + } } else continue; @@ -734,8 +737,8 @@ namespace build2 // R"<delimiter>(<raw_characters>)<delimiter>" // // Where <delimiter> is a potentially-empty character sequence made of - // any source character but parentheses, backslash and spaces. It can be - // at most 16 characters long. + // any source character but parentheses, backslash, and spaces (in + // particular, it can be `"`). It can be at most 16 characters long. // // Note that the <raw_characters> are not processed in any way, not even // for line continuations. @@ -750,7 +753,7 @@ namespace build2 { c = geth (); - if (eos (c) || c == '\"' || c == ')' || c == '\\' || c == ' ') + if (eos (c) || c == ')' || c == '\\' || c == ' ') fail (l) << "invalid raw string literal"; if (c == '(') @@ -1108,21 +1111,18 @@ namespace build2 if (eos (c)) fail (p) << "unterminated comment"; - if (c == '*' && (c = peek ()) == '/') + if (c == '*') { - get (c); - break; + if ((c = peek ()) == '/') + { + get (c); + break; + } } - - if (c != '*' && c != '\\') + else { // Direct buffer scan. // - // Note that we should call get() prior to the direct buffer - // scan (see butl::char_scanner for details). - // - get (c); - const char* b (gptr_); const char* e (egptr_); const char* p (b); diff --git a/libbuild2/cc/lexer.hxx b/libbuild2/cc/lexer.hxx index dc392c6..17d706b 100644 --- a/libbuild2/cc/lexer.hxx +++ b/libbuild2/cc/lexer.hxx @@ -4,14 +4,16 @@ #ifndef LIBBUILD2_CC_LEXER_HXX #define LIBBUILD2_CC_LEXER_HXX -#include <libbutl/sha256.mxx> -#include <libbutl/char-scanner.mxx> +#include <libbutl/sha256.hxx> +#include <libbutl/char-scanner.hxx> #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> #include <libbuild2/diagnostics.hxx> +#include <libbuild2/cc/export.hxx> + namespace build2 { namespace cc @@ -20,13 +22,15 @@ namespace build2 // sequence of tokens returned is similar to what a real C/C++ compiler // would see from its preprocessor. // - // The input is a (partially-)preprocessed translation unit that may still - // contain comments, line continuations, and preprocessor directives such - // as #line, #pragma, but not #include (which is diagnosed). Currently, - // all preprocessor directives except #line are ignored and no values are - // saved from literals. The #line directive (and its shorthand notation) - // is recognized to provide the logical token location. Note that the - // modules-related pseudo-directives are not recognized or handled. + // The input is a potentially (partially-)preprocessed translation unit + // that may still contain comments, line continuations, and preprocessor + // directives such as #line and #pragma. If the input is said to be + // (partially-)preprocessed then #include directives are diagnosed. + // Currently, all preprocessor directives except #line are ignored and no + // values are saved from literals. The #line directive (and its shorthand + // notation) is recognized to provide the logical token location. Note + // that the modules-related pseudo-directives are not recognized or + // handled. // // While at it we also calculate the checksum of the input ignoring // comments, whitespaces, etc. This is used to detect changes that do not @@ -80,15 +84,19 @@ namespace build2 // Output the token value in a format suitable for diagnostics. // - ostream& + LIBBUILD2_CC_SYMEXPORT ostream& operator<< (ostream&, const token&); - class lexer: protected butl::char_scanner<> + class LIBBUILD2_CC_SYMEXPORT lexer: protected butl::char_scanner<> { public: - lexer (ifdstream& is, const path_name& name) + // If preprocessed is true, then assume the input is at least partially + // preprocessed and therefore should not contain #include directives. + // + lexer (ifdstream& is, const path_name& name, bool preprocessed) : char_scanner (is, false /* crlf */), name_ (name), + preprocessed_ (preprocessed), fail ("error", &name_), log_file_ (name) { @@ -173,6 +181,8 @@ namespace build2 private: const path_name& name_; + bool preprocessed_; + const fail_mark fail; // Logical file and line as set by the #line directives. Note that the diff --git a/libbuild2/cc/lexer.test.cxx b/libbuild2/cc/lexer.test.cxx index 284d592..82163fe 100644 --- a/libbuild2/cc/lexer.test.cxx +++ b/libbuild2/cc/lexer.test.cxx @@ -1,14 +1,17 @@ // file : libbuild2/cc/lexer.test.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file -#include <cassert> #include <iostream> #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> +#include <libbuild2/cc/types.hxx> #include <libbuild2/cc/lexer.hxx> +#undef NDEBUG +#include <cassert> + using namespace std; using namespace butl; @@ -62,7 +65,7 @@ namespace build2 is.open (fddup (stdin_fd ())); } - lexer l (is, in); + lexer l (is, in, true /* preprocessed */); // No use printing eos since we will either get it or loop forever. // diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index 9c0b018..08a60b9 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -3,11 +3,10 @@ #include <libbuild2/cc/link-rule.hxx> -#include <map> #include <cstdlib> // exit() #include <cstring> // strlen() -#include <libbutl/filesystem.mxx> // file_exists(), path_search() +#include <libbutl/filesystem.hxx> // file_exists(), path_search() #include <libbuild2/depdb.hxx> #include <libbuild2/scope.hxx> @@ -21,10 +20,11 @@ #include <libbuild2/bin/target.hxx> #include <libbuild2/bin/utility.hxx> +#include <libbuild2/install/utility.hxx> + #include <libbuild2/cc/target.hxx> // c, pc* #include <libbuild2/cc/utility.hxx> -using std::map; using std::exit; using namespace butl; @@ -36,13 +36,228 @@ namespace build2 using namespace bin; using build2::to_string; + bool link_rule:: + deduplicate_export_libs (const scope& bs, + const vector<name>& ns, + names& r, + vector<reference_wrapper<const name>>* seen) const + { + bool top (seen == nullptr); + + vector<reference_wrapper<const name>> seen_storage; + if (top) + seen = &seen_storage; + + // The plan is as follows: resolve the target names in ns into targets + // and then traverse their interface dependencies recursively removing + // duplicates from the list r. + // + for (auto i (ns.begin ()), e (ns.end ()); i != e; ++i) + { + if (i->pair) + { + ++i; + continue; + } + + const name& n (*i); + + if (n.qualified () || + !(n.dir.absolute () && n.dir.normalized ()) || + !(n.type == "lib" || n.type == "liba" || n.type != "libs")) + continue; + + if (!top) + { + // Check if we have already seen this library among interface + // dependencies of our interface dependencies. + // + if (find (seen->begin (), seen->end (), n) != seen->end ()) + continue; + + // Remove duplicates. Because we only consider absolute/normalized + // target names, we can just compare their names. + // + for (auto i (r.begin ()); i != r.end (); ) + { + if (i->pair) + i += 2; + else if (*i == n) + i = r.erase (i); + else + ++i; + } + + // @@ TODO: we could optimize this further by returning false if + // there are no viable candidates (e.g., only pairs/qualified/etc + // left). + // + if (r.empty ()) + return false; + } + + if (const target* t = search_existing (n, bs)) + { + // The same logic as in process_libraries(). + // + const scope& bs (t->base_scope ()); + + if (lookup l = t->lookup_original (c_export_libs, false, &bs).first) + { + if (!deduplicate_export_libs (bs, cast<vector<name>> (l), r, seen)) + return false; + } + + if (lookup l = t->lookup_original (x_export_libs, false, &bs).first) + { + if (!deduplicate_export_libs (bs, cast<vector<name>> (l), r, seen)) + return false; + } + } + + if (!top) + seen->push_back (n); + } + + return true; + } + + optional<path> link_rule:: + find_system_library (const strings& l) const + { + assert (!l.empty ()); + + // Figure out what we are looking for. + // + // See similar code in process_libraries(). + // + // @@ TODO: should we take the link order into account (but do we do + // this when we link system libraries)? + // + string n1, n2; + { + auto i (l.begin ()), e (l.end ()); + + string s (*i); + + if (tsys == "win32-msvc") + { + if (s[0] == '/') + { + // Some option (e.g., /WHOLEARCHIVE:<name>). Fall through to fail. + } + else + { + // Presumably a complete name. + // + n1 = move (s); + i++; + } + } + else + { + if (s[0] == '-') + { + // -l<name>, -l <name> (Note: not -pthread, which is system) + // + if (s[1] == 'l') + { + if (s.size () == 2) // -l <name> + { + if (i + 1 != e) + s = *++i; + else + s.clear (); + } + else // -l<name> + s.erase (0, 2); + + if (!s.empty ()) + { + i++; + + // Here we need to be consistent with search_library(). Maybe + // one day we should generalize it to be usable here (though + // here we don't need library name guessing). + // + const char* p (""); + const char* e1 (nullptr); + const char* e2 (nullptr); + + if (tclass == "windows") + { + if (tsys == "mingw32") + { + p = "lib"; + e1 = ".dll.a"; + e2 = ".a"; + } + else + { + e1 = ".dll.lib"; + e2 = ".lib"; + } + } + else + { + p = "lib"; + e1 = (tclass == "macos" ? ".dylib" : ".so"); + e2 = ".a"; + } + + n1 = p + s + e1; + n2 = e2 != nullptr ? p + s + e2 : string (); + } + } +#if 0 + // -framework <name> (Mac OS) + // + else if (tsys == "darwin" && l == "-framework") + { + // @@ TODO: maybe one day. + } +#endif + else + { + // Some other option (e.g., -Wl,--whole-archive). Fall through + // to fail. + } + } + else + { + // Presumably a complete name. + // + n1 = move (s); + i++; + } + } + + if (i != e) + fail << "unexpected library name '" << *i << "'"; + } + + path p; // Reuse the buffer. + for (const dir_path& d: sys_lib_dirs) + { + auto exists = [&p, &d] (const string& n) + { + return file_exists ((p = d, p /= n), + true /* follow_symlinks */, + true /* ignore_errors */); + }; + + if (exists (n1) || (!n2.empty () && exists (n2))) + return p; + } + + return nullopt; + } + link_rule:: link_rule (data&& d) : common (move (d)), - rule_id (string (x) += ".link 2") + rule_id (string (x) += ".link 3") { - static_assert (sizeof (match_data) <= target::data_size, - "insufficient space"); } link_rule::match_result link_rule:: @@ -67,25 +282,33 @@ namespace build2 { // If excluded or ad hoc, then don't factor it into our tests. // - if (include (a, t, p) != include_type::normal) + // Note that here we don't validate the update operation override + // value (since we may not match). Instead we do this in apply(). + // + lookup l; + if (include (a, t, p, a.operation () == update_id ? &l : nullptr) != + include_type::normal) continue; if (p.is_a (x_src) || (x_mod != nullptr && p.is_a (*x_mod)) || + (x_asp != nullptr && p.is_a (*x_asp)) || + (x_obj != nullptr && p.is_a (*x_obj)) || // Header-only X library (or library with C source and X header). (library && x_header (p, false /* c_hdr */))) { - r.seen_x = r.seen_x || true; + r.seen_x = true; } - else if (p.is_a<c> () || + else if (p.is_a<c> () || p.is_a<S> () || + (x_obj != nullptr && p.is_a<m> ()) || // Header-only C library. (library && p.is_a<h> ())) { - r.seen_c = r.seen_c || true; + r.seen_c = true; } else if (p.is_a<obj> () || p.is_a<bmi> ()) { - r.seen_obj = r.seen_obj || true; + r.seen_obj = true; } else if (p.is_a<obje> () || p.is_a<bmie> ()) { @@ -94,121 +317,131 @@ namespace build2 if (ot != otype::e) fail << p.type ().name << "{} as prerequisite of " << t; - r.seen_obj = r.seen_obj || true; + r.seen_obj = true; } else if (p.is_a<obja> () || p.is_a<bmia> ()) { if (ot != otype::a) fail << p.type ().name << "{} as prerequisite of " << t; - r.seen_obj = r.seen_obj || true; + r.seen_obj = true; } else if (p.is_a<objs> () || p.is_a<bmis> ()) { if (ot != otype::s) fail << p.type ().name << "{} as prerequisite of " << t; - r.seen_obj = r.seen_obj || true; + r.seen_obj = true; } else if (p.is_a<libul> () || p.is_a<libux> ()) { // For a unility library we look at its prerequisites, recursively. - // Since these checks are not exactly light-weight, only do them if - // we haven't already seen any X prerequisites. - // - if (!r.seen_x) - { - // This is a bit iffy: in our model a rule can only search a - // target's prerequisites if it matches. But we don't yet know - // whether we match. However, it seems correct to assume that any - // rule-specific search will always resolve to an existing target - // if there is one. So perhaps it's time to relax this restriction - // a little? Note that this fits particularly well with what we - // doing here since if there is no existing target, then there can - // be no prerequisites. - // - // Note, however, that we cannot link-up a prerequisite target - // member to its group since we are not matching this target. As - // result we have to do all the steps except for setting t.group - // and pass both member and group (we also cannot query t.group - // since it's racy). - // - const target* pg (nullptr); - const target* pt (p.search_existing ()); - - if (p.is_a<libul> ()) + // + // This is a bit iffy: in our model a rule can only search a + // target's prerequisites if it matches. But we don't yet know + // whether we match. However, it seems correct to assume that any + // rule-specific search will always resolve to an existing target if + // there is one. So perhaps it's time to relax this restriction a + // little? Note that this fits particularly well with what we are + // doing here since if there is no existing target, then there can + // be no prerequisites. + // + // Note, however, that we cannot link-up a prerequisite target + // member to its group since we are not matching this target. As + // result we have to do all the steps except for setting t.group and + // pass both member and group (we also cannot query t.group since + // it's racy). + // + const target* pg (nullptr); + const target* pt (p.search_existing ()); + + auto search = [&t, &p] (const target_type& tt) + { + return search_existing (t.ctx, p.prerequisite.key (tt)); + }; + + if (p.is_a<libul> ()) + { + if (pt != nullptr) { - if (pt != nullptr) + // If this is a group then try to pick (again, if exists) a + // suitable member. If it doesn't exist, then we will only be + // considering the group's prerequisites. + // + if (const target* pm = + link_member (pt->as<libul> (), + a, + linfo {ot, lorder::a /* unused */}, + true /* existing */)) { - // If this is a group then try to pick (again, if exists) a - // suitable member. If it doesn't exist, then we will only be - // considering the group's prerequisites. - // - if (const target* pm = - link_member (pt->as<libul> (), - a, - linfo {ot, lorder::a /* unused */}, - true /* existing */)) - { - pg = pt; - pt = pm; - } + pg = pt; + pt = pm; } - else + } + else + { + // It's possible we have no group but have a member so try that. + // + if (ot != otype::e) { - // It's possible we have no group but have a member so try - // that. - // - const target_type& tt (ot == otype::a ? libua::static_type : - ot == otype::s ? libus::static_type : - libue::static_type); - // We know this prerequisite member is a prerequisite since // otherwise the above search would have returned the member // target. // - pt = search_existing (t.ctx, p.prerequisite.key (tt)); + pt = search (ot == otype::a + ? libua::static_type + : libus::static_type); } - } - else if (!p.is_a<libue> ()) - { - // See if we also/instead have a group. - // - pg = search_existing (t.ctx, - p.prerequisite.key (libul::static_type)); + else + { + // Similar semantics to bin::link_member(): prefer static over + // shared. + // + pt = search (libua::static_type); - if (pt == nullptr) - swap (pt, pg); + if (pt == nullptr) + pt = search (libus::static_type); + } } + } + else if (!p.is_a<libue> ()) + { + // See if we also/instead have a group. + // + pg = search (libul::static_type); - if (pt != nullptr) - { - // If we are matching a target, use the original output type - // since that would be the member that we pick. - // - otype pot (pt->is_a<libul> () ? ot : link_type (*pt).type); - match_result pr (match (a, *pt, pg, pot, true /* lib */)); + if (pt == nullptr) + swap (pt, pg); + } - // Do we need to propagate any other seen_* values? Hm, that - // would in fact match with the "see-through" semantics of - // utility libraries we have in other places. - // - r.seen_x = pr.seen_x; - } - else - r.seen_lib = r.seen_lib || true; // Consider as just a library. + if (pt != nullptr) + { + // If we are matching a target, use the original output type since + // that would be the member that we pick. + // + otype pot (pt->is_a<libul> () ? ot : link_type (*pt).type); + + // Propagate values according to the "see-through" semantics of + // utility libraries. + // + r |= match (a, *pt, pg, pot, true /* lib */); } + else + r.seen_lib = true; // Consider as just a library. } else if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libs> ()) { - r.seen_lib = r.seen_lib || true; + r.seen_lib = true; } // Some other c-common header/source (say C++ in a C rule) other than - // a C header (we assume everyone can hanle that). + // a C header (we assume everyone can hanle that) or some other + // #include'able target. // - else if (p.is_a<cc> () && !(x_header (p, true /* c_hdr */))) + else if (p.is_a<cc> () && + !(x_header (p, true /* c_hdr */)) && + !p.is_a (x_inc) && !p.is_a<c_inc> ()) { r.seen_cc = true; break; @@ -219,7 +452,7 @@ namespace build2 } bool link_rule:: - match (action a, target& t, const string& hint) const + match (action a, target& t, const string& hint, match_extra&) const { // NOTE: may be called multiple times and for both inner and outer // operations (see the install rules). @@ -258,17 +491,22 @@ namespace build2 return false; } - if (!(r.seen_x || r.seen_c || r.seen_obj || r.seen_lib)) + // Sometimes we may need to have a binless library whose only purpose is + // to export dependencies on other libraries (potentially in a platform- + // specific manner; think the whole -pthread mess). So allow a library + // without any sources with a hint. + // + if (!(r.seen_x || r.seen_c || r.seen_obj || r.seen_lib || !hint.empty ())) { - l4 ([&]{trace << "no " << x_lang << ", C, or obj/lib prerequisite " - << "for target " << t;}); + l4 ([&]{trace << "no " << x_lang << ", C, obj/lib prerequisite or " + << "hint for target " << t;}); return false; } // We will only chain a C source if there is also an X source or we were // explicitly told to. // - if (r.seen_c && !r.seen_x && hint < x) + if (r.seen_c && !r.seen_x && hint.empty ()) { l4 ([&]{trace << "C prerequisite without " << x_lang << " or hint " << "for target " << t;}); @@ -330,7 +568,7 @@ namespace build2 // string ver; bool verp (true); // Platform-specific. - using verion_map = map<string, string>; + using verion_map = map<optional<string>, string>; if (const verion_map* m = cast_null<verion_map> (t["bin.lib.version"])) { // First look for the target system. @@ -347,14 +585,20 @@ namespace build2 // say "all others -- no version". // if (i == m->end ()) - i = m->find ("*"); + i = m->find (string ("*")); // Finally look for the platform-independent version. // if (i == m->end ()) { verp = false; - i = m->find (""); + + i = m->find (nullopt); + + // For backwards-compatibility. + // + if (i == m->end ()) + i = m->find (string ()); } // If we didn't find anything, fail. If the bin.lib.version was @@ -600,6 +844,15 @@ namespace build2 // if (const libul* ul = pt->is_a<libul> ()) { + // @@ Isn't libul{} member already picked or am I missing something? + // If not, then we may need the same in recursive-binless logic. + // +#if 0 + // @@ TMP hm, this hasn't actually been enabled. So may actually + // enable and see if it trips up (do git-blame for good measure). + // + assert (false); // @@ TMP (remove before 0.16.0 release) +#endif ux = &link_member (*ul, a, li)->as<libux> (); } else if ((ux = pt->is_a<libue> ()) || @@ -616,8 +869,20 @@ namespace build2 return nullptr; }; + // Given the cc.type value return true if the library is recursively + // binless. + // + static inline bool + recursively_binless (const string& type) + { + size_t p (type.find ("recursively-binless")); + return (p != string::npos && + type[p - 1] == ',' && // <lang> is first. + (type[p += 19] == '\0' || type[p] == ',')); + } + recipe link_rule:: - apply (action a, target& xt) const + apply (action a, target& xt, match_extra&) const { tracer trace (x, "link_rule::apply"); @@ -627,7 +892,11 @@ namespace build2 // Note that for_install is signalled by install_rule and therefore // can only be relied upon during execute. // - match_data& md (t.data (match_data ())); + // Note that we don't really need to set it as target data: while there + // are calls to get it, they should only happen after the target has + // been matched. + // + match_data md (*this); const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); @@ -636,11 +905,6 @@ namespace build2 otype ot (lt.type); linfo li (link_info (bs, ot)); - // Set the library type (C, C++, etc) as rule-specific variable. - // - if (lt.library ()) - t.state[a].assign (c_type) = string (x); - bool binless (lt.library ()); // Binary-less until proven otherwise. bool user_binless (lt.library () && cast_false<bool> (t[b_binless])); @@ -648,7 +912,7 @@ namespace build2 // for binless libraries since there could be other output (e.g., .pc // files). // - inject_fsdir (a, t); + const fsdir* dir (inject_fsdir (a, t)); // Process prerequisites, pass 1: search and match prerequisite // libraries, search obj/bmi{} targets, and search targets we do rule @@ -662,7 +926,7 @@ namespace build2 // We do libraries first in order to indicate that we will execute these // targets before matching any of the obj/bmi{}. This makes it safe for // compile::apply() to unmatch them and therefore not to hinder - // parallelism. + // parallelism (or mess up for-install'ness). // // We also create obj/bmi{} chain targets because we need to add // (similar to lib{}) all the bmi{} as prerequisites to all the other @@ -686,33 +950,98 @@ namespace build2 return a.operation () == clean_id && !pt.dir.sub (rs.out_path ()); }; + bool update_match (false); // Have update during match. + auto& pts (t.prerequisite_targets[a]); size_t start (pts.size ()); for (prerequisite_member p: group_prerequisite_members (a, t)) { - include_type pi (include (a, t, p)); + // Note that we have to recognize update=match for *(update), not just + // perform(update). But only actually update for perform(update). + // + lookup l; // The `update` variable value, if any. + include_type pi ( + include (a, t, p, a.operation () == update_id ? &l : nullptr)); // We pre-allocate a NULL slot for each (potential; see clean) // prerequisite target. // pts.push_back (prerequisite_target (nullptr, pi)); - const target*& pt (pts.back ()); + auto& pto (pts.back ()); + + // Use bit 2 of prerequisite_target::include to signal update during + // match. + // + // Not that for now we only allow updating during match ad hoc and + // mark 3 (headers, etc; see below) prerequisites. + // + // By default we update during match headers and ad hoc sources (which + // are commonly marked as such because they are #include'ed). + // + optional<bool> um; + + if (l) + { + const string& v (cast<string> (l)); + + if (v == "match") + um = true; + else if (v == "execute") + um = false; + else if (v != "false" && v != "true") + { + fail << "unrecognized update variable value '" << v + << "' specified for prerequisite " << p.prerequisite; + } + } + + // Skip excluded and ad hoc (unless updated during match) on this + // pass. + // + if (pi != include_type::normal) + { + if (a == perform_update_id && pi == include_type::adhoc) + { + // By default update ad hoc headers/sources during match (see + // above). + // +#if 1 + if (!um) + um = (p.is_a (x_src) || p.is_a<c> () || p.is_a<S> () || + (x_mod != nullptr && p.is_a (*x_mod)) || + (x_obj != nullptr && (p.is_a (*x_obj) || p.is_a<m> ())) || + x_header (p, true)); +#endif + + if (*um) + { + pto.target = &p.search (t); // mark 0 + pto.include |= prerequisite_target::include_udm; + update_match = true; + } + } - if (pi != include_type::normal) // Skip excluded and ad hoc. continue; + } + + const target*& pt (pto); - // Mark: - // 0 - lib + // Mark (2 bits): + // + // 0 - lib or update during match // 1 - src // 2 - mod - // 3 - obj/bmi and also lib not to be cleaned + // 3 - obj/bmi and also lib not to be cleaned (and other stuff) // - uint8_t m (0); + uint8_t mk (0); bool mod (x_mod != nullptr && p.is_a (*x_mod)); + bool hdr (false); - if (mod || p.is_a (x_src) || p.is_a<c> ()) + if (mod || + p.is_a (x_src) || p.is_a<c> () || p.is_a<S> () || + (x_obj != nullptr && (p.is_a (*x_obj) || p.is_a<m> ()))) { binless = binless && (mod ? user_binless : false); @@ -763,8 +1092,8 @@ namespace build2 // be the group -- we will pick a member in part 2 below. // pair<target&, ulock> r ( - search_locked ( - t, rtt, d, dir_path (), *cp.tk.name, nullptr, cp.scope)); + search_new_locked ( + ctx, rtt, d, dir_path (), *cp.tk.name, nullptr, cp.scope)); // If we shouldn't clean obj{}, then it is fair to assume we // shouldn't clean the source either (generated source will be in @@ -800,7 +1129,7 @@ namespace build2 } pt = &r.first; - m = mod ? 2 : 1; + mk = mod ? 2 : 1; } else if (p.is_a<libx> () || p.is_a<liba> () || @@ -809,12 +1138,8 @@ namespace build2 { // Handle imported libraries. // - // Note that since the search is rule-specific, we don't cache the - // target in the prerequisite. - // if (p.proj ()) - pt = search_library ( - a, sys_lib_dirs, usr_lib_dirs, p.prerequisite); + pt = search_library (a, sys_lib_dirs, usr_lib_dirs, p.prerequisite); // The rest is the same basic logic as in search_and_match(). // @@ -822,13 +1147,17 @@ namespace build2 pt = &p.search (t); if (skip (*pt)) - m = 3; // Mark so it is not matched. + mk = 3; // Mark so it is not matched. // If this is the lib{}/libul{} group, then pick the appropriate - // member. + // member. Also note this in prerequisite_target::include (used + // by process_libraries()). // if (const libx* l = pt->is_a<libx> ()) + { pt = link_member (*l, a, li); + pto.include |= include_group; + } } else { @@ -841,8 +1170,11 @@ namespace build2 // Windows module definition (.def). For other platforms (and for // static libraries) treat it as an ordinary prerequisite. // - else if (p.is_a<def> () && tclass == "windows" && ot != otype::a) + else if (p.is_a<def> ()) { + if (tclass != "windows" || ot == otype::a) + continue; + pt = &p.search (t); } // @@ -852,11 +1184,14 @@ namespace build2 // else { - if (!p.is_a<objx> () && !p.is_a<bmix> ()) + if (!p.is_a<objx> () && + !p.is_a<bmix> () && + !(hdr = x_header (p, true))) { // @@ Temporary hack until we get the default outer operation // for update. This allows operations like test and install to - // skip such tacked on stuff. + // skip such tacked on stuff. @@ This doesn't feel temporary + // anymore... // // Note that ad hoc inputs have to be explicitly marked with the // include=adhoc prerequisite-specific variable. @@ -866,6 +1201,12 @@ namespace build2 } pt = &p.search (t); + + if (pt == dir) + { + pt = nullptr; + continue; + } } if (skip (*pt)) @@ -884,21 +1225,58 @@ namespace build2 !pt->is_a<hbmix> () && cast_false<bool> ((*pt)[b_binless]))); - m = 3; + mk = 3; } if (user_binless && !binless) fail << t << " cannot be binless due to " << p << " prerequisite"; - mark (pt, m); + // Upgrade update during match prerequisites to mark 0 (see above for + // details). + // + if (a == perform_update_id) + { + // By default update headers during match (see above). + // +#if 1 + if (!um) + um = hdr; +#endif + + if (*um) + { + if (mk != 3) + fail << "unable to update during match prerequisite " << p << + info << "updating this type of prerequisites during match is " + << "not supported by this rule"; + + mk = 0; + pto.include |= prerequisite_target::include_udm; + update_match = true; + } + } + + mark (pt, mk); } - // Match lib{} (the only unmarked) in parallel and wait for completion. + // Match lib{} first and then update during match (the only unmarked) in + // parallel and wait for completion. We need to match libraries first + // because matching generated headers/sources may lead to matching some + // of the libraries (for example, if generation requires some of the + // metadata; think poptions needed by Qt moc). // - match_members (a, t, pts, start); + { + auto mask (prerequisite_target::include_udm); + + match_members (a, t, pts, start, {mask, 0}); + + if (update_match) + match_members (a, t, pts, start, {mask, mask}); + } // Check if we have any binful utility libraries. // + bool rec_binless (false); // Recursively-binless. if (binless) { if (const libux* l = find_binful (a, t, li)) @@ -909,8 +1287,128 @@ namespace build2 fail << t << " cannot be binless due to binful " << *l << " prerequisite"; } + + // See if we are recursively-binless. + // + if (binless) + { + rec_binless = true; + + for (const target* pt: t.prerequisite_targets[a]) + { + if (pt == nullptr || unmark (pt) != 0) // See above. + continue; + + const file* ft; + if ((ft = pt->is_a<libs> ()) || + (ft = pt->is_a<liba> ()) || + (ft = pt->is_a<libux> ())) + { + if (ft->path ().empty ()) // Binless. + { + // The same lookup as in process_libraries(). + // + if (const string* t = cast_null<string> ( + ft->state[a].lookup_original ( + c_type, true /* target_only */).first)) + { + if (recursively_binless (*t)) + continue; + } + } + + rec_binless = false; + break; + } + } + + // Another thing we must check is for the presence of any simple + // libraries (-lm, shell32.lib, etc) in *.export.libs. See + // process_libraries() for details. + // + if (rec_binless) + { + auto find = [&t, &bs] (const variable& v) -> lookup + { + return t.lookup_original (v, false, &bs).first; + }; + + auto has_simple = [] (lookup l) + { + if (const auto* ns = cast_null<vector<name>> (l)) + { + for (auto i (ns->begin ()), e (ns->end ()); i != e; ++i) + { + if (i->pair) + ++i; + else if (i->simple ()) // -l<name>, etc. + return true; + } + } + + return false; + }; + + if (lt.shared_library ()) // process_libraries()::impl == false + { + if (has_simple (find (x_export_libs)) || + has_simple (find (c_export_libs))) + rec_binless = false; + } + else // process_libraries()::impl == true + { + lookup x (find (x_export_impl_libs)); + lookup c (find (c_export_impl_libs)); + + if (x.defined () || c.defined ()) + { + if (has_simple (x) || has_simple (c)) + rec_binless = false; + } + else + { + // These are strings and we assume if either is defined and + // not empty, then we have simple libraries. + // + if (((x = find (x_libs)) && !x->empty ()) || + ((c = find (c_libs)) && !c->empty ())) + rec_binless = false; + } + } + } + } } + // Set the library type (C, C++, binless) as rule-specific variable. + // + if (lt.library ()) + { + string v (x); + + if (rec_binless) + v += ",recursively-binless"; + else if (binless) + v += ",binless"; + + t.state[a].assign (c_type) = move (v); + } + + // If we have any update during match prerequisites, now is the time to + // update them. Note that we have to do it before any further matches + // since they may rely on these prerequisites already being updated (for + // example, object file matches may need the headers to be already + // updated). We also must do it after matching all our prerequisite + // libraries since they may generate headers that we depend upon. + // + // Note that we ignore the result and whether it renders us out of date, + // leaving it to the common execute logic in perform_update(). + // + // Note also that update_during_match_prerequisites() spoils + // prerequisite_target::data. + // + if (update_match) + update_during_match_prerequisites (trace, a, t); + // Now that we know for sure whether we are binless, derive file name(s) // and add ad hoc group members. Note that for binless we still need the // .pc member (whose name depends on the libray prefix) so we take care @@ -1053,6 +1551,41 @@ namespace build2 if (wasm.path ().empty ()) wasm.derive_path (); + + // We don't want to print this member at level 1 diagnostics. + // + wasm.state[a].assign (ctx.var_backlink) = names { + name ("group"), name ("false")}; + + // If we have -pthread then we get additional .worker.js file + // which is used for thread startup. In a somewhat hackish way we + // represent it as an exe{} member to make sure it gets installed + // next to the main .js file. + // + // @@ Note that our recommendation is to pass -pthread in *.libs + // but checking that is not straightforward (it could come from + // one of the libraries that we are linking). We could have called + // append_libraries() (similar to $x.lib_libs()) and then looked + // there. But this is quite heavy handed and it's not clear this + // is worth the trouble since the -pthread support in Emscripten + // is quite high-touch (i.e., it's not like we can write a library + // that starts some threads and then run its test as on any other + // POSIX platform). + // + if (find_option ("-pthread", cmode) || + find_option ("-pthread", t, c_loptions) || + find_option ("-pthread", t, x_loptions)) + { + exe& worker (add_adhoc_member<exe> (t, "worker.js")); + + if (worker.path ().empty ()) + worker.derive_path (); + + // We don't want to print this member at level 1 diagnostics. + // + worker.state[a].assign (ctx.var_backlink) = names { + name ("group"), name ("false")}; + } } // Add VC's .pdb. Note that we are looking for the link.exe /DEBUG @@ -1060,22 +1593,31 @@ namespace build2 // if (!binless && ot != otype::a && tsys == "win32-msvc") { - if (find_option ("/DEBUG", t, c_loptions, true) || - find_option ("/DEBUG", t, x_loptions, true)) + const string* o; + if ((o = find_option_prefix ("/DEBUG", t, c_loptions, true)) != nullptr || + (o = find_option_prefix ("/DEBUG", t, x_loptions, true)) != nullptr) { - const target_type& tt (*bs.find_target_type ("pdb")); + if (icasecmp (*o, "/DEBUG:NONE") != 0) + { + const target_type& tt (*bs.find_target_type ("pdb")); - // We call the target foo.{exe,dll}.pdb rather than just foo.pdb - // because we can have both foo.exe and foo.dll in the same - // directory. - // - file& pdb (add_adhoc_member<file> (t, tt, e)); + // We call the target foo.{exe,dll}.pdb rather than just + // foo.pdb because we can have both foo.exe and foo.dll in the + // same directory. + // + file& pdb (add_adhoc_member<file> (t, tt, e)); - // Note that the path is derived from the exe/dll path (so it - // will include the version in case of a dll). - // - if (pdb.path ().empty ()) - pdb.derive_path (t.path ()); + // Note that the path is derived from the exe/dll path (so it + // will include the version in case of a dll). + // + if (pdb.path ().empty ()) + pdb.derive_path (t.path ()); + + // We don't want to print this member at level 1 diagnostics. + // + pdb.state[a].assign (ctx.var_backlink) = names { + name ("group"), name ("false")}; + } } } @@ -1097,6 +1639,13 @@ namespace build2 // we will use its bin.lib to decide what will be installed and in // perform_update() we will confirm that it is actually installed. // + // This, of course, works only if we actually have explicit lib{}. + // But the user could only have liba{} (common in testing frameworks + // that provide main()) or only libs{} (e.g., plugin that can also + // be linked). It's also theoretically possible to have both liba{} + // and libs{} but no lib{}, in which case it feels correct not to + // generate the common file at all. + // if (ot != otype::e) { // Note that here we always use the lib name prefix, even on @@ -1108,7 +1657,13 @@ namespace build2 // Note also that the order in which we are adding these members // is important (see add_addhoc_member() for details). // - if (ot == otype::a || !link_members (rs).a) + if (operator>= (t.group->decl, target_decl::implied) // @@ VC14 + ? ot == (link_members (rs).a ? otype::a : otype::s) + : search_existing (ctx, + ot == otype::a + ? libs::static_type + : liba::static_type, + t.dir, t.out, t.name) == nullptr) { auto& pc (add_adhoc_member<pc> (t)); @@ -1141,14 +1696,13 @@ namespace build2 // exists (windows_rpath_assembly() does take care to clean it up // if not used). // -#ifdef _WIN32 - target& dir = -#endif + target& dir ( add_adhoc_member (t, fsdir::static_type, path_cast<dir_path> (t.path () + ".dlls"), t.out, - string () /* name */); + string () /* name */, + nullopt /* ext */)); // By default our backlinking logic will try to symlink the // directory and it can even be done on Windows using junctions. @@ -1162,9 +1716,15 @@ namespace build2 // Wine. So we only resort to copy-link'ing if we are running on // Windows. // + // We also don't want to print this member at level 1 diagnostics. + // + dir.state[a].assign (ctx.var_backlink) = names { #ifdef _WIN32 - dir.state[a].assign (ctx.var_backlink) = "copy"; + name ("copy"), name ("false") +#else + name ("group"), name ("false") #endif + }; } } } @@ -1186,23 +1746,24 @@ namespace build2 continue; // New mark: + // 0 - already matched // 1 - completion // 2 - verification // - uint8_t m (unmark (pt)); + uint8_t mk (unmark (pt)); - if (m == 3) // obj/bmi or lib not to be cleaned + if (mk == 3) // obj/bmi or lib not to be cleaned { - m = 1; // Just completion. + mk = 1; // Just completion. // Note that if this is a library not to be cleaned, we keep it // marked for completion (see the next phase). } - else if (m == 1 || m == 2) // Source/module chain. + else if (mk == 1 || mk == 2) // Source/module chain. { - bool mod (m == 2); + bool mod (mk == 2); // p is_a x_mod - m = 1; + mk = 1; const target& rt (*pt); bool group (!p.prerequisite.belongs (t)); // Group's prerequisite. @@ -1234,7 +1795,21 @@ namespace build2 if (!pt->has_prerequisites () && (!group || !rt.has_prerequisites ())) { - prerequisites ps {p.as_prerequisite ()}; // Source. + prerequisites ps; + + // Add source. + // + // Remove the update variable (we may have stray update=execute + // that was specified together with the header). + // + { + prerequisite pc (p.as_prerequisite ()); + + if (!pc.vars.empty ()) + pc.vars.erase (*ctx.var_update); + + ps.push_back (move (pc)); + } // Add our lib*{} (see the export.* machinery for details) and // bmi*{} (both original and chained; see module search logic) @@ -1253,7 +1828,7 @@ namespace build2 // might depend on the imported one(s) which we will never "see" // unless we start with this library. // - // Note: have similar logic in make_module_sidebuild(). + // Note: have similar logic in make_{module,header}_sidebuild(). // size_t j (start); for (prerequisite_member p: group_prerequisite_members (a, t)) @@ -1339,7 +1914,10 @@ namespace build2 // Most of the time we will have just a single source so fast- // path that case. // - if (p1.is_a (mod ? *x_mod : x_src) || p1.is_a<c> ()) + if (mod + ? p1.is_a (*x_mod) + : (p1.is_a (x_src) || p1.is_a<c> () || p1.is_a<S> () || + (x_obj != nullptr && (p1.is_a (*x_obj) || p1.is_a<m> ())))) { src = true; continue; // Check the rest of the prerequisites. @@ -1352,8 +1930,12 @@ namespace build2 p1.is_a<libx> () || p1.is_a<liba> () || p1.is_a<libs> () || p1.is_a<libux> () || p1.is_a<bmi> () || p1.is_a<bmix> () || - (p.is_a (mod ? *x_mod : x_src) && x_header (p1)) || - (p.is_a<c> () && p1.is_a<h> ())) + ((mod || + p.is_a (x_src) || + (x_asp != nullptr && p.is_a (*x_asp)) || + (x_obj != nullptr && p.is_a (*x_obj))) && x_header (p1)) || + ((p.is_a<c> () || p.is_a<S> () || + (x_obj != nullptr && p.is_a<m> ())) && p1.is_a<h> ())) continue; fail << "synthesized dependency for prerequisite " << p @@ -1366,14 +1948,14 @@ namespace build2 if (!src) fail << "synthesized dependency for prerequisite " << p << " would be incompatible with existing target " << *pt << - info << "no existing c/" << x_name << " source prerequisite" << + info << "no existing C/" << x_lang << " source prerequisite" << info << "specify corresponding " << rtt.name << "{} " << "dependency explicitly"; - m = 2; // Needs verification. + mk = 2; // Needs verification. } } - else // lib*{} + else // lib*{} or update during match { // If this is a static library, see if we need to link it whole. // Note that we have to do it after match since we rely on the @@ -1382,6 +1964,8 @@ namespace build2 bool u; if ((u = pt->is_a<libux> ()) || pt->is_a<liba> ()) { + // Note: go straight for the public variable pool. + // const variable& var (ctx.var_pool["bin.whole"]); // @@ Cache. // See the bin module for the lookup semantics discussion. Note @@ -1391,16 +1975,18 @@ namespace build2 lookup l (p.prerequisite.vars[var]); if (!l.defined ()) - l = pt->lookup_original (var, true).first; + l = pt->lookup_original (var, true /* target_only */).first; if (!l.defined ()) { - bool g (pt->group != nullptr); + const target* g (pt->group); + + target_key tk (pt->key ()); + target_key gk (g != nullptr ? g->key () : target_key {}); + l = bs.lookup_original (var, - &pt->type (), - &pt->name, - (g ? &pt->group->type () : nullptr), - (g ? &pt->group->name : nullptr)).first; + &tk, + g != nullptr ? &gk : nullptr).first; } if (l ? cast<bool> (*l) : u) @@ -1408,7 +1994,7 @@ namespace build2 } } - mark (pt, m); + mark (pt, mk); } // Process prerequisites, pass 3: match everything and verify chains. @@ -1421,10 +2007,10 @@ namespace build2 i = start; for (prerequisite_member p: group_prerequisite_members (a, t)) { - bool adhoc (pts[i].adhoc); + bool adhoc (pts[i].adhoc ()); const target*& pt (pts[i++]); - uint8_t m; + uint8_t mk; if (pt == nullptr) { @@ -1434,10 +2020,15 @@ namespace build2 continue; pt = &p.search (t); - m = 1; // Mark for completion. + mk = 1; // Mark for completion. } - else if ((m = unmark (pt)) != 0) + else { + mk = unmark (pt); + + if (mk == 0) + continue; // Already matched. + // If this is a library not to be cleaned, we can finally blank it // out. // @@ -1449,7 +2040,7 @@ namespace build2 } match_async (a, *pt, ctx.count_busy (), t[a].task_count); - mark (pt, m); + mark (pt, mk); } wg.wait (); @@ -1464,15 +2055,15 @@ namespace build2 // Skipped or not marked for completion. // - uint8_t m; - if (pt == nullptr || (m = unmark (pt)) == 0) + uint8_t mk; + if (pt == nullptr || (mk = unmark (pt)) == 0) continue; - build2::match (a, *pt); + match_complete (a, *pt); // Nothing else to do if not marked for verification. // - if (m == 1) + if (mk == 1) continue; // Finish verifying the existing dependency (which is now matched) @@ -1484,7 +2075,10 @@ namespace build2 for (prerequisite_member p1: group_prerequisite_members (a, *pt)) { - if (p1.is_a (mod ? *x_mod : x_src) || p1.is_a<c> ()) + if (mod + ? p1.is_a (*x_mod) + : (p1.is_a (x_src) || p1.is_a<c> () || p1.is_a<S> () || + (x_obj != nullptr && (p1.is_a (*x_obj) || p1.is_a<m> ())))) { // Searching our own prerequisite is ok, p1 must already be // resolved. @@ -1520,46 +2114,63 @@ namespace build2 switch (a) { - case perform_update_id: return [this] (action a, const target& t) - { - return perform_update (a, t); - }; - case perform_clean_id: return [this] (action a, const target& t) - { - return perform_clean (a, t); - }; + // Keep the recipe (which is match_data) after execution to allow the + // install rule to examine it. + // + case perform_update_id: t.keep_data (a); // Fall through. + case perform_clean_id: return md; default: return noop_recipe; // Configure update. } } + // Append (and optionally hash and detect if rendered out of data) + // libraries to link, recursively. + // void link_rule:: append_libraries (appended_libraries& ls, strings& args, + sha256* cs, bool* update, timestamp mt, const scope& bs, action a, const file& l, bool la, lflags lf, linfo li, - bool self, bool rel) const + optional<bool> for_install, bool self, bool rel, + library_cache* lib_cache) const { struct data { appended_libraries& ls; strings& args; + + sha256* cs; + const dir_path* out_root; + + bool* update; + timestamp mt; + const file& l; action a; linfo li; + optional<bool> for_install; bool rel; compile_target_types tts; - } d {ls, args, l, a, li, rel, compile_types (li.type)}; + } d {ls, args, + cs, cs != nullptr ? &bs.root_scope ()->out_path () : nullptr, + update, mt, + l, a, li, for_install, rel, compile_types (li.type)}; - auto imp = [] (const file&, bool la) + auto imp = [] (const target&, bool la) { return la; }; - auto lib = [&d, this] (const file* const* lc, - const string& p, - lflags f, - bool) + auto lib = [&d, this] ( + const target* const* lc, + const small_vector<reference_wrapper<const string>, 2>& ns, + lflags f, + const string* type, // Whole cc.type in the <lang>[,...] form. + bool) { - const file* l (lc != nullptr ? *lc : nullptr); + // Note: see also make_header_sidebuild(). + + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); // Suppress duplicates. // @@ -1575,45 +2186,33 @@ namespace build2 // that range of elements to the end of args. See GitHub issue #114 // for details. // + // One case where we can prune the graph is if the library is + // recursively-binless. It's tempting to wish that we can do the same + // just for binless, but alas that's not the case: we have to hoist + // its binful interface dependency because, for example, it must + // appear after the preceding static library of which this binless + // library is a dependency. + // // From the process_libraries() semantics we know that this callback // is always called and always after the options callbacks. // - appended_library& al (l != nullptr - ? d.ls.append (*l, d.args.size ()) - : d.ls.append (p, d.args.size ())); + appended_library* al (l != nullptr + ? &d.ls.append (*l, d.args.size ()) + : d.ls.append (ns, d.args.size ())); - if (al.end != appended_library::npos) // Closed. + if (al != nullptr && al->end != appended_library::npos) // Closed. { // Hoist the elements corresponding to this library to the end. + // Note that we cannot prune the traversal since we need to see the + // last occurrence of each library, unless the library is + // recursively-binless (in which case there will be no need to + // hoist since there can be no libraries among the elements). // - if (al.begin != al.end) - { - // Rotate to the left the subrange starting from the first element - // of this library and until the end so that the element after the - // last element of this library becomes the first element of this - // subrange. We also need to adjust begin/end of libraries - // affected by the rotation. - // - rotate (d.args.begin () + al.begin, - d.args.begin () + al.end, - d.args.end ()); - - size_t n (al.end - al.begin); + if (type != nullptr && recursively_binless (*type)) + return false; - for (appended_library& al1: d.ls) - { - if (al1.begin >= al.end) - { - al1.begin -= n; - al1.end -= n; - } - } - - al.end = d.args.size (); - al.begin = al.end - n; - } - - return; + d.ls.hoist (d.args, *al); + return true; } if (l == nullptr) @@ -1622,7 +2221,15 @@ namespace build2 // static library. // if (d.li.type != otype::a) - d.args.push_back (p); + { + for (const string& n: ns) + { + d.args.push_back (n); + + if (d.cs != nullptr) + d.cs->append (n); + } + } } else { @@ -1645,6 +2252,55 @@ namespace build2 if (!lc[i]->is_a<libux> ()) goto done; } + // If requested, verify the target and the library are both for + // install or both not. We can only do this if the library is build + // by our link_rule. + // + else if (d.for_install && + type != nullptr && + *type != "cc" && + type->compare (0, 3, "cc,") != 0) + { + auto* md (l->try_data<link_rule::match_data> (d.a)); + + if (md == nullptr) + fail << "library " << *l << " is not built with cc module-based " + << "link rule" << + info << "mark it as generic with cc.type=cc target-specific " + << "variable"; + + assert (md->for_install); // Must have been executed. + + // The user will get the target name from the context info. + // + if (*md->for_install != *d.for_install) + fail << "incompatible " << *l << " build" << + info << "library is built " << (*md->for_install ? "" : "not ") + << "for install"; + } + + auto newer = [&d, l] () + { + // @@ Work around the unexecuted member for installed libraries + // issue (see search_library() for details). + // + // Note that the member may not even be matched, let alone + // executed, so we have to go through the group to detect this + // case (if the group is not matched, then the member got to be). + // +#if 0 + return l->newer (d.mt); +#else + const target* g (l->group); + target_state s (g != nullptr && + g->matched (d.a, memory_order_acquire) && + g->state[d.a].rule == &file_rule::rule_match + ? target_state::unchanged + : l->executed_state (d.a)); + + return l->newer (d.mt, s); +#endif + }; if (d.li.type == otype::a) { @@ -1654,6 +2310,12 @@ namespace build2 // are automatically handled by process_libraries(). So all we // have to do is implement the "thin archive" logic. // + // We also don't need to do anything special for the out-of-date + // logic: If any of its object files (or the set of its object + // files) changes, then the library will have to be updated as + // well. In other words, we use the library timestamp as a proxy + // for all of its member's timestamps. + // // We may also end up trying to link a non-utility library to a // static library via a utility library (direct linking is taken // care of by perform_update()). So we cut it off here. @@ -1664,6 +2326,11 @@ namespace build2 if (l->mtime () == timestamp_unreal) // Binless. goto done; + // Check if this library renders us out of date. + // + if (d.update != nullptr) + *d.update = *d.update || newer (); + for (const target* pt: l->prerequisite_targets[d.a]) { if (pt == nullptr) @@ -1698,6 +2365,11 @@ namespace build2 if (l->mtime () == timestamp_unreal) // Binless. goto done; + // Check if this library renders us out of date. + // + if (d.update != nullptr) + *d.update = *d.update || newer (); + // On Windows a shared library is a DLL with the import library as // an ad hoc group member. MinGW though can link directly to DLLs // (see search_library() for details). @@ -1733,17 +2405,28 @@ namespace build2 d.args.push_back (move (p)); } + + if (d.cs != nullptr) + { + d.cs->append (f); + hash_path (*d.cs, l->path (), *d.out_root); + } } done: - al.end = d.args.size (); // Close. + if (al != nullptr) + al->end = d.args.size (); // Close. + + return true; }; - auto opt = [&d, this] (const file& l, + auto opt = [&d, this] (const target& lt, const string& t, bool com, bool exp) { + const file& l (lt.as<file> ()); + // Don't try to pass any loptions when linking a static library. // // Note also that we used to pass non-export loptions but that didn't @@ -1755,17 +2438,19 @@ namespace build2 // the exp checks below. // if (d.li.type == otype::a || !exp) - return; + return true; // Suppress duplicates. // if (d.ls.append (l, d.args.size ()).end != appended_library::npos) - return; + return true; // If we need an interface value, then use the group (lib{}). // if (const target* g = exp && l.is_a<libs> () ? l.group : &l) { + // Note: go straight for the public variable pool. + // const variable& var ( com ? (exp ? c_export_loptions : c_loptions) @@ -1774,135 +2459,44 @@ namespace build2 : l.ctx.var_pool[t + (exp ? ".export.loptions" : ".loptions")])); append_options (d.args, *g, var); - } - }; - - process_libraries ( - a, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, self); - } - - void link_rule:: - append_libraries (sha256& cs, bool& update, timestamp mt, - const scope& bs, action a, - const file& l, bool la, lflags lf, linfo li) const - { - // Note that we don't do any duplicate suppression here: there is no way - // to "hoist" things once they are hashed and hashing only the first - // occurrence could miss changes to the command line (e.g., due to - // "hoisting"). - - struct data - { - sha256& cs; - const dir_path& out_root; - bool& update; - timestamp mt; - linfo li; - } d {cs, bs.root_scope ()->out_path (), update, mt, li}; - - auto imp = [] (const file&, bool la) - { - return la; - }; - auto lib = [&d, this] (const file* const* lc, - const string& p, - lflags f, - bool) - { - const file* l (lc != nullptr ? *lc : nullptr); - - if (l == nullptr) - { - if (d.li.type != otype::a) - d.cs.append (p); + if (d.cs != nullptr) + append_options (*d.cs, *g, var); } - else - { - bool lu (l->is_a<libux> ()); - - if (lu) - { - for (ptrdiff_t i (-1); lc[i] != nullptr; --i) - if (!lc[i]->is_a<libux> ()) - return; - } - // We also don't need to do anything special for linking a utility - // library to a static library. If any of its object files (or the - // set of its object files) changes, then the library will have to - // be updated as well. In other words, we use the library timestamp - // as a proxy for all of its member's timestamps. - // - // We do need to cut of the static to static linking, just as in - // append_libraries(). - // - if (d.li.type == otype::a && !lu) - return; - - if (l->mtime () == timestamp_unreal) // Binless. - return; - - // Check if this library renders us out of date. - // - d.update = d.update || l->newer (d.mt); - - // On Windows a shared library is a DLL with the import library as - // an ad hoc group member. MinGW though can link directly to DLLs - // (see search_library() for details). - // - if (tclass == "windows" && l->is_a<libs> ()) - { - if (const libi* li = find_adhoc_member<libi> (*l)) - l = li; - } - - d.cs.append (f); - hash_path (d.cs, l->path (), d.out_root); - } + return true; }; - auto opt = [&d, this] (const file& l, - const string& t, - bool com, - bool exp) - { - if (d.li.type == otype::a || !exp) - return; - - if (const target* g = exp && l.is_a<libs> () ? l.group : &l) - { - const variable& var ( - com - ? (exp ? c_export_loptions : c_loptions) - : (t == x - ? (exp ? x_export_loptions : x_loptions) - : l.ctx.var_pool[t + (exp ? ".export.loptions" : ".loptions")])); - - append_options (d.cs, *g, var); - } - }; - - process_libraries ( - a, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true); + process_libraries (a, bs, li, sys_lib_dirs, + l, la, + lf, imp, lib, opt, + self, + false /* proc_opt_group */, + lib_cache); } void link_rule:: rpath_libraries (rpathed_libraries& ls, strings& args, const scope& bs, action a, const file& l, bool la, - linfo li, bool link, bool self) const + linfo li, bool link, bool self, + library_cache* lib_cache) const { // Use -rpath-link only on targets that support it (Linux, *BSD). Note // that we don't really need it for top-level libraries. // + // Note that more recent versions of FreeBSD are using LLVM lld without + // any mentioning of -rpath-link in the man pages. + // + auto have_link = [this] () {return tclass == "linux" || tclass == "bsd";}; + if (link) { - if (tclass != "linux" && tclass != "bsd") + if (!have_link ()) return; } - auto imp = [link] (const file& l, bool la) + auto imp = [link] (const target& l, bool la) { // If we are not rpath-link'ing, then we only need to rpath interface // libraries (they will include rpath's for their implementations) @@ -1928,38 +2522,116 @@ namespace build2 { rpathed_libraries& ls; strings& args; - bool link; - } d {ls, args, link}; + bool rpath; + bool rpath_link; + } d {ls, args, false, false}; - auto lib = [&d, this] (const file* const* lc, - const string& f, - lflags, - bool sys) + if (link) + d.rpath_link = true; + else { - const file* l (lc != nullptr ? *lc : nullptr); + // While one would naturally expect -rpath to be a superset of + // -rpath-link, according to GNU ld: + // + // "The -rpath option is also used when locating shared objects which + // are needed by shared objects explicitly included in the link; see + // the description of the -rpath-link option. Searching -rpath in + // this way is only supported by native linkers and cross linkers + // which have been configured with the --with-sysroot option." + // + // So we check if this is cross-compilation and request both options + // if that's the case (we have no easy way of detecting whether the + // linker has been configured with the --with-sysroot option, whatever + // that means, so we will just assume the worst case). + // + d.rpath = true; + + if (have_link ()) + { + // Detecting cross-compilation is not as easy as it seems. Comparing + // complete target triplets proved too strict. For example, we may be + // running on x86_64-apple-darwin17.7.0 while the compiler is + // targeting x86_64-apple-darwin17.3.0. Also, there is the whole i?86 + // family of CPUs which, at least for linking, should probably be + // considered the same. + // + const target_triplet& h (*bs.ctx.build_host); + const target_triplet& t (ctgt); + + auto x86 = [] (const string& c) + { + return (c.size () == 4 && + c[0] == 'i' && + (c[1] >= '3' && c[1] <= '6') && + c[2] == '8' && + c[3] == '6'); + }; + + if (t.system != h.system || + (t.cpu != h.cpu && !(x86 (t.cpu) && x86 (h.cpu)))) + d.rpath_link = true; + } + } + + auto lib = [&d, this] ( + const target* const* lc, + const small_vector<reference_wrapper<const string>, 2>& ns, + lflags, + const string*, + bool sys) + { + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); // We don't rpath system libraries. Why, you may ask? There are many // good reasons and I have them written on a napkin somewhere... // + // We also assume system libraries can only depend on other system + // libraries and so can prune the traversal. + // if (sys) - return; + return false; - if (l != nullptr) + auto append = [&d] (const string& f) { - if (!l->is_a<libs> ()) - return; + size_t p (path::traits_type::rfind_separator (f)); + assert (p != string::npos); - if (l->mtime () == timestamp_unreal) // Binless. - return; + if (d.rpath) + { + string o ("-Wl,-rpath,"); + o.append (f, 0, (p != 0 ? p : 1)); // Don't include trailing slash. + d.args.push_back (move (o)); + } + if (d.rpath_link) + { + string o ("-Wl,-rpath-link,"); + o.append (f, 0, (p != 0 ? p : 1)); + d.args.push_back (move (o)); + } + }; + + if (l != nullptr) + { // Suppress duplicates. // // We handle rpath similar to the compilation case by adding the - // options on the first occurrence and ignoring all the subsequent. + // options on the first occurrence and ignoring (and pruning) all + // the subsequent. // if (find (d.ls.begin (), d.ls.end (), l) != d.ls.end ()) - return; + return false; + + // Note that these checks are fairly expensive so we do them after + // duplicate suppression. + // + if (!l->is_a<libs> ()) + return true; + if (l->mtime () == timestamp_unreal) // Binless. + return true; + + append (ns[0]); d.ls.push_back (l); } else @@ -1969,39 +2641,32 @@ namespace build2 // better than checking for a platform-specific extension (maybe // we should cache it somewhere). // - size_t p (path::traits_type::find_extension (f)); + for (const string& f: ns) + { + size_t p (path::traits_type::find_extension (f)); - if (p == string::npos) - return; + if (p == string::npos) + break; - ++p; // Skip dot. + ++p; // Skip dot. - bool c (true); - const char* e; + bool c (true); + const char* e; - if (tclass == "windows") {e = "dll"; c = false;} - else if (tsys == "darwin") e = "dylib"; - else e = "so"; + if (tclass == "windows") {e = "dll"; c = false;} + else if (tsys == "darwin") e = "dylib"; + else e = "so"; - if ((c - ? f.compare (p, string::npos, e) - : icasecmp (f.c_str () + p, e)) != 0) - return; + if ((c + ? f.compare (p, string::npos, e) + : icasecmp (f.c_str () + p, e)) == 0) + append (f); + } } - // Ok, if we are here then it means we have a non-system, shared - // library and its absolute path is in f. - // - string o (d.link ? "-Wl,-rpath-link," : "-Wl,-rpath,"); - - size_t p (path::traits_type::rfind_separator (f)); - assert (p != string::npos); - - o.append (f, 0, (p != 0 ? p : 1)); // Don't include trailing slash. - d.args.push_back (move (o)); + return true; }; - if (self && !link && !la) { // Top-level shared library dependency. @@ -2020,7 +2685,10 @@ namespace build2 process_libraries (a, bs, li, sys_lib_dirs, l, la, 0 /* lflags */, - imp, lib, nullptr); + imp, lib, nullptr, + false /* self */, + false /* proc_opt_group */, + lib_cache); } void link_rule:: @@ -2029,6 +2697,7 @@ namespace build2 const target& t, linfo li, bool link) const { rpathed_libraries ls; + library_cache lc; for (const prerequisite_target& pt: t.prerequisite_targets[a]) { @@ -2042,16 +2711,16 @@ namespace build2 (la = (f = pt->is_a<libux> ())) || ( f = pt->is_a<libs> ())) { - rpath_libraries (ls, args, bs, a, *f, la, li, link, true); + rpath_libraries (ls, args, bs, a, *f, la, li, link, true, &lc); } } } - // Append object files of bmi{} prerequisites that belong to binless - // libraries. + // Append (and optionally hash while at it) object files of bmi{} + // prerequisites that belong to binless libraries. // void link_rule:: - append_binless_modules (strings& args, + append_binless_modules (strings& args, sha256* cs, const scope& bs, action a, const file& t) const { // Note that here we don't need to hoist anything on duplicate detection @@ -2068,25 +2737,12 @@ namespace build2 if (find (args.begin (), args.end (), p) == args.end ()) { args.push_back (move (p)); - append_binless_modules (args, bs, a, o); - } - } - } - } - void link_rule:: - append_binless_modules (sha256& cs, - const scope& bs, action a, const file& t) const - { - for (const target* pt: t.prerequisite_targets[a]) - { - if (pt != nullptr && - pt->is_a<bmix> () && - cast_false<bool> ((*pt)[b_binless])) - { - const objx& o (*find_adhoc_member<objx> (*pt)); - hash_path (cs, o.path (), bs.root_scope ()->out_path ()); - append_binless_modules (cs, bs, a, o); + if (cs != nullptr) + hash_path (*cs, o.path (), bs.root_scope ()->out_path ()); + + append_binless_modules (args, cs, bs, a, o); + } } } } @@ -2094,7 +2750,7 @@ namespace build2 // Filter link.exe noise (msvc.cxx). // void - msvc_filter_link (ifdstream&, const file&, otype); + msvc_filter_link (diag_buffer&, const file&, otype); // Translate target CPU to the link.exe/lib.exe /MACHINE option. // @@ -2102,7 +2758,7 @@ namespace build2 msvc_machine (const string& cpu); // msvc.cxx target_state link_rule:: - perform_update (action a, const target& xt) const + perform_update (action a, const target& xt, match_data& md) const { tracer trace (x, "link_rule::perform_update"); @@ -2114,8 +2770,6 @@ namespace build2 const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); - match_data& md (t.data<match_data> ()); - // Unless the outer install rule signalled that this is update for // install, signal back that we've performed plain update. // @@ -2144,14 +2798,33 @@ namespace build2 // Note that execute_prerequisites() blanks out all the ad hoc // prerequisites so we don't need to worry about them from now on. // + // There is an interesting trade-off between the straight and reverse + // execution. With straight we may end up with inaccurate progress if + // most of our library prerequisites (typically specified last) are + // already up to date. In this case, the progress will first increase + // slowly as we compile this target's source files and then jump + // straight to 100% as we "realize" that all the libraries (and all + // their prerequisites) are already up to date. + // + // Switching to reverse fixes this but messes up incremental building: + // now instead of starting to compile source files right away, we will + // first spend some time making sure all the libraries are up to date + // (which, in case of an error in the source code, will be a complete + // waste). + // + // There doesn't seem to be an easy way to distinguish between + // incremental and from-scratch builds and on balance fast incremental + // builds feel more important. + // target_state ts; - if (optional<target_state> s = - execute_prerequisites (a, - t, - mt, - [] (const target&, size_t) {return false;})) + if (optional<target_state> s = execute_prerequisites ( + a, t, + mt, + [] (const target&, size_t) {return false;})) + { ts = *s; + } else { // An ad hoc prerequisite renders us out-of-date. Let's update from @@ -2165,7 +2838,7 @@ namespace build2 // those that don't match. Note that we have to do it after updating // prerequisites to keep the dependency counts straight. // - if (const variable* var_fi = ctx.var_pool.find ("for_install")) + if (const variable* var_fi = rs.var_pool ().find ("for_install")) { // Parallel prerequisites/prerequisite_targets loop. // @@ -2191,12 +2864,20 @@ namespace build2 // (Re)generate pkg-config's .pc file. While the target itself might be // up-to-date from a previous run, there is no guarantee that .pc exists // or also up-to-date. So to keep things simple we just regenerate it - // unconditionally. + // unconditionally (and avoid doing so on uninstall; see pkgconfig_save() + // for details). // // Also, if you are wondering why don't we just always produce this .pc, // install or no install, the reason is unless and until we are updating // for install, we have no idea where-to things will be installed. // + // There is a further complication: we may have no intention of + // installing the library but still need to update it for install (see + // install_scope() for background). In which case we may still not have + // the installation directories. We handle this in pkgconfig_save() by + // skipping the generation of .pc files (and letting the install rule + // complain if we do end up trying to install them). + // if (for_install && lt.library () && !lt.utility) { bool la (lt.static_library ()); @@ -2210,8 +2891,12 @@ namespace build2 if (!m->is_a (la ? pca::static_type : pcs::static_type)) { - if (t.group->matched (a)) + if (operator>= (t.group->decl, target_decl::implied) // @@ VC14 + ? t.group->matched (a) + : true) + { pkgconfig_save (a, t, la, true /* common */, binless); + } else // Mark as non-existent not to confuse the install rule. // @@ -2323,14 +3008,19 @@ namespace build2 try { + // We assume that what we write to stdin is small enough to + // fit into the pipe's buffer without blocking. + // process pr (rc, args, - -1 /* stdin */, - 1 /* stdout */, - 2 /* stderr */, - nullptr /* cwd */, + -1 /* stdin */, + 1 /* stdout */, + diag_buffer::pipe (ctx) /* stderr */, + nullptr /* cwd */, env_ptrs.empty () ? nullptr : env_ptrs.data ()); + diag_buffer dbuf (ctx, args[0], pr); + try { ofdstream os (move (pr.out_fd)); @@ -2354,7 +3044,8 @@ namespace build2 // was caused by that and let run_finish() deal with it. } - run_finish (args, pr); + dbuf.read (); + run_finish (dbuf, args, pr, 2 /* verbosity */); } catch (const process_error& e) { @@ -2409,6 +3100,8 @@ namespace build2 { // For VC we use link.exe directly. // + // Note: go straight for the public variable pool. + // const string& cs ( cast<string> ( rs[tsys == "win32-msvc" @@ -2419,10 +3112,13 @@ namespace build2 l4 ([&]{trace << "linker mismatch forcing update of " << t;}); } - // Hash and compare any changes to the environment. + // Then the linker environment checksum (original and our modifications). // - if (dd.expect (env_cs.string ()) != nullptr) - l4 ([&]{trace << "environment mismatch forcing update of " << t;}); + { + bool e (dd.expect (env_checksum) != nullptr); + if (dd.expect (env_cs.string ()) != nullptr || e) + l4 ([&]{trace << "environment mismatch forcing update of " << t;}); + } // Next check the target. While it might be incorporated into the linker // checksum, it also might not (e.g., VC link.exe). @@ -2435,8 +3131,8 @@ namespace build2 // are to either replicate the exact process twice, first for hashing // then for building or to go ahead and start building and hash the // result. The first approach is probably more efficient while the - // second is simpler. Let's got with the simpler for now (actually it's - // kind of a hybrid). + // second is simpler. Let's got with the simpler for now (also see a + // note on the cost of library dependency graph traversal below). // cstrings args {nullptr}; // Reserve one for config.bin.ar/config.x. strings sargs; // Argument tail with storage. @@ -2499,6 +3195,9 @@ namespace build2 // probably safe to assume that the two came from the same version // of binutils/LLVM. // + // @@ Note also that GNU ar deprecated -T in favor of --thin in + // version 2.38. + // if (lt.utility) { const string& id (cast<string> (rs["bin.ar.id"])); @@ -2612,10 +3311,72 @@ namespace build2 rpath_libraries (sargs, bs, a, t, li, for_install /* link */); lookup l; - if ((l = t["bin.rpath"]) && !l->empty ()) + { + // See if we need to make the specified paths relative using the + // $ORIGIN (Linux, BSD) or @loader_path (Mac OS) mechanisms. + // + optional<dir_path> origin; + if (for_install && cast_false<bool> (rs["install.relocatable"])) + { + // Note that both $ORIGIN and @loader_path will be expanded to + // the path of the binary that we are building (executable or + // shared library) as opposed to top-level executable. + // + path p (install::resolve_file (t)); + + // If the file is not installable then the install.relocatable + // semantics does not apply, naturally. + // + if (!p.empty ()) + origin = p.directory (); + } + + bool origin_used (false); for (const dir_path& p: cast<dir_paths> (l)) - sargs.push_back ("-Wl,-rpath," + p.string ()); + { + string o ("-Wl,-rpath,"); + + // Note that we only rewrite absolute paths so if the user + // specified $ORIGIN or @loader_path manually, we will pass it + // through as is. + // + if (origin && p.absolute ()) + { + dir_path l; + try + { + l = p.relative (*origin); + } + catch (const invalid_path&) + { + fail << "unable to make rpath " << p << " relative to " + << *origin << + info << "required for relocatable installation"; + } + + o += (tclass == "macos" ? "@loader_path" : "$ORIGIN"); + + if (!l.empty ()) + { + o += path_traits::directory_separator; + o += l.string (); + } + + origin_used = true; + } + else + o += p.string (); + + sargs.push_back (move (o)); + } + + // According to the Internet, `-Wl,-z,origin` is not needed except + // potentially for older BSDs. + // + if (origin_used && tclass == "bsd") + sargs.push_back ("-Wl,-z,origin"); + } if ((l = t["bin.rpath_link"]) && !l->empty ()) { @@ -2649,25 +3410,24 @@ namespace build2 // Extra system library dirs (last). // - assert (sys_lib_dirs_extra <= sys_lib_dirs.size ()); + assert (sys_lib_dirs_mode + sys_lib_dirs_extra <= sys_lib_dirs.size ()); + + // Note that the mode options are added as part of cmode. + // + auto b (sys_lib_dirs.begin () + sys_lib_dirs_mode); + auto x (b + sys_lib_dirs_extra); if (tsys == "win32-msvc") { // If we have no LIB environment variable set, then we add all of // them. But we want extras to come first. // - // Note that the mode options are added as part of cmode. - // - auto b (sys_lib_dirs.begin () + sys_lib_dirs_mode); - auto m (sys_lib_dirs.begin () + sys_lib_dirs_extra); - auto e (sys_lib_dirs.end ()); - - for (auto i (m); i != e; ++i) + for (auto i (b); i != x; ++i) sargs1.push_back ("/LIBPATH:" + i->string ()); if (!getenv ("LIB")) { - for (auto i (b); i != m; ++i) + for (auto i (x), e (sys_lib_dirs.end ()); i != e; ++i) sargs1.push_back ("/LIBPATH:" + i->string ()); } @@ -2678,7 +3438,7 @@ namespace build2 append_option_values ( args, "-L", - sys_lib_dirs.begin () + sys_lib_dirs_extra, sys_lib_dirs.end (), + b, x, [] (const dir_path& d) {return d.string ().c_str ();}); } } @@ -2708,8 +3468,20 @@ namespace build2 // pinpoint exactly what is causing the update. On the other hand, the // checksum is faster and simpler. And we like simple. // + // Note that originally we only hashed inputs here and then re-collected + // them below. But the double traversal of the library graph proved to + // be way more expensive on libraries with lots of dependencies (like + // Boost) than both collecting and hashing in a single pass. So that's + // what we do now. @@ TODO: it would be beneficial to also merge the + // rpath pass above into this. + // + // See also a similar loop inside append_libraries(). + // + bool seen_obj (false); const file* def (nullptr); // Cached if present. { + appended_libraries als; + library_cache lc; sha256 cs; for (const prerequisite_target& p: t.prerequisite_targets[a]) @@ -2747,8 +3519,8 @@ namespace build2 ((la = (f = pt->is_a<liba> ())) || (ls = (f = pt->is_a<libs> ()))))) { - // Link all the dependent interface libraries (shared) or interface - // and implementation (static), recursively. + // Link all the dependent interface libraries (shared) or + // interface and implementation (static), recursively. // // Also check if any of them render us out of date. The tricky // case is, say, a utility library (static) that depends on a @@ -2758,12 +3530,22 @@ namespace build2 // if (la || ls) { - append_libraries (cs, update, mt, bs, a, *f, la, p.data, li); - f = nullptr; // Timestamp checked by hash_libraries(). + append_libraries (als, sargs, + &cs, &update, mt, + bs, a, *f, la, p.data, li, + for_install, true, true, &lc); + f = nullptr; // Timestamp checked by append_libraries(). } else { - hash_path (cs, f->path (), rs.out_path ()); + // Do not hoist libraries over object files since such object + // files might satisfy symbols in the preceding libraries. + // + als.clear (); + + const path& p (f->path ()); + sargs.push_back (relative (p).string ()); + hash_path (cs, p, rs.out_path ()); // @@ Do we actually need to hash this? I don't believe this set // can change without rendering the object file itself out of @@ -2771,7 +3553,9 @@ namespace build2 // marked with bin.binless manually? // if (modules) - append_binless_modules (cs, rs, a, *f); + append_binless_modules (sargs, &cs, bs, a, *f); + + seen_obj = true; } } else if ((f = pt->is_a<bin::def> ())) @@ -2839,6 +3623,10 @@ namespace build2 // path relt (relative (tp)); + path reli; // Import library. + if (lt.shared_library () && (tsys == "win32-msvc" || tsys == "mingw32")) + reli = relative (find_adhoc_member<libi> (t)->path ()); + const process_path* ld (nullptr); if (lt.static_library ()) { @@ -2970,7 +3758,7 @@ namespace build2 // derived from the import library by changing the extension. // Lucky for us -- there is no option to name it. // - out2 += relative (find_adhoc_member<libi> (t)->path ()).string (); + out2 += reli.string (); } else { @@ -2983,14 +3771,17 @@ namespace build2 // If we have /DEBUG then name the .pdb file. It is an ad hoc group // member. // - if (find_option ("/DEBUG", args, true)) + if (const char* o = find_option_prefix ("/DEBUG", args, true)) { - const file& pdb ( - *find_adhoc_member<file> (t, *bs.find_target_type ("pdb"))); + if (icasecmp (o, "/DEBUG:NONE") != 0) + { + const file& pdb ( + *find_adhoc_member<file> (t, *bs.find_target_type ("pdb"))); - out1 = "/PDB:"; - out1 += relative (pdb.path ()).string (); - args.push_back (out1.c_str ()); + out1 = "/PDB:"; + out1 += relative (pdb.path ()).string (); + args.push_back (out1.c_str ()); + } } out = "/OUT:" + relt.string (); @@ -3004,6 +3795,8 @@ namespace build2 { ld = &cpath; + append_diag_color_options (args); + // Add the option that triggers building a shared library and // take care of any extras (e.g., import library). // @@ -3019,8 +3812,7 @@ namespace build2 // On Windows libs{} is the DLL and an ad hoc group member // is the import library. // - const file& imp (*find_adhoc_member<libi> (t)); - out = "-Wl,--out-implib=" + relative (imp.path ()).string (); + out = "-Wl,--out-implib=" + reli.string (); args.push_back (out.c_str ()); } } @@ -3051,60 +3843,6 @@ namespace build2 size_t args_input (args.size ()); #endif - // The same logic as during hashing above. See also a similar loop - // inside append_libraries(). - // - bool seen_obj (false); - { - appended_libraries als; - for (const prerequisite_target& p: t.prerequisite_targets[a]) - { - const target* pt (p.target); - - if (pt == nullptr) - continue; - - if (modules) - { - if (pt->is_a<bmix> ()) - { - pt = find_adhoc_member (*pt, tts.obj); - - if (pt == nullptr) // Header BMIs have no object file. - continue; - } - } - - const file* f; - bool la (false), ls (false); - - if ((f = pt->is_a<objx> ()) || - (!lt.utility && - (la = (f = pt->is_a<libux> ()))) || - (!lt.static_library () && - ((la = (f = pt->is_a<liba> ())) || - (ls = (f = pt->is_a<libs> ()))))) - { - if (la || ls) - append_libraries (als, sargs, bs, a, *f, la, p.data, li); - else - { - // Do not hoist libraries over object files since such object - // files might satisfy symbols in the preceding libraries. - // - als.clear (); - - sargs.push_back (relative (f->path ()).string ()); - - if (modules) - append_binless_modules (sargs, bs, a, *f); - - seen_obj = true; - } - } - } - } - // For MinGW manifest is an object file. // if (!manifest.empty () && tsys == "mingw32") @@ -3231,17 +3969,43 @@ namespace build2 try_rmfile (relt, true); } + // We have no choice but to serialize early if we want the command line + // printed shortly before actually executing the linker. Failed that, it + // may look like we are still executing in parallel. + // + scheduler::alloc_guard jobs_ag; + if (!ctx.dry_run && cast_false<bool> (t[c_serialize])) + jobs_ag = scheduler::alloc_guard (*ctx.sched, phase_unlock (nullptr)); + if (verb == 1) - text << (lt.static_library () ? "ar " : "ld ") << t; + print_diag (lt.static_library () ? "ar" : "ld", t); else if (verb == 2) print_process (args); + // Do any necessary fixups to the command line to make it runnable. + // + // Notice the split in the diagnostics: at verbosity level 1 we print + // the "logical" command line while at level 2 and above -- what we are + // actually executing. + // + // We also need to save the original for the diag_buffer::close() call + // below if at verbosity level 1. + // + cstrings oargs; + // Adjust linker parallelism. // + // Note that we are not going to bother with oargs for this. + // + // Note also that we now have scheduler::serialize() which allows us to + // block until full parallelism is available (this mode can currently + // be forced with cc.serialize=true; maybe we should invent something + // like config.cc.link_serialize or some such which can be used when + // LTO is enabled). + // string jobs_arg; - scheduler::alloc_guard jobs_extra; - if (!lt.static_library ()) + if (!ctx.dry_run && !lt.static_library ()) { switch (ctype) { @@ -3257,8 +4021,10 @@ namespace build2 auto i (find_option_prefix ("-flto", args.rbegin (), args.rend ())); if (i != args.rend () && strcmp (*i, "-flto=auto") == 0) { - jobs_extra = scheduler::alloc_guard (ctx.sched, 0); - jobs_arg = "-flto=" + to_string (1 + jobs_extra.n); + if (jobs_ag.n == 0) // Might already have (see above). + jobs_ag = scheduler::alloc_guard (*ctx.sched, 0); + + jobs_arg = "-flto=" + to_string (1 + jobs_ag.n); *i = jobs_arg.c_str (); } break; @@ -3276,8 +4042,10 @@ namespace build2 strcmp (*i, "-flto=thin") == 0 && !find_option_prefix ("-flto-jobs=", args)) { - jobs_extra = scheduler::alloc_guard (ctx.sched, 0); - jobs_arg = "-flto-jobs=" + to_string (1 + jobs_extra.n); + if (jobs_ag.n == 0) // Might already have (see above). + jobs_ag = scheduler::alloc_guard (*ctx.sched, 0); + + jobs_arg = "-flto-jobs=" + to_string (1 + jobs_ag.n); args.insert (i.base (), jobs_arg.c_str ()); // After -flto=thin. } break; @@ -3288,12 +4056,6 @@ namespace build2 } } - // Do any necessary fixups to the command line to make it runnable. - // - // Notice the split in the diagnostics: at verbosity level 1 we print - // the "logical" command line while at level 2 and above -- what we are - // actually executing. - // // On Windows we need to deal with the command line length limit. The // best workaround seems to be passing (part of) the command line in an // "options file" ("response file" in Microsoft's terminology). Both @@ -3314,7 +4076,7 @@ namespace build2 { auto quote = [s = string ()] (const char* a) mutable -> const char* { - return process::quote_argument (a, s); + return process::quote_argument (a, s, false /* batch */); }; // Calculate the would-be command line length similar to how process' @@ -3379,19 +4141,20 @@ namespace build2 fail << "unable to write to " << f << ": " << e; } + if (verb == 1) + oargs = args; + // Replace input arguments with @file. // targ = '@' + f.string (); args.resize (args_input); args.push_back (targ.c_str()); args.push_back (nullptr); - - //@@ TODO: leave .t file if linker failed and verb > 2? } } #endif - if (verb > 2) + if (verb >= 3) print_process (args); // Remove the target file if any of the subsequent (after the linker) @@ -3409,52 +4172,51 @@ namespace build2 { // VC tools (both lib.exe and link.exe) send diagnostics to stdout. // Also, link.exe likes to print various gratuitous messages. So for - // link.exe we redirect stdout to a pipe, filter that noise out, and - // send the rest to stderr. + // link.exe we filter that noise out. // // For lib.exe (and any other insane linker that may try to pull off // something like this) we are going to redirect stdout to stderr. // For sane compilers this should be harmless. // // Note that we don't need this for LLD's link.exe replacement which - // is quiet. + // is thankfully quiet. // bool filter (tsys == "win32-msvc" && !lt.static_library () && cast<string> (rs["bin.ld.id"]) != "msvc-lld"); process pr (*ld, - args.data (), - 0 /* stdin */, - (filter ? -1 : 2) /* stdout */, - 2 /* stderr */, - nullptr /* cwd */, + args, + 0 /* stdin */, + 2 /* stdout */, + diag_buffer::pipe (ctx, filter /* force */) /* stderr */, + nullptr /* cwd */, env_ptrs.empty () ? nullptr : env_ptrs.data ()); + diag_buffer dbuf (ctx, args[0], pr); + if (filter) + msvc_filter_link (dbuf, t, ot); + + dbuf.read (); + { - try - { - ifdstream is ( - move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit); + bool e (pr.wait ()); - msvc_filter_link (is, t, ot); +#ifdef _WIN32 + // Keep the options file if we have shown it. + // + if (!e && verb >= 3) + trm.cancel (); +#endif - // If anything remains in the stream, send it all to stderr. - // Note that the eof check is important: if the stream is at - // eof, this and all subsequent writes to the diagnostics stream - // will fail (and you won't see a thing). - // - if (is.peek () != ifdstream::traits_type::eof ()) - diag_stream_lock () << is.rdbuf (); + dbuf.close (oargs.empty () ? args : oargs, + *pr.exit, + 1 /* verbosity */); - is.close (); - } - catch (const io_error&) {} // Assume exits with error. + if (!e) + throw failed (); } - - run_finish (args, pr); - jobs_extra.deallocate (); } catch (const process_error& e) { @@ -3476,12 +4238,24 @@ namespace build2 throw failed (); } - // Clean up executable's import library (see above for details). + // Clean up executable's import library (see above for details). And + // make sure we have an import library for a shared library. // - if (lt.executable () && tsys == "win32-msvc") + if (tsys == "win32-msvc") { - try_rmfile (relt + ".lib", true /* ignore_errors */); - try_rmfile (relt + ".exp", true /* ignore_errors */); + if (lt.executable ()) + { + try_rmfile (relt + ".lib", true /* ignore_errors */); + try_rmfile (relt + ".exp", true /* ignore_errors */); + } + else if (lt.shared_library ()) + { + if (!file_exists (reli, + false /* follow_symlinks */, + true /* ignore_error */)) + fail << "linker did not produce import library " << reli << + info << "perhaps this library does not export any symbols?"; + } } // Set executable bit on the .js file so that it can be run with a @@ -3513,12 +4287,17 @@ namespace build2 print_process (args); if (!ctx.dry_run) - run (rl, + { + run (ctx, + rl, args, - dir_path () /* cwd */, + 1 /* finish_verbosity */, env_ptrs.empty () ? nullptr : env_ptrs.data ()); + } } + jobs_ag.deallocate (); + // For Windows generate (or clean up) rpath-emulating assembly. // if (tclass == "windows") @@ -3621,12 +4400,11 @@ namespace build2 } target_state link_rule:: - perform_clean (action a, const target& xt) const + perform_clean (action a, const target& xt, match_data& md) const { const file& t (xt.as<file> ()); ltype lt (link_type (t)); - const match_data& md (t.data<match_data> ()); clean_extras extras; clean_adhoc_extras adhoc_extras; @@ -3699,5 +4477,25 @@ namespace build2 return perform_clean_extra (a, t, extras, adhoc_extras); } + + const target* link_rule:: + import (const prerequisite_key& pk, + const optional<string>&, + const location&) const + { + tracer trace (x, "link_rule::import"); + + // @@ TODO: do we want to make metadata loading optional? + // + optional<dir_paths> usr_lib_dirs; + const target* r (search_library (nullopt /* action */, + sys_lib_dirs, usr_lib_dirs, + pk)); + + if (r == nullptr) + l4 ([&]{trace << "unable to find installed library " << pk;}); + + return r; + } } } diff --git a/libbuild2/cc/link-rule.hxx b/libbuild2/cc/link-rule.hxx index baccf8d..9b491c2 100644 --- a/libbuild2/cc/link-rule.hxx +++ b/libbuild2/cc/link-rule.hxx @@ -4,8 +4,6 @@ #ifndef LIBBUILD2_CC_LINK_RULE_HXX #define LIBBUILD2_CC_LINK_RULE_HXX -#include <set> - #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> @@ -20,11 +18,13 @@ namespace build2 { namespace cc { - class LIBBUILD2_CC_SYMEXPORT link_rule: public simple_rule, virtual common + class LIBBUILD2_CC_SYMEXPORT link_rule: public rule, virtual common { public: link_rule (data&&); + struct match_data; + struct match_result { bool seen_x = false; @@ -32,24 +32,37 @@ namespace build2 bool seen_cc = false; bool seen_obj = false; bool seen_lib = false; + + match_result& operator|= (match_result y) + { + seen_x = seen_x || y.seen_x; + seen_c = seen_c || y.seen_c; + seen_cc = seen_cc || y.seen_cc; + seen_obj = seen_obj || y.seen_obj; + seen_lib = seen_lib || y.seen_lib; + return *this; + } }; match_result match (action, const target&, const target*, otype, bool) const; virtual bool - match (action, target&, const string&) const override; + match (action, target&, const string&, match_extra&) const override; virtual recipe - apply (action, target&) const override; + apply (action, target&, match_extra&) const override; target_state - perform_update (action, const target&) const; + perform_update (action, const target&, match_data&) const; target_state - perform_clean (action, const target&) const; + perform_clean (action, const target&, match_data&) const; - using simple_rule::match; // To make Clang happy. + virtual const target* + import (const prerequisite_key&, + const optional<string>&, + const location&) const override; public: // Library handling. @@ -58,8 +71,14 @@ namespace build2 { static const size_t npos = size_t (~0); - uintptr_t l; // Pointer to library target (last bit 0) or to - // library name (-lpthread or path; last bit 1). + // Each appended_library represents either a library target or a + // library name fragment up to 2 elements long: + // + // target | name + // -------------------------------------------------- + const void* l1; // library target | library name[1] or NULL + const void* l2; // NULL | library name[0] + size_t begin; // First arg belonging to this library. size_t end; // Past last arg belonging to this library. }; @@ -67,64 +86,129 @@ namespace build2 class appended_libraries: public small_vector<appended_library, 128> { public: - // Find existing or append new entry. If appending new, use the second - // argument as the begin value. + // Find existing entry, if any. // - appended_library& - append (const file& l, size_t b) + appended_library* + find (const file& l) { - auto p (reinterpret_cast<uintptr_t> (&l)); auto i (find_if (begin (), end (), - [p] (const appended_library& al) + [&l] (const appended_library& al) { - return al.l == p; + return al.l2 == nullptr && al.l1 == &l; })); - if (i != end ()) - return *i; + return i != end () ? &*i : nullptr; + } - push_back (appended_library {p, b, appended_library::npos}); - return back (); + appended_library* + find (const small_vector<reference_wrapper<const string>, 2>& ns) + { + size_t n (ns.size ()); + + if (n > 2) + return nullptr; + + auto i ( + find_if ( + begin (), end (), + [&ns, n] (const appended_library& al) + { + return al.l2 != nullptr && + *static_cast<const string*> (al.l2) == ns[0].get () && + (n == 2 + ? (al.l1 != nullptr && + *static_cast<const string*> (al.l1) == ns[1].get ()) + : al.l1 == nullptr); + })); + + return i != end () ? &*i : nullptr; } + // Find existing or append new entry. If appending new, use the second + // argument as the begin value. + // appended_library& - append (const string& l, size_t b) + append (const file& l, size_t b) { - auto i (find_if (begin (), end (), - [&l] (const appended_library& al) - { - return (al.l & 1) != 0 && - l == *reinterpret_cast<const string*> ( - al.l & ~uintptr_t (1)); - })); + if (appended_library* r = find (l)) + return *r; - if (i != end ()) - return *i; - - push_back ( - appended_library { - reinterpret_cast<uintptr_t> (&l) | 1, b, appended_library::npos}); + push_back (appended_library {&l, nullptr, b, appended_library::npos}); return back (); } + + // Return NULL if no duplicate tracking can be performed for this + // library. + // + appended_library* + append (const small_vector<reference_wrapper<const string>, 2>& ns, + size_t b) + { + size_t n (ns.size ()); + + if (n > 2) + return nullptr; + + if (appended_library* r = find (ns)) + return r; + + push_back (appended_library { + n == 2 ? &ns[1].get () : nullptr, &ns[0].get (), + b, appended_library::npos}); + + return &back (); + } + + // Hoist the elements corresponding to the specified library to the + // end. + // + void + hoist (strings& args, appended_library& al) + { + if (al.begin != al.end) + { + // Rotate to the left the subrange starting from the first element + // of this library and until the end so that the element after the + // last element of this library becomes the first element of this + // subrange. We also need to adjust begin/end of libraries + // affected by the rotation. + // + rotate (args.begin () + al.begin, + args.begin () + al.end, + args.end ()); + + size_t n (al.end - al.begin); + + for (appended_library& al1: *this) + { + if (al1.begin >= al.end) + { + al1.begin -= n; + al1.end -= n; + } + } + + al.end = args.size (); + al.begin = al.end - n; + } + } }; void append_libraries (appended_libraries&, strings&, + sha256*, bool*, timestamp, const scope&, action, const file&, bool, lflags, linfo, - bool = true, bool = true) const; - - void - append_libraries (sha256&, bool&, timestamp, - const scope&, action, - const file&, bool, lflags, linfo) const; + optional<bool>, bool = true, bool = true, + library_cache* = nullptr) const; using rpathed_libraries = small_vector<const file*, 256>; void rpath_libraries (rpathed_libraries&, strings&, const scope&, - action, const file&, bool, linfo, bool, bool) const; + action, const file&, bool, linfo, bool, bool, + library_cache* = nullptr) const; void rpath_libraries (strings&, @@ -132,20 +216,26 @@ namespace build2 const target&, linfo, bool) const; void - append_binless_modules (strings&, + append_binless_modules (strings&, sha256*, const scope&, action, const file&) const; - void - append_binless_modules (sha256&, - const scope&, action, const file&) const; + bool + deduplicate_export_libs ( + const scope&, + const vector<name>&, + names&, + vector<reference_wrapper<const name>>* = nullptr) const; + + optional<path> + find_system_library (const strings&) const; protected: static void functions (function_family&, const char*); // functions.cxx - private: - friend class install_rule; - friend class libux_install_rule; + // Implementation details. + // + public: // Shared library paths. // @@ -188,27 +278,36 @@ namespace build2 struct match_data { + explicit + match_data (const link_rule& r): rule (r) {} + // The "for install" condition is signalled to us by install_rule when // it is matched for the update operation. It also verifies that if we // have already been executed, then it was for install. // // This has an interesting implication: it means that this rule cannot - // be used to update targets during match. Specifically, we cannot be - // executed for group resolution purposes (not a problem) nor as part - // of the generated source update. The latter case can be a problem: - // imagine a code generator that itself may need to be updated before - // it can be used to re-generate some out-of-date source code. As an - // aside, note that even if we were somehow able to communicate the - // "for install" in this case, the result of such an update may not - // actually be "usable" (e.g., not runnable because of the missing + // be used to update targets to be installed during match (since we + // would notice that they are for install too late). Specifically, we + // cannot be executed for group resolution purposes (should not be a + // problem) nor as part of the generated source update. The latter + // case can be a problem: imagine a source code generator that itself + // may need to be updated before it can be used to re-generate some + // out-of-date source code (or, worse, both the generator and the + // target to be installed depend on the same library). + // + // As an aside, note that even if we were somehow able to communicate + // the "for install" in this case, the result of such an update may + // not actually be "usable" (e.g., not runnable because of the missing // rpaths). There is another prominent case where the result may not - // be usable: cross-compilation. + // be usable: cross-compilation (in fact, if you think about it, "for + // install" is quite similar to cross-compilation: we are building for + // a foreign "environment" and thus cannot execute the results of the + // build). // - // So the current (admittedly fuzzy) thinking is that a project shall - // not try to use its own build for update since it may not be usable - // (because of cross-compilations, being "for install", etc). Instead, - // it should rely on another, "usable" build of itself (this, BTW, is - // related to bpkg's build-time vs run-time dependencies). + // So the current thinking is that a project shall not try to use its + // own "for install" (or, naturally, cross-compilation) build for + // update since it may not be usable. Instead, it should rely on + // another, "usable" build. // optional<bool> for_install; @@ -216,20 +315,28 @@ namespace build2 size_t start; // Parallel prerequisites/prerequisite_targets start. link_rule::libs_paths libs_paths; + + const link_rule& rule; + + target_state + operator() (action a, const target& t) + { + return a == perform_update_id + ? rule.perform_update (a, t, *this) + : rule.perform_clean (a, t, *this); + } }; // Windows rpath emulation (windows-rpath.cxx). // + private: struct windows_dll { - const string& dll; - const string* pdb; // NULL if none. - string pdb_storage; - - bool operator< (const windows_dll& y) const {return dll < y.dll;} + reference_wrapper<const string> dll; + string pdb; // Empty if none. }; - using windows_dlls = std::set<windows_dll>; + using windows_dlls = vector<windows_dll>; timestamp windows_rpath_timestamp (const file&, diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx index 8241a01..cf6c6e4 100644 --- a/libbuild2/cc/module.cxx +++ b/libbuild2/cc/module.cxx @@ -11,10 +11,7 @@ #include <libbuild2/bin/target.hxx> -#include <libbuild2/cc/target.hxx> // pc* - #include <libbuild2/config/utility.hxx> -#include <libbuild2/install/utility.hxx> #include <libbuild2/cc/guess.hxx> @@ -30,6 +27,8 @@ namespace build2 { tracer trace (x, "guess_init"); + context& ctx (rs.ctx); + bool cc_loaded (cast_false<bool> (rs["cc.core.guess.loaded"])); // Adjust module priority (compiler). Also order cc module before us @@ -41,7 +40,10 @@ namespace build2 config::save_module (rs, x, 250); - auto& vp (rs.var_pool ()); + // All the variables we enter are qualified so go straight for the + // public variable pool. + // + auto& vp (rs.var_pool (true /* public */)); // Must already exist. // @@ -55,7 +57,7 @@ namespace build2 // config.x // - strings mode; + strings omode; // Original mode. { // Normally we will have a persistent configuration and computing the // default value every time will be a waste. So try without a default @@ -139,22 +141,37 @@ namespace build2 fail << "invalid path '" << s << "' in " << config_x; } - mode.assign (++v.begin (), v.end ()); + omode.assign (++v.begin (), v.end ()); // Save original path/mode in *.config.path/mode. // rs.assign (x_c_path) = xc; - rs.assign (x_c_mode) = mode; + rs.assign (x_c_mode) = omode; + + // Merge the configured mode options into user-specified (which must + // be done before loading the *.guess module). + // + // In particular, this ability to specify the compiler mode in a + // buildfile is useful in embedded development where the project may + // need to hardcode things like -target, -nostdinc, etc. + // + const strings& mode (cast<strings> (rs.assign (x_mode) += omode)); // Figure out which compiler we are dealing with, its target, etc. // // Note that we could allow guess() to modify mode to support // imaginary options (such as /MACHINE for cl.exe). Though it's not // clear what cc.mode would contain (original or modified). Note that - // we are now folding *.std options into mode options. + // we are now adding *.std options into mode options. + // + // @@ But can't the language standard options alter things like search + // directories? // x_info = &build2::cc::guess ( - x, x_lang, move (xc), + ctx, + x, x_lang, + rs.root_extra->environment_checksum, + move (xc), cast_null<string> (lookup_config (rs, config_x_id)), cast_null<string> (lookup_config (rs, config_x_version)), cast_null<string> (lookup_config (rs, config_x_target)), @@ -178,7 +195,8 @@ namespace build2 if (config_sub) { - ct = run<string> (3, + ct = run<string> (ctx, + 3, *config_sub, xi.target.c_str (), [] (string& l, bool) {return move (l);}); @@ -202,10 +220,24 @@ namespace build2 } } + // Hash the environment (used for change detection). + // + // Note that for simplicity we use the combined checksum for both + // compilation and linking (which may compile, think LTO). + // + { + sha256 cs; + hash_environment (cs, xi.compiler_environment); + hash_environment (cs, xi.platform_environment); + env_checksum = cs.string (); + } + // Assign values to variables that describe the compiler. // - rs.assign (x_path) = process_path_ex (xi.path, x_name, xi.checksum); - const strings& xm (cast<strings> (rs.assign (x_mode) = move (mode))); + // Note: x_mode is dealt with above. + // + rs.assign (x_path) = process_path_ex ( + xi.path, x_name, xi.checksum, env_checksum); rs.assign (x_id) = xi.id.string (); rs.assign (x_id_type) = to_string (xi.id.type); @@ -250,9 +282,9 @@ namespace build2 // if (!cc_loaded) { - // Prepare configuration hints. + // Prepare configuration hints (pretend it belongs to root scope). // - variable_map h (rs.ctx); + variable_map h (rs); // Note that all these variables have already been registered. // @@ -263,8 +295,8 @@ namespace build2 if (!xi.pattern.empty ()) h.assign ("config.cc.pattern") = xi.pattern; - if (!xm.empty ()) - h.assign ("config.cc.mode") = xm; + if (!omode.empty ()) + h.assign ("config.cc.mode") = move (omode); h.assign (c_runtime) = xi.runtime; h.assign (c_stdlib) = xi.c_stdlib; @@ -335,6 +367,8 @@ namespace build2 # ifdef __APPLE__ static const dir_path a_usr_inc ( "/Library/Developer/CommandLineTools/SDKs/MacOSX*.sdk/usr/include"); + static const dir_path a_usr_lib ( + "/Library/Developer/CommandLineTools/SDKs/MacOSX*.sdk/usr/lib"); # endif #endif @@ -344,7 +378,7 @@ namespace build2 struct search_dirs { pair<dir_paths, size_t> lib; - pair<dir_paths, size_t> inc; + pair<dir_paths, size_t> hdr; }; static global_cache<search_dirs> dirs_cache; @@ -357,6 +391,20 @@ namespace build2 const compiler_info& xi (*x_info); const target_triplet& tt (cast<target_triplet> (rs[x_target])); + // Load cc.core.config. + // + if (!cast_false<bool> (rs["cc.core.config.loaded"])) + { + // Prepare configuration hints (pretend it belongs to root scope). + // + variable_map h (rs); + + if (!xi.bin_pattern.empty ()) + h.assign ("config.bin.pattern") = xi.bin_pattern; + + init_module (rs, rs, "cc.core.config", loc, false, h); + } + // Configuration. // using config::lookup_config; @@ -426,20 +474,114 @@ namespace build2 translate_std (xi, tt, rs, mode, v); } - // config.x.translatable_header + // config.x.internal.scope + // + // Note: save omitted. + // + // The effective internal_scope value is chosen based on the following + // priority list: + // + // 1. config.x.internal.scope + // + // 2. config.cc.internal.scope + // + // 3. effective value from bundle amalgamation + // + // 4. x.internal.scope + // + // 5. cc.internal.scope + // + // Note also that we only update x.internal.scope (and not cc.*) to + // reflect the effective value. + // + const string* iscope_str (nullptr); + { + if (lookup l = lookup_config (rs, config_x_internal_scope)) // 1 + { + iscope_str = &cast<string> (l); + + if (*iscope_str == "current") + fail << "'current' value in " << config_x_internal_scope; + } + else if (lookup l = rs["config.cc.internal.scope"]) // 2 + { + iscope_str = &cast<string> (l); + } + else // 3 + { + const scope& as (*rs.bundle_scope ()); + + if (as != rs) + { + // Only use the value if the corresponding module is loaded. + // + bool xl (cast_false<bool> (as[string (x) + ".config.loaded"])); + if (xl) + iscope_str = cast_null<string> (as[x_internal_scope]); + + if (iscope_str == nullptr) + { + if (xl || cast_false<bool> (as["cc.core.config.loaded"])) + iscope_str = cast_null<string> (as["cc.internal.scope"]); + } + + if (iscope_str != nullptr && *iscope_str == "current") + iscope_current = &as; + } + } + + lookup l; + if (iscope_str == nullptr) + { + iscope_str = cast_null<string> (l = rs[x_internal_scope]); // 4 + + if (iscope_str == nullptr) + iscope_str = cast_null<string> (rs["cc.internal.scope"]); // 5 + } + + if (iscope_str != nullptr) + { + const string& s (*iscope_str); + + // Assign effective. + // + if (!l) + rs.assign (x_internal_scope) = s; + + if (s == "current") + { + iscope = internal_scope::current; + + if (iscope_current == nullptr) + iscope_current = &rs; + } + else if (s == "base") iscope = internal_scope::base; + else if (s == "root") iscope = internal_scope::root; + else if (s == "bundle") iscope = internal_scope::bundle; + else if (s == "strong") iscope = internal_scope::strong; + else if (s == "weak") iscope = internal_scope::weak; + else if (s == "global") + ; // Nothing to translate; + else + fail << "invalid " << x_internal_scope << " value '" << s << "'"; + } + } + + // config.x.translate_include // // It's still fuzzy whether specifying (or maybe tweaking) this list in // the configuration will be a common thing to do so for now we use - // omitted. It's also probably too early to think whether we should have - // the cc.* version and what the semantics should be. + // omitted. // - if (x_translatable_headers != nullptr) + if (x_translate_include != nullptr) { - lookup l (lookup_config (rs, *config_x_translatable_headers)); - - // @@ MODHDR: if(modules) ? - // - rs.assign (x_translatable_headers) += cast_null<strings> (l); + if (lookup l = lookup_config (rs, *config_x_translate_include)) + { + // @@ MODHDR: if(modules) ? Yes. + // + rs.assign (x_translate_include).prepend ( + cast<translatable_headers> (l)); + } } // Extract system header/library search paths from the compiler and @@ -448,13 +590,13 @@ namespace build2 // Note that for now module search paths only come from compiler_info. // pair<dir_paths, size_t> lib_dirs; - pair<dir_paths, size_t> inc_dirs; + pair<dir_paths, size_t> hdr_dirs; const optional<pair<dir_paths, size_t>>& mod_dirs (xi.sys_mod_dirs); - if (xi.sys_lib_dirs && xi.sys_inc_dirs) + if (xi.sys_lib_dirs && xi.sys_hdr_dirs) { lib_dirs = *xi.sys_lib_dirs; - inc_dirs = *xi.sys_inc_dirs; + hdr_dirs = *xi.sys_hdr_dirs; } else { @@ -481,27 +623,27 @@ namespace build2 switch (xi.class_) { case compiler_class::gcc: - lib_dirs = gcc_library_search_dirs (xi.path, rs); + lib_dirs = gcc_library_search_dirs (xi, rs); break; case compiler_class::msvc: - lib_dirs = msvc_library_search_dirs (xi.path, rs); + lib_dirs = msvc_library_search_dirs (xi, rs); break; } } - if (xi.sys_inc_dirs) - inc_dirs = *xi.sys_inc_dirs; + if (xi.sys_hdr_dirs) + hdr_dirs = *xi.sys_hdr_dirs; else if (sd != nullptr) - inc_dirs = sd->inc; + hdr_dirs = sd->hdr; else { switch (xi.class_) { case compiler_class::gcc: - inc_dirs = gcc_header_search_dirs (xi.path, rs); + hdr_dirs = gcc_header_search_dirs (xi, rs); break; case compiler_class::msvc: - inc_dirs = msvc_header_search_dirs (xi.path, rs); + hdr_dirs = msvc_header_search_dirs (xi, rs); break; } } @@ -510,17 +652,17 @@ namespace build2 { search_dirs sd; if (!xi.sys_lib_dirs) sd.lib = lib_dirs; - if (!xi.sys_inc_dirs) sd.inc = inc_dirs; + if (!xi.sys_hdr_dirs) sd.hdr = hdr_dirs; dirs_cache.insert (move (key), move (sd)); } } sys_lib_dirs_mode = lib_dirs.second; - sys_inc_dirs_mode = inc_dirs.second; + sys_hdr_dirs_mode = hdr_dirs.second; sys_mod_dirs_mode = mod_dirs ? mod_dirs->second : 0; - sys_lib_dirs_extra = lib_dirs.first.size (); - sys_inc_dirs_extra = inc_dirs.first.size (); + sys_lib_dirs_extra = 0; + sys_hdr_dirs_extra = 0; #ifndef _WIN32 // Add /usr/local/{include,lib}. We definitely shouldn't do this if we @@ -536,11 +678,11 @@ namespace build2 // on the next invocation. // { - auto& is (inc_dirs.first); + auto& hs (hdr_dirs.first); auto& ls (lib_dirs.first); - bool ui (find (is.begin (), is.end (), usr_inc) != is.end ()); - bool uli (find (is.begin (), is.end (), usr_loc_inc) != is.end ()); + bool ui (find (hs.begin (), hs.end (), usr_inc) != hs.end ()); + bool uli (find (hs.begin (), hs.end (), usr_loc_inc) != hs.end ()); #ifdef __APPLE__ // On Mac OS starting from 10.14 there is no longer /usr/include. @@ -563,15 +705,28 @@ namespace build2 // // Is Apple's /usr/include. // - if (!ui && !uli) + // Also, it appears neither Clang nor GCC report MacOSX*.sdk/usr/lib + // with -print-search-dirs but they do search in there. So we add it + // to our list if we see MacOSX*.sdk/usr/include. + // + auto aui (find_if (hs.begin (), hs.end (), + [] (const dir_path& d) + { + return path_match (d, a_usr_inc); + })); + + if (aui != hs.end ()) { - for (const dir_path& d: is) + if (!ui) + ui = true; + + if (find_if (ls.begin (), ls.end (), + [] (const dir_path& d) + { + return path_match (d, a_usr_lib); + }) == ls.end ()) { - if (path_match (d, a_usr_inc)) - { - ui = true; - break; - } + ls.push_back (aui->directory () /= "lib"); } } #endif @@ -579,18 +734,29 @@ namespace build2 { bool ull (find (ls.begin (), ls.end (), usr_loc_lib) != ls.end ()); - // Many platforms don't search in /usr/local/lib by default (but do - // for headers in /usr/local/include). So add it as the last option. + // Many platforms don't search in /usr/local/lib by default but do + // for headers in /usr/local/include. + // + // Note that customarily /usr/local/include is searched before + // /usr/include so we add /usr/local/lib before built-in entries + // (there isn't really a way to add it after since all we can do is + // specify it with -L). // if (!ull && exists (usr_loc_lib, true /* ignore_error */)) - ls.push_back (usr_loc_lib); + { + ls.insert (ls.begin () + sys_lib_dirs_mode, usr_loc_lib); + ++sys_lib_dirs_extra; + } // FreeBSD is at least consistent: it searches in neither. Quoting // its wiki: "FreeBSD can't even find libraries that it installed." // So let's help it a bit. // if (!uli && exists (usr_loc_inc, true /* ignore_error */)) - is.push_back (usr_loc_inc); + { + hs.insert (hs.begin () + sys_hdr_dirs_mode, usr_loc_inc); + ++sys_hdr_dirs_extra; + } } } #endif @@ -632,7 +798,7 @@ namespace build2 if (xi.variant_version) { - dr << " variant: " << '\n' + dr << " variant:" << '\n' << " version " << xi.variant_version->string << '\n' << " major " << xi.variant_version->major << '\n' << " minor " << xi.variant_version->minor << '\n' @@ -667,9 +833,19 @@ namespace build2 } auto& mods (mod_dirs ? mod_dirs->first : dir_paths ()); - auto& incs (inc_dirs.first); + auto& incs (hdr_dirs.first); auto& libs (lib_dirs.first); + if (verb >= 3 && iscope) + { + dr << "\n int scope "; + + if (*iscope == internal_scope::current) + dr << iscope_current->out_path (); + else + dr << *iscope_str; + } + if (verb >= 3 && !mods.empty ()) { dr << "\n mod dirs"; @@ -681,11 +857,14 @@ namespace build2 if (verb >= 3 && !incs.empty ()) { - dr << "\n inc dirs"; + dr << "\n hdr dirs"; for (size_t i (0); i != incs.size (); ++i) { - if (i == sys_inc_dirs_extra) + if ((sys_hdr_dirs_mode != 0 && i == sys_hdr_dirs_mode) || + (sys_hdr_dirs_extra != 0 && + i == sys_hdr_dirs_extra + sys_hdr_dirs_mode)) dr << "\n --"; + dr << "\n " << incs[i]; } } @@ -695,31 +874,36 @@ namespace build2 dr << "\n lib dirs"; for (size_t i (0); i != libs.size (); ++i) { - if (i == sys_lib_dirs_extra) + if ((sys_lib_dirs_mode != 0 && i == sys_lib_dirs_mode) || + (sys_lib_dirs_extra != 0 && + i == sys_lib_dirs_extra + sys_lib_dirs_mode)) dr << "\n --"; + dr << "\n " << libs[i]; } } } rs.assign (x_sys_lib_dirs) = move (lib_dirs.first); - rs.assign (x_sys_inc_dirs) = move (inc_dirs.first); + rs.assign (x_sys_hdr_dirs) = move (hdr_dirs.first); - // Load cc.core.config. - // - if (!cast_false<bool> (rs["cc.core.config.loaded"])) - { - variable_map h (rs.ctx); - - if (!xi.bin_pattern.empty ()) - h.assign ("config.bin.pattern") = xi.bin_pattern; - - init_module (rs, rs, "cc.core.config", loc, false, h); - } + config::save_environment (rs, xi.compiler_environment); + config::save_environment (rs, xi.platform_environment); } + // Global cache of ad hoc importable headers. + // + // The key is a hash of the system header search directories + // (sys_hdr_dirs) where we search for the headers. + // + static map<string, importable_headers> importable_headers_cache; + static mutex importable_headers_mutex; + void module:: - init (scope& rs, const location& loc, const variable_map&) + init (scope& rs, + const location& loc, + const variable_map&, + const compiler_info& xi) { tracer trace (x, "init"); @@ -740,110 +924,82 @@ namespace build2 // load_module (rs, rs, "cc.core", loc); - // Process, sort, and cache (in this->xlate_hdr) translatable headers. - // Keep the cache NULL if unused or empty. - // - // @@ MODHDR TODO: support exclusions entries (e.g., -<stdio.h>)? + // Search include translation headers and groups. // - if (modules && x_translatable_headers != nullptr) + if (modules) { - strings* ih (cast_null<strings> (rs.assign (x_translatable_headers))); + { + sha256 k; + for (const dir_path& d: sys_hdr_dirs) + k.append (d.string ()); + + mlock l (importable_headers_mutex); + importable_headers = &importable_headers_cache[k.string ()]; + } + + auto& hs (*importable_headers); + + ulock ul (hs.mutex); - if (ih != nullptr && !ih->empty ()) + if (hs.group_map.find (header_group_std) == hs.group_map.end ()) + guess_std_importable_headers (xi, sys_hdr_dirs, hs); + + // Process x.translate_include. + // + const variable& var (*x_translate_include); + if (auto* v = cast_null<translatable_headers> (rs[var])) { - // Translate <>-style header names to absolute paths using the - // compiler's include search paths. Otherwise complete and normalize - // since when searching in this list we always use the absolute and - // normalized header target path. - // - for (string& h: *ih) + for (const auto& p: *v) { - if (h.empty ()) - continue; + const string& k (p.first); - path f; - if (h.front () == '<' && h.back () == '>') + if (k.front () == '<' && k.back () == '>') { - h.pop_back (); - h.erase (0, 1); + if (path_pattern (k)) + { + size_t n (hs.insert_angle_pattern (sys_hdr_dirs, k)); - for (const dir_path& d: sys_inc_dirs) + l5 ([&]{trace << "pattern " << k << " searched to " << n + << " headers";}); + } + else { - if (file_exists ((f = d, f /= h), - true /* follow_symlinks */, - true /* ignore_errors */)) - goto found; + // What should we do if not found? While we can fail, this + // could be too drastic if, for example, the header is + // "optional" and may or may not be present/used. So for now + // let's ignore (we could have also removed it from the map as + // an indication). + // + const auto* r (hs.insert_angle (sys_hdr_dirs, k)); + + l5 ([&]{trace << "header " << k << " searched to " + << (r ? r->first.string ().c_str () : "<none>");}); } - - // What should we do if not found? While we can fail, this could - // be too drastic if, for example, the header is "optional" and - // may or may not be present/used. So for now let's restore the - // original form to aid debugging (it can't possibly match any - // absolute path). + } + else if (path_traits::find_separator (k) == string::npos) + { + // Group name. // - h.insert (0, 1, '<'); - h.push_back ('>'); - continue; - - found: - ; // Fall through. + if (k != header_group_all_importable && + k != header_group_std_importable && + k != header_group_all && + k != header_group_std) + fail (loc) << "unknown header group '" << k << "' in " << var; } else { - f = path (move (h)); - - if (f.relative ()) - f.complete (); + // Absolute and normalized header path. + // + if (!path_traits::absolute (k)) + fail (loc) << "relative header path '" << k << "' in " << var; } - - // @@ MODHDR: should we use the more elaborate but robust - // normalize/realize scheme so the we get the same - // path? Feels right. - f.normalize (); - h = move (f).string (); } - - sort (ih->begin (), ih->end ()); - xlate_hdr = ih; } } // Register target types and configure their "installability". // - bool install_loaded (cast_false<bool> (rs["install.loaded"])); - - { - using namespace install; - - rs.insert_target_type (x_src); - - auto insert_hdr = [&rs, install_loaded] (const target_type& tt) - { - rs.insert_target_type (tt); - - // Install headers into install.include. - // - if (install_loaded) - install_path (rs, tt, dir_path ("include")); - }; - - // Note: module (x_mod) is in x_hdr. - // - for (const target_type* const* ht (x_hdr); *ht != nullptr; ++ht) - insert_hdr (**ht); - - // Also register the C header for C-derived languages. - // - if (*x_hdr != &h::static_type) - insert_hdr (h::static_type); - - rs.insert_target_type<pc> (); - rs.insert_target_type<pca> (); - rs.insert_target_type<pcs> (); - - if (install_loaded) - install_path<pc> (rs, dir_path ("pkgconfig")); - } + load_module (rs, rs, (string (x) += ".types"), loc); // Register rules. // @@ -941,34 +1097,37 @@ namespace build2 // them in case they depend on stuff that we need to install (see the // install rule implementations for details). // - if (install_loaded) + if (cast_false<bool> (rs["install.loaded"])) { + // Note: we rely quite heavily in these rule implementations that + // these are the only target types they are registered for. + const install_rule& ir (*this); - r.insert<exe> (perform_install_id, x_install, ir); - r.insert<exe> (perform_uninstall_id, x_uninstall, ir); + r.insert<exe> (perform_install_id, x_install, ir); + r.insert<exe> (perform_uninstall_id, x_install, ir); - r.insert<liba> (perform_install_id, x_install, ir); - r.insert<liba> (perform_uninstall_id, x_uninstall, ir); + r.insert<liba> (perform_install_id, x_install, ir); + r.insert<liba> (perform_uninstall_id, x_install, ir); if (s) { - r.insert<libs> (perform_install_id, x_install, ir); - r.insert<libs> (perform_uninstall_id, x_uninstall, ir); + r.insert<libs> (perform_install_id, x_install, ir); + r.insert<libs> (perform_uninstall_id, x_install, ir); } const libux_install_rule& lr (*this); - r.insert<libue> (perform_install_id, x_install, lr); - r.insert<libue> (perform_uninstall_id, x_uninstall, lr); + r.insert<libue> (perform_install_id, x_install, lr); + r.insert<libue> (perform_uninstall_id, x_install, lr); - r.insert<libua> (perform_install_id, x_install, lr); - r.insert<libua> (perform_uninstall_id, x_uninstall, lr); + r.insert<libua> (perform_install_id, x_install, lr); + r.insert<libua> (perform_uninstall_id, x_install, lr); if (s) { - r.insert<libus> (perform_install_id, x_install, lr); - r.insert<libus> (perform_uninstall_id, x_uninstall, lr); + r.insert<libus> (perform_install_id, x_install, lr); + r.insert<libus> (perform_uninstall_id, x_install, lr); } } } diff --git a/libbuild2/cc/module.hxx b/libbuild2/cc/module.hxx index 85b7158..4213516 100644 --- a/libbuild2/cc/module.hxx +++ b/libbuild2/cc/module.hxx @@ -4,6 +4,8 @@ #ifndef LIBBUILD2_CC_MODULE_HXX #define LIBBUILD2_CC_MODULE_HXX +#include <unordered_map> + #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> @@ -15,6 +17,7 @@ #include <libbuild2/cc/compile-rule.hxx> #include <libbuild2/cc/link-rule.hxx> #include <libbuild2/cc/install-rule.hxx> +#include <libbuild2/cc/predefs-rule.hxx> #include <libbuild2/cc/export.hxx> @@ -45,8 +48,10 @@ namespace build2 // Translate the x.std value (if any) to the standard-selecting // option(s) (if any) and fold them (normally by pre-pending) into the - // compiler mode options. This function may also check/set x.features.* - // variables on the root scope. + // compiler mode options. + // + // This function may also check/set [config.]x.features.* variables on + // the root scope. // virtual void translate_std (const compiler_info&, @@ -57,33 +62,72 @@ namespace build2 const compiler_info* x_info; + string env_checksum; // Environment checksum (also in x.path). + + // Cached x.internal.scope value. + // + using internal_scope = data::internal_scope; + optional<internal_scope> iscope; + const scope* iscope_current = nullptr; + // Temporary storage for data::sys_*_dirs_*. // size_t sys_lib_dirs_mode; - size_t sys_inc_dirs_mode; + size_t sys_hdr_dirs_mode; size_t sys_mod_dirs_mode; size_t sys_lib_dirs_extra; - size_t sys_inc_dirs_extra; + size_t sys_hdr_dirs_extra; bool new_config = false; // See guess() and init() for details. + // Header cache (see compile_rule::enter_header()). + // + // We place it into the config module so that we have an option of + // sharing it for the entire weak amalgamation. + // + public: + // Keep the hash in the key. This way we can compute it outside of the + // lock. + // + struct header_key + { + path file; + size_t hash; + + friend bool + operator== (const header_key& x, const header_key& y) + { + return x.file == y.file; // Note: hash was already compared. + } + }; + + struct header_key_hasher + { + size_t operator() (const header_key& k) const {return k.hash;} + }; + + mutable shared_mutex header_map_mutex; + mutable std::unordered_map<header_key, + const file*, + header_key_hasher> header_map; + private: // Defined in gcc.cxx. // pair<dir_paths, size_t> - gcc_header_search_dirs (const process_path&, scope&) const; + gcc_header_search_dirs (const compiler_info&, scope&) const; pair<dir_paths, size_t> - gcc_library_search_dirs (const process_path&, scope&) const; + gcc_library_search_dirs (const compiler_info&, scope&) const; // Defined in msvc.cxx. // pair<dir_paths, size_t> - msvc_header_search_dirs (const process_path&, scope&) const; + msvc_header_search_dirs (const compiler_info&, scope&) const; pair<dir_paths, size_t> - msvc_library_search_dirs (const process_path&, scope&) const; + msvc_library_search_dirs (const compiler_info&, scope&) const; }; class LIBBUILD2_CC_SYMEXPORT module: public build2::module, @@ -91,19 +135,24 @@ namespace build2 public link_rule, public compile_rule, public install_rule, - public libux_install_rule + public libux_install_rule, + public predefs_rule { public: explicit - module (data&& d) + module (data&& d, const scope& rs) : common (move (d)), link_rule (move (d)), - compile_rule (move (d)), + compile_rule (move (d), rs), install_rule (move (d), *this), - libux_install_rule (move (d), *this) {} + libux_install_rule (move (d), *this), + predefs_rule (move (d)) {} void - init (scope&, const location&, const variable_map&); + init (scope&, + const location&, + const variable_map&, + const compiler_info&); }; } } diff --git a/libbuild2/cc/msvc.cxx b/libbuild2/cc/msvc.cxx index 0b8491b..d21969c 100644 --- a/libbuild2/cc/msvc.cxx +++ b/libbuild2/cc/msvc.cxx @@ -164,18 +164,21 @@ namespace build2 // Filter cl.exe and link.exe noise. // + // Note: must be followed with the dbuf.read() call. + // void - msvc_filter_cl (ifdstream& is, const path& src) + msvc_filter_cl (diag_buffer& dbuf, const path& src) + try { // While it appears VC always prints the source name (event if the // file does not exist), let's do a sanity check. Also handle the // command line errors/warnings which come before the file name. // - for (string l; !eof (getline (is, l)); ) + for (string l; !eof (getline (dbuf.is, l)); ) { if (l != src.leaf ().string ()) { - diag_stream_lock () << l << endl; + dbuf.write (l, true /* newline */); if (msvc_sense_diag (l, 'D').first != string::npos) continue; @@ -184,14 +187,19 @@ namespace build2 break; } } + catch (const io_error& e) + { + fail << "unable to read from " << dbuf.args0 << " stderr: " << e; + } void - msvc_filter_link (ifdstream& is, const file& t, otype lt) + msvc_filter_link (diag_buffer& dbuf, const file& t, otype lt) + try { // Filter lines until we encounter something we don't recognize. We also // have to assume the messages can be translated. // - for (string l; getline (is, l); ) + for (string l; getline (dbuf.is, l); ) { // " Creating library foo\foo.dll.lib and object foo\foo.dll.exp" // @@ -216,12 +224,15 @@ namespace build2 // /INCREMENTAL causes linker to sometimes issue messages but now I // can't quite reproduce it. - // - diag_stream_lock () << l << endl; + dbuf.write (l, true /* newline */); break; } } + catch (const io_error& e) + { + fail << "unable to read from " << dbuf.args0 << " stderr: " << e; + } void msvc_extract_header_search_dirs (const strings& v, dir_paths& r) @@ -233,12 +244,15 @@ namespace build2 dir_path d; try { - // -I can either be in the "-Ifoo" or "-I foo" form. For VC it can - // also be /I. + // -I can either be in the "-Ifoo" or "-I foo" form. For MSVC it can + // also be /I. And from 16.10 it can also be /external:I. // - if (o.size () > 1 && (o[0] == '-' || o[0] == '/') && o[1] == 'I') + size_t p; + if ((o[0] == '-' || o[0] == '/') && + (p = (o[1] == 'I' ? 2 : + o.compare (1, 10, "external:I") == 0 ? 11 : 0)) != 0) { - if (o.size () == 2) + if (o.size () == p) { if (++i == e) break; // Let the compiler complain. @@ -246,10 +260,17 @@ namespace build2 d = dir_path (*i); } else - d = dir_path (o, 2, string::npos); + d = dir_path (o, p, string::npos); } else continue; + + // Ignore relative paths. Or maybe we should warn? + // + if (d.relative ()) + continue; + + d.normalize (); } catch (const invalid_path& e) { @@ -257,10 +278,7 @@ namespace build2 << o << "'"; } - // Ignore relative paths. Or maybe we should warn? - // - if (!d.relative ()) - r.push_back (move (d)); + r.push_back (move (d)); } } @@ -281,6 +299,13 @@ namespace build2 d = dir_path (o, 9, string::npos); else continue; + + // Ignore relative paths. Or maybe we should warn? + // + if (d.relative ()) + continue; + + d.normalize (); } catch (const invalid_path& e) { @@ -288,62 +313,72 @@ namespace build2 << o << "'"; } - // Ignore relative paths. Or maybe we should warn? - // - if (!d.relative ()) - r.push_back (move (d)); + r.push_back (move (d)); + } + } + + // Parse semicolon-separated list of search directories (from INCLUDE/LIB + // environment variables). + // + static void + parse_search_dirs (const string& v, dir_paths& r, const char* what) + { + for (size_t b (0), e (0); next_word (v, b, e, ';'); ) + { + string d (v, b, e - b); + trim (d); + + if (!d.empty ()) + { + try + { + r.push_back (dir_path (move (d)).normalize ()); + } + catch (const invalid_path&) + { + fail << "invalid path '" << d << "' in " << what; + } + } } } // Extract system header search paths from MSVC. // pair<dir_paths, size_t> config_module:: - msvc_header_search_dirs (const process_path&, scope& rs) const + msvc_header_search_dirs (const compiler_info&, scope& rs) const { - // The compiler doesn't seem to have any built-in paths and all of them - // either come from the INCLUDE environment variable or are specified - // explicitly on the command line (we now do this if running out of the - // command prompt; see guess). - - // @@ VC: how are we going to do this? E.g., cl-14 does this internally. - // cl.exe /Be prints INCLUDE. One advantage of going through the - // compiler is that it may be a wrapper (like our msvc-linux). Note - // also that we will still have to incorporate mode options. And this - // is not used for Clang targeting MSVC. - // - // Should we actually bother? INCLUDE is normally used for system - // headers and its highly unlikely we will see an imported library - // that lists one of those directories in pkg-config Cflags value. - // So the only benefit is to be able to print them. Let's wait and - // see. + // MSVC doesn't have any built-in paths and all of them either come from + // the INCLUDE environment variable or are specified explicitly on the + // command line (we now do this if running out of the command prompt; + // see guess). Note that this is not used for Clang targeting MSVC (but + // is for clang-cl). - // Extract -I paths from the compiler mode. + // Extract /I and similar paths from the compiler mode. // dir_paths r; msvc_extract_header_search_dirs (cast<strings> (rs[x_mode]), r); size_t rn (r.size ()); + // @@ This does not work for our msvc-linux wrappers which set the + // environment variable internally. One way to make this work would + // be run `cl.exe /Be` which prints INCLUDE and LIB (but not for + // clang-cl). + // + if (optional<string> v = getenv ("INCLUDE")) + parse_search_dirs (*v, r, "INCLUDE environment variable"); + return make_pair (move (r), rn); } // Extract system library search paths from MSVC. // pair<dir_paths, size_t> config_module:: - msvc_library_search_dirs (const process_path&, scope& rs) const + msvc_library_search_dirs (const compiler_info&, scope& rs) const { - // The linker doesn't seem to have any built-in paths and all of them - // either come from the LIB environment variable or are specified - // explicitly on the command line (we now do this if running out of the - // command prompt; see guess). - - // @@ VC: how are we going to do this? E.g., cl-14 does this internally. - // cl.exe /Be prints LIB. See above for further discussion. - // - // Should we actually bother? LIB is normally used for system - // libraries and its highly unlikely we will see an explicit import - // for a library from one of those directories. So the only benefit - // is to be able to print them. Let's wait and see. - // + // MSVC doesn't seem to have any built-in paths and all of them either + // come from the LIB environment variable or are specified explicitly on + // the command line (we now do this if running out of the command + // prompt; see guess). See the header case above for details. // Extract /LIBPATH paths from the compiler mode. // @@ -351,15 +386,34 @@ namespace build2 msvc_extract_library_search_dirs (cast<strings> (rs[x_mode]), r); size_t rn (r.size ()); + // @@ This does not work for our msvc-linux wrappers (see above for + // details). + // + if (optional<string> v = getenv ("LIB")) + parse_search_dirs (*v, r, "LIB environment variable"); + return make_pair (move (r), rn); } // Inspect the file and determine if it is static or import library. // Return otype::e if it is neither (which we quietly ignore). // + static global_cache<otype> library_type_cache; + static otype library_type (const process_path& ld, const path& l) { + string key; + { + sha256 cs; + cs.append (ld.effect_string ()); + cs.append (l.string ()); + key = cs.string (); + + if (const otype* r = library_type_cache.find (key)) + return *r; + } + // The are several reasonably reliable methods to tell whether it is a // static or import library. One is lib.exe /LIST -- if there aren't any // .obj members, then it is most likely an import library (it can also @@ -400,13 +454,14 @@ namespace build2 // process pr (run_start (ld, args, - 0 /* stdin */, - -1 /* stdout */, - false /* error */)); + 0 /* stdin */, + -1 /* stdout */, + 1 /* stderr (to stdout) */)); bool obj (false), dll (false); string s; + bool io (false); try { ifdstream is ( @@ -424,14 +479,11 @@ namespace build2 // libhello\hello.lib.obj // hello-0.1.0-a.0.19700101000000.dll // - // Archive member name at 746: [...]hello.dll[/][ ]* - // Archive member name at 8C70: [...]hello.lib.obj[/][ ]* - // size_t n (s.size ()); for (; n != 0 && s[n - 1] == ' '; --n) ; // Skip trailing spaces. - if (n >= 7) // At least ": X.obj" or ": X.dll". + if (n >= 5) // At least "X.obj" or "X.dll". { n -= 4; // Beginning of extension. @@ -446,14 +498,18 @@ namespace build2 } } } + + is.close (); } catch (const io_error&) { - // Presumably the child process failed. Let run_finish() deal with - // that. + // Presumably the child process failed so let run_finish() deal with + // that first. + // + io = true; } - if (!run_finish_code (args, pr, s)) + if (!run_finish_code (args, pr, s, 2 /* verbosity */) || io) { diag_record dr; dr << warn << "unable to detect " << l << " library type, ignoring" << @@ -462,23 +518,25 @@ namespace build2 return otype::e; } - if (obj && dll) + otype r; + if (obj != dll) + r = obj ? otype::a : otype::s; + else { - warn << l << " looks like hybrid static/import library, ignoring"; - return otype::e; - } + if (obj && dll) + warn << l << " looks like hybrid static/import library, ignoring"; - if (!obj && !dll) - { - warn << l << " looks like empty static or import library, ignoring"; - return otype::e; + if (!obj && !dll) + warn << l << " looks like empty static or import library, ignoring"; + + r = otype::e; } - return obj ? otype::a : otype::s; + return library_type_cache.insert (move (key), r); } template <typename T> - static T* + static pair<T*, bool> msvc_search_library (const process_path& ld, const dir_path& d, const prerequisite_key& p, @@ -524,20 +582,26 @@ namespace build2 // timestamp mt (mtime (f)); - if (mt != timestamp_nonexistent && library_type (ld, f) == lt) + pair<T*, bool> r (nullptr, true); + + if (mt != timestamp_nonexistent) { - // Enter the target. - // - T* t; - common::insert_library (p.scope->ctx, t, name, d, ld, e, exist, trace); - t->path_mtime (move (f), mt); - return t; + if (library_type (ld, f) == lt) + { + // Enter the target. + // + common::insert_library ( + p.scope->ctx, r.first, name, d, ld, e, exist, trace); + r.first->path_mtime (move (f), mt); + } + else + r.second = false; // Don't search for binless. } - return nullptr; + return r; } - liba* common:: + pair<bin::liba*, bool> common:: msvc_search_static (const process_path& ld, const dir_path& d, const prerequisite_key& p, @@ -545,14 +609,21 @@ namespace build2 { tracer trace (x, "msvc_search_static"); - liba* r (nullptr); + liba* a (nullptr); + bool b (true); - auto search = [&r, &ld, &d, &p, exist, &trace] ( + auto search = [&a, &b, &ld, &d, &p, exist, &trace] ( const char* pf, const char* sf) -> bool { - r = msvc_search_library<liba> ( - ld, d, p, otype::a, pf, sf, exist, trace); - return r != nullptr; + pair<liba*, bool> r (msvc_search_library<liba> ( + ld, d, p, otype::a, pf, sf, exist, trace)); + + if (r.first != nullptr) + a = r.first; + else if (!r.second) + b = false; + + return a != nullptr; }; // Try: @@ -565,10 +636,10 @@ namespace build2 search ("", "") || search ("lib", "") || search ("", "lib") || - search ("", "_static") ? r : nullptr; + search ("", "_static") ? make_pair (a, true) : make_pair (nullptr, b); } - libs* common:: + pair<bin::libs*, bool> common:: msvc_search_shared (const process_path& ld, const dir_path& d, const prerequisite_key& pk, @@ -579,12 +650,14 @@ namespace build2 assert (pk.scope != nullptr); libs* s (nullptr); + bool b (true); - auto search = [&s, &ld, &d, &pk, exist, &trace] ( + auto search = [&s, &b, &ld, &d, &pk, exist, &trace] ( const char* pf, const char* sf) -> bool { - if (libi* i = msvc_search_library<libi> ( - ld, d, pk, otype::s, pf, sf, exist, trace)) + pair<libi*, bool> r (msvc_search_library<libi> ( + ld, d, pk, otype::s, pf, sf, exist, trace)); + if (r.first != nullptr) { ulock l ( insert_library ( @@ -592,6 +665,8 @@ namespace build2 if (!exist) { + libi* i (r.first); + if (l.owns_lock ()) { s->adhoc_member = i; // We are first. @@ -605,6 +680,8 @@ namespace build2 s->path_mtime (path (), i->mtime ()); } } + else if (!r.second) + b = false; return s != nullptr; }; @@ -617,7 +694,7 @@ namespace build2 return search ("", "") || search ("lib", "") || - search ("", "dll") ? s : nullptr; + search ("", "dll") ? make_pair (s, true) : make_pair (nullptr, b); } } } diff --git a/libbuild2/cc/parser+module.test.testscript b/libbuild2/cc/parser+module.test.testscript index 91cbe45..6935f5e 100644 --- a/libbuild2/cc/parser+module.test.testscript +++ b/libbuild2/cc/parser+module.test.testscript @@ -59,6 +59,16 @@ import foo:part; import foo:part.sub; EOO +: import-part-from-part +: +$* <<EOI >>EOO +module foo:part; +import :part.sub; +EOI +module foo:part; +import foo:part.sub; +EOO + : export-imported : $* <<EOI >>EOO diff --git a/libbuild2/cc/parser.cxx b/libbuild2/cc/parser.cxx index 61d62b7..f62847e 100644 --- a/libbuild2/cc/parser.cxx +++ b/libbuild2/cc/parser.cxx @@ -15,9 +15,11 @@ namespace build2 using type = token_type; void parser:: - parse (ifdstream& is, const path_name& in, unit& u) + parse (ifdstream& is, const path_name& in, unit& u, const compiler_id& cid) { - lexer l (is, in); + cid_ = &cid; + + lexer l (is, in, true /* preprocessed */); l_ = &l; u_ = &u; @@ -82,6 +84,12 @@ namespace build2 // to call it __import) or it can have a special attribute (GCC // currently marks it with [[__translated]]). // + // Similarly, MSVC drops the `module;` marker and replaces all + // other `module` keywords with `__preprocessed_module`. + // + // Clang doesn't appear to rewrite anything, at least as of + // version 18. + // if (bb == 0 && t.first) { const string& id (t.value); // Note: tracks t. @@ -102,7 +110,9 @@ namespace build2 // Fall through. } - if (id == "module") + if (id == "module" || + (cid_->type == compiler_type::msvc && + id == "__preprocessed_module")) { location_value l (get_location (t)); l_->next (t); @@ -113,7 +123,9 @@ namespace build2 else n = false; } - else if (id == "import" /*|| id == "__import"*/) + else if (id == "import" /* || + (cid_->type == compiler_type::gcc && + id == "__import")*/) { l_->next (t); @@ -181,7 +193,7 @@ namespace build2 // pair<string, bool> np (parse_module_name (t, true /* partition */)); - // Should be {}-balanced. + // Skip attributes (should be {}-balanced). // for (; t.type != type::eos && t.type != type::semi && !t.first; @@ -195,7 +207,7 @@ namespace build2 if (!u_->module_info.name.empty ()) fail (l) << "multiple module declarations"; - u_->type =np.second + u_->type = np.second ? (ex ? unit_type::module_intf_part : unit_type::module_impl_part) : (ex ? unit_type::module_intf : unit_type::module_impl); u_->module_info.name = move (np.first); @@ -226,22 +238,28 @@ namespace build2 } case type::colon: { + // Add the module name to the partition so that code that doesn't + // need to distinguish between different kinds of imports doesn't + // have to. + // + // Note that if this itself is a partition, then we need to strip + // the partition part from the module name. + // switch (u_->type) { case unit_type::module_intf: case unit_type::module_impl: + un = u_->module_info.name; + break; case unit_type::module_intf_part: case unit_type::module_impl_part: + un.assign (u_->module_info.name, 0, u_->module_info.name.find (':')); break; default: fail (t) << "partition importation out of module purview"; } - // Add the module name to the partition so that code that doesn't - // need to distinguish beetween different kinds of imports doesn't - // have to. - // - un = u_->module_info.name + parse_module_part (t); + parse_module_part (t, un); ut = import_type::module_part; break; } @@ -256,7 +274,7 @@ namespace build2 return; } - // Should be {}-balanced. + // Skip attributes (should be {}-balanced). // for (; t.type != type::eos && t.type != type::semi && !t.first; diff --git a/libbuild2/cc/parser.hxx b/libbuild2/cc/parser.hxx index a1f1e57..0c2eb2d 100644 --- a/libbuild2/cc/parser.hxx +++ b/libbuild2/cc/parser.hxx @@ -10,6 +10,7 @@ #include <libbuild2/diagnostics.hxx> #include <libbuild2/cc/types.hxx> +#include <libbuild2/cc/guess.hxx> // compiler_id namespace build2 { @@ -23,16 +24,19 @@ namespace build2 class parser { public: + // The compiler_id argument should identify the compiler that has done + // the preprocessing. + // unit - parse (ifdstream& is, const path_name& n) + parse (ifdstream& is, const path_name& n, const compiler_id& cid) { unit r; - parse (is, n, r); + parse (is, n, r, cid); return r; } void - parse (ifdstream&, const path_name&, unit&); + parse (ifdstream&, const path_name&, unit&, const compiler_id&); private: void @@ -44,14 +48,6 @@ namespace build2 pair<string, bool> parse_module_name (token&, bool); - string - parse_module_part (token& t) - { - string n; - parse_module_part (t, n); - return n; - } - void parse_module_part (token&, string&); @@ -62,6 +58,7 @@ namespace build2 string checksum; // Translation unit checksum. private: + const compiler_id* cid_; lexer* l_; unit* u_; diff --git a/libbuild2/cc/parser.test.cxx b/libbuild2/cc/parser.test.cxx index 7613741..2270d32 100644 --- a/libbuild2/cc/parser.test.cxx +++ b/libbuild2/cc/parser.test.cxx @@ -1,7 +1,6 @@ // file : libbuild2/cc/parser.test.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file -#include <cassert> #include <iostream> #include <libbuild2/types.hxx> @@ -9,6 +8,9 @@ #include <libbuild2/cc/parser.hxx> +#undef NDEBUG +#include <cassert> + using namespace std; using namespace butl; @@ -42,7 +44,7 @@ namespace build2 } parser p; - unit u (p.parse (is, in)); + unit u (p.parse (is, in, compiler_id (compiler_type::gcc, ""))); switch (u.type) { diff --git a/libbuild2/cc/pkgconfig-libpkg-config.cxx b/libbuild2/cc/pkgconfig-libpkg-config.cxx new file mode 100644 index 0000000..ecbc019 --- /dev/null +++ b/libbuild2/cc/pkgconfig-libpkg-config.cxx @@ -0,0 +1,271 @@ +// file : libbuild2/cc/pkgconfig-libpkg-config.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BOOTSTRAP + +#include <libbuild2/cc/pkgconfig.hxx> + +#include <new> // std::bad_alloc + +#include <libbuild2/diagnostics.hxx> + +namespace build2 +{ + namespace cc + { + // The package dependency traversal depth limit. + // + static const int max_depth = 100; + + static void + error_handler (unsigned int, + const char* file, + size_t line, + const char* msg, + const pkg_config_client_t*, + const void*) + { + if (file != nullptr) + { + path_name n (file); + const location l (n, static_cast<uint64_t> (line)); + error (l) << msg; + } + else + error << msg; + } + + // Deleters. + // + struct fragments_deleter + { + void operator() (pkg_config_list_t* f) const + { + pkg_config_fragment_free (f); + } + }; + + // Convert fragments to strings. Skip the -I/-L options that refer to + // system directories. + // + static strings + to_strings (const pkg_config_list_t& frags, + char type, + const pkg_config_list_t& sysdirs) + { + assert (type == 'I' || type == 'L'); + + strings r; + auto add = [&r] (const pkg_config_fragment_t* frag) + { + string s; + if (frag->type != '\0') + { + s += '-'; + s += frag->type; + } + + s += frag->data; + r.push_back (move (s)); + }; + + // Option that is separated from its value, for example: + // + // -I /usr/lib + // + const pkg_config_fragment_t* opt (nullptr); + + pkg_config_node_t *node; + LIBPKG_CONFIG_FOREACH_LIST_ENTRY(frags.head, node) + { + auto frag (static_cast<const pkg_config_fragment_t*> (node->data)); + + // Add the separated option and directory, unless the latest is a + // system one. + // + if (opt != nullptr) + { + assert (frag->type == '\0'); // See pkg_config_fragment_add(). + + if (!pkg_config_path_match_list (frag->data, &sysdirs)) + { + add (opt); + add (frag); + } + + opt = nullptr; + continue; + } + + // Skip the -I/-L option if it refers to a system directory. + // + if (frag->type == type) + { + // The option is separated from a value, that will (presumably) + // follow. + // + if (*frag->data == '\0') + { + opt = frag; + continue; + } + + if (pkg_config_path_match_list (frag->data, &sysdirs)) + continue; + } + + add (frag); + } + + if (opt != nullptr) // Add the dangling option. + add (opt); + + return r; + } + + // Note that some libpkg-config functions can potentially return NULL, + // failing to allocate the required memory block. However, we will not + // check the returned value for NULL as the library doesn't do so, prior + // to filling the allocated structures. So such a code complication on our + // side would be useless. Also, for some functions the NULL result has a + // special semantics, for example "not found". @@ TODO: can we fix this? + // This is now somewhat addressed, see the eflags argument in + // pkg_config_pkg_find(). + // + pkgconfig:: + pkgconfig (path_type p, + const dir_paths& pc_dirs, + const dir_paths& sys_lib_dirs, + const dir_paths& sys_hdr_dirs) + : path (move (p)) + { + auto add_dirs = [] (pkg_config_list_t& dir_list, + const dir_paths& dirs, + bool suppress_dups) + { + for (const auto& d: dirs) + pkg_config_path_add (d.string ().c_str (), &dir_list, suppress_dups); + }; + + // Initialize the client handle. + // + // Note: omit initializing the filters from environment/defaults. + // + unique_ptr<pkg_config_client_t, void (*) (pkg_config_client_t*)> c ( + pkg_config_client_new (&error_handler, + nullptr /* handler_data */, + false /* init_filters */), + [] (pkg_config_client_t* c) {pkg_config_client_free (c);}); + + if (c == nullptr) + throw std::bad_alloc (); + + add_dirs (c->filter_libdirs, sys_lib_dirs, false /* suppress_dups */); + add_dirs (c->filter_includedirs, sys_hdr_dirs, false /* suppress_dups */); + + // Note that the loaded file directory is added to the (for now empty) + // .pc file search list. Also note that loading of the dependency + // packages is delayed until the flags retrieval, and their file + // directories are not added to the search list. + // + // @@ Hm, is there a way to force this resolution? But we may not + // need this (e.g., only loading from variables). + // + unsigned int e; + pkg_ = pkg_config_pkg_find (c.get (), path.string ().c_str (), &e); + + if (pkg_ == nullptr) + { + if (e == LIBPKG_CONFIG_ERRF_OK) + fail << "package '" << path << "' not found"; + else + // Diagnostics should have already been issued except for allocation + // errors. + // + fail << "unable to load package '" << path << "'"; + } + + // Add the .pc file search directories. + // + assert (c->dir_list.length == 1); // Package file directory (see above). + add_dirs (c->dir_list, pc_dirs, true /* suppress_dups */); + + client_ = c.release (); + } + + void pkgconfig:: + free () + { + assert (client_ != nullptr && pkg_ != nullptr); + + pkg_config_pkg_unref (client_, pkg_); + pkg_config_client_free (client_); + } + + strings pkgconfig:: + cflags (bool stat) const + { + assert (client_ != nullptr); // Must not be empty. + + pkg_config_client_set_flags ( + client_, + // Walk through the private package dependencies (Requires.private) + // besides the public ones while collecting the flags. Note that we do + // this for both static and shared linking. @@ Hm, I wonder why...? + // + LIBPKG_CONFIG_PKG_PKGF_SEARCH_PRIVATE | + + // Collect flags from Cflags.private besides those from Cflags for the + // static linking. + // + (stat + ? LIBPKG_CONFIG_PKG_PKGF_ADD_PRIVATE_FRAGMENTS + : 0)); + + pkg_config_list_t f = LIBPKG_CONFIG_LIST_INITIALIZER; // Empty list. + int e (pkg_config_pkg_cflags (client_, pkg_, &f, max_depth)); + + if (e != LIBPKG_CONFIG_ERRF_OK) + throw failed (); // Assume the diagnostics is issued. + + unique_ptr<pkg_config_list_t, fragments_deleter> fd (&f); + return to_strings (f, 'I', client_->filter_includedirs); + } + + strings pkgconfig:: + libs (bool stat) const + { + assert (client_ != nullptr); // Must not be empty. + + pkg_config_client_set_flags ( + client_, + // Additionally collect flags from the private dependency packages + // (see above) and from the Libs.private value for the static linking. + // + (stat + ? LIBPKG_CONFIG_PKG_PKGF_SEARCH_PRIVATE | + LIBPKG_CONFIG_PKG_PKGF_ADD_PRIVATE_FRAGMENTS + : 0)); + + pkg_config_list_t f = LIBPKG_CONFIG_LIST_INITIALIZER; // Empty list. + int e (pkg_config_pkg_libs (client_, pkg_, &f, max_depth)); + + if (e != LIBPKG_CONFIG_ERRF_OK) + throw failed (); // Assume the diagnostics is issued. + + unique_ptr<pkg_config_list_t, fragments_deleter> fd (&f); + return to_strings (f, 'L', client_->filter_libdirs); + } + + optional<string> pkgconfig:: + variable (const char* name) const + { + assert (client_ != nullptr); // Must not be empty. + + const char* r (pkg_config_tuple_find (client_, &pkg_->vars, name)); + return r != nullptr ? optional<string> (r) : nullopt; + } + } +} + +#endif // BUILD2_BOOTSTRAP diff --git a/libbuild2/cc/pkgconfig-libpkgconf.cxx b/libbuild2/cc/pkgconfig-libpkgconf.cxx new file mode 100644 index 0000000..f3754d3 --- /dev/null +++ b/libbuild2/cc/pkgconfig-libpkgconf.cxx @@ -0,0 +1,355 @@ +// file : libbuild2/cc/pkgconfig-libpkgconf.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BOOTSTRAP + +#include <libbuild2/cc/pkgconfig.hxx> + +#include <libbuild2/diagnostics.hxx> + +// Note that the libpkgconf library did not used to provide the version macro +// that we could use to compile the code conditionally against different API +// versions. Thus, we need to sense the pkgconf_client_new() function +// signature ourselves to call it properly. +// +namespace details +{ + void* + pkgconf_cross_personality_default (); // Never called. +} + +using namespace details; + +template <typename H> +static inline pkgconf_client_t* +call_pkgconf_client_new (pkgconf_client_t* (*f) (H, void*), + H error_handler, + void* error_handler_data) +{ + return f (error_handler, error_handler_data); +} + +template <typename H, typename P> +static inline pkgconf_client_t* +call_pkgconf_client_new (pkgconf_client_t* (*f) (H, void*, P), + H error_handler, + void* error_handler_data) +{ + return f (error_handler, + error_handler_data, + ::pkgconf_cross_personality_default ()); +} + +namespace build2 +{ + namespace cc + { + // The libpkgconf library is not thread-safe, even on the pkgconf_client_t + // level (see issue #128 for details). While it seems that the obvious + // thread-safety issues are fixed, the default personality initialization, + // which is still not thread-safe. So let's keep the mutex for now not to + // introduce potential issues. + // + static mutex pkgconf_mutex; + + // The package dependency traversal depth limit. + // + static const int pkgconf_max_depth = 100; + + // Normally the error_handler() callback can be called multiple times to + // report a single error (once per message line), to produce a multi-line + // message like this: + // + // Package foo was not found in the pkg-config search path.\n + // Perhaps you should add the directory containing `foo.pc'\n + // to the PKG_CONFIG_PATH environment variable\n + // Package 'foo', required by 'bar', not found\n + // + // For the above example callback will be called 4 times. To suppress all + // the junk we will use PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS to get just: + // + // Package 'foo', required by 'bar', not found\n + // + // Also disable merging options like -framework into a single fragment, if + // possible. + // + static const int pkgconf_flags = + PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS + | PKGCONF_PKG_PKGF_SKIP_PROVIDES +#ifdef PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS + | PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS +#endif + ; + +#if defined(LIBPKGCONF_VERSION) && LIBPKGCONF_VERSION >= 10900 + static bool + pkgconf_error_handler (const char* msg, + const pkgconf_client_t*, + void*) +#else + static bool + pkgconf_error_handler (const char* msg, + const pkgconf_client_t*, + const void*) +#endif + { + error << runtime_error (msg); // Sanitize the message (trailing dot). + return true; + } + + // Deleters. Note that they are thread-safe. + // + struct fragments_deleter + { + void operator() (pkgconf_list_t* f) const {pkgconf_fragment_free (f);} + }; + + // Convert fragments to strings. Skip the -I/-L options that refer to system + // directories. + // + static strings + to_strings (const pkgconf_list_t& frags, + char type, + const pkgconf_list_t& sysdirs) + { + assert (type == 'I' || type == 'L'); + + strings r; + + auto add = [&r] (const pkgconf_fragment_t* frag) + { + string s; + if (frag->type != '\0') + { + s += '-'; + s += frag->type; + } + + s += frag->data; + r.push_back (move (s)); + }; + + // Option that is separated from its value, for example: + // + // -I /usr/lib + // + const pkgconf_fragment_t* opt (nullptr); + + pkgconf_node_t *node; + PKGCONF_FOREACH_LIST_ENTRY(frags.head, node) + { + auto frag (static_cast<const pkgconf_fragment_t*> (node->data)); + + // Add the separated option and directory, unless the latest is a + // system one. + // + if (opt != nullptr) + { + // Note that we should restore the directory path that was + // (mis)interpreted as an option, for example: + // + // -I -Ifoo + // + // In the above example option '-I' is followed by directory + // '-Ifoo', which is represented by libpkgconf library as fragment + // 'foo' with type 'I'. + // + if (!pkgconf_path_match_list ( + frag->type == '\0' + ? frag->data + : (string ({'-', frag->type}) + frag->data).c_str (), + &sysdirs)) + { + add (opt); + add (frag); + } + + opt = nullptr; + continue; + } + + // Skip the -I/-L option if it refers to a system directory. + // + if (frag->type == type) + { + // The option is separated from a value, that will (presumably) + // follow. + // + if (*frag->data == '\0') + { + opt = frag; + continue; + } + + if (pkgconf_path_match_list (frag->data, &sysdirs)) + continue; + } + + add (frag); + } + + if (opt != nullptr) // Add the dangling option. + add (opt); + + return r; + } + + // Note that some libpkgconf functions can potentially return NULL, + // failing to allocate the required memory block. However, we will not + // check the returned value for NULL as the library doesn't do so, prior + // to filling the allocated structures. So such a code complication on our + // side would be useless. Also, for some functions the NULL result has a + // special semantics, for example "not found". + // + pkgconfig:: + pkgconfig (path_type p, + const dir_paths& pc_dirs, + const dir_paths& sys_lib_dirs, + const dir_paths& sys_hdr_dirs) + : path (move (p)) + { + auto add_dirs = [] (pkgconf_list_t& dir_list, + const dir_paths& dirs, + bool suppress_dups, + bool cleanup = false) + { + if (cleanup) + { + pkgconf_path_free (&dir_list); + dir_list = PKGCONF_LIST_INITIALIZER; + } + + for (const auto& d: dirs) + pkgconf_path_add (d.string ().c_str (), &dir_list, suppress_dups); + }; + + mlock l (pkgconf_mutex); + + // Initialize the client handle. + // + unique_ptr<pkgconf_client_t, void (*) (pkgconf_client_t*)> c ( + call_pkgconf_client_new (&pkgconf_client_new, + pkgconf_error_handler, + nullptr /* handler_data */), + [] (pkgconf_client_t* c) {pkgconf_client_free (c);}); + + pkgconf_client_set_flags (c.get (), pkgconf_flags); + + // Note that the system header and library directory lists are + // automatically pre-filled by the pkgconf_client_new() call (see + // above). We will re-create these lists from scratch. + // + add_dirs (c->filter_libdirs, + sys_lib_dirs, + false /* suppress_dups */, + true /* cleanup */); + + add_dirs (c->filter_includedirs, + sys_hdr_dirs, + false /* suppress_dups */, + true /* cleanup */); + + // Note that the loaded file directory is added to the (yet empty) + // search list. Also note that loading of the prerequisite packages is + // delayed until flags retrieval, and their file directories are not + // added to the search list. + // + pkg_ = pkgconf_pkg_find (c.get (), path.string ().c_str ()); + + if (pkg_ == nullptr) + fail << "package '" << path << "' not found or invalid"; + + // Add the .pc file search directories. + // + assert (c->dir_list.length == 1); // Package file directory (see above). + add_dirs (c->dir_list, pc_dirs, true /* suppress_dups */); + + client_ = c.release (); + } + + void pkgconfig:: + free () + { + assert (pkg_ != nullptr); + + mlock l (pkgconf_mutex); + pkgconf_pkg_unref (client_, pkg_); + pkgconf_client_free (client_); + } + + strings pkgconfig:: + cflags (bool stat) const + { + assert (client_ != nullptr); // Must not be empty. + + mlock l (pkgconf_mutex); + + pkgconf_client_set_flags ( + client_, + pkgconf_flags | + + // Walk through the private package dependencies (Requires.private) + // besides the public ones while collecting the flags. Note that we do + // this for both static and shared linking. + // + PKGCONF_PKG_PKGF_SEARCH_PRIVATE | + + // Collect flags from Cflags.private besides those from Cflags for the + // static linking. + // + (stat + ? PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS + : 0)); + + pkgconf_list_t f = PKGCONF_LIST_INITIALIZER; // Aggregate initialization. + int e (pkgconf_pkg_cflags (client_, pkg_, &f, pkgconf_max_depth)); + + if (e != PKGCONF_PKG_ERRF_OK) + throw failed (); // Assume the diagnostics is issued. + + unique_ptr<pkgconf_list_t, fragments_deleter> fd (&f); // Auto-deleter. + return to_strings (f, 'I', client_->filter_includedirs); + } + + strings pkgconfig:: + libs (bool stat) const + { + assert (client_ != nullptr); // Must not be empty. + + mlock l (pkgconf_mutex); + + pkgconf_client_set_flags ( + client_, + pkgconf_flags | + + // Additionally collect flags from the private dependency packages + // (see above) and from the Libs.private value for the static linking. + // + (stat + ? PKGCONF_PKG_PKGF_SEARCH_PRIVATE | + PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS + : 0)); + + pkgconf_list_t f = PKGCONF_LIST_INITIALIZER; // Aggregate initialization. + int e (pkgconf_pkg_libs (client_, pkg_, &f, pkgconf_max_depth)); + + if (e != PKGCONF_PKG_ERRF_OK) + throw failed (); // Assume the diagnostics is issued. + + unique_ptr<pkgconf_list_t, fragments_deleter> fd (&f); // Auto-deleter. + return to_strings (f, 'L', client_->filter_libdirs); + } + + optional<string> pkgconfig:: + variable (const char* name) const + { + assert (client_ != nullptr); // Must not be empty. + + mlock l (pkgconf_mutex); + const char* r (pkgconf_tuple_find (client_, &pkg_->vars, name)); + return r != nullptr ? optional<string> (r) : nullopt; + } + } +} + +#endif // BUILD2_BOOTSTRAP diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx index 75c7227..046fbc8 100644 --- a/libbuild2/cc/pkgconfig.cxx +++ b/libbuild2/cc/pkgconfig.cxx @@ -1,13 +1,6 @@ // file : libbuild2/cc/pkgconfig.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file -// In order not to complicate the bootstrap procedure with libpkgconf building -// exclude functionality that involves reading of .pc files. -// -#ifndef BUILD2_BOOTSTRAP -# include <libpkgconf/libpkgconf.h> -#endif - #include <libbuild2/scope.hxx> #include <libbuild2/target.hxx> #include <libbuild2/context.hxx> @@ -25,436 +18,25 @@ #include <libbuild2/cc/utility.hxx> #include <libbuild2/cc/common.hxx> +#include <libbuild2/cc/pkgconfig.hxx> #include <libbuild2/cc/compile-rule.hxx> #include <libbuild2/cc/link-rule.hxx> -#ifndef BUILD2_BOOTSTRAP - -// Note that the libpkgconf library doesn't provide the version macro that we -// could use to compile the code conditionally against different API versions. -// Thus, we need to sense the pkgconf_client_new() function signature -// ourselves to call it properly. -// -namespace details -{ - void* - pkgconf_cross_personality_default (); // Never called. -} - -using namespace details; - -template <typename H> -static inline pkgconf_client_t* -call_pkgconf_client_new (pkgconf_client_t* (*f) (H, void*), - H error_handler, - void* error_handler_data) -{ - return f (error_handler, error_handler_data); -} - -template <typename H, typename P> -static inline pkgconf_client_t* -call_pkgconf_client_new (pkgconf_client_t* (*f) (H, void*, P), - H error_handler, - void* error_handler_data) -{ - return f (error_handler, - error_handler_data, - ::pkgconf_cross_personality_default ()); -} - -#endif - -using namespace std; -using namespace butl; +using namespace std; // VC16 namespace build2 { -#ifndef BUILD2_BOOTSTRAP - - // Load package information from a .pc file. Filter out the -I/-L options - // that refer to system directories. This makes sure all the system search - // directories are "pushed" to the back which minimizes the chances of - // picking up wrong (e.g., old installed version) header/library. - // - // Note that the prerequisite package .pc files search order is as follows: - // - // - in directory of the specified file - // - in pc_dirs directories (in the natural order) - // - class pkgconf - { - public: - using path_type = build2::path; - - path_type path; - - public: - explicit - pkgconf (path_type, - const dir_paths& pc_dirs, - const dir_paths& sys_inc_dirs, - const dir_paths& sys_lib_dirs); - - // Create a special empty object. Querying package information on such - // an object is illegal. - // - pkgconf () = default; - - ~pkgconf (); - - // Movable-only type. - // - pkgconf (pkgconf&& p) - : path (move (p.path)), - client_ (p.client_), - pkg_ (p.pkg_) - { - p.client_ = nullptr; - p.pkg_ = nullptr; - } - - pkgconf& - operator= (pkgconf&& p) - { - if (this != &p) - { - this->~pkgconf (); - new (this) pkgconf (move (p)); // Assume noexcept move-construction. - } - return *this; - } - - pkgconf (const pkgconf&) = delete; - pkgconf& operator= (const pkgconf&) = delete; - - strings - cflags (bool stat) const; - - strings - libs (bool stat) const; - - string - variable (const char*) const; - - string - variable (const string& s) const {return variable (s.c_str ());} - - private: - // Keep them as raw pointers not to deal with API thread-unsafety in - // deleters and introducing additional mutex locks. - // - pkgconf_client_t* client_ = nullptr; - pkgconf_pkg_t* pkg_ = nullptr; - }; - - // Currently the library is not thread-safe, even on the pkgconf_client_t - // level (see issue #128 for details). - // - // @@ An update: seems that the obvious thread-safety issues are fixed. - // However, let's keep mutex locking for now not to introduce potential - // issues before we make sure that there are no other ones. - // - static mutex pkgconf_mutex; - - // The package dependency traversal depth limit. - // - static const int pkgconf_max_depth = 100; - - // Normally the error_handler() callback can be called multiple times to - // report a single error (once per message line), to produce a multi-line - // message like this: - // - // Package foo was not found in the pkg-config search path.\n - // Perhaps you should add the directory containing `foo.pc'\n - // to the PKG_CONFIG_PATH environment variable\n - // Package 'foo', required by 'bar', not found\n - // - // For the above example callback will be called 4 times. To suppress all the - // junk we will use PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS to get just: - // - // Package 'foo', required by 'bar', not found\n - // - // Also disable merging options like -framework into a single fragment, if - // possible. - // - static const int pkgconf_flags = - PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS -#ifdef PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS - | PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS -#endif - ; - - static bool - pkgconf_error_handler (const char* msg, const pkgconf_client_t*, const void*) - { - error << runtime_error (msg); // Sanitize the message. - return true; - } - - // Deleters. Note that they are thread-safe. - // - struct fragments_deleter - { - void operator() (pkgconf_list_t* f) const {pkgconf_fragment_free (f);} - }; - - // Convert fragments to strings. Skip the -I/-L options that refer to system - // directories. - // - static strings - to_strings (const pkgconf_list_t& frags, - char type, - const pkgconf_list_t& sysdirs) - { - assert (type == 'I' || type == 'L'); - - strings r; - - auto add = [&r] (const pkgconf_fragment_t* frag) - { - string s; - if (frag->type != '\0') - { - s += '-'; - s += frag->type; - } - - s += frag->data; - r.push_back (move (s)); - }; - - // Option that is separated from its value, for example: - // - // -I /usr/lib - // - const pkgconf_fragment_t* opt (nullptr); - - pkgconf_node_t *node; - PKGCONF_FOREACH_LIST_ENTRY(frags.head, node) - { - auto frag (static_cast<const pkgconf_fragment_t*> (node->data)); - - // Add the separated option and directory, unless the latest is a system - // one. - // - if (opt != nullptr) - { - // Note that we should restore the directory path that was - // (mis)interpreted as an option, for example: - // - // -I -Ifoo - // - // In the above example option '-I' is followed by directory '-Ifoo', - // which is represented by libpkgconf library as fragment 'foo' with - // type 'I'. - // - if (!pkgconf_path_match_list ( - frag->type == '\0' - ? frag->data - : (string ({'-', frag->type}) + frag->data).c_str (), - &sysdirs)) - { - add (opt); - add (frag); - } - - opt = nullptr; - continue; - } - - // Skip the -I/-L option if it refers to a system directory. - // - if (frag->type == type) - { - // The option is separated from a value, that will (presumably) follow. - // - if (*frag->data == '\0') - { - opt = frag; - continue; - } - - if (pkgconf_path_match_list (frag->data, &sysdirs)) - continue; - } - - add (frag); - } - - if (opt != nullptr) // Add the dangling option. - add (opt); - - return r; - } - - // Note that some libpkgconf functions can potentially return NULL, failing - // to allocate the required memory block. However, we will not check the - // returned value for NULL as the library doesn't do so, prior to filling the - // allocated structures. So such a code complication on our side would be - // useless. Also, for some functions the NULL result has a special semantics, - // for example "not found". - // - pkgconf:: - pkgconf (path_type p, - const dir_paths& pc_dirs, - const dir_paths& sys_lib_dirs, - const dir_paths& sys_inc_dirs) - : path (move (p)) - { - auto add_dirs = [] (pkgconf_list_t& dir_list, - const dir_paths& dirs, - bool suppress_dups, - bool cleanup = false) - { - if (cleanup) - { - pkgconf_path_free (&dir_list); - dir_list = PKGCONF_LIST_INITIALIZER; - } - - for (const auto& d: dirs) - pkgconf_path_add (d.string ().c_str (), &dir_list, suppress_dups); - }; - - mlock l (pkgconf_mutex); - - // Initialize the client handle. - // - unique_ptr<pkgconf_client_t, void (*) (pkgconf_client_t*)> c ( - call_pkgconf_client_new (&pkgconf_client_new, - pkgconf_error_handler, - nullptr /* handler_data */), - [] (pkgconf_client_t* c) {pkgconf_client_free (c);}); - - pkgconf_client_set_flags (c.get (), pkgconf_flags); - - // Note that the system header and library directory lists are - // automatically pre-filled by the pkgconf_client_new() call (see above). - // We will re-create these lists from scratch. - // - add_dirs (c->filter_libdirs, - sys_lib_dirs, - false /* suppress_dups */, - true /* cleanup */); - - add_dirs (c->filter_includedirs, - sys_inc_dirs, - false /* suppress_dups */, - true /* cleanup */); - - // Note that the loaded file directory is added to the (yet empty) search - // list. Also note that loading of the prerequisite packages is delayed - // until flags retrieval, and their file directories are not added to the - // search list. - // - pkg_ = pkgconf_pkg_find (c.get (), path.string ().c_str ()); - - if (pkg_ == nullptr) - fail << "package '" << path << "' not found or invalid"; - - // Add the .pc file search directories. - // - assert (c->dir_list.length == 1); // Package file directory (see above). - add_dirs (c->dir_list, pc_dirs, true /* suppress_dups */); - - client_ = c.release (); - } - - pkgconf:: - ~pkgconf () - { - if (client_ != nullptr) // Not empty. - { - assert (pkg_ != nullptr); - - mlock l (pkgconf_mutex); - pkgconf_pkg_unref (client_, pkg_); - pkgconf_client_free (client_); - } - } - - strings pkgconf:: - cflags (bool stat) const - { - assert (client_ != nullptr); // Must not be empty. - - mlock l (pkgconf_mutex); - - pkgconf_client_set_flags ( - client_, - pkgconf_flags | - - // Walk through the private package dependencies (Requires.private) - // besides the public ones while collecting the flags. Note that we do - // this for both static and shared linking. - // - PKGCONF_PKG_PKGF_SEARCH_PRIVATE | - - // Collect flags from Cflags.private besides those from Cflags for the - // static linking. - // - (stat - ? PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS - : 0)); - - pkgconf_list_t f = PKGCONF_LIST_INITIALIZER; // Aggregate initialization. - int e (pkgconf_pkg_cflags (client_, pkg_, &f, pkgconf_max_depth)); - - if (e != PKGCONF_PKG_ERRF_OK) - throw failed (); // Assume the diagnostics is issued. - - unique_ptr<pkgconf_list_t, fragments_deleter> fd (&f); // Auto-deleter. - return to_strings (f, 'I', client_->filter_includedirs); - } - - strings pkgconf:: - libs (bool stat) const - { - assert (client_ != nullptr); // Must not be empty. - - mlock l (pkgconf_mutex); - - pkgconf_client_set_flags ( - client_, - pkgconf_flags | - - // Additionally collect flags from the private dependency packages - // (see above) and from the Libs.private value for the static linking. - // - (stat - ? PKGCONF_PKG_PKGF_SEARCH_PRIVATE | - PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS - : 0)); - - pkgconf_list_t f = PKGCONF_LIST_INITIALIZER; // Aggregate initialization. - int e (pkgconf_pkg_libs (client_, pkg_, &f, pkgconf_max_depth)); - - if (e != PKGCONF_PKG_ERRF_OK) - throw failed (); // Assume the diagnostics is issued. - - unique_ptr<pkgconf_list_t, fragments_deleter> fd (&f); // Auto-deleter. - return to_strings (f, 'L', client_->filter_libdirs); - } - - string pkgconf:: - variable (const char* name) const - { - assert (client_ != nullptr); // Must not be empty. - - mlock l (pkgconf_mutex); - const char* r (pkgconf_tuple_find (client_, &pkg_->vars, name)); - return r != nullptr ? string (r) : string (); - } - -#endif - namespace cc { using namespace bin; // In pkg-config backslashes, spaces, etc are escaped with a backslash. // + // @@ TODO: handle empty values (save as ''?) + // + // Note: may contain variable expansions (e.g, ${pcfiledir}) so unclear + // if can use quoting. + // static string escape (const string& s) { @@ -481,6 +63,35 @@ namespace build2 return r; } + // Resolve metadata value type from type name. Return in the second half + // of the pair whether this is a dir_path-based type. + // + static pair<const value_type*, bool> + metadata_type (const string& tn) + { + bool d (false); + const value_type* r (nullptr); + + if (tn == "bool") r = &value_traits<bool>::value_type; + else if (tn == "int64") r = &value_traits<int64_t>::value_type; + else if (tn == "uint64") r = &value_traits<uint64_t>::value_type; + else if (tn == "string") r = &value_traits<string>::value_type; + else if (tn == "path") r = &value_traits<path>::value_type; + else if (tn == "dir_path") {r = &value_traits<dir_path>::value_type; d = true;} + else if (tn == "int64s") r = &value_traits<int64s>::value_type; + else if (tn == "uint64s") r = &value_traits<uint64s>::value_type; + else if (tn == "strings") r = &value_traits<strings>::value_type; + else if (tn == "paths") r = &value_traits<paths>::value_type; + else if (tn == "dir_paths") {r = &value_traits<dir_paths>::value_type; d = true;} + + return make_pair (r, d); + } + + // In order not to complicate the bootstrap procedure with libpkg-config + // building, exclude functionality that involves reading of .pc files. + // +#ifndef BUILD2_BOOTSTRAP + // Try to find a .pc file in the pkgconfig/ subdirectory of libd, trying // several names derived from stem. If not found, return false. If found, // load poptions, loptions, libs, and modules, set the corresponding @@ -497,9 +108,8 @@ namespace build2 // Also note that the bootstrapped version of build2 will not search for // .pc files, always returning false (see above for the reasoning). // -#ifndef BUILD2_BOOTSTRAP - // Derive pkgconf search directories from the specified library search + // Derive pkg-config search directories from the specified library search // directory passing them to the callback function for as long as it // returns false (e.g., not found). Return true if the callback returned // true. @@ -543,8 +153,8 @@ namespace build2 return false; } - // Search for the .pc files in the pkgconf directories that correspond to - // the specified library directory. If found, return static (first) and + // Search for the .pc files in the pkg-config directories that correspond + // to the specified library directory. If found, return static (first) and // shared (second) library .pc files. If common is false, then only // consider our .static/.shared files. // @@ -554,6 +164,8 @@ namespace build2 const string& stem, bool common) const { + tracer trace (x, "pkgconfig_search"); + // When it comes to looking for .pc files we have to decide where to // search (which directory(ies)) as well as what to search for (which // names). Suffix is our ".shared" or ".static" extension. @@ -575,28 +187,36 @@ namespace build2 // then you get something like zlib which calls it zlib.pc. So let's // just do it. // - f = dir; - f /= "lib"; - f += stem; - f += sfx; - f += ".pc"; - if (exists (f)) - return f; + // And as you think you've covered all the bases, someone decides to + // play with the case (libXau.* vs xau.pc). So let's also try the + // lower-case versions of the stem unless we are on a case-insensitive + // filesystem. + // + auto check = [&dir, & sfx, &f] (const string& n) + { + f = dir; + f /= n; + f += sfx; + f += ".pc"; + return exists (f); + }; - f = dir; - f /= stem; - f += sfx; - f += ".pc"; - if (exists (f)) + if (check ("lib" + stem) || check (stem)) return f; +#ifndef _WIN32 + string lstem (lcase (stem)); + + if (lstem != stem) + { + if (check ("lib" + lstem) || check (lstem)) + return f; + } +#endif + if (proj) { - f = dir; - f /= proj->string (); - f += sfx; - f += ".pc"; - if (exists (f)) + if (check (proj->string ())) return f; } @@ -636,15 +256,18 @@ namespace build2 if (pkgconfig_derive (libd, check)) { + l6 ([&]{trace << "found " << libd << stem << " in " + << (d.a.empty () ? d.a : d.s).directory ();}); + r.first = move (d.a); r.second = move (d.s); } return r; - }; + } bool common:: - pkgconfig_load (action a, + pkgconfig_load (optional<action> act, const scope& s, lib& lt, liba* at, @@ -653,7 +276,8 @@ namespace build2 const string& stem, const dir_path& libd, const dir_paths& top_sysd, - const dir_paths& top_usrd) const + const dir_paths& top_usrd, + pair<bool, bool> metaonly) const { assert (at != nullptr || st != nullptr); @@ -663,12 +287,16 @@ namespace build2 if (p.first.empty () && p.second.empty ()) return false; - pkgconfig_load (a, s, lt, at, st, p, libd, top_sysd, top_usrd); + pkgconfig_load ( + act, s, lt, at, st, p, libd, top_sysd, top_usrd, metaonly); return true; } + // Action should be absent if called during the load phase. If metaonly is + // true then only load the metadata. + // void common:: - pkgconfig_load (action a, + pkgconfig_load (optional<action> act, const scope& s, lib& lt, liba* at, @@ -676,7 +304,8 @@ namespace build2 const pair<path, path>& paths, const dir_path& libd, const dir_paths& top_sysd, - const dir_paths& top_usrd) const + const dir_paths& top_usrd, + pair<bool /* a */, bool /* s */> metaonly) const { tracer trace (x, "pkgconfig_load"); @@ -687,24 +316,120 @@ namespace build2 assert (!ap.empty () || !sp.empty ()); - // Extract --cflags and set them as lib?{}:export.poptions. Note that we - // still pass --static in case this is pkgconf which has Cflags.private. + const scope& rs (*s.root_scope ()); + + const dir_path* sysroot ( + cast_null<abs_dir_path> (rs["config.cc.pkgconfig.sysroot"])); + + // Append -I<dir> or -L<dir> option suppressing duplicates. Also handle + // the sysroot rewrite. + // + auto append_dir = [sysroot] (strings& ops, string&& o) + { + char c (o[1]); + + // @@ Should we normalize the path for good measure? But on the other + // hand, most of the time when it's not normalized, it will likely + // be "consistently-relative", e.g., something like + // ${prefix}/lib/../include. I guess let's wait and see for some + // real-world examples. + // + // Well, we now support generating relocatable .pc files that have + // a bunch of -I${pcfiledir}/../../include and -L${pcfiledir}/.. . + // + // On the other hand, there could be symlinks involved and just + // normalize() may not be correct. + // + // Note that we do normalize -L paths in the usrd logic later + // (but not when setting as *.export.loptions). + + if (sysroot != nullptr) + { + // Notes: + // + // - The path might not be absolute (we only rewrite absolute ones). + // + // - Do this before duplicate suppression since options in ops + // already have the sysroot rewritten. + // + // - Check if the path already starts with sysroot since some .pc + // files might already be in a good shape (e.g., because they use + // ${pcfiledir} to support relocation properly). + // + const char* op (o.c_str () + 2); + size_t on (o.size () - 2); + + if (path_traits::absolute (op, on)) + { + const string& s (sysroot->string ()); + + const char* sp (s.c_str ()); + size_t sn (s.size ()); + + if (!path_traits::sub (op, on, sp, sn)) // Already in sysroot. + { + // Find the first directory seperator that seperates the root + // component from the rest of the path (think /usr/include, + // c:\install\include). We need to replace the root component + // with sysroot. If there is no separator (say, -Ic:) or the + // path after the separator is empty (say, -I/), then we replace + // the entire path. + // + size_t p (path_traits::find_separator (o, 2)); + if (p == string::npos || p + 1 == o.size ()) + p = o.size (); + + o.replace (2, p - 2, s); + } + } + } + + for (const string& x: ops) + { + if (x.size () > 2 && x[0] == '-' && x[1] == c) + { + if (path_traits::compare (x.c_str () + 2, x.size () - 2, + o.c_str () + 2, o.size () - 2) == 0) + return; // Duplicate. + } + } + + ops.push_back (move (o)); + }; + + // Extract --cflags and set them as lib?{}:export.poptions returing the + // pointer to the set value. If [as]pops are not NULL, then only keep + // options that are present in both. // - auto parse_cflags = [&trace, this] (target& t, - const pkgconf& pc, - bool la) + auto parse_cflags =[&trace, + this, + &append_dir] (target& t, + const pkgconfig& pc, + bool la, + const strings* apops = nullptr, + const strings* spops = nullptr) + -> const strings* { + // Note that we normalize `-[IDU] <arg>` to `-[IDU]<arg>`. + // strings pops; - bool arg (false); - for (auto& o: pc.cflags (la)) + char arg ('\0'); // Option with pending argument. + for (string& o: pc.cflags (la)) { if (arg) { // Can only be an argument for -I, -D, -U options. // - pops.push_back (move (o)); - arg = false; + o.insert (0, 1, arg); + o.insert (0, 1, '-'); + + if (arg == 'I') + append_dir (pops, move (o)); + else + pops.push_back (move (o)); + + arg = '\0'; continue; } @@ -713,11 +438,17 @@ namespace build2 // We only keep -I, -D and -U. // if (n >= 2 && - o[0] == '-' && - (o[1] == 'I' || o[1] == 'D' || o[1] == 'U')) + o[0] == '-' && (o[1] == 'I' || o[1] == 'D' || o[1] == 'U')) { - pops.push_back (move (o)); - arg = (n == 2); + if (n > 2) + { + if (o[1] == 'I') + append_dir (pops, move (o)); + else + pops.push_back (move (o)); + } + else + arg = o[1]; continue; } @@ -726,7 +457,7 @@ namespace build2 } if (arg) - fail << "argument expected after " << pops.back () << + fail << "argument expected after -" << arg << info << "while parsing pkg-config --cflags " << pc.path; if (!pops.empty ()) @@ -739,19 +470,45 @@ namespace build2 // export stub and we shouldn't touch them. // if (p.second) - p.first.get () = move (pops); + { + // If required, only keep common stuff. While removing the entries + // is not the most efficient way, it is simple. + // + if (apops != nullptr || spops != nullptr) + { + for (auto i (pops.begin ()); i != pops.end (); ) + { + if ((apops != nullptr && find ( + apops->begin (), apops->end (), *i) == apops->end ()) || + (spops != nullptr && find ( + spops->begin (), spops->end (), *i) == spops->end ())) + i = pops.erase (i); + else + ++i; + } + } + + p.first = move (pops); + return &p.first.as<strings> (); + } } + + return nullptr; }; // Parse --libs into loptions/libs (interface and implementation). If - // ps is not NULL, add each resolves library target as a prerequisite. + // ps is not NULL, add each resolved library target as a prerequisite. // - auto parse_libs = [a, &s, top_sysd, this] (target& t, - bool binless, - const pkgconf& pc, - bool la, - prerequisites* ps) + auto parse_libs = [this, + &append_dir, + act, &s, top_sysd] (target& t, + bool binless, + const pkgconfig& pc, + bool la, + prerequisites* ps) { + // Note that we normalize `-L <arg>` to `-L<arg>`. + // strings lops; vector<name> libs; @@ -760,22 +517,29 @@ namespace build2 // library is binless. But sometimes we may have other linker options, // for example, -Wl,... or -pthread. It's probably a bad idea to // ignore them. Also, theoretically, we could have just the library - // name/path. + // name/path. Note that (after some meditation) we consider -pthread + // a special form of -l. // // The tricky part, of course, is to know whether what follows after // an option we don't recognize is its argument or another option or // library. What we do at the moment is stop recognizing just library // names (without -l) after seeing an unknown option. // - bool arg (false), first (true), known (true), have_L; - for (auto& o: pc.libs (la)) + bool first (true), known (true), have_L (false); + + string self; // The library itself (-l of just name/path). + + char arg ('\0'); // Option with pending argument. + for (string& o: pc.libs (la)) { if (arg) { - // Can only be an argument for an loption. + // Can only be an argument for an -L option. // - lops.push_back (move (o)); - arg = false; + o.insert (0, 1, arg); + o.insert (0, 1, '-'); + append_dir (lops, move (o)); + arg = '\0'; continue; } @@ -785,44 +549,54 @@ namespace build2 // if (n >= 2 && o[0] == '-' && o[1] == 'L') { + if (n > 2) + append_dir (lops, move (o)); + else + arg = o[1]; have_L = true; - lops.push_back (move (o)); - arg = (n == 2); continue; } - // See if that's -l or just the library name/path. + // See if that's -l, -pthread, or just the library name/path. // - if ((known && o[0] != '-') || - (n > 2 && o[0] == '-' && o[1] == 'l')) + if ((known && n != 0 && o[0] != '-') || + (n > 2 && o[0] == '-' && (o[1] == 'l' || o == "-pthread"))) { // Unless binless, the first one is the library itself, which we // skip. Note that we don't verify this and theoretically it could // be some other library, but we haven't encountered such a beast // yet. // + // What we have enountered (e.g., in the Magick++ library) is the + // library itself repeated in Libs.private. So now we save it and + // filter all its subsequent occurences. + // + // @@ To be safe we probably shouldn't rely on the position and + // filter out all occurrences of the library itself (by name?) + // and complain if none were encountered. + // + // Note also that the same situation can occur if we have a + // binful library for which we could not find the library + // binary and are treating it as binless. We now have a diag + // frame around the call to search_library() to help diagnose + // such situations. + // if (first) { first = false; if (!binless) + { + self = move (o); + continue; + } + } + else + { + if (!binless && o == self) continue; } - // @@ If by some reason this is the library itself (doesn't go - // first or libpkgconf parsed libs in some bizarre way) we will - // have a dependency cycle by trying to lock its target inside - // search_library() as by now it is already locked. To be safe - // we probably shouldn't rely on the position and filter out - // all occurrences of the library itself (by name?) and - // complain if none were encountered. - // - // Note also that the same situation can occur if we have a - // binful library for which we could not find the library - // binary and are treating it as binless. We now have a diag - // frame around the call to search_library() to help diagnose - // such situations. - // libs.push_back (name (move (o))); continue; } @@ -834,7 +608,7 @@ namespace build2 } if (arg) - fail << "argument expected after " << lops.back () << + fail << "argument expected after -" << arg << info << "while parsing pkg-config --libs " << pc.path; // Space-separated list of escaped library flags. @@ -842,7 +616,7 @@ namespace build2 auto lflags = [&pc, la] () -> string { string r; - for (const auto& o: pc.libs (la)) + for (const string& o: pc.libs (la)) { if (!r.empty ()) r += ' '; @@ -851,7 +625,7 @@ namespace build2 return r; }; - if (first && !binless) + if (!binless && self.empty ()) fail << "library expected in '" << lflags () << "'" << info << "while parsing pkg-config --libs " << pc.path; @@ -864,8 +638,8 @@ namespace build2 // import installed, or via a .pc file (which we could have generated // from the export stub). The exception is "runtime libraries" (which // are really the extension of libc or the operating system in case of - // Windows) such as -lm, -ldl, -lpthread, etc. Those we will detect - // and leave as -l*. + // Windows) such as -lm, -ldl, -lpthread (or its -pthread variant), + // etc. Those we will detect and leave as -l*. // // If we managed to resolve all the -l's (sans runtime), then we can // omit -L's for a nice and tidy command line. @@ -891,26 +665,57 @@ namespace build2 if (l[0] != '-') // e.g., just shell32.lib continue; - else if (cmp ("advapi32") || - cmp ("bcrypt") || - cmp ("crypt32") || - cmp ("d3d", 3) || // d3d* - cmp ("gdi32") || - cmp ("imagehlp") || - cmp ("mswsock") || - cmp ("msxml", 5) || // msxml* - cmp ("normaliz") || - cmp ("odbc32") || - cmp ("ole32") || - cmp ("oleaut32") || - cmp ("rpcrt4") || - cmp ("secur32") || - cmp ("shell32") || - cmp ("shlwapi") || - cmp ("version") || - cmp ("ws2") || - cmp ("ws2_32") || - cmp ("wsock32")) + else if (cmp ("advapi32") || + cmp ("authz") || + cmp ("bcrypt") || + cmp ("comdlg32") || + cmp ("crypt32") || + cmp ("d2d1") || + cmp ("d3d", 3) || // d3d* + cmp ("dbgeng") || + cmp ("dbghelp") || + cmp ("dnsapi") || + cmp ("dwmapi") || + cmp ("dwrite") || + cmp ("dxgi") || + cmp ("dxguid") || + cmp ("gdi32") || + cmp ("glu32") || + cmp ("imagehlp") || + cmp ("imm32") || + cmp ("iphlpapi") || + cmp ("kernel32") || + cmp ("mincore") || + cmp ("mpr") || + cmp ("msimg32") || + cmp ("mswsock") || + cmp ("msxml", 5) || // msxml* + cmp ("netapi32") || + cmp ("normaliz") || + cmp ("odbc32") || + cmp ("ole32") || + cmp ("oleaut32") || + cmp ("opengl32") || + cmp ("powrprof") || + cmp ("psapi") || + cmp ("rpcrt4") || + cmp ("secur32") || + cmp ("setupapi") || + cmp ("shell32") || + cmp ("shlwapi") || + cmp ("synchronization") || + cmp ("user32") || + cmp ("userenv") || + cmp ("uuid") || + cmp ("version") || + cmp ("windowscodecs") || + cmp ("winhttp") || + cmp ("winmm") || + cmp ("winspool") || + cmp ("ws2") || + cmp ("ws2_32") || + cmp ("wsock32") || + cmp ("wtsapi32")) { if (tsys == "win32-msvc") { @@ -921,6 +726,11 @@ namespace build2 } continue; } + else if (tsys == "mingw32") + { + if (l == "-pthread") + continue; + } } else { @@ -930,6 +740,7 @@ namespace build2 l == "-lm" || l == "-ldl" || l == "-lrt" || + l == "-pthread" || l == "-lpthread") continue; @@ -948,7 +759,11 @@ namespace build2 } else if (tclass == "macos") { - if (l == "-lSystem") + // Note that Mac OS has libiconv in /usr/lib/ which only comes + // in the shared variant. So we treat it as system. + // + if (l == "-lSystem" || + l == "-liconv") continue; } else if (tclass == "bsd") @@ -965,18 +780,13 @@ namespace build2 { usrd = dir_paths (); - for (auto i (lops.begin ()); i != lops.end (); ++i) + for (const string& o: lops) { - const string& o (*i); - - if (o.size () >= 2 && o[0] == '-' && o[1] == 'L') + // Note: always in the -L<dir> form (see above). + // + if (o.size () > 2 && o[0] == '-' && o[1] == 'L') { - string p; - - if (o.size () == 2) - p = *++i; // We've verified it's there. - else - p = string (o, 2); + string p (o, 2); try { @@ -987,6 +797,7 @@ namespace build2 << lflags () << "'" << info << "while parsing pkg-config --libs " << pc.path; + d.normalize (); usrd->push_back (move (d)); } catch (const invalid_path& e) @@ -1017,7 +828,7 @@ namespace build2 dr << info (f) << "while resolving pkg-config dependency " << l; }); - lt = search_library (a, top_sysd, usrd, pk); + lt = search_library (act, top_sysd, usrd, pk); } if (lt != nullptr) @@ -1066,40 +877,32 @@ namespace build2 { // Translate -L to /LIBPATH. // - for (auto i (lops.begin ()); i != lops.end (); ) + for (string& o: lops) { - string& o (*i); size_t n (o.size ()); - if (n >= 2 && o[0] == '-' && o[1] == 'L') + // Note: always in the -L<dir> form (see above). + // + if (n > 2 && o[0] == '-' && o[1] == 'L') { o.replace (0, 2, "/LIBPATH:"); - - if (n == 2) - { - o += *++i; // We've verified it's there. - i = lops.erase (i); - continue; - } } - - ++i; } } auto p (t.vars.insert (c_export_loptions)); if (p.second) - p.first.get () = move (lops); + p.first = move (lops); } // Set even if empty (export override). // { - auto p (t.vars.insert (la ? c_export_imp_libs : c_export_libs)); + auto p (t.vars.insert (la ? c_export_impl_libs : c_export_libs)); if (p.second) - p.first.get () = move (libs); + p.first = move (libs); } }; @@ -1107,6 +910,10 @@ namespace build2 // may escape things even on non-Windows platforms, for example, // spaces. So we use a slightly modified version of next_word(). // + // @@ TODO: handle quotes (e.g., empty values; see parse_metadata()). + // I wonder what we get here if something is quoted in the + // .pc file. + // auto next = [] (const string& s, size_t& b, size_t& e) -> string { string r; @@ -1142,15 +949,123 @@ namespace build2 return r; }; - // Parse modules and add them to the prerequisites. + // Parse the build2.metadata variable value and, if user is true, + // extract the user metadata, if any, and set extracted variables on the + // specified target. // - auto parse_modules = [&trace, &next, &s, this] - (const pkgconf& pc, prerequisites& ps) + auto parse_metadata = [&next] (target& t, + pkgconfig& pc, + const string& md, + bool user) { - string mstr (pc.variable ("cxx_modules")); + const location loc (pc.path); + + context& ctx (t.ctx); + + optional<uint64_t> ver; + optional<string> pfx; + + variable_pool* vp (nullptr); // Resolve lazily. + + string s; + for (size_t b (0), e (0); !(s = next (md, b, e)).empty (); ) + { + if (!ver) + { + try + { + ver = value_traits<uint64_t>::convert (name (s), nullptr); + } + catch (const invalid_argument& e) + { + fail (loc) << "invalid version in build2.metadata variable: " + << e; + } + + if (*ver != 1) + fail (loc) << "unexpected metadata version " << *ver; + + if (!user) + return; + + continue; + } + + if (!pfx) + { + if (s.empty ()) + fail (loc) << "empty variable prefix in build2.metadata varible"; + + pfx = s; + continue; + } + + // The rest is variable name/type pairs. + // + size_t p (s.find ('/')); + + if (p == string::npos) + fail (loc) << "expected name/type pair instead of '" << s << "'"; + + string vn (s, 0, p); + string tn (s, p + 1); + + optional<string> val (pc.variable (vn)); + + if (!val) + fail (loc) << "metadata variable " << vn << " not set"; + + pair<const value_type*, bool> vt (metadata_type (tn)); + if (vt.first == nullptr) + fail (loc) << "unknown metadata type " << tn; + + names ns; + for (size_t b (0), e (0); !(s = next (*val, b, e)).empty (); ) + { + ns.push_back (vt.second + ? name (dir_path (move (s))) + : name (move (s))); + } + + // These should be public (qualified) variables so go straight for + // the public variable pool. + // + if (vp == nullptr) + vp = &ctx.var_pool.rw (); // Load phase if user==true. + + const variable& var (vp->insert (move (vn))); + + value& v (t.assign (var)); + v.assign (move (ns), &var); + typify (v, *vt.first, &var); + } + + if (!ver) + fail (loc) << "version expected in build2.metadata variable"; + + if (!pfx) + return; // No user metadata. + + // Set export.metadata to indicate the presence of user metadata. + // + t.assign (ctx.var_export_metadata) = names { + name (std::to_string (*ver)), name (move (*pfx))}; + }; + + // Parse modules, enter them as targets, and add them to the + // prerequisites. + // + auto parse_modules = [&trace, this, + &next, &s, <] (const pkgconfig& pc, + prerequisites& ps) + { + optional<string> val (pc.variable ("cxx.modules")); + + if (!val) + return; string m; - for (size_t b (0), e (0); !(m = next (mstr, b, e)).empty (); ) + for (size_t b (0), e (0); !(m = next (*val, b, e)).empty (); ) { // The format is <name>=<path> with `..` used as a partition // separator (see pkgconfig_save() for details). @@ -1159,18 +1074,26 @@ namespace build2 if (p == string::npos || p == 0 || // Empty name. p == m.size () - 1) // Empty path. - fail << "invalid module information in '" << mstr << "'" << - info << "while parsing pkg-config --variable=cxx_modules " + fail << "invalid module information in '" << *val << "'" << + info << "while parsing pkg-config --variable=cxx.modules " << pc.path; string mn (m, 0, p); path mp (m, p + 1, string::npos); + + // Must be absolute but may not be normalized due to a relocatable + // .pc file. We assume there are no symlink shenanigans that would + // require realize(). + // + if (!mp.normalized ()) + mp.normalize (); + path mf (mp.leaf ()); // Extract module properties, if any. // - string pp (pc.variable ("cxx_module_preprocessed." + mn)); - string se (pc.variable ("cxx_module_symexport." + mn)); + optional<string> pp (pc.variable ("cxx.module_preprocessed." + mn)); + optional<string> se (pc.variable ("cxx.module_symexport." + mn)); // Replace the partition separator. // @@ -1189,16 +1112,16 @@ namespace build2 target_decl::implied, trace)); - target& mt (tl.first); + file& mt (tl.first.as<file> ()); // If the target already exists, then setting its variables is not // MT-safe. So currently we only do it if we have the lock (and thus - // nobody can see this target yet) assuming that this has already + // nobody can see this target yet) verifying that this has already // been done otherwise. // // @@ This is not quite correct, though: this target could already // exist but for a "different purpose" (e.g., it could be used as - // a header). + // a header). Well, maybe it shouldn't. // // @@ Could setting it in the rule-specific vars help? (But we // are not matching a rule for it.) Note that we are setting @@ -1207,41 +1130,107 @@ namespace build2 // if (tl.second.owns_lock ()) { + mt.path (move (mp)); mt.vars.assign (c_module_name) = move (mn); // Set module properties. Note that if unspecified we should still // set them to their default values since the hosting project may - // have them set to incompatible value. + // have them set to incompatible values. // { value& v (mt.vars.assign (x_preprocessed)); // NULL - if (!pp.empty ()) v = move (pp); + if (pp) + v = move (*pp); } { - mt.vars.assign (x_symexport) = (se == "true"); + mt.vars.assign (x_symexport) = (se && *se == "true"); } tl.second.unlock (); } + else + { + if (!mt.vars[c_module_name]) + fail << "unexpected metadata for module target " << mt << + info << "module is expected to have assigned name" << + info << "make sure this module is used via " << lt + << " prerequisite"; + } ps.push_back (prerequisite (mt)); } }; - // For now we only populate prerequisites for lib{}. To do it for - // liba{} would require weeding out duplicates that are already in - // lib{}. - // - // Currently, this information is only used by the modules machinery to - // resolve module names to module files (but we cannot only do this if - // modules are enabled since the same installed library can be used by - // multiple builds). + // Parse importable headers, enter them as targets, and add them to + // the prerequisites. // - prerequisites prs; + auto parse_headers = [&trace, this, + &next, &s, <] (const pkgconfig& pc, + const target_type& tt, + const char* lang, + prerequisites& ps) + { + string var (string (lang) + ".importable_headers"); + optional<string> val (pc.variable (var)); + + if (!val) + return; + + string h; + for (size_t b (0), e (0); !(h = next (*val, b, e)).empty (); ) + { + path hp (move (h)); + + // Must be absolute but may not be normalized due to a relocatable + // .pc file. We assume there are no symlink shenanigans that would + // require realize(). + // + if (!hp.normalized ()) + hp.normalize (); + + path hf (hp.leaf ()); + + auto tl ( + s.ctx.targets.insert_locked ( + tt, + hp.directory (), + dir_path (), + hf.base ().string (), + hf.extension (), + target_decl::implied, + trace)); + + file& ht (tl.first.as<file> ()); + + // If the target already exists, then setting its variables is not + // MT-safe. So currently we only do it if we have the lock (and thus + // nobody can see this target yet) verifying that this has already + // been done otherwise. + // + if (tl.second.owns_lock ()) + { + ht.path (move (hp)); + ht.vars.assign (c_importable) = true; + tl.second.unlock (); + } + else + { + if (!cast_false<bool> (ht.vars[c_importable])) + fail << "unexpected metadata for existing header target " << ht << + info << "header is expected to be marked importable" << + info << "make sure this header is used via " << lt + << " prerequisite"; + } - pkgconf apc; - pkgconf spc; + ps.push_back (prerequisite (ht)); + } + }; + + // Load the information from the pkg-config files. + // + pkgconfig apc; + pkgconfig spc; // Create the .pc files search directory list. // @@ -1249,9 +1238,16 @@ namespace build2 // Note that we rely on the "small function object" optimization here. // - auto add_pc_dir = [&pc_dirs] (dir_path&& d) -> bool + auto add_pc_dir = [&trace, &pc_dirs] (dir_path&& d) -> bool { - pc_dirs.emplace_back (move (d)); + // Suppress duplicated. + // + if (find (pc_dirs.begin (), pc_dirs.end (), d) == pc_dirs.end ()) + { + l6 ([&]{trace << "search path " << d;}); + pc_dirs.emplace_back (move (d)); + } + return false; }; @@ -1261,18 +1257,115 @@ namespace build2 bool pa (at != nullptr && !ap.empty ()); if (pa || sp.empty ()) - apc = pkgconf (ap, pc_dirs, sys_lib_dirs, sys_inc_dirs); + apc = pkgconfig (ap, pc_dirs, sys_lib_dirs, sys_hdr_dirs); bool ps (st != nullptr && !sp.empty ()); if (ps || ap.empty ()) - spc = pkgconf (sp, pc_dirs, sys_lib_dirs, sys_inc_dirs); + spc = pkgconfig (sp, pc_dirs, sys_lib_dirs, sys_hdr_dirs); + + // Load the user metadata if we are in the load phase. Otherwise just + // determine if we have metadata. + // + // Note also that we are not failing here if the metadata was requested + // but not present (potentially only partially) letting the caller + // (i.e., the import machinery) verify that the export.metadata was set + // on the target being imported. This would also allow supporting + // optional metadata. + // + bool apc_meta (false); + bool spc_meta (false); + if (!act) + { + // We can only do it during the load phase. + // + assert (lt.ctx.phase == run_phase::load); + + pkgconfig& ipc (ps ? spc : apc); // As below. + + // Since it's not easy to say if things are the same, we load a copy + // into the group and each member, if any. + // + // @@ TODO: check if already loaded? Don't we have the same problem + // below with reloading the rest for lt? What if we passed NULL + // in this case (and I suppose another bool in metaonly)? + // + if (optional<string> md = ipc.variable ("build2.metadata")) + parse_metadata (lt, ipc, *md, true); + + if (pa) + { + if (optional<string> md = apc.variable ("build2.metadata")) + { + parse_metadata (*at, apc, *md, true); + apc_meta = true; + } + } + + if (ps) + { + if (optional<string> md = spc.variable ("build2.metadata")) + { + parse_metadata (*st, spc, *md, true); + spc_meta = true; + } + } + + // If we only need metadata, then we are done. + // + if (at != nullptr && metaonly.first) + { + pa = false; + at = nullptr; + } + + if (st != nullptr && metaonly.second) + { + ps = false; + st = nullptr; + } + + if (at == nullptr && st == nullptr) + return; + } + else + { + if (pa) + { + if (optional<string> md = apc.variable ("build2.metadata")) + { + parse_metadata (*at, apc, *md, false); + apc_meta = true; + } + } + + if (ps) + { + if (optional<string> md = spc.variable ("build2.metadata")) + { + parse_metadata (*st, spc, *md, false); + spc_meta = true; + } + } + } // Sort out the interface dependencies (which we are setting on lib{}). // If we have the shared .pc variant, then we use that. Otherwise -- // static but extract without the --static option (see also the saving // logic). // - pkgconf& ipc (ps ? spc : apc); // Interface package info. + pkgconfig& ipc (ps ? spc : apc); // Interface package info. + bool ipc_meta (ps ? spc_meta : apc_meta); + + // For now we only populate prerequisites for lib{}. To do it for + // liba{} would require weeding out duplicates that are already in + // lib{}. + // + // Currently, this information is only used by the modules machinery to + // resolve module names to module files (but we cannot only do this if + // modules are enabled since the same installed library can be used by + // multiple builds). + // + prerequisites prs; parse_libs ( lt, @@ -1281,23 +1374,61 @@ namespace build2 false, &prs); + const strings* apops (nullptr); if (pa) { - parse_cflags (*at, apc, true); + apops = parse_cflags (*at, apc, true); parse_libs (*at, at->path ().empty (), apc, true, nullptr); } + const strings* spops (nullptr); if (ps) - parse_cflags (*st, spc, false); + spops = parse_cflags (*st, spc, false); + + // Also set common poptions for the group. In particular, this makes + // sure $lib_poptions() in the "common interface" mode works for the + // installed libraries. + // + // Note that if there are no poptions set for either, then we cannot + // possibly have a common subset. + // + if (apops != nullptr || spops != nullptr) + parse_cflags (lt, ipc, false, apops, spops); + + // @@ TODO: we can now load cc.type if there is metadata (but need to + // return this rather than set, see search_library() for + // details). + + // Load the bin.whole flag (whole archive). + // + if (at != nullptr && (pa ? apc_meta : spc_meta)) + { + // Note that if unspecified we leave it unset letting the consumer + // override it, if necessary (see the bin.lib lookup semantics for + // details). + // + if (optional<string> v = (pa ? apc : spc).variable ("bin.whole")) + { + at->vars.assign ("bin.whole") = (*v == "true"); + } + } // For now we assume static and shared variants export the same set of - // modules. While technically possible, having a different set will most - // likely lead to all sorts of complications (at least for installed - // libraries) and life is short. + // modules/importable headers. While technically possible, having + // different sets will most likely lead to all sorts of complications + // (at least for installed libraries) and life is short. // - if (modules) + if (modules && ipc_meta) + { parse_modules (ipc, prs); + // We treat headers outside of any project as C headers (see + // enter_header() for details). + // + parse_headers (ipc, h::static_type /* **x_hdrs */, x, prs); + parse_headers (ipc, h::static_type, "c", prs); + } + assert (!lt.has_prerequisites ()); if (!prs.empty ()) lt.prerequisites (move (prs)); @@ -1315,7 +1446,7 @@ namespace build2 } bool common:: - pkgconfig_load (action, + pkgconfig_load (optional<action>, const scope&, lib&, liba*, @@ -1324,13 +1455,14 @@ namespace build2 const string&, const dir_path&, const dir_paths&, - const dir_paths&) const + const dir_paths&, + pair<bool, bool>) const { return false; } void common:: - pkgconfig_load (action, + pkgconfig_load (optional<action>, const scope&, lib&, liba*, @@ -1338,7 +1470,8 @@ namespace build2 const pair<path, path>&, const dir_path&, const dir_paths&, - const dir_paths&) const + const dir_paths&, + pair<bool, bool>) const { assert (false); // Should never be called. } @@ -1352,6 +1485,11 @@ namespace build2 // file must be generated based on the static library to get accurate // Libs.private. // + // The other things that we omit from the common variant are -l options + // for binless libraries (so that it's usable from other build systems) as + // well as metadata (which could become incomplete due the previous + // omissions; for example, importable headers metadata). + // void link_rule:: pkgconfig_save (action a, const file& l, @@ -1371,26 +1509,147 @@ namespace build2 /* */ pcs::static_type))); assert (t != nullptr); + const path& p (t->path ()); + + // If we are uninstalling, skip regenerating the file if it already + // exists (I think we could have skipped this even if it doesn't exist, + // but let's keep things close to the install case). + // + if (ctx.current_action ().outer_operation () == uninstall_id) + { + if (exists (p)) + return; + } + // This is the lib{} group if we are generating the common file and the // target itself otherwise. // - const file& g (common ? l.group->as<file> () : l); + const target& g (common ? *l.group : l); // By default we assume things go into install.{include, lib}. // + // If include.lib does not resolve, then assume this is update-for- + // install without actual install and remove the file if it exists. + // + // @@ Shouldn't we use target's install value rather than install.lib + // in case it gets installed into a custom location? I suppose one + // can now use cc.pkgconfig.lib to customize this. + // using install::resolve_dir; - dir_path idir (resolve_dir (g, cast<dir_path> (g["install.include"]))); - dir_path ldir (resolve_dir (g, cast<dir_path> (g["install.lib"]))); + small_vector<dir_path, 1> ldirs; - const path& p (t->path ()); + if (const dir_paths* ds = cast_null<dir_paths> (g[c_pkgconfig_lib])) + { + for (const dir_path& d: *ds) + { + bool f (ldirs.empty ()); + + ldirs.push_back (resolve_dir (g, d, {}, !f /* fail_unknown */)); + + if (f && ldirs.back ().empty ()) + break; + } + } + else + ldirs.push_back (resolve_dir (g, + cast<dir_path> (g["install.lib"]), + {}, + false /* fail_unknown */)); + + if (!ldirs.empty () && ldirs.front ().empty ()) + { + rmfile (ctx, p, 3 /* verbosity */); + return; + } + + small_vector<dir_path, 1> idirs; + + if (const dir_paths* ds = cast_null<dir_paths> (g[c_pkgconfig_include])) + { + for (const dir_path& d: *ds) + idirs.push_back (resolve_dir (g, d)); + } + else + idirs.push_back (resolve_dir (g, + cast<dir_path> (g["install.include"]))); + // Note that generation can take some time if we have a large number of + // prerequisite libraries. + // if (verb >= 2) text << "cat >" << p; + else if (verb) + print_diag ("pc", g, *t); if (ctx.dry_run) return; + // See if we should be generating a relocatable .pc file and if so get + // its installation location. The plan is to make all absolute paths + // that we write relative to this location and prefix them with the + // built-in ${pcfiledir} variable (which supported by everybody: the + // original pkg-config, pkgconf, and our libpkg-config library). + // + dir_path rel_base; + if (cast_false<bool> (rs["install.relocatable"])) + { + path f (install::resolve_file (*t)); + if (!f.empty ()) // Shouldn't happen but who knows. + rel_base = f.directory (); + } + + // Note: reloc_*path() expect absolute and normalized paths. + // + // Note also that reloc_path() can be used on dir_path to get the path + // without the trailing slash. + // + auto reloc_path = [&rel_base, + s = string ()] (const path& p, + const char* what) mutable + -> const string& + { + if (rel_base.empty ()) + return p.string (); + + try + { + s = p.relative (rel_base).string (); + } + catch (const invalid_path&) + { + fail << "unable to make " << what << " path " << p << " relative to " + << rel_base; + } + + if (!s.empty ()) s.insert (0, 1, path_traits::directory_separator); + s.insert (0, "${pcfiledir}"); + return s; + }; + + auto reloc_dir_path = [&rel_base, + s = string ()] (const dir_path& p, + const char* what) mutable + -> const string& + { + if (rel_base.empty ()) + return (s = p.representation ()); + + try + { + s = p.relative (rel_base).representation (); + } + catch (const invalid_path&) + { + fail << "unable to make " << what << " path " << p << " relative to " + << rel_base; + } + + if (!s.empty ()) s.insert (0, 1, path_traits::directory_separator); + s.insert (0, "${pcfiledir}"); + return s; + }; + auto_rmfile arm (p); try @@ -1408,6 +1667,20 @@ namespace build2 fail << "no version variable in project " << n << info << "while generating " << p; + // When comparing versions, pkg-config uses RPM semantics, which is + // basically comparing each all-digit/alpha fragments in order. + // This means, for example, a semver with a pre-release will be + // compared incorrectly (pre-release will be greater than the final + // version). We could detect if this project uses stdver and chop + // off any pre-release information (so, essentially only saving the + // major.minor.patch part). But that means such .pc files will + // contain inaccurate version information. And seeing that we don't + // recommend using pkg-config (rather primitive) package dependency + // support, having complete version information for documentation + // seems more important. + // + // @@ Maybe still makes sense to only save version.project_id? + // const string& v (cast<string> (vl)); os << "Name: " << n << endl; @@ -1432,13 +1705,12 @@ namespace build2 for (auto i (v->begin ()); i != v->end (); ++i) { const string& o (*i); - size_t n (o.size ()); // Filter out -I (both -I<dir> and -I <dir> forms). // - if (n >= 2 && o[0] == '-' && o[1] == 'I') + if (o[0] == '-' && o[1] == 'I') { - if (n == 2) + if (o.size () == 2) ++i; continue; @@ -1449,9 +1721,9 @@ namespace build2 } }; - // Given a library target, save its -l-style library name. + // Given a library target, return its -l-style library name. // - auto save_library_target = [&os, this] (const file& l) + auto save_library_target = [this] (const file& l) -> string { // If available (it may not, in case of import-installed libraris), // use the .pc file name to derive the -l library name (in case of @@ -1479,22 +1751,34 @@ namespace build2 } else { - // Derive -l-name from the file name in a fuzzy, platform-specific - // manner. - // - n = l.path ().leaf ().base ().string (); + const path& p (l.path ()); - if (cclass != compiler_class::msvc) - strip_lib (); + if (p.empty ()) // Binless. + { + // For a binless library the target name is all it can possibly + // be. + // + n = l.name; + } + else + { + // Derive -l-name from the file name in a fuzzy, platform- + // specific manner. + // + n = p.leaf ().base ().string (); + + if (cclass != compiler_class::msvc) + strip_lib (); + } } - os << " -l" << n; + return "-l" + n; }; - // Given a (presumably) compiler-specific library name, save its + // Given a (presumably) compiler-specific library name, return its // -l-style library name. // - auto save_library_name = [&os, this] (const string& n) + auto save_library_name = [this] (const string& n) -> string { if (tsys == "win32-msvc") { @@ -1504,23 +1788,20 @@ namespace build2 if (p != string::npos && icasecmp (n.c_str () + p + 1, "lib") == 0) { - os << " -l" << string (n, 0, p); - return; + return "-l" + string (n, 0, p); } - // Fall through and save as is. + // Fall through and return as is. } - os << ' ' << n; + return n; }; - // @@ TODO: support whole archive? - // - // Cflags. // os << "Cflags:"; - os << " -I" << escape (idir.string ()); + for (const dir_path& d: idirs) + os << " -I" << escape (reloc_path (d, "header search")); save_poptions (x_export_poptions); save_poptions (c_export_poptions); os << endl; @@ -1533,51 +1814,117 @@ namespace build2 // dependencies if we don't have the shared variant (see the load // logic for details). And also for the common .pc file, naturally. // - //@@ TODO: would be nice to weed out duplicates. But is it always - // safe? Think linking archives: will have to keep duplicates in - // the second position, not first. Gets even trickier with the - // Libs.private split. - // { os << "Libs:"; // While we don't need it for a binless library itselt, it may be // necessary to resolve its binful dependencies. // - os << " -L" << escape (ldir.string ()); + for (const dir_path& d: ldirs) + os << " -L" << escape (reloc_path (d, "library search")); // Now process ourselves as if we were being linked to something (so - // pretty similar to link_rule::append_libraries()). + // pretty similar to link_rule::append_libraries()). We also reuse + // the link_rule's machinery to suppress duplicates. // + appended_libraries ls; + strings args; bool priv (false); - auto imp = [&priv] (const file&, bool la) {return priv && la;}; - auto lib = [&save_library_target, - &save_library_name] (const file* const* c, - const string& p, - lflags, - bool) + struct data + { + ofdstream& os; + appended_libraries* pls; // Previous. + appended_libraries* ls; // Current. + strings& args; + bool common; + } d {os, nullptr, &ls, args, common}; + + auto imp = [&priv] (const target&, bool la) {return priv && la;}; + + auto lib = [&d, &save_library_target, &save_library_name] ( + const target* const* lc, + const small_vector<reference_wrapper<const string>, 2>& ns, + lflags, + const string*, + bool) { - const file* l (c != nullptr ? *c : nullptr); + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); + + // Suppress duplicates from the previous run (Libs/Libs.private + // split). + // + if (d.pls != nullptr) + { + // Doesn't feel like we can prune here: we may have seen this + // interface library but not its implementation dependencies. + // + if ((l != nullptr + ? d.pls->find (*l) + : d.pls->find (ns)) != nullptr) + return true; + } + + // Suppress duplicates (see append_libraries() for details). + // + // Note that we use the original name for duplicate tracking. + // + appended_library* al (l != nullptr + ? &d.ls->append (*l, d.args.size ()) + : d.ls->append (ns, d.args.size ())); + + if (al != nullptr && al->end != appended_library::npos) + { + d.ls->hoist (d.args, *al); + return true; + } if (l != nullptr) { if (l->is_a<libs> () || l->is_a<liba> ()) // See through libux. - save_library_target (*l); + { + // Omit binless libraries from the common .pc file (see + // above). + // + // Note that in this case we still want to recursively + // traverse such libraries since they may still link to some + // non-binless system libraries (-lm, etc). + // + if (!d.common || !l->path ().empty ()) + d.args.push_back (save_library_target (*l)); + } } else - save_library_name (p); // Something "system'y", save as is. + { + // Something "system'y", save as is. + // + for (const string& n: ns) + d.args.push_back (save_library_name (n)); + } + + if (al != nullptr) + al->end = d.args.size (); // Close. + + return true; }; - auto opt = [] (const file&, - const string&, - bool, bool) + auto opt = [&d] (const target& lt, const string&, bool, bool) { + const file& l (lt.as<file> ()); + //@@ TODO: should we filter -L similar to -I? //@@ TODO: how will the Libs/Libs.private work? - //@@ TODO: remember to use escape() + //@@ TODO: remember to use reloc_*() and escape(). + + if (d.pls != nullptr && d.pls->find (l) != nullptr) + return true; // See link_rule::append_libraries(). + + if (d.ls->append (l, d.args.size ()).end != appended_library::npos) + return true; + + return true; }; // Pretend we are linking an executable using what would be normal, @@ -1585,89 +1932,461 @@ namespace build2 // linfo li {otype::e, la ? lorder::a_s : lorder::s_a}; + library_cache lib_cache; process_libraries (a, bs, li, sys_lib_dirs, l, la, 0, // Link flags. - imp, lib, opt, !binless); + imp, lib, opt, + !binless /* self */, + false /* proc_opt_group */, // @@ !priv? + &lib_cache); + + for (const string& a: args) + os << ' ' << a; os << endl; if (la) { os << "Libs.private:"; + args.clear (); priv = true; + + // Use previous appended_libraries to weed out entries that are + // already in Libs. + // + appended_libraries als; + d.pls = d.ls; + d.ls = &als; + process_libraries (a, bs, li, sys_lib_dirs, l, la, 0, // Link flags. - imp, lib, opt, false); + imp, lib, opt, + false /* self */, + false /* proc_opt_group */, // @@ !priv? + &lib_cache); + + for (const string& a: args) + os << ' ' << a; + os << endl; + + // See also bin.whole below. + } + } + + // Save metadata unless this is the common .pc file (see above). + // + if (common) + { + os.close (); + arm.cancel (); + return; + } + + // The build2.metadata variable is a general indication of the + // metadata being present. Its value is the metadata version + // optionally followed by the user metadata variable prefix and + // variable list (see below for details). Having only the version + // indicates the absense of user metadata. + // + // See if we have the user metadata. + // + lookup um (g[ctx.var_export_metadata]); // Target visibility. + + if (um && !um->empty ()) + { + const names& ns (cast<names> (um)); + + // First verify the version. + // + uint64_t ver; + try + { + // Note: does not change the passed name. + // + ver = value_traits<uint64_t>::convert ( + ns[0], ns[0].pair ? &ns[1] : nullptr); + } + catch (const invalid_argument& e) + { + fail << "invalid metadata version in library " << g << ": " << e + << endf; + } + + if (ver != 1) + fail << "unexpected metadata version " << ver << " in library " + << g; + + // Next verify the metadata variable prefix. + // + if (ns.size () != 2 || !ns[1].simple ()) + fail << "invalid metadata variable prefix in library " << g; + + const string& pfx (ns[1].value); + + // Now find all the target-specific variables with this prefix. + // + // If this is the common .pc file, then we only look in the group. + // Otherwise, in the member and the group. + // + // To allow setting different values for the for-install and + // development build cases (required when a library comes with + // additional "assets"), we recognize the special .for_install + // variable name suffix: if there is a both <prefix>.<name> and + // <prefix>.<name>.for_install variables, then here we take the + // value from the latter. Note that we don't consider just + // <prefix>.for_install as special (so it's available to the user). + // + // We only expect a handful of variables so let's use a vector and + // linear search instead of a map. + // + struct binding + { + const string* name; // Name to be saved (without .for_install). + const variable* var; // Actual variable (potentially .for_install). + const value* val; // Actual value. + }; + vector<binding> vars; + + auto append = [&l, &pfx, &vars, + tmp = string ()] (const target& t, bool dup) mutable + { + for (auto p (t.vars.lookup_namespace (pfx)); + p.first != p.second; + ++p.first) + { + const variable* var (&p.first->first.get ()); + + // Handle .for_install. + // + // The plan is as follows: if this is .for_install, then just + // verify we also have the value without the suffix and skip + // it. Otherwise, check if there also the .for_install variant + // and if so, use that instead. While we could probably do this + // more efficiently by remembering what we saw in vars, this is + // not performance-sensitive and so we keep it simple for now. + // + const string* name; + { + const string& v (var->name); + size_t n (v.size ()); + + if (n > pfx.size () + 1 + 12 && // <prefix>..for_install + v.compare (n - 12, 12, ".for_install") == 0) + { + tmp.assign (v, 0, n - 12); + + if (t.vars.find (tmp) == t.vars.end ()) + fail << v << " variant without " << tmp << " in library " + << l; + + continue; + } + else + { + name = &v; + + tmp = v; tmp += ".for_install"; + + auto i (t.vars.find (tmp)); + if (i != t.vars.end ()) + var = &i->first.get (); + } + } + + if (dup) + { + if (find_if (vars.begin (), vars.end (), + [name] (const binding& p) + { + return *p.name == *name; + }) != vars.end ()) + continue; + } + + // Re-lookup the value in order to apply target type/pattern + // specific prepends/appends. + // + lookup l (t[*var]); + assert (l.defined ()); + + vars.push_back (binding {name, var, l.value}); + } + }; + + append (g, false); + + if (!common) + { + if (l.group != nullptr) + append (*l.group, true); + } + + // First write the build2.metadata variable with the version, + // prefix, and all the variable names/types (which should not + // require any escaping). + // + os << endl + << "build2.metadata = " << ver << ' ' << pfx; + + for (const binding& b: vars) + { + const variable& var (*b.var); + const value& val (*b.val); + + // There is no notion of NULL in pkg-config variables and it's + // probably best not to conflate them with empty. + // + if (val.null) + fail << "null value in exported variable " << var + << " of library " << l; + + if (val.type == nullptr) + fail << "untyped value in exported variable " << var + << " of library " << l; + + // Tighten this to only a sensible subset of types (see + // parsing/serialization code for some of the potential problems). + // + if (!metadata_type (val.type->name).first) + fail << "unsupported value type " << val.type->name + << " in exported variable " << var << " of library " << l; + + os << " \\" << endl + << *b.name << '/' << val.type->name; + } + + os << endl + << endl; + + // Now the variables themselves. + // + string s; // Reuse the buffer. + for (const binding& b: vars) + { + const variable& var (*b.var); + const value& val (*b.val); + + names ns; + names_view nv (reverse (val, ns, true /* reduce */)); + + os << *b.name << " ="; + + auto append = [&rel_base, + &reloc_path, + &reloc_dir_path, + &l, &var, &val, &s] (const name& v) + { + // If this is absolute path or dir_path, then attempt to + // relocate. Without that the result will not be relocatable. + // + if (v.simple ()) + { + path p; + if (!rel_base.empty () && + val.type != nullptr && + (val.type->is_a<path> () || val.type->is_a<paths> ()) && + (p = path (v.value)).absolute ()) + { + p.normalize (); + s += reloc_path (p, var.name.c_str ()); + } + else + s += v.value; + } + else if (v.directory ()) + { + if (!rel_base.empty () && v.dir.absolute ()) + { + dir_path p (v.dir); + p.normalize (); + s += reloc_dir_path (p, var.name.c_str ()); + } + else + s += v.dir.representation (); + } + else + // It seems like we shouldn't end up here due to the type + // check but let's keep it for good measure. + // + fail << "simple or directory value expected instead of '" + << v << "' in exported variable " << var << " of library " + << l; + }; + + for (auto i (nv.begin ()); i != nv.end (); ++i) + { + s.clear (); + append (*i); + + if (i->pair) + { + // @@ What if the value contains the pair character? Maybe + // quote the halves in this case? Note: need to handle in + // parse_metadata() above if enable here. Note: none of the + // types currently allowed use pairs. +#if 0 + s += i->pair; + append (*++i); +#else + fail << "pair in exported variable " << var << " of library " + << l; +#endif + } + + os << ' ' << escape (s); + } + os << endl; } } + else + { + // No user metadata. + // + os << endl + << "build2.metadata = 1" << endl; + } - // If we have modules, list them in the modules variable. We also save - // some extra info about them (yes, the rabbit hole runs deep). This - // code is pretty similar to compiler::search_modules(). + // Save cc.type (see init() for the format documentation). + // + // Note that this value is set by link_rule and therefore should + // be there. + // + { + const string& t ( + cast<string> ( + l.state[a].lookup_original ( + c_type, true /* target_only */).first)); + + // If common, then only save the language (the rest could be + // static/shared-specific; strictly speaking even the language could + // be, but that seems far fetched). + // + os << endl + << "cc.type = " << (common ? string (t, 0, t.find (',')) : t) + << endl; + } + + // Save the bin.whole (whole archive) flag (see the link rule for + // details on the lookup semantics). + // + if (la) + { + // Note: go straight for the public variable pool. + // + if (cast_false<bool> (l.lookup_original ( + ctx.var_pool["bin.whole"], + true /* target_only */).first)) + { + os << endl + << "bin.whole = true" << endl; + } + } + + // If we have modules and/or importable headers, list them in the + // respective variables. We also save some extra info about modules + // (yes, the rabbit hole runs deep). This code is pretty similar to + // compiler::search_modules(). + // + // Note that we want to convey the importable headers information even + // if modules are not enabled. // - if (modules) { struct module { string name; path file; - string pp; + string preprocessed; bool symexport; }; - vector<module> modules; + vector<module> mods; + + // If we were to ever support another C-based language (e.g., + // Objective-C) and libraries that can use a mix of languages (e.g., + // C++ and Objective-C), then we would need to somehow reverse- + // lookup header target type to language. Let's hope we don't. + // + vector<path> x_hdrs; + vector<path> c_hdrs; + // We need to (recursively) see through libu*{}. See similar logic + // in search_modules(). + // // Note that the prerequisite targets are in the member, not the - // group (for now we don't support different sets of modules for - // static/shared library; see load above for details). + // group (for now we don't support different sets of modules/headers + // for static/shared library; see load above for details). // - for (const target* pt: l.prerequisite_targets[a]) + auto collect = [a, this, + &mods, + &x_hdrs, &c_hdrs] (const target& l, + const auto& collect) -> void { - // @@ UTL: we need to (recursively) see through libu*{} (and - // also in search_modules()). - // - if (pt != nullptr && pt->is_a<bmix> ()) + for (const target* pt: l.prerequisite_targets[a]) { - // What we have is a binary module interface. What we need is - // a module interface source it was built from. We assume it's - // the first mxx{} target that we see. - // - const target* mt (nullptr); - for (const target* t: pt->prerequisite_targets[a]) + if (pt == nullptr) + continue; + + if (modules && pt->is_a<bmix> ()) { - if ((mt = t->is_a (*x_mod))) - break; - } + // What we have is a binary module interface. What we need is + // a module interface source it was built from. We assume it's + // the first mxx{} target that we see. + // + const target* mt (nullptr); + for (const target* t: pt->prerequisite_targets[a]) + { + if (t != nullptr && (mt = t->is_a (*x_mod))) + break; + } - // Can/should there be a bmi{} without mxx{}? Can't think of a - // reason. - // - assert (mt != nullptr); + // Can/should there be a bmi{} without mxx{}? Can't think of a + // reason. + // + assert (mt != nullptr); - path p (install::resolve_file (mt->as<file> ())); + path p (install::resolve_file (mt->as<file> ())); - if (p.empty ()) // Not installed. - continue; + if (p.empty ()) // Not installed. + continue; + + string pp; + if (const string* v = cast_null<string> ((*mt)[x_preprocessed])) + pp = *v; + + mods.push_back ( + module { + cast<string> (pt->state[a].vars[c_module_name]), + move (p), + move (pp), + symexport}); + } + else if (pt->is_a (**this->x_hdrs) || pt->is_a<h> ()) + { + if (cast_false<bool> ((*pt)[c_importable])) + { + path p (install::resolve_file (pt->as<file> ())); - string pp; - if (const string* v = cast_null<string> ((*mt)[x_preprocessed])) - pp = *v; - - modules.push_back ( - module { - cast<string> (pt->state[a].vars[c_module_name]), - move (p), - move (pp), - symexport - }); + if (p.empty ()) // Not installed. + continue; + + (pt->is_a<h> () ? c_hdrs : x_hdrs).push_back (move (p)); + } + } + // Note that in prerequisite targets we will have the libux{} + // members, not the group. + // + else if (pt->is_a<libux> ()) + collect (*pt, collect); } - } + }; - if (!modules.empty ()) + collect (l, collect); + + if (size_t n = mods.size ()) { os << endl - << "cxx_modules ="; + << "cxx.modules ="; // The partition separator (`:`) is not a valid character in the // variable name. In fact, from the pkg-config source we can see @@ -1676,7 +2395,7 @@ namespace build2 // for example hello.print..impl. While in the variable values we // can use `:`, for consistency we use `..` there as well. // - for (module& m: modules) + for (module& m: mods) { size_t p (m.name.find (':')); if (p != string::npos) @@ -1684,25 +2403,51 @@ namespace build2 // Module names shouldn't require escaping. // - os << ' ' << m.name << '=' << escape (m.file.string ()); + os << (n != 1 ? " \\\n" : " ") + << m.name << '=' + << escape (reloc_path (m.file, "module interface")); } os << endl; // Module-specific properties. The format is: // - // <lang>_module_<property>.<module> = <value> + // <lang>.module_<property>.<module> = <value> // - for (const module& m: modules) + for (const module& m: mods) { - if (!m.pp.empty ()) - os << "cxx_module_preprocessed." << m.name << " = " << m.pp - << endl; + if (!m.preprocessed.empty ()) + os << "cxx.module_preprocessed." << m.name << " = " + << m.preprocessed << endl; if (m.symexport) - os << "cxx_module_symexport." << m.name << " = true" << endl; + os << "cxx.module_symexport." << m.name << " = true" << endl; } } + + if (size_t n = c_hdrs.size ()) + { + os << endl + << "c.importable_headers ="; + + for (const path& h: c_hdrs) + os << (n != 1 ? " \\\n" : " ") + << escape (reloc_path (h, "header unit")); + + os << endl; + } + + if (size_t n = x_hdrs.size ()) + { + os << endl + << x << ".importable_headers ="; + + for (const path& h: x_hdrs) + os << (n != 1 ? " \\\n" : " ") + << escape (reloc_path (h, "header unit")); + + os << endl; + } } os.close (); diff --git a/libbuild2/cc/pkgconfig.hxx b/libbuild2/cc/pkgconfig.hxx new file mode 100644 index 0000000..a1bcdee --- /dev/null +++ b/libbuild2/cc/pkgconfig.hxx @@ -0,0 +1,129 @@ +// file : libbuild2/cc/pkgconfig.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CC_PKGCONFIG_HXX +#define LIBBUILD2_CC_PKGCONFIG_HXX + +// In order not to complicate the bootstrap procedure with libpkg-config +// building, exclude functionality that involves reading of .pc files. +// +#ifndef BUILD2_BOOTSTRAP + +#ifndef BUILD2_LIBPKGCONF +# include <libpkg-config/pkg-config.h> +#else +# include <libpkgconf/libpkgconf.h> +#endif + +#include <libbuild2/types.hxx> +#include <libbuild2/utility.hxx> + +namespace build2 +{ + namespace cc + { + // Load package information from a .pc file. Filter out the -I/-L options + // that refer to system directories. This makes sure all the system search + // directories are "pushed" to the back which minimizes the chances of + // picking up wrong (e.g., old installed version) header/library. + // + // Note that the prerequisite package .pc files search order is as + // follows: + // + // - in the directory of the specified file + // - in pc_dirs directories (in the specified order) + // + // Issue diagnostics and throw failed on any errors. + // + class pkgconfig + { + public: + using path_type = build2::path; + + path_type path; + + public: + pkgconfig (path_type, + const dir_paths& pc_dirs, + const dir_paths& sys_hdr_dirs, + const dir_paths& sys_lib_dirs); + + // Create an unloaded/empty object. Querying package information on such + // an object is illegal. + // + pkgconfig () = default; + ~pkgconfig (); + + // Movable-only type. + // + pkgconfig (pkgconfig&&) noexcept; + pkgconfig& operator= (pkgconfig&&) noexcept; + + pkgconfig (const pkgconfig&) = delete; + pkgconfig& operator= (const pkgconfig&) = delete; + + strings + cflags (bool static_) const; + + strings + libs (bool static_) const; + + optional<string> + variable (const char*) const; + + optional<string> + variable (const string& s) const {return variable (s.c_str ());} + + private: + void + free (); + +#ifndef BUILD2_LIBPKGCONF + pkg_config_client_t* client_ = nullptr; + pkg_config_pkg_t* pkg_ = nullptr; +#else + pkgconf_client_t* client_ = nullptr; + pkgconf_pkg_t* pkg_ = nullptr; +#endif + }; + + inline pkgconfig:: + ~pkgconfig () + { + if (client_ != nullptr) // Not empty. + free (); + } + + inline pkgconfig:: + pkgconfig (pkgconfig&& p) noexcept + : path (move (p.path)), + client_ (p.client_), + pkg_ (p.pkg_) + { + p.client_ = nullptr; + p.pkg_ = nullptr; + } + + inline pkgconfig& pkgconfig:: + operator= (pkgconfig&& p) noexcept + { + if (this != &p) + { + if (client_ != nullptr) // Not empty. + free (); + + path = move (p.path); + client_ = p.client_; + pkg_ = p.pkg_; + + p.client_ = nullptr; + p.pkg_ = nullptr; + } + return *this; + } + } +} + +#endif // BUILD2_BOOTSTRAP + +#endif // LIBBUILD2_CC_PKGCONFIG_HXX diff --git a/libbuild2/cc/predefs-rule.cxx b/libbuild2/cc/predefs-rule.cxx new file mode 100644 index 0000000..e74192d --- /dev/null +++ b/libbuild2/cc/predefs-rule.cxx @@ -0,0 +1,379 @@ +// file : libbuild2/cc/predefs-rule.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/cc/predefs-rule.hxx> + +#include <libbuild2/depdb.hxx> +#include <libbuild2/target.hxx> +#include <libbuild2/context.hxx> +#include <libbuild2/algorithm.hxx> +#include <libbuild2/filesystem.hxx> +#include <libbuild2/diagnostics.hxx> + +namespace build2 +{ + namespace cc + { + predefs_rule:: + predefs_rule (data&& d) + : common (move (d)), + rule_name (string (x) += ".predefs"), + rule_id (rule_name + " 1") + { + } + + bool predefs_rule:: + match (action, target&, const string& hint, match_extra&) const + { + tracer trace (x, "predefs_rule::match"); + + // We only match with an explicit hint (failed that, we will turn every + // header into predefs). + // + if (hint == rule_name) + { + // Don't match if unsupported compiler. In particular, this allows the + // user to provide a fallback rule. + // + switch (cclass) + { + case compiler_class::gcc: return true; + case compiler_class::msvc: + { + // Only MSVC 19.20 or later. Not tested with clang-cl. + // + if (cvariant.empty () && (cmaj > 19 || (cmaj == 19 && cmin >= 20))) + return true; + + l4 ([&]{trace << "unsupported compiler/version";}); + break; + } + } + } + + return false; + } + + recipe predefs_rule:: + apply (action a, target& xt, match_extra&) const + { + file& t (xt.as<file> ()); + t.derive_path (); + + // Inject dependency on the output directory. + // + inject_fsdir (a, t); + + if (a == perform_update_id) + { + return [this] (action a, const target& xt) + { + return perform_update (a, xt); + }; + } + else if (a == perform_clean_id) + { + return [] (action a, const target& t) + { + // Also remove the temporary input source file in case it wasn't + // removed at the end of the update. + // + return perform_clean_extra (a, t.as<file> (), {".d", ".t"}); + }; + } + else + return noop_recipe; // Configure update. + } + + // Filter noise, sanitize options (msvc.cxx). + // + void + msvc_filter_cl (diag_buffer&, const path& src); + + void + msvc_sanitize_cl (cstrings&); + + target_state predefs_rule:: + perform_update (action a, const target& xt) const + { + tracer trace (x, "predefs_rule::perform_update"); + + const file& t (xt.as<file> ()); + const path& tp (t.path ()); + + context& ctx (t.ctx); + + const scope& rs (t.root_scope ()); + + // Execute prerequisites (the output directory being the only one thus + // not mtime checking). + // + execute_prerequisites (a, t); + + // Use depdb to track changes to options, compiler, etc (similar to + // the compile_rule). + // + depdb dd (tp + ".d"); + { + // First should come the rule name/version. + // + if (dd.expect (rule_id) != nullptr) + l4 ([&]{trace << "rule mismatch forcing update of " << t;}); + + // Then the compiler checksum. + // + if (dd.expect (cast<string> (rs[x_checksum])) != nullptr) + l4 ([&]{trace << "compiler mismatch forcing update of " << t;}); + + // Then the compiler environment checksum. + // + if (dd.expect (env_checksum) != nullptr) + l4 ([&]{trace << "environment mismatch forcing update of " << t;}); + + // Finally the options checksum (as below). + // + { + sha256 cs; + append_options (cs, t, c_coptions); + append_options (cs, t, x_coptions); + append_options (cs, cmode); + + if (dd.expect (cs.string ()) != nullptr) + l4 ([&]{trace << "options mismatch forcing update of " << t;}); + } + } + + // Update if depdb mismatch. + // + bool update (dd.writing () || dd.mtime > t.load_mtime ()); + + dd.close (); + + if (!update) + return target_state::unchanged; // No mtime-based prerequisites. + + // Prepare the compiler command-line. + // + cstrings args {cpath.recall_string ()}; + + // Append compile options. + // + // Note that any command line macros that we specify with -D will end up + // in the predefs, which is something we don't want. So no poptions. + // + append_options (args, t, c_coptions); + append_options (args, t, x_coptions); + append_options (args, cmode); + + // The output and input paths, relative to the working directory for + // easier to read diagnostics. + // + path relo (relative (tp)); + path reli; + + // Add compiler-specific command-line arguments. + // + switch (cclass) + { + case compiler_class::gcc: + { + // Add implied options which may affect predefs, similar to the + // compile rule. + // + if (!find_option_prefix ("-finput-charset=", args)) + args.push_back ("-finput-charset=UTF-8"); + + if (ctype == compiler_type::clang && tsys == "win32-msvc") + { + if (!find_options ({"-nostdlib", "-nostartfiles"}, args)) + { + args.push_back ("-D_MT"); + args.push_back ("-D_DLL"); + } + } + + if (ctype == compiler_type::clang && cvariant == "emscripten") + { + if (x_lang == lang::cxx) + { + if (!find_option_prefix ("DISABLE_EXCEPTION_CATCHING=", args)) + { + args.push_back ("-s"); + args.push_back ("DISABLE_EXCEPTION_CATCHING=0"); + } + } + } + + args.push_back ("-E"); // Stop after the preprocessing stage. + args.push_back ("-dM"); // Generate #define directives. + + // Output. + // + args.push_back ("-o"); + args.push_back (relo.string ().c_str ()); + + // Input. + // + args.push_back ("-x"); + switch (x_lang) + { + case lang::c: args.push_back ("c"); break; + case lang::cxx: args.push_back ("c++"); break; + } + + // With GCC and Clang we can compile /dev/null as stdin by + // specifying `-` and thus omitting the temporary file. + // + args.push_back ("-"); + + break; + } + case compiler_class::msvc: + { + // Add implied options which may affect predefs, similar to the + // compile rule. + // + { + // Note: these affect the _MSVC_EXECUTION_CHARACTER_SET, _UTF8 + // macros. + // + bool sc (find_option_prefixes ( + {"/source-charset:", "-source-charset:"}, args)); + bool ec (find_option_prefixes ( + {"/execution-charset:", "-execution-charset:"}, args)); + + if (!sc && !ec) + args.push_back ("/utf-8"); + else + { + if (!sc) + args.push_back ("/source-charset:UTF-8"); + + if (!ec) + args.push_back ("/execution-charset:UTF-8"); + } + } + + if (x_lang == lang::cxx) + { + if (!find_option_prefixes ({"/EH", "-EH"}, args)) + args.push_back ("/EHsc"); + } + + if (!find_option_prefixes ({"/MD", "/MT", "-MD", "-MT"}, args)) + args.push_back ("/MD"); + + msvc_sanitize_cl (args); + + args.push_back ("/nologo"); + + // /EP may seem like it contradicts /P but it's the recommended + // way to suppress `#line`s from the output of the /P option (see + // /P in the "MSVC Compiler Options" documentation). + // + args.push_back ("/P"); // Write preprocessor output to a file. + args.push_back ("/EP"); // Preprocess to stdout without `#line`s. + + args.push_back ("/PD"); // Print all macro definitions. + args.push_back ("/Zc:preprocessor"); // Preproc. conformance mode. + + // Output (note that while the /Fi: variant is only availbale + // starting with VS2013, /Zc:preprocessor is only available in + // starting from VS2019). + // + args.push_back ("/Fi:"); + args.push_back (relo.string ().c_str ()); + + // Input. + // + switch (x_lang) + { + case lang::c: args.push_back ("/TC"); break; + case lang::cxx: args.push_back ("/TP"); break; + } + + // Input path. + // + // Note that with MSVC we have to use a temporary file. In + // particular compiling `nul` does not work. + // + reli = relo + ".t"; + args.push_back (reli.string ().c_str ()); + + break; + } + } + + args.push_back (nullptr); + + // Run the compiler. + // + if (verb >= 2) + print_process (args); + else if (verb) + print_diag ((string (x_name) + "-predefs").c_str (), t); + + if (!ctx.dry_run) + { + // Create an empty temporary input source file, if necessary. + // + auto_rmfile rmi; + if (!reli.empty ()) + { + rmi = auto_rmfile (reli); + + if (exists (reli, false /* follow_symlinks */)) + rmfile (ctx, reli, 3 /* verbosity */); + + touch (ctx, reli, true /* create */, 3 /* verbosity */); + } + + try + { + // VC cl.exe sends diagnostics to stdout. It also prints the file + // name being compiled as the first line. So for cl.exe we filter + // that noise out. + // + // For other compilers also redirect stdout to stderr, in case any + // of them tries to pull off something similar. For sane compilers + // this should be harmless. + // + // We also redirect stdin to /dev/null in case that's used instead + // of the temporary file. + // + // Note: similar logic as in compile_rule. + // + bool filter (ctype == compiler_type::msvc); + + process pr (cpath, + args, + -2, /* stdin */ + 2, /* stdout */ + diag_buffer::pipe (ctx, filter /* force */) /* stderr */); + + diag_buffer dbuf (ctx, args[0], pr); + + if (filter) + msvc_filter_cl (dbuf, reli); + + dbuf.read (); + + run_finish (dbuf, args, pr, 1 /* verbosity */); + dd.check_mtime (tp); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e; + + if (e.child) + exit (1); + + throw failed (); + } + } + + t.mtime (system_clock::now ()); + return target_state::changed; + } + } +} diff --git a/libbuild2/cc/predefs-rule.hxx b/libbuild2/cc/predefs-rule.hxx new file mode 100644 index 0000000..60aa063 --- /dev/null +++ b/libbuild2/cc/predefs-rule.hxx @@ -0,0 +1,45 @@ +// file : libbuild2/cc/predefs-rule.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_CC_PREDEFS_RULE_HXX +#define LIBBUILD2_CC_PREDEFS_RULE_HXX + +#include <libbuild2/types.hxx> +#include <libbuild2/utility.hxx> + +#include <libbuild2/rule.hxx> + +#include <libbuild2/cc/types.hxx> +#include <libbuild2/cc/common.hxx> + +#include <libbuild2/cc/export.hxx> + +namespace build2 +{ + namespace cc + { + class LIBBUILD2_CC_SYMEXPORT predefs_rule: public rule, + virtual common + { + public: + const string rule_name; + + explicit + predefs_rule (data&&); + + virtual bool + match (action, target&, const string&, match_extra&) const override; + + virtual recipe + apply (action, target&, match_extra&) const override; + + target_state + perform_update (action, const target&) const; + + private: + const string rule_id; + }; + } +} + +#endif // LIBBUILD2_CC_PREDEFS_RULE_HXX diff --git a/libbuild2/cc/std.cppm b/libbuild2/cc/std.cppm new file mode 100644 index 0000000..5368d1c --- /dev/null +++ b/libbuild2/cc/std.cppm @@ -0,0 +1,6781 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING, this entire header is generated by +// utils/generate_std_cppm_in.py +// DO NOT MODIFY! + +module; + +#include <__config> + +#if _LIBCPP_VERSION < 170000 +#error libc++ version 17.0.0 or later required +#endif + +// The headers of Table 24: C++ library headers [tab:headers.cpp] +// and the headers of Table 25: C++ headers for C library facilities [tab:headers.cpp.c] +#include <algorithm> +#include <any> +#include <array> +#if !defined(_LIBCPP_HAS_NO_ATOMIC_HEADER) +# include <atomic> +#endif +#if !defined(_LIBCPP_HAS_NO_THREADS) +# include <barrier> +#endif +#include <bit> +#include <bitset> +#include <cassert> +#include <cctype> +#include <cerrno> +#include <cfenv> +#include <cfloat> +#include <charconv> +#include <chrono> +#include <cinttypes> +#include <climits> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <clocale> +#endif +#include <cmath> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <codecvt> +#endif +#include <compare> +#include <complex> +#include <concepts> +#include <condition_variable> +#include <coroutine> +#include <csetjmp> +#include <csignal> +#include <cstdarg> +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <cuchar> +#if !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS) +# include <cwchar> +#endif +#if !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS) +# include <cwctype> +#endif +#include <deque> +#include <exception> +#include <execution> +#include <expected> +#include <filesystem> +#include <format> +#include <forward_list> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <fstream> +#endif +#include <functional> +#if !defined(_LIBCPP_HAS_NO_THREADS) +# include <future> +#endif +#include <initializer_list> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <iomanip> +#endif +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <ios> +#endif +#include <iosfwd> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <iostream> +#endif +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <istream> +#endif +#include <iterator> +#if !defined(_LIBCPP_HAS_NO_THREADS) +# include <latch> +#endif +#include <limits> +#include <list> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <locale> +#endif +#include <map> +#include <mdspan> +#include <memory> +#include <memory_resource> +#include <mutex> +#include <new> +#include <numbers> +#include <numeric> +#include <optional> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <ostream> +#endif +#include <print> +#include <queue> +#include <random> +#include <ranges> +#include <ratio> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <regex> +#endif +#include <scoped_allocator> +#if !defined(_LIBCPP_HAS_NO_THREADS) +# include <semaphore> +#endif +#include <set> +#if !defined(_LIBCPP_HAS_NO_THREADS) +# include <shared_mutex> +#endif +#include <source_location> +#include <span> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <sstream> +#endif +#include <stack> +#include <stdexcept> +#if !defined(_LIBCPP_HAS_NO_THREADS) +# include <stop_token> +#endif +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <streambuf> +#endif +#include <string> +#include <string_view> +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +# include <strstream> +#endif +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) +#if __has_include(<syncstream>) +# define _LIPCPP_HAS_YES_SYNCSTREAM +# include <syncstream> +#endif +#endif +#include <system_error> +#if !defined(_LIBCPP_HAS_NO_THREADS) +# include <thread> +#endif +#include <tuple> +#include <type_traits> +#include <typeindex> +#include <typeinfo> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <valarray> +#include <variant> +#include <vector> +#include <version> + +#if 0 +// *** Headers not yet available *** +#if __has_include(<debugging>) +# error "update the header information for <debugging> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<debugging>) +#if __has_include(<flat_map>) +# error "update the header information for <flat_map> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<flat_map>) +#if __has_include(<flat_set>) +# error "update the header information for <flat_set> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<flat_set>) +#if __has_include(<generator>) +# error "update the header information for <generator> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<generator>) +#if __has_include(<hazard_pointer>) +# error "update the header information for <hazard_pointer> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<hazard_pointer>) +#if __has_include(<linalg>) +# error "update the header information for <linalg> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<linalg>) +#if __has_include(<rcu>) +# error "update the header information for <rcu> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<rcu>) +#if __has_include(<spanstream>) +# error "update the header information for <spanstream> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<spanstream>) +#if __has_include(<stacktrace>) +# error "update the header information for <stacktrace> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<stacktrace>) +#if __has_include(<stdfloat>) +# error "update the header information for <stdfloat> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<stdfloat>) +#if __has_include(<text_encoding>) +# error "update the header information for <text_encoding> in libcxx/utils/generate_std_cppm_in.py" +#endif // __has_include(<text_encoding>) +#endif + +export module std; + +// algorithm.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + namespace ranges { + // [algorithms.results], algorithm result types + using std::ranges::in_found_result; + using std::ranges::in_fun_result; + using std::ranges::in_in_out_result; + using std::ranges::in_in_result; + using std::ranges::in_out_out_result; + using std::ranges::in_out_result; + // using std::ranges::in_value_result; + using std::ranges::min_max_result; + // using std::ranges::out_value_result; + } // namespace ranges + + // [alg.nonmodifying], non-modifying sequence operations + // [alg.all.of], all of + using std::all_of; + namespace ranges { + using std::ranges::all_of; + } + + // [alg.any.of], any of + using std::any_of; + namespace ranges { + using std::ranges::any_of; + } + + // [alg.none.of], none of + using std::none_of; + namespace ranges { + using std::ranges::none_of; + } + + // [alg.contains], contains +#if 0 + namespace ranges { + using std::ranges::contains; + using std::ranges::contains_subrange; + } // namespace ranges +#endif + + // [alg.foreach], for each + using std::for_each; + + namespace ranges { + using std::ranges::for_each; + using std::ranges::for_each_result; + } // namespace ranges + + using std::for_each_n; + + namespace ranges { + using std::ranges::for_each_n_result; + + using std::ranges::for_each_n; + } // namespace ranges + + // [alg.find], find + using std::find; + using std::find_if; + using std::find_if_not; + + namespace ranges { + using std::ranges::find; + using std::ranges::find_if; + using std::ranges::find_if_not; + } // namespace ranges + + namespace ranges { +#if 0 + using std::ranges::find_last; + using std::ranges::find_last_if; + using std::ranges::find_last_if_not; +#endif + } // namespace ranges + + // [alg.find.end], find end + using std::find_end; + + namespace ranges { + using std::ranges::find_end; + } + + // [alg.find.first.of], find first + using std::find_first_of; + + namespace ranges { + using std::ranges::find_first_of; + } + + // [alg.adjacent.find], adjacent find + using std::adjacent_find; + + namespace ranges { + using std::ranges::adjacent_find; + } + + // [alg.count], count + using std::count; + using std::count_if; + + namespace ranges { + using std::ranges::count; + using std::ranges::count_if; + } // namespace ranges + + // [mismatch], mismatch + using std::mismatch; + + namespace ranges { + using std::ranges::mismatch_result; + + using std::ranges::mismatch; + } // namespace ranges + + // [alg.equal], equal + using std::equal; + + namespace ranges { + using std::ranges::equal; + } + + // [alg.is.permutation], is permutation + using std::is_permutation; + + namespace ranges { + using std::ranges::is_permutation; + } + + // [alg.search], search + using std::search; + + namespace ranges { + using std::ranges::search; + } + + using std::search_n; + + namespace ranges { + using std::ranges::search_n; + } + + namespace ranges { +#if _LIBCPP_STD_VER >= 23 + // [alg.starts.with], starts with + using std::ranges::starts_with; + +#if _LIBCPP_VERSION >= 180000 + // [alg.ends.with], ends with + using std::ranges::ends_with; +#endif + +# if 0 + // [alg.fold], fold + using std::ranges::fold_left; + using std::ranges::fold_left_first; + using std::ranges::fold_right; + using std::ranges::fold_right_last; + using std::ranges::fold_left_with_iter; + using std::ranges::fold_left_with_iter_result; + using std::ranges::fold_left_with_iter; + using std::ranges::fold_left_first_with_iter; + using std::ranges::fold_left_first_with_iter; +# endif +#endif // _LIBCPP_STD_VER >= 23 + } // namespace ranges + + // [alg.modifying.operations], mutating sequence operations + // [alg.copy], copy + using std::copy; + + namespace ranges { + using std::ranges::copy; + using std::ranges::copy_result; + } // namespace ranges + + using std::copy_n; + + namespace ranges { + using std::ranges::copy_n; + using std::ranges::copy_n_result; + } // namespace ranges + + using std::copy_if; + + namespace ranges { + using std::ranges::copy_if; + using std::ranges::copy_if_result; + } // namespace ranges + + using std::copy_backward; + + namespace ranges { + using std::ranges::copy_backward; + using std::ranges::copy_backward_result; + } // namespace ranges + + // [alg.move], move + using std::move; + + namespace ranges { + using std::ranges::move; + using std::ranges::move_result; + } // namespace ranges + + using std::move_backward; + + namespace ranges { + using std::ranges::move_backward; + using std::ranges::move_backward_result; + } // namespace ranges + + // [alg.swap], swap + using std::swap_ranges; + + namespace ranges { + using std::ranges::swap_ranges; + using std::ranges::swap_ranges_result; + } // namespace ranges + + using std::iter_swap; + + // [alg.transform], transform + using std::transform; + + namespace ranges { + using std::ranges::binary_transform_result; + using std::ranges::unary_transform_result; + + using std::ranges::transform; + + } // namespace ranges + + using std::replace; + using std::replace_if; + + namespace ranges { + using std::ranges::replace; + using std::ranges::replace_if; + } // namespace ranges + + using std::replace_copy; + using std::replace_copy_if; + + namespace ranges { + using std::ranges::replace_copy; + using std::ranges::replace_copy_if; + using std::ranges::replace_copy_if_result; + using std::ranges::replace_copy_result; + } // namespace ranges + + // [alg.fill], fill + using std::fill; + using std::fill_n; + + namespace ranges { + using std::ranges::fill; + using std::ranges::fill_n; + } // namespace ranges + + // [alg.generate], generate + using std::generate; + using std::generate_n; + + namespace ranges { + using std::ranges::generate; + using std::ranges::generate_n; + } // namespace ranges + + // [alg.remove], remove + using std::remove; + using std::remove_if; + + namespace ranges { + using std::ranges::remove; + using std::ranges::remove_if; + } // namespace ranges + + using std::remove_copy; + using std::remove_copy_if; + namespace ranges { + using std::ranges::remove_copy; + using std::ranges::remove_copy_if; + using std::ranges::remove_copy_if_result; + using std::ranges::remove_copy_result; + } // namespace ranges + + // [alg.unique], unique + using std::unique; + + namespace ranges { + using std::ranges::unique; + } + + using std::unique_copy; + + namespace ranges { + using std::ranges::unique_copy; + using std::ranges::unique_copy_result; + } // namespace ranges + + // [alg.reverse], reverse + using std::reverse; + + namespace ranges { + using std::ranges::reverse; + } + + using std::reverse_copy; + + namespace ranges { + using std::ranges::reverse_copy; + using std::ranges::reverse_copy_result; + } // namespace ranges + + // [alg.rotate], rotate + using std::rotate; + + namespace ranges { + using std::ranges::rotate; + } + + using std::rotate_copy; + + namespace ranges { + using std::ranges::rotate_copy; + using std::ranges::rotate_copy_result; + } // namespace ranges + + // [alg.random.sample], sample + using std::sample; + + namespace ranges { + using std::ranges::sample; + } + + // [alg.random.shuffle], shuffle + using std::shuffle; + + namespace ranges { + using std::ranges::shuffle; + } + + // [alg.shift], shift + using std::shift_left; + + namespace ranges { + // using std::ranges::shift_left; + } + + using std::shift_right; + + namespace ranges { + // using std::ranges::shift_right; + } + + // [alg.sorting], sorting and related operations + // [alg.sort], sorting + using std::sort; + + namespace ranges { + using std::ranges::sort; + } + + using std::stable_sort; + + namespace ranges { + using std::ranges::stable_sort; + } + + using std::partial_sort; + + namespace ranges { + using std::ranges::partial_sort; + } + using std::partial_sort_copy; + + namespace ranges { + using std::ranges::partial_sort_copy; + using std::ranges::partial_sort_copy_result; + } // namespace ranges + + using std::is_sorted; + using std::is_sorted_until; + + namespace ranges { + using std::ranges::is_sorted; + using std::ranges::is_sorted_until; + } // namespace ranges + + // [alg.nth.element], Nth element + using std::nth_element; + + namespace ranges { + using std::ranges::nth_element; + } + + // [alg.binary.search], binary search + using std::lower_bound; + + namespace ranges { + using std::ranges::lower_bound; + } + + using std::upper_bound; + + namespace ranges { + using std::ranges::upper_bound; + } + + using std::equal_range; + + namespace ranges { + using std::ranges::equal_range; + } + + using std::binary_search; + + namespace ranges { + using std::ranges::binary_search; + } + + // [alg.partitions], partitions + using std::is_partitioned; + + namespace ranges { + using std::ranges::is_partitioned; + } + + using std::partition; + + namespace ranges { + using std::ranges::partition; + } + + using std::stable_partition; + + namespace ranges { + using std::ranges::stable_partition; + } + + using std::partition_copy; + + namespace ranges { + using std::ranges::partition_copy; + using std::ranges::partition_copy_result; + } // namespace ranges + + using std::partition_point; + + namespace ranges { + using std::ranges::partition_point; + } + // [alg.merge], merge + using std::merge; + namespace ranges { + using std::ranges::merge; + using std::ranges::merge_result; + } // namespace ranges + + using std::inplace_merge; + + namespace ranges { + using std::ranges::inplace_merge; + } + + // [alg.set.operations], set operations + using std::includes; + namespace ranges { + using std::ranges::includes; + } + + using std::set_union; + + namespace ranges { + using std::ranges::set_union; + using std::ranges::set_union_result; + } // namespace ranges + + using std::set_intersection; + namespace ranges { + using std::ranges::set_intersection; + using std::ranges::set_intersection_result; + } // namespace ranges + + using std::set_difference; + + namespace ranges { + using std::ranges::set_difference; + using std::ranges::set_difference_result; + } // namespace ranges + + using std::set_symmetric_difference; + + namespace ranges { + using std::ranges::set_symmetric_difference_result; + + using std::ranges::set_symmetric_difference; + } // namespace ranges + + // [alg.heap.operations], heap operations + using std::push_heap; + + namespace ranges { + using std::ranges::push_heap; + } + + using std::pop_heap; + + namespace ranges { + using std::ranges::pop_heap; + } + + using std::make_heap; + + namespace ranges { + using std::ranges::make_heap; + } + + using std::sort_heap; + + namespace ranges { + using std::ranges::sort_heap; + } + + using std::is_heap; + + namespace ranges { + using std::ranges::is_heap; + } + + using std::is_heap_until; + + namespace ranges { + using std::ranges::is_heap_until; + } + + // [alg.min.max], minimum and maximum + using std::min; + + namespace ranges { + using std::ranges::min; + } + + using std::max; + + namespace ranges { + using std::ranges::max; + } + + using std::minmax; + + namespace ranges { + using std::ranges::minmax_result; + + using std::ranges::minmax; + } // namespace ranges + + using std::min_element; + + namespace ranges { + using std::ranges::min_element; + } + + using std::max_element; + + namespace ranges { + using std::ranges::max_element; + } + + using std::minmax_element; + + namespace ranges { + using std::ranges::minmax_element_result; + + using std::ranges::minmax_element; + } // namespace ranges + // [alg.clamp], bounded value + using std::clamp; + + namespace ranges { + using std::ranges::clamp; + } + + // [alg.lex.comparison], lexicographical comparison + using std::lexicographical_compare; + + namespace ranges { + using std::ranges::lexicographical_compare; + } + + // [alg.three.way], three-way comparison algorithms + using std::lexicographical_compare_three_way; + + // [alg.permutation.generators], permutations + using std::next_permutation; + + namespace ranges { + using std::ranges::next_permutation_result; + + using std::ranges::next_permutation; + } // namespace ranges + + using std::prev_permutation; + + namespace ranges { + using std::ranges::prev_permutation_result; + + using std::ranges::prev_permutation; + } // namespace ranges + +} // namespace std + +// any.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + // [any.bad.any.cast], class bad_any_cast + using std::bad_any_cast; + + // [any.class], class any + using std::any; + + // [any.nonmembers], non-member functions + using std::any_cast; + using std::make_any; + using std::swap; + +} // namespace std + +// array.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + // [array], class template array + using std::array; + + using std::operator==; + using std::operator<=>; + + // [array.special], specialized algorithms + using std::swap; + + // [array.creation], array creation functions + using std::to_array; + + // [array.tuple], tuple interface + using std::get; + using std::tuple_element; + using std::tuple_size; + +} // namespace std + +// atomic.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + // [atomics.order], order and consistency + using std::memory_order; + using std::memory_order_acq_rel; + using std::memory_order_acquire; + using std::memory_order_consume; + using std::memory_order_relaxed; + using std::memory_order_release; + using std::memory_order_seq_cst; + + using std::kill_dependency; + + // [atomics.ref.generic], class template atomic_ref + // [atomics.ref.pointer], partial specialization for pointers + // using std::atomic_ref; + + // [atomics.types.generic], class template atomic + using std::atomic; + + // [atomics.nonmembers], non-member functions + using std::atomic_compare_exchange_strong; + using std::atomic_compare_exchange_strong_explicit; + using std::atomic_compare_exchange_weak; + using std::atomic_compare_exchange_weak_explicit; + using std::atomic_exchange; + using std::atomic_exchange_explicit; + using std::atomic_is_lock_free; + using std::atomic_load; + using std::atomic_load_explicit; + using std::atomic_store; + using std::atomic_store_explicit; + + using std::atomic_fetch_add; + using std::atomic_fetch_add_explicit; + using std::atomic_fetch_and; + using std::atomic_fetch_and_explicit; + using std::atomic_fetch_or; + using std::atomic_fetch_or_explicit; + using std::atomic_fetch_sub; + using std::atomic_fetch_sub_explicit; + using std::atomic_fetch_xor; + using std::atomic_fetch_xor_explicit; + using std::atomic_notify_all; + using std::atomic_notify_one; + using std::atomic_wait; + using std::atomic_wait_explicit; + + // [atomics.alias], type aliases + using std::atomic_bool; + using std::atomic_char; + using std::atomic_char16_t; + using std::atomic_char32_t; + using std::atomic_char8_t; + using std::atomic_int; + using std::atomic_llong; + using std::atomic_long; + using std::atomic_schar; + using std::atomic_short; + using std::atomic_uchar; + using std::atomic_uint; + using std::atomic_ullong; + using std::atomic_ulong; + using std::atomic_ushort; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::atomic_wchar_t; +#endif + + using std::atomic_int16_t; + using std::atomic_int32_t; + using std::atomic_int64_t; + using std::atomic_int8_t; + using std::atomic_uint16_t; + using std::atomic_uint32_t; + using std::atomic_uint64_t; + using std::atomic_uint8_t; + + using std::atomic_int_least16_t; + using std::atomic_int_least32_t; + using std::atomic_int_least64_t; + using std::atomic_int_least8_t; + using std::atomic_uint_least16_t; + using std::atomic_uint_least32_t; + using std::atomic_uint_least64_t; + using std::atomic_uint_least8_t; + + using std::atomic_int_fast16_t; + using std::atomic_int_fast32_t; + using std::atomic_int_fast64_t; + using std::atomic_int_fast8_t; + using std::atomic_uint_fast16_t; + using std::atomic_uint_fast32_t; + using std::atomic_uint_fast64_t; + using std::atomic_uint_fast8_t; + + using std::atomic_intmax_t; + using std::atomic_intptr_t; + using std::atomic_ptrdiff_t; + using std::atomic_size_t; + using std::atomic_uintmax_t; + using std::atomic_uintptr_t; + + using std::atomic_signed_lock_free; + using std::atomic_unsigned_lock_free; + + // [atomics.flag], flag type and operations + using std::atomic_flag; + + using std::atomic_flag_clear; + using std::atomic_flag_clear_explicit; + using std::atomic_flag_test; + using std::atomic_flag_test_and_set; + using std::atomic_flag_test_and_set_explicit; + using std::atomic_flag_test_explicit; + + using std::atomic_flag_notify_all; + using std::atomic_flag_notify_one; + using std::atomic_flag_wait; + using std::atomic_flag_wait_explicit; + + // [atomics.fences], fences + using std::atomic_signal_fence; + using std::atomic_thread_fence; + + // [depr.atomics.nonmembers] + using std::atomic_init; + +} // namespace std + +// barrier.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS + using std::barrier; +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// bit.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [bit.cast], bit_cast + using std::bit_cast; + +#if _LIBCPP_STD_VER >= 23 + // [bit.byteswap], byteswap + using std::byteswap; +#endif + + // [bit.pow.two], integral powers of 2 + using std::bit_ceil; + using std::bit_floor; + using std::bit_width; + using std::has_single_bit; + + // [bit.rotate], rotating + using std::rotl; + using std::rotr; + + // [bit.count], counting + using std::countl_one; + using std::countl_zero; + using std::countr_one; + using std::countr_zero; + using std::popcount; + + // [bit.endian], endian + using std::endian; +} // namespace std + +// bitset.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::bitset; + + // [bitset.operators], bitset operators + using std::operator&; + using std::operator|; + using std::operator^; + using std::operator>>; + using std::operator<<; + + // [bitset.hash], hash support + using std::hash; + +} // namespace std + +// cassert.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // This module exports nothing. +} // namespace std + +// cctype.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::isalnum; + using std::isalpha; + using std::isblank; + using std::iscntrl; + using std::isdigit; + using std::isgraph; + using std::islower; + using std::isprint; + using std::ispunct; + using std::isspace; + using std::isupper; + using std::isxdigit; + using std::tolower; + using std::toupper; +} // namespace std + +// cerrno.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // This module exports nothing. +} // namespace std + +// cfenv.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // types + using std::fenv_t; + using std::fexcept_t; + + // functions + using std::feclearexcept; + using std::fegetexceptflag; + using std::feraiseexcept; + using std::fesetexceptflag; + using std::fetestexcept; + + using std::fegetround; + using std::fesetround; + + using std::fegetenv; + using std::feholdexcept; + using std::fesetenv; + using std::feupdateenv; + +} // namespace std + +// cfloat.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // This module exports nothing. +} // namespace std + +// charconv.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + // floating-point format for primitive numerical conversion + using std::chars_format; + + // chars_format is a bitmask type. + // [bitmask.types] specified operators + using std::operator&; + using std::operator&=; + using std::operator^; + using std::operator^=; + using std::operator|; + using std::operator|=; + using std::operator~; + + // [charconv.to.chars], primitive numerical output conversion + using std::to_chars_result; + + using std::to_chars; + + // [charconv.from.chars], primitive numerical input conversion + using std::from_chars_result; + + using std::from_chars; +} // namespace std + +// chrono.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + namespace chrono { + using std::chrono::duration; + using std::chrono::time_point; + + } // namespace chrono + + using std::common_type; + + namespace chrono { + + // [time.traits], customization traits + using std::chrono::treat_as_floating_point; + using std::chrono::treat_as_floating_point_v; + + using std::chrono::duration_values; + + // using std::chrono::is_clock; + // using std::chrono::is_clock_v; + + // [time.duration.nonmember], duration arithmetic + using std::chrono::operator+; + using std::chrono::operator-; + using std::chrono::operator*; + using std::chrono::operator/; + using std::chrono::operator%; + + // [time.duration.comparisons], duration comparisons + using std::chrono::operator==; + using std::chrono::operator!=; + using std::chrono::operator<; + using std::chrono::operator>; + using std::chrono::operator<=; + using std::chrono::operator>=; + using std::chrono::operator<=>; + + // [time.duration.cast], conversions + using std::chrono::ceil; + using std::chrono::duration_cast; + using std::chrono::floor; + using std::chrono::round; + + // [time.duration.io], duration I/O +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::chrono::operator<<; +#endif + // using std::chrono::from_stream; + + // convenience typedefs + using std::chrono::days; + using std::chrono::hours; + using std::chrono::microseconds; + using std::chrono::milliseconds; + using std::chrono::minutes; + using std::chrono::months; + using std::chrono::nanoseconds; + using std::chrono::seconds; + using std::chrono::weeks; + using std::chrono::years; + + // [time.point.nonmember], time_point arithmetic + + // [time.point.comparisons], time_point comparisons + + // [time.point.cast], conversions + using std::chrono::time_point_cast; + + // [time.duration.alg], specialized algorithms + using std::chrono::abs; + + // [time.clock.system], class system_clock + using std::chrono::system_clock; + + using std::chrono::sys_days; + using std::chrono::sys_seconds; + using std::chrono::sys_time; + +#if 0 + // [time.clock.utc], class utc_clock + using std::chrono::utc_clock; + + using std::chrono::utc_seconds; + using std::chrono::utc_time; + + using std::chrono::leap_second_info; + + using std::chrono::get_leap_second_info; + // [time.clock.tai], class tai_clock + using std::chrono::tai_clock; + + using std::chrono::tai_seconds; + using std::chrono::tai_time; + + // [time.clock.gps], class gps_clock + using std::chrono::gps_clock; + + using std::chrono::gps_seconds; + using std::chrono::gps_time; +#endif + // [time.clock.file], type file_clock + using std::chrono::file_clock; + + using std::chrono::file_time; + +#ifndef _LIBCPP_HAS_NO_MONOTONIC_CLOCK + // [time.clock.steady], class steady_clock + using std::chrono::steady_clock; +#endif + + // [time.clock.hires], class high_resolution_clock + using std::chrono::high_resolution_clock; + + // [time.clock.local], local time + using std::chrono::local_days; + using std::chrono::local_seconds; + using std::chrono::local_t; + using std::chrono::local_time; + + // [time.clock.cast], time_point conversions + // using std::chrono::clock_time_conversion; + + // using std::chrono::clock_cast; + + // [time.cal.last], class last_spec + using std::chrono::last_spec; + + // [time.cal.day], class day + using std::chrono::day; + + // [time.cal.month], class month + using std::chrono::month; + + // [time.cal.year], class year + using std::chrono::year; + + // [time.cal.wd], class weekday + using std::chrono::weekday; + + // [time.cal.wdidx], class weekday_indexed + using std::chrono::weekday_indexed; + + // [time.cal.wdlast], class weekday_last + using std::chrono::weekday_last; + + // [time.cal.md], class month_day + using std::chrono::month_day; + + // [time.cal.mdlast], class month_day_last + using std::chrono::month_day_last; + + // [time.cal.mwd], class month_weekday + using std::chrono::month_weekday; + + // [time.cal.mwdlast], class month_weekday_last + using std::chrono::month_weekday_last; + + // [time.cal.ym], class year_month + using std::chrono::year_month; + + // [time.cal.ymd], class year_month_day + using std::chrono::year_month_day; + + // [time.cal.ymdlast], class year_month_day_last + using std::chrono::year_month_day_last; + + // [time.cal.ymwd], class year_month_weekday + using std::chrono::year_month_weekday; + + // [time.cal.ymwdlast], class year_month_weekday_last + using std::chrono::year_month_weekday_last; + + // [time.cal.operators], civil calendar conventional syntax operators + + // [time.hms], class template hh_mm_ss + using std::chrono::hh_mm_ss; + + // [time.12], 12/24 hour functions + using std::chrono::is_am; + using std::chrono::is_pm; + using std::chrono::make12; + using std::chrono::make24; + +#if !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && \ + !defined(_LIBCPP_HAS_NO_LOCALIZATION) + +# ifdef _LIBCPP_ENABLE_EXPERIMENTAL + // [time.zone.db], time zone database + using std::chrono::tzdb; + using std::chrono::tzdb_list; + + // [time.zone.db.access], time zone database access + // using std::chrono::current_zone; + using std::chrono::get_tzdb; + using std::chrono::get_tzdb_list; + // using std::chrono::locate_zone; + + // [time.zone.db.remote], remote time zone database support + using std::chrono::reload_tzdb; + using std::chrono::remote_version; + +# endif // !defined(_LIBCPP_HAS_NO_TIME_ZONE_DATABASE) && !defined(_LIBCPP_HAS_NO_FILESYSTEM) && + // !defined(_LIBCPP_HAS_NO_LOCALIZATION) + +# if 0 + // [time.zone.exception], exception classes + using std::chrono::ambiguous_local_time; + using std::chrono::nonexistent_local_time; + + // [time.zone.info], information classes + using std::chrono::sys_info; + + // [time.zone.timezone], class time_zone + using std::chrono::choose; + using std::chrono::time_zone; + + // [time.zone.zonedtraits], class template zoned_traits + using std::chrono::zoned_traits; + + // [time.zone.zonedtime], class template zoned_time + using std::chrono::zoned_time; + + using std::chrono::zoned_seconds; + + // [time.zone.leap], leap second support + using std::chrono::leap_second; + + // [time.zone.link], class time_zone_link + using std::chrono::time_zone_link; + + // [time.format], formatting + using std::chrono::local_time_format; +# endif +#endif // _LIBCPP_ENABLE_EXPERIMENTAL + } // namespace chrono + +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::formatter; +#endif // _LIBCPP_HAS_NO_LOCALIZATION + + namespace chrono { + // using std::chrono::parse; + + // calendrical constants + using std::chrono::last; + + using std::chrono::Friday; + using std::chrono::Monday; + using std::chrono::Saturday; + using std::chrono::Sunday; + using std::chrono::Thursday; + using std::chrono::Tuesday; + using std::chrono::Wednesday; + + using std::chrono::April; + using std::chrono::August; + using std::chrono::December; + using std::chrono::February; + using std::chrono::January; + using std::chrono::July; + using std::chrono::June; + using std::chrono::March; + using std::chrono::May; + using std::chrono::November; + using std::chrono::October; + using std::chrono::September; + + } // namespace chrono + +} // namespace std +export namespace std::inline literals::inline chrono_literals { + // [time.duration.literals], suffixes for duration literals + using std::literals::chrono_literals::operator""h; + using std::literals::chrono_literals::operator""min; + using std::literals::chrono_literals::operator""s; + using std::literals::chrono_literals::operator""ms; + using std::literals::chrono_literals::operator""us; + using std::literals::chrono_literals::operator""ns; + + // [using std::literals::chrono_literals::.cal.day.nonmembers], non-member functions + using std::literals::chrono_literals::operator""d; + + // [using std::literals::chrono_literals::.cal.year.nonmembers], non-member functions + using std::literals::chrono_literals::operator""y; +} // namespace std::inline literals::inline chrono_literals + +// cinttypes.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::imaxdiv_t; + + using std::imaxabs; + using std::imaxdiv; + using std::strtoimax; + using std::strtoumax; + using std::wcstoimax; + using std::wcstoumax; + + // abs is conditionally here, but always present in cmath.cppm. To avoid + // conflicing declarations omit the using here. + + // div is conditionally here, but always present in cstdlib.cppm. To avoid + // conflicing declarations omit the using here. +} // namespace std + +// climits.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // This module exports nothing. +} // namespace std + +// clocale.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::lconv; + + using std::localeconv; + using std::setlocale; +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// cmath.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + using std::double_t; + using std::float_t; + + using std::acos; + using std::acosf; + using std::acosl; + + using std::asin; + using std::asinf; + using std::asinl; + + using std::atan; + using std::atanf; + using std::atanl; + + using std::atan2; + using std::atan2f; + using std::atan2l; + + using std::cos; + using std::cosf; + using std::cosl; + + using std::sin; + using std::sinf; + using std::sinl; + + using std::tan; + using std::tanf; + using std::tanl; + + using std::acosh; + using std::acoshf; + using std::acoshl; + + using std::asinh; + using std::asinhf; + using std::asinhl; + + using std::atanh; + using std::atanhf; + using std::atanhl; + + using std::cosh; + using std::coshf; + using std::coshl; + + using std::sinh; + using std::sinhf; + using std::sinhl; + + using std::tanh; + using std::tanhf; + using std::tanhl; + + using std::exp; + using std::expf; + using std::expl; + + using std::exp2; + using std::exp2f; + using std::exp2l; + + using std::expm1; + using std::expm1f; + using std::expm1l; + + using std::frexp; + using std::frexpf; + using std::frexpl; + + using std::ilogb; + using std::ilogbf; + using std::ilogbl; + + using std::ldexp; + using std::ldexpf; + using std::ldexpl; + + using std::log; + using std::logf; + using std::logl; + + using std::log10; + using std::log10f; + using std::log10l; + + using std::log1p; + using std::log1pf; + using std::log1pl; + + using std::log2; + using std::log2f; + using std::log2l; + + using std::logb; + using std::logbf; + using std::logbl; + + using std::modf; + using std::modff; + using std::modfl; + + using std::scalbn; + using std::scalbnf; + using std::scalbnl; + + using std::scalbln; + using std::scalblnf; + using std::scalblnl; + + using std::cbrt; + using std::cbrtf; + using std::cbrtl; + + // [c.math.abs], absolute values + using std::abs; + + using std::fabs; + using std::fabsf; + using std::fabsl; + + using std::hypot; + using std::hypotf; + using std::hypotl; + + // [c.math.hypot3], three-dimensional hypotenuse + + using std::pow; + using std::powf; + using std::powl; + + using std::sqrt; + using std::sqrtf; + using std::sqrtl; + + using std::erf; + using std::erff; + using std::erfl; + + using std::erfc; + using std::erfcf; + using std::erfcl; + + using std::lgamma; + using std::lgammaf; + using std::lgammal; + + using std::tgamma; + using std::tgammaf; + using std::tgammal; + + using std::ceil; + using std::ceilf; + using std::ceill; + + using std::floor; + using std::floorf; + using std::floorl; + + using std::nearbyint; + using std::nearbyintf; + using std::nearbyintl; + + using std::rint; + using std::rintf; + using std::rintl; + + using std::lrint; + using std::lrintf; + using std::lrintl; + + using std::llrint; + using std::llrintf; + using std::llrintl; + + using std::round; + using std::roundf; + using std::roundl; + + using std::lround; + using std::lroundf; + using std::lroundl; + + using std::llround; + using std::llroundf; + using std::llroundl; + + using std::trunc; + using std::truncf; + using std::truncl; + + using std::fmod; + using std::fmodf; + using std::fmodl; + + using std::remainder; + using std::remainderf; + using std::remainderl; + + using std::remquo; + using std::remquof; + using std::remquol; + + using std::copysign; + using std::copysignf; + using std::copysignl; + + using std::nan; + using std::nanf; + using std::nanl; + + using std::nextafter; + using std::nextafterf; + using std::nextafterl; + + using std::nexttoward; + using std::nexttowardf; + using std::nexttowardl; + + using std::fdim; + using std::fdimf; + using std::fdiml; + + using std::fmax; + using std::fmaxf; + using std::fmaxl; + + using std::fmin; + using std::fminf; + using std::fminl; + + using std::fma; + using std::fmaf; + using std::fmal; + + // [c.math.lerp], linear interpolation + using std::lerp; + + // [c.math.fpclass], classification / comparison functions + using std::fpclassify; + using std::isfinite; + using std::isgreater; + using std::isgreaterequal; + using std::isinf; + using std::isless; + using std::islessequal; + using std::islessgreater; + using std::isnan; + using std::isnormal; + using std::isunordered; + using std::signbit; + + // [sf.cmath], mathematical special functions +#if 0 + // [sf.cmath.assoc.laguerre], associated Laguerre polynomials + using std::assoc_laguerre; + using std::assoc_laguerref; + using std::assoc_laguerrel; + + // [sf.cmath.assoc.legendre], associated Legendre functions + using std::assoc_legendre; + using std::assoc_legendref; + using std::assoc_legendrel; + + // [sf.cmath.beta], beta function + using std::beta; + using std::betaf; + using std::betal; + + // [sf.cmath.comp.ellint.1], complete elliptic integral of the first kind + using std::comp_ellint_1; + using std::comp_ellint_1f; + using std::comp_ellint_1l; + + // [sf.cmath.comp.ellint.2], complete elliptic integral of the second kind + using std::comp_ellint_2; + using std::comp_ellint_2f; + using std::comp_ellint_2l; + + // [sf.cmath.comp.ellint.3], complete elliptic integral of the third kind + using std::comp_ellint_3; + using std::comp_ellint_3f; + using std::comp_ellint_3l; + + // [sf.cmath.cyl.bessel.i], regular modified cylindrical Bessel functions + using std::cyl_bessel_i; + using std::cyl_bessel_if; + using std::cyl_bessel_il; + + // [sf.cmath.cyl.bessel.j], cylindrical Bessel functions of the first kind + using std::cyl_bessel_j; + using std::cyl_bessel_jf; + using std::cyl_bessel_jl; + + // [sf.cmath.cyl.bessel.k], irregular modified cylindrical Bessel functions + using std::cyl_bessel_k; + using std::cyl_bessel_kf; + using std::cyl_bessel_kl; + + // [sf.cmath.cyl.neumann], cylindrical Neumann functions + // cylindrical Bessel functions of the second kind + using std::cyl_neumann; + using std::cyl_neumannf; + using std::cyl_neumannl; + + // [sf.cmath.ellint.1], incomplete elliptic integral of the first kind + using std::ellint_1; + using std::ellint_1f; + using std::ellint_1l; + + // [sf.cmath.ellint.2], incomplete elliptic integral of the second kind + using std::ellint_2; + using std::ellint_2f; + using std::ellint_2l; + + // [sf.cmath.ellint.3], incomplete elliptic integral of the third kind + using std::ellint_3; + using std::ellint_3f; + using std::ellint_3l; + + // [sf.cmath.expint], exponential integral + using std::expint; + using std::expintf; + using std::expintl; + + // [sf.cmath.hermite], Hermite polynomials + using std::hermite; + using std::hermitef; + using std::hermitel; + + // [sf.cmath.laguerre], Laguerre polynomials + using std::laguerre; + using std::laguerref; + using std::laguerrel; + + // [sf.cmath.legendre], Legendre polynomials + using std::legendre; + using std::legendref; + using std::legendrel; + + // [sf.cmath.riemann.zeta], Riemann zeta function + using std::riemann_zeta; + using std::riemann_zetaf; + using std::riemann_zetal; + + // [sf.cmath.sph.bessel], spherical Bessel functions of the first kind + using std::sph_bessel; + using std::sph_besself; + using std::sph_bessell; + + // [sf.cmath.sph.legendre], spherical associated Legendre functions + using std::sph_legendre; + using std::sph_legendref; + using std::sph_legendrel; + + // [sf.cmath.sph.neumann], spherical Neumann functions; + // spherical Bessel functions of the second kind + using std::sph_neumann; + using std::sph_neumannf; + using std::sph_neumannl; +#endif +} // namespace std + +// codecvt.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::codecvt_mode; + + using std::codecvt_utf16; + using std::codecvt_utf8; + using std::codecvt_utf8_utf16; +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// compare.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + // [cmp.categories], comparison category types + using std::partial_ordering; + using std::strong_ordering; + using std::weak_ordering; + + // named comparison functions + using std::is_eq; + using std::is_gt; + using std::is_gteq; + using std::is_lt; + using std::is_lteq; + using std::is_neq; + + // [cmp.common], common comparison category type + using std::common_comparison_category; + using std::common_comparison_category_t; + + // [cmp.concept], concept three_way_comparable + using std::three_way_comparable; + using std::three_way_comparable_with; + + // [cmp.result], result of three-way comparison + using std::compare_three_way_result; + + using std::compare_three_way_result_t; + + // [comparisons.three.way], class compare_three_way + using std::compare_three_way; + + // [cmp.alg], comparison algorithms + inline namespace __cpo { + using std::__cpo::compare_partial_order_fallback; + using std::__cpo::compare_strong_order_fallback; + using std::__cpo::compare_weak_order_fallback; + using std::__cpo::partial_order; + using std::__cpo::strong_order; + using std::__cpo::weak_order; + } // namespace __cpo + +} // namespace std + +// complex.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + // [complex], class template complex + using std::complex; + + // [complex.ops], operators + using std::operator+; + using std::operator-; + using std::operator*; + using std::operator/; + + using std::operator==; +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::operator>>; + using std::operator<<; +#endif // _LIBCPP_HAS_NO_LOCALIZATION + + // [complex.value.ops], values + using std::imag; + using std::real; + + using std::abs; + using std::arg; + using std::norm; + + using std::conj; + using std::polar; + using std::proj; + + // [complex.transcendentals], transcendentals + using std::acos; + using std::asin; + using std::atan; + + using std::acosh; + using std::asinh; + using std::atanh; + + using std::cos; + using std::cosh; + using std::exp; + using std::log; + using std::log10; + + using std::pow; + + using std::sin; + using std::sinh; + using std::sqrt; + using std::tan; + using std::tanh; + + // [complex.literals], complex literals + inline namespace literals { + inline namespace complex_literals { + using std::operator""il; + using std::operator""i; + using std::operator""if; + } // namespace complex_literals + } // namespace literals + +} // namespace std + +// concepts.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + // [concepts.lang], language-related concepts + // [concept.same], concept same_as + using std::same_as; + + // [concept.derived], concept derived_from + using std::derived_from; + + // [concept.convertible], concept convertible_to + using std::convertible_to; + + // [concept.commonref], concept common_reference_with + using std::common_reference_with; + + // [concept.common], concept common_with + using std::common_with; + + // [concepts.arithmetic], arithmetic concepts + using std::floating_point; + using std::integral; + using std::signed_integral; + using std::unsigned_integral; + + // [concept.assignable], concept assignable_from + using std::assignable_from; + + // [concept.swappable], concept swappable + namespace ranges { + inline namespace __cpo { + using std::ranges::__cpo::swap; + } + } // namespace ranges + + using std::swappable; + using std::swappable_with; + + // [concept.destructible], concept destructible + using std::destructible; + + // [concept.constructible], concept constructible_from + using std::constructible_from; + + // [concept.default.init], concept default_initializable + using std::default_initializable; + + // [concept.moveconstructible], concept move_constructible + using std::move_constructible; + + // [concept.copyconstructible], concept copy_constructible + using std::copy_constructible; + + // [concepts.compare], comparison concepts + // [concept.equalitycomparable], concept equality_comparable + using std::equality_comparable; + using std::equality_comparable_with; + + // [concept.totallyordered], concept totally_ordered + using std::totally_ordered; + using std::totally_ordered_with; + + // [concepts.object], object concepts + using std::copyable; + using std::movable; + using std::regular; + using std::semiregular; + + // [concepts.callable], callable concepts + // [concept.invocable], concept invocable + using std::invocable; + + // [concept.regularinvocable], concept regular_invocable + using std::regular_invocable; + + // [concept.predicate], concept predicate + using std::predicate; + + // [concept.relation], concept relation + using std::relation; + + // [concept.equiv], concept equivalence_relation + using std::equivalence_relation; + + // [concept.strictweakorder], concept strict_weak_order + using std::strict_weak_order; + +} // namespace std + +// condition_variable.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS + // [thread.condition.condvar], class condition_variable + using std::condition_variable; + // [thread.condition.condvarany], class condition_variable_any + using std::condition_variable_any; + + // [thread.condition.nonmember], non-member functions + using std::notify_all_at_thread_exit; + + using std::cv_status; +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// coroutine.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + + // [coroutine.traits], coroutine traits + using std::coroutine_traits; + + // [coroutine.handle], coroutine handle + using std::coroutine_handle; + + // [coroutine.handle.compare], comparison operators + using std::operator==; + using std::operator<=>; + + // [coroutine.handle.hash], hash support + using std::hash; + + // [coroutine.noop], no-op coroutines + using std::noop_coroutine; + using std::noop_coroutine_handle; + using std::noop_coroutine_promise; + + // [coroutine.trivial.awaitables], trivial awaitables + using std::suspend_always; + using std::suspend_never; +} // namespace std + +// csetjmp.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::jmp_buf; + using std::longjmp; +} // namespace std + +// csignal.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::sig_atomic_t; + + // [support.signal], signal handlers + using std::signal; + + using std::raise; + +} // namespace std + +// cstdarg.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::va_list; +} // namespace std + +// cstddef.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::max_align_t; + using std::nullptr_t; + using std::ptrdiff_t; + using std::size_t; + + using std::byte; + + // [support.types.byteops], byte type operations + using std::operator<<=; + using std::operator<<; + using std::operator>>=; + using std::operator>>; + using std::operator|=; + using std::operator|; + using std::operator&=; + using std::operator&; + using std::operator^=; + using std::operator^; + using std::operator~; + using std::to_integer; +} // namespace std + +// cstdint.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // signed + using std::int8_t _LIBCPP_USING_IF_EXISTS; + using std::int16_t _LIBCPP_USING_IF_EXISTS; + using std::int32_t _LIBCPP_USING_IF_EXISTS; + using std::int64_t _LIBCPP_USING_IF_EXISTS; + + using std::int_fast16_t; + using std::int_fast32_t; + using std::int_fast64_t; + using std::int_fast8_t; + + using std::int_least16_t; + using std::int_least32_t; + using std::int_least64_t; + using std::int_least8_t; + + using std::intmax_t; + + using std::intptr_t _LIBCPP_USING_IF_EXISTS; + + // unsigned + using std::uint8_t _LIBCPP_USING_IF_EXISTS; + using std::uint16_t _LIBCPP_USING_IF_EXISTS; + using std::uint32_t _LIBCPP_USING_IF_EXISTS; + using std::uint64_t _LIBCPP_USING_IF_EXISTS; + + using std::uint_fast16_t; + using std::uint_fast32_t; + using std::uint_fast64_t; + using std::uint_fast8_t; + + using std::uint_least16_t; + using std::uint_least32_t; + using std::uint_least64_t; + using std::uint_least8_t; + + using std::uintmax_t; + + using std::uintptr_t _LIBCPP_USING_IF_EXISTS; +} // namespace std + +// cstdio.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::FILE; + using std::fpos_t; + using std::size_t; + + using std::clearerr; + using std::fclose; + using std::feof; + using std::ferror; + using std::fflush; + using std::fgetc; + using std::fgetpos; + using std::fgets; + using std::fopen; + using std::fprintf; + using std::fputc; + using std::fputs; + using std::fread; + using std::freopen; + using std::fscanf; + using std::fseek; + using std::fsetpos; + using std::ftell; + using std::fwrite; + using std::getc; + using std::getchar; + using std::perror; + using std::printf; + using std::putc; + using std::putchar; + using std::puts; + using std::remove; + using std::rename; + using std::rewind; + using std::scanf; + using std::setbuf; + using std::setvbuf; + using std::snprintf; + using std::sprintf; + using std::sscanf; + using std::tmpfile; + using std::tmpnam; + using std::ungetc; + using std::vfprintf; + using std::vfscanf; + using std::vprintf; + using std::vscanf; + using std::vsnprintf; + using std::vsprintf; + using std::vsscanf; +} // namespace std + +// cstdlib.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::div_t; + using std::ldiv_t; + using std::lldiv_t; + using std::size_t; + + // [support.start.term], start and termination + using std::_Exit; + using std::abort; + using std::at_quick_exit; + using std::atexit; + using std::exit; + using std::quick_exit; + + using std::getenv; + using std::system; + + // [c.malloc], C library memory allocation + using std::aligned_alloc; + using std::calloc; + using std::free; + using std::malloc; + using std::realloc; + + using std::atof; + using std::atoi; + using std::atol; + using std::atoll; + using std::strtod; + using std::strtof; + using std::strtol; + using std::strtold; + using std::strtoll; + using std::strtoul; + using std::strtoull; + + // [c.mb.wcs], multibyte / wide string and character conversion functions + using std::mblen; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::mbstowcs; + using std::mbtowc; + using std::wcstombs; + using std::wctomb; +#endif + // [alg.c.library], C standard library algorithms + using std::bsearch; + using std::qsort; + + // [c.math.rand], low-quality random number generation + using std::rand; + using std::srand; + + // [c.math.abs], absolute values + using std::abs; + + using std::labs; + using std::llabs; + + using std::div; + using std::ldiv; + using std::lldiv; +} // namespace std + +// cstring.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::size_t; + + using std::memchr; + using std::memcmp; + using std::memcpy; + using std::memmove; + using std::memset; + using std::strcat; + using std::strchr; + using std::strcmp; + using std::strcoll; + using std::strcpy; + using std::strcspn; + using std::strerror; + using std::strlen; + using std::strncat; + using std::strncmp; + using std::strncpy; + using std::strpbrk; + using std::strrchr; + using std::strspn; + using std::strstr; + using std::strtok; + using std::strxfrm; +} // namespace std + +// ctime.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::clock_t; + using std::size_t; + using std::time_t; + + using std::timespec; + using std::tm; + + using std::asctime; + using std::clock; + using std::ctime; + using std::difftime; + using std::gmtime; + using std::localtime; + using std::mktime; + using std::strftime; + using std::time; + using std::timespec_get; +} // namespace std + +// cuchar.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // Note the Standard does not mark these symbols optional, but libc++'s header + // does. So this seems strictly not to be conforming. + + // mbstate_t is conditionally here, but always present in cwchar.cppm. To avoid + // conflicing declarations omit the using here. + + // size_t is conditionally here, but always present in cstddef.cppm. To avoid + // conflicing declarations omit the using here. + +#if !defined(_LIBCPP_HAS_NO_C8RTOMB_MBRTOC8) + using std::mbrtoc8 _LIBCPP_USING_IF_EXISTS; + using std::c8rtomb _LIBCPP_USING_IF_EXISTS; +#endif + using std::mbrtoc16 _LIBCPP_USING_IF_EXISTS; + using std::c16rtomb _LIBCPP_USING_IF_EXISTS; + using std::mbrtoc32 _LIBCPP_USING_IF_EXISTS; + using std::c32rtomb _LIBCPP_USING_IF_EXISTS; +} // namespace std + +// cwchar.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::mbstate_t; + using std::size_t; + using std::wint_t; + + using std::tm; + + using std::btowc; + using std::fgetwc; + using std::fgetws; + using std::fputwc; + using std::fputws; + using std::fwide; + using std::fwprintf; + using std::fwscanf; + using std::getwc; + using std::getwchar; + using std::putwc; + using std::putwchar; + using std::swprintf; + using std::swscanf; + using std::ungetwc; + using std::vfwprintf; + using std::vfwscanf; + using std::vswprintf; + using std::vswscanf; + using std::vwprintf; + using std::vwscanf; + using std::wcscat; + using std::wcschr; + using std::wcscmp; + using std::wcscoll; + using std::wcscpy; + using std::wcscspn; + using std::wcsftime; + using std::wcslen; + using std::wcsncat; + using std::wcsncmp; + using std::wcsncpy; + using std::wcspbrk; + using std::wcsrchr; + using std::wcsspn; + using std::wcsstr; + using std::wcstod; + using std::wcstof; + using std::wcstok; + using std::wcstol; + using std::wcstold; + using std::wcstoll; + using std::wcstoul; + using std::wcstoull; + using std::wcsxfrm; + using std::wctob; + using std::wmemchr; + using std::wmemcmp; + using std::wmemcpy; + using std::wmemmove; + using std::wmemset; + using std::wprintf; + using std::wscanf; + + // [c.mb.wcs], multibyte / wide string and character conversion functions + using std::mbrlen; + using std::mbrtowc; + using std::mbsinit; + using std::mbsrtowcs; + using std::wcrtomb; + using std::wcsrtombs; +#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS +} // namespace std + +// cwctype.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wctrans_t; + using std::wctype_t; + using std::wint_t; + + using std::iswalnum; + using std::iswalpha; + using std::iswblank; + using std::iswcntrl; + using std::iswctype; + using std::iswdigit; + using std::iswgraph; + using std::iswlower; + using std::iswprint; + using std::iswpunct; + using std::iswspace; + using std::iswupper; + using std::iswxdigit; + using std::towctrans; + using std::towlower; + using std::towupper; + using std::wctrans; + using std::wctype; +#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS +} // namespace std + +// deque.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [deque], class template deque + using std::deque; + + using std::operator==; + using std::operator<=>; + + using std::swap; + + // [deque.erasure], erasure + using std::erase; + using std::erase_if; + + namespace pmr { + using std::pmr::deque; + } +} // namespace std + +// exception.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::bad_exception; + using std::current_exception; + using std::exception; + using std::exception_ptr; + using std::get_terminate; + using std::make_exception_ptr; + using std::nested_exception; + using std::rethrow_exception; + using std::rethrow_if_nested; + using std::set_terminate; + using std::terminate; + using std::terminate_handler; + using std::throw_with_nested; + using std::uncaught_exception; + using std::uncaught_exceptions; +} // namespace std + +// execution.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifdef _LIBCPP_ENABLE_EXPERIMENTAL +export namespace std { + // [execpol.type], execution policy type trait + using std::is_execution_policy; + using std::is_execution_policy_v; +} // namespace std + +export namespace std::execution { + // [execpol.seq], sequenced execution policy + using std::execution::sequenced_policy; + + // [execpol.par], parallel execution policy + using std::execution::parallel_policy; + + // [execpol.parunseq], parallel and unsequenced execution policy + using std::execution::parallel_unsequenced_policy; + + // [execpol.unseq], unsequenced execution policy + using std::execution::unsequenced_policy; + + // [execpol.objects], execution policy objects + using std::execution::par; + using std::execution::par_unseq; + using std::execution::seq; + using std::execution::unseq; +} // namespace std::execution +#endif // _LIBCPP_ENABLE_EXPERIMENTAL + +// expected.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if _LIBCPP_STD_VER >= 23 + // [expected.unexpected], class template unexpected + using std::unexpected; + + // [expected.bad], class template bad_expected_access + using std::bad_expected_access; + + // in-place construction of unexpected values + using std::unexpect; + using std::unexpect_t; + + // [expected.expected], class template expected + using std::expected; +#endif // _LIBCPP_STD_VER >= 23 +} // namespace std + +// filesystem.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std::filesystem { + // [fs.class.path], paths + using std::filesystem::path; + + // [fs.path.nonmember], path non-member functions + using std::filesystem::hash_value; + using std::filesystem::swap; + + // [fs.class.filesystem.error], filesystem errors + using std::filesystem::filesystem_error; + +#ifndef _LIBCPP_HAS_NO_FILESYSTEM + // [fs.class.directory.entry], directory entries + using std::filesystem::directory_entry; + + // [fs.class.directory.iterator], directory iterators + using std::filesystem::directory_iterator; + + // [fs.dir.itr.nonmembers], range access for directory iterators + using std::filesystem::begin; + using std::filesystem::end; + + // [fs.class.rec.dir.itr], recursive directory iterators + using std::filesystem::recursive_directory_iterator; +#endif // _LIBCPP_HAS_NO_FILESYSTEM + + // [fs.rec.dir.itr.nonmembers], range access for recursive directory iterators + + // [fs.class.file.status], file status + using std::filesystem::file_status; + using std::filesystem::space_info; + + // [fs.enum], enumerations + using std::filesystem::copy_options; + using std::filesystem::directory_options; + using std::filesystem::file_type; + using std::filesystem::perm_options; + using std::filesystem::perms; + + using std::filesystem::file_time_type; + + // several of these enums are a bitmask type. + // [bitmask.types] specified operators + using std::filesystem::operator&; + using std::filesystem::operator&=; + using std::filesystem::operator^; + using std::filesystem::operator^=; + using std::filesystem::operator|; + using std::filesystem::operator|=; + using std::filesystem::operator~; + +#ifndef _LIBCPP_HAS_NO_FILESYSTEM + // [fs.op.funcs], filesystem operations + using std::filesystem::absolute; + using std::filesystem::canonical; + using std::filesystem::copy; + using std::filesystem::copy_file; + using std::filesystem::copy_symlink; + using std::filesystem::create_directories; + using std::filesystem::create_directory; + using std::filesystem::create_directory_symlink; + using std::filesystem::create_hard_link; + using std::filesystem::create_symlink; + using std::filesystem::current_path; + using std::filesystem::equivalent; + using std::filesystem::exists; + using std::filesystem::file_size; + using std::filesystem::hard_link_count; + + using std::filesystem::is_block_file; + using std::filesystem::is_character_file; + using std::filesystem::is_directory; + using std::filesystem::is_empty; + using std::filesystem::is_fifo; + using std::filesystem::is_other; + using std::filesystem::is_regular_file; + using std::filesystem::is_socket; + using std::filesystem::is_symlink; + + using std::filesystem::last_write_time; + using std::filesystem::permissions; + using std::filesystem::proximate; + using std::filesystem::read_symlink; + using std::filesystem::relative; + using std::filesystem::remove; + + using std::filesystem::remove_all; + using std::filesystem::rename; + using std::filesystem::resize_file; + using std::filesystem::space; + using std::filesystem::status; + using std::filesystem::status_known; + using std::filesystem::symlink_status; + using std::filesystem::temp_directory_path; + using std::filesystem::weakly_canonical; +#endif // _LIBCPP_HAS_NO_FILESYSTEM + + // [depr.fs.path.factory] + using std::filesystem::u8path; +} // namespace std::filesystem + +// [fs.path.hash], hash support +export namespace std { + using std::hash; +} + +export namespace std::ranges { +#ifndef _LIBCPP_HAS_NO_FILESYSTEM + using std::ranges::enable_borrowed_range; + using std::ranges::enable_view; +#endif // _LIBCPP_HAS_NO_FILESYSTEM +} // namespace std::ranges + +// flat_map.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if 0 + // [flat.map], class template flat_Âmap + using std::flat_map; + + using std::sorted_unique; + using std::sorted_unique_t; + + using std::uses_allocator; + + // [flat.map.erasure], erasure for flat_Âmap + using std::erase_if; + + // [flat.multimap], class template flat_Âmultimap + using std::flat_multimap; + + using std::sorted_equivalent; + using std::sorted_equivalent_t; + + // [flat.multimap.erasure], erasure for flat_Âmultimap +#endif +} // namespace std + +// flat_set.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if 0 + // [flat.set], class template flat_Âset + using std::flat_set; + + using std::sorted_unique; + using std::sorted_unique_t; + + using std::uses_allocator; + + // [flat.set.erasure], erasure for flat_Âset + using std::erase_if; + + // [flat.multiset], class template flat_Âmultiset + using std::flat_multiset; + + using std::sorted_equivalent; + using std::sorted_equivalent_t; +#endif +} // namespace std + +// format.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [format.context], class template basic_format_context + using std::basic_format_context; + using std::format_context; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wformat_context; +#endif + + // [format.args], class template basic_format_args + using std::basic_format_args; + using std::format_args; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wformat_args; +#endif + + // [format.fmt.string], class template basic_format_string + using std::basic_format_string; + using std::format_string; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wformat_string; +#endif + + // [format.functions], formatting functions + using std::format; + using std::format_to; + using std::vformat; + using std::vformat_to; + + using std::format_to_n; + using std::format_to_n_result; + using std::formatted_size; + + // [format.formatter], formatter + using std::formatter; + +#if _LIBCPP_STD_VER >= 23 + // [format.formattable], concept formattable + using std::formattable; +#endif + + // [format.parse.ctx], class template basic_format_parse_context + using std::basic_format_parse_context; + using std::format_parse_context; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wformat_parse_context; +#endif + +#if _LIBCPP_STD_VER >= 23 + // [format.range], formatting of ranges + // [format.range.fmtkind], variable template format_kind + using std::format_kind; + using std::range_format; + + // [format.range.formatter], class template range_formatter + using std::range_formatter; +#endif // _LIBCPP_STD_VER >= 23 + + // [format.arg], class template basic_format_arg + using std::basic_format_arg; + using std::visit_format_arg; + + // [format.arg.store], class template format-arg-store + using std::make_format_args; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::make_wformat_args; +#endif + + // [format.error], class format_error + using std::format_error; +} // namespace std + +// forward_list.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [forward.list], class template forward_list + using std::forward_list; + + using std::operator==; + using std::operator<=>; + + using std::swap; + + // [forward.list.erasure], erasure + using std::erase; + using std::erase_if; + + namespace pmr { + using std::pmr::forward_list; + } +} // namespace std + +// fstream.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::basic_filebuf; + +# ifndef _LIBCPP_HAS_NO_FILESYSTEM + using std::swap; +# endif + + using std::filebuf; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wfilebuf; +# endif + + using std::basic_ifstream; + + using std::ifstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wifstream; +# endif + + using std::basic_ofstream; + + using std::ofstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wofstream; +# endif + + using std::basic_fstream; + + using std::fstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wfstream; +# endif +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// functional.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [func.invoke], invoke + using std::invoke; +#if _LIBCPP_STD_VER >= 23 + using std::invoke_r; +#endif + + // [refwrap], reference_wrapper + using std::reference_wrapper; + + using std::cref; + using std::ref; + + // [arithmetic.operations], arithmetic operations + using std::divides; + using std::minus; + using std::modulus; + using std::multiplies; + using std::negate; + using std::plus; + // [comparisons], comparisons + using std::equal_to; + using std::greater; + using std::greater_equal; + using std::less; + using std::less_equal; + using std::not_equal_to; + + // [comparisons.three.way], class compare_three_way + using std::compare_three_way; + + // [logical.operations], logical operations + using std::logical_and; + using std::logical_not; + using std::logical_or; + + // [bitwise.operations], bitwise operations + using std::bit_and; + using std::bit_not; + using std::bit_or; + using std::bit_xor; + + // [func.identity], identity + using std::identity; + + // [func.not.fn], function template not_fn + using std::not_fn; + + // [func.bind.partial], function templates bind_front and bind_back + // using std::bind_back; + using std::bind_front; + + // [func.bind], bind + using std::is_bind_expression; + using std::is_bind_expression_v; + using std::is_placeholder; + using std::is_placeholder_v; + + using std::bind; + + namespace placeholders { + // M is the implementation-defined number of placeholders + using std::placeholders::_1; + using std::placeholders::_10; + using std::placeholders::_2; + using std::placeholders::_3; + using std::placeholders::_4; + using std::placeholders::_5; + using std::placeholders::_6; + using std::placeholders::_7; + using std::placeholders::_8; + using std::placeholders::_9; + } // namespace placeholders + + // [func.memfn], member function adaptors + using std::mem_fn; + + // [func.wrap], polymorphic function wrappers + using std::bad_function_call; + + using std::function; + + using std::swap; + + using std::operator==; + + // [func.wrap.move], move only wrapper + // using std::move_only_function; + + // [func.search], searchers + using std::default_searcher; + + using std::boyer_moore_searcher; + + using std::boyer_moore_horspool_searcher; + + // [unord.hash], class template hash + using std::hash; + + namespace ranges { + // [range.cmp], concept-constrained comparisons + using std::ranges::equal_to; + using std::ranges::greater; + using std::ranges::greater_equal; + using std::ranges::less; + using std::ranges::less_equal; + using std::ranges::not_equal_to; + } // namespace ranges +} // namespace std + +// future.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS + using std::future_errc; + using std::future_status; + using std::launch; + + // launch is a bitmask type. + // [bitmask.types] specified operators + using std::operator&; + using std::operator&=; + using std::operator^; + using std::operator^=; + using std::operator|; + using std::operator|=; + using std::operator~; + + // [futures.errors], error handling + using std::is_error_code_enum; + using std::make_error_code; + using std::make_error_condition; + + using std::future_category; + + // [futures.future.error], class future_error + using std::future_error; + + // [futures.promise], class template promise + using std::promise; + + using std::swap; + + using std::uses_allocator; + + // [futures.unique.future], class template future + using std::future; + + // [futures.shared.future], class template shared_future + using std::shared_future; + + // [futures.task], class template packaged_task + using std::packaged_task; + + // [futures.async], function template async + using std::async; +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// generator.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if 0 + using std::generator; +#endif +} // namespace std + +// hazard_pointer.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if 0 +# if _LIBCPP_STD_VER >= 23 + // 4.1.3, class template hazard_pointer_obj_base + using std::hazard_pointer_obj_base; + // 4.1.4, class hazard_pointer + using std::hazard_pointer; + // 4.1.5, Construct non-empty hazard_pointer + using std::make_hazard_pointer; + // 4.1.6, Hazard pointer swap + using std::swap; +# endif // _LIBCPP_STD_VER >= 23 +#endif +} // namespace std + +// initializer_list.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::initializer_list; + + // [support.initlist.range], initializer list range access + using std::begin; + using std::end; +} // namespace std + +// iomanip.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::get_money; + using std::get_time; + using std::put_money; + using std::put_time; + using std::resetiosflags; + using std::setbase; + using std::setfill; + using std::setiosflags; + using std::setprecision; + using std::setw; + + using std::quoted; +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// ios.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::fpos; + // based on [tab:fpos.operations] + using std::operator!=; // Note not affected by P1614, seems like a bug. + using std::operator-; + using std::operator==; + + using std::streamoff; + using std::streamsize; + + using std::basic_ios; + using std::ios_base; + + // [std.ios.manip], manipulators + using std::boolalpha; + using std::noboolalpha; + + using std::noshowbase; + using std::showbase; + + using std::noshowpoint; + using std::showpoint; + + using std::noshowpos; + using std::showpos; + + using std::noskipws; + using std::skipws; + + using std::nouppercase; + using std::uppercase; + + using std::nounitbuf; + using std::unitbuf; + + // [adjustfield.manip], adjustfield + using std::internal; + using std::left; + using std::right; + + // [basefield.manip], basefield + using std::dec; + using std::hex; + using std::oct; + + // [floatfield.manip], floatfield + using std::defaultfloat; + using std::fixed; + using std::hexfloat; + using std::scientific; + + // [error.reporting], error reporting + using std::io_errc; + + using std::iostream_category; + using std::is_error_code_enum; + using std::make_error_code; + using std::make_error_condition; + + // [iosfwd.syn] + using std::ios; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wios; +# endif +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// iosfwd.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::streampos; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wstreampos; +#endif + using std::u16streampos; + using std::u32streampos; + using std::u8streampos; + +#ifdef _LIBCPP_HAS_YES_SYNCSTREAM + using std::basic_osyncstream; + using std::basic_syncbuf; +#endif + + using std::istreambuf_iterator; + using std::ostreambuf_iterator; + +#ifdef _LIBCPP_HAS_YES_SYNCSTREAM + using std::osyncstream; + using std::syncbuf; +#endif + +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +#ifdef _LIBCPP_HAS_YES_SYNCSTREAM + using std::wosyncstream; + using std::wsyncbuf; +#endif +#endif + + using std::fpos; +} // namespace std + +// iostream.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::cerr; + using std::cin; + using std::clog; + using std::cout; + +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wcerr; + using std::wcin; + using std::wclog; + using std::wcout; +# endif +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// istream.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::basic_istream; + + using std::istream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wistream; +# endif + + using std::basic_iostream; + + using std::iostream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wiostream; +# endif + + using std::ws; + + using std::operator>>; +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// iterator.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [iterator.assoc.types], associated types + // [incrementable.traits], incrementable traits + using std::incrementable_traits; + using std::iter_difference_t; + + using std::indirectly_readable_traits; + using std::iter_value_t; + + // [iterator.traits], iterator traits + using std::iterator_traits; + + using std::iter_reference_t; + + namespace ranges { + // [iterator.cust], customization point objects + inline namespace __cpo { + // [iterator.cust.move], ranges::iter_move + using std::ranges::__cpo::iter_move; + + // [iterator.cust.swap], ranges::iter_swap + using std::ranges::__cpo::iter_swap; + } // namespace __cpo + } // namespace ranges + + using std::iter_rvalue_reference_t; + + // [iterator.concepts], iterator concepts + // [iterator.concept.readable], concept indirectly_readable + using std::indirectly_readable; + + using std::iter_common_reference_t; + + // [iterator.concept.writable], concept indirectly_writable + using std::indirectly_writable; + + // [iterator.concept.winc], concept weakly_incrementable + using std::weakly_incrementable; + + // [iterator.concept.inc], concept incrementable + using std::incrementable; + + // [iterator.concept.iterator], concept input_or_output_iterator + using std::input_or_output_iterator; + + // [iterator.concept.sentinel], concept sentinel_for + using std::sentinel_for; + + // [iterator.concept.sizedsentinel], concept sized_sentinel_for + using std::disable_sized_sentinel_for; + + using std::sized_sentinel_for; + + // [iterator.concept.input], concept input_iterator + using std::input_iterator; + + // [iterator.concept.output], concept output_iterator + using std::output_iterator; + + // [iterator.concept.forward], concept forward_iterator + using std::forward_iterator; + + // [iterator.concept.bidir], concept bidirectional_iterator + using std::bidirectional_iterator; + + // [iterator.concept.random.access], concept random_access_iterator + using std::random_access_iterator; + + // [iterator.concept.contiguous], concept contiguous_iterator + using std::contiguous_iterator; + + // [indirectcallable], indirect callable requirements + // [indirectcallable.indirectinvocable], indirect callables + using std::indirectly_unary_invocable; + + using std::indirectly_regular_unary_invocable; + + using std::indirect_unary_predicate; + + using std::indirect_binary_predicate; + + using std::indirect_equivalence_relation; + + using std::indirect_strict_weak_order; + + using std::indirect_result_t; + + // [projected], projected + using std::projected; + + // [alg.req], common algorithm requirements + // [alg.req.ind.move], concept indirectly_movable + using std::indirectly_movable; + + using std::indirectly_movable_storable; + + // [alg.req.ind.copy], concept indirectly_copyable + using std::indirectly_copyable; + + using std::indirectly_copyable_storable; + + // [alg.req.ind.swap], concept indirectly_swappable + using std::indirectly_swappable; + + // [alg.req.ind.cmp], concept indirectly_comparable + using std::indirectly_comparable; + + // [alg.req.permutable], concept permutable + using std::permutable; + + // [alg.req.mergeable], concept mergeable + using std::mergeable; + + // [alg.req.sortable], concept sortable + using std::sortable; + + // [iterator.primitives], primitives + // [std.iterator.tags], iterator tags + using std::bidirectional_iterator_tag; + using std::contiguous_iterator_tag; + using std::forward_iterator_tag; + using std::input_iterator_tag; + using std::output_iterator_tag; + using std::random_access_iterator_tag; + + // [iterator.operations], iterator operations + using std::advance; + using std::distance; + using std::next; + using std::prev; + + // [range.iter.ops], range iterator operations + namespace ranges { + // [range.iter.op.advance], ranges​::​advance + using std::ranges::advance; + + // [range.iter.op.distance], ranges​::​distance + using std::ranges::distance; + + // [range.iter.op.next], ranges​::​next + using std::ranges::next; + + // [range.iter.op.prev], ranges​::​prev + using std::ranges::prev; + } // namespace ranges + + // [predef.iterators], predefined iterators and sentinels + // [reverse.iterators], reverse iterators + using std::reverse_iterator; + + using std::operator==; + using std::operator!=; + using std::operator<; + using std::operator>; + using std::operator<=; + using std::operator>=; + using std::operator<=>; + + using std::operator-; + using std::operator+; + + using std::make_reverse_iterator; + + // using std::disable_sized_sentinel_for; + + // [insert.iterators], insert iterators + using std::back_insert_iterator; + using std::back_inserter; + + using std::front_insert_iterator; + using std::front_inserter; + + using std::insert_iterator; + using std::inserter; + + // [const.iterators], constant iterators and sentinels + // [const.iterators.alias], alias templates + // using std::const_iterator; + // using std::const_sentinel; + // using std::iter_const_reference_t; + + // [const.iterators.iterator], class template basic_const_iterator + // using std::basic_const_iterator; + + // using std::common_type; + + // using std::make_const_iterator; + + // [move.iterators], move iterators and sentinels + using std::move_iterator; + + using std::make_move_iterator; + + using std::move_sentinel; + + using std::common_iterator; + + // [default.sentinel], default sentinel + using std::default_sentinel; + using std::default_sentinel_t; + + // [iterators.counted], counted iterators + using std::counted_iterator; + + // [unreachable.sentinel], unreachable sentinel + using std::unreachable_sentinel; + using std::unreachable_sentinel_t; + + // [stream.iterators], stream iterators + using std::istream_iterator; + + using std::ostream_iterator; + + using std::istreambuf_iterator; + using std::ostreambuf_iterator; + + // [iterator.range], range access + using std::begin; + using std::cbegin; + using std::cend; + using std::crbegin; + using std::crend; + using std::end; + using std::rbegin; + using std::rend; + + using std::empty; + using std::size; + using std::ssize; + + using std::data; + + // [depr.iterator] + using std::iterator; +} // namespace std + +// latch.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS + using std::latch; +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// limits.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [fp.style], floating-point type properties + using std::float_denorm_style; + using std::float_round_style; + + // [numeric.limits], class template numeric_Âlimits + using std::numeric_limits; +} // namespace std + +// list.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [list], class template list + using std::list; + + using std::operator==; + using std::operator<=>; + + using std::swap; + + // [list.erasure], erasure + using std::erase; + using std::erase_if; + + namespace pmr { + using std::pmr::list; + } +} // namespace std + +// locale.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + // [locale], locale + using std::has_facet; + using std::locale; + using std::use_facet; + + // [locale.convenience], convenience interfaces + using std::isalnum; + using std::isalpha; + using std::isblank; + using std::iscntrl; + using std::isdigit; + using std::isgraph; + using std::islower; + using std::isprint; + using std::ispunct; + using std::isspace; + using std::isupper; + using std::isxdigit; + using std::tolower; + using std::toupper; + + // [category.ctype], ctype + using std::codecvt; + using std::codecvt_base; + using std::codecvt_byname; + using std::ctype; + using std::ctype_base; + using std::ctype_byname; + + // [category.numeric], numeric + using std::num_get; + using std::num_put; + using std::numpunct; + using std::numpunct_byname; + + // [category.collate], collation + using std::collate; + using std::collate_byname; + + // [category.time], date and time + using std::time_base; + using std::time_get; + using std::time_get_byname; + using std::time_put; + using std::time_put_byname; + + // [category.monetary], money + using std::money_base; + using std::money_get; + using std::money_put; + using std::moneypunct; + using std::moneypunct_byname; + + // [category.messages], message retrieval + using std::messages; + using std::messages_base; + using std::messages_byname; + + // [depr.conversions.buffer] + using std::wbuffer_convert; + + // [depr.conversions.string] + using std::wstring_convert; +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// map.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [map], class template map + using std::map; + + using std::operator==; + using std::operator<=>; + + using std::swap; + + // [map.erasure], erasure for map + using std::erase_if; + + // [multimap], class template multimap + using std::multimap; + + namespace pmr { + using std::pmr::map; + using std::pmr::multimap; + } // namespace pmr +} // namespace std + +// mdspan.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if _LIBCPP_STD_VER >= 23 + // [mdspan.extents], class template extents + using std::extents; + + // [mdspan.extents.dextents], alias template dextents + using std::dextents; + + // [mdspan.layout], layout mapping + using std::layout_left; + using std::layout_right; +#if _LIBCPP_VERSION >= 180000 + using std::layout_stride; +#endif + + // [mdspan.accessor.default], class template default_accessor + using std::default_accessor; + + // [mdspan.mdspan], class template mdspan + using std::mdspan; +#endif // _LIBCPP_STD_VER >= 23 +} // namespace std + +// memory.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [pointer.traits], pointer traits + using std::pointer_traits; + + // [pointer.conversion], pointer conversion + using std::to_address; + + // [ptr.align], pointer alignment + using std::align; + using std::assume_aligned; + + // [obj.lifetime], explicit lifetime management + // using std::start_lifetime_as; + // using std::start_lifetime_as_array; + + // [allocator.tag], allocator argument tag + using std::allocator_arg; + using std::allocator_arg_t; + + // [allocator.uses], uses_allocator + using std::uses_allocator; + + // [allocator.uses.trait], uses_allocator + using std::uses_allocator_v; + + // [allocator.uses.construction], uses-allocator construction + using std::uses_allocator_construction_args; + + using std::make_obj_using_allocator; + using std::uninitialized_construct_using_allocator; + + // [allocator.traits], allocator traits + using std::allocator_traits; + +#if _LIBCPP_STD_VER >= 23 + using std::allocation_result; + + using std::allocate_at_least; +#endif + + // [default.allocator], the default allocator + using std::allocator; + using std::operator==; + + // [specialized.addressof], addressof + using std::addressof; + + // [specialized.algorithms], specialized algorithms + // [special.mem.concepts], special memory concepts + + using std::uninitialized_default_construct; + using std::uninitialized_default_construct_n; + + namespace ranges { + using std::ranges::uninitialized_default_construct; + using std::ranges::uninitialized_default_construct_n; + } // namespace ranges + + using std::uninitialized_value_construct; + using std::uninitialized_value_construct_n; + + namespace ranges { + using std::ranges::uninitialized_value_construct; + using std::ranges::uninitialized_value_construct_n; + } // namespace ranges + + using std::uninitialized_copy; + using std::uninitialized_copy_n; + + namespace ranges { + using std::ranges::uninitialized_copy; + using std::ranges::uninitialized_copy_result; + + using std::ranges::uninitialized_copy_n; + using std::ranges::uninitialized_copy_n_result; + } // namespace ranges + + using std::uninitialized_move; + using std::uninitialized_move_n; + + namespace ranges { + using std::ranges::uninitialized_move; + using std::ranges::uninitialized_move_result; + + using std::ranges::uninitialized_move_n; + using std::ranges::uninitialized_move_n_result; + } // namespace ranges + + using std::uninitialized_fill; + using std::uninitialized_fill_n; + + namespace ranges { + using std::ranges::uninitialized_fill; + using std::ranges::uninitialized_fill_n; + } // namespace ranges + + // [specialized.construct], construct_at + using std::construct_at; + + namespace ranges { + using std::ranges::construct_at; + } + // [specialized.destroy], destroy + using std::destroy; + using std::destroy_at; + using std::destroy_n; + + namespace ranges { + using std::ranges::destroy; + using std::ranges::destroy_at; + using std::ranges::destroy_n; + } // namespace ranges + + // [unique.ptr], class template unique_ptr + using std::default_delete; + using std::unique_ptr; + + using std::make_unique; + using std::make_unique_for_overwrite; + + using std::operator<; + using std::operator>; + using std::operator<=; + using std::operator>=; + using std::operator<=>; + + using std::operator<<; + + // [util.smartptr.weak.bad], class bad_weak_ptr + using std::bad_weak_ptr; + + // [util.smartptr.shared], class template shared_ptr + using std::shared_ptr; + + // [util.smartptr.shared.create], shared_ptr creation + using std::allocate_shared; + using std::allocate_shared_for_overwrite; + using std::make_shared; + using std::make_shared_for_overwrite; + + // [util.smartptr.shared.spec], shared_ptr specialized algorithms + using std::swap; + + // [util.smartptr.shared.cast], shared_ptr casts + using std::const_pointer_cast; + using std::dynamic_pointer_cast; + using std::reinterpret_pointer_cast; + using std::static_pointer_cast; + + using std::get_deleter; + + // [util.smartptr.shared.io], shared_ptr I/O + + // [util.smartptr.weak], class template weak_ptr + using std::weak_ptr; + + // [util.smartptr.weak.spec], weak_ptr specialized algorithms + + // [util.smartptr.ownerless], class template owner_less + using std::owner_less; + + // [util.smartptr.enab], class template enable_shared_from_this + using std::enable_shared_from_this; + + // [util.smartptr.hash], hash support + using std::hash; + + // [util.smartptr.atomic], atomic smart pointers + // using std::atomic; + + // [out.ptr.t], class template out_ptr_t + // using std::out_ptr_t; + + // [out.ptr], function template out_ptr + // using std::out_ptr; + + // [inout.ptr.t], class template inout_ptr_t + // using std::inout_ptr_t; + + // [inout.ptr], function template inout_ptr + // using std::inout_ptr; + +#ifndef _LIBCPP_HAS_NO_THREADS + // [depr.util.smartptr.shared.atomic] + using std::atomic_is_lock_free; + + using std::atomic_load; + using std::atomic_load_explicit; + + using std::atomic_store; + using std::atomic_store_explicit; + + using std::atomic_exchange; + using std::atomic_exchange_explicit; + + using std::atomic_compare_exchange_strong; + using std::atomic_compare_exchange_strong_explicit; + using std::atomic_compare_exchange_weak; + using std::atomic_compare_exchange_weak_explicit; +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// memory_resource.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std::pmr { + // [mem.res.class], class memory_resource + using std::pmr::memory_resource; + + using std::pmr::operator==; + + // [mem.poly.allocator.class], class template polymorphic_allocator + using std::pmr::polymorphic_allocator; + + // [mem.res.global], global memory resources + using std::pmr::get_default_resource; + using std::pmr::new_delete_resource; + using std::pmr::null_memory_resource; + using std::pmr::set_default_resource; + + // [mem.res.pool], pool resource classes + using std::pmr::monotonic_buffer_resource; + using std::pmr::pool_options; + using std::pmr::synchronized_pool_resource; + using std::pmr::unsynchronized_pool_resource; +} // namespace std::pmr + +// mutex.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS + // [thread.mutex.class], class mutex + using std::mutex; + // [thread.mutex.recursive], class recursive_mutex + using std::recursive_mutex; + // [thread.timedmutex.class] class timed_mutex + using std::timed_mutex; + // [thread.timedmutex.recursive], class recursive_timed_mutex + using std::recursive_timed_mutex; + + using std::adopt_lock_t; + using std::defer_lock_t; + using std::try_to_lock_t; + + using std::adopt_lock; + using std::defer_lock; + using std::try_to_lock; + + // [thread.lock], locks + using std::lock_guard; + using std::scoped_lock; + using std::unique_lock; + + using std::swap; + + // [thread.lock.algorithm], generic locking algorithms + using std::lock; + using std::try_lock; +#endif // _LIBCPP_HAS_NO_THREADS + + using std::once_flag; + + using std::call_once; +} // namespace std + +// new.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [alloc.errors], storage allocation errors + using std::bad_alloc; + using std::bad_array_new_length; + + using std::destroying_delete; + using std::destroying_delete_t; + + // global operator new control + using std::align_val_t; + + using std::nothrow; + using std::nothrow_t; + + using std::get_new_handler; + using std::new_handler; + using std::set_new_handler; + + // [ptr.launder], pointer optimization barrier + using std::launder; +#if 0 + // [hardware.interference], hardware interference size + using std::hardware_constructive_interference_size; + using std::hardware_destructive_interference_size; +#endif +} // namespace std + +export { + using ::operator new; + using ::operator delete; + using ::operator new[]; + using ::operator delete[]; +} // export + +// numbers.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std::numbers { + using std::numbers::e_v; + using std::numbers::egamma_v; + using std::numbers::inv_pi_v; + using std::numbers::inv_sqrt3_v; + using std::numbers::inv_sqrtpi_v; + using std::numbers::ln10_v; + using std::numbers::ln2_v; + using std::numbers::log10e_v; + using std::numbers::log2e_v; + using std::numbers::phi_v; + using std::numbers::pi_v; + using std::numbers::sqrt2_v; + using std::numbers::sqrt3_v; + + using std::numbers::e; + using std::numbers::egamma; + using std::numbers::inv_pi; + using std::numbers::inv_sqrt3; + using std::numbers::inv_sqrtpi; + using std::numbers::ln10; + using std::numbers::ln2; + using std::numbers::log10e; + using std::numbers::log2e; + using std::numbers::phi; + using std::numbers::pi; + using std::numbers::sqrt2; + using std::numbers::sqrt3; +} // namespace std::numbers + +// numeric.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [accumulate], accumulate + using std::accumulate; + + // [reduce], reduce + using std::reduce; + + // [inner.product], inner product + using std::inner_product; + + // [transform.reduce], transform reduce + using std::transform_reduce; + + // [partial.sum], partial sum + using std::partial_sum; + + // [exclusive.scan], exclusive scan + using std::exclusive_scan; + + // [inclusive.scan], inclusive scan + using std::inclusive_scan; + + // [transform.exclusive.scan], transform exclusive scan + using std::transform_exclusive_scan; + + // [transform.inclusive.scan], transform inclusive scan + using std::transform_inclusive_scan; + + // [adjacent.difference], adjacent difference + using std::adjacent_difference; + + // [numeric.iota], iota + using std::iota; + + namespace ranges { + // using std::ranges::iota_result; + // using std::ranges::iota; + } // namespace ranges + + // [numeric.ops.gcd], greatest common divisor + using std::gcd; + + // [numeric.ops.lcm], least common multiple + using std::lcm; + + // [numeric.ops.midpoint], midpoint + using std::midpoint; +} // namespace std + +// optional.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [optional.optional], class template optional + using std::optional; + + // [optional.nullopt], no-value state indicator + using std::nullopt; + using std::nullopt_t; + + // [optional.bad.access], class bad_optional_access + using std::bad_optional_access; + + // [optional.relops], relational operators + using std::operator==; + using std::operator!=; + using std::operator<; + using std::operator>; + using std::operator<=; + using std::operator>=; + using std::operator<=>; + + // [optional.specalg], specialized algorithms + using std::swap; + + using std::make_optional; + + // [optional.hash], hash support + using std::hash; +} // namespace std + +// ostream.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::basic_ostream; + + using std::ostream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wostream; +# endif + + using std::endl; + using std::ends; + using std::flush; + +# if 0 + using std::emit_on_flush; + using std::flush_emit; + using std::noemit_on_flush; +# endif + using std::operator<<; + +# if 0 + // [ostream.formatted.print], print functions + using std::print; + using std::println; + + using std::vprint_nonunicode; + using std::vprint_unicode; +# endif +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// print.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if _LIBCPP_STD_VER >= 23 + // [print.fun], print functions + using std::print; + using std::println; + + using std::vprint_nonunicode; +# ifndef _LIBCPP_HAS_NO_UNICODE + using std::vprint_unicode; +# endif // _LIBCPP_HAS_NO_UNICODE +#endif // _LIBCPP_STD_VER >= 23 +} // namespace std + +// queue.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [queue], class template queue + using std::queue; + + using std::operator==; + using std::operator!=; + using std::operator<; + using std::operator>; + using std::operator<=; + using std::operator>=; + using std::operator<=>; + + using std::swap; + using std::uses_allocator; + + // [priority.queue], class template priority_queue + using std::priority_queue; +} // namespace std + +// random.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [rand.req.urng], uniform random bit generator requirements + using std::uniform_random_bit_generator; + + // [rand.eng.lcong], class template linear_congruential_engine + using std::linear_congruential_engine; + + // [rand.eng.mers], class template mersenne_twister_engine + using std::mersenne_twister_engine; + + // [rand.eng.sub], class template subtract_with_carry_engine + using std::subtract_with_carry_engine; + + // [rand.adapt.disc], class template discard_block_engine + using std::discard_block_engine; + + // [rand.adapt.ibits], class template independent_bits_engine + using std::independent_bits_engine; + + // [rand.adapt.shuf], class template shuffle_order_engine + using std::shuffle_order_engine; + + // [rand.predef], engines and engine adaptors with predefined parameters + using std::knuth_b; + using std::minstd_rand; + using std::minstd_rand0; + using std::mt19937; + using std::mt19937_64; + using std::ranlux24; + using std::ranlux24_base; + using std::ranlux48; + using std::ranlux48_base; + + using std::default_random_engine; + +#ifndef _LIBCPP_HAS_NO_RANDOM_DEVICE + // [rand.device], class random_device + using std::random_device; +#endif + + // [rand.util.seedseq], class seed_seq + using std::seed_seq; + + // [rand.util.canonical], function template generate_canonical + using std::generate_canonical; + + // [rand.dist.uni.int], class template uniform_int_distribution + using std::uniform_int_distribution; + + // [rand.dist.uni.real], class template uniform_real_distribution + using std::uniform_real_distribution; + + // [rand.dist.bern.bernoulli], class bernoulli_distribution + using std::bernoulli_distribution; + + // [rand.dist.bern.bin], class template binomial_distribution + using std::binomial_distribution; + + // [rand.dist.bern.geo], class template geometric_distribution + using std::geometric_distribution; + + // [rand.dist.bern.negbin], class template negative_binomial_distribution + using std::negative_binomial_distribution; + + // [rand.dist.pois.poisson], class template poisson_distribution + using std::poisson_distribution; + + // [rand.dist.pois.exp], class template exponential_distribution + using std::exponential_distribution; + + // [rand.dist.pois.gamma], class template gamma_distribution + using std::gamma_distribution; + + // [rand.dist.pois.weibull], class template weibull_distribution + using std::weibull_distribution; + + // [rand.dist.pois.extreme], class template extreme_value_distribution + using std::extreme_value_distribution; + + // [rand.dist.norm.normal], class template normal_distribution + using std::normal_distribution; + + // [rand.dist.norm.lognormal], class template lognormal_distribution + using std::lognormal_distribution; + + // [rand.dist.norm.chisq], class template chi_squared_distribution + using std::chi_squared_distribution; + + // [rand.dist.norm.cauchy], class template cauchy_distribution + using std::cauchy_distribution; + + // [rand.dist.norm.f], class template fisher_f_distribution + using std::fisher_f_distribution; + + // [rand.dist.norm.t], class template student_t_distribution + using std::student_t_distribution; + + // [rand.dist.samp.discrete], class template discrete_distribution + using std::discrete_distribution; + + // [rand.dist.samp.pconst], class template piecewise_constant_distribution + using std::piecewise_constant_distribution; + + // [rand.dist.samp.plinear], class template piecewise_linear_distribution + using std::piecewise_linear_distribution; +} // namespace std + +// ranges.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + namespace ranges { + inline namespace __cpo { + // [range.access], range access + using std::ranges::__cpo::begin; + using std::ranges::__cpo::cbegin; + using std::ranges::__cpo::cend; + using std::ranges::__cpo::crbegin; + using std::ranges::__cpo::crend; + using std::ranges::__cpo::end; + using std::ranges::__cpo::rbegin; + using std::ranges::__cpo::rend; + + using std::ranges::__cpo::cdata; + using std::ranges::__cpo::data; + using std::ranges::__cpo::empty; + using std::ranges::__cpo::size; + using std::ranges::__cpo::ssize; + } // namespace __cpo + + // [range.range], ranges + using std::ranges::range; + + using std::ranges::enable_borrowed_range; + + using std::ranges::borrowed_range; + + // using std::ranges::const_iterator_t; + // using std::ranges::const_sentinel_t; + using std::ranges::iterator_t; + // using std::ranges::range_const_reference_t; + using std::ranges::range_common_reference_t; + using std::ranges::range_difference_t; + using std::ranges::range_reference_t; + using std::ranges::range_rvalue_reference_t; + using std::ranges::range_size_t; + using std::ranges::range_value_t; + using std::ranges::sentinel_t; + + // [range.sized], sized ranges + using std::ranges::disable_sized_range; + using std::ranges::sized_range; + + // [range.view], views + using std::ranges::enable_view; + using std::ranges::view; + using std::ranges::view_base; + + // [range.refinements], other range refinements + using std::ranges::bidirectional_range; + using std::ranges::common_range; + // using std::ranges::constant_range; + using std::ranges::contiguous_range; + using std::ranges::forward_range; + using std::ranges::input_range; + using std::ranges::output_range; + using std::ranges::random_access_range; + using std::ranges::viewable_range; + + // [view.interface], class template view_Âinterface + using std::ranges::view_interface; + + // [range.subrange], sub-ranges + using std::ranges::subrange; + using std::ranges::subrange_kind; + + using std::ranges::get; + } // namespace ranges + + using std::ranges::get; + + namespace ranges { + + // [range.dangling], dangling iterator handling + using std::ranges::dangling; + + // [range.elementsof], class template elements_Âof + // using std::ranges::elements_of; + + using std::ranges::borrowed_iterator_t; + + using std::ranges::borrowed_subrange_t; + +#if _LIBCPP_STD_VER >= 23 + // [range.utility.conv], range conversions + using std::ranges::to; +#endif + + // [range.empty], empty view + using std::ranges::empty_view; + + namespace views { + using std::ranges::views::empty; + } + + // [range.single], single view + using std::ranges::single_view; + + namespace views { + using std::ranges::views::single; + } // namespace views + + // [range.iota], iota view + using std::ranges::iota_view; + + namespace views { + using std::ranges::views::iota; + } // namespace views + +#if _LIBCPP_STD_VER >= 23 + // [range.repeat], repeat view + using std::ranges::repeat_view; + + namespace views { + using std::ranges::views::repeat; + } // namespace views +#endif // _LIBCPP_STD_VER >= 23 + +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + // [range.istream], istream view + using std::ranges::basic_istream_view; + using std::ranges::istream_view; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::ranges::wistream_view; +# endif + + namespace views { + using std::ranges::views::istream; + } +#endif // _LIBCPP_HAS_NO_LOCALIZATION + + // [range.adaptor.object], range adaptor objects + // using std::ranges::range_adaptor_closure; + + // [range.all], all view + namespace views { + using std::ranges::views::all; + using std::ranges::views::all_t; + } // namespace views + + // [range.ref.view], ref view + using std::ranges::ref_view; + + // [range.owning.view], owning view + using std::ranges::owning_view; + +#if _LIBCPP_STD_VER >= 23 + // [range.as.rvalue], as rvalue view + using std::ranges::as_rvalue_view; + + namespace views { + using std::ranges::views::as_rvalue; + } // namespace views +#endif // _LIBCPP_STD_VER >= 23 + + // [range.filter], filter view + using std::ranges::filter_view; + + namespace views { + using std::ranges::views::filter; + } // namespace views + + // [range.transform], transform view + using std::ranges::transform_view; + + namespace views { + using std::ranges::views::transform; + } // namespace views + + // [range.take], take view + using std::ranges::take_view; + + namespace views { + using std::ranges::views::take; + } // namespace views + + // [range.take.while], take while view + using std::ranges::take_while_view; + + namespace views { + using std::ranges::views::take_while; + } // namespace views + + // [range.drop], drop view + using std::ranges::drop_view; + + namespace views { + using std::ranges::views::drop; + } // namespace views + + // [range.drop.while], drop while view + using std::ranges::drop_while_view; + + namespace views { + using std::ranges::views::drop_while; + } // namespace views + +#ifdef _LIBCPP_ENABLE_EXPERIMENTAL + using std::ranges::join_view; + + namespace views { + using std::ranges::views::join; + } // namespace views +#endif // _LIBCPP_ENABLE_EXPERIMENTAL +#if 0 + using std::ranges::join_with_view; + + namespace views { + using std::ranges::views::join_with; + } // namespace views +#endif + using std::ranges::lazy_split_view; + + // [range.split], split view + using std::ranges::split_view; + + namespace views { + using std::ranges::views::lazy_split; + using std::ranges::views::split; + } // namespace views + + // [range.counted], counted view + namespace views { + using std::ranges::views::counted; + } // namespace views + + // [range.common], common view + using std::ranges::common_view; + + namespace views { + using std::ranges::views::common; + } // namespace views + + // [range.reverse], reverse view + using std::ranges::reverse_view; + + namespace views { + using std::ranges::views::reverse; + } // namespace views + + // [range.as.const], as const view +#if 0 + using std::ranges::as_const_view; + + namespace views { + using std::ranges::views::as_const; + } // namespace views +#endif + // [range.elements], elements view + using std::ranges::elements_view; + + using std::ranges::keys_view; + using std::ranges::values_view; + + namespace views { + using std::ranges::views::elements; + using std::ranges::views::keys; + using std::ranges::views::values; + } // namespace views + +#if _LIBCPP_STD_VER >= 23 + // [range.zip], zip view + using std::ranges::zip_view; + + namespace views { + using std::ranges::views::zip; + } // namespace views +#endif // _LIBCPP_STD_VER >= 23 + +#if 0 + // [range.zip.transform], zip transform view + using std::ranges::zip_transform_view; + + namespace views { + using std::ranges::views::zip_transform; + } + + using std::ranges::adjacent_view; + + namespace views { + using std::ranges::views::adjacent; + using std::ranges::views::pairwise; + } // namespace views + + using std::ranges::adjacent_transform_view; + + namespace views { + using std::ranges::views::adjacent_transform; + using std::ranges::views::pairwise_transform; + } // namespace views + + using std::ranges::chunk_view; + + using std::ranges::chunk_view<V>; + + namespace views { + using std::ranges::views::chunk; + } + + using std::ranges::slide_view; + + namespace views { + using std::ranges::views::slide; + } +#endif + +#if _LIBCPP_STD_VER >= 23 +#if _LIBCPP_VERSION >= 180000 + // [range.chunk.by], chunk by view + using std::ranges::chunk_by_view; + + namespace views { + using std::ranges::views::chunk_by; + } +#endif +#endif // _LIBCPP_STD_VER >= 23 + +#if 0 + // [range.stride], stride view + using std::ranges::stride_view; + + namespace views { + using std::ranges::views::stride; + } + + using std::ranges::cartesian_product_view; + + namespace views { + using std::ranges::views::cartesian_product; + } +#endif + } // namespace ranges + + namespace views = ranges::views; + + using std::tuple_element; + using std::tuple_size; + +#if _LIBCPP_STD_VER >= 23 + using std::from_range; + using std::from_range_t; +#endif // _LIBCPP_STD_VER >= 23 +} // namespace std + +// ratio.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [ratio.ratio], class template ratio + using std::ratio; + + // [ratio.arithmetic], ratio arithmetic + using std::ratio_add; + using std::ratio_divide; + using std::ratio_multiply; + using std::ratio_subtract; + + // [ratio.comparison], ratio comparison + using std::ratio_equal; + using std::ratio_greater; + using std::ratio_greater_equal; + using std::ratio_less; + using std::ratio_less_equal; + using std::ratio_not_equal; + + using std::ratio_equal_v; + using std::ratio_greater_equal_v; + using std::ratio_greater_v; + using std::ratio_less_equal_v; + using std::ratio_less_v; + using std::ratio_not_equal_v; + + // [ratio.si], convenience SI typedefs + using std::atto; + using std::centi; + using std::deca; + using std::deci; + using std::exa; + using std::femto; + using std::giga; + using std::hecto; + using std::kilo; + using std::mega; + using std::micro; + using std::milli; + using std::nano; + using std::peta; + using std::pico; + using std::tera; + + // These are not supported by libc++, due to the range of intmax_t + // using std::yocto; + // using std::yotta; + // using std::zepto; + // using std::zetta +} // namespace std + +// rcu.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if 0 +# if _LIBCPP_STD_VER >= 23 + // 2.2.3, class template rcu_obj_base using std::rcu_obj_base; + // 2.2.4, class rcu_domain + using std::rcu_domain; + using std::rcu_default_domain(); + using std::rcu_barrier; + using std::rcu_retire; + using std::rcu_synchronize; +# endif // _LIBCPP_STD_VER >= 23 +#endif +} // namespace std + +// regex.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + // [re.const], regex constants + namespace regex_constants { + using std::regex_constants::error_type; + using std::regex_constants::match_flag_type; + using std::regex_constants::syntax_option_type; + + // regex_constants is a bitmask type. + // [bitmask.types] specified operators + using std::regex_constants::operator&; + using std::regex_constants::operator&=; + using std::regex_constants::operator^; + using std::regex_constants::operator^=; + using std::regex_constants::operator|; + using std::regex_constants::operator|=; + using std::regex_constants::operator~; + + } // namespace regex_constants + + // [re.badexp], class regex_error + using std::regex_error; + + // [re.traits], class template regex_traits + using std::regex_traits; + + // [re.regex], class template basic_regex + using std::basic_regex; + + using std::regex; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wregex; +# endif + + // [re.regex.swap], basic_regex swap + using std::swap; + + // [re.submatch], class template sub_match + using std::sub_match; + + using std::csub_match; + using std::ssub_match; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wcsub_match; + using std::wssub_match; +# endif + + // [re.submatch.op], sub_match non-member operators + using std::operator==; + using std::operator<=>; + + using std::operator<<; + + // [re.results], class template match_results + using std::match_results; + + using std::cmatch; + using std::smatch; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wcmatch; + using std::wsmatch; +# endif + + // match_results comparisons + + // [re.results.swap], match_results swap + + // [re.alg.match], function template regex_match + using std::regex_match; + + // [re.alg.search], function template regex_search + using std::regex_search; + + // [re.alg.replace], function template regex_replace + using std::regex_replace; + + // [re.regiter], class template regex_iterator + using std::regex_iterator; + + using std::cregex_iterator; + using std::sregex_iterator; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wcregex_iterator; + using std::wsregex_iterator; +# endif + + // [re.tokiter], class template regex_token_iterator + using std::regex_token_iterator; + + using std::cregex_token_iterator; + using std::sregex_token_iterator; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wcregex_token_iterator; + using std::wsregex_token_iterator; +# endif + + namespace pmr { + using std::pmr::match_results; + + using std::pmr::cmatch; + using std::pmr::smatch; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::pmr::wcmatch; + using std::pmr::wsmatch; +# endif + } // namespace pmr +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// scoped_allocator.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // class template scoped_allocator_adaptor + using std::scoped_allocator_adaptor; + + // [scoped.adaptor.operators], scoped allocator operators + using std::operator==; + +} // namespace std + +// semaphore.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS + // [thread.sema.cnt], class template counting_semaphore + using std::counting_semaphore; + + using std::binary_semaphore; +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// set.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [set], class template set + using std::set; + + using std::operator==; + using std::operator<=>; + + using std::swap; + + // [set.erasure], erasure for set + using std::erase_if; + + // [multiset], class template multiset + using std::multiset; + + namespace pmr { + using std::pmr::multiset; + using std::pmr::set; + } // namespace pmr +} // namespace std + +// shared_mutex.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS + // [thread.sharedmutex.class], class shared_Âmutex + using std::shared_mutex; + // [thread.sharedtimedmutex.class], class shared_Âtimed_Âmutex + using std::shared_timed_mutex; + // [thread.lock.shared], class template shared_Âlock + using std::shared_lock; + using std::swap; +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// source_location.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::source_location; +} // namespace std + +// span.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // constants + using std::dynamic_extent; + + // [views.span], class template span + using std::span; + + namespace ranges { + using std::ranges::enable_borrowed_range; + using std::ranges::enable_view; + } // namespace ranges + + // [span.objectrep], views of object representation + using std::as_bytes; + + using std::as_writable_bytes; +} // namespace std + +// spanstream.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if 0 + using std::basic_spanbuf; + + using std::swap; + + using std::spanbuf; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wspanbuf; +# endif + + using std::basic_ispanstream; + + using std::ispanstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wispanstream; +# endif + + using std::basic_ospanstream; + + using std::ospanstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wospanstream; +# endif + + using std::basic_spanstream; + + using std::spanstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wspanstream; +# endif +#endif +} // namespace std + +// sstream.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::basic_stringbuf; + + using std::swap; + + using std::stringbuf; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wstringbuf; +# endif + + using std::basic_istringstream; + + using std::istringstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wistringstream; +# endif + + using std::basic_ostringstream; + + using std::ostringstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wostringstream; +# endif + + using std::basic_stringstream; + + using std::stringstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wstringstream; +# endif +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// stack.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [stack], class template stack + using std::stack; + + using std::operator==; + using std::operator!=; + using std::operator<; + using std::operator>; + using std::operator<=; + using std::operator>=; + using std::operator<=>; + + using std::swap; + using std::uses_allocator; +} // namespace std + +// stacktrace.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if 0 + // [stacktrace.entry], class stacktrace_Âentry + using std::stacktrace_entry; + + // [stacktrace.basic], class template basic_Âstacktrace + using std::basic_stacktrace; + + // basic_Âstacktrace typedef-names + using std::stacktrace; + + // [stacktrace.basic.nonmem], non-member functions + using std::swap; + + using std::to_string; + + using std::operator<<; + + namespace pmr { + using std::pmr::stacktrace; + } + + // [stacktrace.basic.hash], hash support + using std::hash; +#endif +} // namespace std + +// stdexcept.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::domain_error; + using std::invalid_argument; + using std::length_error; + using std::logic_error; + using std::out_of_range; + using std::overflow_error; + using std::range_error; + using std::runtime_error; + using std::underflow_error; +} // namespace std + +// stdfloat.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if defined(__STDCPP_FLOAT16_T__) + using std::float16_t; +#endif +#if defined(__STDCPP_FLOAT32_T__) + using std::float32_t; +#endif +#if defined(__STDCPP_FLOAT64_T__) + using std::float64_t; +#endif +#if defined(__STDCPP_FLOAT128_T__) + using std::float128_t; +#endif +#if defined(__STDCPP_BFLOAT16_T__) + using std::bfloat16_t; +#endif +} // namespace std + +// stop_token.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS +# ifdef _LIBCPP_ENABLE_EXPERIMENTAL + // [stoptoken], class stop_Âtoken + using std::stop_token; + + // [stopsource], class stop_Âsource + using std::stop_source; + + // no-shared-stop-state indicator + using std::nostopstate; + using std::nostopstate_t; + + // [stopcallback], class template stop_Âcallback + using std::stop_callback; +# endif // _LIBCPP_ENABLE_EXPERIMENTAL +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// streambuf.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::basic_streambuf; + using std::streambuf; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wstreambuf; +# endif +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// string.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [char.traits], character traits + using std::char_traits; + + // [basic.string], basic_string + using std::basic_string; + + using std::operator+; + using std::operator==; + using std::operator<=>; + + // [string.special], swap + using std::swap; + + // [string.io], inserters and extractors + using std::operator>>; + using std::operator<<; + using std::getline; + + // [string.erasure], erasure + using std::erase; + using std::erase_if; + + // basic_string typedef-names + using std::string; + using std::u16string; + using std::u32string; + using std::u8string; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wstring; +#endif + + // [string.conversions], numeric conversions + using std::stod; + using std::stof; + using std::stoi; + using std::stol; + using std::stold; + using std::stoll; + using std::stoul; + using std::stoull; + using std::to_string; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::to_wstring; +#endif + + namespace pmr { + using std::pmr::basic_string; + using std::pmr::string; + using std::pmr::u16string; + using std::pmr::u32string; + using std::pmr::u8string; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::pmr::wstring; +#endif + } // namespace pmr + + // [basic.string.hash], hash support + using std::hash; + + // TODO MODULES is this a bug? +#if _LIBCPP_STD_VER >= 23 + using std::operator""s; +#else + inline namespace literals { + inline namespace string_literals { + // [basic.string.literals], suffix for basic_string literals + using std::literals::string_literals::operator""s; + } // namespace string_literals + } // namespace literals +#endif +} // namespace std + +// string_view.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [string.view.template], class template basic_string_view + using std::basic_string_view; + + namespace ranges { + using std::ranges::enable_borrowed_range; + using std::ranges::enable_view; + } // namespace ranges + + // [string.view.comparison], non-member comparison functions + using std::operator==; + using std::operator<=>; + + // [string.view.io], inserters and extractors + using std::operator<<; + + // basic_string_view typedef-names + using std::string_view; + using std::u16string_view; + using std::u32string_view; + using std::u8string_view; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wstring_view; +#endif + + // [string.view.hash], hash support + using std::hash; + + inline namespace literals { + inline namespace string_view_literals { + // [string.view.literals], suffix for basic_string_view literals + using std::literals::string_view_literals::operator""sv; + } // namespace string_view_literals + } // namespace literals +} // namespace std + +// strstream.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::istrstream; + using std::ostrstream; + using std::strstream; + using std::strstreambuf; +#endif // _LIBCPP_HAS_NO_LOCALIZATION +} // namespace std + +// syncstream.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifdef _LIBCPP_HAS_YES_SYNCSTREAM + +export namespace std { +#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM) + using std::basic_syncbuf; + + // [syncstream.syncbuf.special], specialized algorithms + using std::swap; + + using std::syncbuf; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wsyncbuf; +# endif + using std::basic_osyncstream; + + using std::osyncstream; +# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wosyncstream; +# endif +#endif // !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM) +} // namespace std + +#endif + +// system_error.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::error_category; + using std::generic_category; + using std::system_category; + + using std::error_code; + using std::error_condition; + using std::system_error; + + using std::is_error_code_enum; + using std::is_error_condition_enum; + + using std::errc; + + // [syserr.errcode.nonmembers], non-member functions + using std::make_error_code; + + using std::operator<<; + + // [syserr.errcondition.nonmembers], non-member functions + using std::make_error_condition; + + // [syserr.compare], comparison operator functions + using std::operator==; + using std::operator<=>; + + // [syserr.hash], hash support + using std::hash; + + // [syserr], system error support + using std::is_error_code_enum_v; + using std::is_error_condition_enum_v; +} // namespace std + +// text_encoding.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#if 0 +# if _LIBCPP_STD_VER >= 23 + using std::text_encoding; + + // hash support + using std::hash; +# endif // _LIBCPP_STD_VER >= 23 +#endif +} // namespace std + +// thread.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { +#ifndef _LIBCPP_HAS_NO_THREADS + // [thread.thread.class], class thread + using std::thread; + + using std::swap; + + // [thread.jthread.class], class jthread +# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) + using std::jthread; +# endif + + // [thread.thread.this], namespace this_thread + namespace this_thread { + using std::this_thread::get_id; + + using std::this_thread::sleep_for; + using std::this_thread::sleep_until; + using std::this_thread::yield; + } // namespace this_thread + + // [thread.thread.id] + using std::operator==; + using std::operator<=>; +# ifndef _LIBCPP_HAS_NO_LOCALIZATION + using std::operator<<; +# endif // _LIBCPP_HAS_NO_LOCALIZATION + +# if _LIBCPP_STD_VER >= 23 + using std::formatter; +# endif + + using std::hash; +#endif // _LIBCPP_HAS_NO_THREADS +} // namespace std + +// tuple.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [tuple.tuple], class template tuple + using std::tuple; + + // [tuple.like], concept tuple-like + +#if _LIBCPP_STD_VER >= 23 + // [tuple.common.ref], common_reference related specializations + using std::basic_common_reference; + using std::common_type; +#endif + + // [tuple.creation], tuple creation functions + using std::ignore; + + using std::forward_as_tuple; + using std::make_tuple; + using std::tie; + using std::tuple_cat; + + // [tuple.apply], calling a function with a tuple of arguments + using std::apply; + + using std::make_from_tuple; + + // [tuple.helper], tuple helper classes + using std::tuple_element; + using std::tuple_size; + + // [tuple.elem], element access + using std::get; + using std::tuple_element_t; + + // [tuple.rel], relational operators + using std::operator==; + using std::operator<=>; + + // [tuple.traits], allocator-related traits + using std::uses_allocator; + + // [tuple.special], specialized algorithms + using std::swap; + + // [tuple.helper], tuple helper classes + using std::tuple_size_v; +} // namespace std + +// type_traits.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [meta.help], helper class + using std::integral_constant; + + using std::bool_constant; + using std::false_type; + using std::true_type; + + // [meta.unary.cat], primary type categories + using std::is_array; + using std::is_class; + using std::is_enum; + using std::is_floating_point; + using std::is_function; + using std::is_integral; + using std::is_lvalue_reference; + using std::is_member_function_pointer; + using std::is_member_object_pointer; + using std::is_null_pointer; + using std::is_pointer; + using std::is_rvalue_reference; + using std::is_union; + using std::is_void; + + // [meta.unary.comp], composite type categories + using std::is_arithmetic; + using std::is_compound; + using std::is_fundamental; + using std::is_member_pointer; + using std::is_object; + using std::is_reference; + using std::is_scalar; + + // [meta.unary.prop], type properties + using std::is_abstract; + using std::is_aggregate; + using std::is_const; + using std::is_empty; + using std::is_final; + using std::is_polymorphic; + using std::is_standard_layout; + using std::is_trivial; + using std::is_trivially_copyable; + using std::is_volatile; + + using std::is_bounded_array; +#if _LIBCPP_STD_VER >= 23 + using std::is_scoped_enum; +#endif + using std::is_signed; + using std::is_unbounded_array; + using std::is_unsigned; + + using std::is_constructible; + using std::is_copy_constructible; + using std::is_default_constructible; + using std::is_move_constructible; + + using std::is_assignable; + using std::is_copy_assignable; + using std::is_move_assignable; + + using std::is_swappable; + using std::is_swappable_with; + + using std::is_destructible; + + using std::is_trivially_constructible; + using std::is_trivially_copy_constructible; + using std::is_trivially_default_constructible; + using std::is_trivially_move_constructible; + + using std::is_trivially_assignable; + using std::is_trivially_copy_assignable; + using std::is_trivially_destructible; + using std::is_trivially_move_assignable; + + using std::is_nothrow_constructible; + using std::is_nothrow_copy_constructible; + using std::is_nothrow_default_constructible; + using std::is_nothrow_move_constructible; + + using std::is_nothrow_assignable; + using std::is_nothrow_copy_assignable; + using std::is_nothrow_move_assignable; + + using std::is_nothrow_swappable; + using std::is_nothrow_swappable_with; + + using std::is_nothrow_destructible; + + // using std::is_implicit_lifetime; + + using std::has_virtual_destructor; + + using std::has_unique_object_representations; + + // using std::reference_constructs_from_temporary; + // using std::reference_converts_from_temporary; + + // [meta.unary.prop.query], type property queries + using std::alignment_of; + using std::extent; + using std::rank; + + // [meta.rel], type relations + using std::is_base_of; + using std::is_convertible; + // using std::is_layout_compatible; + using std::is_nothrow_convertible; + // using std::is_pointer_interconvertible_base_of; + using std::is_same; + + using std::is_invocable; + using std::is_invocable_r; + + using std::is_nothrow_invocable; + using std::is_nothrow_invocable_r; + + // [meta.trans.cv], const-volatile modifications + using std::add_const; + using std::add_cv; + using std::add_volatile; + using std::remove_const; + using std::remove_cv; + using std::remove_volatile; + + using std::add_const_t; + using std::add_cv_t; + using std::add_volatile_t; + using std::remove_const_t; + using std::remove_cv_t; + using std::remove_volatile_t; + + // [meta.trans.ref], reference modifications + using std::add_lvalue_reference; + using std::add_rvalue_reference; + using std::remove_reference; + + using std::add_lvalue_reference_t; + using std::add_rvalue_reference_t; + using std::remove_reference_t; + + // [meta.trans.sign], sign modifications + using std::make_signed; + using std::make_unsigned; + + using std::make_signed_t; + using std::make_unsigned_t; + + // [meta.trans.arr], array modifications + using std::remove_all_extents; + using std::remove_extent; + + using std::remove_all_extents_t; + using std::remove_extent_t; + + // [meta.trans.ptr], pointer modifications + using std::add_pointer; + using std::remove_pointer; + + using std::add_pointer_t; + using std::remove_pointer_t; + + // [meta.trans.other], other transformations + using std::basic_common_reference; + using std::common_reference; + using std::common_type; + using std::conditional; + using std::decay; + using std::enable_if; + using std::invoke_result; + using std::remove_cvref; + using std::type_identity; + using std::underlying_type; + using std::unwrap_ref_decay; + using std::unwrap_reference; + + using std::common_reference_t; + using std::common_type_t; + using std::conditional_t; + using std::decay_t; + using std::enable_if_t; + using std::invoke_result_t; + using std::remove_cvref_t; + using std::type_identity_t; + using std::underlying_type_t; + using std::unwrap_ref_decay_t; + using std::unwrap_reference_t; + using std::void_t; + + // [meta.logical], logical operator traits + using std::conjunction; + using std::disjunction; + using std::negation; + + // [meta.unary.cat], primary type categories + using std::is_array_v; + using std::is_class_v; + using std::is_enum_v; + using std::is_floating_point_v; + using std::is_function_v; + using std::is_integral_v; + using std::is_lvalue_reference_v; + using std::is_member_function_pointer_v; + using std::is_member_object_pointer_v; + using std::is_null_pointer_v; + using std::is_pointer_v; + using std::is_rvalue_reference_v; + using std::is_union_v; + using std::is_void_v; + + // [meta.unary.comp], composite type categories + using std::is_arithmetic_v; + using std::is_compound_v; + using std::is_fundamental_v; + using std::is_member_pointer_v; + using std::is_object_v; + using std::is_reference_v; + using std::is_scalar_v; + + // [meta.unary.prop], type properties + using std::has_unique_object_representations_v; + using std::has_virtual_destructor_v; + using std::is_abstract_v; + using std::is_aggregate_v; + using std::is_assignable_v; + using std::is_bounded_array_v; + using std::is_const_v; + using std::is_constructible_v; + using std::is_copy_assignable_v; + using std::is_copy_constructible_v; + using std::is_default_constructible_v; + using std::is_destructible_v; + using std::is_empty_v; + using std::is_final_v; + // using std::is_implicit_lifetime_v; + using std::is_move_assignable_v; + using std::is_move_constructible_v; + using std::is_nothrow_assignable_v; + using std::is_nothrow_constructible_v; + using std::is_nothrow_copy_assignable_v; + using std::is_nothrow_copy_constructible_v; + using std::is_nothrow_default_constructible_v; + using std::is_nothrow_destructible_v; + using std::is_nothrow_move_assignable_v; + using std::is_nothrow_move_constructible_v; + using std::is_nothrow_swappable_v; + using std::is_nothrow_swappable_with_v; + using std::is_polymorphic_v; +#if _LIBCPP_STD_VER >= 23 + using std::is_scoped_enum_v; +#endif + using std::is_signed_v; + using std::is_standard_layout_v; + using std::is_swappable_v; + using std::is_swappable_with_v; + using std::is_trivial_v; + using std::is_trivially_assignable_v; + using std::is_trivially_constructible_v; + using std::is_trivially_copy_assignable_v; + using std::is_trivially_copy_constructible_v; + using std::is_trivially_copyable_v; + using std::is_trivially_default_constructible_v; + using std::is_trivially_destructible_v; + using std::is_trivially_move_assignable_v; + using std::is_trivially_move_constructible_v; + using std::is_unbounded_array_v; + using std::is_unsigned_v; + using std::is_volatile_v; + // using std::reference_constructs_from_temporary_v; + // using std::reference_converts_from_temporary_v; + + // [meta.unary.prop.query], type property queries + using std::alignment_of_v; + using std::extent_v; + using std::rank_v; + + // [meta.rel], type relations + using std::is_base_of_v; + using std::is_convertible_v; + using std::is_invocable_r_v; + using std::is_invocable_v; + // using std::is_layout_compatible_v; + using std::is_nothrow_convertible_v; + using std::is_nothrow_invocable_r_v; + using std::is_nothrow_invocable_v; + // using std::is_pointer_interconvertible_base_of_v; + using std::is_same_v; + + // [meta.logical], logical operator traits + using std::conjunction_v; + using std::disjunction_v; + using std::negation_v; + + // [meta.member], member relationships + // using std::is_corresponding_member; + // using std::is_pointer_interconvertible_with_class; + + // [meta.const.eval], constant evaluation context + using std::is_constant_evaluated; + + // [depr.meta.types] + using std::aligned_storage; + using std::aligned_storage_t; + using std::aligned_union; + using std::aligned_union_t; + using std::is_pod; + using std::is_pod_v; +} // namespace std + +// typeindex.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::hash; + using std::type_index; +} // namespace std + +// typeinfo.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::bad_cast; + using std::bad_typeid; + using std::type_info; +} // namespace std + +// unordered_map.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [unord.map], class template unordered_Âmap + using std::unordered_map; + + // [unord.multimap], class template unordered_Âmultimap + using std::unordered_multimap; + + using std::operator==; + + using std::swap; + + // [unord.map.erasure], erasure for unordered_Âmap + using std::erase_if; + + namespace pmr { + using std::pmr::unordered_map; + using std::pmr::unordered_multimap; + } // namespace pmr +} // namespace std + +// unordered_set.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [unord.set], class template unordered_Âset + using std::unordered_set; + + // [unord.multiset], class template unordered_Âmultiset + using std::unordered_multiset; + + using std::operator==; + + using std::swap; + + // [unord.set.erasure], erasure for unordered_Âset + using std::erase_if; + + namespace pmr { + using std::pmr::unordered_multiset; + using std::pmr::unordered_set; + } // namespace pmr +} // namespace std + +// utility.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [utility.swap], swap + using std::swap; + + // [utility.exchange], exchange + using std::exchange; + + // [forward], forward/move + using std::forward; +#if _LIBCPP_STD_VER >= 23 + using std::forward_like; +#endif + using std::move; + using std::move_if_noexcept; + + // [utility.as.const], as_const + using std::as_const; + + // [declval], declval + using std::declval; + + // [utility.intcmp], integer comparison functions + using std::cmp_equal; + using std::cmp_not_equal; + + using std::cmp_greater; + using std::cmp_greater_equal; + using std::cmp_less; + using std::cmp_less_equal; + + using std::in_range; + +#if _LIBCPP_STD_VER >= 23 + // [utility.underlying], to_underlying + using std::to_underlying; + + // [utility.unreachable], unreachable + using std::unreachable; +#endif // _LIBCPP_STD_VER >= 23 + + // [intseq], compile-time integer sequences + using std::index_sequence; + using std::integer_sequence; + + using std::make_index_sequence; + using std::make_integer_sequence; + + using std::index_sequence_for; + + // [pairs], class template pair + using std::pair; + +#if _LIBCPP_STD_VER >= 23 + using std::basic_common_reference; + using std::common_type; +#endif + // [pairs.spec], pair specialized algorithms + using std::operator==; + using std::operator<=>; + + using std::make_pair; + + // [pair.astuple], tuple-like access to pair + using std::tuple_element; + using std::tuple_size; + + using std::get; + + // [pair.piecewise], pair piecewise construction + using std::piecewise_construct; + using std::piecewise_construct_t; + + // in-place construction + using std::in_place; + using std::in_place_t; + + using std::in_place_type; + using std::in_place_type_t; + + using std::in_place_index; + using std::in_place_index_t; + + // [depr.relops] + namespace rel_ops { + using rel_ops::operator!=; + using rel_ops::operator>; + using rel_ops::operator<=; + using rel_ops::operator>=; + } // namespace rel_ops +} // namespace std + +// valarray.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + using std::gslice; + using std::gslice_array; + using std::indirect_array; + using std::mask_array; + using std::slice; + using std::slice_array; + using std::valarray; + + using std::swap; + + using std::operator*; + using std::operator/; + using std::operator%; + using std::operator+; + using std::operator-; + + using std::operator^; + using std::operator&; + using std::operator|; + + using std::operator<<; + using std::operator>>; + + using std::operator&&; + using std::operator||; + + using std::operator==; + using std::operator!=; + + using std::operator<; + using std::operator>; + using std::operator<=; + using std::operator>=; + + using std::abs; + using std::acos; + using std::asin; + using std::atan; + + using std::atan2; + + using std::cos; + using std::cosh; + using std::exp; + using std::log; + using std::log10; + + using std::pow; + + using std::sin; + using std::sinh; + using std::sqrt; + using std::tan; + using std::tanh; + + using std::begin; + using std::end; +} // namespace std + +// variant.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [variant.variant], class template variant + using std::variant; + + // [variant.helper], variant helper classes + using std::variant_alternative; + using std::variant_npos; + using std::variant_size; + using std::variant_size_v; + + // [variant.get], value access + using std::get; + using std::get_if; + using std::holds_alternative; + using std::variant_alternative_t; + + // [variant.relops], relational operators + using std::operator==; + using std::operator!=; + using std::operator<; + using std::operator>; + using std::operator<=; + using std::operator>=; + using std::operator<=>; + + // [variant.visit], visitation + using std::visit; + + // [variant.monostate], class monostate + using std::monostate; + + // [variant.specalg], specialized algorithms + using std::swap; + + // [variant.bad.access], class bad_variant_access + using std::bad_variant_access; + + // [variant.hash], hash support + using std::hash; +} // namespace std + +// vector.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // [vector], class template vector + using std::vector; + + using std::operator==; + using std::operator<=>; + + using std::swap; + + // [vector.erasure], erasure + using std::erase; + using std::erase_if; + + namespace pmr { + using std::pmr::vector; + } + + // hash support + using std::hash; + +#if _LIBCPP_STD_VER >= 23 + // [vector.bool.fmt], formatter specialization for vector<bool> + using std::formatter; +#endif +} // namespace std + +// version.inc +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +export namespace std { + // This module exports nothing. +} // namespace std diff --git a/libbuild2/cc/target.cxx b/libbuild2/cc/target.cxx index b17e1ef..6a518dd 100644 --- a/libbuild2/cc/target.cxx +++ b/libbuild2/cc/target.cxx @@ -21,11 +21,10 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; extern const char h_ext_def[] = "h"; - const target_type h::static_type { "h", @@ -36,11 +35,10 @@ namespace build2 &target_pattern_var<h_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; extern const char c_ext_def[] = "c"; - const target_type c::static_type { "c", @@ -51,11 +49,51 @@ namespace build2 &target_pattern_var<c_ext_def>, nullptr, &file_search, - false + target_type::flag::none }; - extern const char pc_ext[] = "pc"; // VC14 rejects constexpr. + extern const char m_ext_def[] = "m"; + const target_type m::static_type + { + "m", + &cc::static_type, + &target_factory<m>, + nullptr, /* fixed_extension */ + &target_extension_var<m_ext_def>, + &target_pattern_var<m_ext_def>, + nullptr, + &file_search, + target_type::flag::none + }; + extern const char S_ext_def[] = "S"; + const target_type S::static_type + { + "S", + &cc::static_type, + &target_factory<S>, + nullptr, /* fixed_extension */ + &target_extension_var<S_ext_def>, + &target_pattern_var<S_ext_def>, + nullptr, + &file_search, + target_type::flag::none + }; + + const target_type c_inc::static_type + { + "c_inc", + &cc::static_type, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + &target_search, + target_type::flag::none + }; + + extern const char pc_ext[] = "pc"; // VC14 rejects constexpr. const target_type pc::static_type { "pc", @@ -66,11 +104,10 @@ namespace build2 &target_pattern_fix<pc_ext>, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false + target_type::flag::none }; extern const char pca_ext[] = "static.pc"; // VC14 rejects constexpr. - const target_type pca::static_type { "pca", @@ -81,11 +118,10 @@ namespace build2 &target_pattern_fix<pca_ext>, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false + target_type::flag::none }; extern const char pcs_ext[] = "shared.pc"; // VC14 rejects constexpr. - const target_type pcs::static_type { "pcs", @@ -96,7 +132,7 @@ namespace build2 &target_pattern_fix<pcs_ext>, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false + target_type::flag::none }; } } diff --git a/libbuild2/cc/target.hxx b/libbuild2/cc/target.hxx index 7067421..01f2d6e 100644 --- a/libbuild2/cc/target.hxx +++ b/libbuild2/cc/target.hxx @@ -23,11 +23,14 @@ namespace build2 class LIBBUILD2_CC_SYMEXPORT cc: public file { public: - using file::file; + cc (context& c, dir_path d, dir_path o, string n) + : file (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const = 0; }; // There is hardly a c-family compilation without a C header inclusion. @@ -36,11 +39,14 @@ namespace build2 class LIBBUILD2_CC_SYMEXPORT h: public cc { public: - using cc::cc; + h (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; // This one we define in cc but the target type is only registered by the @@ -52,11 +58,65 @@ namespace build2 class LIBBUILD2_CC_SYMEXPORT c: public cc { public: - using cc::cc; + c (context& ctx, dir_path d, dir_path o, string n) + : cc (ctx, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } + + public: + static const target_type static_type; + }; + + // Objective-C source file (the same rationale for having it here as for + // c{} above). + // + class LIBBUILD2_CC_SYMEXPORT m: public cc + { + public: + m (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } + + public: + static const target_type static_type; + }; + + // Assembler with C preprocessor source file (the same rationale for + // having it here as for c{} above). + // + class LIBBUILD2_CC_SYMEXPORT S: public cc + { + public: + S (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } + + public: + static const target_type static_type; + }; + + // This is an abstract base target for deriving additional targets that + // can be #include'd in C translation units (the same rationale for having + // it here as for c{} above). In particular, only such targets will be + // considered to reverse-lookup extensions to target types (see + // dyndep_rule::map_extension() for background). + // + class LIBBUILD2_CC_SYMEXPORT c_inc: public cc + { + public: + c_inc (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; // pkg-config file targets. @@ -64,31 +124,40 @@ namespace build2 class LIBBUILD2_CC_SYMEXPORT pc: public file // .pc (common) { public: - using file::file; + pc (context& c, dir_path d, dir_path o, string n) + : file (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; class LIBBUILD2_CC_SYMEXPORT pca: public pc // .static.pc { public: - using pc::pc; + pca (context& c, dir_path d, dir_path o, string n) + : pc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; class LIBBUILD2_CC_SYMEXPORT pcs: public pc // .shared.pc { public: - using pc::pc; + pcs (context& c, dir_path d, dir_path o, string n) + : pc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; } } diff --git a/libbuild2/cc/types.cxx b/libbuild2/cc/types.cxx new file mode 100644 index 0000000..c6cfae9 --- /dev/null +++ b/libbuild2/cc/types.cxx @@ -0,0 +1,202 @@ +// file : libbuild2/cc/types.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include <libbuild2/cc/types.hxx> + +#include <libbuild2/cc/utility.hxx> + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cc + { + const string header_group_all ("all"); + const string header_group_all_importable ("all-importable"); + const string header_group_std ("std"); + const string header_group_std_importable ("std-importable"); + + // Find the position where the group should be inserted unless the group + // is already there. + // + using groups = importable_headers::groups; + + static inline optional<groups::const_iterator> + find_angle (const groups& gs, const string& g) + { + for (auto i (gs.begin ()); i != gs.end (); ++i) + { + // After last angle-bracket file. + // + if (i->front () != '<' || i->back () != '>' || path_pattern (*i)) + return i; + + if (*i == g) + return nullopt; + } + + return gs.begin (); + } + + static inline optional<groups::const_iterator> + find_angle_pattern (const groups& gs, const string& g) + { + for (auto i (gs.begin ()); i != gs.end (); ++i) + { + // After last angle-bracket file pattern. + // + if (i->front () != '<' || i->back () != '>') + return i; + + if (*i == g) + return nullopt; + } + + return gs.begin (); + } + + auto importable_headers:: + insert_angle (const dir_paths& sys_hdr_dirs, + const string& s) -> pair<const path, groups>* + { + assert (s.front () == '<' && s.back () == '>'); + + // First see if it has already been inserted. + // + auto i (group_map.find (s)); + if (i == group_map.end ()) + { + path f (s, 1, s.size () - 2); + + path p; // Reuse the buffer. + for (const dir_path& d: sys_hdr_dirs) + { + if (file_exists ((p = d, p /= f), + true /* follow_symlinks */, + true /* ignore_errors */)) + goto found; + } + + return nullptr; + + found: + normalize_header (p); + + // Note that it's possible this header has already been entered as + // part of a different group. + // + auto j (header_map.emplace (move (p), groups {}).first); + + if (auto p = find_angle (j->second, s)) + j->second.insert (*p, s); + + i = group_map.emplace (s, reinterpret_cast<uintptr_t> (&*j)).first; + } + + return reinterpret_cast<pair<const path, groups>*> (i->second); + } + + auto importable_headers:: + insert_angle (path p, const string& s) -> pair<const path, groups>& + { + assert (s.front () == '<' && s.back () == '>'); + + // First see if it has already been inserted. + // + auto i (group_map.find (s)); + if (i == group_map.end ()) + { + // Note that it's possible this header has already been entered as + // part of a different group. + // + auto j (header_map.emplace (move (p), groups {}).first); + + if (auto p = find_angle (j->second, s)) + j->second.insert (*p, s); + + i = group_map.emplace (s, reinterpret_cast<uintptr_t> (&*j)).first; + } + + return *reinterpret_cast<pair<const path, groups>*> (i->second); + } + + size_t importable_headers:: + insert_angle_pattern (const dir_paths& sys_hdr_dirs, const string& pat) + { + tracer trace ("importable_headers::insert_angle_pattern"); + + assert (pat.front () == '<' && pat.back () == '>' && path_pattern (pat)); + + // First see if it has already been inserted. + // + auto i (group_map.find (pat)); + if (i == group_map.end ()) + { + path f (pat, 1, pat.size () - 2); + + struct data + { + uintptr_t n; + const string& pat; + const dir_path* dir; + } d {0, pat, nullptr}; + + auto process = [&d, this] (path&& pe, const string&, bool interm) + { + if (interm) + return true; + + path p (*d.dir / pe); + normalize_header (p); + + string s (move (pe).string ()); + s.insert (0, 1, '<'); + s.push_back ('>'); + + // Note that it's possible this header has already been entered as + // part of a different group. + // + auto j (header_map.emplace (move (p), groups {}).first); + + if (auto p = find_angle (j->second, s)) + j->second.insert (*p, move (s)); + + if (auto p = find_angle_pattern (j->second, d.pat)) + j->second.insert (*p, d.pat); + + d.n++; + return true; + }; + + for (const dir_path& dir: sys_hdr_dirs) + { + d.dir = &dir; + + try + { + path_search ( + f, + process, + dir, + path_match_flags::follow_symlinks, + [&trace] (const dir_entry& de) + { + l5 ([&]{trace << "skipping inaccessible/dangling entry " + << de.base () / de.path ();}); + return true; + }); + } + catch (const system_error& e) + { + fail << "unable to scan " << dir << ": " << e; + } + } + + i = group_map.emplace (pat, d.n).first; + } + + return static_cast<size_t> (i->second); + } + } +} diff --git a/libbuild2/cc/types.hxx b/libbuild2/cc/types.hxx index 20b67c2..93f31bc 100644 --- a/libbuild2/cc/types.hxx +++ b/libbuild2/cc/types.hxx @@ -4,6 +4,8 @@ #ifndef LIBBUILD2_CC_TYPES_HXX #define LIBBUILD2_CC_TYPES_HXX +#include <unordered_map> + #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> @@ -81,6 +83,80 @@ namespace build2 build2::cc::module_info module_info; }; + // Ad hoc (as opposed to marked with x.importable) importable headers. + // + // Note that these are only searched for in the system header search + // directories (sys_hdr_dirs). + // + struct importable_headers + { + mutable shared_mutex mutex; + + using groups = small_vector<string, 3>; + + // Map of groups (e.g., std, <vector>, <boost/*.hpp>) that have already + // been inserted. + // + // For angle-bracket file groups (e.g., <vector>), the value is a + // pointer to the corresponding header_map element. For angle-bracket + // file pattern groups (e.g., <boost/**.hpp>), the value is the number + // of files in the group. + // + // Note that while the case-sensitivity of header names in #include + // directives is implementation-defined, our group names are case- + // sensitive (playing loose with the case will lead to portability + // issues sooner or later so we don't bother with any more elborate + // solutions). + // + std::unordered_map<string, uintptr_t> group_map; + + // Map of absolute and normalized header paths to groups (e.g., std, + // <vector>, <boost/**.hpp>) to which they belong. The groups are + // ordered from the most to least specific (e.g., <vector> then std). + // + std::unordered_map<path, groups> header_map; + + // Note that all these functions assume the instance is unique-locked. + // + + // Search for and insert an angle-bracket file, for example <vector>, + // making it belong to the angle-bracket file group itself. Return the + // pointer to the corresponding header_map element if the file has been + // found and NULL otherwise (so can be used as bool). + // + pair<const path, groups>* + insert_angle (const dir_paths& sys_hdr_dirs, const string& file); + + // As above but for a manually-searched absolute and normalized path. + // + pair<const path, groups>& + insert_angle (path, const string& file); + + // Search for and insert an angle-bracket file pattern, for example + // <boost/**.hpp>, making each header belong to the angle-bracket file + // group (e.g., <boost/any.hpp>) and the angle-bracket file pattern + // group itself. Return the number of files found that match the + // pattern. + // + size_t + insert_angle_pattern (const dir_paths& sys_hdr_dirs, const string& pat); + }; + + // Headers and header groups whose inclusion should or should not be + // translated to the corresponding header unit imports. + // + // The key is either an absolute and normalized header path or a reference + // to an importable_headers group (e.g., <vector>, std). + // + using translatable_headers = map<string, optional<bool>>; + + // Special translatable header groups. + // + extern const string header_group_all; + extern const string header_group_all_importable; + extern const string header_group_std; + extern const string header_group_std_importable; + // Compiler language. // enum class lang {c, cxx}; @@ -99,6 +175,10 @@ namespace build2 const target_type& bmi; const target_type& hbmi; }; + + // "Unhide" operator<< from the build2 namespace. + // + using build2::operator<<; } } diff --git a/libbuild2/cc/utility.cxx b/libbuild2/cc/utility.cxx index 283e1b4..e02f85a 100644 --- a/libbuild2/cc/utility.cxx +++ b/libbuild2/cc/utility.cxx @@ -3,10 +3,6 @@ #include <libbuild2/cc/utility.hxx> -#include <libbuild2/file.hxx> - -using namespace std; - namespace build2 { namespace cc diff --git a/libbuild2/cc/utility.hxx b/libbuild2/cc/utility.hxx index a856fd0..6ba4a20 100644 --- a/libbuild2/cc/utility.hxx +++ b/libbuild2/cc/utility.hxx @@ -9,6 +9,7 @@ #include <libbuild2/utility.hxx> #include <libbuild2/target.hxx> +#include <libbuild2/filesystem.hxx> #include <libbuild2/bin/target.hxx> #include <libbuild2/bin/utility.hxx> @@ -48,6 +49,14 @@ namespace build2 compile_target_types compile_types (otype); + + // Normalize an absolute path to an existing header. + // + inline void + normalize_header (path& f) + { + normalize_external (f, "header"); + } } } diff --git a/libbuild2/cc/windows-rpath.cxx b/libbuild2/cc/windows-rpath.cxx index eddb9c4..eb62ad1 100644 --- a/libbuild2/cc/windows-rpath.cxx +++ b/libbuild2/cc/windows-rpath.cxx @@ -45,6 +45,8 @@ namespace build2 // Return the greatest (newest) timestamp of all the DLLs that we will be // adding to the assembly or timestamp_nonexistent if there aren't any. // + // Note: called during the execute phase. + // timestamp link_rule:: windows_rpath_timestamp (const file& t, const scope& bs, @@ -53,31 +55,59 @@ namespace build2 { timestamp r (timestamp_nonexistent); + // Duplicate suppression similar to rpath_libraries(). + // + rpathed_libraries ls; + // We need to collect all the DLLs, so go into implementation of both // shared and static (in case they depend on shared). // - auto imp = [] (const file&, bool) {return true;}; - - auto lib = [&r] (const file* const* lc, - const string& f, - lflags, - bool sys) + auto imp = [] (const target&, bool) {return true;}; + + auto lib = [&r, &ls] ( + const target* const* lc, + const small_vector<reference_wrapper<const string>, 2>& ns, + lflags, + const string*, + bool sys) { - const file* l (lc != nullptr ? *lc : nullptr); + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); // We don't rpath system libraries. // if (sys) - return; + return false; - // Skip static libraries. - // if (l != nullptr) { - // This can be an "undiscovered" DLL (see search_library()). + // Suppress duplicates. // - if (!l->is_a<libs> () || l->path ().empty ()) // Also covers binless. - return; + if (find (ls.begin (), ls.end (), l) != ls.end ()) + return false; + + // Ignore static libraries. Note that this can be an "undiscovered" + // DLL (see search_library()). + // + if (l->is_a<libs> () && !l->path ().empty ()) // Also covers binless. + { + // Handle the case where the library is a member of a group (for + // example, people are trying to hack something up with pre-built + // libraries; see GH issue #366). + // + timestamp t; + if (l->group_state (action () /* inner */)) + { + t = l->group->is_a<mtime_target> ()->mtime (); + assert (t != timestamp_unknown); + } + else + t = l->load_mtime (); + + if (t > r) + r = t; + } + + ls.push_back (l); } else { @@ -91,25 +121,29 @@ namespace build2 // // Though this can happen on MinGW with direct DLL link... // - size_t p (path::traits_type::find_extension (f)); + for (const string& f: ns) + { + size_t p (path::traits_type::find_extension (f)); - if (p == string::npos || icasecmp (f.c_str () + p + 1, "dll") != 0) - return; - } + if (p != string::npos && icasecmp (f.c_str () + p + 1, "dll") == 0) + { + timestamp t (mtime (f.c_str ())); - // Ok, this is a DLL. - // - timestamp t (l != nullptr - ? l->load_mtime () - : mtime (f.c_str ())); + if (t > r) + r = t; + } + } + } - if (t > r) - r = t; + return true; }; + library_cache lib_cache; for (const prerequisite_target& pt: t.prerequisite_targets[a]) { - if (pt.adhoc || pt == nullptr) + // Note: during execute so check for ad hoc first to avoid data races. + // + if (pt.adhoc () || pt == nullptr) continue; bool la; @@ -120,7 +154,10 @@ namespace build2 ( f = pt->is_a<libs> ())) process_libraries (a, bs, li, sys_lib_dirs, *f, la, pt.data, - imp, lib, nullptr, true); + imp, lib, nullptr, + true /* self */, + false /* proc_opt_group */, + &lib_cache); } return r; @@ -135,76 +172,107 @@ namespace build2 action a, linfo li) const -> windows_dlls { + // Note that we cannot reuse windows_dlls for duplicate suppression + // since it would only keep track of shared libraries. + // windows_dlls r; + rpathed_libraries ls; - auto imp = [] (const file&, bool) {return true;}; - - auto lib = [&r, &bs] (const file* const* lc, - const string& f, - lflags, - bool sys) + struct + { + const scope& bs; + rpathed_libraries& ls; + } d {bs, ls}; + + auto imp = [] (const target&, bool) {return true;}; + + auto lib = [&d, &r] ( + const target* const* lc, + const small_vector<reference_wrapper<const string>, 2>& ns, + lflags, + const string*, + bool sys) { - const file* l (lc != nullptr ? *lc : nullptr); + const file* l (lc != nullptr ? &(*lc)->as<file> () : nullptr); if (sys) - return; + return false; if (l != nullptr) { + // Suppress duplicates. + // + if (find (d.ls.begin (), d.ls.end (), l) != d.ls.end ()) + return false; + if (l->is_a<libs> () && !l->path ().empty ()) // Also covers binless. { // Get .pdb if there is one. // - const target_type* tt (bs.find_target_type ("pdb")); + const target_type* tt (d.bs.find_target_type ("pdb")); const target* pdb (tt != nullptr ? find_adhoc_member (*l, *tt) : nullptr); - r.insert ( + + // Here we assume it's not a duplicate due to the check above. + // + r.push_back ( windows_dll { - f, - pdb != nullptr ? &pdb->as<file> ().path ().string () : nullptr, - string () + ns[0], + pdb != nullptr ? pdb->as<file> ().path ().string () : string (), }); } + + d.ls.push_back (l); } else { - size_t p (path::traits_type::find_extension (f)); - - if (p != string::npos && icasecmp (f.c_str () + p + 1, "dll") == 0) + string pdb; + for (const string& f: ns) { - // See if we can find a corresponding .pdb. - // - windows_dll wd {f, nullptr, string ()}; - string& pdb (wd.pdb_storage); + size_t p (path::traits_type::find_extension (f)); - // First try "our" naming: foo.dll.pdb. - // - pdb = f; - pdb += ".pdb"; - - if (!exists (path (pdb))) + if (p != string::npos && icasecmp (f.c_str () + p + 1, "dll") == 0) { - // Then try the usual naming: foo.pdb. - // - pdb.assign (f, 0, p); - pdb += ".pdb"; - - if (!exists (path (pdb))) - pdb.clear (); + if (find_if (r.begin (), r.end (), + [&f] (const windows_dll& e) + { + return e.dll.get () == f; + }) == r.end ()) + { + // See if we can find a corresponding .pdb. First try "our" + // naming: foo.dll.pdb. + // + pdb = f; + pdb += ".pdb"; + + if (!exists (path (pdb))) + { + // Then try the usual naming: foo.pdb. + // + pdb.assign (f, 0, p); + pdb += ".pdb"; + + if (!exists (path (pdb))) + pdb.clear (); + } + + r.push_back ( + windows_dll {f, pdb.empty () ? string () : move (pdb)}); + } } - - if (!pdb.empty ()) - wd.pdb = &pdb; - - r.insert (move (wd)); } } + + return true; }; + library_cache lib_cache; for (const prerequisite_target& pt: t.prerequisite_targets[a]) { - if (pt.adhoc || pt == nullptr) + // Note: during execute so check for ad hoc first to avoid data races. + // + if (pt.adhoc () || pt == nullptr) continue; bool la; @@ -215,7 +283,10 @@ namespace build2 ( f = pt->is_a<libs> ())) process_libraries (a, bs, li, sys_lib_dirs, *f, la, pt.data, - imp, lib, nullptr, true); + imp, lib, nullptr, + true /* self */, + false /* proc_opt_group */, + &lib_cache); } return r; @@ -311,11 +382,16 @@ namespace build2 // of the same amalgamation. This way if the amalgamation is moved // as a whole, the links will remain valid. // + // Note: mkanylink() is from libbutl and thus doesn't handle the + // dry-run mode. + // try { - switch (mkanylink (f, l, - true /* copy */, - f.sub (as.out_path ()) /* relative */)) + switch (as.ctx.dry_run + ? entry_type::symlink + : mkanylink (f, l, + true /* copy */, + f.sub (as.out_path ()) /* relative */)) { case entry_type::regular: print ("cp"); break; case entry_type::symlink: print ("ln -s"); break; @@ -343,16 +419,16 @@ namespace build2 //@@ Would be nice to avoid copying. Perhaps reuse buffers // by adding path::assign() and traits::leaf(). // - path dp (wd.dll); // DLL path. - path dn (dp.leaf ()); // DLL name. + path dp (wd.dll.get ()); // DLL path. + path dn (dp.leaf ()); // DLL name. link (dp, ad / dn); // Link .pdb if there is one. // - if (wd.pdb != nullptr) + if (!wd.pdb.empty ()) { - path pp (*wd.pdb); + path pp (wd.pdb); link (pp, ad / pp.leaf ()); } } |