aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-11-30 14:48:19 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-11-30 14:48:19 +0200
commit879b5f52cb86f24352f4ed245fcce5f1ab885f97 (patch)
treeff483ce4aa64dda7d17b82f956333f0705257568
parent074a8c04a384a9752466bd2af69b695333b2955c (diff)
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.
-rw-r--r--build2/algorithm.cxx102
-rw-r--r--build2/scope.hxx33
-rw-r--r--build2/target-state.hxx44
-rw-r--r--build2/target.hxx35
4 files changed, 166 insertions, 48 deletions
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<dir> ());
+ const scope* op_s (nullptr);
+
+ using op_iterator = scope::operation_callback_map::const_iterator;
+ pair<op_iterator, op_iterator> 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<recipe_function*> ());
- 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<recipe_function*> ());
+ 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<dir> ())
+ 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<dir> ())
+ 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 <map>
#include <unordered_set>
-#include <unordered_map>
#include <libbutl/path-map.mxx>
@@ -17,11 +17,14 @@
#include <build2/variable.hxx>
#include <build2/target-key.hxx>
#include <build2/target-type.hxx>
+#include <build2/target-state.hxx>
#include <build2/rule-map.hxx>
#include <build2/operation.hxx>
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<callback> pre;
+ function<callback> post;
+ };
+
+ using operation_callback_map = std::multimap<action_id,
+ operation_callback>;
+
+ 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 <build2/types.hxx>
+#include <build2/utility.hxx>
+
+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<uint8_t> (r) > static_cast<uint8_t> (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 <build2/scope.hxx>
#include <build2/variable.hxx>
#include <build2/operation.hxx>
-#include <build2/target-type.hxx>
#include <build2/target-key.hxx>
+#include <build2/target-type.hxx>
+#include <build2/target-state.hxx>
#include <build2/prerequisite.hxx>
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<uint8_t> (r) > static_cast<uint8_t> (l))
- l = r;
-
- return l;
- }
-
// Recipe.
//
// The returned target state is normally changed or unchanged. If there is