aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2021-10-13 20:05:27 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2021-10-13 20:05:27 +0300
commit4564a26c0b88d684c12c396d7ef5b0e66f686964 (patch)
tree130e744bd182110184171fb38677f0fca60ec73f
parentb7997a0becbecd775694aa7f106afb3c0e777b8d (diff)
Add --cwd|-t option to env pseudo-builtin
-rw-r--r--doc/testscript.cli11
-rw-r--r--libbuild2/script/parser.cxx40
-rw-r--r--libbuild2/script/parser.hxx7
-rw-r--r--libbuild2/script/run.cxx46
-rw-r--r--libbuild2/script/script.cxx10
-rw-r--r--libbuild2/script/script.hxx1
-rw-r--r--tests/test/script/runner/driver.cxx132
-rw-r--r--tests/test/script/runner/env.testscript34
8 files changed, 201 insertions, 80 deletions
diff --git a/doc/testscript.cli b/doc/testscript.cli
index 4721a88..50f975d 100644
--- a/doc/testscript.cli
+++ b/doc/testscript.cli
@@ -2437,11 +2437,12 @@ with a newline.
\h#builtins-env|\c{env}|
\
-env [-t <sec>] [-u <name>]... [-] [<name>=<value>]... -- <cmd>
+env [-t <sec>] [-c <dir>] [-u <name>]... [-] [<name>=<value>]... -- \
+ <cmd>
\
-Run a command limiting its execution time and/or adding/removing the variables
-to/from the environment.
+Run a command limiting its execution time, changing its working directory,
+and/or 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.
@@ -2463,6 +2464,10 @@ env - --unset=FOO -- $*
Terminate the command if it fails to complete within the specified number
of seconds. See also the \l{#builtins-timeout \c{timeout}} builtin.|
+\li|\n\c{-c|--cwd <dir>}
+
+ Change the command's working directory.|
+
\li|\n\c{-u|--unset <name>}
Remove the specified variable from the environment.||
diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx
index ebfd5fc..f234d58 100644
--- a/libbuild2/script/parser.cxx
+++ b/libbuild2/script/parser.cxx
@@ -1028,8 +1028,9 @@ namespace build2
if (prog && tt == type::word && t.value == "env")
{
parsed_env r (parse_env_builtin (t, tt));
+ c.cwd = move (r.cwd);
c.variables = move (r.variables);
- c.timeout = r.timeout;
+ c.timeout = r.timeout;
env = true;
}
@@ -1411,10 +1412,16 @@ namespace build2
return r;
};
+ auto bad = [&i, &o, this] (const string& v)
+ {
+ fail (i->second) << "env: invalid value '" << v << "' for option '"
+ << o << "'";
+ };
+
// As above but convert the option value to a number and fail on
// error.
//
- auto num = [&i, &o, &str, this] (const char* ln, const char* sn)
+ auto num = [&str, &bad] (const char* ln, const char* sn)
{
optional<uint64_t> r;
if (optional<string> s = str (ln, sn))
@@ -1422,8 +1429,29 @@ namespace build2
r = parse_number (*s);
if (!r)
- fail (i->second) << "env: invalid value '" << *s
- << "' for option '" << o << "'";
+ bad (*s);
+ }
+
+ return r;
+ };
+
+ // As above but convert the option value to a directory path and fail
+ // on error.
+ //
+ auto dir = [&str, &bad] (const char* ln, const char* sn)
+ {
+ optional<dir_path> r;
+ if (optional<string> s = str (ln, sn))
+ try
+ {
+ // Note that we don't need to check that the path is not empty,
+ // since str() fails for empty values.
+ //
+ r = dir_path (move (*s));
+ }
+ catch (const invalid_path& e)
+ {
+ bad (e.path);
}
return r;
@@ -1435,6 +1463,10 @@ namespace build2
{
r.timeout = chrono::seconds (*v);
}
+ else if (optional<dir_path> v = dir ("--cwd", "-c"))
+ {
+ r.cwd = move (*v);
+ }
else if (optional<string> v = str ("--unset", "-u"))
{
verify_environment_var_name (*v, "env: ", i->second, o.c_str ());
diff --git a/libbuild2/script/parser.hxx b/libbuild2/script/parser.hxx
index 9098b3c..2a10311 100644
--- a/libbuild2/script/parser.hxx
+++ b/libbuild2/script/parser.hxx
@@ -130,15 +130,16 @@ namespace build2
pre_parse_line_start (token&, token_type&, lexer_mode);
// Parse the env pseudo-builtin arguments up to the program name. Return
- // the program execution timeout, the list of the variables that should
- // be unset ("name") and/or set ("name=value") in the command
+ // the program execution timeout, CWD, 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.
//
struct parsed_env
{
optional<duration> timeout;
- environment_vars variables;
+ optional<dir_path> cwd;
+ environment_vars variables;
};
parsed_env
diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx
index 91fc9ac..f3b5cad 100644
--- a/libbuild2/script/run.cxx
+++ b/libbuild2/script/run.cxx
@@ -1227,6 +1227,8 @@ namespace build2
//
const command& c (*bc);
+ const dir_path& wdir (*env.work_dir.path);
+
// Register the command explicit cleanups. Verify that the path being
// cleaned up is a sub-path of the script working directory. Fail if
// this is not the case.
@@ -1234,7 +1236,7 @@ namespace build2
for (const auto& cl: c.cleanups)
{
const path& p (cl.path);
- path np (normalize (p, *env.work_dir.path, ll));
+ path np (normalize (p, wdir, ll));
const string& ls (np.leaf ().string ());
bool wc (ls == "*" || ls == "**" || ls == "***");
@@ -1320,6 +1322,10 @@ namespace build2
if (!first || !last)
fail (ll) << program << " builtin must be the only pipe command";
+ if (c.cwd)
+ fail (ll) << "current working directory cannot be specified for "
+ << program << " builtin";
+
if (!c.variables.empty ())
fail (ll) << "environment variables cannot be (un)set for "
<< program << " builtin";
@@ -1455,7 +1461,7 @@ namespace build2
}
case redirect_type::file:
{
- isp = normalize (in.file.path, *env.work_dir.path, ll);
+ isp = normalize (in.file.path, wdir, ll);
open_stdin ();
break;
@@ -1549,9 +1555,9 @@ namespace build2
// or null-device descriptor for merge, pass or null redirects
// respectively (not opening any file).
//
- auto open = [&env, &ll, &std_path] (const redirect& r,
- int dfd,
- path& p) -> auto_fd
+ auto open = [&env, &wdir, &ll, &std_path] (const redirect& r,
+ int dfd,
+ path& p) -> auto_fd
{
assert (dfd == 1 || dfd == 2);
const char* what (dfd == 1 ? "stdout" : "stderr");
@@ -1592,7 +1598,7 @@ namespace build2
//
p = r.file.mode == redirect_fmode::compare
? std_path (what)
- : normalize (r.file.path, *env.work_dir.path, ll);
+ : normalize (r.file.path, wdir, ll);
m |= r.file.mode == redirect_fmode::append
? fdopen_mode::at_end
@@ -1810,6 +1816,28 @@ namespace build2
}
};
+ // Derive the process/builtin CWD.
+ //
+ // If the process/builtin CWD is specified via the env pseudo-builtin,
+ // then use that, completing it relative to the script environment work
+ // directory, if it is relative. Otherwise, use the script environment
+ // work directory.
+ //
+ dir_path completed_cwd;
+ if (c.cwd && c.cwd->relative ())
+ completed_cwd = wdir / *c.cwd;
+
+ const dir_path& cwd (!completed_cwd.empty () ? completed_cwd :
+ c.cwd ? *c.cwd :
+ wdir);
+
+ // Unless CWD is the script environment work directory (which always
+ // exists), verify that it exists and fail if it doesn't.
+ //
+ if (&cwd != &wdir && !exists (cwd))
+ fail (ll) << "specified working directory " << cwd
+ << " does not exist";
+
// Absent if the process/builtin misses the "unsuccessful" deadline.
//
optional<process_exit> exit;
@@ -2069,7 +2097,7 @@ namespace build2
builtin b (bi->function (r,
c.arguments,
move (ifd), move (ofd.out), move (efd),
- *env.work_dir.path,
+ cwd,
bcs));
pipe_command pc (b, c, ll, prev_cmd);
@@ -2159,7 +2187,7 @@ namespace build2
program (path (s, 1, s.size () - 1));
}
else
- program (*env.work_dir.path / p);
+ program (wdir / p);
}
}
catch (const invalid_path& e)
@@ -2189,7 +2217,7 @@ namespace build2
*pe.path,
args.data (),
{ifd.get (), -1}, process::pipe (ofd), {-1, efd.get ()},
- env.work_dir.path->string ().c_str (),
+ cwd.string ().c_str (),
pe.vars);
// Can't throw.
diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx
index 298d71f..9e6eeed 100644
--- a/libbuild2/script/script.cxx
+++ b/libbuild2/script/script.cxx
@@ -411,7 +411,7 @@ namespace build2
{
// Print the env builtin if any of its options/arguments are present.
//
- if (!c.variables.empty () || c.timeout)
+ if (c.timeout || c.cwd || !c.variables.empty ())
{
o << "env";
@@ -421,6 +421,14 @@ namespace build2
o << " -t "
<< chrono::duration_cast<chrono::seconds> (*c.timeout).count ();
+ // CWD.
+ //
+ if (c.cwd)
+ {
+ o << " -c ";
+ print_path (*c.cwd);
+ }
+
// Variable unsets/sets.
//
auto b (c.variables.begin ()), i (b), e (c.variables.end ());
diff --git a/libbuild2/script/script.hxx b/libbuild2/script/script.hxx
index dd31e33..d162900 100644
--- a/libbuild2/script/script.hxx
+++ b/libbuild2/script/script.hxx
@@ -324,6 +324,7 @@ namespace build2
process_path program;
strings arguments;
+ optional<dir_path> cwd; // From env builtin.
environment_vars variables; // From env builtin.
optional<duration> timeout; // From env builtin.
diff --git a/tests/test/script/runner/driver.cxx b/tests/test/script/runner/driver.cxx
index 9c05e27..b84f167 100644
--- a/tests/test/script/runner/driver.cxx
+++ b/tests/test/script/runner/driver.cxx
@@ -17,6 +17,7 @@
#include <exception>
#include <libbutl/path.hxx>
+#include <libbutl/path-io.hxx>
#include <libbutl/optional.hxx>
#include <libbutl/fdstream.hxx>
#include <libbutl/filesystem.hxx>
@@ -45,8 +46,8 @@ main (int argc, char* argv[])
// Usage: driver [-i <int>] (-o <string>)* (-e <string>)* (-f <file>)*
// (-d <dir>)* (-v <name>)* [(-t (a|m|s|z)) | (-s <int>)]
//
- // Execute actions specified by -i, -o, -e, -f, -d, -v, and -l options in
- // the order as they appear on the command line. After that terminate
+ // Execute actions specified by -i, -o, -e, -f, -d, -w, -v, and -l 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).
//
@@ -67,6 +68,9 @@ main (int argc, char* argv[])
// Create a directory with the path specified. Create parent directories
// if required.
//
+ // -w
+ // Print CWD to stdout.
+ //
// -v <name>
// If the specified variable is set then print its value to stdout and
// the string '<none>' otherwise.
@@ -95,10 +99,7 @@ main (int argc, char* argv[])
for (int i (1); i < argc; ++i)
{
- string o (argv[i++]);
- assert (i < argc);
-
- string v (argv[i]);
+ string o (argv[i]);
auto toi = [] (const string& s) -> int
{
@@ -118,69 +119,80 @@ main (int argc, char* argv[])
return r;
};
- if (o == "-i")
+ if (o == "-w")
{
- assert (ifd == 3); // Make sure is not set yet.
-
- ifd = toi (v);
- assert (ifd >= 0 && ifd < 3);
+ cout << dir_path::current_directory () << endl;
+ }
+ else // Handle options other than flags.
+ {
+ ++i;
+ assert (i < argc);
+ string v (argv[i]);
- if (ifd == 0)
- cin.ignore (numeric_limits<streamsize>::max ());
- else if (cin.peek () != istream::traits_type::eof ())
+ if (o == "-i")
{
- ostream& o (ifd == 1 ? cout : cerr);
- o << cin.rdbuf ();
- o.flush ();
+ assert (ifd == 3); // Make sure is not set yet.
+
+ ifd = toi (v);
+ assert (ifd >= 0 && ifd < 3);
+
+ if (ifd == 0)
+ cin.ignore (numeric_limits<streamsize>::max ());
+ else if (cin.peek () != istream::traits_type::eof ())
+ {
+ ostream& o (ifd == 1 ? cout : cerr);
+ o << cin.rdbuf ();
+ o.flush ();
+ }
}
- }
- else if (o == "-o")
- {
- cout << v << endl;
- }
- else if (o == "-e")
- {
- cerr << v << endl;
- }
- else if (o == "-f")
- {
- ofdstream os (v);
- os.close ();
- }
- else if (o == "-d")
- {
- try_mkdir_p (dir_path (v));
- }
- else if (o == "-v")
- {
- optional<string> var (getenv (v));
- cout << (var ? *var : "<none>") << endl;
- }
- else if (o == "-l")
- {
- size_t t (toi (v));
+ else if (o == "-o")
+ {
+ cout << v << endl;
+ }
+ else if (o == "-e")
+ {
+ cerr << v << endl;
+ }
+ else if (o == "-f")
+ {
+ ofdstream os (v);
+ os.close ();
+ }
+ else if (o == "-d")
+ {
+ try_mkdir_p (dir_path (v));
+ }
+ else if (o == "-v")
+ {
+ optional<string> var (getenv (v));
+ cout << (var ? *var : "<none>") << endl;
+ }
+ else if (o == "-l")
+ {
+ size_t t (toi (v));
- // MinGW GCC 4.9 doesn't implement this_thread so use Win32 Sleep().
- //
+ // MinGW GCC 4.9 doesn't implement this_thread so use Win32 Sleep().
+ //
#ifndef _WIN32
- this_thread::sleep_for (chrono::seconds (t));
+ this_thread::sleep_for (chrono::seconds (t));
#else
- Sleep (static_cast<DWORD> (t * 1000));
+ Sleep (static_cast<DWORD> (t * 1000));
#endif
+ }
+ else if (o == "-t")
+ {
+ assert (aterm == '\0' && !status); // Make sure exit method is not set.
+ assert (v.size () == 1 && v.find_first_of ("amsz") != string::npos);
+ aterm = v[0];
+ }
+ else if (o == "-s")
+ {
+ assert (!status && aterm == '\0'); // Make sure exit method is not set.
+ status = toi (v);
+ }
+ else
+ assert (false);
}
- else if (o == "-t")
- {
- assert (aterm == '\0' && !status); // Make sure exit method is not set.
- assert (v.size () == 1 && v.find_first_of ("amsz") != string::npos);
- aterm = v[0];
- }
- else if (o == "-s")
- {
- assert (!status && aterm == '\0'); // Make sure exit method is not set.
- status = toi (v);
- }
- else
- assert (false);
}
switch (aterm)
diff --git a/tests/test/script/runner/env.testscript b/tests/test/script/runner/env.testscript
index ef90c3b..512139a 100644
--- a/tests/test/script/runner/env.testscript
+++ b/tests/test/script/runner/env.testscript
@@ -3,6 +3,40 @@
.include ../common.testscript
+: cwd
+:
+{
+ : not-exist
+ :
+ $c <'env -c a -- $* -w' && $b 2>>/~%EOE% != 0
+ %testscript:1:1: error: specified working directory .+/a/ does not exist%
+ info: test id: 1
+ EOE
+
+ : process
+ :
+ $c <<EOI && $b
+ mkdir a;
+ env -c a -- $* -w >/~%.+/a%
+ EOI
+
+ : builtin
+ :
+ $c <<EOI && $b
+ mkdir a;
+ env -c a -- touch b;
+ test -f a/b
+ EOI
+
+ : absolute
+ :
+ $c <<EOI && $b
+ mkdir a;
+ env -c $~/a -- touch b;
+ test -f a/b
+ EOI
+}
+
: variables
:
{