From 879b5f52cb86f24352f4ed245fcce5f1ab885f97 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 30 Nov 2017 14:48:19 +0200 Subject: Implement support for scope operation callbacks An entity (module, core) can register a function that will be called when an action is executed on the dir{} target that corresponds to the scope. The pre callback is called just before the recipe and the post -- immediately after. --- build2/algorithm.cxx | 102 +++++++++++++++++++++++++++++++++++++++++------- build2/scope.hxx | 33 +++++++++++++++- build2/target-state.hxx | 44 +++++++++++++++++++++ build2/target.hxx | 35 +---------------- 4 files changed, 166 insertions(+), 48 deletions(-) create mode 100644 build2/target-state.hxx diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index 6976343..fc71c1a 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -843,13 +843,13 @@ namespace build2 return r; } + // Execute the specified recipe (if any) and the scope operation callbacks + // (if any/applicable) then merge and return the resulting target state. + // static target_state - execute_impl (action a, target& t) + execute_recipe (action a, target& t, const recipe& r) { - assert (t.task_count.load (memory_order_consume) == target::count_busy () - && t.state_ == target_state::unknown); - - target_state ts; + target_state ts (target_state::unknown); try { @@ -860,14 +860,55 @@ namespace build2 dr << info << "while " << diag_doing (a, t); }); - ts = t.recipe_ (a, t); + // If this is a dir{} target, see if we have any operation callbacks + // in the corresponding scope. + // + const dir* op_t (t.is_a ()); + const scope* op_s (nullptr); + + using op_iterator = scope::operation_callback_map::const_iterator; + pair op_p; + + if (op_t != nullptr) + { + op_s = &scopes.find (t.dir); + + if (op_s->out_path () == t.dir && !op_s->operation_callbacks.empty ()) + { + op_p = op_s->operation_callbacks.equal_range (a); + + if (op_p.first == op_p.second) + op_s = nullptr; // Ignore. + } + else + op_s = nullptr; // Ignore. + } + + // Pre operations. + // + // Note that here we assume the dir{} target cannot be part of a group + // and as a result we (a) don't try to avoid calling post callbacks in + // case of a group failure and (b) merge the pre and post states with + // the group state. + // + if (op_s != nullptr) + { + for (auto i (op_p.first); i != op_p.second; ++i) + if (const auto& f = i->second.pre) + ts |= f (a, *op_s, *op_t); + } + + // Recipe. + // + ts |= r != nullptr ? r (a, t) : target_state::unchanged; - // Decrement the target count (see target::recipe() for details). + // Post operations. // + if (op_s != nullptr) { - recipe_function** f (t.recipe_.target ()); - if (f == nullptr || *f != &group_action) - target_count.fetch_sub (1, memory_order_relaxed); + for (auto i (op_p.first); i != op_p.second; ++i) + if (const auto& f = i->second.post) + ts |= f (a, *op_s, *op_t); } // See the recipe documentation for details on what's going on here. @@ -894,6 +935,25 @@ namespace build2 ts = t.state_ = target_state::failed; } + return ts; + } + + static target_state + execute_impl (action a, target& t) + { + assert (t.task_count.load (memory_order_consume) == target::count_busy () + && t.state_ == target_state::unknown); + + target_state ts (execute_recipe (a, t, t.recipe_)); + + // Decrement the target count (see target::recipe() for details). + // + { + recipe_function** f (t.recipe_.target ()); + if (f == nullptr || *f != &group_action) + target_count.fetch_sub (1, memory_order_relaxed); + } + // Decrement the task count (to count_executed) and wake up any threads // that might be waiting for this target. // @@ -950,10 +1010,11 @@ namespace build2 // size_t touc (target::count_touched ()); size_t matc (target::count_matched ()); + size_t appc (target::count_applied ()); size_t exec (target::count_executed ()); size_t busy (target::count_busy ()); - for (size_t tc (target::count_applied ());;) + for (size_t tc (appc);;) { if (t.task_count.compare_exchange_strong ( tc, @@ -965,7 +1026,13 @@ namespace build2 // if ((tc >= touc && tc <= matc) || t.state_ == target_state::unchanged) { - t.state_ = target_state::unchanged; + // If we have a noop recipe, there could still be scope operations. + // + if (tc == appc && t.is_a ()) + execute_recipe (a, t, nullptr /* recipe */); + else + t.state_ = target_state::unchanged; + t.task_count.store (exec, memory_order_release); sched.resume (t.task_count); } @@ -1022,10 +1089,11 @@ namespace build2 // size_t touc (target::count_touched ()); size_t matc (target::count_matched ()); + size_t appc (target::count_applied ()); size_t exec (target::count_executed ()); size_t busy (target::count_busy ()); - for (size_t tc (target::count_applied ());;) + for (size_t tc (appc);;) { if (t.task_count.compare_exchange_strong ( tc, @@ -1035,7 +1103,13 @@ namespace build2 { if ((tc >= touc && tc <= matc) || t.state_ == target_state::unchanged) { - t.state_ = target_state::unchanged; + // If we have a noop recipe, there could still be scope operations. + // + if (tc == appc && t.is_a ()) + execute_recipe (a, t, nullptr /* recipe */); + else + t.state_ = target_state::unchanged; + t.task_count.store (exec, memory_order_release); sched.resume (t.task_count); } diff --git a/build2/scope.hxx b/build2/scope.hxx index ad1b295..00aba52 100644 --- a/build2/scope.hxx +++ b/build2/scope.hxx @@ -5,8 +5,8 @@ #ifndef BUILD2_SCOPE_HXX #define BUILD2_SCOPE_HXX +#include #include -#include #include @@ -17,11 +17,14 @@ #include #include #include +#include #include #include namespace build2 { + class dir; + class scope { public: @@ -240,6 +243,34 @@ namespace build2 public: rule_map rules; + // Operation callbacks. + // + // An entity (module, core) can register a function that will be called + // when an action is executed on the dir{} target that corresponds to this + // scope. The pre callback is called just before the recipe and the post + // -- immediately after. The callbacks are only called if the recipe + // (including noop recipe) is executed for the corresponding target. The + // callbacks should only be registered during the load phase. + // + // It only makes sense for callbacks to return target_state changed or + // unchanged and to throw failed in case of an error. These pre/post + // states will be merged with the recipe state and become the target + // state. See execute_recipe() for details. + // + public: + struct operation_callback + { + using callback = target_state (action, const scope&, const dir&); + + function pre; + function post; + }; + + using operation_callback_map = std::multimap; + + operation_callback_map operation_callbacks; + // Modules. // public: diff --git a/build2/target-state.hxx b/build2/target-state.hxx new file mode 100644 index 0000000..2913abb --- /dev/null +++ b/build2/target-state.hxx @@ -0,0 +1,44 @@ +// file : build2/target-state.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TARGET_STATE_HXX +#define BUILD2_TARGET_STATE_HXX + +#include +#include + +namespace build2 +{ + // The order of the enumerators is arranged so that their integral values + // indicate whether one "overrides" the other in the "merge" operator| + // (see below). + // + // Note that postponed is "greater" than unchanged since it may result in + // the changed state. + // + enum class target_state: uint8_t + { + unknown, + unchanged, + postponed, + busy, + changed, + failed, + group // Target's state is the group's state. + }; + + inline target_state& + operator |= (target_state& l, target_state r) + { + if (static_cast (r) > static_cast (l)) + l = r; + + return l; + } + + ostream& + operator<< (ostream&, target_state); // target.cxx +} + +#endif // BUILD2_TARGET_STATE_HXX diff --git a/build2/target.hxx b/build2/target.hxx index e541667..632e723 100644 --- a/build2/target.hxx +++ b/build2/target.hxx @@ -17,8 +17,9 @@ #include #include #include -#include #include +#include +#include #include namespace build2 @@ -34,38 +35,6 @@ namespace build2 const target& search (const target&, const prerequisite&); const target* search_existing (const prerequisite&); - // Target state. - // - enum class target_state: uint8_t - { - // The order of the enumerators is arranged so that their integral values - // indicate whether one "overrides" the other in the "merge" operator| - // (see below). - // - // Note that postponed is "greater" than unchanged since it may result in - // the changed state. - // - unknown, - unchanged, - postponed, - busy, - changed, - failed, - group // Target's state is the group's state. - }; - - ostream& - operator<< (ostream&, target_state); - - inline target_state& - operator |= (target_state& l, target_state r) - { - if (static_cast (r) > static_cast (l)) - l = r; - - return l; - } - // Recipe. // // The returned target state is normally changed or unchanged. If there is -- cgit v1.1