Skip to content

Commit

Permalink
[Compiler] add initial support for incremental build
Browse files Browse the repository at this point in the history
Incremental build now fully supports static files and images.
XHTML, Bade and Stylus files has only limited support.

refs #11
  • Loading branch information
Roman Kříž committed May 29, 2016
1 parent ea87290 commit cefa9a1
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 20 deletions.
11 changes: 10 additions & 1 deletion lib/epuber/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,16 @@ def compile(build_folder, check: false, write: false, release: false, verbose: f
# build folder cleanup
remove_unnecessary_files
remove_empty_folders

source_paths = file_resolver.files.select { |a| a.is_a?(FileTypes::SourceFile) }.map { |a| a.source_path }
compilation_context.source_file_database.cleanup(source_paths)
compilation_context.target_file_database.cleanup(source_paths)
end
ensure
self.class.globals_catcher.clear_all

compilation_context.source_file_database.save_to_file
compilation_context.target_file_database.save_to_file
end

# Archives current target files to epub
Expand Down Expand Up @@ -207,10 +214,12 @@ def parse_target_file_requests
end
end

# @param [FileTypes::AbstractFile] file
# @param [Epuber::Compiler::FileTypes::AbstractFile] file
#
def process_file(file)
file.compilation_context = compilation_context
file.process(compilation_context)
file.compilation_context = nil
end

# @return nil
Expand Down
18 changes: 18 additions & 0 deletions lib/epuber/compiler/compilation_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ class CompilationContext
#
attr_accessor :file_resolver

# This will track source files regardless of current target
#
# @return [Epuber::Compiler::FileDatabase]
#
attr_reader :source_file_database

# This will track source files depend on current target
#
# @return [Epuber::Compiler::FileDatabase]
#
attr_reader :target_file_database

# @return [Array<Epuber::Plugin>]
#
def plugins
Expand Down Expand Up @@ -76,10 +88,16 @@ def verbose?
verbose
end

def debug?
!release_build
end

def initialize(book, target)
@book = book
@target = target

@source_file_database = FileDatabase.new(Config.instance.file_stat_database_path)
@target_file_database = FileDatabase.new(Config.instance.target_file_stat_database_path(target))
end
end
end
Expand Down
32 changes: 26 additions & 6 deletions lib/epuber/compiler/file_database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,42 @@ def initialize(path)

# @param [String] file_path
#
def changed?(file_path)
def changed?(file_path, transitive: true, default_value: true)
stat = @all_files[file_path]
return false if stat.nil?
return default_value if stat.nil?

result = (stat != FileStat.new(file_path))
result || stat.dependency_paths.any? { |path| changed?(path) }

if transitive
result ||= stat.dependency_paths.any? do |path|
changed?(path, transitive: transitive, default_value: false)
end
end

result
end

# @param [String] file_path
#
# @return [FileStat]
#
def file_stat_for(file_path)
@all_files[file_path]
end

# @param [String] file_path
#
def up_to_date?(file_path)
!changed?(file_path)
def up_to_date?(file_path, transitive: true)
!changed?(file_path, transitive: transitive)
end

# @param [String] file_path
#
def update_metadata(file_path)
@all_files[file_path] = FileStat.new(file_path)
old_stat = @all_files[file_path]
old_dependencies = old_stat ? old_stat.dependency_paths : []

@all_files[file_path] = FileStat.new(file_path, dependency_paths: old_dependencies)
end

# @param [String] file_path path to file that will be dependent on
Expand Down Expand Up @@ -81,6 +99,8 @@ def cleanup(file_paths)
# @param [String] path
#
def save_to_file(path = store_file_path)
FileUtils.mkdir_p(File.dirname(path))

File.write(path, @all_files.to_yaml)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/epuber/compiler/file_stat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ class FileStat
# @param [String] path
# @param [File::Stat] stat
#
def initialize(path, stat = nil)
def initialize(path, stat = nil, dependency_paths: [])
@file_path = path

stat ||= File.stat(path)
@mtime = stat.mtime
@ctime = stat.ctime
@size = stat.size

@dependency_paths = []
@dependency_paths = dependency_paths
end

# @param [String] path
Expand Down
3 changes: 3 additions & 0 deletions lib/epuber/compiler/file_types/abstract_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def properties
#
attr_accessor :path_type

# @return [Epuber::Compiler::CompilationContext] non-nil value only during #process() method
#
attr_accessor :compilation_context

def ==(other)
self.class == other.class && final_destination_path == other.final_destination_path
Expand Down
35 changes: 31 additions & 4 deletions lib/epuber/compiler/file_types/bade_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ module FileTypes
require_relative 'xhtml_file'

class BadeFile < XHTMLFile
PRECOMPILED_CACHE_NAME = 'bade_precompiled'

# @param [Epuber::Compiler::CompilationContext] compilation_context
#
def process(compilation_context)
target = compilation_context.target
book = compilation_context.book
file_resolver = compilation_context.file_resolver

bade_content = load_source(compilation_context)
up_to_date = source_file_up_to_date?
precompiled_exists = File.exist?(precompiled_path)

variables = {
__book: book,
Expand All @@ -27,12 +30,36 @@ def process(compilation_context)
__const: Hash.new { |_hash, key| UI.warning("Undefined constant with key `#{key}`", location: caller_locations[0]) }.merge!(target.constants),
}

xhtml_content = Bade::Renderer.from_source(bade_content, source_path)
.with_locals(variables)
.render(new_line: '', indent: '')
if up_to_date && precompiled_exists && compilation_context.debug?
precompiled = Bade::Precompiled.from_yaml_file(precompiled_path)

renderer = Bade::Renderer.from_precompiled(precompiled)
.with_locals(variables)

xhtml_content = renderer.render(new_line: '', indent: '')

else
compilation_context.source_file_database.update_metadata(source_path) if compilation_context.debug?

bade_content = load_source(compilation_context)

renderer = Bade::Renderer.from_source(bade_content, source_path)
.with_locals(variables)

FileUtils.mkdir_p(File.dirname(precompiled_path))
renderer.precompiled.write_yaml_to_file(precompiled_path)

xhtml_content = renderer.render(new_line: '', indent: '')
end

write_compiled(common_process(xhtml_content, compilation_context))
end

# @return [String]
#
def precompiled_path
File.join(Config.instance.build_cache_path(PRECOMPILED_CACHE_NAME), source_path + '.precompiled.yml')
end
end
end
end
Expand Down
10 changes: 6 additions & 4 deletions lib/epuber/compiler/file_types/image_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ module FileTypes
require_relative 'source_file'

class ImageFile < SourceFile
# @param [Compiler::CompilationContext] compilation_context
# @param [Compiler::CompilationContext] _compilation_context
#
def process(compilation_context)
def process(_compilation_context)
return if destination_file_up_to_date?

dest = final_destination_path
source = abs_source_path

return if self.class.file_uptodate?(source, dest)

img = Magick::Image::read(source).first

resolution = img.columns * img.rows
Expand All @@ -35,6 +35,8 @@ def process(compilation_context)
# file is already old
self.class.file_copy!(source, dest)
end

update_metadata!
end
end
end
Expand Down
46 changes: 45 additions & 1 deletion lib/epuber/compiler/file_types/source_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,55 @@ def initialize(source_path)
@source_path = source_path
end

# Source file does not change from last build
# @warning Using only this method can cause not updating files that are different for targets
#
# @return [Bool]
#
def source_file_up_to_date?
source_db = compilation_context.source_file_database
source_db.up_to_date?(source_path)
end

# Source file does not change from last build of this target
#
# @return [Bool]
#
def destination_file_up_to_date?
source_db = compilation_context.source_file_database
target_db = compilation_context.target_file_database

destination_file_exist? && # destination file must exist
target_db.up_to_date?(source_path) && # source file must be up-to-date from last build of this target
source_db.file_stat_for(source_path) == target_db.file_stat_for(source_path)
end

# Final destination path exist
#
# @return [Bool]
#
def destination_file_exist?
File.exist?(final_destination_path)
end

# Updates information about source file in file databases
#
# @return [nil]
#
def update_metadata!
compilation_context.source_file_database.update_metadata(source_path)
compilation_context.target_file_database.update_metadata(source_path)
end

def default_file_copy
if self.class.file_copy?(abs_source_path, final_destination_path)
ok = destination_file_up_to_date? && destination_file_exist?

unless ok
UI.print_processing_debug_info("Copying to #{pkg_destination_path}")
self.class.file_copy!(abs_source_path, final_destination_path)
end

update_metadata!
end

def write_compiled(content)
Expand Down
4 changes: 2 additions & 2 deletions lib/epuber/compiler/file_types/static_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ module FileTypes
require_relative 'source_file'

class StaticFile < SourceFile
# @param [Compiler::CompilationContext] compilation_context
# @param [Compiler::CompilationContext] _compilation_context
#
def process(compilation_context)
def process(_compilation_context)
default_file_copy
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/epuber/compiler/file_types/stylus_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ class StylusFile < SourceFile
# @param [Compiler::CompilationContext] compilation_context
#
def process(compilation_context)
return if destination_file_up_to_date?

Stylus.define('__is_debug', !compilation_context.release_build)
Stylus.define('__is_verbose_mode', compilation_context.verbose?)
Stylus.define('__target_name', compilation_context.target.name)
Stylus.define('__book_title', compilation_context.book.title)

write_compiled(Stylus.compile(File.new(abs_source_path)))

update_metadata!
end
end
end
Expand Down
22 changes: 22 additions & 0 deletions lib/epuber/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,28 @@ def release_build_path(target)
File.join(working_path, 'release_build', target.name.to_s)
end

# @param [String] cache_name
#
# @return [String]
#
def build_cache_path(cache_name)
File.join(working_path, 'build_cache', cache_name)
end

# @return [String]
#
def file_stat_database_path
File.join(working_path, 'metadata', 'source_file_stats.yml')
end

# @param [Epuber::Book::Target] target
#
# @return [String]
#
def target_file_stat_database_path(target)
File.join(working_path, 'metadata', 'target_stats', target.name.to_s, 'file_stats.yml')
end

# ---------------------------------------------------------------------------------------------------------------- #

# Singleton
Expand Down
1 change: 1 addition & 0 deletions spec/unit/compiler/file/bade_file_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module FileTypes
ctx = CompilationContext.new(book, book.default_target)
ctx.file_resolver = FileResolver.new('/', '/.build')

file.compilation_context = ctx
file.process(ctx)

expect(File.read('some_file.xhtml')).to eq '<?xml version="1.0" encoding="UTF-8" standalone="no"?>
Expand Down
11 changes: 11 additions & 0 deletions spec/unit/compiler/file/image_file_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ module FileTypes
@tmp_dir = Dir.mktmpdir
end

let (:ctx) do
book = Book.new

ctx = CompilationContext.new(book, book.default_target)
ctx.file_resolver = FileResolver.new(@tmp_dir, File.join(@tmp_dir, '/.build'))

ctx
end

it "copy file's content to destination" do
img_source = File.join(spec_root, '../test_project/images/001_Frie_9780804137508_art_r1_fmt.png')
img_dest = File.join(@tmp_dir, 'dest_image.png')
Expand All @@ -29,6 +38,7 @@ module FileTypes
file.destination_path = img_dest
resolve_file_paths(file)

file.compilation_context = ctx
file.process(nil)

expect(File.exist?(img_dest)).to be_truthy
Expand All @@ -47,6 +57,7 @@ module FileTypes
file.destination_path = dest
resolve_file_paths(file)

file.compilation_context = ctx
file.process(nil)


Expand Down

0 comments on commit cefa9a1

Please sign in to comment.