summaryrefslogtreecommitdiff
path: root/src/buildtool/execution_engine/traverser/traverser.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/buildtool/execution_engine/traverser/traverser.hpp')
-rw-r--r--src/buildtool/execution_engine/traverser/traverser.hpp187
1 files changed, 187 insertions, 0 deletions
diff --git a/src/buildtool/execution_engine/traverser/traverser.hpp b/src/buildtool/execution_engine/traverser/traverser.hpp
new file mode 100644
index 00000000..ea44f30c
--- /dev/null
+++ b/src/buildtool/execution_engine/traverser/traverser.hpp
@@ -0,0 +1,187 @@
+#ifndef INCLUDED_SRC_BUILDTOOL_EXECUTION_ENGINE_TRAVERSER_TRAVERSER_HPP
+#define INCLUDED_SRC_BUILDTOOL_EXECUTION_ENGINE_TRAVERSER_TRAVERSER_HPP
+
+#include "gsl-lite/gsl-lite.hpp"
+#include "src/buildtool/execution_engine/dag/dag.hpp"
+#include "src/buildtool/logging/logger.hpp"
+#include "src/buildtool/multithreading/task_system.hpp"
+#include "src/utils/cpp/concepts.hpp"
+
+/// \brief Concept required for Runners used by the Traverser.
+template <class T>
+concept Runnable = requires(T const r,
+ DependencyGraph::ActionNode const* action,
+ DependencyGraph::ArtifactNode const* artifact) {
+ { r.Process(action) }
+ ->same_as<bool>;
+ { r.Process(artifact) }
+ ->same_as<bool>;
+};
+
+/// \brief Class to traverse the dependency graph executing necessary actions
+/// \tparam Executor Type of the executor
+// Traversal of the graph and execution of actions are concurrent, using
+/// the //src/buildtool/execution_engine/task_system.
+/// Graph remains constant and the only parts of the nodes that are modified are
+/// their traversal state
+template <Runnable Executor>
+class Traverser {
+ public:
+ Traverser(Executor const& r, DependencyGraph const& graph, std::size_t jobs)
+ : runner_{r}, graph_{graph}, tasker_{jobs} {}
+ Traverser() = delete;
+ Traverser(Traverser const&) = delete;
+ Traverser(Traverser&&) = delete;
+ auto operator=(Traverser const&) -> Traverser& = delete;
+ auto operator=(Traverser &&) -> Traverser& = delete;
+ ~Traverser() = default;
+
+ // Traverse the whole graph
+ [[nodiscard]] auto Traverse() noexcept -> bool {
+ auto const& ids = graph_.ArtifactIdentifiers();
+ return Traverse(ids);
+ };
+
+ // Traverse starting by the artifacts with the given identifiers, avoiding
+ // executing actions that are not strictly needed to build the given
+ // artifacs
+ [[nodiscard]] auto Traverse(
+ std::unordered_set<ArtifactIdentifier> const& target_ids) noexcept
+ -> bool;
+
+ private:
+ Executor const& runner_{};
+ DependencyGraph const& graph_;
+ TaskSystem tasker_{}; // THIS SHOULD BE THE LAST MEMBER VARIABLE
+
+ // Visits discover nodes and queue visits to their children nodes.
+ void Visit(gsl::not_null<DependencyGraph::ArtifactNode const*>
+ artifact_node) noexcept;
+ void Visit(
+ gsl::not_null<DependencyGraph::ActionNode const*> action_node) noexcept;
+
+ // Notify all actions that have it as a dependency that it is available and
+ // queue execution of those that become ready (that were only waiting for
+ // this artifact)
+ void NotifyAvailable(
+ gsl::not_null<DependencyGraph::ArtifactNode const*> const&
+ artifact_node) noexcept;
+
+ // Calls NotifyAvailable on all the action's outputs
+ void NotifyAvailable(
+ gsl::not_null<DependencyGraph::ActionNode const*> const&
+ action_node) noexcept;
+
+ // Visit to nodes are queued only once
+ template <typename NodeTypePtr>
+ void QueueVisit(NodeTypePtr node) noexcept {
+ // in case the node was already discovered, there is no need to queue
+ // the visit
+ if (node->TraversalState()->GetAndMarkDiscovered()) {
+ return;
+ }
+ tasker_.QueueTask([this, node]() noexcept { Visit(node); });
+ }
+
+ // Queue task to process the node by the executor after making sure that the
+ // node is required and that it was not yet queued to be processed. The task
+ // queued will call notify that the node is available in case processing it
+ // was successful
+ template <typename NodeTypePtr>
+ void QueueProcessing(NodeTypePtr node) noexcept {
+ if (not node->TraversalState()->IsRequired() or
+ node->TraversalState()->GetAndMarkQueuedToBeProcessed()) {
+ return;
+ }
+
+ auto process_node = [this, node]() {
+ if (runner_.Process(node)) {
+ NotifyAvailable(node);
+ }
+ else {
+ Logger::Log(LogLevel::Error, "Build failed.");
+ std::exit(EXIT_FAILURE);
+ }
+ };
+ tasker_.QueueTask(process_node);
+ }
+};
+
+template <Runnable Executor>
+auto Traverser<Executor>::Traverse(
+ std::unordered_set<ArtifactIdentifier> const& target_ids) noexcept -> bool {
+ for (auto artifact_id : target_ids) {
+ auto const* artifact_node = graph_.ArtifactNodeWithId(artifact_id);
+ if (artifact_node != nullptr) {
+ QueueVisit(artifact_node);
+ }
+ else {
+ Logger::Log(
+ LogLevel::Error,
+ "artifact with id {} can not be found in dependency graph.",
+ artifact_id);
+ return false;
+ }
+ }
+ return true;
+}
+
+template <Runnable Executor>
+void Traverser<Executor>::Visit(
+ gsl::not_null<DependencyGraph::ArtifactNode const*>
+ artifact_node) noexcept {
+ artifact_node->TraversalState()->MarkRequired();
+ // Visits are queued only once per artifact node, but it could be that the
+ // builder action had multiple outputs and was queued and executed through
+ // the visit to another of the outputs, in which case the current artifact
+ // would be available and there is nothing else to do
+ if (artifact_node->TraversalState()->IsAvailable()) {
+ return;
+ }
+
+ if (artifact_node->HasBuilderAction()) {
+ QueueVisit(gsl::not_null<DependencyGraph::ActionNode const*>(
+ artifact_node->BuilderActionNode()));
+ }
+ else {
+ QueueProcessing(artifact_node);
+ }
+}
+
+template <Runnable Executor>
+void Traverser<Executor>::Visit(
+ gsl::not_null<DependencyGraph::ActionNode const*> action_node) noexcept {
+ action_node->TraversalState()->MarkRequired();
+ for (auto const& dep : action_node->Children()) {
+ if (not dep->TraversalState()->IsAvailable()) {
+ QueueVisit(dep);
+ }
+ }
+
+ if (action_node->TraversalState()->IsReady()) {
+ QueueProcessing(action_node);
+ }
+}
+
+template <Runnable Executor>
+void Traverser<Executor>::NotifyAvailable(
+ gsl::not_null<DependencyGraph::ArtifactNode const*> const&
+ artifact_node) noexcept {
+ artifact_node->TraversalState()->MakeAvailable();
+ for (auto const& action_node : artifact_node->Parents()) {
+ if (action_node->TraversalState()->NotifyAvailableDepAndCheckReady()) {
+ QueueProcessing(action_node);
+ }
+ }
+}
+
+template <Runnable Executor>
+void Traverser<Executor>::NotifyAvailable(
+ gsl::not_null<DependencyGraph::ActionNode const*> const&
+ action_node) noexcept {
+ for (auto const& output : action_node->Parents()) {
+ NotifyAvailable(output);
+ }
+}
+
+#endif // INCLUDED_SRC_BUILDTOOL_EXECUTION_ENGINE_TRAVERSER_TRAVERSER_HPP