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.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +12 -0
- data/.yardopts +2 -0
- data/GETTING_STARTED.md +268 -0
- data/Gemfile +14 -0
- data/LICENSE +20 -0
- data/README.markdown +11 -0
- data/README.yard +178 -0
- data/Rakefile +21 -0
- data/bin/rakep +4 -0
- data/examples/copying_files.md +12 -0
- data/examples/minifying_files.md +37 -0
- data/examples/modifying_pipelines.md +67 -0
- data/examples/multiple_pipelines.md +77 -0
- data/lib/generators/rake/pipeline/install/install_generator.rb +70 -0
- data/lib/rake-pipeline.rb +462 -0
- data/lib/rake-pipeline/cli.rb +56 -0
- data/lib/rake-pipeline/dsl.rb +9 -0
- data/lib/rake-pipeline/dsl/pipeline_dsl.rb +246 -0
- data/lib/rake-pipeline/dsl/project_dsl.rb +108 -0
- data/lib/rake-pipeline/dynamic_file_task.rb +194 -0
- data/lib/rake-pipeline/error.rb +17 -0
- data/lib/rake-pipeline/file_wrapper.rb +182 -0
- data/lib/rake-pipeline/filter.rb +249 -0
- data/lib/rake-pipeline/filters.rb +4 -0
- data/lib/rake-pipeline/filters/concat_filter.rb +63 -0
- data/lib/rake-pipeline/filters/gsub_filter.rb +56 -0
- data/lib/rake-pipeline/filters/ordering_concat_filter.rb +38 -0
- data/lib/rake-pipeline/filters/pipeline_finalizing_filter.rb +21 -0
- data/lib/rake-pipeline/graph.rb +178 -0
- data/lib/rake-pipeline/manifest.rb +86 -0
- data/lib/rake-pipeline/manifest_entry.rb +34 -0
- data/lib/rake-pipeline/matcher.rb +141 -0
- data/lib/rake-pipeline/middleware.rb +72 -0
- data/lib/rake-pipeline/precompile.rake +8 -0
- data/lib/rake-pipeline/project.rb +335 -0
- data/lib/rake-pipeline/rails_plugin.rb +10 -0
- data/lib/rake-pipeline/railtie.rb +34 -0
- data/lib/rake-pipeline/reject_matcher.rb +29 -0
- data/lib/rake-pipeline/server.rb +15 -0
- data/lib/rake-pipeline/sorted_pipeline.rb +19 -0
- data/lib/rake-pipeline/version.rb +6 -0
- data/rails/init.rb +2 -0
- data/rake-pipeline.gemspec +24 -0
- data/spec/cli_spec.rb +71 -0
- data/spec/concat_filter_spec.rb +37 -0
- data/spec/dsl/pipeline_dsl_spec.rb +165 -0
- data/spec/dsl/project_dsl_spec.rb +41 -0
- data/spec/dynamic_file_task_spec.rb +119 -0
- data/spec/encoding_spec.rb +106 -0
- data/spec/file_wrapper_spec.rb +132 -0
- data/spec/filter_spec.rb +332 -0
- data/spec/graph_spec.rb +56 -0
- data/spec/gsub_filter_spec.rb +87 -0
- data/spec/manifest_entry_spec.rb +46 -0
- data/spec/manifest_spec.rb +67 -0
- data/spec/matcher_spec.rb +141 -0
- data/spec/middleware_spec.rb +199 -0
- data/spec/ordering_concat_filter_spec.rb +42 -0
- data/spec/pipeline_spec.rb +232 -0
- data/spec/project_spec.rb +295 -0
- data/spec/rake_acceptance_spec.rb +738 -0
- data/spec/rake_tasks_spec.rb +21 -0
- data/spec/reject_matcher_spec.rb +31 -0
- data/spec/sorted_pipeline_spec.rb +27 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/spec_helpers/file_utils.rb +35 -0
- data/spec/support/spec_helpers/filters.rb +37 -0
- data/spec/support/spec_helpers/input_helpers.rb +23 -0
- data/spec/support/spec_helpers/memory_file_wrapper.rb +31 -0
- data/spec/support/spec_helpers/memory_manifest.rb +19 -0
- data/tools/perfs +101 -0
- metadata +215 -0
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
directory "doc"
|
5
|
+
|
6
|
+
desc "generate documentation"
|
7
|
+
task :docs => Dir["lib/**"] do
|
8
|
+
sh "devbin/yard doc --readme README.yard --hide-void-return"
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "generate a dependency graph from the documentation"
|
12
|
+
task :graph => ["doc", :docs] do
|
13
|
+
sh "devbin/yard graph --dependencies | dot -Tpng -o doc/arch.png"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "run the specs"
|
17
|
+
task :spec do
|
18
|
+
sh "rspec"
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :spec
|
data/bin/rakep
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
The `copy` method also accepts an array. You can use this to copy the
|
2
|
+
file into multiple locations. Here's an example `Assetfile`:
|
3
|
+
|
4
|
+
```
|
5
|
+
input "source" do
|
6
|
+
match "*.js" do
|
7
|
+
copy do |input|
|
8
|
+
["/staging/#{input.path}", "/production/#{input.path}"]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
```
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Minifying files is a very common task for web developers. There are two
|
2
|
+
common uses cases:
|
3
|
+
|
4
|
+
1. Create an unminified and minified file
|
5
|
+
2. Generate a minified file from an unminifed file.
|
6
|
+
|
7
|
+
Doing #2 is very easy with rake pipeline. Doing #1 is slightly more
|
8
|
+
complicated. For these examples assume there is a `MinifyFilter` that
|
9
|
+
actually does the work.
|
10
|
+
|
11
|
+
Doing the first use case creates a problem. Filters are destructive.
|
12
|
+
That means if you change one file (move it, rewrite) it will not be
|
13
|
+
available for the next filters. For example, taking `app.js` and
|
14
|
+
minifying it will remove the unminfied source from the pipeline.
|
15
|
+
|
16
|
+
You must first duplicate the unminified file then minify it. Here's an
|
17
|
+
`Assetfile`
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
input "source" do
|
21
|
+
match "application.js" do
|
22
|
+
# Duplicate the source file for future filters (application.js)
|
23
|
+
# and provide duplicate in "application.min.js" for minifcation
|
24
|
+
copy ["application.js", "application.min.js"]
|
25
|
+
end
|
26
|
+
|
27
|
+
match "application.min.js" do
|
28
|
+
filter MinifyFilter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
output "compiled"
|
33
|
+
```
|
34
|
+
|
35
|
+
Now you have two files: `application.js` and `application.min.js`. You
|
36
|
+
can use this same technique everytime you need to transform a file and
|
37
|
+
keep the source around for future filters.
|
@@ -0,0 +1,67 @@
|
|
1
|
+
You may want to do some trickery with your input files. Here is a use
|
2
|
+
case: you need to sort your files in a custom way. The easiest way to do
|
3
|
+
this is to insert a new pipeline into your pipeline. This pipeline must
|
4
|
+
act like a filter because it will be used as such. Let's start out by
|
5
|
+
describing the most basic pipeline:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class PassThroughPipeline < Rake::Pipeline
|
9
|
+
# this need to act like a filter
|
10
|
+
attr_accessor :pipeline
|
11
|
+
|
12
|
+
# simply return the original input_files
|
13
|
+
def output_files
|
14
|
+
input_files
|
15
|
+
end
|
16
|
+
|
17
|
+
# this is very imporant! define this method
|
18
|
+
# to do nothing and files will not be copied
|
19
|
+
# to the output directory
|
20
|
+
def finalize
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
At this point you can insert it into your pipeline:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
input "**/*.js" do
|
29
|
+
# stick our pass through
|
30
|
+
pass_through = pipeline.copy PassThroughPipeline
|
31
|
+
pipeline.add_filter pass_through
|
32
|
+
|
33
|
+
# now continue on with your life
|
34
|
+
concat "application.js"
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Now we can begin to do all sorts of crazyness in this pass through
|
39
|
+
pipeline. You could expand directories to groups of files or you could
|
40
|
+
collapse then. You could even skip files if you wanted to. Hell, you can
|
41
|
+
even sort them--and that's what we're going to do. So let's get going
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class SortedPipeline < PassThroughPipeline
|
45
|
+
def output_files
|
46
|
+
super.sort do |f1, f2|
|
47
|
+
# just an easy example of reverse sorting
|
48
|
+
f2.fullpath <=> f1.fullpath
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
Now add it to the pipeline:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
input "**/*.js" do
|
58
|
+
# stick our pass through
|
59
|
+
pass_through = pipeline.copy SortedPipeline
|
60
|
+
pipeline.add_filter pass_through
|
61
|
+
|
62
|
+
# now continue on with your life
|
63
|
+
concat "application.js"
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Voila! You can sort stuff. Let your mind run wild with possibilities!
|
@@ -0,0 +1,77 @@
|
|
1
|
+
Your build process may become to complex to express in terms of a single
|
2
|
+
`input` block. You can break your `Assetfile` into multiple input
|
3
|
+
blocks. You can also use an `input`'s output as the input for a new
|
4
|
+
`input` block.
|
5
|
+
|
6
|
+
Say you have different pipelines for different types of files. There is
|
7
|
+
one for JS, CSS, and other assets. The output of these 3 different build
|
8
|
+
steps needs be the input for the next build step. You can express that
|
9
|
+
rather easily with `Rake::Pipeline`. Here are the pipelines for the
|
10
|
+
different types. The internals are left out because they are not
|
11
|
+
relavant to this example.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# Stage 1
|
15
|
+
input "js" do
|
16
|
+
# do JS stuff
|
17
|
+
end
|
18
|
+
|
19
|
+
input "css" do
|
20
|
+
# do css stuff
|
21
|
+
end
|
22
|
+
|
23
|
+
input "assets" do
|
24
|
+
# do asset stuff
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
Now let's add a line at the top of the `Assetfile`. Let's stay that all
|
29
|
+
the pipelines should output into one directory.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# All `input` blocks will output to `tmp/stage1` unless output is
|
33
|
+
# set again
|
34
|
+
output "tmp/stage1"
|
35
|
+
|
36
|
+
input "js" do
|
37
|
+
# do JS stuff
|
38
|
+
end
|
39
|
+
|
40
|
+
input "css" do
|
41
|
+
# do css stuff
|
42
|
+
end
|
43
|
+
|
44
|
+
input "assets" do
|
45
|
+
# do asset stuff
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
Now let's hookup stage 2.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# Stage 1
|
53
|
+
output "tmp/stage1"
|
54
|
+
input "js" do
|
55
|
+
# do JS stuff
|
56
|
+
end
|
57
|
+
|
58
|
+
input "css" do
|
59
|
+
# do css stuff
|
60
|
+
end
|
61
|
+
|
62
|
+
input "assets" do
|
63
|
+
# do asset stuff
|
64
|
+
end
|
65
|
+
|
66
|
+
# Stage 2
|
67
|
+
# output of next input block should go to the real output directory
|
68
|
+
output "compiled"
|
69
|
+
input "tmp/stage1" do
|
70
|
+
# do stage 2 stuff
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
You can repeat this process over and over again for as many stages as
|
75
|
+
you like. Just remember that the final input should output to where you
|
76
|
+
want the final files. Also, keep the intermediate build steps inside
|
77
|
+
temp directories so they are ignored by source control.
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Rake
|
2
|
+
class Pipeline
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
desc "Install Rake::Pipeline in this Rails app"
|
6
|
+
|
7
|
+
def disable_asset_pipeline_railtie
|
8
|
+
say_status :config, "Updating configuration to remove asset pipeline"
|
9
|
+
gsub_file app, "require 'rails/all'", <<-RUBY.strip_heredoc
|
10
|
+
# Pick the frameworks you want:
|
11
|
+
require "active_record/railtie"
|
12
|
+
require "action_controller/railtie"
|
13
|
+
require "action_mailer/railtie"
|
14
|
+
require "active_resource/railtie"
|
15
|
+
require "rails/test_unit/railtie"
|
16
|
+
RUBY
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO: Support sprockets API
|
20
|
+
def disable_asset_pipeline_config
|
21
|
+
regex = /^\n?\s*#.*\n\s*(#\s*)?config\.assets.*\n/
|
22
|
+
gsub_file app, regex, ''
|
23
|
+
gsub_file Rails.root.join("config/environments/development.rb"), regex, ''
|
24
|
+
gsub_file Rails.root.join("config/environments/production.rb"), regex, ''
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove_assets_group
|
28
|
+
regex = /^\n(#.*\n)+group :assets.*\n(.*\n)*?end\n/
|
29
|
+
|
30
|
+
gsub_file "Gemfile", regex, ''
|
31
|
+
end
|
32
|
+
|
33
|
+
def enable_assets_in_development
|
34
|
+
gsub_file "config/environments/development.rb", /^end/, "\n config.rake_pipeline_enabled = true\nend"
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO: Support asset-pipeline like API
|
38
|
+
def add_assetfile
|
39
|
+
create_file "Assetfile", <<-RUBY.strip_heredoc
|
40
|
+
# NOTE: The Assetfile will eventually be replaced with an asset-pipeline
|
41
|
+
# compatible API. This is mostly important so that plugins can easily
|
42
|
+
# inject into the pipeline.
|
43
|
+
#
|
44
|
+
# Depending on demand and how the API shakes out, we may retain the
|
45
|
+
# Assetfile API but pull in the information from the Rails API.
|
46
|
+
|
47
|
+
input "app/assets"
|
48
|
+
output "public"
|
49
|
+
|
50
|
+
match "*.js" do
|
51
|
+
concat "application.js"
|
52
|
+
end
|
53
|
+
|
54
|
+
match "*.css" do
|
55
|
+
concat "application.css"
|
56
|
+
end
|
57
|
+
|
58
|
+
# copy any remaining files
|
59
|
+
concat
|
60
|
+
RUBY
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def app
|
65
|
+
@app ||= Rails.root.join("config/application.rb")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,462 @@
|
|
1
|
+
require "rake-pipeline/file_wrapper"
|
2
|
+
require "rake-pipeline/filter"
|
3
|
+
require "rake-pipeline/manifest_entry"
|
4
|
+
require "rake-pipeline/manifest"
|
5
|
+
require "rake-pipeline/dynamic_file_task"
|
6
|
+
require "rake-pipeline/filters"
|
7
|
+
require "rake-pipeline/dsl"
|
8
|
+
require "rake-pipeline/matcher"
|
9
|
+
require "rake-pipeline/reject_matcher"
|
10
|
+
require "rake-pipeline/sorted_pipeline"
|
11
|
+
require "rake-pipeline/error"
|
12
|
+
require "rake-pipeline/project"
|
13
|
+
require "rake-pipeline/cli"
|
14
|
+
require "rake-pipeline/graph"
|
15
|
+
|
16
|
+
if defined?(Rails::Railtie)
|
17
|
+
require "rake-pipeline/railtie"
|
18
|
+
elsif defined?(Rails)
|
19
|
+
require "rake-pipeline/rails_plugin"
|
20
|
+
end
|
21
|
+
|
22
|
+
require "thread"
|
23
|
+
|
24
|
+
# Use the Rake namespace
|
25
|
+
module Rake
|
26
|
+
# Override Rake::Task to support recursively re-enabling
|
27
|
+
# a task and its dependencies.
|
28
|
+
class Task
|
29
|
+
|
30
|
+
# @param [Rake::Application] app a Rake Application
|
31
|
+
# @return [void]
|
32
|
+
def recursively_reenable(app)
|
33
|
+
reenable
|
34
|
+
|
35
|
+
prerequisites.each do |dep|
|
36
|
+
app[dep].recursively_reenable(app)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Override Rake::FileTask to make it sortable
|
42
|
+
class FileTask
|
43
|
+
# implement Ruby protocol for sorting
|
44
|
+
#
|
45
|
+
# @return [Fixnum]
|
46
|
+
def <=>(other)
|
47
|
+
[name, prerequisites] <=> [other.name, other.prerequisites]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# A Pipeline is responsible for taking a directory of input
|
52
|
+
# files, applying a number of filters to the inputs, and
|
53
|
+
# outputting them into an output directory.
|
54
|
+
#
|
55
|
+
# The normal way to build and configure a pipeline is by
|
56
|
+
# using {.build}. Inside the block passed to {.build}, all
|
57
|
+
# methods of {DSL} are available.
|
58
|
+
#
|
59
|
+
# @see DSL Rake::Pipeline::DSL for information on the methods
|
60
|
+
# available inside the block.
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# !!!ruby
|
64
|
+
# Rake::Pipeline.build do
|
65
|
+
# # process all js, css and html files in app/assets
|
66
|
+
# input "app/assets", "**/*.{js,coffee,css,scss,html}"
|
67
|
+
#
|
68
|
+
# # processed files should be outputted to public
|
69
|
+
# output "public"
|
70
|
+
#
|
71
|
+
# # process all coffee files
|
72
|
+
# match "*.coffee" do
|
73
|
+
# # compile all CoffeeScript files. the output file
|
74
|
+
# # for the compilation should be the input name
|
75
|
+
# # with the .coffee extension replaced with .js
|
76
|
+
# filter(CoffeeCompiler) do |input|
|
77
|
+
# input.sub(/\.coffee$/, '.js')
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# # specify filters for js files. this includes the
|
82
|
+
# # output of the previous step, which converted
|
83
|
+
# # coffee files to js files
|
84
|
+
# match "*.js" do
|
85
|
+
# # first, wrap all JS files in a custom filter
|
86
|
+
# filter ClosureFilter
|
87
|
+
# # then, concatenate all JS files into a single file
|
88
|
+
# concat "application.js"
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# # specify filters for css and scss files
|
92
|
+
# match "*.{css,scss}" do
|
93
|
+
# # compile CSS and SCSS files using the SCSS
|
94
|
+
# # compiler. if an input file has the extension
|
95
|
+
# # scss, replace it with css
|
96
|
+
# filter(ScssCompiler) do |input|
|
97
|
+
# input.sub(/\.scss$/, 'css')
|
98
|
+
# end
|
99
|
+
# # then, concatenate all CSS files into a single file
|
100
|
+
# concat "application.css"
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# # the remaining files not specified by a matcher (the
|
104
|
+
# # HTML files) are simply copied over.
|
105
|
+
#
|
106
|
+
# # you can also specify filters here that will apply to
|
107
|
+
# # all processed files (application.js and application.css)
|
108
|
+
# # up until this point, as well as the HTML files.
|
109
|
+
# end
|
110
|
+
class Pipeline
|
111
|
+
class Error < StandardError ; end
|
112
|
+
class TmpInputError < Error
|
113
|
+
def initialize(file)
|
114
|
+
@file = file
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_s
|
118
|
+
"Temporary files cannot be input! #{@file} is inside a pipeline's tmp directory"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Hash[String, String]] the directory paths for the input files
|
123
|
+
# and their matching globs.
|
124
|
+
attr_accessor :inputs
|
125
|
+
|
126
|
+
# @return [String] the directory path for the output files.
|
127
|
+
attr_reader :output_root
|
128
|
+
|
129
|
+
# @return [String] the directory path for temporary files
|
130
|
+
attr_accessor :tmpdir
|
131
|
+
|
132
|
+
# @return [Array] an Array of Rake::Task objects. This
|
133
|
+
# property is populated by the #generate_rake_tasks
|
134
|
+
# method.
|
135
|
+
attr_reader :rake_tasks
|
136
|
+
|
137
|
+
# @return [String] a list of files that will be outputted
|
138
|
+
# to the output directory when the pipeline is invoked
|
139
|
+
attr_reader :output_files
|
140
|
+
|
141
|
+
# @return [Array] this pipeline's filters.
|
142
|
+
attr_reader :filters
|
143
|
+
|
144
|
+
attr_writer :input_files
|
145
|
+
|
146
|
+
# @return [Project] the Project that created this pipeline
|
147
|
+
attr_accessor :project
|
148
|
+
|
149
|
+
# @param [Hash] options
|
150
|
+
# @option options [Hash] :inputs
|
151
|
+
# set the pipeline's {#inputs}.
|
152
|
+
# @option options [String] :tmpdir
|
153
|
+
# set the pipeline's {#tmpdir}.
|
154
|
+
# @option options [String] :output_root
|
155
|
+
# set the pipeline's {#output_root}.
|
156
|
+
# @option options [Rake::Application] :rake_application
|
157
|
+
# set the pipeline's {#rake_application}.
|
158
|
+
def initialize(options={})
|
159
|
+
@filters = []
|
160
|
+
@invoke_mutex = Mutex.new
|
161
|
+
@clean_mutex = Mutex.new
|
162
|
+
@tmp_id = 0
|
163
|
+
@inputs = options[:inputs] || {}
|
164
|
+
@tmpdir = options[:tmpdir] || File.expand_path("tmp")
|
165
|
+
@project = options[:project]
|
166
|
+
|
167
|
+
if options[:output_root]
|
168
|
+
self.output_root = options[:output_root]
|
169
|
+
end
|
170
|
+
|
171
|
+
if options[:rake_application]
|
172
|
+
self.rake_application = options[:rake_application]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Build a new pipeline taking a block. The block will
|
177
|
+
# be evaluated by the Rake::Pipeline::DSL class.
|
178
|
+
#
|
179
|
+
# @see Rake::Pipeline::Filter Rake::Pipeline::Filter
|
180
|
+
#
|
181
|
+
# @example
|
182
|
+
# Rake::Pipeline.build do
|
183
|
+
# input "app/assets"
|
184
|
+
# output "public"
|
185
|
+
#
|
186
|
+
# concat "app.js"
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# @see DSL the Rake::Pipeline::DSL documentation.
|
190
|
+
# All instance methods of DSL are available inside
|
191
|
+
# the build block.
|
192
|
+
#
|
193
|
+
# @return [Rake::Pipeline] the newly configured pipeline
|
194
|
+
def self.build(options={}, &block)
|
195
|
+
pipeline = new(options)
|
196
|
+
pipeline.build(options, &block)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Evaluate a block using the Rake::Pipeline DSL against an
|
200
|
+
# existing pipeline.
|
201
|
+
#
|
202
|
+
# @see Rake::Pipeline.build
|
203
|
+
#
|
204
|
+
# @return [Rake::Pipeline] this pipeline with any modifications
|
205
|
+
# made by the given block.
|
206
|
+
def build(options={}, &block)
|
207
|
+
DSL::PipelineDSL.evaluate(self, options, &block) if block
|
208
|
+
self
|
209
|
+
end
|
210
|
+
|
211
|
+
# Copy the current pipeline's attributes over.
|
212
|
+
#
|
213
|
+
# @param [Class] target_class the class to create a new
|
214
|
+
# instance of. Defaults to the class of the current
|
215
|
+
# pipeline. Is overridden in {Matcher}
|
216
|
+
# @param [Proc] block a block to pass to the {DSL DSL}
|
217
|
+
# @return [Pipeline] the new pipeline
|
218
|
+
# @api private
|
219
|
+
def copy(target_class=self.class, &block)
|
220
|
+
pipeline = target_class.new
|
221
|
+
pipeline.inputs = inputs
|
222
|
+
pipeline.tmpdir = tmpdir
|
223
|
+
pipeline.rake_application = rake_application
|
224
|
+
pipeline.project = project
|
225
|
+
pipeline.build &block
|
226
|
+
pipeline
|
227
|
+
end
|
228
|
+
|
229
|
+
# Set the output root of this pipeline and expand its path.
|
230
|
+
#
|
231
|
+
# @param [String] root this pipeline's output root
|
232
|
+
def output_root=(root)
|
233
|
+
@output_root = File.expand_path(root)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Set the temporary directory for this pipeline and expand its path.
|
237
|
+
#
|
238
|
+
# @param [String] root this pipeline's temporary directory
|
239
|
+
def tmpdir=(dir)
|
240
|
+
@tmpdir = File.expand_path(dir)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Add an input directory, optionally filtering which files within
|
244
|
+
# the input directory are included.
|
245
|
+
#
|
246
|
+
# @param [String] root the input root directory; required
|
247
|
+
# @param [String] pattern a pattern to match within +root+;
|
248
|
+
# optional; defaults to "**/*"
|
249
|
+
def add_input(root, pattern = nil)
|
250
|
+
pattern ||= "**/*"
|
251
|
+
@inputs[root] = pattern
|
252
|
+
end
|
253
|
+
|
254
|
+
# If you specify #inputs, this method will
|
255
|
+
# calculate the input files for the directory. If you supply
|
256
|
+
# input_files directly, this method will simply return the
|
257
|
+
# input_files you supplied.
|
258
|
+
#
|
259
|
+
# @return [Array<FileWrapper>] An Array of file wrappers
|
260
|
+
# that represent the inputs for the current pipeline.
|
261
|
+
def input_files
|
262
|
+
return @input_files if @input_files
|
263
|
+
|
264
|
+
assert_input_provided
|
265
|
+
|
266
|
+
result = []
|
267
|
+
|
268
|
+
@inputs.each do |root, glob|
|
269
|
+
expanded_root = File.expand_path(root)
|
270
|
+
files = Dir[File.join(expanded_root, glob)].sort.select { |f| File.file?(f) }
|
271
|
+
|
272
|
+
files.each do |file|
|
273
|
+
relative_path = file.sub(%r{^#{Regexp.escape(expanded_root)}/}, '')
|
274
|
+
wrapped_file = FileWrapper.new(expanded_root, relative_path)
|
275
|
+
|
276
|
+
raise TmpInputError, file if wrapped_file.in_directory?(tmpdir)
|
277
|
+
|
278
|
+
result << wrapped_file
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
result.sort
|
283
|
+
end
|
284
|
+
|
285
|
+
# for Pipelines, this is every file, but it may be overridden
|
286
|
+
# by subclasses
|
287
|
+
alias eligible_input_files input_files
|
288
|
+
|
289
|
+
# @return [Rake::Application] The Rake::Application to install
|
290
|
+
# rake tasks onto. Defaults to Rake.application
|
291
|
+
def rake_application
|
292
|
+
@rake_application || Rake.application
|
293
|
+
end
|
294
|
+
|
295
|
+
# Set the rake_application on the pipeline and apply it to filters.
|
296
|
+
#
|
297
|
+
# @return [void]
|
298
|
+
def rake_application=(rake_application)
|
299
|
+
@rake_application = rake_application
|
300
|
+
@filters.each { |filter| filter.rake_application = rake_application }
|
301
|
+
@rake_tasks = nil
|
302
|
+
end
|
303
|
+
|
304
|
+
# Add one or more filters to the current pipeline.
|
305
|
+
#
|
306
|
+
# @param [Array<Filter>] filters a list of filters
|
307
|
+
# @return [void]
|
308
|
+
def add_filters(*filters)
|
309
|
+
filters.each do |filter|
|
310
|
+
filter.rake_application = rake_application
|
311
|
+
filter.pipeline = self
|
312
|
+
end
|
313
|
+
@filters.concat(filters)
|
314
|
+
end
|
315
|
+
alias add_filter add_filters
|
316
|
+
|
317
|
+
# Invoke the pipeline, processing the inputs into the output.
|
318
|
+
#
|
319
|
+
# @return [void]
|
320
|
+
def invoke
|
321
|
+
@invoke_mutex.synchronize do
|
322
|
+
@tmp_id = 0
|
323
|
+
|
324
|
+
self.rake_application = Rake::Application.new
|
325
|
+
|
326
|
+
setup
|
327
|
+
|
328
|
+
@rake_tasks.each { |task| task.invoke }
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Set up the filters and generate rake tasks. In general, this method
|
333
|
+
# is called by invoke.
|
334
|
+
#
|
335
|
+
# @return [void]
|
336
|
+
# @api private
|
337
|
+
def setup
|
338
|
+
setup_filters
|
339
|
+
generate_rake_tasks
|
340
|
+
record_input_files
|
341
|
+
end
|
342
|
+
|
343
|
+
# Set up the filters. This will loop through all of the filters for
|
344
|
+
# the current pipeline and wire up their input_files and output_files.
|
345
|
+
#
|
346
|
+
# Because matchers implement the filter API, matchers will also be
|
347
|
+
# set up as part of this process.
|
348
|
+
#
|
349
|
+
# @return [void]
|
350
|
+
# @api private
|
351
|
+
def setup_filters
|
352
|
+
last = @filters.last
|
353
|
+
|
354
|
+
@filters.inject(eligible_input_files) do |current_inputs, filter|
|
355
|
+
filter.input_files = current_inputs
|
356
|
+
|
357
|
+
# if filters are being reinvoked, they should keep their roots but
|
358
|
+
# get updated with new files.
|
359
|
+
filter.output_root ||= begin
|
360
|
+
output = if filter == last
|
361
|
+
output_root
|
362
|
+
else
|
363
|
+
generate_tmpdir
|
364
|
+
end
|
365
|
+
|
366
|
+
File.expand_path(output)
|
367
|
+
end
|
368
|
+
|
369
|
+
filter.setup_filters if filter.respond_to?(:setup_filters)
|
370
|
+
|
371
|
+
filter.output_files
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# A list of the output files that invoking this pipeline will
|
376
|
+
# generate.
|
377
|
+
#
|
378
|
+
# @return [Array<FileWrapper>]
|
379
|
+
def output_files
|
380
|
+
@filters.last.output_files unless @filters.empty?
|
381
|
+
end
|
382
|
+
|
383
|
+
# Add a final filter to the pipeline that will copy the
|
384
|
+
# pipeline's generated files to the output.
|
385
|
+
#
|
386
|
+
# @return [void]
|
387
|
+
# @api private
|
388
|
+
def finalize
|
389
|
+
add_filter(Rake::Pipeline::PipelineFinalizingFilter.new)
|
390
|
+
end
|
391
|
+
|
392
|
+
# A unique fingerprint. It's used to generate unique temporary
|
393
|
+
# directory names. It must be unique to the pipeline. It must be
|
394
|
+
# the same across processes.
|
395
|
+
#
|
396
|
+
# @return [String]
|
397
|
+
# @api private
|
398
|
+
def fingerprint
|
399
|
+
if project
|
400
|
+
project.pipelines.index self
|
401
|
+
else
|
402
|
+
1
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# the Manifest used in this pipeline
|
407
|
+
def manifest
|
408
|
+
project.manifest
|
409
|
+
end
|
410
|
+
|
411
|
+
# the Manifest used in this pipeline
|
412
|
+
def last_manifest
|
413
|
+
project.last_manifest
|
414
|
+
end
|
415
|
+
|
416
|
+
protected
|
417
|
+
# Generate a new temporary directory name.
|
418
|
+
#
|
419
|
+
# @return [String] a unique temporary directory name
|
420
|
+
def generate_tmpname
|
421
|
+
"rake-pipeline-#{fingerprint}-tmp-#{@tmp_id += 1}"
|
422
|
+
end
|
423
|
+
|
424
|
+
# Generate a new temporary directory name under the main tmpdir.
|
425
|
+
#
|
426
|
+
# @return [void]
|
427
|
+
def generate_tmpdir
|
428
|
+
File.join(tmpdir, self.generate_tmpname)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Generate all of the rake tasks for this pipeline.
|
432
|
+
#
|
433
|
+
# @return [void]
|
434
|
+
def generate_rake_tasks
|
435
|
+
@rake_tasks = filters.collect { |f| f.generate_rake_tasks }.flatten
|
436
|
+
end
|
437
|
+
|
438
|
+
# Assert that an input root and glob were both provided.
|
439
|
+
#
|
440
|
+
# @raise Rake::Pipeline::Error if input root or glob were missing.
|
441
|
+
# @return [void]
|
442
|
+
def assert_input_provided
|
443
|
+
if inputs.empty?
|
444
|
+
raise Rake::Pipeline::Error, "You cannot get input files without " \
|
445
|
+
"first providing input files and an input root"
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
# This is needed to ensure that every file procesed in the pipeline
|
450
|
+
# has an entry in the manifest. This is used to compare input files
|
451
|
+
# for the global dirty check
|
452
|
+
def record_input_files
|
453
|
+
input_files.each do |file|
|
454
|
+
full_path = file.fullpath
|
455
|
+
|
456
|
+
if File.exists?(full_path) && !manifest[full_path]
|
457
|
+
manifest[full_path] ||= ManifestEntry.new({}, File.mtime(full_path).to_i)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end
|