aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/script
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/script')
-rw-r--r--libbuild2/script/parser.cxx119
-rw-r--r--libbuild2/script/run.cxx200
-rw-r--r--libbuild2/script/run.hxx4
3 files changed, 238 insertions, 85 deletions
diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx
index 84d2afc..30c7ff6 100644
--- a/libbuild2/script/parser.cxx
+++ b/libbuild2/script/parser.cxx
@@ -1668,9 +1668,9 @@ namespace build2
if (es > 255)
{
- diag_record dr;
+ diag_record dr (fail (l));
- dr << fail (l) << "expected exit status instead of ";
+ dr << "expected exit status instead of ";
to_stream (dr.os, ns, quote_mode::normal);
dr << info << "exit status is an unsigned integer less than 256";
@@ -2218,7 +2218,7 @@ namespace build2
// Copy the tokens and start playing.
//
- replay_data (replay_tokens (ln.tokens));
+ replay_data (ln.tokens);
// We don't really need to change the mode since we already know
// the line type.
@@ -2433,7 +2433,7 @@ namespace build2
// Prepare for the condition reevaluation.
//
- replay_data (replay_tokens (ln.tokens));
+ replay_data (ln.tokens);
next (t, tt);
li = wli;
}
@@ -2460,19 +2460,19 @@ namespace build2
//
struct loop_data
{
- lines::const_iterator i;
- lines::const_iterator e;
const function<exec_set_function>& exec_set;
const function<exec_cmd_function>& exec_cmd;
const function<exec_cond_function>& exec_cond;
const function<exec_for_function>& exec_for;
+ lines::const_iterator i;
+ lines::const_iterator e;
const iteration_index* ii;
size_t& li;
variable_pool* var_pool;
decltype (fcend)& fce;
lines::const_iterator& fe;
- } ld {i, e,
- exec_set, exec_cmd, exec_cond, exec_for,
+ } ld {exec_set, exec_cmd, exec_cond, exec_for,
+ i, e,
ii, li,
var_pool,
fcend,
@@ -2558,7 +2558,6 @@ namespace build2
const location& ll;
size_t fli;
iteration_index& fi;
-
} d {ld, env, vname, attrs, ll, fli, fi};
function<void (string&&)> f (
@@ -2676,12 +2675,19 @@ namespace build2
if (val)
{
- // If this value is a vector, then save its element type so
+ // If the value type provides custom iterate function, then
+ // use that (see value_type::iterate for details).
+ //
+ auto iterate (val.type != nullptr
+ ? val.type->iterate
+ : nullptr);
+
+ // If this value is a container, then save its element type so
// that we can typify each element below.
//
const value_type* etype (nullptr);
- if (val.type != nullptr)
+ if (!iterate && val.type != nullptr)
{
etype = val.type->element_type;
@@ -2693,37 +2699,84 @@ namespace build2
size_t fli (li);
iteration_index fi {1, ii};
- names& ns (val.as<names> ());
- for (auto ni (ns.begin ()), ne (ns.end ()); ni != ne; ++ni)
+ names* ns (!iterate ? &val.as<names> () : nullptr);
+
+ // Similar to above.
+ //
+ struct loop_data
+ {
+ const function<exec_set_function>& exec_set;
+ const function<exec_cmd_function>& exec_cmd;
+ const function<exec_cond_function>& exec_cond;
+ const function<exec_for_function>& exec_for;
+ lines::const_iterator i;
+ lines::const_iterator e;
+ const location& ll;
+ size_t& li;
+ variable_pool* var_pool;
+ const variable& var;
+ const attributes& val_attrs;
+ decltype (fcend)& fce;
+ lines::const_iterator& fe;
+ iteration_index& fi;
+
+ } ld {exec_set, exec_cmd, exec_cond, exec_for,
+ i, e,
+ ll, li,
+ var_pool, *var, val_attrs,
+ fcend, fe, fi};
+
+ function<bool (value&&, bool first)> iteration =
+ [this, &ld] (value&& v, bool)
{
- li = fli;
+ ld.exec_for (ld.var, move (v), ld.val_attrs, ld.ll);
- // Set the variable value.
+ // Find the construct end, if it is not found yet.
//
- bool pair (ni->pair);
- names n;
- n.push_back (move (*ni));
- if (pair) n.push_back (move (*++ni));
- value v (move (n)); // Untyped.
+ if (ld.fe == ld.e)
+ ld.fe = ld.fce (ld.i, true, false);
+
+ if (!exec_lines (
+ ld.i + 1, ld.fe,
+ ld.exec_set, ld.exec_cmd, ld.exec_cond, ld.exec_for,
+ &ld.fi, ld.li,
+ ld.var_pool))
+ return false;
+
+ ld.fi.index++;
+ return true;
+ };
- if (etype != nullptr)
- typify (v, *etype, var);
+ if (!iterate)
+ {
+ for (auto nb (ns->begin ()), ni (nb), ne (ns->end ());
+ ni != ne;
+ ++ni)
+ {
+ bool first (ni == nb);
- exec_for (*var, move (v), val_attrs, ll);
+ li = fli;
- // Find the construct end, if it is not found yet.
- //
- if (fe == e)
- fe = fcend (i, true, false);
+ // Set the variable value.
+ //
+ bool pair (ni->pair);
+ names n;
+ n.push_back (move (*ni));
+ if (pair) n.push_back (move (*++ni));
+ value v (move (n)); // Untyped.
- if (!exec_lines (i + 1, fe,
- exec_set, exec_cmd, exec_cond, exec_for,
- &fi, li,
- var_pool))
- return false;
+ if (etype != nullptr)
+ typify (v, *etype, var);
- fi.index++;
+ if (!iteration (move (v), first))
+ return false;
+ }
+ }
+ else
+ {
+ if (!iterate (val, iteration))
+ return false;
}
}
diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx
index f8f98c1..31e4537 100644
--- a/libbuild2/script/run.cxx
+++ b/libbuild2/script/run.cxx
@@ -1301,7 +1301,6 @@ namespace build2
// Terminate processes gracefully and set the terminate flag for the
// pipe commands.
//
- diag_record dr;
for (pipe_command* c (pc); c != nullptr; c = c->prev)
{
if (process* p = c->proc)
@@ -1324,6 +1323,10 @@ namespace build2
c->terminated = true;
}
+ // Delay any failure until we process the entire pipeline.
+ //
+ maybe_diag_record dr;
+
// Wait a bit for the processes to terminate and kill the remaining
// ones.
//
@@ -1810,6 +1813,24 @@ namespace build2
//
const string& program (c.program.recall.string ());
+ // Optimize standalone true/false builtins (used in conditions, etc). We
+ // know they don't read/write stdin/stdout/stderr so none of the below
+ // complications are necessary.
+ //
+ if (resolve && (program == "true" || program == "false") &&
+ first && last && cf == nullptr &&
+ !c.in && !c.out && !c.err && !c.exit)
+ {
+ // Note: we can safely ignore agruments, env vars, and timeout.
+ //
+ // Don't print the true and false builtins, since they are normally
+ // used for the commands execution flow control (also below).
+ //
+ return program == "true";
+ }
+
+ // Standard streams.
+ //
const redirect& in ((c.in ? *c.in : env.in).effective ());
const redirect* out (!last || (cf != nullptr && !last_cmd)
@@ -1818,6 +1839,36 @@ namespace build2
const redirect& err ((c.err ? *c.err : env.err).effective ());
+ // If the output redirect is `none` (default for testscript) or `null`
+ // and this is a builtin which is known not to write to stdout, then
+ // redirect its stdout to stderr, unless stderr itself is redirected to
+ // stdout. This way we will give up an expensive fdopen() in favor of a
+ // cheap fddup().
+ //
+ // Note that we ignore the pseudo builtins since they are handled prior
+ // to opening any file descriptors for stdout.
+ //
+ const redirect out_merge (redirect_type::merge, 2);
+ if (out != nullptr &&
+ (out->type == redirect_type::none ||
+ out->type == redirect_type::null) &&
+ err.type != redirect_type::merge &&
+ resolve &&
+ (program == "cp" ||
+ program == "false" ||
+ program == "ln" ||
+ program == "mkdir" ||
+ program == "mv" ||
+ program == "rm" ||
+ program == "rmdir" ||
+ program == "sleep" ||
+ program == "test" ||
+ program == "touch" ||
+ program == "true"))
+ {
+ out = &out_merge;
+ }
+
auto process_args = [&c] () -> cstrings
{
return build2::process_args (c.program.recall_string (), c.arguments);
@@ -1964,11 +2015,14 @@ namespace build2
// If this is the first pipeline command, then open stdin descriptor
// according to the redirect specified.
//
+ // Specifically, save into ifd the descriptor of a newly created file,
+ // existing file, stdin descriptor duplicate, or null-device descriptor
+ // for the here_*_literal, file, pass, or null/none redirects
+ // respectively (not creating any pipe).
+ //
path isp;
- if (!first)
- assert (!c.in); // No redirect expected.
- else
+ if (first)
{
// Open a file for passing to the command stdin.
//
@@ -2002,25 +2056,58 @@ namespace build2
break;
}
case redirect_type::none:
- // Somehow need to make sure that the child process doesn't read
- // from stdin. That is tricky to do in a portable way. Here we
- // suppose that the program which (erroneously) tries to read some
- // data from stdin being redirected to /dev/null fails not being
- // able to read the expected data, and so the command doesn't pass
- // through.
- //
- // @@ Obviously doesn't cover the case when the process reads
- // whatever available.
- // @@ Another approach could be not to redirect stdin and let the
- // process to hang which can be interpreted as a command failure.
- // @@ Both ways are quite ugly. Is there some better way to do
- // this?
- // @@ Maybe we can create a pipe, write a byte into it, close the
- // writing end, and after the process terminates make sure we can
- // still read this byte out?
- //
+ {
+ // Somehow need to make sure that the child process doesn't read
+ // from stdin. That is tricky to do in a portable way. Here we
+ // suppose that the program which (erroneously) tries to read some
+ // data from stdin being redirected to /dev/null fails not being
+ // able to read the expected data, and so the command doesn't pass
+ // through.
+ //
+ // @@ Obviously doesn't cover the case when the process reads
+ // whatever available.
+ // @@ Another approach could be not to redirect stdin and let the
+ // process to hang which can be interpreted as a command
+ // failure.
+ // @@ Both ways are quite ugly. Is there some better way to do
+ // this?
+ // @@ Maybe we can create a pipe, write a byte into it, close the
+ // writing end, and after the process terminates make sure we
+ // can still read this byte out?
+ //
+ // Let's optimize for the builtins, which are known not to read
+ // any input, by not opening /dev/null but duplicating the stdin
+ // file descriptor instead.
+ //
+ if (resolve &&
+ (program == "cp" ||
+ program == "date" ||
+ program == "echo" ||
+ program == "false" ||
+ program == "find" ||
+ program == "ln" ||
+ program == "mkdir" ||
+ program == "mv" ||
+ program == "rm" ||
+ program == "rmdir" ||
+ program == "sleep" ||
+ program == "test" ||
+ program == "touch" ||
+ program == "true"))
+ {
+ try
+ {
+ ifd = fddup (0);
+ }
+ catch (const io_error& e)
+ {
+ fail (ll) << "unable to duplicate stdin: " << e;
+ }
+
+ break;
+ }
+ }
// Fall through.
- //
case redirect_type::null:
{
ifd = open_null ();
@@ -2057,8 +2144,10 @@ namespace build2
case redirect_type::here_doc_ref: assert (false); break;
}
}
+ else
+ assert (!c.in); // No redirect expected.
- assert (ifd.get () != -1);
+ assert (ifd != nullfd);
// Calculate the process/builtin execution deadline. Note that we should
// also consider the left-hand side processes deadlines, not to keep
@@ -2152,14 +2241,16 @@ namespace build2
else
pc.next = &pc; // Points to itself.
- // Open a file for command output redirect if requested explicitly
- // (file overwrite/append redirects) or for the purpose of the output
+ // Open a file for command output redirect if requested explicitly (file
+ // overwrite/append redirects) or for the purpose of the output
// validation (none, here_*, file comparison redirects), register the
// file for cleanup, return the file descriptor. Interpret trace
// redirect according to the verbosity level (as null if below 2, as
- // pass otherwise). Return nullfd, standard stream descriptor duplicate
- // or null-device descriptor for merge, pass or null redirects
- // respectively (not opening any file).
+ // pass otherwise). Return nullfd, standard stream descriptor duplicate,
+ // or null-device descriptor for merge, pass (except for the buffered
+ // stderr), or null redirects respectively (not opening/creating any
+ // file/pipe). Create the pipe and return its write end for the pass
+ // redirect of the buffered stderr.
//
auto open = [&env, &wdir, &ll, &std_path, &c, &pc] (const redirect& r,
int dfd,
@@ -2276,10 +2367,12 @@ namespace build2
path osp;
fdpipe ofd;
- // If this is the last command in the pipeline than redirect the
- // command process stdout to a file. Otherwise create a pipe and
- // redirect the stdout to the write-end of the pipe. The read-end will
- // be passed as stdin for the next command in the pipeline.
+ // If this is either not the last command in the pipeline or the
+ // command's output needs to be read by the specified function, then
+ // create a pipe and redirect the stdout to the write-end of the
+ // pipe. The read-end will be passed as stdin for the next command in
+ // the pipeline or the function. Otherwise, proceed according to the
+ // specified redirect (see open() lambda for details).
//
// @@ Shouldn't we allow the here-* and file output redirects for a
// command with pipelined output? Say if such redirect is present
@@ -2290,16 +2383,21 @@ namespace build2
// script failures investigation and, for example, for validation
// "tightening".
//
- if (last && out != nullptr)
- ofd.out = open (*out, 1, osp);
- else
+ if (!last || out == nullptr)
{
assert (!c.out); // No redirect expected.
+
+ // Otherwise we wouldn't be here.
+ //
+ assert (!last || (cf != nullptr && !last_cmd));
+
ofd = open_pipe ();
}
+ else
+ ofd.out = open (*out, 1, osp); // Note: may or may not open file.
path esp;
- auto_fd efd (open (err, 2, esp));
+ auto_fd efd (open (err, 2, esp)); // Note: may or may not open file/pipe.
// Merge standard streams.
//
@@ -2547,7 +2645,7 @@ namespace build2
// Verify the exit status and issue the diagnostics on failure.
//
- diag_record dr;
+ maybe_diag_record dr;
path pr (cmd_path (cmd));
@@ -2563,16 +2661,16 @@ namespace build2
if (c->unread_stdout)
{
- dr << "stdout ";
+ *dr << "stdout ";
if (c->unread_stderr)
- dr << "and ";
+ *dr << "and ";
}
if (c->unread_stderr)
- dr << "stderr ";
+ *dr << "stderr ";
- dr << "not closed after exit";
+ *dr << "not closed after exit";
};
// Fail if the process is terminated due to reaching the deadline.
@@ -2588,7 +2686,7 @@ namespace build2
if (verb == 1)
{
dr << info << "command line: ";
- print_process (dr, *c->args);
+ print_process (*dr, *c->args);
}
fail = true;
@@ -2643,23 +2741,23 @@ namespace build2
dr << error (ll) << w << ' ' << pr << ' ';
if (!exit->normal ())
- dr << *exit;
+ *dr << *exit;
else
{
uint16_t ec (exit->code ()); // Make sure printed as integer.
if (!valid)
{
- dr << "exit code " << ec << " out of 0-255 range";
+ *dr << "exit code " << ec << " out of 0-255 range";
}
else
{
if (cmd.exit)
- dr << "exit code " << ec
- << (cmp == exit_comparison::eq ? " != " : " == ")
- << exc;
+ *dr << "exit code " << ec
+ << (cmp == exit_comparison::eq ? " != " : " == ")
+ << exc;
else
- dr << "exited with code " << ec;
+ *dr << "exited with code " << ec;
}
}
@@ -2669,7 +2767,7 @@ namespace build2
if (verb == 1)
{
dr << info << "command line: ";
- print_process (dr, *c->args);
+ print_process (*dr, *c->args);
}
if (non_empty (*c->esp, ll) && avail_on_failure (*c->esp, env))
@@ -2683,7 +2781,7 @@ namespace build2
// Print cached stderr.
//
- print_file (dr, *c->esp, ll);
+ print_file (*dr, *c->esp, ll);
}
else if (c->unread_stdout || c->unread_stderr)
unread_output_diag (true /* main_error */);
@@ -2754,7 +2852,7 @@ namespace build2
// Execute the builtin.
//
// Don't print the true and false builtins, since they are normally
- // used for the commands execution flow control.
+ // used for the commands execution flow control (also above).
//
if (verb >= 2 && program != "true" && program != "false")
print_process (args);
diff --git a/libbuild2/script/run.hxx b/libbuild2/script/run.hxx
index c4c2aa2..955c37e 100644
--- a/libbuild2/script/run.hxx
+++ b/libbuild2/script/run.hxx
@@ -39,7 +39,9 @@ namespace build2
// can be used in diagnostics.
//
// Optionally, execute the specified function at the end of the pipe,
- // either after the last command or instead of it.
+ // either after the last command (last_cmd=false) or instead of it
+ // (last_cmd=true). Note that the last_cmd argument is only meaningful if
+ // the function is specified.
//
void
run (environment&,