diff options
Diffstat (limited to 'libbuild2/bin')
-rw-r--r-- | libbuild2/bin/buildfile | 4 | ||||
-rw-r--r-- | libbuild2/bin/def-rule.cxx | 850 | ||||
-rw-r--r-- | libbuild2/bin/def-rule.hxx | 41 | ||||
-rw-r--r-- | libbuild2/bin/functions.cxx | 4 | ||||
-rw-r--r-- | libbuild2/bin/guess.cxx | 341 | ||||
-rw-r--r-- | libbuild2/bin/guess.hxx | 71 | ||||
-rw-r--r-- | libbuild2/bin/init.cxx | 461 | ||||
-rw-r--r-- | libbuild2/bin/init.hxx | 14 | ||||
-rw-r--r-- | libbuild2/bin/rule.cxx | 159 | ||||
-rw-r--r-- | libbuild2/bin/rule.hxx | 44 | ||||
-rw-r--r-- | libbuild2/bin/target.cxx | 63 | ||||
-rw-r--r-- | libbuild2/bin/target.hxx | 214 | ||||
-rw-r--r-- | libbuild2/bin/utility.cxx | 28 | ||||
-rw-r--r-- | libbuild2/bin/utility.hxx | 2 |
14 files changed, 2020 insertions, 276 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/functions.cxx b/libbuild2/bin/functions.cxx index 59fcdf2..1c6c0f4 100644 --- a/libbuild2/bin/functions.cxx +++ b/libbuild2/bin/functions.cxx @@ -24,12 +24,14 @@ namespace build2 // bin.lib value on. As a result, it can be omitted in which case the // function call scope is used (covers project-local lib{} targets). // + // Note that this function is not pure. + // // @@ TODO: support for target (note that if it's out of project, then // it's imported, which means it might still be qualified.) // // @@ TODO: support utility libraries (see link_member()). // - f[".link_member"] = [] (const scope* bs, names ns) + f.insert (".link_member", false) += [] (const scope* bs, names ns) { string t (convert<string> (move (ns))); diff --git a/libbuild2/bin/guess.cxx b/libbuild2/bin/guess.cxx index 21936d9..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 (); } @@ -54,7 +57,7 @@ namespace build2 { process_path r ( run_try_search (prog, - true /* init */, + false /* init (cached) */, dir_path () /* fallback */, true /* path_only */, paths)); @@ -80,14 +83,33 @@ namespace build2 dr << info << "use " << var << " to override"; }); - return run_search (prog, true, dir_path (), true); + return run_search (prog, false, dir_path (), true); } - ar_info - guess_ar (const path& ar, const path* rl, const char* paths) + // Extracting ar/ranlib information requires running them which can become + // expensive if done repeatedly. So we cache the result. + // + static global_cache<ar_info> ar_cache; + + const ar_info& + guess_ar (context& ctx, const path& ar, const path* rl, const char* paths) { tracer trace ("bin::guess_ar"); + // First check the cache. + // + string key; + { + sha256 cs; + cs.append (ar.string ()); + if (rl != nullptr) cs.append (rl->string ()); + if (paths != nullptr) cs.append (paths); + key = cs.string (); + + if (const ar_info* r = ar_cache.find (key)) + return *r; + } + guess_result arr, rlr; process_path arp (search (ar, paths, "config.bin.ar")); @@ -158,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)); @@ -208,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 (); @@ -228,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 ()) { @@ -261,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 @@ -274,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 (); @@ -291,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 ()) { @@ -307,24 +341,78 @@ namespace build2 fail << "unable to guess " << *rl << " signature"; } - return ar_info { - move (arp), - move (arr.id), - move (arr.signature), - move (arr.checksum), - move (*arr.version), - - move (rlp), - move (rlr.id), - move (rlr.signature), - move (rlr.checksum)}; + // 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), + move (arr.id), + move (arr.signature), + move (arr.checksum), + move (*arr.version), + nullptr, + + move (rlp), + move (rlr.id), + move (rlr.signature), + move (rlr.checksum), + nullptr}); } - ld_info - guess_ld (const path& ld, const char* paths) + // 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 (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; + cs.append (ld.string ()); + if (paths != nullptr) cs.append (paths); + key = cs.string (); + + if (const ld_info* r = ld_cache.find (key)) + return *r; + } + guess_result r; process_path pp (search (ld, paths, "config.bin.ld")); @@ -364,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: // - else if (l.compare (0, 4, "LLD ") == 0) + // Debian LLD 14.0.6 (compatible with GNU linkers) + // + 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 @@ -412,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 (); @@ -448,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 (); @@ -475,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 (); @@ -484,19 +577,55 @@ namespace build2 if (r.empty ()) fail << "unable to guess " << ld << " signature"; - return ld_info { - move (pp), - move (r.id), - move (r.signature), - move (r.checksum), - move (r.version)}; + 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), + ld_env}); } - rc_info - guess_rc (const path& rc, const char* paths) + // 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 (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; + cs.append (rc.string ()); + if (paths != nullptr) cs.append (paths); + key = cs.string (); + + if (const rc_info* r = rc_cache.find (key)) + return *r; + } + guess_result r; process_path pp (search (rc, paths, "config.bin.rc")); @@ -533,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 (); @@ -566,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 (); @@ -575,8 +704,128 @@ namespace build2 if (r.empty ()) fail << "unable to guess " << rc << " signature"; - return rc_info { - move (pp), move (r.id), move (r.signature), move (r.checksum)}; + 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), + 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 27bb5ed..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. // - ar_info - guess_ar (const path& ar, const path* ranlib, const char* paths); + const ar_info& + 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; }; - ld_info - guess_ld (const path& ld, const char* paths); + const ld_info& + 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 (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; }; - rc_info - guess_rc (const path& rc, const char* paths); + 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 9c16432..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> @@ -29,8 +28,10 @@ namespace build2 { namespace bin { - static const fail_rule fail_; + 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. // @@ -40,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"); @@ -75,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"); @@ -103,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} @@ -116,12 +126,23 @@ namespace build2 // // If unspecified, defaults to false for liba{} and to true for libu*{}. // - vp.insert<bool> ("bin.whole", variable_visibility::target); + vp.insert<bool> ("bin.whole", variable_visibility::target); - vp.insert<string> ("bin.exe.prefix"); - vp.insert<string> ("bin.exe.suffix"); - vp.insert<string> ("bin.lib.prefix"); - vp.insert<string> ("bin.lib.suffix"); + // Mark library as binless. + // + // For example, the user can mark a C++ library consisting of only + // module interfaces as binless so it becomes a modules equivalent to + // header-only library (which we will call a module interface-only + // library). + // + vp.insert<bool> ("bin.binless", variable_visibility::target); + + // Executable and library name prefixes and suffixes. + // + vp.insert<string> ("bin.exe.prefix"); + vp.insert<string> ("bin.exe.suffix"); + vp.insert<string> ("bin.lib.prefix"); + vp.insert<string> ("bin.lib.suffix"); // The optional custom clean patterns should be just the pattern stem, // without the library prefix/name or extension. For example, `-[A-Z]` @@ -132,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; } @@ -183,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 @@ -219,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 << "'";}); } @@ -260,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 @@ -428,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) @@ -524,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 { @@ -534,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) { @@ -552,22 +610,20 @@ namespace build2 { auto& r (bs.rules); - r.insert<obj> (perform_update_id, "bin.obj", fail_); - r.insert<obj> (perform_clean_id, "bin.obj", fail_); + r.insert<obj> (perform_update_id, "bin.obj", obj_); + r.insert<obj> (perform_clean_id, "bin.obj", obj_); - r.insert<bmi> (perform_update_id, "bin.bmi", fail_); - r.insert<bmi> (perform_clean_id, "bin.bmi", fail_); + r.insert<bmi> (perform_update_id, "bin.bmi", obj_); + r.insert<bmi> (perform_clean_id, "bin.bmi", obj_); - r.insert<hbmi> (perform_update_id, "bin.hbmi", fail_); - r.insert<hbmi> (perform_clean_id, "bin.hbmi", fail_); + r.insert<hbmi> (perform_update_id, "bin.hbmi", obj_); + r.insert<hbmi> (perform_clean_id, "bin.hbmi", obj_); - r.insert<libul> (perform_update_id, "bin.libul", fail_); - r.insert<libul> (perform_clean_id, "bin.libul", fail_); + r.insert<libul> (perform_update_id, "bin.libul", libul_); + r.insert<libul> (perform_clean_id, "bin.libul", libul_); // Similar to alias. // - - //@@ outer r.insert<lib> (perform_id, 0, "bin.lib", lib_); r.insert<lib> (configure_id, 0, "bin.lib", lib_); @@ -588,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_); } } @@ -614,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"); @@ -672,7 +743,7 @@ namespace build2 nullptr, config::save_default_commented))); - 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). @@ -711,34 +782,39 @@ namespace build2 } } - rs.assign<process_path_ex> ("bin.ar.path") = - process_path_ex (move (ari.ar_path), "ar", ari.ar_checksum); - rs.assign<string> ("bin.ar.id") = move (ari.ar_id); - rs.assign<string> ("bin.ar.signature") = move (ari.ar_signature); - rs.assign<string> ("bin.ar.checksum") = move (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; { - semantic_version& v (ari.ar_version); + const semantic_version& v (ari.ar_version); rs.assign<string> ("bin.ar.version") = v.string (); rs.assign<uint64_t> ("bin.ar.version.major") = v.major; rs.assign<uint64_t> ("bin.ar.version.minor") = v.minor; rs.assign<uint64_t> ("bin.ar.version.patch") = v.patch; - rs.assign<string> ("bin.ar.version.build") = move (v.build); + 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 (move (ari.ranlib_path), - "ranlib", - ari.ranlib_checksum); - rs.assign<string> ("bin.ranlib.id") = - move (ari.ranlib_id); - rs.assign<string> ("bin.ranlib.signature") = - move (ari.ranlib_signature); - rs.assign<string> ("bin.ranlib.checksum") = - move (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); } } @@ -783,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"); } @@ -815,7 +894,7 @@ namespace build2 path (apply_pattern (ld_d, pat.pattern)), config::save_default_commented))); - 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). @@ -847,22 +926,27 @@ namespace build2 << " checksum " << ldi.checksum; } - rs.assign<process_path_ex> ("bin.ld.path") = - process_path_ex (move (ldi.path), "ld", ldi.checksum); - rs.assign<string> ("bin.ld.id") = move (ldi.id); - rs.assign<string> ("bin.ld.signature") = move (ldi.signature); - rs.assign<string> ("bin.ld.checksum") = move (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; if (ldi.version) { - semantic_version& v (*ldi.version); + const semantic_version& v (*ldi.version); rs.assign<string> ("bin.ld.version") = v.string (); rs.assign<uint64_t> ("bin.ld.version.major") = v.major; rs.assign<uint64_t> ("bin.ld.version.minor") = v.minor; rs.assign<uint64_t> ("bin.ld.version.patch") = v.patch; - rs.assign<string> ("bin.ld.version.build") = move (v.build); + rs.assign<string> ("bin.ld.version.build") = v.build; } + + config::save_environment (rs, ldi.environment); } return true; @@ -894,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 { @@ -904,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"])) { @@ -936,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"); } @@ -968,7 +1057,7 @@ namespace build2 path (apply_pattern (rc_d, pat.pattern)), config::save_default_commented))); - 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). @@ -982,11 +1071,16 @@ namespace build2 << " checksum " << rci.checksum; } - rs.assign<process_path_ex> ("bin.rc.path") = - process_path_ex (move (rci.path), "rc", rci.checksum); - rs.assign<string> ("bin.rc.id") = move (rci.id); - rs.assign<string> ("bin.rc.signature") = move (rci.signature); - rs.assign<string> ("bin.rc.checksum") = move (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; @@ -1011,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 0abfcb5..c7147bf 100644 --- a/libbuild2/bin/rule.cxx +++ b/libbuild2/bin/rule.cxx @@ -17,28 +17,173 @@ namespace build2 { namespace bin { - // fail_rule + // Search for an existing (declared real) member and match it if found. // - bool fail_rule:: - match (action a, target& t, const string&) const + static void + dist_match (action a, target& t, const target_type& tt) { - const char* n (t.dynamic_type ().name); // Ignore derived type. + 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 + { + 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 " << n << "s{} member" << endf; } - recipe fail_rule:: - apply (action, target&) const {return empty_recipe;} + recipe obj_rule:: + 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, target&) const + { + return true; + } + + recipe libul_rule:: + 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 51693a7..9dd1d14 100644 --- a/libbuild2/bin/rule.hxx +++ b/libbuild2/bin/rule.hxx @@ -9,22 +9,52 @@ #include <libbuild2/rule.hxx> +#include <libbuild2/dist/rule.hxx> + #include <libbuild2/bin/export.hxx> namespace build2 { namespace bin { - // "Fail rule" for obj{}, [h]bmi{}, and libu{} that issues diagnostics if - // someone tries to build any of these groups directly. + // "Fail rule" for obj{} and [h]bmi{} that issues diagnostics if someone + // tries to build these groups directly. + // + // Note that for dist it acts as a pass-through to all existing (declared) + // members. // - class fail_rule: public simple_rule + class obj_rule: public dist::rule { public: - fail_rule () {} + obj_rule () {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; + + virtual recipe + apply (action, target&) const override; + }; + + // 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. + // + // 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 override; virtual recipe apply (action, target&) const override; @@ -32,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 94851cd..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,20 +47,20 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; const target_type libx::static_type { "libx", - &target::static_type, + &mtime_target::static_type, nullptr, nullptr, nullptr, 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 8be8c23..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,90 +146,125 @@ 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. // - class LIBBUILD2_BIN_SYMEXPORT libx: public target + // Use mtime_target as a base for the "trust me it exists" functionality + // which we use, for example, to have installed lib{} prerequisites that + // are matched by the fallback file rule. + // + class LIBBUILD2_BIN_SYMEXPORT libx: public mtime_target { public: - using target::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; @@ -236,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; @@ -245,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. @@ -287,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]. @@ -317,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. @@ -334,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). @@ -346,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 6b0c4de..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) @@ -47,9 +44,11 @@ namespace build2 return lmembers {a, s}; } - const target* + const file* link_member (const libx& x, action a, linfo li, bool exist) { + const target* r; + if (x.is_a<libul> ()) { // For libul{} that is linked to an executable the member choice @@ -58,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 @@ -72,7 +76,7 @@ namespace build2 // Called by the compile rule during execute. // - return x.ctx.phase == run_phase::match && !exist + r = x.ctx.phase == run_phase::match && !exist ? &search (x, tt, x.dir, x.out, x.name) : search_existing (x.ctx, tt, x.dir, x.out, x.name); } @@ -85,17 +89,21 @@ namespace build2 // Make sure group members are resolved. // group_view gv (resolve_members (a, l)); - assert (gv.members != nullptr); - pair<otype, bool> r ( + 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)); - if (!r.second) - fail << (r.first == otype::s ? "shared" : "static") + if (!p.second) + fail << (p.first == otype::s ? "shared" : "static") << " variant of " << l << " is not available"; - return r.first == otype::s ? static_cast<const target*> (l.s) : l.a; + r = p.first == otype::s ? static_cast<const target*> (l.s) : l.a; } + + return static_cast<const file*> (r); } pattern_paths diff --git a/libbuild2/bin/utility.hxx b/libbuild2/bin/utility.hxx index 5d7eed4..e12bb5f 100644 --- a/libbuild2/bin/utility.hxx +++ b/libbuild2/bin/utility.hxx @@ -59,7 +59,7 @@ namespace build2 // If existing is true, then only return the member target if it exists // (currently only used and supported for utility libraries). // - LIBBUILD2_BIN_SYMEXPORT const target* + LIBBUILD2_BIN_SYMEXPORT const file* link_member (const libx&, action, linfo, bool existing = false); // As above but return otype::a or otype::s as well as an indication if |