From 630dc4ccf3207f7cdd5b410582e1e572081b80e8 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sat, 16 Dec 2017 09:43:38 +0200 Subject: Add support for structured result output (--structured-result) --- build2/b-options.cxx | 19 +++++++- build2/b-options.hxx | 4 ++ build2/b-options.ixx | 6 +++ build2/b.cli | 26 +++++++++- build2/b.cxx | 115 ++++++++++++++++++++++++++++++++++++-------- build2/config/operation.cxx | 8 +-- build2/dist/operation.cxx | 14 +++--- build2/operation.cxx | 24 +++++---- build2/operation.hxx | 30 +++++++++++- 9 files changed, 202 insertions(+), 44 deletions(-) diff --git a/build2/b-options.cxx b/build2/b-options.cxx index 6ee835d..acb5a7e 100644 --- a/build2/b-options.cxx +++ b/build2/b-options.cxx @@ -583,6 +583,7 @@ namespace build2 queue_depth_ (4), queue_depth_specified_ (false), serial_stop_ (), + structured_result_ (), match_only_ (), no_column_ (), no_line_ (), @@ -734,6 +735,20 @@ namespace build2 << " default concurrency)." << ::std::endl; os << std::endl + << "\033[1m--structured-result\033[0m Write the result of executing actions on targets in a" << ::std::endl + << " structured form. In this mode, instead of printing to" << ::std::endl + << " STDERR\033[0m diagnostics messages about the outcome of executing" << ::std::endl + << " actions on targets, the driver writes to STDOUT\033[0m a" << ::std::endl + << " structured result description one line per the" << ::std::endl + << " action/target pair. Each line has the following format:" << ::std::endl + << ::std::endl + << " \033[4mstate\033[0m \033[4mmeta-operation\033[0m \033[4moperation\033[0m \033[4mtarget\033[0m" << ::std::endl + << ::std::endl + << " Where \033[4mstate\033[0m can be one of \033[1munchanged\033[0m, \033[1mchanged\033[0m, or \033[1mfailed\033[0m." << ::std::endl + << " If the action is a pre or post operation, then the outer" << ::std::endl + << " operation is specified in parenthesis. For example:" << ::std::endl; + + os << std::endl << "\033[1m--match-only\033[0m Match the rules but do not execute the operation. This" << ::std::endl << " mode is primarily useful for profiling." << ::std::endl; @@ -841,6 +856,8 @@ namespace build2 &::build2::cl::thunk< options, bool, &options::serial_stop_ >; _cli_options_map_["-s"] = &::build2::cl::thunk< options, bool, &options::serial_stop_ >; + _cli_options_map_["--structured-result"] = + &::build2::cl::thunk< options, bool, &options::structured_result_ >; _cli_options_map_["--match-only"] = &::build2::cl::thunk< options, bool, &options::match_only_ >; _cli_options_map_["--no-column"] = @@ -968,7 +985,7 @@ namespace build2 << ::std::endl << "\033[1mDESCRIPTION\033[0m" << ::std::endl << ::std::endl - << "The \033[1mbuild2\033[0m driver performs a set of meta-operations on operations on targets" << ::std::endl + << "The \033[1mbuild2\033[0m driver executes a set of meta-operations on operations on targets" << ::std::endl << "according to the build specification, or \033[4mbuildspec\033[0m for short. This process can" << ::std::endl << "be controlled by specifying driver \033[4moptions\033[0m and build system \033[4mvariables\033[0m." << ::std::endl << ::std::endl diff --git a/build2/b-options.hxx b/build2/b-options.hxx index 535e70c..b6edae8 100644 --- a/build2/b-options.hxx +++ b/build2/b-options.hxx @@ -438,6 +438,9 @@ namespace build2 serial_stop () const; const bool& + structured_result () const; + + const bool& match_only () const; const bool& @@ -515,6 +518,7 @@ namespace build2 size_t queue_depth_; bool queue_depth_specified_; bool serial_stop_; + bool structured_result_; bool match_only_; bool no_column_; bool no_line_; diff --git a/build2/b-options.ixx b/build2/b-options.ixx index 1c99dc3..ef304d3 100644 --- a/build2/b-options.ixx +++ b/build2/b-options.ixx @@ -301,6 +301,12 @@ namespace build2 } inline const bool& options:: + structured_result () const + { + return this->structured_result_; + } + + inline const bool& options:: match_only () const { return this->match_only_; diff --git a/build2/b.cli b/build2/b.cli index 724a676..d7634c0 100644 --- a/build2/b.cli +++ b/build2/b.cli @@ -21,7 +21,7 @@ namespace build2 \h|DESCRIPTION| - The \cb{build2} driver performs a set of meta-operations on operations on + The \cb{build2} driver executes a set of meta-operations on operations on targets according to the build specification, or for short. This process can be controlled by specifying driver and build system . @@ -424,6 +424,30 @@ namespace build2 \c{-j 0} for default concurrency)." } + bool --structured-result + { + "Write the result of execution in a structured form. In this mode, + instead of printing to \cb{STDERR} diagnostics messages about the + outcome of executing actions on targets, the driver writes to + \cb{STDOUT} a structured result description one line per the + buildspec action/target pair. Each line has the following format: + + \c{\i{state} \i{meta-operation} \i{operation} \i{target}} + + Where \ci{state} can be one of \cb{unchanged}, \cb{changed}, or + \cb{failed}. If the action is a pre or post operation, then the + outer operation is specified in parenthesis. For example: + + \ + unchanged perform update(test) dir{./} + changed perform test dir{./} + \ + + Currently only the \cb{perform} meta-operation supports the structured + result output. + " + } + bool --match-only { "Match the rules but do not execute the operation. This mode is primarily diff --git a/build2/b.cxx b/build2/b.cxx index a88d1c0..0d88f5f 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -57,12 +57,67 @@ namespace build2 { int main (int argc, char* argv[]); -} -int -main (int argc, char* argv[]) -{ - return build2::main (argc, argv); + // Structured result printer (--structured-result mode). + // + class result_printer + { + public: + result_printer (const action_targets& tgs): tgs_ (tgs) {} + ~result_printer (); + + private: + const action_targets& tgs_; + }; + + result_printer:: + ~result_printer () + { + // Let's do some sanity checking even when we are not in the structred + // output mode. + // + for (const action_target& at: tgs_) + { + switch (at.state) + { + case target_state::unknown: continue; // Not a target/no result. + case target_state::unchanged: + case target_state::changed: + case target_state::failed: break; // Valid states. + default: assert (false); + } + + if (ops.structured_result ()) + { + cout << at.state + << ' ' << current_mif->name + << ' ' << current_inner_oif->name; + + if (current_outer_oif != nullptr) + cout << '(' << current_outer_oif->name << ')'; + + // There are two ways one may wish to identify the target of the + // operation: as something specific but inherently non-portable (say, + // a filesystem path, for example c:\tmp\foo.exe) or as something + // regular that can be used to refer to a target in a portable way + // (for example, c:\tmp\exe{foo}; note that the directory part is + // still not portable). Which one should we use is a good question. + // Let's go with the portable one for now and see how it goes (we + // can always add a format version, e.g., --structured-result=2). + + // Set the stream verbosity to 0 to suppress extension printing by + // default (this can still be overriden by the target type's print + // function as is the case for file{}, for example). + // + uint16_t v (stream_verb (cout)); + stream_verb (cout, 0); + + cout << ' ' << at.as_target () << endl; + + stream_verb (cout, v); + } + } + } } int build2:: @@ -1143,33 +1198,43 @@ main (int argc, char* argv[]) action a (mid, pre_oid, oid); - // Run quiet. - // - if (mif->match != nullptr) - mif->match (mparams, a, tgs); + { + result_printer p (tgs); - if (mif->execute != nullptr && !ops.match_only ()) - mif->execute (mparams, a, tgs, true); + if (mif->match != nullptr) + mif->match (mparams, a, tgs); + + if (mif->execute != nullptr && !ops.match_only ()) + mif->execute (mparams, a, tgs, true /* quiet */); + } if (mif->operation_post != nullptr) mif->operation_post (mparams, pre_oid); l5 ([&]{trace << "end pre-operation batch " << pre_oif->name << ", id " << static_cast (pre_oid);}); + + tgs.reset (); } set_current_oif (*oif); action a (mid, oid, 0); - if (mif->match != nullptr) - mif->match (mparams, a, tgs); + { + result_printer p (tgs); - if (mif->execute != nullptr && !ops.match_only ()) - mif->execute (mparams, a, tgs, verb == 0); + if (mif->match != nullptr) + mif->match (mparams, a, tgs); + + if (mif->execute != nullptr && !ops.match_only ()) + mif->execute (mparams, a, tgs, ops.structured_result () /*quiet*/); + } if (post_oid != 0) { + tgs.reset (); + l5 ([&]{trace << "start post-operation batch " << post_oif->name << ", id " << static_cast (post_oid);}); @@ -1180,13 +1245,15 @@ main (int argc, char* argv[]) action a (mid, post_oid, oid); - // Run quiet. - // - if (mif->match != nullptr) - mif->match (mparams, a, tgs); + { + result_printer p (tgs); - if (mif->execute != nullptr && !ops.match_only ()) - mif->execute (mparams, a, tgs, true); + if (mif->match != nullptr) + mif->match (mparams, a, tgs); + + if (mif->execute != nullptr && !ops.match_only ()) + mif->execute (mparams, a, tgs, true /* quiet */); + } if (mif->operation_post != nullptr) mif->operation_post (mparams, post_oid); @@ -1248,3 +1315,9 @@ main (int argc, char* argv[]) return r; } + +int +main (int argc, char* argv[]) +{ + return build2::main (argc, argv); +} diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index 64919f8..c5de54f 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -369,9 +369,9 @@ namespace build2 // target-specific. However, inside match(), things can proceed in // parallel. // - for (const void* v: ts) + for (const action_target& at: ts) { - const target& t (*static_cast (v)); + const target& t (at.as_target ()); const scope* rs (t.base_scope ().root_scope ()); if (rs == nullptr) @@ -600,9 +600,9 @@ namespace build2 // Note: doing everything in the load phase (disfigure_project () does // modify the model). // - for (const void* v: ts) + for (const action_target& at: ts) { - const scope& root (*static_cast (v)); + const scope& root (*static_cast (at.target)); if (!disfigure_project (a, root, projects)) { diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx index d63e483..7539fc1 100644 --- a/build2/dist/operation.cxx +++ b/build2/dist/operation.cxx @@ -60,7 +60,7 @@ namespace build2 // For now we assume all the targets are from the same project. // - const target& t (*static_cast (ts[0])); + const target& t (ts[0].as_target ()); const scope* rs (t.base_scope ().root_scope ()); if (rs == nullptr) @@ -99,9 +99,9 @@ namespace build2 // Verify all the targets are from the same project. // - for (const void* v: ts) + for (const action_target& at: ts) { - const target& t (*static_cast (v)); + const target& t (at.as_target ()); if (rs != t.base_scope ().root_scope ()) fail << "target " << t << " is from a different project" << @@ -252,6 +252,8 @@ namespace build2 // things down while this little cheat seems harmless (i.e., assume // the dist mete-opreation is "compatible" with perform). // + // Note also that we don't do any structured result printing. + // size_t on (current_on); set_current_mif (mo_perform); current_on = on + 1; @@ -264,7 +266,7 @@ namespace build2 action a (perform_id, update_id); mo_perform.match (params, a, files); - mo_perform.execute (params, a, files, true); // Run quiet. + mo_perform.execute (params, a, files, true /* quiet */); if (mo_perform.operation_post != nullptr) mo_perform.operation_post (params, update_id); @@ -288,9 +290,9 @@ namespace build2 // module& mod (*rs->modules.lookup (module::name)); - for (const void* v: files) + for (const action_target& at: files) { - const file& t (*static_cast (v)); + const file& t (*at.as_target ().is_a ()); // Figure out where this file is inside the target directory. // diff --git a/build2/operation.cxx b/build2/operation.cxx index d917f1e..fab5cf5 100644 --- a/build2/operation.cxx +++ b/build2/operation.cxx @@ -157,7 +157,7 @@ namespace build2 for (; i != n; ++i) { - const target& t (*static_cast (ts[i])); + const target& t (ts[i].as_target ()); l5 ([&]{trace << diag_doing (a, t);}); target_state s (match_async (a, t, 0, task_count, false)); @@ -188,7 +188,8 @@ namespace build2 bool fail (false); for (size_t j (0); j != n; ++j) { - const target& t (*static_cast (ts[j])); + action_target& at (ts[j]); + const target& t (at.as_target ()); // Finish matching targets that we have started. Note that we use the // state for the "final" action that will be executed and not our @@ -208,7 +209,8 @@ namespace build2 { case target_state::postponed: { - // We bailed before matching it. + // We bailed before matching it (leave state in action_target as + // unknown). // if (verb != 0) info << "not " << diag_did (a, t); @@ -227,6 +229,7 @@ namespace build2 if (verb != 0) info << "failed to " << diag_do (a, t); + at.state = s; fail = true; break; } @@ -315,9 +318,9 @@ namespace build2 atomic_count task_count (0); wait_guard wg (task_count); - for (const void* vt: ts) + for (const action_target& at: ts) { - const target& t (*static_cast (vt)); + const target& t (at.as_target ()); l5 ([&]{trace << diag_doing (a, t);}); @@ -361,15 +364,16 @@ namespace build2 // Re-examine all the targets and print diagnostics. // bool fail (false); - for (const void* vt: ts) + for (action_target& at: ts) { - const target& t (*static_cast (vt)); + const target& t (at.as_target ()); - switch (t.executed_state (false)) + switch ((at.state = t.executed_state (false))) { case target_state::unknown: { - // We bailed before executing it. + // We bailed before executing it (leave state in action_target as + // unknown). // if (verb != 0 && !quiet) info << "not " << diag_did (a, t); @@ -491,7 +495,7 @@ namespace build2 if (i != 0) cout << endl; - const scope& s (*static_cast (ts[i])); + const scope& s (*static_cast (ts[i].target)); // This could be a simple project that doesn't set project name. // diff --git a/build2/operation.hxx b/build2/operation.hxx index abd85c0..c157e0a 100644 --- a/build2/operation.hxx +++ b/build2/operation.hxx @@ -11,12 +11,15 @@ #include #include +#include namespace build2 { class location; class scope; class target_key; + class target; + struct opspec; // While we are using uint8_t for the meta/operation ids, we assume @@ -181,7 +184,32 @@ namespace build2 // Normally a list of resolved and matched targets to execute. But can be // something else, depending on the meta-operation. // - typedef vector action_targets; + // The state is used to print structured result state. If it is not unknown, + // then this is assumed to be a target. + // + struct action_target + { + using target_type = build2::target; + + const void* target = nullptr; + target_state state = target_state::unknown; + + action_target () = default; + action_target (const void* t): target (t) {} + + const target_type& + as_target () const {return *static_cast (target);} + }; + + class action_targets: public vector + { + public: + using vector::vector; + + void + reset () {for (auto& x: *this) x.state = target_state::unknown;} + }; + struct meta_operation_info { -- cgit v1.1