require 'digest' require_relative 'artefact_engine.rb' require_relative 'context.rb' module Comfpile class Core attr_reader :processing_stack def initialize(build_dir_path: File.join('/tmp/comfpile/', Digest::MD5.hexdigest(Dir.pwd))) @build_dir_path = File.expand_path(build_dir_path) @artefact_engines = [] @artefact_prio_counter = 0 # Artefacts are arranged in a double-hash. # First query is stage, second filename. @artefacts = {} # Stack-style processing queue for item processing @processing_stack = [] @context_key_provider = ContextKeyProvider.new() end def get_context_key(key) return @context_key_provider.provide_context(key) end def find_artefact(stage, file = :none, context = :default) context = get_context_key(context) @artefacts.dig(context, stage, file) end # Create or find a new artefact. # # This will first see if a given artefact matching # the stage and target descriptions already exists, and # will return it, if found. Otherwise it will # consult all given artefact crafting engines to # see if a new artefact can be created, which will be returned. # # If no artefact can be made, nil is returned # # @param [Symbol, String] stage State that shall be created (e.g. :parsed, :compiled, etc.) # @param [String] file File or other target that shall be looked at (e.g. "main.cpp") # # @return [Comfpile::Artefact, nil] Found or created artefact, or nil if nothing could be done # def craft_artefact(stage, file = :none, context = :default) context = get_context_key(context) artefact = find_artefact(stage, file, context) return artefact unless artefact.nil? @artefact_engines.each do |engine| artefact = engine.craft stage, file, context if artefact @artefacts[context] ||= {} @artefacts[context][stage] ||= {} @artefacts[context][stage][file] = artefact @processing_stack.push artefact return artefact end end nil end def add_artefact(stage, file = :none, context = :default, engine: nil, artefact_class: Comfpile::Artefact) context = get_context_key context return unless find_artefact(stage, file, context).nil? a = Artefact.new(self, engine, stage, file, context); @artefacts[context] ||= {} @artefacts[context][stage] ||= {} @artefacts[context][stage][file] = a yield(a) if block_given? nil end # @yieldparam [Comfpile::ArtefactEngine] Engine that was newly created def add_artefact_engine(engine_class = Comfpile::ArtefactEngine, **options) new_engine = if(engine_class.is_a? Comfpile::ArtefactEngine) engine_class else engine = engine_class.new(self, subpriority: @artefact_prio_counter, **options) @artefact_prio_counter += 1 engine end yield(new_engine) if block_given? @artefact_engines << new_engine @artefact_engines.sort! new_engine end def processing_stack_prune() loop do return if @processing_stack.empty? if @processing_stack[-1].completed? @processing_stack.pop else break end end nil end def processing_stack_find_next() return nil if @processing_stack.empty? i = 0 @processing_stack.reverse_each do |a| i += 1 if a.waiting? # puts "DBG - Found item after #{i} checks" return a end end # TODO: Fix up later-on for parallel runner capabilities raise "Deadlock found, no item was willing to run!" nil end def get_context_build_dir_path(context) context = context.context if context.is_a? Artefact File.expand_path(File.join(@build_dir_path, context.to_s)) end def execute_step return if @processing_stack.empty? # puts "Got #{@processing_stack.length} items..." processing_stack_prune artefact = processing_stack_find_next return nil if artefact.nil? # puts "Processing artefact #{artefact.stage} #{artefact.target}" build_path = get_context_build_dir_path(artefact) FileUtils.mkpath build_path Dir.chdir build_path do begin artefact.execute_step rescue ArtefactExecSkipError end end nil end def execute_all step_count = 0 until @processing_stack.empty? execute_step() step_count += 1 end puts "Done after #{step_count} exec loops..." end def craft_and_complete(stage, target) artefact = craft_artefact(stage, target) return nil if artefact.nil? while artefact.in_progress? execute_step end artefact end end end