aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/bin
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/bin')
-rw-r--r--libbuild2/bin/buildfile4
-rw-r--r--libbuild2/bin/def-rule.cxx850
-rw-r--r--libbuild2/bin/def-rule.hxx41
-rw-r--r--libbuild2/bin/guess.cxx238
-rw-r--r--libbuild2/bin/guess.hxx65
-rw-r--r--libbuild2/bin/init.cxx384
-rw-r--r--libbuild2/bin/init.hxx14
-rw-r--r--libbuild2/bin/rule.cxx149
-rw-r--r--libbuild2/bin/rule.hxx31
-rw-r--r--libbuild2/bin/target.cxx61
-rw-r--r--libbuild2/bin/target.hxx208
-rw-r--r--libbuild2/bin/utility.cxx12
12 files changed, 1848 insertions, 209 deletions
diff --git a/libbuild2/bin/buildfile b/libbuild2/bin/buildfile
index 0df78e6..f17fa8a 100644
--- a/libbuild2/bin/buildfile
+++ b/libbuild2/bin/buildfile
@@ -4,10 +4,10 @@
# NOTE: shared imports should go into root.build.
#
include ../
-imp_libs = ../lib{build2} # Implied interface dependency.
+impl_libs = ../lib{build2} # Implied interface dependency.
./: lib{build2-bin}: libul{build2-bin}: {hxx ixx txx cxx}{** -**.test...} \
- $imp_libs
+ $impl_libs
# Unit tests.
#
diff --git a/libbuild2/bin/def-rule.cxx b/libbuild2/bin/def-rule.cxx
new file mode 100644
index 0000000..143cc35
--- /dev/null
+++ b/libbuild2/bin/def-rule.cxx
@@ -0,0 +1,850 @@
+// file : libbuild2/bin/def-rule.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/bin/def-rule.hxx>
+
+#include <libbuild2/depdb.hxx>
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/algorithm.hxx>
+#include <libbuild2/filesystem.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/bin/target.hxx>
+#include <libbuild2/bin/utility.hxx>
+
+namespace build2
+{
+ namespace bin
+ {
+ // In C global uninitialized data becomes a "common symbol" (an equivalent
+ // definition compiled as C++ results in a BSS symbol) which allows some
+ // archaic merging of multiple such definitions during linking (see GNU ld
+ // --warn-common for background). Note that this merging may happen with
+ // other data symbol types, not just common.
+ //
+ struct symbols
+ {
+ set<string> d; // data
+ set<string> r; // read-only data
+ set<string> b; // uninitialized data (BSS)
+ set<string> c; // common uninitialized data
+ set<string> t; // text (code)
+ };
+
+ static void
+ read_dumpbin (diag_buffer& dbuf, ifdstream& is, symbols& syms)
+ {
+ // Note: io_error is handled by the caller.
+
+ // Lines that describe symbols look like:
+ //
+ // 0 1 2 3 4 5 6
+ // IDX OFFSET SECT SYMTYPE VISIBILITY SYMNAME
+ // ----------------------------------------------------------------------
+ // 02E 00000130 SECTA notype External | _standbyState
+ // 02F 00000009 SECT9 notype Static | _LocalRecoveryInProgress
+ // 064 00000020 SECTC notype () Static | _XLogCheckBuffer
+ // 065 00000000 UNDEF notype () External | _BufferGetTag
+ //
+ // IDX is the symbol index and OFFSET is its offset.
+ //
+ // SECT[ION] is the name of the section where the symbol is defined. If
+ // UNDEF, then it's a symbol to be resolved at link time from another
+ // object file.
+ //
+ // SYMTYPE is always notype for C/C++ symbols as there's no typeinfo and
+ // no way to get the symbol type from name (de)mangling. However, we
+ // care if "notype" is suffixed by "()" or not. The presence of () means
+ // the symbol is a function, the absence means it isn't.
+ //
+ // VISIBILITY indicates whether it's a compilation-unit local static
+ // symbol ("Static"), or whether it's available for use from other
+ // compilation units ("External"). Note that there are other values,
+ // such as "WeakExternal", and "Label".
+ //
+ // SYMNAME is the symbol name.
+ //
+ // The first symbol in each section appears to specify the section type,
+ // for example:
+ //
+ // 006 00000000 SECT3 notype Static | .rdata
+ // B44 00000000 SECT4 notype Static | .rdata$r
+ // AA2 00000000 SECT5 notype Static | .bss
+ //
+ // Note that an UNDEF data symbol with non-zero OFFSET is a "common
+ // symbol", equivalent to the nm `C` type.
+ //
+ // We keep a map of read-only (.rdata, .xdata) and uninitialized (.bss)
+ // sections to their types (R and B, respectively). If a section is not
+ // found in this map, then it's assumed to be normal data (.data).
+ //
+ auto parse_line = [&syms,
+ secs = map<string, char> ()] (const string& l) mutable
+ {
+ size_t b (0), e (0), n;
+
+ // IDX (note that it can be more than 3 characters).
+ //
+ if (next_word (l, b, e) == 0)
+ return;
+
+ // OFFSET (always 8 characters).
+ //
+ n = next_word (l, b, e);
+
+ if (n != 8)
+ return;
+
+ string off (l, b, n);
+
+ // SECT
+ //
+ n = next_word (l, b, e);
+
+ if (n == 0)
+ return;
+
+ string sec (l, b, n);
+
+ // TYPE
+ //
+ n = next_word (l, b, e);
+
+ if (l.compare (b, n, "notype") != 0)
+ return;
+
+ bool dat;
+ if (l[e] == ' ' && l[e + 1] == '(' && l[e + 2] == ')')
+ {
+ e += 3;
+ dat = false;
+ }
+ else
+ dat = true;
+
+ // VISIBILITY
+ //
+ n = next_word (l, b, e);
+
+ if (n == 0)
+ return;
+
+ string vis (l, b, n);
+
+ // |
+ //
+ n = next_word (l, b, e);
+
+ if (n != 1 || l[b] != '|')
+ return;
+
+ // SYMNAME
+ //
+ n = next_word (l, b, e);
+
+ if (n == 0)
+ return;
+
+ string s (l, b, n);
+
+ // See if this is the section type symbol.
+ //
+ if (dat &&
+ off == "00000000" &&
+ sec != "UNDEF" &&
+ vis == "Static" &&
+ s[0] == '.')
+ {
+ auto cmp = [&s] (const char* n, size_t l)
+ {
+ return s.compare (0, l, n) == 0 && (s[l] == '\0' || s[l] == '$');
+ };
+
+ if (cmp (".rdata", 6) ||
+ cmp (".xdata", 6)) secs.emplace (move (sec), 'R');
+ else if (cmp (".bss", 4)) secs.emplace (move (sec), 'B');
+
+ return;
+ }
+
+ // We can only export extern symbols.
+ //
+ if (vis != "External")
+ return;
+
+ if (dat)
+ {
+ if (sec != "UNDEF")
+ {
+ auto i (secs.find (sec));
+ switch (i == secs.end () ? 'D' : i->second)
+ {
+ case 'D': syms.d.insert (move (s)); break;
+ case 'R': syms.r.insert (move (s)); break;
+ case 'B': syms.b.insert (move (s)); break;
+ }
+ }
+ else
+ {
+ if (off != "00000000")
+ syms.c.insert (move (s));
+ }
+ }
+ else
+ {
+ if (sec != "UNDEF")
+ syms.t.insert (move (s));
+ }
+ };
+
+ // 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]);
+
+ for (string l; ist.fd != nullfd || dst.fd != nullfd; )
+ {
+ if (ist.fd != nullfd && getline_non_blocking (is, l))
+ {
+ if (eof (is))
+ ist.fd = nullfd;
+ else
+ {
+ parse_line (l);
+ l.clear ();
+ }
+
+ continue;
+ }
+
+ ifdselect (fds);
+
+ if (dst.ready)
+ {
+ if (!dbuf.read ())
+ dst.fd = nullfd;
+ }
+ }
+ }
+
+ static void
+ read_posix_nm (diag_buffer& dbuf, ifdstream& is, symbols& syms)
+ {
+ // Note: io_error is handled by the caller.
+
+ // Lines that describe symbols look like:
+ //
+ // <NAME> <TYPE> <VALUE> <SIZE>
+ //
+ // The types that we are interested in are T, D, R, and B.
+ //
+ auto parse_line = [&syms] (const string& l)
+ {
+ size_t b (0), e (0), n;
+
+ // NAME
+ //
+ n = next_word (l, b, e);
+
+ if (n == 0)
+ return;
+
+ string s (l, b, n);
+
+ // TYPE
+ //
+ n = next_word (l, b, e);
+
+ if (n != 1)
+ return;
+
+ switch (l[b])
+ {
+ case 'D': syms.d.insert (move (s)); break;
+ case 'R': syms.r.insert (move (s)); break;
+ case 'B': syms.b.insert (move (s)); break;
+ case 'c':
+ case 'C': syms.c.insert (move (s)); break;
+ case 'T': syms.t.insert (move (s)); break;
+ }
+ };
+
+ // 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]);
+
+ for (string l; ist.fd != nullfd || dst.fd != nullfd; )
+ {
+ if (ist.fd != nullfd && getline_non_blocking (is, l))
+ {
+ if (eof (is))
+ ist.fd = nullfd;
+ else
+ {
+ parse_line (l);
+ l.clear ();
+ }
+
+ continue;
+ }
+
+ ifdselect (fds);
+
+ if (dst.ready)
+ {
+ if (!dbuf.read ())
+ dst.fd = nullfd;
+ }
+ }
+ }
+
+ static void
+ write_win32_msvc (ostream& os, const symbols& syms, bool i386)
+ {
+ // Our goal here is to export the same types of symbols as what gets
+ // exported by MSVC with __declspec(dllexport) (can be viewed with
+ // dumpbin /EXPORTS).
+ //
+ // Some special C++ symbol patterns:
+ //
+ // Data symbols:
+ //
+ // ??_C* -- string literal (R, not exported)
+ // ??_7* -- vtable (R, exported)
+ // ??_R* -- rtti, can be prefixed with _CT/__CT (D/R, not exported)
+ //
+ // Text symbols:
+ //
+ // ??_G* -- scalar deleting destructor (not exported)
+ // ??_E* -- vector deleting destructor (not exported)
+ //
+ // The following two symbols seem to be related to exception
+ // throwing and most likely should not be exported.
+ //
+ // R _CTA3?AVinvalid_argument@std@@
+ // R _TI3?AVinvalid_argument@std@@
+ //
+ // There are also what appears to be floating point literals:
+ //
+ // R __real@3f80000
+ //
+ // For some reason i386 object files have extern "C" symbols (both
+ // data and text) prefixed with an underscore which must be stripped
+ // in the .def file.
+ //
+ // Note that the extra prefix seems to be also added to special
+ // symbols so something like _CT??... becomes __CT??... on i386.
+ // However, for such symbols the underscore shall not be removed.
+ // Which means an extern "C" _CT becomes __CT on i383 and hard to
+ // distinguish from the special symbols. We deal with this by only
+ // stripping the underscore if the symbols doesn't contain any
+ // special characters (?@).
+ //
+ auto extern_c = [] (const string& s)
+ {
+ return s.find_first_of ("?@") == string::npos;
+ };
+
+ auto strip = [i386, &extern_c] (const string& s) -> const char*
+ {
+ const char* r (s.c_str ());
+
+ if (i386 && s[0] == '_' && extern_c (s))
+ r++;
+
+ return r;
+ };
+
+ // Code.
+ //
+ for (const string& s: syms.t)
+ {
+ auto filter = [&strip] (const string& s) -> const char*
+ {
+ if (s.compare (0, 4, "??_G") == 0 ||
+ s.compare (0, 4, "??_E") == 0)
+ return nullptr;
+
+ return strip (s);
+ };
+
+ if (const char* v = filter (s))
+ os << " " << v << '\n';
+ }
+
+ // Data.
+ //
+ // Note that it's not easy to import data without a dllimport
+ // declaration.
+ //
+ {
+ auto filter = [&strip] (const string& s) -> const char*
+ {
+ if (s.compare (0, 4, "??_R") == 0 ||
+ s.compare (0, 4, "??_C") == 0)
+ return nullptr;
+
+ return strip (s);
+ };
+
+ for (const string& s: syms.d)
+ if (const char* v = filter (s))
+ os << " " << v << " DATA\n";
+
+ for (const string& s: syms.b)
+ if (const char* v = filter (s))
+ os << " " << v << " DATA\n";
+
+ // For common symbols, only write extern C.
+ //
+ for (const string& s: syms.c)
+ if (extern_c (s))
+ if (const char* v = filter (s))
+ os << " " << v << " DATA\n";
+
+ // Read-only data contains an especially large number of various
+ // special symbols. Instead of trying to filter them out case by case,
+ // we will try to recognize C/C++ identifiers plus the special symbols
+ // that we need to export (e.g., vtable).
+ //
+ // Note that it looks like rdata should not be declared DATA. It is
+ // known to break ??_7 (vtable) exporting (see GH issue 315).
+ //
+ for (const string& s: syms.r)
+ {
+ if (extern_c (s) || // C
+ (s[0] == '?' && s[1] != '?') || // C++
+ s.compare (0, 4, "??_7") == 0) // vtable
+ {
+ os << " " << strip (s) << '\n';
+ }
+ }
+ }
+ }
+
+ static void
+ write_mingw32 (ostream& os, const symbols& syms, bool i386)
+ {
+ // Our goal here is to export the same types of symbols as what gets
+ // exported by GCC with __declspec(dllexport) (can be viewed with
+ // dumpbin /EXPORTS).
+ //
+ // Some special C++ symbol patterns (Itanium C++ ABI):
+ //
+ // Data symbols:
+ //
+ // _ZTVN* -- vtable (R, exported)
+ // _ZTIN* -- typeinfo (R, exported)
+ // _ZTSN* -- typeinfo name (R, not exported)
+ //
+ // There are also some special R symbols which start with .refptr.
+ // that are not exported.
+ //
+ // Normal symbols (both text and data) appear to start with _ZN.
+ //
+ // Note that we have the same extra underscore for i386 as in the
+ // win32-msvc case above but here even for mangled symbols (e.g., __Z*).
+ //
+ auto skip = [i386] (const string& s) -> size_t
+ {
+ return i386 && s[0] == '_' ? 1 : 0;
+ };
+
+ // Code.
+ //
+ for (const string& s: syms.t)
+ {
+ auto filter = [&skip] (const string& s) -> const char*
+ {
+ return s.c_str () + skip (s);
+ };
+
+ if (const char* v = filter (s))
+ os << " " << v << '\n';
+ }
+
+ // Data.
+ //
+ {
+ auto filter = [&skip] (const string& s) -> const char*
+ {
+ return s.c_str () + skip (s);
+ };
+
+ for (const string& s: syms.d)
+ if (const char* v = filter (s))
+ os << " " << v << " DATA\n";
+
+ for (const string& s: syms.b)
+ if (const char* v = filter (s))
+ os << " " << v << " DATA\n";
+
+ for (const string& s: syms.c)
+ if (const char* v = filter (s))
+ os << " " << v << " DATA\n";
+
+ // Read-only data contains an especially large number of various
+ // special symbols. Instead of trying to filter them out case by case,
+ // we will try to recognize C/C++ identifiers plus the special symbols
+ // that we need to export (e.g., vtable and typeinfo).
+ //
+ // For the description of GNU binutils .def format, see:
+ //
+ // https://sourceware.org/binutils/docs/binutils/def-file-format.html
+ //
+ // @@ Maybe CONSTANT is more appropriate than DATA?
+ //
+ for (const string& s: syms.r)
+ {
+ if (s.find_first_of (".") != string::npos) // Special (.refptr.*)
+ continue;
+
+ size_t p (skip (s)), n (s.size () - p);
+
+ if ((n < 2 || s[p] != '_' || s[p + 1] != 'Z') || // C
+ (s[p + 2] == 'N' ) || // C++ (normal)
+ (s[p + 2] == 'T' && (s[p + 3] == 'V' || // vtable
+ s[p + 3] == 'I') && // typeinfo
+ s[p + 4] == 'N'))
+ {
+ os << " " << s.c_str () + p << " DATA\n";
+ }
+ }
+ }
+ }
+
+ bool def_rule::
+ match (action a, target& t) const
+ {
+ tracer trace ("bin::def_rule::match");
+
+ // See if we have an object file or a utility library.
+ //
+ for (prerequisite_member p: reverse_group_prerequisite_members (a, t))
+ {
+ // If excluded or ad hoc, then don't factor it into our tests.
+ //
+ if (include (a, t, p) != include_type::normal)
+ continue;
+
+ if (p.is_a<obj> () || p.is_a<objs> () ||
+ p.is_a<bmi> () || p.is_a<bmis> () ||
+ p.is_a<libul> () || p.is_a<libus> ())
+ return true;
+ }
+
+ l4 ([&]{trace << "no object or utility library prerequisite for target "
+ << t;});
+ return false;
+ }
+
+ recipe def_rule::
+ apply (action a, target& xt) const
+ {
+ def& t (xt.as<def> ());
+
+ t.derive_path ();
+
+ // Inject dependency on the output directory.
+ //
+ inject_fsdir (a, t);
+
+ // Match prerequisites only picking object files and utility libraries.
+ //
+ match_prerequisite_members (
+ a,
+ t,
+ [] (action a,
+ const target& t,
+ const prerequisite_member& p,
+ include_type i) -> prerequisite_target
+ {
+ return
+ i == include_type::adhoc ? nullptr :
+ //
+ // If this is a target group, then pick the appropriate member
+ // (the same semantics as what we have in link-rule).
+ //
+ p.is_a<obj> () ? &search (t, objs::static_type, p.key ()) :
+ p.is_a<bmi> () ? &search (t, bmis::static_type, p.key ()) :
+ p.is_a<libul> () ? link_member (p.search (t).as<libul> (),
+ a,
+ linfo {otype::s, lorder::s}) :
+ p.is_a<objs> () ||
+ p.is_a<bmis> () ||
+ p.is_a<libus> () ? &p.search (t) : nullptr;
+ });
+
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean_depdb; // Standard clean.
+ default: return noop_recipe; // Configure update.
+ }
+ }
+
+ target_state def_rule::
+ perform_update (action a, const target& xt)
+ {
+ tracer trace ("bin::def_rule::perform_update");
+
+ const def& t (xt.as<def> ());
+ const path& tp (t.path ());
+
+ context& ctx (t.ctx);
+
+ const scope& bs (t.base_scope ());
+ const scope& rs (*bs.root_scope ());
+
+ // For link.exe we use its /DUMP option to access dumpbin.exe. Otherwise
+ // (lld-link, MinGW), we use nm (llvm-nm, MinGW nm). For good measure
+ // (e.g., the bin.def module is loaded without bin.ld), we also handle
+ // the direct dumpbin.exe usage.
+ //
+ const string& lid (cast_empty<string> (rs["bin.ld.id"]));
+
+ // Update prerequisites and determine if anything changed.
+ //
+ timestamp mt (t.load_mtime ());
+ optional<target_state> ts (execute_prerequisites (a, t, mt));
+
+ bool update (!ts);
+
+ // We use depdb to track changes to the input set, etc.
+ //
+ 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 nm checksum.
+ //
+ if (dd.expect (lid == "msvc"
+ ? cast<string> (rs["bin.ld.checksum"])
+ : cast<string> (rs["bin.nm.checksum"])) != nullptr)
+ l4 ([&]{trace << "linker mismatch forcing update of " << t;});
+
+ // @@ TODO: track in depdb if making symbol filtering configurable.
+
+ // Collect and hash the list of object files seeing through libus{}.
+ //
+ vector<reference_wrapper<const objs>> os;
+ {
+ sha256 cs;
+
+ auto collect = [a, &rs, &os, &cs] (const file& t,
+ const auto& collect) -> void
+ {
+ for (const target* pt: t.prerequisite_targets[a])
+ {
+ if (pt == nullptr)
+ continue;
+
+ const objs* o;
+ if ((o = pt->is_a<objs> ()) != nullptr)
+ ;
+ else if (pt->is_a<hbmi> ())
+ o = find_adhoc_member<objs> (*pt);
+ //
+ // Note that in prerequisite targets we will have the libux{}
+ // members, not the group.
+ //
+ else if (const libus* l = pt->is_a<libus> ())
+ {
+ collect (*l, collect);
+ continue;
+ }
+ else
+ continue;
+
+ hash_path (cs, o->path (), rs.out_path ());
+ os.push_back (*o);
+ }
+ };
+
+ collect (t, collect);
+
+ if (dd.expect (cs.string ()) != nullptr)
+ l4 ([&]{trace << "file set mismatch forcing update of " << t;});
+ }
+
+ // Update if any mismatch or depdb is newer that the output.
+ //
+ if (dd.writing () || dd.mtime > mt)
+ update = true;
+
+ dd.close ();
+
+ // If nothing changed, then we are done.
+ //
+ if (!update)
+ return *ts;
+
+ const process_path& nm (lid == "msvc"
+ ? cast<process_path> (rs["bin.ld.path"])
+ : cast<process_path> (rs["bin.nm.path"]));
+
+ cstrings args {nm.recall_string ()};
+
+ string nid;
+ if (lid == "msvc")
+ {
+ args.push_back ("/DUMP"); // Must come first.
+ args.push_back ("/NOLOGO");
+ args.push_back ("/SYMBOLS");
+ }
+ else
+ {
+ nid = cast<string> (rs["bin.nm.id"]);
+
+ if (nid == "msvc")
+ {
+ args.push_back ("/NOLOGO");
+ args.push_back ("/SYMBOLS");
+ }
+ else
+ {
+ // Note that llvm-nm's --no-weak is only available since LLVM 7.
+ //
+ args.push_back ("--extern-only");
+ args.push_back ("--format=posix");
+ }
+ }
+
+ args.push_back (nullptr); // Argument placeholder.
+ args.push_back (nullptr);
+
+ const char*& arg (*(args.end () - 2));
+
+ // We could print the prerequisite if it's a single obj{}/libu{} (with
+ // the latter being the common case). But it doesn't feel like that's
+ // worth the variability and the associated possibility of confusion.
+ //
+ if (verb == 1)
+ print_diag ("def", t);
+
+ // Extract symbols from each object file.
+ //
+ symbols syms;
+ for (const objs& o: os)
+ {
+ // Use a relative path for nicer diagnostics.
+ //
+ path rp (relative (o.path ()));
+ arg = rp.string ().c_str ();
+
+ if (verb >= 2)
+ print_process (args);
+
+ if (ctx.dry_run)
+ continue;
+
+ // Both dumpbin.exe and nm send their output to stdout. While nm sends
+ // diagnostics to stderr, dumpbin sends it to stdout together with the
+ // output. To keep things uniform we will buffer stderr in both cases.
+ //
+ process pr (
+ run_start (nm,
+ args,
+ 0 /* stdin */,
+ -1 /* stdout */,
+ diag_buffer::pipe (ctx) /* stderr */));
+
+ // Note that while we read both streams until eof in the 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
+ // stdout hard. The latter should happen first so the order of the
+ // dbuf/is variables is important.
+ //
+ diag_buffer dbuf (ctx, args[0], pr, (fdstream_mode::non_blocking |
+ fdstream_mode::skip));
+
+ bool io (false);
+ try
+ {
+ ifdstream is (move (pr.in_ofd),
+ fdstream_mode::non_blocking,
+ ifdstream::badbit);
+
+ if (lid == "msvc" || nid == "msvc")
+ read_dumpbin (dbuf, is, syms);
+ else
+ read_posix_nm (dbuf, is, syms);
+
+ is.close ();
+ }
+ catch (const io_error&)
+ {
+ // Presumably the child process failed so let run_finish() deal with
+ // that first.
+ //
+ io = true;
+ }
+
+ if (!run_finish_code (dbuf, args, pr, 1 /* verbosity */) || io)
+ fail << "unable to extract symbols from " << arg;
+ }
+
+#if 0
+ for (const string& s: syms.d) text << "D " << s;
+ for (const string& s: syms.r) text << "R " << s;
+ for (const string& s: syms.b) text << "B " << s;
+ for (const string& s: syms.c) text << "C " << s;
+ for (const string& s: syms.t) text << "T " << s;
+#endif
+
+ if (verb >= 3)
+ text << "cat >" << tp;
+
+ if (!ctx.dry_run)
+ {
+ const auto& tgt (cast<target_triplet> (rs["bin.target"]));
+
+ bool i386 (tgt.cpu.size () == 4 &&
+ tgt.cpu[0] == 'i' && tgt.cpu[2] == '8' && tgt.cpu[3] == '6');
+
+ auto_rmfile rm (tp);
+ try
+ {
+ ofdstream os (tp);
+
+ os << "; Auto-generated, do not edit.\n"
+ << "EXPORTS\n";
+
+ if (tgt.system == "mingw32")
+ write_mingw32 (os, syms, i386);
+ else
+ write_win32_msvc (os, syms, i386);
+
+ os.close ();
+ rm.cancel ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write to " << tp << ": " << e;
+ }
+
+ dd.check_mtime (tp);
+ }
+
+ t.mtime (system_clock::now ());
+ return target_state::changed;
+ }
+
+ const string def_rule::rule_id_ {"bin.def 2"};
+ }
+}
diff --git a/libbuild2/bin/def-rule.hxx b/libbuild2/bin/def-rule.hxx
new file mode 100644
index 0000000..acdf841
--- /dev/null
+++ b/libbuild2/bin/def-rule.hxx
@@ -0,0 +1,41 @@
+// file : libbuild2/bin/def-rule.hxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#ifndef LIBBUILD2_BIN_DEF_RULE_HXX
+#define LIBBUILD2_BIN_DEF_RULE_HXX
+
+#include <libbuild2/types.hxx>
+#include <libbuild2/utility.hxx>
+
+#include <libbuild2/rule.hxx>
+
+#include <libbuild2/bin/export.hxx>
+
+namespace build2
+{
+ namespace bin
+ {
+ // Generate a .def file from one or more object files and/or utility
+ // libraries that exports all their symbols.
+ //
+ class LIBBUILD2_BIN_SYMEXPORT def_rule: public simple_rule
+ {
+ public:
+ def_rule () {}
+
+ virtual bool
+ match (action, target&) const override;
+
+ virtual recipe
+ apply (action, target&) const override;
+
+ static target_state
+ perform_update (action, const target&);
+
+ private:
+ static const string rule_id_;
+ };
+ }
+}
+
+#endif // LIBBUILD2_BIN_DEF_RULE_HXX
diff --git a/libbuild2/bin/guess.cxx b/libbuild2/bin/guess.cxx
index 9f15030..e9759b8 100644
--- a/libbuild2/bin/guess.cxx
+++ b/libbuild2/bin/guess.cxx
@@ -34,9 +34,12 @@ namespace build2
// Return 0-version if the version is invalid.
//
static inline semantic_version
- parse_version (const string& s, size_t p = 0, const char* bs = ".-+~ ")
+ parse_version (const string& s, size_t p = 0,
+ semantic_version::flags f = semantic_version::allow_omit_patch |
+ semantic_version::allow_build,
+ const char* bs = ".-+~ ")
{
- optional<semantic_version> v (parse_semantic_version (s, p, bs));
+ optional<semantic_version> v (parse_semantic_version (s, p, f, bs));
return v ? *v : semantic_version ();
}
@@ -89,7 +92,7 @@ namespace build2
static global_cache<ar_info> ar_cache;
const ar_info&
- guess_ar (const path& ar, const path* rl, const char* paths)
+ guess_ar (context& ctx, const path& ar, const path* rl, const char* paths)
{
tracer trace ("bin::guess_ar");
@@ -177,7 +180,11 @@ namespace build2
// "LLVM version 3.5.2"
// "LLVM version 5.0.0"
//
- if (l.compare (0, 13, "LLVM version ") == 0)
+ // But it can also be prefixed with some stuff, for example:
+ //
+ // "Debian LLVM version 14.0.6"
+ //
+ if (l.find ("LLVM version ") != string::npos)
{
semantic_version v (parse_version (l, l.rfind (' ') + 1));
return guess_result ("llvm", move (l), move (v));
@@ -227,7 +234,11 @@ namespace build2
// (yes, it goes to stdout) but that seems harmless.
//
sha256 cs;
- arr = run<guess_result> (3, are, "--version", f, false, false, &cs);
+ arr = run<guess_result> (ctx,
+ 3,
+ are, "--version",
+ f,
+ false , false, &cs);
if (!arr.empty ())
arr.checksum = cs.string ();
@@ -247,10 +258,10 @@ namespace build2
: guess_result ();
};
- // Redirect STDERR to STDOUT and ignore exit status.
+ // Redirect stderr to stdout and ignore exit status.
//
sha256 cs;
- arr = run<guess_result> (3, are, f, false, true, &cs);
+ arr = run<guess_result> (ctx, 3, are, f, false, true, &cs);
if (!arr.empty ())
{
@@ -280,7 +291,7 @@ namespace build2
// "LLVM version ".
//
- if (l.compare (0, 13, "LLVM version ") == 0)
+ if (l.find ("LLVM version ") != string::npos)
return guess_result ("llvm", move (l), semantic_version ());
// On FreeBSD we get "ranlib" rather than "BSD ranlib" for some
@@ -293,7 +304,11 @@ namespace build2
};
sha256 cs;
- rlr = run<guess_result> (3, rle, "--version", f, false, false, &cs);
+ rlr = run<guess_result> (ctx,
+ 3,
+ rle, "--version",
+ f,
+ false, false, &cs);
if (!rlr.empty ())
rlr.checksum = cs.string ();
@@ -310,10 +325,10 @@ namespace build2
: guess_result ();
};
- // Redirect STDERR to STDOUT and ignore exit status.
+ // Redirect stderr to stdout and ignore exit status.
//
sha256 cs;
- rlr = run<guess_result> (3, rle, f, false, true, &cs);
+ rlr = run<guess_result> (ctx, 3, rle, f, false, true, &cs);
if (!rlr.empty ())
{
@@ -326,6 +341,9 @@ namespace build2
fail << "unable to guess " << *rl << " signature";
}
+ // None of the ar/ranlib implementations we recognize seem to use
+ // environment variables (not even Microsoft lib.exe).
+ //
return ar_cache.insert (move (key),
ar_info {
move (arp),
@@ -333,25 +351,57 @@ namespace build2
move (arr.signature),
move (arr.checksum),
move (*arr.version),
+ nullptr,
move (rlp),
move (rlr.id),
move (rlr.signature),
- move (rlr.checksum)});
+ move (rlr.checksum),
+ nullptr});
}
+ // Linker environment variables (see also the cc module which duplicates
+ // some of these).
+ //
+ // Notes:
+ //
+ // - GNU linkers search in LD_LIBRARY_PATH in addition to LD_RUN_PATH but
+ // we assume the former is part of the built-in list. Interestingly,
+ // LLD does not search in either.
+ //
+ // - The LLD family of linkers have a bunch of undocumented, debugging-
+ // related variables (LLD_REPRODUCE, LLD_VERSION, LLD_IN_TEST) that we
+ // ignore.
+ //
+ // - ld64 uses a ton of environment variables (according to the source
+ // code) but none of them are documented in the man pages. So someone
+ // will need to figure out what's important (some of them are clearly
+ // for debugging of ld itself).
+ //
+ // See also the note on environment and caching below if adding any new
+ // variables.
+ //
+ static const char* gnu_ld_env[] = {
+ "LD_RUN_PATH", "GNUTARGET", "LDEMULATION", "COLLECT_NO_DEMANGLE", nullptr};
+
+ static const char* msvc_ld_env[] = {
+ "LIB", "LINK", "_LINK_", nullptr};
+
// Extracting ld information requires running it which can become
// expensive if done repeatedly. So we cache the result.
//
static global_cache<ld_info> ld_cache;
const ld_info&
- guess_ld (const path& ld, const char* paths)
+ guess_ld (context& ctx, const path& ld, const char* paths)
{
tracer trace ("bin::guess_ld");
// First check the cache.
//
+ // Note that none of the information that we cache can be affected by
+ // the environment.
+ //
string key;
{
sha256 cs;
@@ -402,17 +452,22 @@ namespace build2
string id;
optional<semantic_version> ver;
+ size_t p;
+
// Microsoft link.exe output starts with "Microsoft (R) ".
//
if (l.compare (0, 14, "Microsoft (R) ") == 0)
{
id = "msvc";
}
- // LLD prints a line in the form "LLD X.Y.Z ...".
+ // LLD prints a line in the form "LLD X.Y.Z ...". But it can also
+ // be prefixed with some stuff, for example:
+ //
+ // Debian LLD 14.0.6 (compatible with GNU linkers)
//
- else if (l.compare (0, 4, "LLD ") == 0)
+ else if ((p = l.find ("LLD ")) != string::npos)
{
- ver = parse_version (l, 4);
+ ver = parse_version (l, p + 4);
// The only way to distinguish between various LLD drivers is via
// their name. Handle potential prefixes (say a target) and
@@ -450,12 +505,12 @@ namespace build2
: guess_result (move (id), move (l), move (ver)));
};
- // Redirect STDERR to STDOUT and ignore exit status. Note that in case
+ // Redirect stderr to stdout and ignore exit status. Note that in case
// of link.exe we will hash the diagnostics (yes, it goes to stdout)
// but that seems harmless.
//
sha256 cs;
- r = run<guess_result> (3, env, "--version", f, false, true, &cs);
+ r = run<guess_result> (ctx, 3, env, "--version", f, false, true, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -486,7 +541,7 @@ namespace build2
};
sha256 cs;
- r = run<guess_result> (3, env, "-v", f, false, false, &cs);
+ r = run<guess_result> (ctx, 3, env, "-v", f, false, false, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -513,7 +568,7 @@ namespace build2
// option.
//
sha256 cs;
- r = run<guess_result> (3, env, "-version", f, false, false, &cs);
+ r = run<guess_result> (ctx, 3, env, "-version", f, false, false, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -522,27 +577,44 @@ namespace build2
if (r.empty ())
fail << "unable to guess " << ld << " signature";
+ const char* const* ld_env ((r.id == "gnu" ||
+ r.id == "gnu-gold") ? gnu_ld_env :
+ (r.id == "msvc" ||
+ r.id == "msvc-lld") ? msvc_ld_env :
+ nullptr);
+
return ld_cache.insert (move (key),
ld_info {
move (pp),
move (r.id),
move (r.signature),
move (r.checksum),
- move (r.version)});
+ move (r.version),
+ ld_env});
}
+ // Resource compiler environment variables.
+ //
+ // See also the note on environment and caching below if adding any new
+ // variables.
+ //
+ static const char* msvc_rc_env[] = {"INCLUDE", nullptr};
+
// Extracting rc information requires running it which can become
// expensive if done repeatedly. So we cache the result.
//
static global_cache<rc_info> rc_cache;
const rc_info&
- guess_rc (const path& rc, const char* paths)
+ guess_rc (context& ctx, const path& rc, const char* paths)
{
tracer trace ("bin::guess_rc");
// First check the cache.
//
+ // Note that none of the information that we cache can be affected by
+ // the environment.
+ //
string key;
{
sha256 cs;
@@ -590,7 +662,7 @@ namespace build2
// option.
//
sha256 cs;
- r = run<guess_result> (3, env, "--version", f, false, false, &cs);
+ r = run<guess_result> (ctx, 3, env, "--version", f, false, false, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -623,7 +695,7 @@ namespace build2
};
sha256 cs;
- r = run<guess_result> (3, env, "/?", f, false, false, &cs);
+ r = run<guess_result> (ctx, 3, env, "/?", f, false, false, &cs);
if (!r.empty ())
r.checksum = cs.string ();
@@ -632,12 +704,128 @@ namespace build2
if (r.empty ())
fail << "unable to guess " << rc << " signature";
+ const char* const* rc_env ((r.id == "msvc" ||
+ r.id == "msvc-llvm") ? msvc_rc_env :
+ nullptr);
+
return rc_cache.insert (move (key),
rc_info {
move (pp),
move (r.id),
move (r.signature),
- move (r.checksum)});
+ move (r.checksum),
+ rc_env});
+ }
+
+ // Extracting nm information requires running it which can become
+ // expensive if done repeatedly. So we cache the result.
+ //
+ static global_cache<nm_info> nm_cache;
+
+ const nm_info&
+ guess_nm (context& ctx, const path& nm, const char* paths)
+ {
+ tracer trace ("bin::guess_nm");
+
+ // First check the cache.
+ //
+ // Note that none of the information that we cache can be affected by
+ // the environment.
+ //
+ string key;
+ {
+ sha256 cs;
+ cs.append (nm.string ());
+ if (paths != nullptr) cs.append (paths);
+ key = cs.string ();
+
+ if (const nm_info* r = nm_cache.find (key))
+ return *r;
+ }
+
+ guess_result r;
+
+ process_path pp (search (nm, paths, "config.bin.nm"));
+
+ // We should probably assume the utility output language words can be
+ // translated and even rearranged. Thus pass LC_ALL=C.
+ //
+ process_env env (pp);
+
+ // For now let's assume that all the platforms other than Windows
+ // recognize LC_ALL.
+ //
+#ifndef _WIN32
+ const char* evars[] = {"LC_ALL=C", nullptr};
+ env.vars = evars;
+#endif
+
+ // Both GNU Binutils and LLVM nm recognize the --version option.
+ //
+ // Microsoft dumpbin.exe does not recogize --version but will still
+ // issue its standard banner (and even exit with zero status).
+ //
+ // FreeBSD uses nm from ELF Toolchain which recognizes --version.
+ //
+ // Mac OS X nm doesn't have an option to display version or help. If we
+ // run it without any arguments, then it looks for a.out. So there
+ // doesn't seem to be a way to detect it.
+ //
+ // Version extraction is a @@ TODO.
+ {
+ auto f = [] (string& l, bool) -> guess_result
+ {
+ // Binutils nm --version output first line starts with "GNU nm" but
+ // search for "GNU ", similar to other tools.
+ //
+ if (l.find ("GNU ") != string::npos)
+ return guess_result ("gnu", move (l), semantic_version ());
+
+ // LLVM nm --version output has a line that starts with
+ // "LLVM version" followed by a version.
+ //
+ // But let's assume it can be prefixed with some stuff like the rest
+ // of the LLVM tools (see above).
+ //
+ if (l.find ("LLVM version ") != string::npos)
+ return guess_result ("llvm", move (l), semantic_version ());
+
+ if (l.compare (0, 14, "Microsoft (R) ") == 0)
+ return guess_result ("msvc", move (l), semantic_version ());
+
+ // nm --version from ELF Toolchain prints:
+ //
+ // nm (elftoolchain r3477M)
+ //
+ if (l.find ("elftoolchain") != string::npos)
+ return guess_result ("elftoolchain", move (l), semantic_version ());
+
+ return guess_result ();
+ };
+
+ // Suppress all the errors because we may be trying an unsupported
+ // option.
+ //
+ sha256 cs;
+ r = run<guess_result> (ctx, 3, env, "--version", f, false, false, &cs);
+
+ if (!r.empty ())
+ r.checksum = cs.string ();
+ }
+
+ // Since there are some unrecognizable nm's (e.g., on Mac OS X), we will
+ // have to assume generic if we managed to find the executable.
+ //
+ if (r.empty ())
+ r = guess_result ("generic", "", semantic_version ());
+
+ return nm_cache.insert (move (key),
+ nm_info {
+ move (pp),
+ move (r.id),
+ move (r.signature),
+ move (r.checksum),
+ nullptr /* environment */});
}
}
}
diff --git a/libbuild2/bin/guess.hxx b/libbuild2/bin/guess.hxx
index 9a63fa1..7dc7b33 100644
--- a/libbuild2/bin/guess.hxx
+++ b/libbuild2/bin/guess.hxx
@@ -16,7 +16,7 @@ namespace build2
// Currently recognized ar/ranlib and their ids:
//
// gnu GNU binutils
- // llvm LLVM ar
+ // llvm LLVM llvm-ar
// bsd FreeBSD (and maybe other BSDs)
// msvc Microsoft's lib.exe
// msvc-llvm LLVM llvm-lib.exe
@@ -28,6 +28,12 @@ namespace build2
// a toolchain-specific manner (usually the output of --version/-V) and
// is not bulletproof.
//
+ // The environment is an optional list of environment variables that
+ // affect ar/ranlib result.
+ //
+ // Watch out for the environment not to affect any of the extracted
+ // information since we cache it.
+ //
struct ar_info
{
process_path ar_path;
@@ -35,18 +41,20 @@ namespace build2
string ar_signature;
string ar_checksum;
semantic_version ar_version;
+ const char* const* ar_environment;
process_path ranlib_path;
string ranlib_id;
string ranlib_signature;
string ranlib_checksum;
+ const char* const* ranlib_environment;
};
// The ranlib path can be NULL, in which case no ranlib guessing will be
// attemplated and the returned ranlib_* members will be left empty.
//
const ar_info&
- guess_ar (const path& ar, const path* ranlib, const char* paths);
+ guess_ar (context&, const path& ar, const path* ranlib, const char* paths);
// ld information.
//
@@ -72,6 +80,12 @@ namespace build2
// toolchain-specific manner (usually the output of --version/-version/-v)
// and is not bulletproof.
//
+ // The environment is an optional list of environment variables that
+ // affect the linker result.
+ //
+ // Watch out for the environment not to affect any of the extracted
+ // information since we cache it.
+ //
// Note that for now the version is extracted only for some linkers. Once
// it's done for all of them, we should drop optional.
//
@@ -81,12 +95,12 @@ namespace build2
string id;
string signature;
string checksum;
-
optional<semantic_version> version;
+ const char* const* environment;
};
const ld_info&
- guess_ld (const path& ld, const char* paths);
+ guess_ld (context&, const path& ld, const char* paths);
// rc information.
//
@@ -102,16 +116,57 @@ namespace build2
// toolchain-specific manner (usually the output of --version) and is not
// bulletproof.
//
+ // The environment is an optional list of environment variables that
+ // affect the resource compiler result.
+ //
+ // Watch out for the environment not to affect any of the extracted
+ // information since we cache it.
+ //
struct rc_info
{
process_path path;
string id;
string signature;
string checksum;
+ const char* const* environment;
};
const rc_info&
- guess_rc (const path& rc, const char* paths);
+ guess_rc (context&, const path& rc, const char* paths);
+
+ // nm information.
+ //
+ // Currently recognized nm and nm-like utilities and their ids:
+ //
+ // gnu GNU binutils nm
+ // msvc Microsoft's dumpbin.exe
+ // llvm LLVM llvm-nm
+ // elftoolchain ELF Toolchain (used by FreeBSD)
+ // generic Other/generic/unrecognized (including Mac OS X)
+ //
+ // The signature is normally the --version line.
+ //
+ // The checksum is used to detect nm changes. It is calculated in a
+ // toolchain-specific manner (usually the output of --version) and is not
+ // bulletproof.
+ //
+ // The environment is an optional list of environment variables that
+ // affect the resource compiler result.
+ //
+ // Watch out for the environment not to affect any of the extracted
+ // information since we cache it.
+ //
+ struct nm_info
+ {
+ process_path path;
+ string id;
+ string signature;
+ string checksum;
+ const char* const* environment;
+ };
+
+ const nm_info&
+ guess_nm (context&, const path& nm, const char* paths);
}
}
diff --git a/libbuild2/bin/init.cxx b/libbuild2/bin/init.cxx
index ff00e89..610082e 100644
--- a/libbuild2/bin/init.cxx
+++ b/libbuild2/bin/init.cxx
@@ -3,8 +3,6 @@
#include <libbuild2/bin/init.hxx>
-#include <map>
-
#include <libbuild2/scope.hxx>
#include <libbuild2/function.hxx>
#include <libbuild2/variable.hxx>
@@ -18,6 +16,7 @@
#include <libbuild2/install/utility.hxx>
#include <libbuild2/bin/rule.hxx>
+#include <libbuild2/bin/def-rule.hxx>
#include <libbuild2/bin/guess.hxx>
#include <libbuild2/bin/target.hxx>
#include <libbuild2/bin/utility.hxx>
@@ -32,6 +31,7 @@ namespace build2
static const obj_rule obj_;
static const libul_rule libul_;
static const lib_rule lib_;
+ static const def_rule def_;
// Default config.bin.*.lib values.
//
@@ -41,24 +41,30 @@ namespace build2
bool
vars_init (scope& rs,
- scope&,
- const location&,
- bool first,
+ scope& bs,
+ const location& loc,
+ bool,
bool,
module_init_extra&)
{
tracer trace ("bin::vars_init");
l5 ([&]{trace << "for " << rs;});
- assert (first);
+ // We only support root loading (which means there can only be one).
+ //
+ if (rs != bs)
+ fail (loc) << "bin.vars module must be loaded in project root";
// Enter variables.
//
+ // All the variables we enter are qualified so go straight for the
+ // public variable pool.
+ //
+ auto& vp (rs.var_pool (true /* public */));
+
// Target is a string and not target_triplet because it can be
// specified by the user.
//
- auto& vp (rs.var_pool ());
-
vp.insert<string> ("config.bin.target");
vp.insert<string> ("config.bin.pattern");
@@ -76,6 +82,9 @@ namespace build2
// example, addition of rpaths for prerequisite libraries (see the cc
// module for an example). Default is true.
//
+ // Note also that a rule may need to make rpath relative if
+ // install.relocatable is true.
+ //
vp.insert<dir_paths> ("config.bin.rpath");
vp.insert<bool> ("config.bin.rpath.auto");
@@ -104,12 +113,12 @@ namespace build2
// Link whole archive. Note: with target visibility.
//
// The lookup semantics is as follows: we first look for a prerequisite-
- // specific value, then for a target-specific value in the library being
- // linked, and then for target type/pattern-specific value starting from
- // the scope of the target being linked-to. In that final lookup we do
- // not look in the target being linked-to itself since that is used to
- // indicate how this target should be linked to other targets. For
- // example:
+ // specific value, then for a target-specific value in the prerequisite
+ // library, and then for target type/pattern-specific value starting
+ // from the scope of the target being linked. In that final lookup we do
+ // not look in the target being linked itself since that is used to
+ // indicate how this target should be used as a prerequisite of other
+ // targets. For example:
//
// exe{test}: liba{foo}
// liba{foo}: libua{foo1 foo2}
@@ -144,8 +153,70 @@ namespace build2
vp.insert<string> ("bin.lib.load_suffix");
vp.insert<string> ("bin.lib.load_suffix_pattern");
- vp.insert<map<string, string>> ("bin.lib.version");
- vp.insert<string> ("bin.lib.version_pattern");
+ vp.insert<map<optional<string>, string>> ("bin.lib.version");
+ vp.insert<string> ("bin.lib.version_pattern");
+
+ return true;
+ }
+
+ bool
+ types_init (scope& rs,
+ scope& bs,
+ const location& loc,
+ bool,
+ bool,
+ module_init_extra&)
+ {
+ tracer trace ("bin::types_init");
+ l5 ([&]{trace << "for " << rs;});
+
+ // We only support root loading (which means there can only be one).
+ //
+ if (rs != bs)
+ fail (loc) << "bin.types module must be loaded in project root";
+
+ // Register target types.
+ //
+ // Note that certain platform-specific and toolchain-specific types are
+ // registered in bin and bin.ld.
+ //
+ // Note also that it would make sense to configure their default
+ // "installability" here but that requires the knowledge of the platform
+ // in some cases. So we do it all in bin for now. One way to support
+ // both use-cases would be to detect if we are loaded after bin.guess
+ // and then decide whether to do it here or delay to bin.
+ //
+ // NOTE: remember to update the documentation if changing anything here!
+ //
+ rs.insert_target_type<obj> ();
+ rs.insert_target_type<obje> ();
+ rs.insert_target_type<obja> ();
+ rs.insert_target_type<objs> ();
+
+ rs.insert_target_type<bmi> ();
+ rs.insert_target_type<bmie> ();
+ rs.insert_target_type<bmia> ();
+ rs.insert_target_type<bmis> ();
+
+ rs.insert_target_type<hbmi> ();
+ rs.insert_target_type<hbmie> ();
+ rs.insert_target_type<hbmia> ();
+ rs.insert_target_type<hbmis> ();
+
+ rs.insert_target_type<libul> ();
+ rs.insert_target_type<libue> ();
+ rs.insert_target_type<libua> ();
+ rs.insert_target_type<libus> ();
+
+ rs.insert_target_type<lib> ();
+ rs.insert_target_type<liba> ();
+ rs.insert_target_type<libs> ();
+
+ // Register the def{} target type. Note that we do it here since it is
+ // input and can be specified unconditionally (i.e., not only when
+ // building for Windows).
+ //
+ rs.insert_target_type<def> ();
return true;
}
@@ -195,6 +266,8 @@ namespace build2
//
const target_triplet* tgt (nullptr);
{
+ // Note: go straight for the public variable pool.
+ //
const variable& var (ctx.var_pool["config.bin.target"]);
// We first see if the value was specified via the configuration
@@ -231,9 +304,9 @@ namespace build2
//
if (!hint && config_sub)
{
- s = run<string> (3,
- *config_sub,
- s.c_str (),
+ s = run<string> (ctx,
+ 3,
+ *config_sub, s.c_str (),
[] (string& l, bool) {return move (l);});
l5 ([&]{trace << "config.sub target: '" << s << "'";});
}
@@ -272,6 +345,8 @@ namespace build2
//
const string* pat (nullptr);
{
+ // Note: go straight for the public variable pool.
+ //
const variable& var (ctx.var_pool["config.bin.pattern"]);
// We first see if the value was specified via the configuration
@@ -440,53 +515,22 @@ namespace build2
tracer trace ("bin::init");
l5 ([&]{trace << "for " << bs;});
- // Load bin.config.
+ // Load bin.{config,types}.
//
load_module (rs, rs, "bin.config", loc, extra.hints);
+ load_module (rs, rs, "bin.types", loc);
// Cache some config values we will be needing below.
//
const target_triplet& tgt (cast<target_triplet> (rs["bin.target"]));
- // Register target types and configure their default "installability".
+ // Configure target type default "installability". Also register
+ // additional platform-specific types.
//
bool install_loaded (cast_false<bool> (rs["install.loaded"]));
{
using namespace install;
- if (first)
- {
- rs.insert_target_type<obj> ();
- rs.insert_target_type<obje> ();
- rs.insert_target_type<obja> ();
- rs.insert_target_type<objs> ();
-
- rs.insert_target_type<bmi> ();
- rs.insert_target_type<bmie> ();
- rs.insert_target_type<bmia> ();
- rs.insert_target_type<bmis> ();
-
- rs.insert_target_type<hbmi> ();
- rs.insert_target_type<hbmie> ();
- rs.insert_target_type<hbmia> ();
- rs.insert_target_type<hbmis> ();
-
- rs.insert_target_type<libul> ();
- rs.insert_target_type<libue> ();
- rs.insert_target_type<libua> ();
- rs.insert_target_type<libus> ();
-
- rs.insert_target_type<lib> ();
- rs.insert_target_type<liba> ();
- rs.insert_target_type<libs> ();
-
- // Register the def{} target type. Note that we do it here since it
- // is input and can be specified unconditionally (i.e., not only
- // when building for Windows).
- //
- rs.insert_target_type<def> ();
- }
-
// Note: libu*{} members are not installable.
//
if (install_loaded)
@@ -536,6 +580,8 @@ namespace build2
if (tgt.cpu == "wasm32" || tgt.cpu == "wasm64")
{
+ // @@ TODO: shouldn't this be wrapped in if(first) somehow?
+
const target_type& wasm (
rs.derive_target_type(
target_type {
@@ -546,8 +592,8 @@ namespace build2
nullptr, /* default_extension */
&target_pattern_fix<wasm_ext>,
&target_print_0_ext_verb, // Fixed extension, no use printing.
- &file_search,
- false /* see_through */}));
+ &target_search, // Note: don't look for an existing file.
+ target_type::flag::none}));
if (install_loaded)
{
@@ -578,8 +624,6 @@ namespace build2
// Similar to alias.
//
-
- //@@ outer
r.insert<lib> (perform_id, 0, "bin.lib", lib_);
r.insert<lib> (configure_id, 0, "bin.lib", lib_);
@@ -600,6 +644,18 @@ namespace build2
if (rs.find_module ("dist"))
{
+ // Note that without custom dist rules in setups along the follwing
+ // lines the source file will be unreachable by dist:
+ //
+ // lib{foo}: obj{foo}
+ // obja{foo}: cxx{foo}
+ // objs{foo}: cxx{foo}
+ //
+ r.insert<obj> (dist_id, 0, "bin.obj", obj_);
+ r.insert<bmi> (dist_id, 0, "bin.bmi", obj_);
+ r.insert<hbmi> (dist_id, 0, "bin.hbmi", obj_);
+ r.insert<libul> (dist_id, 0, "bin.libul", libul_);
+
r.insert<lib> (dist_id, 0, "bin.lib", lib_);
}
}
@@ -626,7 +682,10 @@ namespace build2
//
if (first)
{
- 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 */));
vp.insert<path> ("config.bin.ar");
vp.insert<path> ("config.bin.ranlib");
@@ -684,7 +743,7 @@ namespace build2
nullptr,
config::save_default_commented)));
- const ar_info& ari (guess_ar (ar, ranlib, pat.paths));
+ const ar_info& ari (guess_ar (rs.ctx, ar, ranlib, pat.paths));
// If this is a configuration with new values, then print the report
// at verbosity level 2 and up (-v).
@@ -723,8 +782,11 @@ namespace build2
}
}
- rs.assign<process_path_ex> ("bin.ar.path") =
- process_path_ex (ari.ar_path, "ar", ari.ar_checksum);
+ rs.assign<process_path_ex> ("bin.ar.path") = process_path_ex (
+ ari.ar_path,
+ "ar",
+ ari.ar_checksum,
+ hash_environment (ari.ar_environment));
rs.assign<string> ("bin.ar.id") = ari.ar_id;
rs.assign<string> ("bin.ar.signature") = ari.ar_signature;
rs.assign<string> ("bin.ar.checksum") = ari.ar_checksum;
@@ -739,13 +801,20 @@ namespace build2
rs.assign<string> ("bin.ar.version.build") = v.build;
}
+ config::save_environment (rs, ari.ar_environment);
+
if (ranlib != nullptr)
{
- rs.assign<process_path_ex> ("bin.ranlib.path") =
- process_path_ex (ari.ranlib_path, "ranlib", ari.ranlib_checksum);
+ rs.assign<process_path_ex> ("bin.ranlib.path") = process_path_ex (
+ ari.ranlib_path,
+ "ranlib",
+ ari.ranlib_checksum,
+ hash_environment (ari.ranlib_environment));
rs.assign<string> ("bin.ranlib.id") = ari.ranlib_id;
rs.assign<string> ("bin.ranlib.signature") = ari.ranlib_signature;
rs.assign<string> ("bin.ranlib.checksum") = ari.ranlib_checksum;
+
+ config::save_environment (rs, ari.ranlib_environment);
}
}
@@ -790,7 +859,10 @@ namespace build2
//
if (first)
{
- 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 */));
vp.insert<path> ("config.bin.ld");
}
@@ -822,7 +894,7 @@ namespace build2
path (apply_pattern (ld_d, pat.pattern)),
config::save_default_commented)));
- const ld_info& ldi (guess_ld (ld, pat.paths));
+ const ld_info& ldi (guess_ld (rs.ctx, ld, pat.paths));
// If this is a configuration with new values, then print the report
// at verbosity level 2 and up (-v).
@@ -854,8 +926,11 @@ namespace build2
<< " checksum " << ldi.checksum;
}
- rs.assign<process_path_ex> ("bin.ld.path") =
- process_path_ex (ldi.path, "ld", ldi.checksum);
+ rs.assign<process_path_ex> ("bin.ld.path") = process_path_ex (
+ ldi.path,
+ "ld",
+ ldi.checksum,
+ hash_environment (ldi.environment));
rs.assign<string> ("bin.ld.id") = ldi.id;
rs.assign<string> ("bin.ld.signature") = ldi.signature;
rs.assign<string> ("bin.ld.checksum") = ldi.checksum;
@@ -870,6 +945,8 @@ namespace build2
rs.assign<uint64_t> ("bin.ld.version.patch") = v.patch;
rs.assign<string> ("bin.ld.version.build") = v.build;
}
+
+ config::save_environment (rs, ldi.environment);
}
return true;
@@ -901,6 +978,8 @@ namespace build2
if (lid == "msvc")
{
+ // @@ TODO: shouldn't this be wrapped in if(first) somehow?
+
const target_type& pdb (
rs.derive_target_type(
target_type {
@@ -911,8 +990,8 @@ namespace build2
nullptr, /* default_extension */
&target_pattern_fix<pdb_ext>,
&target_print_0_ext_verb, // Fixed extension, no use printing.
- &file_search,
- false /* see_through */}));
+ &target_search, // Note: don't look for an existing file.
+ target_type::flag::none}));
if (cast_false<bool> (rs["install.loaded"]))
{
@@ -943,7 +1022,10 @@ namespace build2
//
if (first)
{
- 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 */));
vp.insert<path> ("config.bin.rc");
}
@@ -975,7 +1057,7 @@ namespace build2
path (apply_pattern (rc_d, pat.pattern)),
config::save_default_commented)));
- const rc_info& rci (guess_rc (rc, pat.paths));
+ const rc_info& rci (guess_rc (rs.ctx, rc, pat.paths));
// If this is a configuration with new values, then print the report
// at verbosity level 2 and up (-v).
@@ -989,11 +1071,16 @@ namespace build2
<< " checksum " << rci.checksum;
}
- rs.assign<process_path_ex> ("bin.rc.path") =
- process_path_ex (rci.path, "rc", rci.checksum);
+ rs.assign<process_path_ex> ("bin.rc.path") = process_path_ex (
+ rci.path,
+ "rc",
+ rci.checksum,
+ hash_environment (rci.environment));
rs.assign<string> ("bin.rc.id") = rci.id;
rs.assign<string> ("bin.rc.signature") = rci.signature;
rs.assign<string> ("bin.rc.checksum") = rci.checksum;
+
+ config::save_environment (rs, rci.environment);
}
return true;
@@ -1018,20 +1105,167 @@ namespace build2
return true;
}
+ bool
+ nm_config_init (scope& rs,
+ scope& bs,
+ const location& loc,
+ bool first,
+ bool,
+ module_init_extra& extra)
+ {
+ tracer trace ("bin::nm_config_init");
+ l5 ([&]{trace << "for " << bs;});
+
+ // Make sure bin.config is loaded.
+ //
+ load_module (rs, bs, "bin.config", loc, extra.hints);
+
+ // Enter configuration variables.
+ //
+ if (first)
+ {
+ // All the variables we enter are qualified so go straight for the
+ // public variable pool.
+ //
+ auto& vp (rs.var_pool (true /* public */));
+
+ vp.insert<path> ("config.bin.nm");
+ }
+
+ // Configuration.
+ //
+ if (first)
+ {
+ using config::lookup_config;
+
+ bool new_cfg (false); // Any new configuration values?
+
+ // config.bin.nm
+ //
+ // Use the target to decide on the default nm name. Note that in case
+ // of win32-msvc this is insufficient and we fallback to the linker
+ // type (if available) to decide between dumpbin and llvm-nm (with
+ // fallback to dumpbin).
+ //
+ // Finally note that the dumpbin.exe functionality is available via
+ // link.exe /DUMP.
+ //
+ const string& tsys (cast<string> (rs["bin.target.system"]));
+ const char* nm_d (tsys == "win32-msvc"
+ ? (cast_empty<string> (rs["bin.ld.id"]) == "msvc-lld"
+ ? "llvm-nm"
+ : "dumpbin")
+ : "nm");
+
+ // This can be either a pattern or search path(s).
+ //
+ pattern_paths pat (lookup_pattern (rs));
+
+ const path& nm (
+ cast<path> (
+ lookup_config (new_cfg,
+ rs,
+ "config.bin.nm",
+ path (apply_pattern (nm_d, pat.pattern)),
+ config::save_default_commented)));
+
+ const nm_info& nmi (guess_nm (rs.ctx, nm, pat.paths));
+
+ // If this is a configuration with new values, then print the report
+ // at verbosity level 2 and up (-v).
+ //
+ if (verb >= (new_cfg ? 2 : 3))
+ {
+ text << "bin.nm " << project (rs) << '@' << rs << '\n'
+ << " nm " << nmi.path << '\n'
+ << " id " << nmi.id << '\n'
+ << " signature " << nmi.signature << '\n'
+ << " checksum " << nmi.checksum;
+ }
+
+ rs.assign<process_path_ex> ("bin.nm.path") = process_path_ex (
+ nmi.path,
+ "nm",
+ nmi.checksum,
+ hash_environment (nmi.environment));
+ rs.assign<string> ("bin.nm.id") = nmi.id;
+ rs.assign<string> ("bin.nm.signature") = nmi.signature;
+ rs.assign<string> ("bin.nm.checksum") = nmi.checksum;
+
+ config::save_environment (rs, nmi.environment);
+ }
+
+ return true;
+ }
+
+ bool
+ nm_init (scope& rs,
+ scope& bs,
+ const location& loc,
+ bool,
+ bool,
+ module_init_extra& extra)
+ {
+ tracer trace ("bin::nm_init");
+ l5 ([&]{trace << "for " << bs;});
+
+ // Make sure the bin core and nm.config are loaded.
+ //
+ load_module (rs, bs, "bin", loc, extra.hints);
+ load_module (rs, bs, "bin.nm.config", loc, extra.hints);
+
+ return true;
+ }
+
+ bool
+ def_init (scope& rs,
+ scope& bs,
+ const location& loc,
+ bool,
+ bool,
+ module_init_extra& extra)
+ {
+ tracer trace ("bin::def_init");
+ l5 ([&]{trace << "for " << bs;});
+
+ // Make sure the bin core is loaded (def{} target type). We also load
+ // nm.config unless we are using MSVC link.exe and can access dumpbin
+ // via its /DUMP option.
+ //
+ const string* lid (cast_null<string> (rs["bin.ld.id"]));
+
+ load_module (rs, bs, "bin", loc, extra.hints);
+
+ if (lid == nullptr || *lid != "msvc")
+ load_module (rs, bs, "bin.nm.config", loc, extra.hints);
+
+ // Register the def{} rule.
+ //
+ bs.insert_rule<def> (perform_update_id, "bin.def", def_);
+ bs.insert_rule<def> (perform_clean_id, "bin.def", def_);
+ bs.insert_rule<def> (configure_update_id, "bin.def", def_);
+
+ return true;
+ }
+
static const module_functions mod_functions[] =
{
// NOTE: don't forget to also update the documentation in init.hxx if
// changing anything here.
{"bin.vars", nullptr, vars_init},
+ {"bin.types", nullptr, types_init},
{"bin.config", nullptr, config_init},
- {"bin", nullptr, init},
{"bin.ar.config", nullptr, ar_config_init},
{"bin.ar", nullptr, ar_init},
{"bin.ld.config", nullptr, ld_config_init},
{"bin.ld", nullptr, ld_init},
{"bin.rc.config", nullptr, rc_config_init},
{"bin.rc", nullptr, rc_init},
+ {"bin.nm.config", nullptr, nm_config_init},
+ {"bin.nm", nullptr, nm_init},
+ {"bin.def", nullptr, def_init},
+ {"bin", nullptr, init},
{nullptr, nullptr, nullptr}
};
diff --git a/libbuild2/bin/init.hxx b/libbuild2/bin/init.hxx
index 7bb9d1d..b163bf5 100644
--- a/libbuild2/bin/init.hxx
+++ b/libbuild2/bin/init.hxx
@@ -20,16 +20,26 @@ namespace build2
// Submodules:
//
// `bin.vars` -- registers some variables.
+ // `bin.types` -- registers target types.
// `bin.config` -- loads bin.vars and sets some variables.
+ // `bin` -- loads bin.{types,config} and registers rules and
+ // functions.
+ //
// `bin.ar.config` -- loads bin.config and registers/sets more variables.
// `bin.ar` -- loads bin and bin.ar.config.
+ //
// `bin.ld.config` -- loads bin.config and registers/sets more variables.
// `bin.ld` -- loads bin and bin.ld.config and registers more
// target types for msvc.
+ //
// `bin.rc.config` -- loads bin.config and registers/sets more variables.
// `bin.rc` -- loads bin and bin.rc.config.
- // `bin` -- loads bin.config and registers target types and
- // rules.
+ //
+ // `bin.nm.config` -- loads bin.config and registers/sets more variables.
+ // `bin.nm` -- loads bin and bin.nm.config.
+ //
+ // `bin.def` -- loads bin, bin.nm.config unless using MSVC link.exe,
+ // and registers the .def file generation rule.
//
extern "C" LIBBUILD2_BIN_SYMEXPORT const module_functions*
build2_bin_load ();
diff --git a/libbuild2/bin/rule.cxx b/libbuild2/bin/rule.cxx
index 021a768..c7147bf 100644
--- a/libbuild2/bin/rule.cxx
+++ b/libbuild2/bin/rule.cxx
@@ -17,12 +17,30 @@ namespace build2
{
namespace bin
{
+ // Search for an existing (declared real) member and match it if found.
+ //
+ static void
+ dist_match (action a, target& t, const target_type& tt)
+ {
+ if (const target* m = search_existing (t.ctx, tt, t.dir, t.out, t.name))
+ {
+ // Only a real target declaration can have prerequisites (which is
+ // the reason we are doing this).
+ //
+ if (m->decl == target_decl::real)
+ match_sync (a, *m);
+ }
+ }
+
// obj_rule
//
bool obj_rule::
- match (action a, target& t, const string&) const
+ match (action a, target& t) const
{
- const char* n (t.dynamic_type ().name); // Ignore derived type.
+ if (a.meta_operation () == dist_id)
+ return true;
+
+ const char* n (t.dynamic_type->name); // Ignore derived type.
fail << diag_doing (a, t) << " target group" <<
info << "explicitly select " << n << "e{}, " << n << "a{}, or "
@@ -30,27 +48,142 @@ namespace build2
}
recipe obj_rule::
- apply (action, target&) const {return empty_recipe;}
+ apply (action a, target& t) const
+ {
+ // We only get here for dist.
+ //
+ const target_type* ett (nullptr);
+ const target_type* att (nullptr);
+ const target_type* stt (nullptr);
+
+ if (t.is_a<obj> ())
+ {
+ ett = &obje::static_type;
+ att = &obja::static_type;
+ stt = &objs::static_type;
+ }
+ else if (t.is_a<bmi> ())
+ {
+ ett = &bmie::static_type;
+ att = &bmia::static_type;
+ stt = &bmis::static_type;
+ }
+ else if (t.is_a<hbmi> ())
+ {
+ ett = &hbmie::static_type;
+ att = &hbmia::static_type;
+ stt = &hbmis::static_type;
+ }
+ else
+ assert (false);
+
+ dist_match (a, t, *ett);
+ dist_match (a, t, *att);
+ dist_match (a, t, *stt);
+
+ // Delegate to the default dist rule to match prerequisites.
+ //
+ return dist::rule::apply (a, t);
+ }
// libul_rule
//
bool libul_rule::
- match (action a, target& t, const string&) const
+ match (action, target&) const
{
- fail << diag_doing (a, t) << " target group" <<
- info << "explicitly select libua{} or libus{} member" << endf;
+ return true;
}
recipe libul_rule::
- apply (action, target&) const {return empty_recipe;}
+ apply (action a, target& t) const
+ {
+ if (a.meta_operation () == dist_id)
+ {
+ dist_match (a, t, libua::static_type);
+ dist_match (a, t, libus::static_type);
+
+ // Delegate to the default dist rule to match prerequisites.
+ //
+ return dist::rule::apply (a, t);
+ }
+
+ // Pick one of the members. First looking for the one already matched.
+ //
+ const target* m (nullptr);
+
+ const libus* ls (nullptr);
+ {
+ ls = search_existing<libus> (t.ctx, t.dir, t.out, t.name);
+
+ if (ls != nullptr && ls->matched (a))
+ m = ls;
+ }
+
+ const libua* la (nullptr);
+ if (m == nullptr)
+ {
+ la = search_existing<libua> (t.ctx, t.dir, t.out, t.name);
+
+ if (la != nullptr && la->matched (a))
+ m = la;
+ }
+
+ if (m == nullptr)
+ {
+ const scope& bs (t.base_scope ());
+
+ lmembers lm (link_members (*bs.root_scope ()));
+
+ if (lm.s && lm.a)
+ {
+ // Use the bin.exe.lib order as a heuristics to pick the library
+ // (i.e., the most likely utility library to be built is the one
+ // most likely to be linked).
+ //
+ lorder lo (link_order (bs, otype::e));
+
+ (lo == lorder::s_a || lo == lorder::s ? lm.a : lm.s) = false;
+ }
+
+ if (lm.s)
+ m = ls != nullptr ? ls : &search<libus> (t, t.dir, t.out, t.name);
+ else
+ m = la != nullptr ? la : &search<libua> (t, t.dir, t.out, t.name);
+ }
+
+ // Save the member we picked in case others (e.g., $x.lib_poptions())
+ // need this information.
+ //
+ t.prerequisite_targets[a].push_back (m);
+
+ if (match_sync (a, *m, unmatch::safe).first)
+ return noop_recipe;
+
+ return [] (action a, const target& t)
+ {
+ const target* m (t.prerequisite_targets[a].back ());
+
+ // For update always return unchanged so we are consistent whether we
+ // managed to unmatch or now. Note that for clean we may get postponed
+ // so let's return the actual target state.
+ //
+ target_state r (execute_sync (a, *m));
+ return a == perform_update_id ? target_state::unchanged : r;
+ };
+ }
// lib_rule
//
// The whole logic is pretty much as if we had our two group members as
// our prerequisites.
//
+ // Note also that unlike the obj and libul rules above, we don't need to
+ // delegate to the default dist rule since any group prerequisites will be
+ // matched by one of the members (the key difference here is that unlike
+ // those rules, we insert and match members unconditionally).
+ //
bool lib_rule::
- match (action a, target& xt, const string&) const
+ match (action a, target& xt) const
{
lib& t (xt.as<lib> ());
diff --git a/libbuild2/bin/rule.hxx b/libbuild2/bin/rule.hxx
index ffb975d..9dd1d14 100644
--- a/libbuild2/bin/rule.hxx
+++ b/libbuild2/bin/rule.hxx
@@ -9,6 +9,8 @@
#include <libbuild2/rule.hxx>
+#include <libbuild2/dist/rule.hxx>
+
#include <libbuild2/bin/export.hxx>
namespace build2
@@ -18,28 +20,41 @@ namespace build2
// "Fail rule" for obj{} and [h]bmi{} that issues diagnostics if someone
// tries to build these groups directly.
//
- class obj_rule: public simple_rule
+ // Note that for dist it acts as a pass-through to all existing (declared)
+ // members.
+ //
+ class obj_rule: public dist::rule
{
public:
obj_rule () {}
virtual bool
- match (action, target&, const string&) const override;
+ match (action, target&) const override;
virtual recipe
apply (action, target&) const override;
};
- // "Fail rule" for libul{} that issues diagnostics if someone tries to
- // build this group directly.
+ // This rule picks, matches, and unmatches (if possible) a member for the
+ // purpose of making its metadata (for example, library's poptions, if
+ // it's one of the cc libraries) available.
+ //
+ // The underlying idea here is that someone else (e.g., cc::link_rule)
+ // makes a more informed choice and we piggy back on that decision,
+ // falling back to making our own based on bin.lib and bin.exe.lib. Note
+ // that for update this rule always returns target_state::unchanged.
//
- class libul_rule: public simple_rule
+ // Note also that for dist it acts as a pass-through to all existing
+ // (declared) members.
+ //
+ class libul_rule: public dist::rule
{
public:
+ explicit
libul_rule () {}
virtual bool
- match (action, target&, const string&) const override;
+ match (action, target&) const override;
virtual recipe
apply (action, target&) const override;
@@ -47,13 +62,15 @@ namespace build2
// Pass-through to group members rule, similar to alias.
//
+ // Note that for dist it always passes to both members.
+ //
class LIBBUILD2_BIN_SYMEXPORT lib_rule: public simple_rule
{
public:
lib_rule () {}
virtual bool
- match (action, target&, const string&) const override;
+ match (action, target&) const override;
virtual recipe
apply (action, target&) const override;
diff --git a/libbuild2/bin/target.cxx b/libbuild2/bin/target.cxx
index bf701c9..7e4875a 100644
--- a/libbuild2/bin/target.cxx
+++ b/libbuild2/bin/target.cxx
@@ -21,7 +21,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::none
};
const target_type bmix::static_type
@@ -34,7 +34,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::none
};
const target_type hbmix::static_type
@@ -47,7 +47,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::none
};
const target_type libx::static_type
@@ -60,7 +60,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::member_hint // Use untyped hint for group members.
};
const target_type libux::static_type
@@ -73,7 +73,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::none
};
// Note that we link groups during the load phase since this is often
@@ -108,7 +108,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type bmie::static_type
@@ -121,7 +121,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type hbmie::static_type
@@ -134,7 +134,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type obja::static_type
@@ -147,7 +147,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type bmia::static_type
@@ -160,7 +160,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type hbmia::static_type
@@ -173,7 +173,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type objs::static_type
@@ -186,7 +186,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type bmis::static_type
@@ -199,7 +199,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type hbmis::static_type
@@ -212,7 +212,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type libue::static_type
@@ -225,7 +225,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type libua::static_type
@@ -238,7 +238,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
const target_type libus::static_type
@@ -251,7 +251,7 @@ namespace build2
&target_pattern_var<nullptr>,
nullptr,
&target_search, // Note: not _file(); don't look for an existing file.
- false
+ target_type::flag::none
};
// obj{}, [h]bmi{}, and libu{} group factory.
@@ -292,7 +292,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::member_hint // Use untyped hint for group members.
};
const target_type bmi::static_type
@@ -305,7 +305,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::member_hint // Use untyped hint for group members.
};
const target_type hbmi::static_type
@@ -318,7 +318,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::member_hint // Use untyped hint for group members.
};
// The same as g_factory() but without E.
@@ -352,7 +352,7 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false
+ target_type::flag::member_hint // Use untyped hint for group members.
};
// What extensions should we use? At the outset, this is platform-
@@ -374,8 +374,8 @@ namespace build2
&target_extension_var<nullptr>,
&target_pattern_var<nullptr>,
nullptr,
- &file_search,
- false
+ &target_search, // Note: not _file(); don't look for an existing file.
+ target_type::flag::none
};
const target_type libs::static_type
@@ -387,8 +387,8 @@ namespace build2
&target_extension_var<nullptr>,
&target_pattern_var<nullptr>,
nullptr,
- &file_search,
- false
+ &target_search, // Note: not _file(); don't look for an existing file.
+ target_type::flag::none
};
// lib
@@ -435,7 +435,10 @@ namespace build2
nullptr,
nullptr,
&target_search,
- false // Note: not see-through ("alternatives" group).
+
+ // Note: not see-through ("alternatives" group).
+ //
+ target_type::flag::member_hint // Use untyped hint for group members.
};
// libi
@@ -449,8 +452,8 @@ namespace build2
&target_extension_var<nullptr>,
&target_pattern_var<nullptr>,
nullptr,
- &file_search,
- false
+ &target_search, // Note: not _file(); don't look for an existing file.
+ target_type::flag::none
};
// def
@@ -467,7 +470,7 @@ namespace build2
&target_pattern_fix<def_ext>,
nullptr,
&file_search,
- false
+ target_type::flag::none
};
}
}
diff --git a/libbuild2/bin/target.hxx b/libbuild2/bin/target.hxx
index f8d2dd0..8f2a92e 100644
--- a/libbuild2/bin/target.hxx
+++ b/libbuild2/bin/target.hxx
@@ -22,7 +22,11 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT objx: public file
{
public:
- using file::file;
+ objx (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;
@@ -31,41 +35,55 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT obje: public objx
{
public:
- using objx::objx;
+ obje (context& c, dir_path d, dir_path o, string n)
+ : objx (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_BIN_SYMEXPORT obja: public objx
{
public:
- using objx::objx;
+ obja (context& c, dir_path d, dir_path o, string n)
+ : objx (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_BIN_SYMEXPORT objs: public objx
{
public:
- using objx::objx;
+ objs (context& c, dir_path d, dir_path o, string n)
+ : objx (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;}
};
+ // Note: this is a "choice" target group.
+ //
class LIBBUILD2_BIN_SYMEXPORT obj: public target
{
public:
- using target::target;
+ obj (context& c, dir_path d, dir_path o, string n)
+ : target (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;}
};
// Binary module interface (BMI).
@@ -100,7 +118,11 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT bmix: public file
{
public:
- using file::file;
+ bmix (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;
@@ -111,7 +133,11 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT hbmix: public bmix
{
public:
- using bmix::bmix;
+ hbmix (context& c, dir_path d, dir_path o, string n)
+ : bmix (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
@@ -120,84 +146,111 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT bmie: public bmix
{
public:
- using bmix::bmix;
+ bmie (context& c, dir_path d, dir_path o, string n)
+ : bmix (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_BIN_SYMEXPORT hbmie: public hbmix
{
public:
- using hbmix::hbmix;
+ hbmie (context& c, dir_path d, dir_path o, string n)
+ : hbmix (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_BIN_SYMEXPORT bmia: public bmix
{
public:
- using bmix::bmix;
+ bmia (context& c, dir_path d, dir_path o, string n)
+ : bmix (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_BIN_SYMEXPORT hbmia: public hbmix
{
public:
- using hbmix::hbmix;
+ hbmia (context& c, dir_path d, dir_path o, string n)
+ : hbmix (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_BIN_SYMEXPORT bmis: public bmix
{
public:
- using bmix::bmix;
+ bmis (context& c, dir_path d, dir_path o, string n)
+ : bmix (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_BIN_SYMEXPORT hbmis: public hbmix
{
public:
- using hbmix::hbmix;
+ hbmis (context& c, dir_path d, dir_path o, string n)
+ : hbmix (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;}
};
+ // Note: this is a "choice" target group (similar to obj{}).
+ //
class LIBBUILD2_BIN_SYMEXPORT bmi: public target
{
public:
- using target::target;
+ bmi (context& c, dir_path d, dir_path o, string n)
+ : target (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;}
};
+ // Note: this is a "choice" target group (similar to bmi{} and obj{}).
+ //
class LIBBUILD2_BIN_SYMEXPORT hbmi: public target
{
public:
- using target::target;
+ hbmi (context& c, dir_path d, dir_path o, string n)
+ : target (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;}
};
-
// Common base for lib{} and libul{} groups.
//
// Use mtime_target as a base for the "trust me it exists" functionality
@@ -207,7 +260,11 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT libx: public mtime_target
{
public:
- using mtime_target::mtime_target;
+ libx (context& c, dir_path d, dir_path o, string n)
+ : mtime_target (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
public:
static const target_type static_type;
@@ -240,7 +297,11 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT libux: public file
{
public:
- using file::file;
+ libux (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;
@@ -249,41 +310,58 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT libue: public libux
{
public:
- using libux::libux;
+ libue (context& c, dir_path d, dir_path o, string n)
+ : libux (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_BIN_SYMEXPORT libua: public libux
{
public:
- using libux::libux;
+ libua (context& c, dir_path d, dir_path o, string n)
+ : libux (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_BIN_SYMEXPORT libus: public libux
{
public:
- using libux::libux;
+ libus (context& c, dir_path d, dir_path o, string n)
+ : libux (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;}
};
+ // Note: this is a "choice" target group.
+ //
+ // @@ Ideally this shouldn't derive from mtime_target (via libx). Maybe
+ // get rid of libx?
+ //
class LIBBUILD2_BIN_SYMEXPORT libul: public libx
{
public:
- using libx::libx;
+ libul (context& c, dir_path d, dir_path o, string n)
+ : libx (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;}
};
// The lib{} target group.
@@ -291,23 +369,27 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT liba: public file
{
public:
- using file::file;
+ liba (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_BIN_SYMEXPORT libs: public file
{
public:
- using file::file;
+ libs (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 override {return static_type;}
};
// Standard layout type compatible with group_view's const target*[2].
@@ -321,16 +403,32 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT lib: public libx, public lib_members
{
public:
- using libx::libx;
+ lib (context& c, dir_path d, dir_path o, string n)
+ : libx (c, move (d), move (o), move (n))
+ {
+ dynamic_type = &static_type;
+ }
virtual group_view
group_members (action) const override;
+ // Match options for the install operation on the liba{}/libs{} and
+ // libua{}/libus{} target types (note: not lib{}/libul{} nor libue{}).
+ //
+ // If only install_runtime option is specified, then only install the
+ // runtime files omitting everything buildtime (headers, pkg-config
+ // files, shared library version-related symlinks, etc).
+ //
+ // Note that it's either runtime-only or runtime and buildtime (i.e.,
+ // everything), so match with install_all instead of install_buildtime
+ // (the latter is only useful in the rule implementations).
+ //
+ static constexpr uint64_t option_install_runtime = 0x01;
+ static constexpr uint64_t option_install_buildtime = 0x02;
+ static constexpr uint64_t option_install_all = match_extra::all_options;
+
public:
static const target_type static_type;
-
- virtual const target_type&
- dynamic_type () const override {return static_type;}
};
// Windows import library.
@@ -338,11 +436,14 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT libi: public file
{
public:
- using file::file;
+ libi (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;}
};
// Windows module definition (.def).
@@ -350,11 +451,14 @@ namespace build2
class LIBBUILD2_BIN_SYMEXPORT def: public file
{
public:
- using file::file;
+ def (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;}
};
}
}
diff --git a/libbuild2/bin/utility.cxx b/libbuild2/bin/utility.cxx
index 11230cd..a03ea50 100644
--- a/libbuild2/bin/utility.cxx
+++ b/libbuild2/bin/utility.cxx
@@ -14,9 +14,6 @@ namespace build2
lorder
link_order (const scope& bs, otype ot)
{
- // Initialize to suppress 'may be used uninitialized' warning produced
- // by MinGW GCC 5.4.0.
- //
const char* var (nullptr);
switch (ot)
@@ -60,6 +57,11 @@ namespace build2
// prefer static over shared since it could be faster (but I am sure
// someone will probably want this configurable).
//
+ // Maybe we should use the bin.exe.lib order as a heuristics (i.e.,
+ // the most likely utility library to be built is the one most likely
+ // to be linked)? Will need the variables rs-only, similar to
+ // bin.lib, which probably is a good thing. See also libul_rule.
+ //
if (li.type == otype::e)
{
// Utility libraries are project-local which means the primarily
@@ -87,7 +89,9 @@ namespace build2
// Make sure group members are resolved.
//
group_view gv (resolve_members (a, l));
- assert (gv.members != nullptr);
+
+ if (gv.members == nullptr)
+ fail << "group " << l << " has no members";
pair<otype, bool> p (
link_member (lmembers {l.a != nullptr, l.s != nullptr}, li.order));