drake 0.8.4.1.1.0 → 0.8.4.1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.drake CHANGED
@@ -1,6 +1,10 @@
1
1
 
2
2
  = Drake Changelog
3
3
 
4
+ == Version 0.8.4.1.2.0
5
+
6
+ * github master now represents mainline-merge-candidate branch.
7
+
4
8
  == Version 0.8.4.1.0.16-18
5
9
 
6
10
  * Merge with latest comp_tree.
data/README CHANGED
@@ -1,6 +1,6 @@
1
1
  = DRAKE -- Distributed Rake
2
2
 
3
- A branch of Rake supporting parallel task execution.
3
+ A branch of Rake supporting automatic parallelizing of tasks.
4
4
 
5
5
  == Synopsis
6
6
 
@@ -18,90 +18,7 @@ or equivalently,
18
18
 
19
19
  == Notes
20
20
 
21
- === Compatibility
22
-
23
- Drake is 100% compatible with Rake. The code path for
24
- <tt>--threads=1</tt> is effectively identical to that of Rake's.
25
- Drake passes all of Rake's unit tests, with any number of threads from
26
- 1 to 1000 (the most tested).
27
-
28
- === Dependencies
29
-
30
- In a given Rakefile, it is possible (even likely) that the
31
- dependency tree has not been properly defined. Consider
32
-
33
- task :a => [:x, :y, :z]
34
-
35
- With single-threaded Rake, _x_,_y_,_z_ will be invoked <em>in that
36
- order</em> before _a_ is invoked (assuming there are no other rules
37
- involving these tasks). However with <code>drake -jN</code> (for +N+
38
- > 1), one should not expect any particular order of execution. Since
39
- there is no dependency specified between _x_,_y_,_z_ above, Drake is
40
- free to run them in any order.
41
-
42
- If you wish _x_,_y_,_z_ to be invoked sequentially, then write
43
-
44
- task :a => seq[:x, :y, :z]
45
-
46
- This is shorthand for
47
-
48
- task :a => :z
49
- task :z => :y
50
- task :y => :x
51
-
52
- Upon invoking _a_, the above rules say: "Can't do _a_ until _z_ is
53
- complete; can't do _z_ until _y_ is complete; can't do _y_ until _x_
54
- is complete; therefore do _x_." In this fashion the sequence
55
- _x_,_y_,_z_ is enforced.
56
-
57
- The problem of insufficient dependencies plagues Makefiles as well,
58
- and is sometimes called "not j-safe".
59
-
60
- === MultiTask
61
-
62
- When more than one thread is given, +multitask+ behaves just like
63
- +task+. Those tasks which may properly be run in parallel will be run
64
- in parallel; those which cannot, will not. It is not the user's job
65
- to decide. In other words, for <tt>-jN</tt> (+N+ > 1), +multitask+ is
66
- an alias of +task+.
67
-
68
- For <tt>-j1</tt> (default), +multitask+ behaves as the original.
69
-
70
- === Task#invoke inside Task#invoke
71
-
72
- Parallelizing code means surrendering control over the
73
- micro-management of its execution. Manually invoking tasks inside
74
- other tasks is rather contrary to this notion, throwing a monkey
75
- wrench into the system. An exception will be raised when this is
76
- attempted in multi-threaded mode.
77
-
78
- === Migrating to -j
79
-
80
- First of all, do you want to bother with <tt>-j</tt>? If you are
81
- satisfied with your build time, then there is really no reason to use
82
- it.
83
-
84
- If on the other hand your build takes twenty minutes to complete, you
85
- may be interested in investing some time getting the full dependency
86
- tree correct in order to take advantage of multiple CPUs or cores.
87
-
88
- Though Drake cannot fathom what <em>you</em> mean by a correct
89
- dependency, there is a tool available which may help you get closer to
90
- saying what you mean:
91
-
92
- % drake --rand[=SEED]
93
-
94
- This will randomize the order of sibling prerequisites for each task.
95
- When given the optional SEED string, it will call
96
- <tt>srand(SEED.hash)</tt> to produce the same permutation each time.
97
- The randomize option also disables +multitask+, making it a regular
98
- +task+. (In multi-threaded mode, +multitask+ is already a regular
99
- +task+.)
100
-
101
- Though this option may produce an error due to unspecified
102
- dependencies, with SEED at least it will be an error which is exactly
103
- the same on each run. In addition you'll have the major debugging
104
- advantage of using a single thread.
21
+ See parallel.rdoc.
105
22
 
106
23
  == Links
107
24
 
data/Rakefile CHANGED
@@ -63,23 +63,22 @@ task :tf => :test_functional
63
63
  task :tu => :test_units
64
64
  task :tc => :test_contribs
65
65
  task :test => :test_units
66
+ task :test_all => [:test_serial, :test_parallel]
66
67
 
67
- all_test_files = FileList[
68
+ test_files = FileList[
68
69
  'test/test*.rb',
69
70
  'test/contrib/test*.rb',
70
71
  'test/fun*.rb'
71
72
  ]
72
73
 
73
- task :test_all => [:test_single_threaded, :test_parallel]
74
-
75
- Rake::TestTask.new(:test_parallel) do |t|
76
- t.test_files = FileList['test/parallel.rb'] + all_test_files
74
+ Rake::TestTask.new(:test_serial) do |t|
75
+ t.test_files = ['test/serial_setup.rb'] + test_files
77
76
  t.warning = true
78
77
  t.verbose = false
79
78
  end
80
79
 
81
- Rake::TestTask.new(:test_single_threaded) do |t|
82
- t.test_files = FileList['test/single_threaded.rb'] + all_test_files
80
+ Rake::TestTask.new(:test_parallel) do |t|
81
+ t.test_files = ['test/parallel_setup.rb'] + test_files
83
82
  t.warning = true
84
83
  t.verbose = false
85
84
  end
@@ -102,12 +101,6 @@ Rake::TestTask.new(:test_contribs) do |t|
102
101
  t.verbose = false
103
102
  end
104
103
 
105
- Rake::TestTask.new(:test_current) do |t|
106
- t.test_files = FileList['test/parallel.rb', 'test/test_tasks.rb']
107
- t.warning = true
108
- t.verbose = false
109
- end
110
-
111
104
  begin
112
105
  require 'rcov/rcovtask'
113
106
 
@@ -167,7 +160,6 @@ PKG_FILES = FileList[
167
160
  '[A-Z]*',
168
161
  'bin/drake',
169
162
  'lib/**/*.rb',
170
- 'lib/rake/comp_tree/**/*.rb',
171
163
  'test/**/*.rb',
172
164
  'test/**/*.rf',
173
165
  'test/**/*.mf',
@@ -187,7 +179,7 @@ else
187
179
 
188
180
  s.name = 'drake'
189
181
  s.version = $package_version
190
- s.summary = "A fork of Rake supporting parallel task execution."
182
+ s.summary = "A branch of Rake supporting automatic parallelizing of tasks."
191
183
  s.description = <<-EOF
192
184
  Rake is a Make-like program implemented in Ruby. Tasks
193
185
  and dependencies are specified in standard Ruby syntax.
@@ -195,6 +187,7 @@ else
195
187
 
196
188
  #### Dependencies and requirements.
197
189
 
190
+ s.add_dependency('comp_tree', '>= 0.7.1')
198
191
  #s.add_dependency('log4r', '> 1.0.4')
199
192
  #s.requirements << ""
200
193
 
@@ -40,6 +40,9 @@ Options are:
40
40
  [<tt>--libdir</tt> _directory_ (-I)]
41
41
  Add _directory_ to the list of directories searched for require.
42
42
 
43
+ [<tt>--threads</tt> _number_ (-j)]
44
+ Run up to N independent tasks simultaneously in separate threads.
45
+
43
46
  [<tt>--nosearch</tt> (-N)]
44
47
  Do not search for a Rakefile in parent directories.
45
48
 
@@ -60,6 +63,12 @@ Options are:
60
63
  [<tt>--rakelibdir</tt> _rakelibdir_ (-R)]
61
64
  Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')
62
65
 
66
+ [<tt>--randomize</tt>[=_seed_]]
67
+ Randomize the order of sibling prerequisites for each task. When
68
+ _seed_ is given, <tt>srand(seed.hash)</tt> will be called so that
69
+ the same permutations will be produced for subsequent runs with
70
+ the same _seed_.
71
+
63
72
  [<tt>--require</tt> _name_ (-r)]
64
73
  Require _name_ before executing the Rakefile.
65
74
 
data/doc/parallel.rdoc ADDED
@@ -0,0 +1,112 @@
1
+
2
+ = Automatically Running Tasks in Parallel
3
+
4
+ Consider these task definitions:
5
+
6
+ task :default => [:a, :b]
7
+ task :a => [:x, :y]
8
+ task :b
9
+
10
+ To create the dependency graph, let nodes represent tasks and child
11
+ nodes represent prerequisites,
12
+
13
+ default
14
+ / \
15
+ / \
16
+ a b
17
+ / \
18
+ / \
19
+ x y
20
+
21
+ Notice the dependency graph already contains the information necessary
22
+ for parallelizing tasks. In particular, tasks _x_, _y_, and _b_ may
23
+ run in parallel. When _x_ and _y_ are finished, _a_ and _b_ may run
24
+ in parallel.
25
+
26
+ So why, then, does +multitask+ exist? Rake already has the
27
+ information it needs -- why must we provide it a second time? The
28
+ short answer is probably that +multitask+ was easy to implement, while
29
+ dynamically finding parallelizable tasks and safely executing them in
30
+ separate threads requires a little more code.
31
+
32
+ Rake now supports the -j option which does exactly this. To run up to
33
+ three tasks in parallel,
34
+
35
+ % rake -j3
36
+
37
+ or equivalently,
38
+
39
+ % rake --threads 3
40
+
41
+ == A Note on Dependencies
42
+
43
+ Without +multitask+ or -j (or --randomize, explained below), Rake
44
+ invokes tasks in a predetermined order. Rakefile authors inevitably
45
+ come to rely on this order. Even though their dependency graph may be
46
+ wide and hierarchical, the only graph which has ever been tested is
47
+ linear: one which results from visiting each node in sequence.
48
+
49
+ Consider
50
+
51
+ task :a => [:x, :y, :z]
52
+
53
+ When Rake runs in single-threaded mode (the default), _x_,_y_,_z_ will
54
+ be invoked <em>in that order</em> before _a_ is invoked, assuming no
55
+ other rules exist involving these tasks. However with -j _N_ for
56
+ _N_ > 1, one should not expect any particular order of execution.
57
+ Since there is no dependency specified between _x_,_y_,_z_ above, Rake
58
+ is free to run them in any order.
59
+
60
+ The problem of underspecified dependencies plagues Makefiles as well
61
+ (GNU make has supported -j for some time).
62
+
63
+ == Migrating to -j
64
+
65
+ Suppose -j produces errors from insufficient dependencies in your
66
+ Rakefile. Do you want to bother fixing them? If you are satisfied
67
+ with your build time, then there is really no reason to use -j.
68
+
69
+ If on the other hand your build takes twenty minutes to complete, you
70
+ may be interested in getting the full dependency graph correct in
71
+ order to take advantage of multiple CPUs or cores.
72
+
73
+ Though Rake cannot fathom what <em>you</em> mean by a correct
74
+ dependency, there is a tool available which may help you get closer to
75
+ saying what you mean:
76
+
77
+ % rake --randomize[=SEED]
78
+
79
+ This will randomize the order of sibling prerequisites for each task.
80
+ When SEED is given, <tt>srand(SEED.hash)</tt> will be called so that
81
+ the same permutations will be produced for subsequent runs with the
82
+ same SEED.
83
+
84
+ Though this option may cause an error due to underspecified
85
+ dependencies, with SEED at least it will be an error which is exactly
86
+ the same on each run. In addition you'll have the major debugging
87
+ advantage of using a single thread.
88
+
89
+ == Task#invoke inside Task#invoke
90
+
91
+ Parallelizing tasks means surrendering control over the
92
+ micro-management of their execution. Manually invoking tasks inside
93
+ other tasks is rather contrary to this notion, throwing a monkey
94
+ wrench into the system. An exception will be raised when this is
95
+ attempted in -j mode.
96
+
97
+ == What is the Difference Between -j and Multitask?
98
+
99
+ What if you replaced +task+ with +multitask+ everywhere in your
100
+ Rakefile? Isn't that the same as -j?
101
+
102
+ It is effectively the same when the number of tasks is small. However
103
+ an all-multitask Rakefile becomes problematic as the number of tasks
104
+ and dependencies increase. The number of lines in the dependency
105
+ graph would be equal to the number of threads running simultaneously.
106
+ Multitask blindly fires off N threads for the N prerequisites of a
107
+ task.
108
+
109
+ A simplistic +multitask+ setup for compiling 100 C files might spawn
110
+ 100 threads running 100 compiler processes all at the same time. To
111
+ reduce the load you could make several reasonably-sized multitasks
112
+ then tie them together with a regular +task+. Or you could just use -j.
data/doc/rakefile.rdoc CHANGED
@@ -150,6 +150,12 @@ to do extra synchronization for Rake's benefit. However, if there are
150
150
  user data structures shared between the parallel prerequisites, the
151
151
  user must do whatever is necessary to prevent race conditions.
152
152
 
153
+ === Automatically Running Tasks in Parallel
154
+
155
+ Rake now supports the command-line option -j for automatically
156
+ detecting non-dependent tasks and safely executing them in parallel.
157
+ See parallel.rdoc.
158
+
153
159
  == Tasks with Arguments
154
160
 
155
161
  Prior to version 0.8.0, rake was only able to handle command line
data/install.rb CHANGED
@@ -85,4 +85,4 @@ end
85
85
 
86
86
  # and the executable
87
87
 
88
- installBIN("bin/drake", "drake")
88
+ installBIN("bin/rake", "rake")
data/lib/rake.rb CHANGED
@@ -29,7 +29,7 @@
29
29
  # as a library via a require statement, but it can be distributed
30
30
  # independently as an application.
31
31
 
32
- RAKEVERSION = '0.8.4.1.1.0'
32
+ RAKEVERSION = '0.8.4.1.2.0'
33
33
 
34
34
  require 'rbconfig'
35
35
  require 'fileutils'
@@ -37,7 +37,6 @@ require 'singleton'
37
37
  require 'monitor'
38
38
  require 'optparse'
39
39
  require 'ostruct'
40
- require 'rake/parallel'
41
40
 
42
41
  require 'rake/win32'
43
42
 
@@ -408,13 +407,13 @@ module Rake
408
407
  # InvocationChain tracks the chain of task invocations to detect
409
408
  # circular dependencies.
410
409
  class InvocationChain
410
+ attr_reader :value # :nodoc:
411
+
411
412
  def initialize(value, tail)
412
413
  @value = value
413
414
  @tail = tail
414
415
  end
415
416
 
416
- attr_reader :value # :nodoc:
417
-
418
417
  def member?(obj)
419
418
  @value == obj || @tail.member?(obj)
420
419
  end
@@ -580,74 +579,46 @@ module Rake
580
579
  self
581
580
  end
582
581
 
583
- def base_invoke(*args) #:nodoc:
582
+ def invoke_serial(*args) # :nodoc:
584
583
  task_args = TaskArguments.new(arg_names, args)
585
584
  invoke_with_call_chain(task_args, InvocationChain::EMPTY)
586
585
  end
587
586
 
588
587
  # Invoke the task if it is needed. Prerequites are invoked first.
589
588
  def invoke(*args)
590
- if application.num_threads == 1
591
- base_invoke(*args)
589
+ if application.options.threads == 1
590
+ invoke_serial(*args)
592
591
  else
593
- if application.parallel.lock.locked?
594
- raise "Calling Task#invoke within a task is not allowed."
595
- end
596
- application.parallel.lock.synchronize {
597
- application.parallel.tasks.clear
598
- application.parallel.needed.clear
599
- base_invoke(*args)
600
- application.invoke_parallel(self.name)
601
- }
592
+ invoke_parallel(*args)
602
593
  end
603
594
  end
604
-
595
+
605
596
  # Same as invoke, but explicitly pass a call chain to detect
606
597
  # circular dependencies.
607
598
  def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
608
599
  new_chain = InvocationChain.append(self, invocation_chain)
609
- @lock.synchronize do
610
- if application.options.trace and application.num_threads == 1
611
- puts "** Invoke #{name} #{format_trace_flags}"
612
- end
613
- return if @already_invoked
614
- @already_invoked = true
615
-
616
- if application.options.randomize
617
- @prerequisites = @prerequisites.sort_by { rand }
618
- end
619
-
620
- if application.num_threads == 1
600
+ if application.options.threads == 1
601
+ @lock.synchronize do
602
+ return unless prepare_invoke
621
603
  invoke_prerequisites(task_args, new_chain)
622
604
  execute(task_args) if needed?
623
- else
624
- #
625
- # Parallel mode -- gather tasks for batch execution.
626
- #
627
- # Either the task knows it's needed or we've marked it as
628
- # needed.
629
- #
630
- # Why do we manually mark tasks as needed? Since this is a
631
- # dry run, files are not created or modified. Therefore the
632
- # 'needed?' result does not propagate through the recursion.
633
- #
634
- prereqs = invoke_prerequisites_parallel(task_args, new_chain)
635
- if needed? or application.parallel.needed[self]
636
- application.parallel.tasks[name] = [task_args, prereqs]
637
- unless invocation_chain == InvocationChain::EMPTY
638
- application.parallel.needed[invocation_chain.value] = true
639
- end
640
- end
641
605
  end
606
+ else
607
+ return unless prepare_invoke
608
+ invoke_with_call_chain_collector(task_args, new_chain, invocation_chain)
642
609
  end
643
610
  end
644
611
  protected :invoke_with_call_chain
645
612
 
646
- def invoke_prerequisite(prereq_name, task_args, invocation_chain) #:nodoc:
647
- prereq = application[prereq_name, @scope]
648
- prereq_args = task_args.new_scope(prereq.arg_names)
649
- prereq.invoke_with_call_chain(prereq_args, invocation_chain)
650
- prereq
613
+ def prepare_invoke # :nodoc:
614
+ if application.options.randomize
615
+ @prerequisites = @prerequisites.sort_by { rand }
616
+ end
617
+ if application.options.trace
618
+ puts "** Invoke #{name} #{format_trace_flags}"
619
+ end
620
+ return if @already_invoked
621
+ @already_invoked = true
651
622
  end
652
623
 
653
624
  # Invoke all the prerequisites of a task.
@@ -657,14 +628,13 @@ module Rake
657
628
  }
658
629
  end
659
630
 
660
- # Parallel dry-run accumulator.
661
- # This also serves to circumvent MultiTask#invoke_prerequisites.
662
- def invoke_prerequisites_parallel(task_args, invocation_chain) #:nodoc:
663
- @prerequisites.map { |n|
664
- invoke_prerequisite(n, task_args, invocation_chain)
665
- }
631
+ def invoke_prerequisite(prereq_name, task_args, invocation_chain) #:nodoc:
632
+ prereq = application[prereq_name, @scope]
633
+ prereq_args = task_args.new_scope(prereq.arg_names)
634
+ prereq.invoke_with_call_chain(prereq_args, invocation_chain)
635
+ prereq
666
636
  end
667
-
637
+
668
638
  # Format the trace flags for display.
669
639
  def format_trace_flags
670
640
  flags = []
@@ -1009,21 +979,6 @@ def import(*fns)
1009
979
  end
1010
980
  end
1011
981
 
1012
- #
1013
- # +seq+ : Force tasks to be executed sequentially.
1014
- #
1015
- def seq
1016
- Rake::SEQ_LAMBDA
1017
- end
1018
- module Rake
1019
- SEQ_LAMBDA = lambda { |*task_names|
1020
- (1...task_names.size).each { |n|
1021
- task task_names[n] => task_names[n - 1]
1022
- }
1023
- task_names.last
1024
- }
1025
- end
1026
-
1027
982
  # ###########################################################################
1028
983
  # This a FileUtils extension that defines several additional commands to be
1029
984
  # added to the FileUtils utility functions.
@@ -1754,22 +1709,12 @@ module Rake
1754
1709
  attr_accessor :last_description
1755
1710
  alias :last_comment :last_description # Backwards compatibility
1756
1711
 
1757
- attr_accessor :num_threads
1758
- attr_reader :parallel
1759
-
1760
1712
  def initialize
1761
1713
  super
1762
1714
  @tasks = Hash.new
1763
1715
  @rules = Array.new
1764
1716
  @scope = Array.new
1765
1717
  @last_description = nil
1766
-
1767
- @num_threads = 1
1768
-
1769
- @parallel = Struct.new(:tasks, :needed, :lock).new
1770
- @parallel.tasks = Hash.new
1771
- @parallel.needed = Hash.new
1772
- @parallel.lock = Mutex.new
1773
1718
  end
1774
1719
 
1775
1720
  def create_rule(*args, &block)
@@ -2024,6 +1969,26 @@ module Rake
2024
1969
 
2025
1970
  end # TaskManager
2026
1971
 
1972
+ #
1973
+ # Lazily pull in the parallelizing code
1974
+ #
1975
+ class Options < OpenStruct # :nodoc:
1976
+ attr_reader :threads
1977
+
1978
+ def initialize
1979
+ super
1980
+ @threads = 1
1981
+ end
1982
+
1983
+ def threads=(n)
1984
+ if n > 1 and require('rake/parallel')
1985
+ Task.module_eval { include Parallel::TaskMixin }
1986
+ Application.module_eval { include Parallel::ApplicationMixin }
1987
+ end
1988
+ @threads = n
1989
+ end
1990
+ end
1991
+
2027
1992
  ######################################################################
2028
1993
  # Rake main application object. When invoking +rake+ from the
2029
1994
  # command line, a Rake::Application object is created and run.
@@ -2118,7 +2083,7 @@ module Rake
2118
2083
 
2119
2084
  # Application options from the command line
2120
2085
  def options
2121
- @options ||= OpenStruct.new
2086
+ @options ||= Options.new
2122
2087
  end
2123
2088
 
2124
2089
  # private ----------------------------------------------------------------
@@ -2306,17 +2271,8 @@ module Rake
2306
2271
  "Execute some Ruby code, then continue with normal task processing.",
2307
2272
  lambda { |value| eval(value) }
2308
2273
  ],
2309
- ['--threads', '-j N', "Specifies the number of threads to run simultaneously.",
2310
- lambda { |value| self.num_threads = value.to_i }
2311
- ],
2312
- ['--randomize[=SEED]', "Randomize task prerequisite orders",
2313
- lambda { |value|
2314
- MultiTask.class_eval { remove_method(:invoke_prerequisites) }
2315
- options.randomize = true
2316
- if value
2317
- srand(value.hash)
2318
- end
2319
- }
2274
+ ['--threads', '-j N', "Run up to N independent tasks simultaneously in separate threads.",
2275
+ lambda { |value| options.threads = value.to_i }
2320
2276
  ],
2321
2277
  ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.",
2322
2278
  lambda { |value| $:.push(value) }
@@ -2338,6 +2294,13 @@ module Rake
2338
2294
  "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')",
2339
2295
  lambda { |value| options.rakelib = value.split(':') }
2340
2296
  ],
2297
+ ['--randomize[=SEED]', "Randomize the order of sibling prerequisites.",
2298
+ lambda { |value|
2299
+ options.randomize = true
2300
+ MultiTask.class_eval { remove_method(:invoke_prerequisites) }
2301
+ srand(value.hash) if value
2302
+ }
2303
+ ],
2341
2304
  ['--require', '-r MODULE', "Require MODULE before executing rakefile.",
2342
2305
  lambda { |value|
2343
2306
  begin