From 4564a26c0b88d684c12c396d7ef5b0e66f686964 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 13 Oct 2021 20:05:27 +0300 Subject: Add --cwd|-t option to env pseudo-builtin --- libbuild2/script/parser.cxx | 40 +++++++++++++++++++++++++++++++++++---- libbuild2/script/parser.hxx | 7 ++++--- libbuild2/script/run.cxx | 46 ++++++++++++++++++++++++++++++++++++--------- libbuild2/script/script.cxx | 10 +++++++++- libbuild2/script/script.hxx | 1 + 5 files changed, 87 insertions(+), 17 deletions(-) (limited to 'libbuild2/script') 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 r; if (optional 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 r; + if (optional 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 v = dir ("--cwd", "-c")) + { + r.cwd = move (*v); + } else if (optional 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 timeout; - environment_vars variables; + optional 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 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 (*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 cwd; // From env builtin. environment_vars variables; // From env builtin. optional timeout; // From env builtin. -- cgit v1.1