lib/nanoc/base/services/compiler.rb in nanoc-4.4.6 vs lib/nanoc/base/services/compiler.rb in nanoc-4.4.7

- old
+ new

@@ -71,133 +71,277 @@ def compiled_content_cache @compiled_content_cache end end - # Provides functionality for (re)calculating the content of an item rep, without caching or - # outdatedness checking. - class RecalculatePhase - include Nanoc::Int::ContractsSupport + # All phases for the compilation of a single item rep. Phases will be repeated for every rep. + module Phases + # Provides functionality for (re)calculating the content of an item rep, without caching or + # outdatedness checking. + class Recalculate + include Nanoc::Int::ContractsSupport - def initialize(action_provider:, dependency_store:, compilation_context:) - @action_provider = action_provider - @dependency_store = dependency_store - @compilation_context = compilation_context + def initialize(action_provider:, dependency_store:, compilation_context:) + @action_provider = action_provider + @dependency_store = dependency_store + @compilation_context = compilation_context + end + + contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any + def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument + dependency_tracker = Nanoc::Int::DependencyTracker.new(@dependency_store) + dependency_tracker.enter(rep.item) + + executor = Nanoc::Int::Executor.new(rep, @compilation_context, dependency_tracker) + + @action_provider.memory_for(rep).each do |action| + case action + when Nanoc::Int::ProcessingActions::Filter + executor.filter(action.filter_name, action.params) + when Nanoc::Int::ProcessingActions::Layout + executor.layout(action.layout_identifier, action.params) + when Nanoc::Int::ProcessingActions::Snapshot + executor.snapshot(action.snapshot_name) + else + raise Nanoc::Int::Errors::InternalInconsistency, "unknown action #{action.inspect}" + end + end + ensure + dependency_tracker.exit + end end - contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any - def run(rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument - dependency_tracker = Nanoc::Int::DependencyTracker.new(@dependency_store) - dependency_tracker.enter(rep.item) + # Provides functionality for (re)calculating the content of an item rep, with caching or + # outdatedness checking. Delegates to s::Recalculate if outdated or no cache available. + class Cache + include Nanoc::Int::ContractsSupport - executor = Nanoc::Int::Executor.new(rep, @compilation_context, dependency_tracker) + def initialize(compiled_content_cache:, wrapped:) + @compiled_content_cache = compiled_content_cache + @wrapped = wrapped + end - @action_provider.memory_for(rep).each do |action| - case action - when Nanoc::Int::ProcessingActions::Filter - executor.filter(action.filter_name, action.params) - when Nanoc::Int::ProcessingActions::Layout - executor.layout(action.layout_identifier, action.params) - when Nanoc::Int::ProcessingActions::Snapshot - executor.snapshot(action.snapshot_name) + contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any + def run(rep, is_outdated:) + if can_reuse_content_for_rep?(rep, is_outdated: is_outdated) + Nanoc::Int::NotificationCenter.post(:cached_content_used, rep) + rep.snapshot_contents = @compiled_content_cache[rep] else - raise Nanoc::Int::Errors::InternalInconsistency, "unknown action #{action.inspect}" + @wrapped.run(rep, is_outdated: is_outdated) end + + rep.compiled = true + @compiled_content_cache[rep] = rep.snapshot_contents end - ensure - dependency_tracker.exit + + contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Bool + def can_reuse_content_for_rep?(rep, is_outdated:) + !is_outdated && !@compiled_content_cache[rep].nil? + end end - end - # Provides functionality for (re)calculating the content of an item rep, with caching or - # outdatedness checking. Delegates to RecalculatePhase if outdated or no cache available. - class CachePhase - include Nanoc::Int::ContractsSupport + # Provides functionality for suspending and resuming item rep compilation (using fibers). + class Resume + include Nanoc::Int::ContractsSupport - def initialize(compiled_content_cache:, wrapped:) - @compiled_content_cache = compiled_content_cache - @wrapped = wrapped + def initialize(wrapped:) + @wrapped = wrapped + end + + contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any + def run(rep, is_outdated:) + fiber = fiber_for(rep, is_outdated: is_outdated) + while fiber.alive? + Nanoc::Int::NotificationCenter.post(:compilation_started, rep) + res = fiber.resume + + case res + when Nanoc::Int::Errors::UnmetDependency + Nanoc::Int::NotificationCenter.post(:compilation_suspended, rep, res) + raise(res) + when Proc + fiber.resume(res.call) + else + # TODO: raise + end + end + + Nanoc::Int::NotificationCenter.post(:compilation_ended, rep) + end + + private + + contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => Fiber + def fiber_for(rep, is_outdated:) + @fibers ||= {} + + @fibers[rep] ||= + Fiber.new do + @wrapped.run(rep, is_outdated: is_outdated) + @fibers.delete(rep) + end + + @fibers[rep] + end end - contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any - def run(rep, is_outdated:) - if can_reuse_content_for_rep?(rep, is_outdated: is_outdated) - Nanoc::Int::NotificationCenter.post(:cached_content_used, rep) - rep.snapshot_contents = @compiled_content_cache[rep] - else - @wrapped.run(rep, is_outdated: is_outdated) + class Write + include Nanoc::Int::ContractsSupport + + def initialize(wrapped:) + @wrapped = wrapped end - rep.compiled = true - @compiled_content_cache[rep] = rep.snapshot_contents + contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any + def run(rep, is_outdated:) + @wrapped.run(rep, is_outdated: is_outdated) + + rep.snapshot_defs.each do |sdef| + ItemRepWriter.new.write(rep, sdef.name) + end + end end - contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Bool - def can_reuse_content_for_rep?(rep, is_outdated:) - !is_outdated && !@compiled_content_cache[rep].nil? + class MarkDone + include Nanoc::Int::ContractsSupport + + def initialize(wrapped:, outdatedness_store:) + @wrapped = wrapped + @outdatedness_store = outdatedness_store + end + + contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any + def run(rep, is_outdated:) + @wrapped.run(rep, is_outdated: is_outdated) + @outdatedness_store.remove(rep) + end end end - # Provides functionality for suspending and resuming item rep compilation (using fibers). - class ResumePhase - include Nanoc::Int::ContractsSupport + module Stages + class Preprocess + def initialize(action_provider:, site:, dependency_store:, checksum_store:) + @action_provider = action_provider + @site = site + @dependency_store = dependency_store + @checksum_store = checksum_store + end - def initialize(wrapped:) - @wrapped = wrapped + def run + @action_provider.preprocess(@site) + + @dependency_store.objects = @site.items.to_a + @site.layouts.to_a + @checksum_store.objects = @site.items.to_a + @site.layouts.to_a + @site.code_snippets + [@site.config] + + @site.freeze + end end - contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any - def run(rep, is_outdated:) - fiber = fiber_for(rep, is_outdated: is_outdated) - while fiber.alive? - Nanoc::Int::NotificationCenter.post(:compilation_started, rep) - res = fiber.resume + class Prune + def initialize(config:, reps:) + @config = config + @reps = reps + end - case res - when Nanoc::Int::Errors::UnmetDependency - Nanoc::Int::NotificationCenter.post(:compilation_suspended, rep, res) - raise(res) - when Proc - fiber.resume(res.call) - else - # TODO: raise + def run + if @config[:prune][:auto_prune] + Nanoc::Pruner.new(@config, @reps, exclude: prune_config_exclude).run end end - Nanoc::Int::NotificationCenter.post(:compilation_ended, rep) - end + private - private + def prune_config + @config[:prune] || {} + end - contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => Fiber - def fiber_for(rep, is_outdated:) - @fibers ||= {} + def prune_config_exclude + prune_config[:exclude] || {} + end + end - @fibers[rep] ||= - Fiber.new do - @wrapped.run(rep, is_outdated: is_outdated) - @fibers.delete(rep) + class DetermineOutdatedness + def initialize(reps:, outdatedness_checker:, outdatedness_store:) + @reps = reps + @outdatedness_checker = outdatedness_checker + @outdatedness_store = outdatedness_store + end + + def run + outdated_reps_tmp = @reps.select do |r| + @outdatedness_store.include?(r) || @outdatedness_checker.outdated?(r) end - @fibers[rep] - end - end + outdated_items = outdated_reps_tmp.map(&:item).uniq + outdated_reps = Set.new(outdated_items.flat_map { |i| @reps[i] }) - class WritePhase - include Nanoc::Int::ContractsSupport + outdated_reps.each { |r| @outdatedness_store.add(r) } - def initialize(wrapped:) - @wrapped = wrapped + yield(outdated_items) + end end - contract Nanoc::Int::ItemRep, C::KeywordArgs[is_outdated: C::Bool] => C::Any - def run(rep, is_outdated:) - @wrapped.run(rep, is_outdated: is_outdated) + class CompileReps + def initialize(outdatedness_store:, dependency_store:, action_provider:, compilation_context:, compiled_content_cache:) + @outdatedness_store = outdatedness_store + @dependency_store = dependency_store + @action_provider = action_provider + @compilation_context = compilation_context + @compiled_content_cache = compiled_content_cache + end - rep.snapshot_defs.each do |sdef| - ItemRepWriter.new.write(rep, sdef.name) + def run + selector = Nanoc::Int::ItemRepSelector.new(@outdatedness_store.to_a) + selector.each do |rep| + handle_errors_while(rep) { compile_rep(rep, is_outdated: @outdatedness_store.include?(rep)) } + end + ensure + @outdatedness_store.store + @compiled_content_cache.store end + + private + + def handle_errors_while(item_rep) + yield + rescue => e + raise Nanoc::Int::Errors::CompilationError.new(e, item_rep) + end + + def compile_rep(rep, is_outdated:) + item_rep_compiler.run(rep, is_outdated: is_outdated) + end + + def item_rep_compiler + @_item_rep_compiler ||= begin + recalculate_phase = Phases::Recalculate.new( + action_provider: @action_provider, + dependency_store: @dependency_store, + compilation_context: @compilation_context, + ) + + cache_phase = Phases::Cache.new( + compiled_content_cache: @compiled_content_cache, + wrapped: recalculate_phase, + ) + + resume_phase = Phases::Resume.new( + wrapped: cache_phase, + ) + + write_phase = Phases::Write.new( + wrapped: resume_phase, + ) + + mark_done_phase = Phases::MarkDone.new( + wrapped: write_phase, + outdatedness_store: @outdatedness_store, + ) + + mark_done_phase + end + end end end include Nanoc::Int::ContractsSupport @@ -223,57 +367,51 @@ attr_reader :outdatedness_checker # @api private attr_reader :reps - def initialize(site, compiled_content_cache:, checksum_store:, rule_memory_store:, action_provider:, dependency_store:, outdatedness_checker:, reps:) + # @api private + attr_reader :outdatedness_store + + def initialize(site, compiled_content_cache:, checksum_store:, rule_memory_store:, action_provider:, dependency_store:, outdatedness_checker:, reps:, outdatedness_store:) @site = site @compiled_content_cache = compiled_content_cache @checksum_store = checksum_store @rule_memory_store = rule_memory_store @dependency_store = dependency_store @outdatedness_checker = outdatedness_checker @reps = reps @action_provider = action_provider + @outdatedness_store = outdatedness_store end def run_all - @action_provider.preprocess(@site) + preprocess_stage.run build_reps - prune - run - @action_provider.postprocess(@site, @reps) - end - - def run + prune_stage.run load_stores - @site.freeze - - compile_reps + determine_outdatedness + forget_dependencies_if_needed store + compile_reps + store_output_state + @action_provider.postprocess(@site, @reps) ensure Nanoc::Int::TempFilenameFactory.instance.cleanup( Nanoc::Filter::TMP_BINARY_ITEMS_DIR, ) Nanoc::Int::TempFilenameFactory.instance.cleanup( Nanoc::Int::ItemRepWriter::TMP_TEXT_ITEMS_DIR, ) end def load_stores - # FIXME: icky hack to update the dependency/checksum store’s list of objects - # (does not include preprocessed objects otherwise) - dependency_store.objects = site.items.to_a + site.layouts.to_a - checksum_store.objects = site.items.to_a + site.layouts.to_a + site.code_snippets + [site.config] - stores.each(&:load) end - # Store the modified helper data used for compiling the site. - # - # @return [void] + # TODO: rename to store_preprocessed_state def store # Calculate rule memory (@reps.to_a + @site.layouts.to_a).each do |obj| rule_memory_store[obj] = action_provider.memory_for(obj).serialize end @@ -282,13 +420,18 @@ objects_to_checksum = site.items.to_a + site.layouts.to_a + site.code_snippets + [site.config] objects_to_checksum.each { |obj| checksum_store.add(obj) } # Store - stores.each(&:store) + checksum_store.store + rule_memory_store.store end + def store_output_state + @dependency_store.store + end + def build_reps builder = Nanoc::Int::ItemRepBuilder.new( site, action_provider, @reps ) builder.run @@ -303,75 +446,66 @@ ) end private - def prune - if site.config[:prune][:auto_prune] - Nanoc::Pruner.new(site.config, reps, exclude: prune_config_exclude).run - end + def preprocess_stage + @_preprocess_stage ||= Stages::Preprocess.new( + action_provider: action_provider, + site: site, + dependency_store: dependency_store, + checksum_store: checksum_store, + ) end - def prune_config - site.config[:prune] || {} + def prune_stage + @_prune_stage ||= Stages::Prune.new( + config: site.config, + reps: reps, + ) end - def prune_config_exclude - prune_config[:exclude] || {} + def determine_outdatedness_stage + @_determine_outdatedness_stage ||= Stages::DetermineOutdatedness.new( + reps: reps, + outdatedness_checker: outdatedness_checker, + outdatedness_store: outdatedness_store, + ) end - def compile_reps - outdated_items = @reps.select { |r| outdatedness_checker.outdated?(r) }.map(&:item).uniq - outdated_items.each { |i| @dependency_store.forget_dependencies_for(i) } + def compile_reps_stage + @_compile_reps_stage ||= Stages::CompileReps.new( + outdatedness_store: @outdatedness_store, + dependency_store: @dependency_store, + action_provider: action_provider, + compilation_context: compilation_context, + compiled_content_cache: compiled_content_cache, + ) + end - reps_to_recompile = Set.new(outdated_items.flat_map { |i| @reps[i] }) - selector = Nanoc::Int::ItemRepSelector.new(reps_to_recompile) - selector.each do |rep| - handle_errors_while(rep) { compile_rep(rep, is_outdated: reps_to_recompile.include?(rep)) } + def determine_outdatedness + determine_outdatedness_stage.run do |outdated_items| + @outdated_items = outdated_items end end - def handle_errors_while(item_rep) - yield - rescue => e - raise Nanoc::Int::Errors::CompilationError.new(e, item_rep) + def forget_dependencies_if_needed + @outdated_items.each { |i| @dependency_store.forget_dependencies_for(i) } end - def compile_rep(rep, is_outdated:) - item_rep_compiler.run(rep, is_outdated: is_outdated) + def compile_reps + compile_reps_stage.run end - def item_rep_compiler - @_item_rep_compiler ||= begin - recalculate_phase = RecalculatePhase.new( - action_provider: action_provider, - dependency_store: @dependency_store, - compilation_context: compilation_context, - ) - - cache_phase = CachePhase.new( - compiled_content_cache: compiled_content_cache, - wrapped: recalculate_phase, - ) - - resume_phase = ResumePhase.new( - wrapped: cache_phase, - ) - - WritePhase.new( - wrapped: resume_phase, - ) - end - end - # Returns all stores that can load/store data that can be used for # compilation. def stores [ checksum_store, compiled_content_cache, @dependency_store, rule_memory_store, + @outdatedness_store, ] end end end