338 lines
No EOL
10 KiB
Ruby
338 lines
No EOL
10 KiB
Ruby
|
|
require 'debug'
|
|
|
|
module Comfpile
|
|
class ArtefactExecSkipError < StandardError
|
|
end
|
|
|
|
class Artefact
|
|
attr_reader :core, :engine
|
|
|
|
# @return [Comfpile::ContextKey] The context key of this artefact
|
|
attr_reader :context
|
|
|
|
attr_reader :exit_state
|
|
attr_reader :stage, :target
|
|
|
|
attr_reader :parent_artefact
|
|
|
|
# @return [Array<Comfpile::Artefact>] List of all artefacts
|
|
# included in this artefact.
|
|
# Included artefacts are also required, but will additionally
|
|
# update this Artefact's age.
|
|
attr_reader :included_artefacts
|
|
# @return [Array<Comfpile::Artefact>] List of all artefacts
|
|
# required to build this artefact.
|
|
# Required artefacts are needed to build this artefact, but
|
|
# do not modify the artefact's age.
|
|
attr_reader :required_artefacts
|
|
# @return [Array<Comfpile::Artefact>] List of all artefacts
|
|
# potentially referenced by this artefact.
|
|
# Referenced artefacts are those that are potentially used
|
|
# by this artefact through e.g. function calls, but are
|
|
# not directly needed to build this artefact. Example being
|
|
# how sourcecode of a library is eventually needed for e.g.
|
|
# linking steps, but the objects can be built separately.
|
|
attr_reader :referenced_artefacts
|
|
|
|
attr_reader :build_dir
|
|
|
|
# ARTEFACT STATES
|
|
#
|
|
# The following states are known to the system:
|
|
# - blocked: The Artefact is blocked and waiting on other artefacts
|
|
# - waiting: The Artefact is idle and ready to be queued
|
|
# - running: The Artefact is currently being run
|
|
# Note that this is not a state, but is determined by the
|
|
# @running flag
|
|
# - succeeded: it has finished its work without issue
|
|
# - skipped: it didn't run/won't run because of failed dependencies
|
|
# - failed: it has failed due to a requirement not being met
|
|
#
|
|
# Meta-States exist:
|
|
# - in_progress/completed: Anything but/Only succeeded, skipped, failed
|
|
|
|
def initialize(core, engine, stage, target, context, **opts)
|
|
raise ArgumentError, "Unknown options supplied!" unless opts.empty?
|
|
|
|
@core = core
|
|
@engine = engine
|
|
|
|
@stage = stage
|
|
@target = target
|
|
@context = @core.get_context_key context
|
|
|
|
@parent_artefact = nil
|
|
|
|
@required_artefacts = []
|
|
@included_artefacts = []
|
|
@referenced_artefacts = []
|
|
|
|
@log_current_level = :debug
|
|
@log_current_line = "Not started..."
|
|
|
|
@steps = []
|
|
@step_additions = nil
|
|
@steps_done_ctr = 0
|
|
|
|
@waitlist = []
|
|
|
|
@parameters = {}
|
|
|
|
@exit_state = nil
|
|
@running = false
|
|
|
|
@build_dir = opts[:build_dir] || @core.get_context_build_dir_path(self)
|
|
end
|
|
|
|
def [](key)
|
|
v = @parameters[key]
|
|
return v unless v.nil?
|
|
|
|
if @parent_artefact
|
|
return @parent_artefact[key]
|
|
end
|
|
end
|
|
def []=(key, value)
|
|
@parameters[key] = value
|
|
end
|
|
|
|
#
|
|
# Log an event for this artefact.
|
|
# Saves the given argument as log line,
|
|
# optionally prints it to a logger. Use this to
|
|
# save state progress messages as well as
|
|
# error messages.
|
|
#
|
|
# @param [String] text Text message to log
|
|
# @param [Symbol] state Level to log at. Known levels
|
|
# are:
|
|
# - :debug
|
|
# - :info
|
|
# - :warning
|
|
# - :error
|
|
#
|
|
def log(text, level = :debug)
|
|
@log_current_line = text
|
|
@log_current_level = level
|
|
|
|
print "\033[2K> %-30s %s: %s\r" % [@stage, @target, text]
|
|
|
|
nil
|
|
end
|
|
|
|
private def add_step_data(data)
|
|
@step_additions ||= []
|
|
|
|
@step_additions << data
|
|
end
|
|
private def process_additional_step_data
|
|
unless @step_additions.nil?
|
|
@steps.insert(@steps_done_ctr, @step_additions)
|
|
@steps.flatten!
|
|
@step_additions = nil
|
|
end
|
|
end
|
|
|
|
def add_step(&block)
|
|
add_step_data({
|
|
type: :block,
|
|
executed: false,
|
|
block: block
|
|
})
|
|
end
|
|
|
|
def parent_artefact(stage, target, context = @context)
|
|
@parent_artefact = require_artefact(stage, target, context)
|
|
end
|
|
|
|
def include_artefact(stage, target, context = @context, **opts)
|
|
artefact = require_artefact(stage, target, context, **opts)
|
|
@included_artefacts << artefact
|
|
|
|
artefact
|
|
end
|
|
|
|
def require_artefact(stage, target, context = @context, **opts)
|
|
artefact = reference_artefact(stage, target, context, required: true, **opts)
|
|
@required_artefacts << artefact
|
|
|
|
artefact
|
|
end
|
|
|
|
def reference_artefact(stage, target, context = @context, required: false)
|
|
artefact = craft_artefact(stage, target, context)
|
|
|
|
wait_on(artefact) if required
|
|
|
|
@referenced_artefacts << artefact
|
|
|
|
artefact
|
|
end
|
|
|
|
#
|
|
# Wait on a specific artefact to complete
|
|
#
|
|
# @param [Comfpile::Artefact, nil] artefact The artefact to wait on
|
|
# @param [Boolean] required Whether or not this artefact is required.
|
|
# When set to true (default), a failed artefact will skip this artefact.
|
|
# When set to false, this artefact will ignore the failure.
|
|
#
|
|
# @return [Boolean] true when we have to wait on this artefact or
|
|
# it failed, false if we're all good
|
|
def wait_on(artefact, required: true)
|
|
if(artefact.nil?)
|
|
fail! "Missing artefact dependency for #{stage} #{target}!" if required
|
|
|
|
true
|
|
elsif artefact.in_progress?
|
|
@waitlist << {
|
|
artefact: artefact,
|
|
required: true
|
|
}
|
|
|
|
true
|
|
elsif(required and not artefact.succeeded?)
|
|
skip! "Failed artefact dependency: #{artefact}"
|
|
|
|
true
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
# Find or create a new artefact
|
|
#
|
|
# @param [Symbol] stage The type of item to create.
|
|
# can either be a stage for file processing (e.g. :parsed,
|
|
# :sourcefile, :x86_debug_compiled), or an action (:clean)
|
|
# @param [String] target Target file. Usually expressed
|
|
# as path relative to Comfpile's resource locations.
|
|
#
|
|
# @return [nil, Artefact] Returns nil if no engine was
|
|
# found that can craft this, else returns the
|
|
# created or looked-up artefact.
|
|
#
|
|
def craft_artefact(stage, target, context = @context)
|
|
@core.craft_artefact(stage, target, context)
|
|
end
|
|
|
|
def find_artefact(stage, target, context = @context)
|
|
@core.find_artefact(stage, target, context)
|
|
end
|
|
|
|
def waitlist_empty?
|
|
return true if completed?
|
|
|
|
loop do
|
|
return true if @waitlist.empty?
|
|
|
|
item = @waitlist[-1]
|
|
|
|
return false if item[:artefact].in_progress?
|
|
|
|
if not item[:required]
|
|
@waitlist.pop
|
|
elsif item[:artefact].succeeded?
|
|
@waitlist.pop
|
|
else
|
|
skip! "Failed artefact dependency: #{item[:artefact]}"
|
|
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
def done_with_steps?
|
|
@steps_done_ctr >= @steps.length
|
|
end
|
|
|
|
def state
|
|
return :blocked unless waitlist_empty?
|
|
return @exit_state unless @exit_state.nil?
|
|
|
|
return :running if @running
|
|
|
|
return :waiting
|
|
end
|
|
|
|
def completed?
|
|
not @exit_state.nil?
|
|
end
|
|
|
|
def succeeded?
|
|
@exit_state == :succeeded
|
|
end
|
|
|
|
def in_progress?
|
|
not completed?
|
|
end
|
|
|
|
def waiting?
|
|
self.state == :waiting
|
|
end
|
|
|
|
private def mark_state_change(state, reason, abort: false)
|
|
return unless @exit_state.nil?
|
|
|
|
log "Reached state #{state}: #{reason}"
|
|
@exit_state = state
|
|
@reason = reason
|
|
|
|
abort_step! if abort
|
|
end
|
|
|
|
def skip!(reason, **opts)
|
|
mark_state_change(:skipped, reason, **opts)
|
|
end
|
|
def fail!(reason, **opts)
|
|
mark_state_change(:failed, reason, **opts)
|
|
end
|
|
def succeed!(reason, **opts)
|
|
mark_state_change(:succeeded, reason, **opts)
|
|
end
|
|
|
|
def abort_step!
|
|
raise ArtefactExecSkipError
|
|
end
|
|
alias retry_step! abort_step!
|
|
|
|
def execute_step
|
|
return unless waiting?
|
|
@running = true
|
|
|
|
process_additional_step_data
|
|
|
|
next_step = @steps[@steps_done_ctr]
|
|
succeed! "All done", abort: true if next_step.nil?
|
|
|
|
case next_step[:type]
|
|
when :block
|
|
instance_exec &next_step[:block]
|
|
else
|
|
fail! "Unknown artefact step taken!", abort: true
|
|
end
|
|
|
|
@steps_done_ctr += 1
|
|
succeed! "All done", abort: true if waitlist_empty? and done_with_steps?
|
|
|
|
ensure
|
|
@running = false
|
|
end
|
|
|
|
def method_missing(m_name, *args, **opts, &block)
|
|
if(@parent_artefact.nil?)
|
|
super(m_name, *args, **opts, &block)
|
|
else
|
|
@parent_artefact.send(m_name, *args, **opts, &block)
|
|
end
|
|
end
|
|
|
|
def inspect
|
|
"#<Compfile::Artefact #{@stage} #{@target}>"
|
|
end
|
|
def to_s
|
|
inspect
|
|
end
|
|
end
|
|
end |