aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/cc
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/cc')
-rw-r--r--libbuild2/cc/buildfile36
-rw-r--r--libbuild2/cc/common.cxx1464
-rw-r--r--libbuild2/cc/common.hxx235
-rw-r--r--libbuild2/cc/common.ixx27
-rw-r--r--libbuild2/cc/common.txx19
-rw-r--r--libbuild2/cc/compile-rule.cxx4449
-rw-r--r--libbuild2/cc/compile-rule.hxx102
-rw-r--r--libbuild2/cc/functions.cxx303
-rw-r--r--libbuild2/cc/gcc.cxx312
-rw-r--r--libbuild2/cc/guess.cxx758
-rw-r--r--libbuild2/cc/guess.hxx25
-rw-r--r--libbuild2/cc/init.cxx103
-rw-r--r--libbuild2/cc/install-rule.cxx560
-rw-r--r--libbuild2/cc/install-rule.hxx57
-rw-r--r--libbuild2/cc/lexer+comment.test.testscript5
-rw-r--r--libbuild2/cc/lexer+raw-string-literal.test.testscript2
-rw-r--r--libbuild2/cc/lexer.cxx34
-rw-r--r--libbuild2/cc/lexer.hxx34
-rw-r--r--libbuild2/cc/lexer.test.cxx7
-rw-r--r--libbuild2/cc/link-rule.cxx1912
-rw-r--r--libbuild2/cc/link-rule.hxx245
-rw-r--r--libbuild2/cc/module.cxx495
-rw-r--r--libbuild2/cc/module.hxx75
-rw-r--r--libbuild2/cc/msvc.cxx261
-rw-r--r--libbuild2/cc/parser+module.test.testscript10
-rw-r--r--libbuild2/cc/parser.cxx42
-rw-r--r--libbuild2/cc/parser.hxx19
-rw-r--r--libbuild2/cc/parser.test.cxx6
-rw-r--r--libbuild2/cc/pkgconfig-libpkg-config.cxx271
-rw-r--r--libbuild2/cc/pkgconfig-libpkgconf.cxx355
-rw-r--r--libbuild2/cc/pkgconfig.cxx2125
-rw-r--r--libbuild2/cc/pkgconfig.hxx129
-rw-r--r--libbuild2/cc/predefs-rule.cxx379
-rw-r--r--libbuild2/cc/predefs-rule.hxx45
-rw-r--r--libbuild2/cc/std.cppm6781
-rw-r--r--libbuild2/cc/target.cxx58
-rw-r--r--libbuild2/cc/target.hxx93
-rw-r--r--libbuild2/cc/types.cxx202
-rw-r--r--libbuild2/cc/types.hxx80
-rw-r--r--libbuild2/cc/utility.cxx4
-rw-r--r--libbuild2/cc/utility.hxx9
-rw-r--r--libbuild2/cc/windows-rpath.cxx222
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 = &in;}
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 = &in;}
+ 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, &lt] (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, &lt] (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 ());
}
}