Permalink
Browse files

Completely skip computation if nothing has changed

This commit introduces a global dirty state. This check is used when
invoking a project. It checks to see if any of the known files have
changed, or if any files have been deleted or added. Projects are dirty
if the assetfile has changed. When projects are clean invoking them does
nothing. This check saves costly build time. The build tree is
constructed internally every time. This can be costly. Running those
rake tasks would not change anything anyway so that process can be by
passed.

This commit should make the preview server much faster. Say you have a
set set of assets: images, application.js, application.css, and
index.html. Opening the preview server will send an HTTP request for
index.html. This request will compile the pipeline. Further requests to
application.js, application.css and images should return almost
instantly because pipeline does not have to do any processing.
  • Loading branch information...
1 parent 26b512c commit 75f79b80cc15874d19898ff37b4873c80b6fcc41 twinturbo committed Oct 30, 2012
View
@@ -333,6 +333,7 @@ def invoke
def setup
setup_filters
generate_rake_tasks
+ record_input_files
end
# Set up the filters. This will loop through all of the filters for
@@ -447,5 +448,17 @@ def assert_input_provided
end
end
+ # This is needed to ensure that every file procesed in the pipeline
+ # has an entry in the manifest. This is used to compare input files
+ # for the global dirty check
+ def record_input_files
+ input_files.each do |file|
+ full_path = file.fullpath
+
+ if File.exists?(full_path) && !manifest[full_path]
+ manifest[full_path] ||= ManifestEntry.new({}, File.mtime(full_path).to_i)
+ end
+ end
+ end
end
end
@@ -207,6 +207,7 @@ def generate_rake_tasks
@rake_tasks = outputs.map do |output, inputs|
additional_paths = []
inputs.each do |input|
+
create_file_task(input.fullpath).dynamic do
additional_paths += additional_dependencies(input)
end
@@ -58,6 +58,25 @@ def [](key)
def []=(key, value)
@entries[key] = value
end
+
+ def empty?
+ entries.empty?
+ end
+
+ def files
+ entries.inject({}) do |hash, pair|
+ file = pair.first
+ entry = pair.last
+
+ hash.merge!(file => entry.mtime)
+
+ entry.deps.each_pair do |name, time|
+ hash.merge!(name => time)
+ end
+
+ hash
+ end
+ end
end
end
end
@@ -110,16 +110,15 @@ def build(&block)
# @see Rake::Pipeline#invoke
def invoke
@invoke_mutex.synchronize do
- if assetfile_path
- source = File.read(assetfile_path)
- if digest(source) != assetfile_digest
- rebuild_from_assetfile(assetfile_path, source)
- end
- end
-
last_manifest.read_manifest
- pipelines.each(&:invoke)
- manifest.write_manifest
+
+ if dirty?
+ rebuild_from_assetfile(assetfile_path) if assetfile_dirty?
+
+ pipelines.each(&:invoke)
+
+ manifest.write_manifest
+ end
end
end
@@ -264,6 +263,58 @@ def setup_pipelines
def digest(str)
Digest::SHA1.hexdigest(str)
end
+
+ def dirty?
+ assetfile_dirty? || files_dirty?
+ end
+
+ def assetfile_dirty?
+ if assetfile_path
+ source = File.read(assetfile_path)
+ digest(source) != assetfile_digest
+ else
+ false
+ end
+ end
+
+ # Returns true if any of these conditions are met:
+ # The pipeline hasn't been invoked yet
+ # The input files have been modified
+ # Any of the input files have been deleted
+ # There are new input files
+ def files_dirty?
+ return true if manifest.empty?
+
+ previous_files = manifest.files
+
+ input_files.each do |input_file|
+ if !File.exists? input_file
+ return true # existing input file has been deleted
+ elsif !previous_files[input_file]
+ return true # there is a new file in the pipeline
+ elsif File.mtime(input_file).to_i != previous_files[input_file]
+ return true # existing file has been changed
+ end
+ end
+
+ false
+ end
+
+ def input_files
+ static_input_files = pipelines.collect do |p|
+ p.input_files.reject { |file| file.in_directory? tmpdir }.map(&:fullpath)
+ end.flatten
+
+ dynamic_input_files = static_input_files.collect do |file|
+ if manifest[file]
+ manifest[file].deps.keys
+ else
+ nil
+ end
+ end.flatten.compact
+
+ static_input_files + dynamic_input_files
+ end
end
end
end
@@ -163,6 +163,7 @@ def copy_files
it "can be configured using the pipeline" do
pipeline = Rake::Pipeline.new
+ pipeline.project = Rake::Pipeline::Project.new
pipeline.add_input tmp, 'app/javascripts/*.js'
pipeline.output_root = File.expand_path("public")
pipeline.tmpdir = "temporary"

0 comments on commit 75f79b8

Please sign in to comment.