aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2020-06-16 17:08:39 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2020-06-18 14:18:59 +0300
commit728b075cb5e0df9c386f8377e0f6961e5ccc5143 (patch)
tree8bd60cb16a260031d46b5d79adab5821dac3dd27
parent448747903956f70f85f5135a224bbbae7f7b276a (diff)
Add env script pseudo-builtin
Also disable C++ recipe tests when cross-testing.
-rw-r--r--doc/testscript.cli30
-rw-r--r--libbuild2/build/script/parser+diag.test.testscript36
-rw-r--r--libbuild2/build/script/parser.cxx6
-rw-r--r--libbuild2/build/script/parser.hxx9
-rw-r--r--libbuild2/script/parser.cxx162
-rw-r--r--libbuild2/script/parser.hxx14
-rw-r--r--libbuild2/script/run.cxx11
-rw-r--r--libbuild2/script/script.cxx130
-rw-r--r--libbuild2/script/script.hxx8
-rw-r--r--libbuild2/test/script/parser+env.test.testscript77
-rw-r--r--tests/build/root.build1
-rw-r--r--tests/recipe/cxx/testscript269
-rw-r--r--tests/test/script/runner/driver.cxx25
-rw-r--r--tests/test/script/runner/env.testscript7
14 files changed, 618 insertions, 167 deletions
diff --git a/doc/testscript.cli b/doc/testscript.cli
index c0f0d4e..49725df 100644
--- a/doc/testscript.cli
+++ b/doc/testscript.cli
@@ -2426,6 +2426,34 @@ Write strings to \c{stdout} separating them with a single space and ending
with a newline.
+\h#builtins-env|\c{env}|
+
+\
+env [-u <name>]... [-] [<name>=<value>]... -- <cmd>
+\
+
+Run a command adding/removing the variables to/from the environment.
+
+Note that \c{env} is a \i{pseudo-builtin}. In particular, its name and the
+\c{--} separator must be specified \i{literally} on the command line.
+Specifically, they must not be the result of an expansion. Also note that the
+\c{--} separator must always be present.
+
+To avoid ambiguity, the variable assignments can be separated from the options
+with the explicit \c{-} separator. In the example below the \c{--unset}
+variable is added to the environment:
+
+\
+env - --unset=FOO -- $*
+\
+
+\dl|
+
+\li|\n\c{-u|--unset <name>}
+
+ Remove the specified variable from the environment.||
+
+
\h#builtins-exit|\c{exit}|
\
@@ -2643,7 +2671,7 @@ is ECMAScript (more specifically, ECMA-262-based C++11 regular expressions).
Edit \i{file} in place.|
-\li|\n\c{-e|--expression <script>}\n
+\li|\n\c{-e|--expression <script>}
Editing commands to be executed (required).||
diff --git a/libbuild2/build/script/parser+diag.test.testscript b/libbuild2/build/script/parser+diag.test.testscript
index 60683bc..5b4e64a 100644
--- a/libbuild2/build/script/parser+diag.test.testscript
+++ b/libbuild2/build/script/parser+diag.test.testscript
@@ -55,3 +55,39 @@ $* <<EOI >>~%EOO%
buildfile:12:1: info: previous call is here
EOE
}
+
+: inside-if
+:
+$* <<EOI 2>>EOE != 0
+ if true
+ diag copy >= $>
+ fi
+ EOI
+ buildfile:12:3: error: 'diag' call inside flow control construct
+ EOE
+
+: inside-if-cond
+:
+$* <<EOI 2>>EOE != 0
+ if diag copy >= $>
+ true
+ fi
+ EOI
+ buildfile:11:4: error: 'diag' call inside flow control construct
+ EOE
+
+: second-command
+:
+$* <<EOI 2>>EOE != 0
+ true && diag copy >= $>
+ EOI
+ buildfile:11:9: error: 'diag' call must be the only command
+ EOE
+
+: via-env
+:
+$* <<EOI 2>>EOE != 0
+ env -- diag copy >= $>
+ EOI
+ buildfile:11:8: error: 'diag' call via 'env' builtin
+ EOE
diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx
index 2c41ac1..8f2c46d 100644
--- a/libbuild2/build/script/parser.cxx
+++ b/libbuild2/build/script/parser.cxx
@@ -330,6 +330,7 @@ namespace build2
optional<process_path> parser::
parse_program (token& t, build2::script::token_type& tt,
bool first,
+ bool env,
names& ns)
{
const location l (get_location (t));
@@ -369,13 +370,16 @@ namespace build2
// Verify that the special builtin is not called inside an improper
// context (flow control construct or complex expression).
//
- auto verify = [first, &v, &l, this] ()
+ auto verify = [first, env, &v, &l, this] ()
{
if (level_ != 0)
fail (l) << "'" << v << "' call inside flow control construct";
if (!first)
fail (l) << "'" << v << "' call must be the only command";
+
+ if (env)
+ fail (l) << "'" << v << "' call via 'env' builtin";
};
if (v == "diag")
diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx
index 73bcd09..5ada8be 100644
--- a/libbuild2/build/script/parser.hxx
+++ b/libbuild2/build/script/parser.hxx
@@ -98,13 +98,16 @@ namespace build2
//
// During pre-parsing try to deduce the low-verbosity script
// diagnostics name as a program/builtin name or obtain the custom
- // low-verbosity diagnostics specified with the diag builtin. Note
- // that the diag builtin can only appear at the beginning of the
- // command line.
+ // low-verbosity diagnostics specified with the diag builtin. Also
+ // handle the depdb builtin calls.
+ //
+ // Note that the diag and depdb builtins can only appear at the
+ // beginning of the command line.
//
virtual optional<process_path>
parse_program (token&, build2::script::token_type&,
bool first,
+ bool env,
names&) override;
protected:
diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx
index a00651c..d5cff1a 100644
--- a/libbuild2/script/parser.cxx
+++ b/libbuild2/script/parser.cxx
@@ -97,7 +97,7 @@ namespace build2
}
optional<process_path> parser::
- parse_program (token& t, type& tt, bool, names& ns)
+ parse_program (token& t, type& tt, bool, bool, names& ns)
{
parse_names (t, tt,
ns,
@@ -1019,6 +1019,18 @@ namespace build2
}
}
+ bool prog (p == pending::program_first ||
+ p == pending::program_next);
+
+ // Check if this is the env pseudo-builtin.
+ //
+ bool env (false);
+ if (prog && tt == type::word && t.value == "env")
+ {
+ c.variables = parse_env_builtin (t, tt);
+ env = true;
+ }
+
// Parse the next chunk as names to get expansion, etc. Note that
// we do it in the chunking mode to detect whether anything in
// each chunk is quoted. If we are waiting for the command
@@ -1033,10 +1045,10 @@ namespace build2
//
reset_quoted (t);
- if (p == pending::program_first || p == pending::program_next)
+ if (prog)
{
optional<process_path> pp (
- parse_program (t, tt, p == pending::program_first, ns));
+ parse_program (t, tt, p == pending::program_first, env, ns));
// During pre-parsing we are not interested in the
// parse_program() call result, so just discard the potentially
@@ -1088,7 +1100,7 @@ namespace build2
{
diag_record dr (fail (l));
dr << "invalid string value ";
- to_stream (dr.os, n, true); // Quote.
+ to_stream (dr.os, n, true /* quote */);
}
// If it is a quoted chunk, then we add the word as is.
@@ -1275,6 +1287,146 @@ namespace build2
return make_pair (move (expr), move (hd));
}
+ environment_vars parser::
+ parse_env_builtin (token& t, token_type& tt)
+ {
+ // enter: 'env' word token
+ // leave: first token of the program name
+
+ next (t, tt); // Skip 'env'.
+
+ // Note that the -u option and its value can belong to the different
+ // name chunks. That's why we parse the env builtin arguments in the
+ // chunking mode into the argument/location pair list up to the '--'
+ // separator and parse this list into the variable sets/unsets
+ // afterwords.
+ //
+ // Align the size with environment_vars (double because of -u <var>
+ // which is two arguments).
+ //
+ using args = small_vector<pair<string, location>, 4>;
+
+ args as;
+ names ns; // Reuse to reduce allocations.
+ while (tt != type::word || t.value != "--")
+ {
+ location l (get_location (t));
+
+ if (!start_names (tt))
+ fail (l) << "env: expected option, variable, or '--' separator "
+ << "instead of " << t;
+
+ parse_names (t, tt,
+ ns,
+ pattern_mode::ignore,
+ true /* chunk */,
+ "env builtin argument",
+ nullptr);
+
+ if (pre_parse_)
+ continue;
+
+ for (name& n: ns)
+ {
+ try
+ {
+ as.emplace_back (
+ value_traits<string>::convert (move (n), nullptr), l);
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail (l));
+ dr << "invalid string value ";
+ to_stream (dr.os, n, true /* quote */);
+ }
+ }
+
+ ns.clear ();
+ }
+
+ location l (get_location (t)); // '--' location.
+ next (t, tt); // Skip '--'.
+
+ if (tt == type::newline || tt == type::eos)
+ fail (t) << "env: expected program name instead of " << t;
+
+ // Parse the env builtin options and arguments.
+ //
+ environment_vars r;
+
+ // Note: args is empty in the pre-parse mode.
+ //
+ auto i (as.begin ()), e (as.end ());
+
+ // Parse the variable unsets (from options).
+ //
+ for (; i != e; ++i)
+ {
+ string& o (i->first);
+
+ // Bail out if the options and arguments separator is encountered.
+ //
+ if (o == "-")
+ {
+ ++i;
+ break;
+ }
+
+ // Unset the variable, adding its name to the resulting variable list.
+ //
+ auto unset = [&r, &i, this] (string&& v, const char* o)
+ {
+ if (v.empty ())
+ fail (i->second) << "env: empty value for option '" << o << "'";
+
+ if (v.find ('=') != string::npos)
+ fail (i->second) << "env: invalid value '" << v << "' for "
+ << "option '" << o << "': contains '='";
+
+ r.push_back (move (v));
+ };
+
+ // If this is the --unset|-u option then add the variable unset and
+ // bail out to parsing the variable sets otherwise.
+ //
+ if (o == "--unset" || o == "-u")
+ {
+ if (++i == e)
+ fail (l) << "env: missing value for option '" << o << "'";
+
+ unset (move (i->first), o.c_str ());
+ }
+ else if (o.compare (0, 8, "--unset=") == 0)
+ unset (string (o, 8), "--unset");
+ else
+ break;
+ }
+
+ // Parse the variable sets (from arguments).
+ //
+ for (; i != e; ++i)
+ {
+ string& a (i->first);
+
+ // Validate the variable assignment.
+ //
+ size_t p (a.find ('='));
+
+ if (p == string::npos)
+ fail (i->second)
+ << "env: expected variable assignment instead of '" << a << "'";
+
+ if (p == 0)
+ fail (i->second) << "env: empty variable name";
+
+ // Add the variable set to the resulting list.
+ //
+ r.push_back (move (a));
+ }
+
+ return r;
+ }
+
command_exit parser::
parse_command_exit (token& t, type& tt)
{
@@ -1310,7 +1462,7 @@ namespace build2
diag_record dr;
dr << fail (l) << "expected exit status instead of ";
- to_stream (dr.os, ns, true); // Quote.
+ to_stream (dr.os, ns, true /* quote */);
dr << info << "exit status is an unsigned integer less than 256";
}
diff --git a/libbuild2/script/parser.hxx b/libbuild2/script/parser.hxx
index bec6867..da69591 100644
--- a/libbuild2/script/parser.hxx
+++ b/libbuild2/script/parser.hxx
@@ -129,6 +129,15 @@ namespace build2
line_type
pre_parse_line_start (token&, token_type&, lexer_mode);
+ // Parse the env pseudo-builtin arguments up to the program name. Return
+ // the list of the variables that should be unset ("name") and/or set
+ // ("name=value") in the command environment and the token/type that
+ // starts the program name. Note that the variable unsets come first, if
+ // present.
+ //
+ environment_vars
+ parse_env_builtin (token&, token_type&);
+
// Execute.
//
protected:
@@ -166,7 +175,8 @@ namespace build2
//
protected:
// Parse the command's leading name chunk. The argument first is true if
- // this is the first command in the line.
+ // this is the first command in the line. The argument env is true if
+ // the command is executed via the env pseudo-builtin.
//
// During the execution phase try to parse and translate the leading
// names into the process path and return nullopt if choose not to do
@@ -189,7 +199,7 @@ namespace build2
// recognize and execute certain directives, or some such.
//
virtual optional<process_path>
- parse_program (token&, token_type&, bool first, names&);
+ parse_program (token&, token_type&, bool first, bool env, names&);
// Set lexer pointers for both the current and the base classes.
//
diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx
index 46c061c..b90ba48 100644
--- a/libbuild2/script/run.cxx
+++ b/libbuild2/script/run.cxx
@@ -1619,16 +1619,19 @@ namespace build2
? process::path_search (args[0])
: process_path ());
- // Note: the builtin-escaping character '^' is not printed.
+ // Note that CWD and builtin-escaping character '^' are not printed.
//
+ process_env pe (resolve ? pp : c.program, c.variables);
+
if (verb >= 2)
- print_process (args);
+ print_process (pe, args);
process pr (
- resolve ? pp : c.program,
+ *pe.path,
args.data (),
{ifd.get (), -1}, process::pipe (ofd), {-1, efd.get ()},
- env.work_dir.path->string ().c_str ());
+ env.work_dir.path->string ().c_str (),
+ pe.vars);
ifd.reset ();
ofd.out.reset ();
diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx
index d0d3304..ee238cc 100644
--- a/libbuild2/script/script.cxx
+++ b/libbuild2/script/script.cxx
@@ -229,22 +229,57 @@ namespace build2
}
}
- // Quote if empty or contains spaces or any of the special characters.
- // Note that we use single quotes since double quotes still allow
- // expansion.
+ // Quote a string unconditionally, assuming it contains some special
+ // characters.
//
- // @@ What if it contains single quotes?
+ // If the quote character is present in the string then it is double
+ // quoted rather than single quoted. In this case the following characters
+ // are escaped:
+ //
+ // \"
+ //
+ static void
+ to_stream_quoted (ostream& o, const char* s)
+ {
+ if (strchr (s, '\'') != nullptr)
+ {
+ o << '"';
+
+ for (; *s != '\0'; ++s)
+ {
+ // Escape characters special inside double quotes.
+ //
+ if (strchr ("\\\"", *s) != nullptr)
+ o << '\\';
+
+ o << *s;
+ }
+
+ o << '"';
+ }
+ else
+ o << '\'' << s << '\'';
+ }
+
+ static inline void
+ to_stream_quoted (ostream& o, const string& s)
+ {
+ to_stream_quoted (o, s.c_str ());
+ }
+
+ // Quote if empty or contains spaces or any of the command line special
+ // characters.
//
static void
to_stream_q (ostream& o, const string& s)
{
// NOTE: update dump(line) if adding any new special character.
//
- if (s.empty () || s.find_first_of (" |&<>=\\\"") != string::npos)
- o << '\'' << s << '\'';
+ if (s.empty () || s.find_first_of (" |&<>=\\\"'") != string::npos)
+ to_stream_quoted (o, s);
else
o << s;
- };
+ }
void
to_stream (ostream& o, const command& c, command_to_stream m)
@@ -373,6 +408,87 @@ namespace build2
if ((m & command_to_stream::header) == command_to_stream::header)
{
+ // Print the env builtin arguments, if any environment variable
+ // (un)sets are present.
+ //
+ if (!c.variables.empty ())
+ {
+ o << "env";
+
+ auto b (c.variables.begin ()), i (b), e (c.variables.end ());
+
+ // Print a variable name or assignment to the stream, quoting it if
+ // necessary.
+ //
+ auto print = [&o] (const string& v, bool name)
+ {
+ size_t p (v.find_first_of (" \\\"'"));
+
+ // Print the variable name/assignment as is if it doesn't contain
+ // any special characters.
+ //
+ if (p == string::npos)
+ {
+ o << v;
+ return;
+ }
+
+ // If the variable name contains any special characters, then
+ // quote the name/assignment as a whole.
+ //
+ size_t eq;
+ if (name || (eq = v.find ('=')) > p)
+ {
+ to_stream_quoted (o, v);
+ return;
+ }
+
+ // Finally, if the variable value contains any special characters,
+ // then we quote only the value.
+ //
+ assert (eq != string::npos);
+
+ o.write (v.c_str (), eq + 1); // Includes '='.
+ to_stream_quoted (o, v.c_str () + eq + 1);
+ };
+
+ // Variable unsets.
+ //
+ // Print the variable unsets as the -u options until a variable set
+ // is encountered (contains '=') or the end of the variable list is
+ // reached. In the former case, to avoid a potential ambiguity add
+ // the '-' separator, if there are any options.
+ //
+ // Note that we rely on the fact that unsets come first, which is
+ // guaranteed by parser::parse_env_builtin().
+ //
+ for (; i != e; ++i)
+ {
+ const string& v (*i);
+
+ if (v.find ('=') == string::npos) // Variable unset.
+ {
+ o << " -u "; print (v, true /* name*/);
+ }
+ else // Variable set.
+ {
+ if (i != b)
+ o << " -";
+
+ break;
+ }
+ }
+
+ // Variable sets.
+ //
+ for (; i != e; ++i)
+ {
+ o << ' '; print (*i, false /* name */);
+ }
+
+ o << " -- ";
+ }
+
// Program.
//
to_stream_q (o, c.program.recall_string ());
diff --git a/libbuild2/script/script.hxx b/libbuild2/script/script.hxx
index d751169..8e1c852 100644
--- a/libbuild2/script/script.hxx
+++ b/libbuild2/script/script.hxx
@@ -298,6 +298,11 @@ namespace build2
// command
//
+ // Align with butl::process_env, assuming it is not very common to (un)set
+ // more than two variables.
+ //
+ using environment_vars = small_vector<string, 2>;
+
struct command
{
// We use NULL initial as an indication that the path stored in recall
@@ -306,7 +311,8 @@ namespace build2
//
process_path program;
- strings arguments;
+ strings arguments;
+ environment_vars variables;
optional<redirect> in;
optional<redirect> out;
diff --git a/libbuild2/test/script/parser+env.test.testscript b/libbuild2/test/script/parser+env.test.testscript
new file mode 100644
index 0000000..b1e864c
--- /dev/null
+++ b/libbuild2/test/script/parser+env.test.testscript
@@ -0,0 +1,77 @@
+# file : libbuild2/test/script/parser+env.test.testscript
+# license : MIT; see accompanying LICENSE file
+
+: unset
+:
+{
+ $* <'env -u a -- cmd' >'env -u a -- cmd' : short-opt
+ $* <'env --unset a -- cmd' >'env -u a -- cmd' : long-opt
+ $* <'env --unset=a -- cmd' >'env -u a -- cmd' : long-opt-eq
+ $* <'env -u a -u b -- cmd' >'env -u a -u b -- cmd' : mult-opt
+ $* <'env -u "a b" -- cmd' >"env -u 'a b' -- cmd" : quote
+
+ : invalid-opt
+ :
+ $* <'env -w a -- cmd' 2>>EOE != 0
+ testscript:1:5: error: env: expected variable assignment instead of '-w'
+ EOE
+
+ : no-val
+ :
+ $* <'env -u -- cmd' 2>>EOE != 0
+ testscript:1:8: error: env: missing value for option '-u'
+ EOE
+
+ : empty-val
+ :
+ $* <'env --unset= -- cmd' 2>>EOE != 0
+ testscript:1:5: error: env: empty value for option '--unset'
+ EOE
+
+ : invalid-val
+ :
+ $* <'env --unset=a=b -- cmd' 2>>EOE != 0
+ testscript:1:5: error: env: invalid value 'a=b' for option '--unset': contains '='
+ EOE
+
+ : no-sep
+ :
+ $* <'env -u a cmd' 2>>EOE != 0
+ testscript:1:13: error: env: expected option, variable, or '--' separator instead of <newline>
+ EOE
+
+ $* <'env && cmd' 2>>EOE != 0
+ testscript:1:5: error: env: expected option, variable, or '--' separator instead of '&&'
+ EOE
+}
+
+: set
+:
+{
+ $* <'env a=b -- cmd' >'env a=b -- cmd' : var
+ $* <'env -u a b=c -- cmd' >'env -u a - b=c -- cmd' : opt-var
+ $* <'env a="b c" -- cmd' >"env a='b c' -- cmd" : quote
+ $* <'env "a b"=c -- cmd' >"env 'a b=c' -- cmd" : quote-name
+
+ : double-quote
+ :
+ $* <<EOF >>EOF
+ env a="'a\"'" -- cmd
+ EOF
+
+ : expected-assign
+ :
+ $* <'env a -- cmd' 2>>EOE != 0
+ testscript:1:5: error: env: expected variable assignment instead of 'a'
+ EOE
+}
+
+: non-first
+:
+$* <'cmd1 && env -u a b=c -- cmd2' >'cmd1 && env -u a - b=c -- cmd2'
+
+: no-cmd
+:
+$* <'env -u a --' 2>>EOE != 0
+ testscript:1:12: error: env: expected program name instead of <newline>
+ EOE
diff --git a/tests/build/root.build b/tests/build/root.build
index e3f0f61..5bf6731 100644
--- a/tests/build/root.build
+++ b/tests/build/root.build
@@ -22,7 +22,6 @@ import.build2 = [null]
import! [metadata] b = build2%exe{b}
static = $($b:b.static) # True if testing statically-linked build system.
-shared = (!$static) # For '$shared || exit' idiom.
testscript{*}: test = $b
diff --git a/tests/recipe/cxx/testscript b/tests/recipe/cxx/testscript
index 6601c0a..6b11c0f 100644
--- a/tests/recipe/cxx/testscript
+++ b/tests/recipe/cxx/testscript
@@ -1,168 +1,169 @@
# file : tests/recipe/cxx/testscript
# license : MIT; see accompanying LICENSE file
-# Ad hoc C++ recipes not supported in a statically-linked build system.
+# Ad hoc C++ recipes not supported in a statically-linked build system. Also
+# disable when cross-testing for the sake of simplicity.
#
-+$shared || exit
-
-+mkdir build
-+cat <<EOI >=build/bootstrap.build
- project = test
- amalgamation =
- subprojects =
-
- using config
- using test
- EOI
-
-+cat <<EOI >=build/root.build
- EOI
-
-+cat <<EOI >=buildfile
- ./:
- {{ c++ 1
- // Dummy recipe to trigger cleanup.
- }}
- EOI
-
-: update-clean
-:
+if (!$static && $test.target == $build.host)
{
- echo 'bar' >=bar;
+ +mkdir build
+ +cat <<EOI >=build/bootstrap.build
+ project = test
+ amalgamation =
+ subprojects =
+
+ using config
+ using test
+ EOI
- cat <<EOI >=buildfile;
- foo: bar
- % update clean
- {{ c++ 1
- recipe
- apply (action a, target& xt) const override
- {
- file& t (xt.as<file> ());
+ +cat <<EOI >=build/root.build
+ EOI
- t.derive_path ();
- inject_fsdir (a, t);
- match_prerequisite_members (a, t);
+ +cat <<EOI >=buildfile
+ ./:
+ {{ c++ 1
+ // Dummy recipe to trigger cleanup.
+ }}
+ EOI
- switch (a)
+ : update-clean
+ :
+ {
+ echo 'bar' >=bar;
+
+ cat <<EOI >=buildfile;
+ foo: bar
+ % update clean
+ {{ c++ 1
+ recipe
+ apply (action a, target& xt) const override
{
- case perform_update_id: return perform_update;
- case perform_clean_id: return perform_clean_depdb;
- default: assert (false); return noop_recipe;
+ file& t (xt.as<file> ());
+
+ t.derive_path ();
+ inject_fsdir (a, t);
+ match_prerequisite_members (a, t);
+
+ switch (a)
+ {
+ case perform_update_id: return perform_update;
+ case perform_clean_id: return perform_clean_depdb;
+ default: assert (false); return noop_recipe;
+ }
}
- }
- static target_state
- perform_update (action a, const target& xt)
- {
- const file& t (xt.as<file> ());
- const path& tp (t.path ());
+ static target_state
+ perform_update (action a, const target& xt)
+ {
+ const file& t (xt.as<file> ());
+ const path& tp (t.path ());
- timestamp mt (t.load_mtime ());
- auto pr (execute_prerequisites<file> (a, t, mt));
+ timestamp mt (t.load_mtime ());
+ auto pr (execute_prerequisites<file> (a, t, mt));
- bool update (!pr.first);
- target_state r (update ? target_state::changed : *pr.first);
+ bool update (!pr.first);
+ target_state r (update ? target_state::changed : *pr.first);
- const file& s (pr.second);
- const path& sp (s.path ());
+ const file& s (pr.second);
+ const path& sp (s.path ());
- depdb dd (tp + ".d");
- dd.expect (sp);
+ depdb dd (tp + ".d");
+ dd.expect (sp);
- if (dd.writing () || dd.mtime > mt)
- update = true;
+ if (dd.writing () || dd.mtime > mt)
+ update = true;
- dd.close ();
+ dd.close ();
- if (!update)
- return r;
+ if (!update)
+ return r;
- if (verb == 1)
- text << "cp " << t;
- else if (verb >= 2)
- text << "cp " << sp << ' ' << tp;
+ if (verb == 1)
+ text << "cp " << t;
+ else if (verb >= 2)
+ text << "cp " << sp << ' ' << tp;
- cpfile (sp, tp);
- return target_state::changed;
- }
- }}
- EOI
+ cpfile (sp, tp);
+ return target_state::changed;
+ }
+ }}
+ EOI
- $* 2>>~%EOE%;
- %^(c\+\+|ld).*%+
- cp file{foo}
- EOE
+ env BDEP_SYNC=0 -- $* 2>>~%EOE%;
+ %^(c\+\+|ld).*%+
+ cp file{foo}
+ EOE
- cat <<<foo >'bar';
+ cat <<<foo >'bar';
- # While at it, make sure there is no rebuild.
- #
- $* 2>/'info: dir{./} is up to date';
+ # While at it, make sure there is no rebuild.
+ #
+ env BDEP_SYNC=0 -- $* 2>/'info: dir{./} is up to date';
- $* clean 2>-
-}
+ env BDEP_SYNC=0 -- $* clean 2>-
+ }
-#\
-@@ TMP disabled: env BDEP_SYNC=0
-: test
-:
-{
- echo 'bar' >=bar;
+ : test
+ :
+ {
+ echo 'bar' >=bar;
- cat <<EOI >=buildfile;
- foo: bar
- {{
- cp $path($<) $path($>)
- }}
- % test
- {{ c++ 1 --
+ cat <<EOI >=buildfile;
+ foo: bar
+ {{
+ cp $path($<) $path($>)
+ }}
+ % test
+ {{ c++ 1 --
- #include <iostream>
+ #include <iostream>
- --
+ --
- recipe
- apply (action a, target& t) const override
- {
- if (a.outer ())
+ recipe
+ apply (action a, target& t) const override
{
- match_inner (a, t);
- return execute_inner;
+ if (a.outer ())
+ {
+ match_inner (a, t);
+ return execute_inner;
+ }
+ else
+ return perform_test;
}
- else
- return perform_test;
- }
-
- static target_state
- perform_test (action, const target& xt)
- {
- const file& t (xt.as<file> ());
- const path& tp (t.path ());
-
- if (verb == 1)
- text << "test " << t;
- else if (verb >= 2)
- text << "cat " << tp;
-
- ifdstream ifs (tp);
- if (ifs.peek () != ifdstream::traits_type::eof ())
- std::cerr << ifs.rdbuf ();
- ifs.close ();
-
- return target_state::changed;
- }
- }}
- EOI
- $* test 2>>~%EOE%;
- %^(c\+\+|ld).*%+
- cp file{foo}
- test file{foo}
- bar
- EOE
+ static target_state
+ perform_test (action, const target& xt)
+ {
+ const file& t (xt.as<file> ());
+ const path& tp (t.path ());
- $* clean 2>-
-}
-#\
+ if (verb == 1)
+ text << "test " << t;
+ else if (verb >= 2)
+ text << "cat " << tp;
+
+ ifdstream ifs (tp);
+ if (ifs.peek () != ifdstream::traits_type::eof ())
+ std::cerr << ifs.rdbuf ();
+ ifs.close ();
--$* clean 2>- # Clean recipe builds.
+ return target_state::changed;
+ }
+ }}
+ EOI
+
+ env BDEP_SYNC=0 -- $* test 2>>~%EOE%;
+ %^(c\+\+|ld).*%+
+ cp file{foo}
+ test file{foo}
+ bar
+ EOE
+
+ env BDEP_SYNC=0 -- $* clean 2>-
+ }
+
+ # Clean recipe builds if the testscript is enabled (see above for details).
+ #
+ -$* clean 2>-
+}
diff --git a/tests/test/script/runner/driver.cxx b/tests/test/script/runner/driver.cxx
index 08d5bba..d5a74a4 100644
--- a/tests/test/script/runner/driver.cxx
+++ b/tests/test/script/runner/driver.cxx
@@ -34,22 +34,22 @@ main (int argc, char* argv[])
using butl::optional;
// Usage: driver [-i <int>] (-o <string>)* (-e <string>)* (-f <file>)*
- // (-d <dir>)* [(-t (a|m|s|z)) | (-s <int>)]
+ // (-d <dir>)* (-v <name>)* [(-t (a|m|s|z)) | (-s <int>)]
//
- // Execute actions specified by -i, -o, -e, -f, -d options in the order as
- // they appear on the command line. After that terminate abnormally if -t
- // option is provided, otherwise exit normally with the status specified by
- // -s option (0 by default).
+ // Execute actions specified by -i, -o, -e, -f, -d, and -v options in the
+ // order as they appear on the command line. After that terminate abnormally
+ // if -t option is provided, otherwise exit normally with the status
+ // specified by -s option (0 by default).
//
// -i <fd>
- // Forward STDIN data to the standard stream denoted by the file
+ // Forward stdin data to the standard stream denoted by the file
// descriptor. Read and discard if 0.
//
// -o <string>
- // Print the line to STDOUT.
+ // Print the line to stdout.
//
// -e <string>
- // Print the line to STDERR.
+ // Print the line to stderr.
//
// -f <path>
// Create an empty file with the path specified.
@@ -58,6 +58,10 @@ main (int argc, char* argv[])
// Create a directory with the path specified. Create parent directories
// if required.
//
+ // -v <name>
+ // If the specified variable is set the print its value to stdout and the
+ // string '<none>' otherwise.
+ //
// -t <method>
// Abnormally terminate itself using one of the following methods:
//
@@ -135,6 +139,11 @@ main (int argc, char* argv[])
{
try_mkdir_p (dir_path (v));
}
+ else if (o == "-v")
+ {
+ optional<string> var (getenv (v));
+ cout << (var ? *var : "<none>") << endl;
+ }
else if (o == "-t")
{
assert (aterm == '\0' && !status); // Make sure exit method is not set.
diff --git a/tests/test/script/runner/env.testscript b/tests/test/script/runner/env.testscript
new file mode 100644
index 0000000..6fcedfa
--- /dev/null
+++ b/tests/test/script/runner/env.testscript
@@ -0,0 +1,7 @@
+# file : tests/test/script/runner/env.testscript
+# license : MIT; see accompanying LICENSE file
+
+.include ../common.testscript
+
+$c <'env abc=xyz -- $* -v abc >xyz' && $b : set
+$c <'env --unset=abc -- $* -v abc >"<none>"' && env abc=xyz -- $b : unset