aboutsummaryrefslogtreecommitdiff
path: root/libbuild2
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2021-06-21 11:59:04 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2021-06-21 11:59:04 +0200
commit195de65a84864cf5931325eeb2356f7d98155055 (patch)
tree4ba33483d9032acb3de82547d5e9e0307a3c8353 /libbuild2
parentd822a0ae2c9fe312ad7a31e829ce2951be6a3ea0 (diff)
Add support for automatic generation of symbol exporting .def file
Diffstat (limited to 'libbuild2')
-rw-r--r--libbuild2/bin/def-rule.cxx610
-rw-r--r--libbuild2/bin/def-rule.hxx41
-rw-r--r--libbuild2/bin/guess.cxx94
-rw-r--r--libbuild2/bin/guess.hxx35
-rw-r--r--libbuild2/bin/init.cxx127
-rw-r--r--libbuild2/bin/init.hxx6
-rw-r--r--libbuild2/cc/link-rule.hxx4
-rw-r--r--libbuild2/cc/msvc.cxx11
-rw-r--r--libbuild2/config/operation.hxx4
-rw-r--r--libbuild2/test/script/parser.hxx3
-rw-r--r--libbuild2/test/script/script.hxx4
-rw-r--r--libbuild2/types.hxx3
-rw-r--r--libbuild2/variable.hxx3
13 files changed, 926 insertions, 19 deletions
diff --git a/libbuild2/bin/def-rule.cxx b/libbuild2/bin/def-rule.cxx
new file mode 100644
index 0000000..3805f3b
--- /dev/null
+++ b/libbuild2/bin/def-rule.cxx
@@ -0,0 +1,610 @@
+// 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/diagnostics.hxx>
+
+#include <libbuild2/bin/target.hxx>
+#include <libbuild2/bin/utility.hxx>
+
+namespace build2
+{
+ namespace bin
+ {
+ struct symbols
+ {
+ set<string> d; // data
+ set<string> r; // read-only data
+ set<string> b; // uninitialized data (BSS)
+ set<string> t; // text (code)
+ };
+
+ static void
+ parse_dumpbin (istream& is, symbols& syms)
+ {
+ // 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
+ //
+
+ // 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).
+ //
+ map<string, char> sections;
+
+ string l;
+ while (!eof (getline (is, l)))
+ {
+ size_t b (0), e (0), n;
+
+ // IDX (note that it can be more than 3 characters).
+ //
+ if (next_word (l, b, e) == 0)
+ continue;
+
+ // OFFSET (always 8 characters).
+ //
+ n = next_word (l, b, e);
+
+ if (n != 8)
+ continue;
+
+ string off (l, b, n);
+
+ // SECT
+ //
+ n = next_word (l, b, e);
+
+ if (n == 0 || l.compare (b, n, "UNDEF") == 0)
+ continue;
+
+ string sec (l, b, n);
+
+ // TYPE
+ //
+ n = next_word (l, b, e);
+
+ if (l.compare (b, n, "notype") != 0)
+ continue;
+
+ bool d;
+ if (l[e] == ' ' && l[e + 1] == '(' && l[e + 2] == ')')
+ {
+ e += 3;
+ d = false;
+ }
+ else
+ d = true;
+
+ // VISIBILITY
+ //
+ n = next_word (l, b, e);
+
+ if (n == 0)
+ continue;
+
+ string vis (l, b, n);
+
+ // |
+ //
+ n = next_word (l, b, e);
+
+ if (n != 1 || l[b] != '|')
+ continue;
+
+ // SYMNAME
+ //
+ n = next_word (l, b, e);
+
+ if (n == 0)
+ continue;
+
+ string s (l, b, n);
+
+ // See if this is the section type symbol.
+ //
+ if (d && off == "00000000" && 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)) sections.emplace (move (sec), 'R');
+ else if (cmp (".bss", 4)) sections.emplace (move (sec), 'B');
+
+ continue;
+ }
+
+ // We can only export extern symbols.
+ //
+ if (vis != "External")
+ continue;
+
+ if (d)
+ {
+ auto i (sections.find (sec));
+ switch (i == sections.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
+ syms.t.insert (move (s));
+ }
+ }
+
+ static void
+ parse_llvm_nm (istream& is, symbols& syms)
+ {
+ // Lines that describe symbols look like:
+ //
+ // <NAME> <TYPE> <VALUE> <SIZE>
+ //
+ // The types that we are interested in are T, D, R, and B.
+ //
+ string l;
+ while (!eof (getline (is, l)))
+ {
+ size_t b (0), e (0), n;
+
+ // NAME
+ //
+ n = next_word (l, b, e);
+
+ if (n == 0)
+ continue;
+
+ string s (l, b, n);
+
+ // TYPE
+ //
+ n = next_word (l, b, e);
+
+ if (n != 1)
+ continue;
+
+ 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 'T': syms.t.insert (move (s)); break;
+ }
+ }
+ }
+
+ bool def_rule::
+ match (action a, target& t, const string&) 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 dunpbin.exe. For
+ // lld-link we use llvm-nm.
+ //
+ const string& lid (cast<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-lld"
+ ? cast<string> (rs["bin.nm.checksum"])
+ : cast<string> (rs["bin.ld.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-lld"
+ ? cast<process_path> (rs["bin.nm.path"])
+ : cast<process_path> (rs["bin.ld.path"]));
+
+ const string& cpu (cast<string> (rs["bin.target.cpu"]));
+ bool i386 (cpu.size () == 4 &&
+ cpu[0] == 'i' && cpu[2] == '8' && cpu[3] == '6');
+
+ cstrings args {nm.recall_string ()};
+
+ if (lid == "msvc-lld")
+ {
+ args.push_back ("--no-weak");
+ args.push_back ("--defined-only");
+ args.push_back ("--format=posix");
+ }
+ else
+ {
+ args.push_back ("/DUMP"); // Must come first.
+ args.push_back ("/NOLOGO");
+ args.push_back ("/SYMBOLS");
+ }
+
+ args.push_back (nullptr); // Argument placeholder.
+ args.push_back (nullptr);
+
+ const char*& arg (*(args.end () - 2));
+
+ if (verb == 1)
+ text << "gen " << 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 link.exe /DUMP and llvm-nm send their output to stdout. While
+ // llvm-nm sends diagnostics to stderr, link.exe sends it to stdout
+ // together with the output.
+ //
+ process pr (run_start (nm,
+ args,
+ 0 /* stdin */,
+ -1 /* stdout */));
+ bool io (false);
+ try
+ {
+ ifdstream is (
+ move (pr.in_ofd), fdstream_mode::skip, ifdstream::badbit);
+
+ if (lid == "msvc-lld")
+ parse_llvm_nm (is, syms);
+ else
+ parse_dumpbin (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 (args.data (), pr) || io)
+ fail << "unable to extract symbols from " << arg;
+ }
+
+ /*
+ 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.t) text << "T " << s;
+ */
+
+ if (verb >= 3)
+ text << "cat >" << tp;
+
+ if (!ctx.dry_run)
+ {
+ auto_rmfile rm (tp);
+
+ try
+ {
+ ofdstream os (tp);
+
+ os << "; Auto-generated, do not edit.\n"
+ << "EXPORTS\n";
+
+ // Our goal here is to export the same types of symbols as what gets
+ // exported with __declspec(dllexport) (which 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;
+ };
+
+ 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';
+ }
+
+ // Note that it's not easy to import data without a dllimport
+ // declaration.
+ //
+ if (true)
+ {
+ 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";
+
+ // 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).
+ //
+ //
+ 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) << " DATA\n";
+ }
+ }
+ }
+
+ 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 1"};
+ }
+}
diff --git a/libbuild2/bin/def-rule.hxx b/libbuild2/bin/def-rule.hxx
new file mode 100644
index 0000000..32423a0
--- /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 string&) 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 8dd3cb6..92fc682 100644
--- a/libbuild2/bin/guess.cxx
+++ b/libbuild2/bin/guess.cxx
@@ -696,5 +696,99 @@ namespace build2
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 (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).
+ //
+ // 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.
+ //
+ if (l.compare (0, 13, "LLVM version ") == 0)
+ return guess_result ("llvm", move (l), semantic_version ());
+
+ if (l.compare (0, 14, "Microsoft (R) ") == 0)
+ return guess_result ("msvc", 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> (3, env, "--version", f, false, false, &cs);
+
+ if (!r.empty ())
+ r.checksum = cs.string ();
+ }
+
+ //@@ TODO: BSD, Mac OS.
+
+ if (r.empty ())
+ fail << "unable to guess " << nm << " signature";
+
+ 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 3307999..3a2c219 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
@@ -133,6 +133,39 @@ namespace build2
const rc_info&
guess_rc (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
+ // generic Other/generic/unrecognized
+ //
+ // 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 (const path& nm, const char* paths);
}
}
diff --git a/libbuild2/bin/init.cxx b/libbuild2/bin/init.cxx
index 7e022fd8..7d1f171 100644
--- a/libbuild2/bin/init.cxx
+++ b/libbuild2/bin/init.cxx
@@ -16,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>
@@ -30,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.
//
@@ -937,6 +939,21 @@ namespace build2
}
}
+ // Register .def file rule.
+ //
+ if (lid == "msvc" || lid == "msvc-lld")
+ {
+ // If we are using link.exe, then we can access dumpbin via the
+ // link.exe /DUMP option. But for lld-link we need llvm-nm.
+ //
+ if (lid == "msvc-lld")
+ load_module (rs, bs, "bin.nm.config", loc, extra.hints);
+
+ 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;
}
@@ -1039,6 +1056,114 @@ 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)
+ {
+ auto& vp (rs.var_pool ());
+
+ 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.
+ //
+ // 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 (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;
+ }
+
static const module_functions mod_functions[] =
{
// NOTE: don't forget to also update the documentation in init.hxx if
@@ -1053,6 +1178,8 @@ namespace build2
{"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},
{nullptr, nullptr, nullptr}
};
diff --git a/libbuild2/bin/init.hxx b/libbuild2/bin/init.hxx
index 7bb9d1d..6b0db27 100644
--- a/libbuild2/bin/init.hxx
+++ b/libbuild2/bin/init.hxx
@@ -21,6 +21,8 @@ namespace build2
//
// `bin.vars` -- registers some variables.
// `bin.config` -- loads bin.vars and sets some variables.
+ // `bin` -- loads bin.config and registers target types and
+ // rules.
// `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.
@@ -28,8 +30,8 @@ namespace build2
// 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.
//
extern "C" LIBBUILD2_BIN_SYMEXPORT const module_functions*
build2_bin_load ();
diff --git a/libbuild2/cc/link-rule.hxx b/libbuild2/cc/link-rule.hxx
index c68466a..c761d53 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>
@@ -262,7 +260,7 @@ namespace build2
bool operator< (const windows_dll& y) const {return dll < y.dll;}
};
- using windows_dlls = std::set<windows_dll>;
+ using windows_dlls = set<windows_dll>;
timestamp
windows_rpath_timestamp (const file&,
diff --git a/libbuild2/cc/msvc.cxx b/libbuild2/cc/msvc.cxx
index cceb1fe..9e8ae18 100644
--- a/libbuild2/cc/msvc.cxx
+++ b/libbuild2/cc/msvc.cxx
@@ -426,6 +426,7 @@ namespace build2
bool obj (false), dll (false);
string s;
+ bool io (false);
try
{
ifdstream is (
@@ -465,14 +466,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) || io)
{
diag_record dr;
dr << warn << "unable to detect " << l << " library type, ignoring" <<
diff --git a/libbuild2/config/operation.hxx b/libbuild2/config/operation.hxx
index ded3c8b..9e2a91e 100644
--- a/libbuild2/config/operation.hxx
+++ b/libbuild2/config/operation.hxx
@@ -4,8 +4,6 @@
#ifndef LIBBUILD2_CONFIG_OPERATION_HXX
#define LIBBUILD2_CONFIG_OPERATION_HXX
-#include <set>
-
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
@@ -29,7 +27,7 @@ namespace build2
// Configuration exporting.
//
- using project_set = std::set<const scope*>; // Pointers for comparison.
+ using project_set = set<const scope*>; // Pointers for comparison.
// If inherit is false, then don't rely on inheritance from outer scopes
// (used for config.config.save/$config.save()). In this case the already
diff --git a/libbuild2/test/script/parser.hxx b/libbuild2/test/script/parser.hxx
index f118ad3..3b008aa 100644
--- a/libbuild2/test/script/parser.hxx
+++ b/libbuild2/test/script/parser.hxx
@@ -4,7 +4,6 @@
#ifndef LIBBUILD2_TEST_SCRIPT_PARSER_HXX
#define LIBBUILD2_TEST_SCRIPT_PARSER_HXX
-#include <set>
#include <unordered_map>
#include <libbuild2/types.hxx>
@@ -132,7 +131,7 @@ namespace build2
// Pre-parse state.
//
using id_map = std::unordered_map<string, location>;
- using include_set = std::set<path>;
+ using include_set = set<path>;
group* group_;
id_map* id_map_;
diff --git a/libbuild2/test/script/script.hxx b/libbuild2/test/script/script.hxx
index 7dae78c..22f6725 100644
--- a/libbuild2/test/script/script.hxx
+++ b/libbuild2/test/script/script.hxx
@@ -4,8 +4,6 @@
#ifndef LIBBUILD2_TEST_SCRIPT_SCRIPT_HXX
#define LIBBUILD2_TEST_SCRIPT_SCRIPT_HXX
-#include <set>
-
#include <libbuild2/types.hxx>
#include <libbuild2/forward.hxx>
#include <libbuild2/utility.hxx>
@@ -382,7 +380,7 @@ namespace build2
}
};
- std::set<path_name_value, compare_paths> paths_;
+ set<path_name_value, compare_paths> paths_;
};
}
}
diff --git a/libbuild2/types.hxx b/libbuild2/types.hxx
index c8fe221..0005e75 100644
--- a/libbuild2/types.hxx
+++ b/libbuild2/types.hxx
@@ -14,6 +14,7 @@
#endif
#include <map>
+#include <set>
#include <array>
#include <tuple>
#include <regex>
@@ -97,6 +98,8 @@ namespace build2
using std::map;
using std::multimap;
+ using std::set;
+ using std::multiset;
using std::array;
using std::vector;
using butl::vector_view; // <libbutl/vector-view.mxx>
diff --git a/libbuild2/variable.hxx b/libbuild2/variable.hxx
index 573f968..53e393d 100644
--- a/libbuild2/variable.hxx
+++ b/libbuild2/variable.hxx
@@ -4,7 +4,6 @@
#ifndef LIBBUILD2_VARIABLE_HXX
#define LIBBUILD2_VARIABLE_HXX
-#include <set>
#include <type_traits> // aligned_storage
#include <unordered_map>
@@ -1442,7 +1441,7 @@ namespace build2
};
private:
- std::multiset<pattern> patterns_;
+ multiset<pattern> patterns_;
// Global pool flag/context.
//