1 | #!/usr/bin/env ruby
|
---|
2 |
|
---|
3 | # Original is https://github.com/jimweirich/rake/
|
---|
4 | # Copyright (c) 2003 Jim Weirich
|
---|
5 | # License: MIT-LICENSE
|
---|
6 |
|
---|
7 | require 'getoptlong'
|
---|
8 | require 'fileutils'
|
---|
9 |
|
---|
10 | class String
|
---|
11 | def ext(newext='')
|
---|
12 | return self.dup if ['.', '..'].include? self
|
---|
13 | if newext != ''
|
---|
14 | newext = (newext =~ /^\./) ? newext : ("." + newext)
|
---|
15 | end
|
---|
16 | self.chomp(File.extname(self)) << newext
|
---|
17 | end
|
---|
18 |
|
---|
19 | def pathmap(spec=nil, &block)
|
---|
20 | return self if spec.nil?
|
---|
21 | result = ''
|
---|
22 | spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag|
|
---|
23 | case frag
|
---|
24 | when '%f'
|
---|
25 | result << File.basename(self)
|
---|
26 | when '%n'
|
---|
27 | result << File.basename(self).ext
|
---|
28 | when '%d'
|
---|
29 | result << File.dirname(self)
|
---|
30 | when '%x'
|
---|
31 | result << File.extname(self)
|
---|
32 | when '%X'
|
---|
33 | result << self.ext
|
---|
34 | when '%p'
|
---|
35 | result << self
|
---|
36 | when '%s'
|
---|
37 | result << (File::ALT_SEPARATOR || File::SEPARATOR)
|
---|
38 | when '%-'
|
---|
39 | # do nothing
|
---|
40 | when '%%'
|
---|
41 | result << "%"
|
---|
42 | when /%(-?\d+)d/
|
---|
43 | result << pathmap_partial($1.to_i)
|
---|
44 | when /^%\{([^}]*)\}(\d*[dpfnxX])/
|
---|
45 | patterns, operator = $1, $2
|
---|
46 | result << pathmap('%' + operator).pathmap_replace(patterns, &block)
|
---|
47 | when /^%/
|
---|
48 | fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'"
|
---|
49 | else
|
---|
50 | result << frag
|
---|
51 | end
|
---|
52 | end
|
---|
53 | result
|
---|
54 | end
|
---|
55 | end
|
---|
56 |
|
---|
57 | module MiniRake
|
---|
58 | class Task
|
---|
59 | TASKS = Hash.new
|
---|
60 | RULES = Array.new
|
---|
61 |
|
---|
62 | # List of prerequisites for a task.
|
---|
63 | attr_reader :prerequisites
|
---|
64 |
|
---|
65 | # Source dependency for rule synthesized tasks. Nil if task was not
|
---|
66 | # sythesized from a rule.
|
---|
67 | attr_accessor :source
|
---|
68 |
|
---|
69 | # Create a task named +task_name+ with no actions or prerequisites..
|
---|
70 | # use +enhance+ to add actions and prerequisites.
|
---|
71 | def initialize(task_name)
|
---|
72 | @name = task_name
|
---|
73 | @prerequisites = []
|
---|
74 | @actions = []
|
---|
75 | end
|
---|
76 |
|
---|
77 | # Enhance a task with prerequisites or actions. Returns self.
|
---|
78 | def enhance(deps=nil, &block)
|
---|
79 | @prerequisites |= deps if deps
|
---|
80 | @actions << block if block_given?
|
---|
81 | self
|
---|
82 | end
|
---|
83 |
|
---|
84 | # Name of the task.
|
---|
85 | def name
|
---|
86 | @name.to_s
|
---|
87 | end
|
---|
88 |
|
---|
89 | # Invoke the task if it is needed. Prerequites are invoked first.
|
---|
90 | def invoke
|
---|
91 | puts "Invoke #{name} (already=[#{@already_invoked}], needed=[#{needed?}])" if $trace
|
---|
92 | return if @already_invoked
|
---|
93 | @already_invoked = true
|
---|
94 | prerequisites = @prerequisites.collect{ |n| n.is_a?(Proc) ? n.call(name) : n }.flatten
|
---|
95 | prerequisites.each { |n| Task[n].invoke }
|
---|
96 | execute if needed?
|
---|
97 | end
|
---|
98 |
|
---|
99 | # Execute the actions associated with this task.
|
---|
100 | def execute
|
---|
101 | puts "Execute #{name}" if $trace
|
---|
102 | self.class.enhance_with_matching_rule(name) if @actions.empty?
|
---|
103 | unless $dryrun
|
---|
104 | @actions.each { |act| act.call(self) }
|
---|
105 | end
|
---|
106 | end
|
---|
107 |
|
---|
108 | # Is this task needed?
|
---|
109 | def needed?
|
---|
110 | true
|
---|
111 | end
|
---|
112 |
|
---|
113 | # Timestamp for this task. Basic tasks return the current time for
|
---|
114 | # their time stamp. Other tasks can be more sophisticated.
|
---|
115 | def timestamp
|
---|
116 | prerequisites = @prerequisites.collect{ |n| n.is_a?(Proc) ? n.call(name) : n }.flatten
|
---|
117 | prerequisites.collect { |n| Task[n].timestamp }.max || Time.now
|
---|
118 | end
|
---|
119 |
|
---|
120 | # Class Methods ----------------------------------------------------
|
---|
121 |
|
---|
122 | class << self
|
---|
123 |
|
---|
124 | # Clear the task list. This cause rake to immediately forget all
|
---|
125 | # the tasks that have been assigned. (Normally used in the unit
|
---|
126 | # tests.)
|
---|
127 | def clear
|
---|
128 | TASKS.clear
|
---|
129 | RULES.clear
|
---|
130 | end
|
---|
131 |
|
---|
132 | # List of all defined tasks.
|
---|
133 | def tasks
|
---|
134 | TASKS.keys.sort.collect { |tn| Task[tn] }
|
---|
135 | end
|
---|
136 |
|
---|
137 | # Return a task with the given name. If the task is not currently
|
---|
138 | # known, try to synthesize one from the defined rules. If no
|
---|
139 | # rules are found, but an existing file matches the task name,
|
---|
140 | # assume it is a file task with no dependencies or actions.
|
---|
141 | def [](task_name)
|
---|
142 | task_name = task_name.to_s
|
---|
143 | if task_name.end_with?(":")
|
---|
144 | task_name = task_name.slice(0, task_name.length - 1)
|
---|
145 | end
|
---|
146 | if task = TASKS[task_name]
|
---|
147 | return task
|
---|
148 | end
|
---|
149 | if task = enhance_with_matching_rule(task_name)
|
---|
150 | return task
|
---|
151 | end
|
---|
152 | if File.exist?(task_name)
|
---|
153 | return FileTask.define_task(task_name)
|
---|
154 | end
|
---|
155 | fail "Don't know how to rake #{task_name}"
|
---|
156 | end
|
---|
157 |
|
---|
158 | # Define a task given +args+ and an option block. If a rule with
|
---|
159 | # the given name already exists, the prerequisites and actions are
|
---|
160 | # added to the existing task.
|
---|
161 | def define_task(args, &block)
|
---|
162 | task_name, deps = resolve_args(args)
|
---|
163 | lookup(task_name).enhance([deps].flatten, &block)
|
---|
164 | end
|
---|
165 |
|
---|
166 | # Define a rule for synthesizing tasks.
|
---|
167 | def create_rule(args, &block)
|
---|
168 | pattern, deps = resolve_args(args)
|
---|
169 | pattern = Regexp.new(Regexp.quote(pattern) + '$') if String === pattern
|
---|
170 | RULES << [pattern, deps, block]
|
---|
171 | end
|
---|
172 |
|
---|
173 |
|
---|
174 | # Lookup a task. Return an existing task if found, otherwise
|
---|
175 | # create a task of the current type.
|
---|
176 | def lookup(task_name)
|
---|
177 | name = task_name.to_s
|
---|
178 | TASKS[name] ||= self.new(name)
|
---|
179 | end
|
---|
180 |
|
---|
181 | # If a rule can be found that matches the task name, enhance the
|
---|
182 | # task with the prerequisites and actions from the rule. Set the
|
---|
183 | # source attribute of the task appropriately for the rule. Return
|
---|
184 | # the enhanced task or nil of no rule was found.
|
---|
185 | def enhance_with_matching_rule(task_name)
|
---|
186 | RULES.each do |pattern, extensions, block|
|
---|
187 | if pattern.match(task_name)
|
---|
188 | ext = extensions.first
|
---|
189 | deps = extensions[1..-1]
|
---|
190 | case ext
|
---|
191 | when String
|
---|
192 | source = task_name.sub(/\.[^.]*$/, ext)
|
---|
193 | when Proc
|
---|
194 | source = ext.call(task_name)
|
---|
195 | else
|
---|
196 | fail "Don't know how to handle rule dependent: #{ext.inspect}"
|
---|
197 | end
|
---|
198 | if File.exist?(source)
|
---|
199 | task = FileTask.define_task({task_name => [source]+deps}, &block)
|
---|
200 | task.source = source
|
---|
201 | return task
|
---|
202 | end
|
---|
203 | end
|
---|
204 | end
|
---|
205 | nil
|
---|
206 | end
|
---|
207 |
|
---|
208 | private
|
---|
209 |
|
---|
210 | # Resolve the arguments for a task/rule.
|
---|
211 | def resolve_args(args)
|
---|
212 | case args
|
---|
213 | when Hash
|
---|
214 | fail "Too Many Task Names: #{args.keys.join(' ')}" if args.size > 1
|
---|
215 | fail "No Task Name Given" if args.size < 1
|
---|
216 | task_name = args.keys[0]
|
---|
217 | deps = args[task_name]
|
---|
218 | deps = [deps] if (String===deps) || (Regexp===deps) || (Proc===deps)
|
---|
219 | else
|
---|
220 | task_name = args
|
---|
221 | deps = []
|
---|
222 | end
|
---|
223 | [task_name, deps]
|
---|
224 | end
|
---|
225 | end
|
---|
226 | end
|
---|
227 |
|
---|
228 |
|
---|
229 | ######################################################################
|
---|
230 | class FileTask < Task
|
---|
231 | # Is this file task needed? Yes if it doesn't exist, or if its time
|
---|
232 | # stamp is out of date.
|
---|
233 | def needed?
|
---|
234 | return true unless File.exist?(name)
|
---|
235 | prerequisites = @prerequisites.collect{ |n| n.is_a?(Proc) ? n.call(name) : n }.flatten
|
---|
236 | latest_prereq = prerequisites.collect{|n| Task[n].timestamp}.max
|
---|
237 | return false if latest_prereq.nil?
|
---|
238 | timestamp < latest_prereq
|
---|
239 | end
|
---|
240 |
|
---|
241 | # Time stamp for file task.
|
---|
242 | def timestamp
|
---|
243 | stat = File::stat(name.to_s)
|
---|
244 | stat.directory? ? Time.at(0) : stat.mtime
|
---|
245 | end
|
---|
246 | end
|
---|
247 |
|
---|
248 | module DSL
|
---|
249 | # Declare a basic task.
|
---|
250 | def task(args, &block)
|
---|
251 | MiniRake::Task.define_task(args, &block)
|
---|
252 | end
|
---|
253 |
|
---|
254 | # Declare a file task.
|
---|
255 | def file(args, &block)
|
---|
256 | MiniRake::FileTask.define_task(args, &block)
|
---|
257 | end
|
---|
258 |
|
---|
259 | # Declare a set of files tasks to create the given directories on
|
---|
260 | # demand.
|
---|
261 | def directory(args, &block)
|
---|
262 | MiniRake::FileTask.define_task(args) do |t|
|
---|
263 | block.call(t) unless block.nil?
|
---|
264 | dir = args.is_a?(Hash) ? args.keys.first : args
|
---|
265 | (dir.split(File::SEPARATOR) + ['']).inject do |acc, part|
|
---|
266 | (acc + File::SEPARATOR).tap do |d|
|
---|
267 | Dir.mkdir(d) unless File.exists? d
|
---|
268 | end + part
|
---|
269 | end
|
---|
270 | end
|
---|
271 | end
|
---|
272 |
|
---|
273 | # Declare a rule for auto-tasks.
|
---|
274 | def rule(args, &block)
|
---|
275 | MiniRake::Task.create_rule(args, &block)
|
---|
276 | end
|
---|
277 |
|
---|
278 | # Write a message to standard out if $verbose is enabled.
|
---|
279 | def log(msg)
|
---|
280 | print " " if $trace && $verbose
|
---|
281 | puts msg if $verbose
|
---|
282 | end
|
---|
283 |
|
---|
284 | # Run the system command +cmd+.
|
---|
285 | def sh(cmd)
|
---|
286 | puts cmd if $verbose
|
---|
287 | system(cmd) or fail "Command Failed: [#{cmd}]"
|
---|
288 | end
|
---|
289 |
|
---|
290 | def desc(text)
|
---|
291 | end
|
---|
292 | end
|
---|
293 | end
|
---|
294 |
|
---|
295 | Rake = MiniRake
|
---|
296 | extend MiniRake::DSL
|
---|
297 |
|
---|
298 |
|
---|
299 | ######################################################################
|
---|
300 | # Task Definition Functions ...
|
---|
301 |
|
---|
302 | ######################################################################
|
---|
303 | # Rake main application object. When invoking +rake+ from the command
|
---|
304 | # line, a RakeApp object is created and run.
|
---|
305 | #
|
---|
306 | class RakeApp
|
---|
307 | RAKEFILES = ['rakefile', 'Rakefile']
|
---|
308 |
|
---|
309 | OPTIONS = [
|
---|
310 | ['--dry-run', '-n', GetoptLong::NO_ARGUMENT,
|
---|
311 | "Do a dry run without executing actions."],
|
---|
312 | ['--help', '-H', GetoptLong::NO_ARGUMENT,
|
---|
313 | "Display this help message."],
|
---|
314 | ['--libdir', '-I', GetoptLong::REQUIRED_ARGUMENT,
|
---|
315 | "Include LIBDIR in the search path for required modules."],
|
---|
316 | ['--nosearch', '-N', GetoptLong::NO_ARGUMENT,
|
---|
317 | "Do not search parent directories for the Rakefile."],
|
---|
318 | ['--quiet', '-q', GetoptLong::NO_ARGUMENT,
|
---|
319 | "Do not log messages to standard output (default)."],
|
---|
320 | ['--rakefile', '-f', GetoptLong::REQUIRED_ARGUMENT,
|
---|
321 | "Use FILE as the rakefile."],
|
---|
322 | ['--require', '-r', GetoptLong::REQUIRED_ARGUMENT,
|
---|
323 | "Require MODULE before executing rakefile."],
|
---|
324 | ['--tasks', '-T', GetoptLong::NO_ARGUMENT,
|
---|
325 | "Display the tasks and dependencies, then exit."],
|
---|
326 | ['--pull-gems','-p', GetoptLong::NO_ARGUMENT,
|
---|
327 | "Pull all git mrbgems."],
|
---|
328 | ['--trace', '-t', GetoptLong::NO_ARGUMENT,
|
---|
329 | "Turn on invoke/execute tracing."],
|
---|
330 | ['--usage', '-h', GetoptLong::NO_ARGUMENT,
|
---|
331 | "Display usage."],
|
---|
332 | ['--verbose', '-v', GetoptLong::NO_ARGUMENT,
|
---|
333 | "Log message to standard output."],
|
---|
334 | ['--directory', '-C', GetoptLong::REQUIRED_ARGUMENT,
|
---|
335 | "Change executing directory of rakefiles."]
|
---|
336 | ]
|
---|
337 |
|
---|
338 | # Create a RakeApp object.
|
---|
339 | def initialize
|
---|
340 | @rakefile = nil
|
---|
341 | @nosearch = false
|
---|
342 | end
|
---|
343 |
|
---|
344 | # True if one of the files in RAKEFILES is in the current directory.
|
---|
345 | # If a match is found, it is copied into @rakefile.
|
---|
346 | def have_rakefile
|
---|
347 | RAKEFILES.each do |fn|
|
---|
348 | if File.exist?(fn)
|
---|
349 | @rakefile = fn
|
---|
350 | return true
|
---|
351 | end
|
---|
352 | end
|
---|
353 | return false
|
---|
354 | end
|
---|
355 |
|
---|
356 | # Display the program usage line.
|
---|
357 | def usage
|
---|
358 | puts "rake [-f rakefile] {options} targets..."
|
---|
359 | end
|
---|
360 |
|
---|
361 | # Display the rake command line help.
|
---|
362 | def help
|
---|
363 | usage
|
---|
364 | puts
|
---|
365 | puts "Options are ..."
|
---|
366 | puts
|
---|
367 | OPTIONS.sort.each do |long, short, mode, desc|
|
---|
368 | if mode == GetoptLong::REQUIRED_ARGUMENT
|
---|
369 | if desc =~ /\b([A-Z]{2,})\b/
|
---|
370 | long = long + "=#{$1}"
|
---|
371 | end
|
---|
372 | end
|
---|
373 | printf " %-20s (%s)\n", long, short
|
---|
374 | printf " %s\n", desc
|
---|
375 | end
|
---|
376 | end
|
---|
377 |
|
---|
378 | # Display the tasks and dependencies.
|
---|
379 | def display_tasks
|
---|
380 | MiniRake::Task.tasks.each do |t|
|
---|
381 | puts "#{t.class} #{t.name}"
|
---|
382 | t.prerequisites.each { |pre| puts " #{pre}" }
|
---|
383 | end
|
---|
384 | end
|
---|
385 |
|
---|
386 | # Return a list of the command line options supported by the
|
---|
387 | # program.
|
---|
388 | def command_line_options
|
---|
389 | OPTIONS.collect { |lst| lst[0..-2] }
|
---|
390 | end
|
---|
391 |
|
---|
392 | # Do the option defined by +opt+ and +value+.
|
---|
393 | def do_option(opt, value)
|
---|
394 | case opt
|
---|
395 | when '--dry-run'
|
---|
396 | $dryrun = true
|
---|
397 | $trace = true
|
---|
398 | when '--help'
|
---|
399 | help
|
---|
400 | exit
|
---|
401 | when '--libdir'
|
---|
402 | $:.push(value)
|
---|
403 | when '--nosearch'
|
---|
404 | @nosearch = true
|
---|
405 | when '--quiet'
|
---|
406 | $verbose = false
|
---|
407 | when '--rakefile'
|
---|
408 | RAKEFILES.clear
|
---|
409 | RAKEFILES << value
|
---|
410 | when '--require'
|
---|
411 | require value
|
---|
412 | when '--tasks'
|
---|
413 | $show_tasks = true
|
---|
414 | when '--pull-gems'
|
---|
415 | $pull_gems = true
|
---|
416 | when '--trace'
|
---|
417 | $trace = true
|
---|
418 | when '--usage'
|
---|
419 | usage
|
---|
420 | exit
|
---|
421 | when '--verbose'
|
---|
422 | $verbose = true
|
---|
423 | when '--version'
|
---|
424 | puts "rake, version #{RAKEVERSION}"
|
---|
425 | exit
|
---|
426 | when '--directory'
|
---|
427 | Dir.chdir value
|
---|
428 | else
|
---|
429 | fail "Unknown option: #{opt}"
|
---|
430 | end
|
---|
431 | end
|
---|
432 |
|
---|
433 | # Read and handle the command line options.
|
---|
434 | def handle_options
|
---|
435 | $verbose = false
|
---|
436 | $pull_gems = false
|
---|
437 | opts = GetoptLong.new(*command_line_options)
|
---|
438 | opts.each { |opt, value| do_option(opt, value) }
|
---|
439 | end
|
---|
440 |
|
---|
441 | # Run the +rake+ application.
|
---|
442 | def run
|
---|
443 | handle_options
|
---|
444 | begin
|
---|
445 | here = Dir.pwd
|
---|
446 | while ! have_rakefile
|
---|
447 | Dir.chdir("..")
|
---|
448 | if Dir.pwd == here || @nosearch
|
---|
449 | fail "No Rakefile found (looking for: #{RAKEFILES.join(', ')})"
|
---|
450 | end
|
---|
451 | here = Dir.pwd
|
---|
452 | end
|
---|
453 | tasks = []
|
---|
454 | ARGV.each do |task_name|
|
---|
455 | if /^(\w+)=(.*)/.match(task_name)
|
---|
456 | ENV[$1] = $2
|
---|
457 | else
|
---|
458 | tasks << task_name
|
---|
459 | end
|
---|
460 | end
|
---|
461 | puts "(in #{Dir.pwd})"
|
---|
462 | $rakefile = @rakefile
|
---|
463 | load @rakefile
|
---|
464 | if $show_tasks
|
---|
465 | display_tasks
|
---|
466 | else
|
---|
467 | tasks.push("default") if tasks.size == 0
|
---|
468 | tasks.each do |task_name|
|
---|
469 | MiniRake::Task[task_name].invoke
|
---|
470 | end
|
---|
471 | end
|
---|
472 | rescue Exception => ex
|
---|
473 | puts "rake aborted!"
|
---|
474 | puts ex.message
|
---|
475 | if $trace
|
---|
476 | puts ex.backtrace.join("\n")
|
---|
477 | else
|
---|
478 | puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
|
---|
479 | end
|
---|
480 | exit 1
|
---|
481 | end
|
---|
482 | end
|
---|
483 | end
|
---|
484 |
|
---|
485 | if __FILE__ == $0 then
|
---|
486 | RakeApp.new.run
|
---|
487 | end
|
---|