// file : build2/test/script/script -*- C++ -*- // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUILD2_TEST_SCRIPT_SCRIPT #define BUILD2_TEST_SCRIPT_SCRIPT #include #include #include #include #include #include // replay_tokens namespace build2 { class target; namespace test { namespace script { class parser; // Required by VC for 'friend class parser' declaration. // Pre-parse 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 (test command). // using lines = small_vector; // Parse object model. // // redirect // enum class redirect_type { none, pass, null, merge, here_string, here_document, file }; struct redirect { redirect_type type; struct doc_type { string doc; // Note: includes trailing newline, if required. string end; }; struct file_type { using path_type = build2::path; path_type path; bool append = false; }; union { int fd; // Merge-to descriptor. string str; // Note: includes trailing newline, if required. doc_type doc; file_type file; }; explicit redirect (redirect_type = redirect_type::none); redirect (redirect&&); redirect (const redirect&); redirect& operator= (redirect&&); redirect& operator= (const redirect&); ~redirect (); }; // 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 // scope. 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 status; }; // command // struct command { path program; strings arguments; redirect in; redirect out; redirect 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; // Ignored 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&); // description // struct description { string id; string summary; string details; bool empty () const { return id.empty () && summary.empty () && details.empty (); } }; // scope // class script; class scope { public: scope* const parent; // NULL for the root (script) scope. script* const root; // Self for the root (script) scope. // The chain of if-else scope alternatives. See also if_cond_ below. // unique_ptr if_chain; // Note that if we pass the variable name as a string, then it will // be looked up in the wrong pool. // variable_map vars; const path& id_path; // Id path ($@, relative in POSIX form). const dir_path& wd_path; // Working dir ($~, absolute and normalized). optional desc; test::script::cleanups cleanups; // Variables. // public: // Lookup the variable starting from this scope, continuing with outer // scopes, then the target being tested, then the testscript target, // and then outer buildfile scopes (including testscript-type/pattern // specific). // lookup find (const variable&) const; // As above but only look for buildfile variables. // lookup find_in_buildfile (const string&) const; // Return a value suitable for assignment. If the variable does not // exist in this scope's map, then a new one with the NULL value is // added and returned. Otherwise the existing value is returned. // value& assign (const variable& var) {return vars.assign (var);} // Return a value suitable for append/prepend. If the variable does // not exist in this scope's map, then outer scopes are searched for // the same variable. If found then a new variable with the found // value is added to this scope and returned. Otherwise this function // proceeds as assign() above. // value& append (const variable&); // Reset special $*, $N variables based on the test.* values. // void reset_special (); // Cleanup. // public: // 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 script working directory. // void clean (cleanup, bool implicit); public: virtual ~scope () = default; protected: scope (const string& id, scope* parent); // Pre-parse data. // public: virtual bool empty () const = 0; protected: friend class parser; location start_loc_; location end_loc_; optional if_cond_; }; // group // class group: public scope { public: vector> scopes; public: group (const string& id, group& p): scope (id, &p) {} protected: group (const string& id): scope (id, nullptr) {} // For root. // Pre-parse data. // public: virtual bool empty () const override { return setup_.empty () && tdown_.empty () && find_if (scopes.begin (), scopes.end (), [] (const unique_ptr& s) { return !s->empty (); }) == scopes.end (); } private: friend class parser; lines setup_; lines tdown_; }; // test // class test: public scope { public: test (const string& id, group& p): scope (id, &p) {} // Pre-parse data. // public: virtual bool empty () const override { return tests_.empty (); } private: friend class parser; lines tests_; }; // script // class script_base // Make sure certain things are initialized early. { protected: script_base (); public: variable_pool var_pool; const variable& test_var; // test const variable& opts_var; // test.options const variable& args_var; // test.arguments const variable& wd_var; // $~ const variable& id_var; // $@ const variable& cmd_var; // $* const variable* cmdN_var[10]; // $N }; class script: public script_base, public group { public: script (target& test_target, testscript& script_target, const dir_path& root_wd); script (script&&) = delete; script (const script&) = delete; script& operator= (script&&) = delete; script& operator= (const script&) = delete; public: target& test_target; // Target we are testing. testscript& script_target; // Target of the testscript file. // Pre-parse data. // private: friend class parser; // Testscript file paths. Specifically, replay_token::file points to // these paths. // std::set paths_; }; } } } #include #endif // BUILD2_TEST_SCRIPT_SCRIPT