summaryrefslogtreecommitdiffhomepage
path: root/minirake
diff options
context:
space:
mode:
Diffstat (limited to 'minirake')
-rwxr-xr-xminirake151
1 files changed, 136 insertions, 15 deletions
diff --git a/minirake b/minirake
index 542c37a79..cd2d85854 100755
--- a/minirake
+++ b/minirake
@@ -7,6 +7,10 @@
require 'getoptlong'
require 'fileutils'
+$rake_fiber_table = {}
+$rake_jobs = 1
+$rake_failed = []
+
class String
def ext(newext='')
return self.dup if ['.', '..'].include? self
@@ -86,14 +90,36 @@ module MiniRake
@name.to_s
end
- # Invoke the task if it is needed. Prerequites are invoked first.
+ def done?; @done end
+ def running?; @running end
+
+ # Invoke the task if it is needed. Prerequisites are invoked first.
def invoke
puts "Invoke #{name} (already=[#{@already_invoked}], needed=[#{needed?}])" if $trace
return if @already_invoked
- @already_invoked = true
prerequisites = @prerequisites.collect{ |n| n.is_a?(Proc) ? n.call(name) : n }.flatten
- prerequisites.each { |n| Task[n].invoke }
- execute if needed?
+ prerequisites.each do |n|
+ t = Task[n]
+ unless t.done?
+ return prerequisites.select{|v| v = Task[v]; v && (!v.done? || !v.running?) }
+ end
+ end
+
+ @already_invoked = true
+
+ if needed?
+ @running = true
+ if $rake_root_fiber
+ return Fiber.new do
+ self.execute
+ $rake_root_fiber.transfer
+ end
+ else
+ self.execute
+ end
+ end
+
+ @done = true
end
# Execute the actions associated with this task.
@@ -103,6 +129,8 @@ module MiniRake
unless $dryrun
@actions.each { |act| act.call(self) }
end
+ @done = true
+ @running = false
end
# Is this task needed?
@@ -281,7 +309,19 @@ module MiniRake
# Run the system command +cmd+.
def sh(cmd)
puts cmd if $verbose
- system(cmd) or fail "Command Failed: [#{cmd}]"
+
+ if !$rake_root_fiber || Fiber.current == $rake_root_fiber
+ system(cmd) or fail "Command Failed: [#{cmd}]"
+ return
+ end
+
+ pid = Process.spawn(cmd)
+ $rake_fiber_table[pid] = {
+ fiber: Fiber.current,
+ command: cmd,
+ process_waiter: Process.detach(pid)
+ }
+ $rake_root_fiber.transfer
end
def desc(text)
@@ -329,7 +369,9 @@ class RakeApp
['--verbose', '-v', GetoptLong::NO_ARGUMENT,
"Log message to standard output."],
['--directory', '-C', GetoptLong::REQUIRED_ARGUMENT,
- "Change executing directory of rakefiles."]
+ "Change executing directory of rakefiles."],
+ ['--jobs', '-j', GetoptLong::REQUIRED_ARGUMENT,
+ 'Execute rake with parallel jobs.']
]
# Create a RakeApp object.
@@ -422,6 +464,8 @@ class RakeApp
exit
when '--directory'
Dir.chdir value
+ when '--jobs'
+ $rake_jobs = [value.to_i, 1].max
else
fail "Unknown option: #{opt}"
end
@@ -438,6 +482,12 @@ class RakeApp
# Run the +rake+ application.
def run
handle_options
+
+ unless $rake_root_fiber
+ require 'fiber'
+ $rake_root_fiber = Fiber.current
+ end
+
begin
here = Dir.pwd
while ! have_rakefile
@@ -447,12 +497,12 @@ class RakeApp
end
here = Dir.pwd
end
- tasks = []
+ root_tasks = []
ARGV.each do |task_name|
if /^(\w+)=(.*)/.match(task_name)
ENV[$1] = $2
else
- tasks << task_name
+ root_tasks << task_name
end
end
puts "(in #{Dir.pwd})"
@@ -461,22 +511,93 @@ class RakeApp
if $show_tasks
display_tasks
else
- tasks.push("default") if tasks.size == 0
- tasks.each do |task_name|
- MiniRake::Task[task_name].invoke
+ root_tasks.push("default") if root_tasks.empty?
+ # revese tasks for popping
+ root_tasks.reverse!
+
+ tasks = []
+ until root_tasks.empty?
+ root_name = root_tasks.pop
+ tasks << root_name
+ until tasks.empty?
+ task_name = tasks.pop
+ t = MiniRake::Task[task_name]
+ f = t.invoke
+
+ # append additional tasks to task queue
+ if f.kind_of?(Array)
+ tasks.push(*f)
+ tasks.uniq!
+ end
+
+ unless f.kind_of? Fiber
+ tasks.insert 0, task_name unless t.done?
+ if root_name == task_name
+ wait_process
+ end
+ next
+ end
+
+ wait_process while $rake_fiber_table.size >= $rake_jobs
+
+ f.transfer
+ end
end
+
+ wait_process until $rake_fiber_table.empty?
+ end
+ rescue Exception => e
+ begin
+ $rake_failed << e
+ wait_process until $rake_fiber_table.empty?
+ rescue Exception => next_e
+ e = next_e
+ retry
end
- rescue Exception => ex
- puts "rake aborted!"
+ end
+
+ return if $rake_failed.empty?
+
+ puts "rake aborted!"
+ $rake_failed.each do |ex|
puts ex.message
- if $trace
+ if $trace || $verbose
puts ex.backtrace.join("\n")
else
puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
end
- exit 1
end
+ exit 1
end
+
+ def wait_process(count = 0)
+ dur = [0.0001 * (10 ** count), 1].min
+ sleep dur
+
+ exited = []
+ $rake_fiber_table.each do |pid, v|
+ exited << pid unless v[:process_waiter].alive?
+ end
+
+ exited.each do |pid|
+ ent = $rake_fiber_table.delete pid
+ st = ent[:process_waiter].value
+
+ # ignore process that isn't created by `sh` method
+ return if ent.nil?
+
+ if st.exitstatus != 0
+ raise "Command Failed: [#{ent[:command]}]"
+ end
+
+ fail 'task scheduling bug!' if $rake_fiber_table.size >= $rake_jobs
+
+ ent[:fiber].transfer
+ end
+
+ wait_process(count + 1) if !$rake_fiber_table.empty? && exited.empty?
+ end
+
end
if __FILE__ == $0 then