From cd10a583ad1f3c299383c07fd8c6ccd6e3199e6b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 11 Nov 2020 15:14:19 +0200 Subject: Add ${c,cxx}.lib_{poptions,libs,rpaths}() functions These functions can be used to query library metadata for options and libraries that should be used when compiling/linking dependent targets, similar to how cc::{compile,link}_rule do it. With this support it should be possible to more or less re-create their semantics in ad hoc recipes. --- libbuild2/cc/compile-rule.cxx | 59 ++++++----- libbuild2/cc/compile-rule.hxx | 33 ++++-- libbuild2/cc/functions.cxx | 231 ++++++++++++++++++++++++++++++++++++++++++ libbuild2/cc/link-rule.cxx | 72 ++++++------- libbuild2/cc/link-rule.hxx | 46 +++++---- libbuild2/cc/module.cxx | 18 +++- libbuild2/cc/module.hxx | 8 +- libbuild2/cc/utility.hxx | 13 ++- libbuild2/cc/utility.ixx | 12 ++- libbuild2/functions-name.cxx | 4 +- 10 files changed, 391 insertions(+), 105 deletions(-) create mode 100644 libbuild2/cc/functions.cxx diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index 832ed9b..6ddff08 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -380,11 +380,9 @@ namespace build2 // template void compile_rule:: - append_lib_options (const scope& bs, - T& args, - action a, - const target& t, - linfo li) const + append_lib_options (T& args, + const scope& bs, + action a, const file& l, bool la, linfo li) const { // See through utility libraries. // @@ -409,11 +407,25 @@ namespace build2 append_options (args, l, var); }; - // In case we don't have the "small function object" optimization. - // - const function impf (imp); - const function optf (opt); + process_libraries (a, bs, li, sys_lib_dirs, + l, la, 0, // Hack: lflags unused. + imp, nullptr, opt); + } + void compile_rule:: + append_lib_options (strings& args, + const scope& bs, + action a, const file& l, bool la, linfo li) const + { + append_lib_options (args, bs, a, l, la, li); + } + + template + void compile_rule:: + append_lib_options (T& args, + const scope& bs, + action a, const target& t, linfo li) const + { for (prerequisite_member p: group_prerequisite_members (a, t)) { if (include (a, t, p) != include_type::normal) // Excluded/ad hoc. @@ -427,14 +439,13 @@ namespace build2 pt = link_member (*l, a, li); bool la; - if (!((la = pt->is_a ()) || - (la = pt->is_a ()) || - pt->is_a ())) - continue; - - process_libraries (a, bs, li, sys_lib_dirs, - pt->as (), la, 0, // Hack: lflags unused. - impf, nullptr, optf); + const file* f; + if ((la = (f = pt->is_a ())) || + (la = (f = pt->is_a ())) || + ( (f = pt->is_a ()))) + { + append_lib_options (args, bs, a, *f, la, li); + } } } } @@ -443,8 +454,8 @@ namespace build2 // recursively, prerequisite libraries first. // void compile_rule:: - append_lib_prefixes (const scope& bs, - prefix_map& m, + append_lib_prefixes (prefix_map& m, + const scope& bs, action a, target& t, linfo li) const @@ -888,7 +899,7 @@ namespace build2 // Hash *.export.poptions from prerequisite libraries. // - append_lib_options (bs, cs, a, t, li); + append_lib_options (cs, bs, a, t, li); } append_options (cs, t, c_coptions); @@ -1474,7 +1485,7 @@ namespace build2 // Then process the include directories from prerequisite libraries. // - append_lib_prefixes (bs, m, a, t, li); + append_lib_prefixes (m, bs, a, t, li); return m; } @@ -2906,7 +2917,7 @@ namespace build2 // Add *.export.poptions from prerequisite libraries. // - append_lib_options (bs, args, a, t, li); + append_lib_options (args, bs, a, t, li); // Populate the src-out with the -I$out_base -I$src_base pairs. // @@ -4302,7 +4313,7 @@ namespace build2 append_options (args, t, x_poptions); append_options (args, t, c_poptions); - append_lib_options (t.base_scope (), args, a, t, li); + append_lib_options (args, t.base_scope (), a, t, li); if (md.symexport) append_symexport_options (args, t); @@ -5929,7 +5940,7 @@ namespace build2 // Add *.export.poptions from prerequisite libraries. // - append_lib_options (bs, args, a, t, li); + append_lib_options (args, bs, a, t, li); if (md.symexport) append_symexport_options (args, t); diff --git a/libbuild2/cc/compile-rule.hxx b/libbuild2/cc/compile-rule.hxx index d6cb002..cbbb142 100644 --- a/libbuild2/cc/compile-rule.hxx +++ b/libbuild2/cc/compile-rule.hxx @@ -53,6 +53,15 @@ namespace build2 target_state perform_clean (action, const target&) const; + public: + void + append_lib_options (strings&, + const scope&, + action, const file&, bool, linfo) const; + protected: + static void + functions (function_family&, const char*); // functions.cxx + private: struct match_data; using environment = small_vector; @@ -63,11 +72,15 @@ namespace build2 template void - append_lib_options (const scope&, - T&, - action, - const target&, - linfo) const; + append_lib_options (T&, + const scope&, + action, const file&, bool, linfo) const; + + template + void + append_lib_options (T&, + const scope&, + action, const target&, linfo) const; // Mapping of include prefixes (e.g., foo in ) for auto- // generated headers to directories where they will be generated. @@ -94,11 +107,9 @@ namespace build2 append_prefixes (prefix_map&, const target&, const variable&) const; void - append_lib_prefixes (const scope&, - prefix_map&, - action, - target&, - linfo) const; + append_lib_prefixes (prefix_map&, + const scope&, + action, target&, linfo) const; prefix_map build_prefix_map (const scope&, action, target&, linfo) const; @@ -168,7 +179,7 @@ namespace build2 action, const file&, const match_data&, const path&) const; - // Compiler-specific language selection option. Return the number of + // Compiler-specific language selection options. Return the number of // options (arguments, really) appended. // size_t diff --git a/libbuild2/cc/functions.cxx b/libbuild2/cc/functions.cxx new file mode 100644 index 0000000..ca93b63 --- /dev/null +++ b/libbuild2/cc/functions.cxx @@ -0,0 +1,231 @@ +// file : libbuild2/cc/functions.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +namespace build2 +{ + const target& + to_target (const scope&, name&&, name&&); // libbuild2/functions-name.cxx + + namespace cc + { + using namespace bin; + + // Common thunk for $x.lib_*(, [, ...]) functions. + // + struct lib_data + { + const char* x; + void (*f) (strings&, + const vector_view&, const module&, const scope&, + action, const file&, bool, linfo); + }; + + static value + lib_thunk (const scope* bs, + vector_view vs, + const function_overload& f) + { + const lib_data& d (*reinterpret_cast (&f.data)); + + if (bs == nullptr) + fail << f.name << " called out of scope"; + + const scope* rs (bs->root_scope ()); + + if (rs == nullptr) + fail << f.name << " called out of project"; + + if (bs->ctx.phase != run_phase::execute) + fail << f.name << " can only be called during execution"; + + const module* m (rs->find_module (d.x)); + + if (m == nullptr) + fail << f.name << " called without " << d.x << " module being loaded"; + + // We can assume these are present due to function's types signature. + // + names& ts_ns (vs[0].as ()); // + names& ot_ns (vs[1].as ()); // + + linfo li; + { + string t (convert (move (ot_ns))); + + const target_type* tt (bs->find_target_type (t)); + + if (tt == nullptr) + fail << "unknown target type '" << t << "'"; + + // Try both linker and compiler output types. + // + otype ot (link_type (*tt).type); + + switch (ot) + { + case otype::e: + case otype::a: + case otype::s: + break; + default: + ot = compile_type (*tt); + switch (ot) + { + case otype::e: + case otype::a: + case otype::s: + break; + default: + fail << "target type " << t << " is not compiler/linker output"; + } + } + + li = link_info (*bs, ot); + } + + // In a somewhat hackish way strip the outer operation to match how we + // call the underlying functions in the compile/link rules. This should + // be harmless since ad hoc recipes are always for the inner operation. + // + action a (rs->ctx.current_action ().inner_action ()); + + strings r; + for (auto i (ts_ns.begin ()); i != ts_ns.end (); ++i) + { + name& n (*i), o; + const target& t (to_target (*bs, move (n), move (n.pair ? *++i : o))); + + const file* f; + bool la (false); + + if ((la = (f = t.is_a ())) || + (la = (f = t.is_a ())) || + ( (f = t.is_a ()))) + { + d.f (r, vs, *m, *bs, a, *f, la, li); + } + else + fail << t << " is not a library target"; + } + + return value (move (r)); + } + + void compile_rule:: + functions (function_family& f, const char* x) + { + // $.lib_poptions(, ) + // + // Return the preprocessor options that should be passed when compiling + // sources that depend on the specified libraries. The second argument + // is the output target type (obje, objs, etc). + // + // Note that this function can only be called during execution after all + // the specified library targets have been matched. Normally it is used + // in ad hoc recipes to implement custom compilation. + // + f[".lib_poptions"].insert ( + &lib_thunk, + lib_data { + x, + [] (strings& r, + const vector_view&, const module& m, const scope& bs, + action a, const file& l, bool la, linfo li) + { + m.append_lib_options (r, bs, a, l, la, li); + }}); + } + + void link_rule:: + functions (function_family& f, const char* x) + { + // $.lib_libs(, [, [, ]]) + // + // Return the libraries (and any associated options) that should be + // passed when linking targets that depend on the specified libraries. + // The second argument is the output target type (exe, libs, etc). + // + // The following flags are supported: + // + // whole - link the specified libraries in the whole archive mode + // + // If the last argument is false, then do not return the specified + // libraries themselves. + // + // Note that this function can only be called during execution after all + // the specified library targets have been matched. Normally it is used + // in ad hoc recipes to implement custom linking. + // + f[".lib_libs"].insert, optional> ( + &lib_thunk, + lib_data { + x, + [] (strings& r, + const vector_view& vs, const module& m, const scope& bs, + action a, const file& l, bool la, linfo li) + { + lflags lf (0); + if (vs.size () > 2) + { + for (const name& f: vs[2].as ()) + { + string s (convert (name (f))); + + if (s == "whole") + lf |= lflag_whole; + else + fail << "invalid flag '" << s << "'"; + } + } + + bool self (vs.size () > 3 ? convert (vs[3]) : true); + + m.append_libraries (r, bs, a, l, la, lf, li, self); + }}); + + // $.lib_rpaths(, [, [, ]]) + // + // Return the rpath options that should be passed when linking targets + // that depend on the specified libraries. The second argument is the + // output target type (exe, libs, etc). + // + // If the third argument is true, then use rpath-link options rather + // than rpath (which is what should normally be used when linking for + // install, for example). + // + // If the last argument is false, then do not return the options for the + // specified libraries themselves. + // + // Note that this function can only be called during execution after all + // the specified library targets have been matched. Normally it is used + // in ad hoc recipes to implement custom linking. + // + f[".lib_rpaths"].insert, optional> ( + &lib_thunk, + lib_data { + x, + [] (strings& r, + const vector_view& vs, const module& m, const scope& bs, + action a, const file& l, bool la, linfo li) + { + bool link (vs.size () > 2 ? convert (vs[2]) : false); + bool self (vs.size () > 3 ? convert (vs[3]) : true); + m.rpath_libraries (r, bs, a, l, la, li, link, self); + }}); + } + } +} diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index 6acb1c7..b22cae0 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -1490,8 +1490,9 @@ namespace build2 void link_rule:: append_libraries (strings& args, - const file& l, bool la, lflags lf, - const scope& bs, action a, linfo li) const + const scope& bs, action a, + const file& l, bool la, lflags lf, linfo li, + bool self) const { struct data { @@ -1664,14 +1665,13 @@ namespace build2 }; process_libraries ( - a, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, true); + a, bs, li, sys_lib_dirs, l, la, lf, imp, lib, opt, self); } void link_rule:: - append_libraries (sha256& cs, - bool& update, timestamp mt, - const file& l, bool la, lflags lf, - const scope& bs, action a, linfo li) const + append_libraries (sha256& cs, bool& update, timestamp mt, + const scope& bs, action a, + const file& l, bool la, lflags lf, linfo li) const { struct data { @@ -1771,11 +1771,9 @@ namespace build2 void link_rule:: rpath_libraries (strings& args, - const target& t, const scope& bs, - action a, - linfo li, - bool link) const + action a, const file& l, bool la, + linfo li, bool link, bool self) const { // Use -rpath-link only on targets that support it (Linux, *BSD). Note // that we don't really need it for top-level libraries. @@ -1874,12 +1872,30 @@ namespace build2 d.args.push_back (move (o)); }; - // In case we don't have the "small function object" optimization. - // - const function impf (imp); - const function< - void (const file* const*, const string&, lflags, bool)> libf (lib); + if (self && !link && !la) + { + // Top-level shared library dependency. + // + if (!l.path ().empty ()) // Not binless. + { + // It is either matched or imported so should be a cc library. + // + if (!cast_false (l.vars[c_system])) + args.push_back ("-Wl,-rpath," + l.path ().directory ().string ()); + } + } + + process_libraries (a, bs, li, sys_lib_dirs, + l, la, 0 /* lflags */, + imp, lib, nullptr); + } + + void link_rule:: + rpath_libraries (strings& args, + const scope& bs, action a, + const target& t, linfo li, bool link) const + { for (const prerequisite_target& pt: t.prerequisite_targets[a]) { if (pt == nullptr) @@ -1892,23 +1908,7 @@ namespace build2 (la = (f = pt->is_a ())) || ( f = pt->is_a ())) { - if (!link && !la) - { - // Top-level shared library dependency. - // - if (!f->path ().empty ()) // Not binless. - { - // It is either matched or imported so should be a cc library. - // - if (!cast_false (f->vars[c_system])) - args.push_back ( - "-Wl,-rpath," + f->path ().directory ().string ()); - } - } - - process_libraries (a, bs, li, sys_lib_dirs, - *f, la, pt.data, - impf, libf, nullptr); + rpath_libraries (args, bs, a, *f, la, li, link, true); } } } @@ -2431,7 +2431,7 @@ namespace build2 if (cast_true (t[for_install ? "bin.rpath_link.auto" : "bin.rpath.auto"])) - rpath_libraries (sargs, t, bs, a, li, for_install /* link */); + rpath_libraries (sargs, bs, a, t, li, for_install /* link */); lookup l; @@ -2575,7 +2575,7 @@ namespace build2 // if (la || ls) { - append_libraries (cs, update, mt, *f, la, p.data, bs, a, li); + append_libraries (cs, update, mt, bs, a, *f, la, p.data, li); f = nullptr; // Timestamp checked by hash_libraries(). } else @@ -2886,7 +2886,7 @@ namespace build2 (ls = (f = pt->is_a ()))))) { if (la || ls) - append_libraries (sargs, *f, la, p.data, bs, a, li); + append_libraries (sargs, bs, a, *f, la, p.data, li); else { sargs.push_back (relative (f->path ()).string ()); // string()&& diff --git a/libbuild2/cc/link-rule.hxx b/libbuild2/cc/link-rule.hxx index db48160..a93defc 100644 --- a/libbuild2/cc/link-rule.hxx +++ b/libbuild2/cc/link-rule.hxx @@ -51,6 +51,33 @@ namespace build2 using simple_rule::match; // To make Clang happy. + public: + // Library handling. + // + void + append_libraries (strings&, + const scope&, action, + const file&, bool, lflags, linfo, bool = true) const; + + void + append_libraries (sha256&, bool&, timestamp, + const scope&, action, + const file&, bool, lflags, linfo) const; + + void + rpath_libraries (strings&, + const scope&, + action, const file&, bool, linfo, bool, bool) const; + + void + rpath_libraries (strings&, + const scope&, action, + const target&, linfo, bool) const; + + protected: + static void + functions (function_family&, const char*); // functions.cxx + private: friend class install_rule; friend class libux_install_rule; @@ -126,25 +153,6 @@ namespace build2 link_rule::libs_paths libs_paths; }; - // Library handling. - // - void - append_libraries (strings&, - const file&, bool, lflags, - const scope&, action, linfo) const; - - void - append_libraries (sha256&, - bool&, timestamp, - const file&, bool, lflags, - const scope&, action, linfo) const; - - void - rpath_libraries (strings&, - const target&, - const scope&, action, linfo, - bool) const; - // Windows rpath emulation (windows-rpath.cxx). // struct windows_dll diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx index 4b4f5e2..14182a2 100644 --- a/libbuild2/cc/module.cxx +++ b/libbuild2/cc/module.cxx @@ -6,6 +6,7 @@ #include // left, setw() #include +#include #include #include @@ -677,6 +678,18 @@ namespace build2 { tracer trace (x, "init"); + context& ctx (rs.ctx); + + // Register the module function family if this is the first instance of + // this modules. + // + if (!function_family::defined (ctx.functions, x)) + { + function_family f (ctx.functions, x); + compile_rule::functions (f, x); + link_rule::functions (f, x); + } + // Load cc.core. Besides other things, this will load bin (core) plus // extra bin.* modules we may need. // @@ -798,13 +811,12 @@ namespace build2 bool s (tsys != "emscripten"); auto& r (rs.rules); + const compile_rule& cr (*this); + const link_rule& lr (*this); // We register for configure so that we detect unresolved imports // during configuration rather that later, e.g., during update. // - const compile_rule& cr (*this); - const link_rule& lr (*this); - r.insert (perform_update_id, x_compile, cr); r.insert (perform_clean_id, x_compile, cr); r.insert (configure_update_id, x_compile, cr); diff --git a/libbuild2/cc/module.hxx b/libbuild2/cc/module.hxx index 81456b3..85b7158 100644 --- a/libbuild2/cc/module.hxx +++ b/libbuild2/cc/module.hxx @@ -88,10 +88,10 @@ namespace build2 class LIBBUILD2_CC_SYMEXPORT module: public build2::module, public virtual common, - link_rule, - compile_rule, - install_rule, - libux_install_rule + public link_rule, + public compile_rule, + public install_rule, + public libux_install_rule { public: explicit diff --git a/libbuild2/cc/utility.hxx b/libbuild2/cc/utility.hxx index 458aa25..a856fd0 100644 --- a/libbuild2/cc/utility.hxx +++ b/libbuild2/cc/utility.hxx @@ -32,10 +32,19 @@ namespace build2 extern const dir_path module_build_dir; // cc/build/ extern const dir_path module_build_modules_dir; // cc/build/modules/ - // Compile output type from source target. + // Compile output type from output target type (obj*{}, bmi*{}, etc). + // + // If input unit type is specified, then restrict the tests only to output + // types that can be produced from this input. // otype - compile_type (const target&, unit_type); + compile_type (const target_type&, optional = nullopt); + + inline otype + compile_type (const target& t, optional ut = nullopt) + { + return compile_type (t.type (), ut); + } compile_target_types compile_types (otype); diff --git a/libbuild2/cc/utility.ixx b/libbuild2/cc/utility.ixx index 0b94780..46a4d0d 100644 --- a/libbuild2/cc/utility.ixx +++ b/libbuild2/cc/utility.ixx @@ -6,21 +6,23 @@ namespace build2 namespace cc { inline otype - compile_type (const target& t, unit_type u) + compile_type (const target_type& t, optional u) { using namespace bin; auto test = [&t, u] (const auto& h, const auto& i, const auto& o) { - return t.is_a (u == unit_type::module_header ? h : - u == unit_type::module_iface ? i : - o); + return (u + ? t.is_a (*u == unit_type::module_header ? h : + *u == unit_type::module_iface ? i : o) + : t.is_a (h) || t.is_a (i) || t.is_a (o)); }; return test (hbmie::static_type, bmie::static_type, obje::static_type) ? otype::e : + test (hbmis::static_type, bmis::static_type, objs::static_type) ? otype::s : test (hbmia::static_type, bmia::static_type, obja::static_type) ? otype::a : - otype::s; + static_cast (0xFF); } inline compile_target_types diff --git a/libbuild2/functions-name.cxx b/libbuild2/functions-name.cxx index 9b1a80b..8b61c81 100644 --- a/libbuild2/functions-name.cxx +++ b/libbuild2/functions-name.cxx @@ -32,7 +32,9 @@ namespace build2 return make_pair (move (n), move (e)); } - static const target& + // Note: this helper mey be used by other functions that operate on targets. + // + LIBBUILD2_SYMEXPORT const target& to_target (const scope& s, name&& n, name&& o) { if (const target* r = search_existing (n, s, o.dir)) -- cgit v1.1