class Task
Represents a single unit of work within a build graph walk.
Definitions
def initialize(walker, node)
Create a new task associated with the given walker and node.
Signature
-
parameter
walkerWalker the walker driving the graph traversal.
-
parameter
nodeNode the node this task is responsible for updating.
Implementation
def initialize(walker, node)
@walker = walker
@walker.tasks[node] = self
@node = node
@fiber = nil
@error = nil
# Tasks that must be complete before finishing this task.
@children = []
@state = nil
@inputs_failed = false
end
attr :state
The state of the task, one of nil, :complete or :failed.
attr :error
The error, if the execution of the node fails.
attr :inputs_failed
A list of any inputs whose relevant tasks failed:
def traverse
Traverse the graph without performing any real work. Unlike Build::Graph::Task#visit, this does not
wait for inputs to be generated, making it suitable for graph analysis tasks such
as visualization where actual files do not need to exist.
Implementation
def traverse
update_inputs_and_outputs
@walker.enter(self)
update_outputs!
@state = :complete
@walker.exit(self)
return self
end
def visit
Derived task can override this function to provide appropriate behaviour.
Implementation
def visit
update_inputs_and_outputs
# Inforn the walker a new task is being generated for this node:
@walker.enter(self)
if @fiber
raise RuntimeError, "Task is already running!"
end
@fiber = Fiber.new do
# If all inputs were good, we can update the node.
if wait_for_inputs?
begin
yield
rescue TransientError => error
fail!(error)
end
else
fail!(InputsFailed)
end
wait_for_children!
update_outputs!
@state ||= :complete
@walker.exit(self)
@fiber = nil
end
# Schedule the work, hopefully synchronously:
@fiber.resume
# This allows the child task to be passed back to the parent when it is first invoked.
return self
end
def invoke(node)
Implementation
def invoke(node)
child_task = @walker.call(node, self)
raise ArgumentError.new("Invalid child task") unless child_task
@children << child_task
return child_task
end
def failed?
Signature
-
returns
Boolean whether the task has failed.
Implementation
def failed?
@state == :failed
end
def complete?
Signature
-
returns
Boolean whether the task has completed successfully.
Implementation
def complete?
@state == :complete
end
def dirty?
Returns true if the outputs of the task are out of date w.r.t. the inputs. Currently, does not take into account if the input is a glob and files have been added.
Implementation
def dirty?
if @outputs
@outputs.dirty?(@inputs)
else
true
end
end
def changed!
Resets the node in the walker if inputs or outputs have changed since the last run.
Implementation
def changed!
@walker.delete(@node) if (@inputs.update! or @outputs.update!)
end
def directories
Signature
-
returns
Array(String) the list of root directories for all input and output paths.
Implementation
def directories
(@inputs.roots + @outputs.roots).collect{|path| path.to_s}
end
def to_s
Signature
-
returns
String a short human-readable summary of the task.
Implementation
def to_s
"#<#{self.class} #{node_string} #{state_string}>"
end
def inspect
Signature
-
returns
String a detailed human-readable representation including object identity.
Implementation
def inspect
"\#<#{self.class}:0x#{self.object_id.to_s(16)} #{node_string} #{state_string}>"
end
def update_inputs_and_outputs
If the node inputs is a glob, this part of the process converts the glob into an actual list of files. If we are not inheriting outputs from children tasks, update our outputs now.
Implementation
def update_inputs_and_outputs
@inputs = Files::State.new(@node.inputs)
unless @node.inherit_outputs?
@outputs = Files::State.new(@node.outputs)
end
end
def children_outputs
Implementation
def children_outputs
@children.collect(&:outputs).inject(Files::Paths::NONE, &:+)
end
def update_outputs!
If the node's outputs were a glob, this checks the filesystem to figure out what files were actually generated. If it inherits the outputs of the child tasks, merge them into our own outputs.
Implementation
def update_outputs!
if @node.inherit_outputs?
@outputs = Files::State.new(self.children_outputs)
else
# After the task has finished, we update the output states:
@outputs.update!
end
end
def fail!(error)
Fail the task with the given error. Any task which is waiting on this task will also fail (eventually).
Implementation
def fail!(error)
Console.error(self, "Task failed!", exception: error)
@error = error
@state = :failed
end
def wait_for_inputs?
Implementation
def wait_for_inputs?
# Wait on any inputs, returns whether any inputs failed:
if @inputs&.any?
unless @walker.wait_on_paths(self, @inputs)
return false
end
end
return true
end
def wait_for_children?
Implementation
def wait_for_children?
if @children&.any?
unless @walker.wait_for_children(self, @children)
return false
end
end
return true
end