aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-12-16 09:43:38 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-12-16 09:43:38 +0200
commit630dc4ccf3207f7cdd5b410582e1e572081b80e8 (patch)
treedc6b4dfb5608799fc63f4f1b6f6fceee8530cb0c
parentb3df2f69ff340e2c5c6d215bea6689594f0a3d80 (diff)
Add support for structured result output (--structured-result)
-rw-r--r--build2/b-options.cxx19
-rw-r--r--build2/b-options.hxx4
-rw-r--r--build2/b-options.ixx6
-rw-r--r--build2/b.cli26
-rw-r--r--build2/b.cxx115
-rw-r--r--build2/config/operation.cxx8
-rw-r--r--build2/dist/operation.cxx14
-rw-r--r--build2/operation.cxx24
-rw-r--r--build2/operation.hxx30
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 <buildspec> for short.
This process can be controlled by specifying driver <options> and build
system <variables>.
@@ -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<uint16_t> (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<uint16_t> (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<const target*> (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<const scope*> (v));
+ const scope& root (*static_cast<const scope*> (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<const target*> (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<const target*> (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> (module::name));
- for (const void* v: files)
+ for (const action_target& at: files)
{
- const file& t (*static_cast<const file*> (v));
+ const file& t (*at.as_target ().is_a<file> ());
// 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<const target*> (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<const target*> (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<const target*> (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<const target*> (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<const scope*> (ts[i]));
+ const scope& s (*static_cast<const scope*> (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 <build2/utility.hxx>
#include <build2/variable.hxx>
+#include <build2/target-state.hxx>
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<const void*> 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<const target_type*> (target);}
+ };
+
+ class action_targets: public vector<action_target>
+ {
+ public:
+ using vector<action_target>::vector;
+
+ void
+ reset () {for (auto& x: *this) x.state = target_state::unknown;}
+ };
+
struct meta_operation_info
{