diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2020-06-16 17:08:39 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2020-06-18 14:18:59 +0300 |
commit | 728b075cb5e0df9c386f8377e0f6961e5ccc5143 (patch) | |
tree | 8bd60cb16a260031d46b5d79adab5821dac3dd27 | |
parent | 448747903956f70f85f5135a224bbbae7f7b276a (diff) |
Add env script pseudo-builtin
Also disable C++ recipe tests when cross-testing.
-rw-r--r-- | doc/testscript.cli | 30 | ||||
-rw-r--r-- | libbuild2/build/script/parser+diag.test.testscript | 36 | ||||
-rw-r--r-- | libbuild2/build/script/parser.cxx | 6 | ||||
-rw-r--r-- | libbuild2/build/script/parser.hxx | 9 | ||||
-rw-r--r-- | libbuild2/script/parser.cxx | 162 | ||||
-rw-r--r-- | libbuild2/script/parser.hxx | 14 | ||||
-rw-r--r-- | libbuild2/script/run.cxx | 11 | ||||
-rw-r--r-- | libbuild2/script/script.cxx | 130 | ||||
-rw-r--r-- | libbuild2/script/script.hxx | 8 | ||||
-rw-r--r-- | libbuild2/test/script/parser+env.test.testscript | 77 | ||||
-rw-r--r-- | tests/build/root.build | 1 | ||||
-rw-r--r-- | tests/recipe/cxx/testscript | 269 | ||||
-rw-r--r-- | tests/test/script/runner/driver.cxx | 25 | ||||
-rw-r--r-- | tests/test/script/runner/env.testscript | 7 |
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 |