From b808c255b6a9ddba085bf5646e7d20ec344f2e2d Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 28 Apr 2020 08:48:53 +0200 Subject: Initial support for ad hoc recipes (still work in progress) --- libbuild2/script/script.hxx | 471 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 libbuild2/script/script.hxx (limited to 'libbuild2/script/script.hxx') diff --git a/libbuild2/script/script.hxx b/libbuild2/script/script.hxx new file mode 100644 index 0000000..f4998b7 --- /dev/null +++ b/libbuild2/script/script.hxx @@ -0,0 +1,471 @@ +// file : libbuild2/script/script.hxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_SCRIPT_SCRIPT_HXX +#define LIBBUILD2_SCRIPT_SCRIPT_HXX + +#include +#include +#include + +#include +#include + +namespace build2 +{ + namespace script + { + // Pre-parsed representation. + // + + enum class line_type + { + var, + cmd, + cmd_if, + cmd_ifn, + cmd_elif, + cmd_elifn, + cmd_else, + cmd_end + }; + + ostream& + operator<< (ostream&, line_type); + + struct line + { + line_type type; + replay_tokens tokens; + + union + { + const variable* var; // Pre-entered for line_type::var. + }; + }; + + // Most of the time we will have just one line (a command). + // + using lines = small_vector; + + // Print the script lines, trying to reproduce their original (non- + // expanded) representation. + // + // Note that the exact spacing and partial quoting may not be restored due + // to the information loss. + // + void + dump (ostream&, const string& ind, const lines&); + + // Parse object model. + // + + // redirect + // + enum class redirect_type + { + // No data is allowed to be read or written. + // + // Note that redirect of this type cannot be currently specified on the + // script command line and can only be set via the environment object + // as a default redirect (see below). + // + none, + + pass, + null, + trace, + merge, + here_str_literal, + here_str_regex, + here_doc_literal, + here_doc_regex, + here_doc_ref, // Reference to here_doc literal or regex. + file, + }; + + // Pre-parsed (but not instantiated) regex lines. The idea here is that + // we should be able to re-create their (more or less) exact text + // representation for diagnostics but also instantiate without any + // re-parsing. + // + struct regex_line + { + // If regex is true, then value is the regex expression. Otherwise, it + // is a literal. Note that special characters can be present in both + // cases. For example, //+ is a regex, while /+ is a literal, both + // with '+' as a special character. Flags are only valid for regex. + // Literals falls apart into textual (has no special characters) and + // special (has just special characters instead) ones. For example + // foo is a textual literal, while /.+ is a special one. Note that + // literal must not have value and special both non-empty. + // + bool regex; + + string value; + string flags; + string special; + + uint64_t line; + uint64_t column; + + // Create regex with optional special characters. + // + regex_line (uint64_t l, uint64_t c, + string v, string f, string s = string ()) + : regex (true), + value (move (v)), + flags (move (f)), + special (move (s)), + line (l), + column (c) {} + + // Create a literal, either text or special. + // + regex_line (uint64_t l, uint64_t c, string v, bool s) + : regex (false), + value (s ? string () : move (v)), + special (s ? move (v) : string ()), + line (l), + column (c) {} + }; + + struct regex_lines + { + char intro; // Introducer character. + string flags; // Global flags (here-document). + + small_vector lines; + }; + + // Output file redirect mode. + // + enum class redirect_fmode + { + compare, + overwrite, + append + }; + + struct redirect + { + redirect_type type; + + struct file_type + { + using path_type = build2::path; + path_type path; + redirect_fmode mode; // Meaningless for input redirect. + }; + + union + { + int fd; // Merge-to descriptor. + string str; // Note: with trailing newline, if requested. + regex_lines regex; // Note: with trailing blank, if requested. + file_type file; + reference_wrapper ref; // Note: no chains. + }; + + // Modifiers and the original representation (potentially an alias). + // + build2::token token; + + string end; // Here-document end marker (no regex intro/flags). + uint64_t end_line; // Here-document end marker location. + uint64_t end_column; + + // Create redirect of a type other than reference. + // + explicit + redirect (redirect_type); + + // Create redirect of the reference type. + // + redirect (redirect_type t, const redirect& r, build2::token tk) + : type (redirect_type::here_doc_ref), + ref (r), + token (move (tk)) + { + // There is no support (and need) for reference chains. + // + assert (t == redirect_type::here_doc_ref && + r.type != redirect_type::here_doc_ref); + } + + // Create redirect of the merge type. + // + // Note that it's the caller's responsibility to make sure that the file + // descriptor is valid for this redirect (2 for stdout, etc). + // + redirect (redirect_type t, int f) + : type (redirect_type::merge), fd (f) + { + assert (t == redirect_type::merge && (f == 1 || f == 2)); + } + + redirect (redirect&&) noexcept; + redirect& operator= (redirect&&) noexcept; + + // @@ Defining optional movable-only redirects in the command class make + // the older C++ compilers (GCC 4.9, Clang 4, VC 15) fail to compile the + // command vector manipulating code. Thus, we make the redirect class + // copyable to workaround the issue. + // + redirect (const redirect&); + redirect& operator= (const redirect&); + + ~redirect (); + + const redirect& + effective () const noexcept + { + return type == redirect_type::here_doc_ref ? ref.get () : *this; + } + + const string& + modifiers () const noexcept + { + return token.value; + } + }; + + // cleanup + // + enum class cleanup_type + { + always, // &foo - cleanup, fail if does not exist. + maybe, // &?foo - cleanup, ignore if does not exist. + never // &!foo - don’t cleanup, ignore if doesn’t exist. + }; + + // File or directory to be automatically cleaned up at the end of the + // script execution. If the path ends with a trailing slash, then it is + // assumed to be a directory, otherwise -- a file. A directory that is + // about to be cleaned up must be empty. + // + // The last component in the path may contain a wildcard that have the + // following semantics: + // + // dir/* - remove all immediate files + // dir/*/ - remove all immediate sub-directories (must be empty) + // dir/** - remove all files recursively + // dir/**/ - remove all sub-directories recursively (must be empty) + // dir/*** - remove directory dir with all files and sub-directories + // recursively + // + struct cleanup + { + cleanup_type type; + build2::path path; + }; + using cleanups = vector; + + // command_exit + // + enum class exit_comparison {eq, ne}; + + struct command_exit + { + // C/C++ don't apply constraints on program exit code other than it + // being of type int. + // + // POSIX specifies that only the least significant 8 bits shall be + // available from wait() and waitpid(); the full value shall be + // available from waitid() (read more at _Exit, _exit Open Group + // spec). + // + // While the Linux man page for waitid() doesn't mention any + // deviations from the standard, the FreeBSD implementation (as of + // version 11.0) only returns 8 bits like the other wait*() calls. + // + // Windows supports 32-bit exit codes. + // + // Note that in shells some exit values can have special meaning so + // using them can be a source of confusion. For bash values in the + // [126, 255] range are such a special ones (see Appendix E, "Exit + // Codes With Special Meanings" in the Advanced Bash-Scripting Guide). + // + exit_comparison comparison; + uint8_t code; + }; + + // command + // + struct command + { + path program; + strings arguments; + + optional in; + optional out; + optional err; + + script::cleanups cleanups; + + command_exit exit {exit_comparison::eq, 0}; + }; + + enum class command_to_stream: uint16_t + { + header = 0x01, + here_doc = 0x02, // Note: printed on a new line. + all = header | here_doc + }; + + void + to_stream (ostream&, const command&, command_to_stream); + + ostream& + operator<< (ostream&, const command&); + + // command_pipe + // + using command_pipe = vector; + + void + to_stream (ostream&, const command_pipe&, command_to_stream); + + ostream& + operator<< (ostream&, const command_pipe&); + + // command_expr + // + enum class expr_operator {log_or, log_and}; + + struct expr_term + { + expr_operator op; // OR-ed to an implied false for the first term. + command_pipe pipe; + }; + + using command_expr = vector; + + void + to_stream (ostream&, const command_expr&, command_to_stream); + + ostream& + operator<< (ostream&, const command_expr&); + + // Script execution environment. + // + class environment + { + public: + build2::context& context; + + // The platform script programs run on. + // + const target_triplet& host; + + // The work directory is used as the builtin/process CWD and to complete + // relative paths. Any attempt to remove or move this directory (or its + // parent directory) using the rm or mv builtins will fail. Must be an + // absolute path. + // + const dir_name_view work_dir; + + // If the sanbox directory is not NULL, then any attempt to remove or + // move a filesystem entry outside this directory using an explicit + // cleanup or the rm/mv builtins will fail, unless the --force option is + // specified for the builtin. Must be an absolute path. + // + const dir_name_view sandbox_dir; + + // The temporary directory is used by the script running machinery to + // create special files. Must be an absolute path, unless empty. Can be + // empty until the create_temp_dir() function call, which can be used + // for creating this directory on demand. + // + const dir_path& temp_dir; + + // If true, the temporary directory will not be removed on the script + // failure. In particular, this allows the script running machinery to + // refer to the special files in diagnostics. + // + const bool temp_dir_keep; + + // Default process streams redirects. + // + // If a stream redirect is not specified on the script command line, + // then the respective redirect data member will be used as the default. + // + const redirect in; + const redirect out; + const redirect err; + + environment (build2::context& ctx, + const target_triplet& h, + const dir_name_view& wd, + const dir_name_view& sd, + const dir_path& td, bool tk, + redirect&& i = redirect (redirect_type::pass), + redirect&& o = redirect (redirect_type::pass), + redirect&& e = redirect (redirect_type::pass)) + : context (ctx), host (h), + work_dir (wd), sandbox_dir (sd), temp_dir (td), temp_dir_keep (tk), + in (move (i)), out (move (o)), err (move (e)) + { + } + + // Create environment without the sandbox. + // + environment (build2::context& ctx, + const target_triplet& h, + const dir_name_view& wd, + const dir_path& td, bool tk, + redirect&& i = redirect (redirect_type::pass), + redirect&& o = redirect (redirect_type::pass), + redirect&& e = redirect (redirect_type::pass)) + : environment (ctx, h, + wd, dir_name_view (), td, tk, + move (i), move (o), move (e)) + { + } + + // Cleanup. + // + public: + script::cleanups cleanups; + paths special_cleanups; + + // Register a cleanup. If the cleanup is explicit, then override the + // cleanup type if this path is already registered. Ignore implicit + // registration of a path outside root directory (see below). + // + void + clean (cleanup, bool implicit); + + // Register cleanup of a special file. Such files are created to + // maintain the script running machinery and must be removed first, not + // to interfere with the user-defined wildcard cleanups if the working + // and temporary directories are the same. + // + void + clean_special (path); + + public: + // Set variable value with optional (non-empty) attributes. + // + virtual void + set_variable (string&& name, + names&&, + const string& attrs, + const location&) = 0; + + // Create the temporary directory and set the temp_dir reference target + // to its path. Must only be called if temp_dir is empty. + // + virtual void + create_temp_dir () = 0; + + public: + virtual + ~environment () = default; + }; + } +} + +#include + +#endif // LIBBUILD2_SCRIPT_SCRIPT_HXX -- cgit v1.1