From ffa0839de796fbefc48bacc4777648ff19b3fee6 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 8 Sep 2017 16:16:11 +0200 Subject: Add ability to pass scope to buildfile functions, add $install.resolve() --- build2/function.cxx | 36 +++++- build2/function.hxx | 232 ++++++++++++++++++++++++++++++++++---- build2/functions-path.cxx | 6 +- build2/install/functions.cxx | 27 +++++ build2/install/init.cxx | 11 +- build2/parser.cxx | 6 +- build2/version/rule.cxx | 4 +- tests/function/install/buildfile | 5 + tests/function/install/testscript | 33 ++++++ unit-tests/function/driver.cxx | 20 +++- 10 files changed, 342 insertions(+), 38 deletions(-) create mode 100644 build2/install/functions.cxx create mode 100644 tests/function/install/buildfile create mode 100644 tests/function/install/testscript diff --git a/build2/function.cxx b/build2/function.cxx index f7de749..2d04b30 100644 --- a/build2/function.cxx +++ b/build2/function.cxx @@ -58,6 +58,29 @@ namespace build2 return os; } + bool function_map:: + defined (const string& name) const + { + assert (!name.empty ()); + + // If this is a qualified function name then check if it is already + // defined. + // + if (name.back () != '.') + return map_.find (name) != map_.end (); + + // If any function of the specified family is already defined, then one of + // them should be the first element that is greater than the dot-terminated + // family name. Here we rely on the fact that the dot character is less + // than any character of unqualified function and family names. + // + size_t n (name.size ()); + assert (n > 1); + + auto i (map_.upper_bound (name)); + return i != map_.end () && i->first.compare (0, n, name) == 0; + } + auto function_map:: insert (string name, function_overload f) -> iterator { @@ -74,7 +97,8 @@ namespace build2 } pair function_map:: - call (const string& name, + call (const scope& base, + const string& name, vector_view args, const location& loc, bool fa) const @@ -174,7 +198,7 @@ namespace build2 })); auto f (r.back ()); - return make_pair (f->impl (move (args), *f), true); + return make_pair (f->impl (base, move (args), *f), true); } case 0: { @@ -231,18 +255,20 @@ namespace build2 } value function_family:: - default_thunk (vector_view args, const function_overload& f) + default_thunk (const scope& base, + vector_view args, + const function_overload& f) try { // Call the cast thunk. // struct cast_data // Prefix of function_cast::data. { - value (*const thunk) (vector_view, const void*); + value (*const thunk) (const scope&, vector_view, const void*); }; auto d (reinterpret_cast (&f.data)); - return d->thunk (move (args), d); + return d->thunk (base, move (args), d); } catch (const invalid_argument& e) { diff --git a/build2/function.hxx b/build2/function.hxx index 0e592b1..96bf401 100644 --- a/build2/function.hxx +++ b/build2/function.hxx @@ -5,9 +5,9 @@ #ifndef BUILD2_FUNCTION_HXX #define BUILD2_FUNCTION_HXX +#include #include // index_sequence #include // aligned_storage -#include #include #include @@ -45,6 +45,9 @@ namespace build2 // expected to issue diagnostics and throw failed. Note that the arguments // are conceptually "moved" and can be reused by the implementation. // + // A function can also optionally receive the current scope by having the + // first argument of the const scope& type. + // // Normally functions come in families that share a common qualification // (e.g., string. or path.). The function_family class is a "registrar" // that simplifies handling of function families. For example: @@ -64,7 +67,9 @@ namespace build2 // struct function_overload; - using function_impl = value (vector_view, const function_overload&); + using function_impl = value (const scope&, + vector_view, + const function_overload&); struct function_overload { @@ -134,7 +139,7 @@ namespace build2 class function_map { public: - using map_type = std::unordered_multimap; + using map_type = std::multimap; using iterator = map_type::iterator; using const_iterator = map_type::const_iterator; @@ -145,9 +150,12 @@ namespace build2 erase (iterator i) {map_.erase (i);} value - call (const string& name, vector_view args, const location& l) const + call (const scope& base, + const string& name, + vector_view args, + const location& l) const { - return call (name, args, l, true).first; + return call (base, name, args, l, true).first; } // As above but do not fail if no match was found (but still do if the @@ -156,11 +164,12 @@ namespace build2 // functions. // pair - try_call (const string& name, + try_call (const scope& base, + const string& name, vector_view args, const location& l) const { - return call (name, args, l, false); + return call (base, name, args, l, false); } iterator @@ -175,9 +184,20 @@ namespace build2 const_iterator end () const {return map_.end ();} + // Return true if the function with this name is already defined. If the + // name ends with '.', then instead check if any function with this prefix + // (which we call a family) is already defined. + // + bool + defined (const string&) const; + private: pair - call (const string&, vector_view, const location&, bool fail) const; + call (const scope&, + const string&, + vector_view, + const location&, + bool fail) const; map_type map_; }; @@ -195,7 +215,7 @@ namespace build2 // exceptions), you would normally call the default implementation. // static value - default_thunk (vector_view, const function_overload&); + default_thunk (const scope&, vector_view, const function_overload&); // A function family uses a common qualification (though you can pass // empty string to supress it). For an unqualified name (doesn't not @@ -212,6 +232,13 @@ namespace build2 entry operator[] (string name) const; + static bool + defined (string qual) + { + qual += '.'; + return functions.defined (qual); + } + private: const string qual_; function_impl* thunk_; @@ -392,12 +419,12 @@ namespace build2 // struct data { - value (*const thunk) (vector_view, const void*); + value (*const thunk) (const scope&, vector_view, const void*); R (*const impl) (A...); }; static value - thunk (vector_view args, const void* d) + thunk (const scope&, vector_view args, const void* d) { return thunk (move (args), static_cast (d)->impl, @@ -417,6 +444,39 @@ namespace build2 } }; + // Specialization for functions that expect the current scope as a first + // argument. + // + template + struct function_cast + { + struct data + { + value (*const thunk) (const scope&, vector_view, const void*); + R (*const impl) (const scope&, A...); + }; + + static value + thunk (const scope& base, vector_view args, const void* d) + { + return thunk (base, move (args), + static_cast (d)->impl, + std::index_sequence_for ()); + } + + template + static value + thunk (const scope& base, vector_view args, + R (*impl) (const scope&, A...), + std::index_sequence) + { + return value ( + impl (base, + function_arg::cast ( + i < args.size () ? &args[i] : nullptr)...)); + } + }; + // Specialization for void return type. In this case we return NULL value. // template @@ -424,12 +484,12 @@ namespace build2 { struct data { - value (*const thunk) (vector_view, const void*); + value (*const thunk) (const scope&, vector_view, const void*); void (*const impl) (A...); }; static value - thunk (vector_view args, const void* d) + thunk (const scope&, vector_view args, const void* d) { thunk (move (args), static_cast (d)->impl, @@ -447,6 +507,35 @@ namespace build2 } }; + template + struct function_cast + { + struct data + { + value (*const thunk) (const scope&, vector_view, const void*); + void (*const impl) (const scope&, A...); + }; + + static value + thunk (const scope& base, vector_view args, const void* d) + { + thunk (base, move (args), + static_cast (d)->impl, + std::index_sequence_for ()); + return value (nullptr); + } + + template + static void + thunk (const scope& base, vector_view args, + void (*impl) (const scope&, A...), + std::index_sequence) + { + impl (base, + function_arg::cast (i < args.size () ? &args[i] : nullptr)...); + } + }; + // Customization for coerced lambdas (see below). // #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6 @@ -455,12 +544,12 @@ namespace build2 { struct data { - value (*const thunk) (vector_view, const void*); + value (*const thunk) (const scope&, vector_view, const void*); R (L::*const impl) (A...) const; }; static value - thunk (vector_view args, const void* d) + thunk (const scope&, vector_view args, const void* d) { return thunk (move (args), static_cast (d)->impl, @@ -482,17 +571,49 @@ namespace build2 } }; + template + struct function_cast_lamb + { + struct data + { + value (*const thunk) (const scope&, vector_view, const void*); + R (L::*const impl) (const scope&, A...) const; + }; + + static value + thunk (const scope& base, vector_view args, const void* d) + { + return thunk (base, move (args), + static_cast (d)->impl, + std::index_sequence_for ()); + } + + template + static value + thunk (const scope& base, vector_view args, + R (L::*impl) (const scope&, A...) const, + std::index_sequence) + { + const L* l (nullptr); // Undefined behavior. + + return value ( + (l->*impl) (base, + function_arg::cast ( + i < args.size () ? &args[i] : nullptr)...)); + } + }; + template struct function_cast_lamb { struct data { - value (*const thunk) (vector_view, const void*); + value (*const thunk) (const scope&, vector_view, const void*); void (L::*const impl) (A...) const; }; static value - thunk (vector_view args, const void* d) + thunk (const scope&, vector_view args, const void* d) { thunk (move (args), static_cast (d)->impl, @@ -512,6 +633,37 @@ namespace build2 i < args.size () ? &args[i] : nullptr)...); } }; + + template + struct function_cast_lamb + { + struct data + { + value (*const thunk) (const scope&, vector_view, const void*); + void (L::*const impl) (const scope&, A...) const; + }; + + static value + thunk (const scope& base, vector_view args, const void* d) + { + thunk (base, move (args), + static_cast (d)->impl, + std::index_sequence_for ()); + return value (nullptr); + } + + template + static void + thunk (const scope& base, vector_view args, + void (L::*impl) (const scope&, A...) const, + std::index_sequence) + { + const L* l (nullptr); + (l->*impl) (base, + function_arg::cast ( + i < args.size () ? &args[i] : nullptr)...); + } + }; #endif // Customization for member functions. @@ -521,12 +673,12 @@ namespace build2 { struct data { - value (*const thunk) (vector_view, const void*); + value (*const thunk) (const scope&, vector_view, const void*); R (T::*const impl) () const; }; static value - thunk (vector_view args, const void* d) + thunk (const scope&, vector_view args, const void* d) { auto mf (static_cast (d)->impl); return value ((function_arg::cast (&args[0]).*mf) ()); @@ -538,12 +690,12 @@ namespace build2 { struct data { - value (*const thunk) (vector_view, const void*); + value (*const thunk) (const scope&, vector_view, const void*); void (T::*const impl) () const; }; static value - thunk (vector_view args, const void* d) + thunk (const scope&, vector_view args, const void* d) { auto mf (static_cast (d)->impl); (function_arg::cast (args[0]).*mf) (); @@ -558,12 +710,12 @@ namespace build2 { struct data { - value (*const thunk) (vector_view, const void*); + value (*const thunk) (const scope&, vector_view, const void*); R T::*const impl; }; static value - thunk (vector_view args, const void* d) + thunk (const scope&, vector_view args, const void* d) { auto dm (static_cast (d)->impl); return value (move (function_arg::cast (&args[0]).*dm)); @@ -593,6 +745,23 @@ namespace build2 typename cast::data {&cast::thunk, impl})); } + template + void + operator= (R (*impl) (const scope&, A...)) && + { + using args = function_args; + using cast = function_cast; + + insert (move (name), + function_overload ( + nullptr, + args::min, + args::max, + function_overload::types (args::types, args::max), + thunk, + typename cast::data {&cast::thunk, impl})); + } + // Support for assigning a (capture-less) lambda. // // GCC up until version 6 has a bug (#62052) that is triggered by calling @@ -625,6 +794,23 @@ namespace build2 thunk, typename cast::data {&cast::thunk, op})); } + + template + void + coerce_lambda (R (L::*op) (const scope&, A...) const) && + { + using args = function_args; + using cast = function_cast_lamb; + + insert (move (name), + function_overload ( + nullptr, + args::min, + args::max, + function_overload::types (args::types, args::max), + thunk, + typename cast::data {&cast::thunk, op})); + } #else template void diff --git a/build2/functions-path.cxx b/build2/functions-path.cxx index 4542858..45ec8ed 100644 --- a/build2/functions-path.cxx +++ b/build2/functions-path.cxx @@ -10,10 +10,12 @@ using namespace std; namespace build2 { static value - path_thunk (vector_view args, const function_overload& f) + path_thunk (const scope& base, + vector_view args, + const function_overload& f) try { - return function_family::default_thunk (move (args), f); + return function_family::default_thunk (base, move (args), f); } catch (const invalid_path& e) { diff --git a/build2/install/functions.cxx b/build2/install/functions.cxx new file mode 100644 index 0000000..b9298b2 --- /dev/null +++ b/build2/install/functions.cxx @@ -0,0 +1,27 @@ +// file : build2/install/functions.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include + +#include + +using namespace std; + +namespace build2 +{ + namespace install + { + void + functions () + { + function_family f ("install"); + + // Resolve potentially relative install.* value to an absolute directory + // based on (other) install.* values visible from the calling scope. + // + f["resolve"] = &resolve_dir; + } + } +} diff --git a/build2/install/init.cxx b/build2/install/init.cxx index 757d4ef..b5fd007 100644 --- a/build2/install/init.cxx +++ b/build2/install/init.cxx @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -125,12 +126,20 @@ namespace build2 } void + functions (); // functions.cxx + + void boot (scope& r, const location&, unique_ptr&) { tracer trace ("install::boot"); - l5 ([&]{trace << "for " << r.out_path ();}); + // Register install function family if this is the first instance of the + // install modules. + // + if (!function_family::defined ("install")) + functions (); + // Register the install and uninstall operations. // r.operations.insert (install_id, install); diff --git a/build2/parser.cxx b/build2/parser.cxx index 66af0a5..3782230 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -2893,7 +2893,7 @@ namespace build2 })); p = functions.try_call ( - "builtin.concat", vector_view (a), loc); + *scope_, "builtin.concat", vector_view (a), loc); } if (!p.second) @@ -3398,7 +3398,7 @@ namespace build2 // Note that we "move" args to call(). // - result_data = functions.call (name, args, loc); + result_data = functions.call (*scope_, name, args, loc); what = "function call"; } else @@ -3506,7 +3506,7 @@ namespace build2 })); p = functions.try_call ( - "string", vector_view (&result_data, 1), loc); + *scope_, "string", vector_view (&result_data, 1), loc); } if (!p.second) diff --git a/build2/version/rule.cxx b/build2/version/rule.cxx index a9d62ba..ceeb003 100644 --- a/build2/version/rule.cxx +++ b/build2/version/rule.cxx @@ -281,7 +281,7 @@ namespace build2 // Perform substitutions for the project itself (normally the version.* // variables but we allow anything set on the root scope). // - auto subst_self = [&rs] (const location& l, const string& s) + auto subst_self = [&rs, &t] (const location& l, const string& s) { if (lookup x = rs.vars[s]) { @@ -291,7 +291,7 @@ namespace build2 return convert ( functions.call ( - "string", vector_view (&v, 1), l)); + t.base_scope (), "string", vector_view (&v, 1), l)); } else fail (l) << "undefined project variable '" << s << "'" << endf; diff --git a/tests/function/install/buildfile b/tests/function/install/buildfile new file mode 100644 index 0000000..356ca77 --- /dev/null +++ b/tests/function/install/buildfile @@ -0,0 +1,5 @@ +# file : tests/function/install/buildfile +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +./: test{testscript} $b diff --git a/tests/function/install/testscript b/tests/function/install/testscript new file mode 100644 index 0000000..b0a0172 --- /dev/null +++ b/tests/function/install/testscript @@ -0,0 +1,33 @@ +# file : tests/function/install/testscript +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +.include ../../common.test + ++cat <+build/bootstrap.build +using config +using install +EOI + +root = ($cxx.target.class != 'windows' ? '/usr/' : 'C:\') + ++cat <<"EOI" >=build/config.build +config.version = 1 +config.install.root = $root +EOI + +: realize +: +{ + : existing + : + a = ($cxx.target.class != 'windows' ? '/usr/lib/a/' : 'C:\lib\a\'); + $* <'print $install.resolve([dir_path] lib/a)' >$a + + : non-existing + : + $* <'print $install.resolve([dir_path] foo/a)' 2>>EOE != 0 + error: unknown installation directory name 'foo' + info: did you forget to specify config.install.foo? + EOE +} diff --git a/unit-tests/function/driver.cxx b/unit-tests/function/driver.cxx index 4650a18..9286a48 100644 --- a/unit-tests/function/driver.cxx +++ b/unit-tests/function/driver.cxx @@ -22,6 +22,17 @@ namespace build2 &value_traits::value_type }; + static dir_path + scoped (const scope&, dir_path d) + { + return d; + } + + static void + scoped_void (const scope&, dir_path) + { + } + int main (int, char* argv[]) { @@ -43,6 +54,11 @@ namespace build2 f["ambig"] = [](names a, optional) {return a;}; f["ambig"] = [](names a, optional) {return a;}; + f["scoped"] = [](const scope&, names a) {return a;}; + f["scoped_void"] = [](const scope&, names) {}; + f["scoped"] = &scoped; + f["scoped_void"] = &scoped_void; + f[".qual"] = []() {return "abc";}; f[".length"] = &path::size; // Member function. @@ -60,7 +76,7 @@ namespace build2 1, function_overload::arg_variadic, function_overload::types (arg_bool, 1), - [] (vector_view args, const function_overload&) + [] (const scope&, vector_view args, const function_overload&) { return value (static_cast (args.size ())); })); @@ -74,7 +90,7 @@ namespace build2 0, function_overload::arg_variadic, function_overload::types (), - [] (vector_view args, const function_overload&) + [] (const scope&, vector_view args, const function_overload&) { for (value& a: args) { -- cgit v1.1