rake-pipeline-fork 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +12 -0
  5. data/.yardopts +2 -0
  6. data/GETTING_STARTED.md +268 -0
  7. data/Gemfile +14 -0
  8. data/LICENSE +20 -0
  9. data/README.markdown +11 -0
  10. data/README.yard +178 -0
  11. data/Rakefile +21 -0
  12. data/bin/rakep +4 -0
  13. data/examples/copying_files.md +12 -0
  14. data/examples/minifying_files.md +37 -0
  15. data/examples/modifying_pipelines.md +67 -0
  16. data/examples/multiple_pipelines.md +77 -0
  17. data/lib/generators/rake/pipeline/install/install_generator.rb +70 -0
  18. data/lib/rake-pipeline.rb +462 -0
  19. data/lib/rake-pipeline/cli.rb +56 -0
  20. data/lib/rake-pipeline/dsl.rb +9 -0
  21. data/lib/rake-pipeline/dsl/pipeline_dsl.rb +246 -0
  22. data/lib/rake-pipeline/dsl/project_dsl.rb +108 -0
  23. data/lib/rake-pipeline/dynamic_file_task.rb +194 -0
  24. data/lib/rake-pipeline/error.rb +17 -0
  25. data/lib/rake-pipeline/file_wrapper.rb +182 -0
  26. data/lib/rake-pipeline/filter.rb +249 -0
  27. data/lib/rake-pipeline/filters.rb +4 -0
  28. data/lib/rake-pipeline/filters/concat_filter.rb +63 -0
  29. data/lib/rake-pipeline/filters/gsub_filter.rb +56 -0
  30. data/lib/rake-pipeline/filters/ordering_concat_filter.rb +38 -0
  31. data/lib/rake-pipeline/filters/pipeline_finalizing_filter.rb +21 -0
  32. data/lib/rake-pipeline/graph.rb +178 -0
  33. data/lib/rake-pipeline/manifest.rb +86 -0
  34. data/lib/rake-pipeline/manifest_entry.rb +34 -0
  35. data/lib/rake-pipeline/matcher.rb +141 -0
  36. data/lib/rake-pipeline/middleware.rb +72 -0
  37. data/lib/rake-pipeline/precompile.rake +8 -0
  38. data/lib/rake-pipeline/project.rb +335 -0
  39. data/lib/rake-pipeline/rails_plugin.rb +10 -0
  40. data/lib/rake-pipeline/railtie.rb +34 -0
  41. data/lib/rake-pipeline/reject_matcher.rb +29 -0
  42. data/lib/rake-pipeline/server.rb +15 -0
  43. data/lib/rake-pipeline/sorted_pipeline.rb +19 -0
  44. data/lib/rake-pipeline/version.rb +6 -0
  45. data/rails/init.rb +2 -0
  46. data/rake-pipeline.gemspec +24 -0
  47. data/spec/cli_spec.rb +71 -0
  48. data/spec/concat_filter_spec.rb +37 -0
  49. data/spec/dsl/pipeline_dsl_spec.rb +165 -0
  50. data/spec/dsl/project_dsl_spec.rb +41 -0
  51. data/spec/dynamic_file_task_spec.rb +119 -0
  52. data/spec/encoding_spec.rb +106 -0
  53. data/spec/file_wrapper_spec.rb +132 -0
  54. data/spec/filter_spec.rb +332 -0
  55. data/spec/graph_spec.rb +56 -0
  56. data/spec/gsub_filter_spec.rb +87 -0
  57. data/spec/manifest_entry_spec.rb +46 -0
  58. data/spec/manifest_spec.rb +67 -0
  59. data/spec/matcher_spec.rb +141 -0
  60. data/spec/middleware_spec.rb +199 -0
  61. data/spec/ordering_concat_filter_spec.rb +42 -0
  62. data/spec/pipeline_spec.rb +232 -0
  63. data/spec/project_spec.rb +295 -0
  64. data/spec/rake_acceptance_spec.rb +738 -0
  65. data/spec/rake_tasks_spec.rb +21 -0
  66. data/spec/reject_matcher_spec.rb +31 -0
  67. data/spec/sorted_pipeline_spec.rb +27 -0
  68. data/spec/spec_helper.rb +38 -0
  69. data/spec/support/spec_helpers/file_utils.rb +35 -0
  70. data/spec/support/spec_helpers/filters.rb +37 -0
  71. data/spec/support/spec_helpers/input_helpers.rb +23 -0
  72. data/spec/support/spec_helpers/memory_file_wrapper.rb +31 -0
  73. data/spec/support/spec_helpers/memory_manifest.rb +19 -0
  74. data/tools/perfs +101 -0
  75. metadata +215 -0
@@ -0,0 +1,56 @@
1
+ require "thor"
2
+
3
+ module Rake
4
+ class Pipeline
5
+ class CLI < Thor
6
+ class_option :assetfile, :default => "Assetfile", :aliases => "-c"
7
+ default_task :build
8
+
9
+ desc "build", "Build the project."
10
+ method_option :pretend, :type => :boolean, :aliases => "-p"
11
+ method_option :clean, :type => :boolean, :aliases => "-C"
12
+ def build
13
+ if options[:pretend]
14
+ project.output_files.each do |file|
15
+ say_status :create, relative_path(file)
16
+ end
17
+ else
18
+ options[:clean] ? project.clean : project.cleanup_tmpdir
19
+ project.invoke
20
+ end
21
+ end
22
+
23
+ desc "clean", "Remove the pipeline's temporary and output files."
24
+ method_option :pretend, :type => :boolean, :aliases => "-p"
25
+ def clean
26
+ if options[:pretend]
27
+ project.files_to_clean.each do |file|
28
+ say_status :remove, relative_path(file)
29
+ end
30
+ else
31
+ project.clean
32
+ end
33
+ end
34
+
35
+ desc "server", "Run the Rake::Pipeline preview server."
36
+ def server
37
+ require "rake-pipeline/server"
38
+ Rake::Pipeline::Server.new.start
39
+ end
40
+
41
+ private
42
+ def project
43
+ @project ||= Rake::Pipeline::Project.new(options[:assetfile])
44
+ end
45
+
46
+ # @param [FileWrapper|String] path
47
+ # @return [String] The path to the file with the current
48
+ # directory stripped out.
49
+ def relative_path(path)
50
+ pathstr = path.respond_to?(:fullpath) ? path.fullpath : path
51
+ pathstr.sub(%r|#{Dir.pwd}/|, '')
52
+ end
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,9 @@
1
+ module Rake
2
+ class Pipeline
3
+ module DSL
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'rake-pipeline/dsl/pipeline_dsl'
9
+ require 'rake-pipeline/dsl/project_dsl'
@@ -0,0 +1,246 @@
1
+ module Rake
2
+ class Pipeline
3
+ module DSL
4
+ # This class is used by {ProjectDSL} to provide a convenient DSL for
5
+ # configuring a pipeline.
6
+ #
7
+ # All instance methods of {PipelineDSL} are available in the context
8
+ # the block passed to +Rake::Pipeline.+{Pipeline.build}.
9
+ class PipelineDSL
10
+ # @return [Pipeline] the pipeline the DSL should configure
11
+ attr_reader :pipeline
12
+
13
+ # Configure a pipeline with a passed in block.
14
+ #
15
+ # @param [Pipeline] pipeline the pipeline that the PipelineDSL
16
+ # should configure.
17
+ # @param [Proc] block the block describing the
18
+ # configuration. This block will be evaluated in
19
+ # the context of a new instance of {PipelineDSL}
20
+ # @return [void]
21
+ def self.evaluate(pipeline, options, &block)
22
+ dsl = new(pipeline)
23
+
24
+ # If any before filters, apply them to the pipeline.
25
+ # They will be run in reverse of insertion order.
26
+ if before_filters = options[:before_filters]
27
+ before_filters.each do |klass, args, block|
28
+ dsl.filter klass, *args, &block
29
+ end
30
+ end
31
+
32
+ # Evaluate the block in the context of the DSL.
33
+ dsl.instance_eval(&block)
34
+
35
+ # If any after filters, apply them to the pipeline.
36
+ # They will be run in insertion order.
37
+ if after_filters = options[:after_filters]
38
+ after_filters.each do |klass, args, block|
39
+ dsl.filter klass, *args, &block
40
+ end
41
+ end
42
+
43
+ # the FinalizingFilter should always come after all
44
+ # user specified after filters
45
+ pipeline.finalize
46
+ end
47
+
48
+ # Create a new {PipelineDSL} to configure a pipeline.
49
+ #
50
+ # @param [Pipeline] pipeline the pipeline that the PipelineDSL
51
+ # should configure.
52
+ # @return [void]
53
+ def initialize(pipeline)
54
+ @pipeline = pipeline
55
+ end
56
+
57
+ # Add an input location and files to a pipeline.
58
+ #
59
+ # @example
60
+ # !!!ruby
61
+ # Rake::Pipeline::Project.build do
62
+ # input "app" do
63
+ # input "assets", "**/*.js"
64
+ # # ...
65
+ # end
66
+ # end
67
+ #
68
+ # @param [String] root the root path where the pipeline
69
+ # should find its input files.
70
+ # @param [String] glob a file pattern that represents
71
+ # the list of files that the pipeline should
72
+ # process within +root+. The default is +"**/*"+.
73
+ # @return [void]
74
+ def input(root, glob="**/*")
75
+ pipeline.add_input root, glob
76
+ end
77
+
78
+ # Add a filter to the pipeline.
79
+ #
80
+ # In addition to a filter class, {#filter} takes a
81
+ # block that describes how the filter should map
82
+ # input files to output files.
83
+ #
84
+ # By default, the block maps an input file into
85
+ # an output file with the same name.
86
+ #
87
+ # Any additional arguments passed to {#filter} will
88
+ # be passed on to the filter class's constructor.
89
+ #
90
+ # @see Filter#outputs Filter#output (for an example
91
+ # of how a list of input files gets mapped to
92
+ # its outputs)
93
+ #
94
+ # @param [Class] filter_class the class of the filter.
95
+ # @param [Array] ctor_args a list of arguments to pass
96
+ # to the filter's constructor.
97
+ # @param [Proc] block an output file name generator.
98
+ # @return [void]
99
+ def filter(filter_class, *ctor_args, &block)
100
+ filter = filter_class.new(*ctor_args, &block)
101
+ pipeline.add_filter(filter)
102
+ end
103
+
104
+ # Apply a number of filters, but only to files matching
105
+ # a particular pattern.
106
+ #
107
+ # Inside the block passed to {#match match}, you may
108
+ # specify any number of filters that should be applied
109
+ # to files matching the pattern.
110
+ #
111
+ # @param [String] pattern a glob pattern to match
112
+ # @param [Proc] block a block that supplies filters
113
+ # @return [Matcher]
114
+ #
115
+ # @example
116
+ # !!!ruby
117
+ # Rake::Pipeline::Project.build do
118
+ # output "public"
119
+ #
120
+ # input "app/assets" do
121
+ # # compile coffee files into JS files
122
+ # match "*.coffee" do
123
+ # coffee_script
124
+ # end
125
+ #
126
+ # # because the previous step converted coffeee
127
+ # # into JS, the coffee files will be included here
128
+ # match "*.js" do
129
+ # uglify
130
+ # concat "application.js"
131
+ # end
132
+ # end
133
+ # end
134
+ def match(pattern, &block)
135
+ matcher = pipeline.copy(Matcher, &block)
136
+ matcher.glob = pattern
137
+ pipeline.add_filter matcher
138
+ matcher
139
+ end
140
+
141
+ # Reject files matching a pattern or block. You may specify
142
+ # a glob or a glob.
143
+ #
144
+ # @param [String] pattern a glob pattern to match
145
+ # @param [Proc] a block used to evaluate each file. Returning
146
+ # true will skip that file.
147
+ # @return [RejectMatcher]
148
+ #
149
+ # @example
150
+ # !!!ruby
151
+ # Rake::Pipeline::Project.build do
152
+ # output "public"
153
+ #
154
+ # input "app/assets" do
155
+ # # reject everything maching *.min
156
+ # reject "*.min"
157
+ # end
158
+ #
159
+ # input "app/javascripts" do
160
+ # reject do |file|
161
+ # # process the file here
162
+ # end
163
+ # end
164
+ # end
165
+ def reject(pattern = '', &block)
166
+ matcher = pipeline.copy(RejectMatcher)
167
+ matcher.glob = pattern
168
+ matcher.block = block
169
+ pipeline.add_filter matcher
170
+ matcher
171
+ end
172
+ alias_method :exclude, :reject
173
+ alias_method :skip, :reject
174
+
175
+ # Apply filters in a sorted fashion. Use this when you need
176
+ # something other than file name ordering.
177
+ #
178
+ # @param [Proc] block used to sort inputs
179
+ # @return [SortedPipeline]
180
+ #
181
+ # @example
182
+ # !!!ruby
183
+ # Rake::Pipeline::Project.build do
184
+ # match "*.js" do
185
+ # # inputs will be sorted according to the block and
186
+ # # passed to concat
187
+ # sort do |f1, f2|
188
+ # # reverse the inputs
189
+ # f2.fullpath <=> f1.fullpath
190
+ # end
191
+ # concat "application.js"
192
+ # end
193
+ # end
194
+ # end
195
+ def sort(&block)
196
+ sorter = pipeline.copy(SortedPipeline)
197
+ sorter.comparator = block
198
+ pipeline.add_filter sorter
199
+ sorter
200
+ end
201
+
202
+ # Specify the output directory for the pipeline.
203
+ #
204
+ # @param [String] root the output directory.
205
+ # @return [void]
206
+ def output(root)
207
+ pipeline.output_root = root
208
+ end
209
+
210
+ # A helper method for adding a concat filter to
211
+ # the pipeline.
212
+ # If the first argument is an Array, it adds a new
213
+ # {OrderingConcatFilter}, otherwise it adds a new
214
+ # {ConcatFilter}.
215
+ #
216
+ # @see OrderingConcatFilter#initialize
217
+ # @see ConcatFilter#initialize
218
+ def concat(*args, &block)
219
+ if args.first.kind_of?(Array)
220
+ filter(Rake::Pipeline::OrderingConcatFilter, *args, &block)
221
+ else
222
+ filter(Rake::Pipeline::ConcatFilter, *args, &block)
223
+ end
224
+ end
225
+ alias_method :copy, :concat
226
+
227
+ # A helper method for adding a gsub filter to the pipeline.
228
+ #
229
+ # @see GsubFilter#initialize
230
+ def gsub(*args, &block)
231
+ filter(Rake::Pipeline::GsubFilter, *args, &block)
232
+ end
233
+ alias_method :replace, :gsub
234
+
235
+ # A helper method like gsub, but removes everything
236
+ # specified by the matcher. The matcher is the first argument
237
+ # passed to String#gsub
238
+ #
239
+ # @see String#gsub
240
+ def strip(matcher)
241
+ filter(Rake::Pipeline::GsubFilter, matcher, '')
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,108 @@
1
+ module Rake
2
+ class Pipeline
3
+ module DSL
4
+ # This class exists purely to provide a convenient DSL for
5
+ # configuring a project.
6
+ #
7
+ # All instance methods of {ProjectDSL} are available in the context
8
+ # the block passed to +Rake::Pipeline::Project.+{Project.build}.
9
+ #
10
+ # When configuring a project, you *must* provide an output root
11
+ # and a series of files using at least one {#input} block.
12
+ class ProjectDSL
13
+ # @return [Project] the project the DSL should configure
14
+ attr_reader :project
15
+
16
+ # Configure a project with a passed in block.
17
+ #
18
+ # @param [Project] project the project that the ProjectDSL
19
+ # should configure.
20
+ # @param [Proc] block the block describing the
21
+ # configuration. This block will be evaluated in
22
+ # the context of a new instance of {ProjectDSL}
23
+ # @return [void]
24
+ def self.evaluate(project, &block)
25
+ new(project).instance_eval(&block)
26
+ end
27
+
28
+ # Create a new {ProjectDSL} to configure a project.
29
+ #
30
+ # @param [Project] project
31
+ # the project that the ProjectDSL should configure.
32
+ # @return [void]
33
+ def initialize(project)
34
+ @project = project
35
+ @before_filters = []
36
+ @after_filters = []
37
+ @project.before_filters = @before_filters
38
+ @project.after_filters = @after_filters
39
+ end
40
+
41
+ # Add a filter to every input block. The parameters
42
+ # to +before_filter+ are the same as the parameters
43
+ # to {PipelineDSL#filter}.
44
+ #
45
+ # Filters will be executed before the specified
46
+ # filters in reverse of insertion order.
47
+ #
48
+ # @see {PipelineDSL#filter}
49
+ def before_filter(klass, *args, &block)
50
+ @before_filters.unshift [klass, args, block]
51
+ end
52
+
53
+ # Add a filter to every input block. The parameters
54
+ # to +after_filter+ are the same as the parameters
55
+ # to {PipelineDSL#filter}.
56
+ #
57
+ # Filters will be executed after the specified
58
+ # filters in insertion order.
59
+ #
60
+ # @see {PipelineDSL#filter}
61
+ def after_filter(klass, *args, &block)
62
+ @after_filters.push [klass, args, block]
63
+ end
64
+
65
+ # Specify the default output directory for the project.
66
+ #
67
+ # Pipelines created in this project will place their
68
+ # outputs here unless the value is overriden in their
69
+ # {#input} block.
70
+ #
71
+ # @param [String] root the output directory.
72
+ # @return [void]
73
+ def output(root)
74
+ project.default_output_root = root
75
+ end
76
+
77
+ # Specify the location of the root temporary directory.
78
+ #
79
+ # Pipelines will store intermediate build artifacts
80
+ # in a subdirectory of this directory.
81
+ #
82
+ # This defaults to "tmp" in the current working directory.
83
+ #
84
+ # @param [String] root the temporary directory
85
+ # @return [void]
86
+ def tmpdir(root)
87
+ project.tmpdir = root
88
+ end
89
+
90
+ # Add a new pipeline with the given inputs to the project.
91
+ #
92
+ # @see Project.build_pipeline
93
+ def input(*inputs, &block)
94
+ # Allow pipelines without a specified block. This is possible
95
+ # if before and after filters are all that are needed for a
96
+ # given input.
97
+ block = proc {} unless block_given?
98
+ project.build_pipeline(*inputs, &block)
99
+ end
100
+ alias inputs input
101
+
102
+ def map(path, &block)
103
+ project.maps[path] = block
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,194 @@
1
+ module Rake
2
+ class Pipeline
3
+ # This class extends Rake's {Rake::FileTask} class to add support
4
+ # for dynamic dependencies. Typically, Rake handles static dependencies,
5
+ # where a file's dependencies are known before the task is invoked.
6
+ # A {DynamicFileTask} also supports dynamic dependencies, meaning the
7
+ # file's dependencies can be determined just before invoking the task.
8
+ # Because calculating a file's dependencies at runtime may be an expensive
9
+ # operation (it could involve reading the file from disk and parsing it
10
+ # to extract dependency information, for example), the results of this
11
+ # calculation are stored on disk in a manifest file, and reused on
12
+ # subsequent runs if possible.
13
+ #
14
+ # For example, consider this file app.c:
15
+ #
16
+ # #include "app.h"
17
+ # some_stuff();
18
+ #
19
+ # If we have a task that compiles app.c into app.o, it needs to
20
+ # process app.c to look for additional dependencies specified
21
+ # by the file itself.
22
+ class DynamicFileTask < Rake::FileTask
23
+ class ManifestRequired < StandardError
24
+ def to_s
25
+ "DynamicFileTask's cannot be invoked without a manifest."
26
+ end
27
+ end
28
+
29
+ attr_accessor :manifest, :last_manifest
30
+
31
+ # @return [Boolean] true if the task has a block to invoke
32
+ # for dynamic dependencies, false otherwise.
33
+ def has_dynamic_block?
34
+ !!@dynamic
35
+ end
36
+
37
+ # @return [ManifestEntry] the manifest entry from the current
38
+ # manifest. This is the entry that will be written to disk after
39
+ # the task runs.
40
+ def manifest_entry
41
+ manifest[name]
42
+ end
43
+
44
+ # Set the current manifest entry,
45
+ #
46
+ # @param [ManifestEntry] new_entry
47
+ # @return [ManifestEntry]
48
+ def manifest_entry=(new_entry)
49
+ manifest[name] = new_entry
50
+ end
51
+
52
+ def last_manifest_entry
53
+ last_manifest[name]
54
+ end
55
+
56
+ # Invoke this task. This method only checks to see if there
57
+ # is a manifest then delegates to super
58
+ def invoke(*args)
59
+ raise ManifestRequired if has_dynamic_block? && !manifest
60
+ super
61
+ end
62
+
63
+ # In addition to the regular FileTask check, a DynamicFileTask
64
+ # should be invoked when any of it's prerequisites are required,
65
+ # there is no manifest or it's dependencies are out of date.
66
+ #
67
+ # @return [Boolean]
68
+ def needed?
69
+ return true if super
70
+
71
+ return true if prerequisites_needed?
72
+
73
+ # if we have no manifest, this file task is needed
74
+ return true unless last_manifest_entry
75
+
76
+ # If any of this task's dynamic dependencies have changed,
77
+ # this file task is needed
78
+ last_manifest_entry.deps.each do |dep, time|
79
+ return true if File.mtime(dep).to_i > time
80
+ end
81
+
82
+ # Otherwise, it's not needed
83
+ false
84
+ end
85
+
86
+ # Add a block that will return dynamic dependencies. This
87
+ # block can assume that all static dependencies are up
88
+ # to date.
89
+ #
90
+ # @return [DynamicFileTask] self
91
+ def dynamic(&block)
92
+ @dynamic = block
93
+ self
94
+ end
95
+
96
+ # Invoke the task's dynamic block.
97
+ def invoke_dynamic_block
98
+ @dynamic.call(self)
99
+ end
100
+
101
+ # At runtime, we will call this to get dynamic prerequisites.
102
+ #
103
+ # @return [Array[String]] an array of paths to the task's
104
+ # dynamic dependencies.
105
+ def dynamic_prerequisites
106
+ @dynamic_prerequisites ||= begin
107
+ dynamics = if has_dynamic_block?
108
+ dynamic_prerequisites_from_manifest || invoke_dynamic_block
109
+ else
110
+ []
111
+ end
112
+
113
+ # Make sure we don't dynamically depend on ourselves, as
114
+ # that will create a circular reference, and that makes
115
+ # everybody sad.
116
+ dynamics.reject { |x| x == name }
117
+ end
118
+ end
119
+
120
+ # Override rake's invoke_prerequisites method to invoke
121
+ # static prerequisites and then any dynamic prerequisites.
122
+ def invoke_prerequisites(task_args, invocation_chain)
123
+ super
124
+
125
+ raise ManifestRequired if has_dynamic_block? && !manifest
126
+
127
+ # Retrieve the dynamic prerequisites. If all goes well,
128
+ # we will not have to invoke the dynamic block to do this.
129
+ dynamics = dynamic_prerequisites
130
+
131
+ # invoke dynamic prerequisites just as we would invoke
132
+ # static prerequisites.
133
+ dynamics.each do |prereq|
134
+ task = lookup_prerequisite(prereq)
135
+ prereq_args = task_args.new_scope(task.arg_names)
136
+ task.invoke_with_call_chain(prereq_args, invocation_chain)
137
+ end
138
+
139
+ # Create a new manifest entry for each dynamic dependency.
140
+ # When the pipeline finishes, these manifest entries will be written
141
+ # to the file system.
142
+ entry = Rake::Pipeline::ManifestEntry.new
143
+
144
+ dynamics.each do |dynamic|
145
+ entry.deps.merge!(dynamic => mtime_or_now(dynamic).to_i)
146
+ end
147
+
148
+ self.manifest_entry = entry
149
+ end
150
+
151
+ # After invoking a task, add the mtime of the task's output
152
+ # to its current manifest entry.
153
+ def invoke_with_call_chain(*)
154
+ super
155
+
156
+ manifest_entry.mtime = mtime_or_now(name).to_i
157
+ end
158
+
159
+ private
160
+ # @return the mtime of the given file if it exists, and
161
+ # the current time otherwise.
162
+ def mtime_or_now(filename)
163
+ File.file?(filename) ? File.mtime(filename) : Time.now
164
+ end
165
+
166
+ # @return [Array<String>] a list of file paths that this
167
+ # task depends on.
168
+ # @return [nil] if the dependencies couldn't be read
169
+ # from the manifest.
170
+ def dynamic_prerequisites_from_manifest
171
+ # Try to avoid invoking the dynamic block if this file
172
+ # is not needed. If so, we may have all the information
173
+ # we need in the manifest file.
174
+ if !needed? && last_manifest_entry
175
+ mtime = last_manifest_entry.mtime
176
+ end
177
+
178
+ # If the output file of this task still exists and
179
+ # it hasn't been updated, we can simply return the
180
+ # list of dependencies in the manifest, which
181
+ # come from the return value of the dynamic block
182
+ # in a previous run.
183
+ if File.exist?(name) && mtime == File.mtime(name).to_i
184
+ return last_manifest_entry.deps.map { |k,v| k }
185
+ end
186
+ end
187
+
188
+ def prerequisites_needed?
189
+ prerequisite_tasks.any? { |n| n.needed? }
190
+ end
191
+ end
192
+ end
193
+ end
194
+