aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-09-08 16:16:11 +0200
committerKaren Arutyunov <karen@codesynthesis.com>2017-09-11 13:57:29 +0300
commitffa0839de796fbefc48bacc4777648ff19b3fee6 (patch)
tree0f493314df633b6f35f9b9db78c7163506c8eed1
parent66e516ad81225b888469b24e726095533c4f9c4c (diff)
Add ability to pass scope to buildfile functions, add $install.resolve()
-rw-r--r--build2/function.cxx36
-rw-r--r--build2/function.hxx232
-rw-r--r--build2/functions-path.cxx6
-rw-r--r--build2/install/functions.cxx27
-rw-r--r--build2/install/init.cxx11
-rw-r--r--build2/parser.cxx6
-rw-r--r--build2/version/rule.cxx4
-rw-r--r--tests/function/install/buildfile5
-rw-r--r--tests/function/install/testscript33
-rw-r--r--unit-tests/function/driver.cxx20
10 files changed, 342 insertions, 38 deletions
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<value, bool> function_map::
- call (const string& name,
+ call (const scope& base,
+ const string& name,
vector_view<value> 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<value> args, const function_overload& f)
+ default_thunk (const scope& base,
+ vector_view<value> args,
+ const function_overload& f)
try
{
// Call the cast thunk.
//
struct cast_data // Prefix of function_cast::data.
{
- value (*const thunk) (vector_view<value>, const void*);
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
};
auto d (reinterpret_cast<const cast_data*> (&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 <map>
#include <utility> // index_sequence
#include <type_traits> // aligned_storage
-#include <unordered_map>
#include <build2/types.hxx>
#include <build2/utility.hxx>
@@ -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<value>, const function_overload&);
+ using function_impl = value (const scope&,
+ vector_view<value>,
+ const function_overload&);
struct function_overload
{
@@ -134,7 +139,7 @@ namespace build2
class function_map
{
public:
- using map_type = std::unordered_multimap<string, function_overload>;
+ using map_type = std::multimap<string, function_overload>;
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<value> args, const location& l) const
+ call (const scope& base,
+ const string& name,
+ vector_view<value> 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<value, bool>
- try_call (const string& name,
+ try_call (const scope& base,
+ const string& name,
vector_view<value> 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<value, bool>
- call (const string&, vector_view<value>, const location&, bool fail) const;
+ call (const scope&,
+ const string&,
+ vector_view<value>,
+ 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<value>, const function_overload&);
+ default_thunk (const scope&, vector_view<value>, 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<value>, const void*);
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
R (*const impl) (A...);
};
static value
- thunk (vector_view<value> args, const void* d)
+ thunk (const scope&, vector_view<value> args, const void* d)
{
return thunk (move (args),
static_cast<const data*> (d)->impl,
@@ -417,6 +444,39 @@ namespace build2
}
};
+ // Specialization for functions that expect the current scope as a first
+ // argument.
+ //
+ template <typename R, typename... A>
+ struct function_cast<R, const scope&, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
+ R (*const impl) (const scope&, A...);
+ };
+
+ static value
+ thunk (const scope& base, vector_view<value> args, const void* d)
+ {
+ return thunk (base, move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ }
+
+ template <size_t... i>
+ static value
+ thunk (const scope& base, vector_view<value> args,
+ R (*impl) (const scope&, A...),
+ std::index_sequence<i...>)
+ {
+ return value (
+ impl (base,
+ function_arg<A>::cast (
+ i < args.size () ? &args[i] : nullptr)...));
+ }
+ };
+
// Specialization for void return type. In this case we return NULL value.
//
template <typename... A>
@@ -424,12 +484,12 @@ namespace build2
{
struct data
{
- value (*const thunk) (vector_view<value>, const void*);
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
void (*const impl) (A...);
};
static value
- thunk (vector_view<value> args, const void* d)
+ thunk (const scope&, vector_view<value> args, const void* d)
{
thunk (move (args),
static_cast<const data*> (d)->impl,
@@ -447,6 +507,35 @@ namespace build2
}
};
+ template <typename... A>
+ struct function_cast<void, const scope&, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
+ void (*const impl) (const scope&, A...);
+ };
+
+ static value
+ thunk (const scope& base, vector_view<value> args, const void* d)
+ {
+ thunk (base, move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ return value (nullptr);
+ }
+
+ template <size_t... i>
+ static void
+ thunk (const scope& base, vector_view<value> args,
+ void (*impl) (const scope&, A...),
+ std::index_sequence<i...>)
+ {
+ impl (base,
+ function_arg<A>::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<value>, const void*);
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
R (L::*const impl) (A...) const;
};
static value
- thunk (vector_view<value> args, const void* d)
+ thunk (const scope&, vector_view<value> args, const void* d)
{
return thunk (move (args),
static_cast<const data*> (d)->impl,
@@ -482,17 +571,49 @@ namespace build2
}
};
+ template <typename L, typename R, typename... A>
+ struct function_cast_lamb<L, R, const scope&, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
+ R (L::*const impl) (const scope&, A...) const;
+ };
+
+ static value
+ thunk (const scope& base, vector_view<value> args, const void* d)
+ {
+ return thunk (base, move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ }
+
+ template <size_t... i>
+ static value
+ thunk (const scope& base, vector_view<value> args,
+ R (L::*impl) (const scope&, A...) const,
+ std::index_sequence<i...>)
+ {
+ const L* l (nullptr); // Undefined behavior.
+
+ return value (
+ (l->*impl) (base,
+ function_arg<A>::cast (
+ i < args.size () ? &args[i] : nullptr)...));
+ }
+ };
+
template <typename L, typename... A>
struct function_cast_lamb<L, void, A...>
{
struct data
{
- value (*const thunk) (vector_view<value>, const void*);
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
void (L::*const impl) (A...) const;
};
static value
- thunk (vector_view<value> args, const void* d)
+ thunk (const scope&, vector_view<value> args, const void* d)
{
thunk (move (args),
static_cast<const data*> (d)->impl,
@@ -512,6 +633,37 @@ namespace build2
i < args.size () ? &args[i] : nullptr)...);
}
};
+
+ template <typename L, typename... A>
+ struct function_cast_lamb<L, void, const scope&, A...>
+ {
+ struct data
+ {
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
+ void (L::*const impl) (const scope&, A...) const;
+ };
+
+ static value
+ thunk (const scope& base, vector_view<value> args, const void* d)
+ {
+ thunk (base, move (args),
+ static_cast<const data*> (d)->impl,
+ std::index_sequence_for<A...> ());
+ return value (nullptr);
+ }
+
+ template <size_t... i>
+ static void
+ thunk (const scope& base, vector_view<value> args,
+ void (L::*impl) (const scope&, A...) const,
+ std::index_sequence<i...>)
+ {
+ const L* l (nullptr);
+ (l->*impl) (base,
+ function_arg<A>::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<value>, const void*);
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
R (T::*const impl) () const;
};
static value
- thunk (vector_view<value> args, const void* d)
+ thunk (const scope&, vector_view<value> args, const void* d)
{
auto mf (static_cast<const data*> (d)->impl);
return value ((function_arg<T>::cast (&args[0]).*mf) ());
@@ -538,12 +690,12 @@ namespace build2
{
struct data
{
- value (*const thunk) (vector_view<value>, const void*);
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
void (T::*const impl) () const;
};
static value
- thunk (vector_view<value> args, const void* d)
+ thunk (const scope&, vector_view<value> args, const void* d)
{
auto mf (static_cast<const data*> (d)->impl);
(function_arg<T>::cast (args[0]).*mf) ();
@@ -558,12 +710,12 @@ namespace build2
{
struct data
{
- value (*const thunk) (vector_view<value>, const void*);
+ value (*const thunk) (const scope&, vector_view<value>, const void*);
R T::*const impl;
};
static value
- thunk (vector_view<value> args, const void* d)
+ thunk (const scope&, vector_view<value> args, const void* d)
{
auto dm (static_cast<const data*> (d)->impl);
return value (move (function_arg<T>::cast (&args[0]).*dm));
@@ -593,6 +745,23 @@ namespace build2
typename cast::data {&cast::thunk, impl}));
}
+ template <typename R, typename... A>
+ void
+ operator= (R (*impl) (const scope&, A...)) &&
+ {
+ using args = function_args<A...>;
+ using cast = function_cast<R, const scope&, A...>;
+
+ 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 <typename L, typename R, typename... A>
+ void
+ coerce_lambda (R (L::*op) (const scope&, A...) const) &&
+ {
+ using args = function_args<A...>;
+ using cast = function_cast_lamb<L, R, const scope&, A...>;
+
+ 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 <typename L>
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<value> args, const function_overload& f)
+ path_thunk (const scope& base,
+ vector_view<value> 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 <build2/function.hxx>
+#include <build2/variable.hxx>
+
+#include <build2/install/utility.hxx>
+
+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 <build2/scope.hxx>
#include <build2/target.hxx>
#include <build2/rule.hxx>
+#include <build2/function.hxx>
#include <build2/operation.hxx>
#include <build2/diagnostics.hxx>
@@ -125,12 +126,20 @@ namespace build2
}
void
+ functions (); // functions.cxx
+
+ void
boot (scope& r, const location&, unique_ptr<module_base>&)
{
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<value> (a), loc);
+ *scope_, "builtin.concat", vector_view<value> (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<value> (&result_data, 1), loc);
+ *scope_, "string", vector_view<value> (&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<string> (
functions.call (
- "string", vector_view<value> (&v, 1), l));
+ t.base_scope (), "string", vector_view<value> (&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 <<EOI >+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<bool>::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<string>) {return a;};
f["ambig"] = [](names a, optional<uint64_t>) {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<value> args, const function_overload&)
+ [] (const scope&, vector_view<value> args, const function_overload&)
{
return value (static_cast<uint64_t> (args.size ()));
}));
@@ -74,7 +90,7 @@ namespace build2
0,
function_overload::arg_variadic,
function_overload::types (),
- [] (vector_view<value> args, const function_overload&)
+ [] (const scope&, vector_view<value> args, const function_overload&)
{
for (value& a: args)
{