gabrielg-vlad 1.2.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.
@@ -0,0 +1,43 @@
1
+ == Converting from Capistrano
2
+
3
+ * 'set scm' is removed. Vlad.load :scm => :something if you don't use subversion.
4
+ * 'task' blocks are renamed to 'remote_task'.
5
+ * Most variables are the same. See variables.txt for details.
6
+ * No +with_command+ / +sudo+ / +via+ wonkiness
7
+ * Uses real ssh so env vars and the like are not a problem
8
+ - no +with_env+ as a result.
9
+ * Vlad doesn't use ':no_release' or ':primary'.
10
+ - If you have a task that needs to run on only one host from a role,
11
+ you should declare a new role for that host:
12
+
13
+ role :master_db, "master.example.com"
14
+
15
+ ..and then override the role for the task you want to limit:
16
+
17
+ Rake::Task["mytask"].options[:roles] = :master_db
18
+
19
+ * The 'host' method can be used to consolidate multiple 'role' calls.
20
+ - host "www.example.com", :app, :web, :db
21
+ specifies a host with three roles.
22
+ * migrate_env is now migrate_args.
23
+ * Vlad doesn't have before/after magic add-on tasks.
24
+
25
+ == BEFORE:
26
+
27
+ set :application, "rubyholic"
28
+ set :domain, "zenspider.textdriven.com"
29
+ set :repository, "svn://svn.example.com/rubyholic/branches/stable"
30
+ set :deploy_to, "/users/home/zenspider/domains/new.rubyholic.com"
31
+
32
+ set :user, "zenspider"
33
+ set :use_sudo, false
34
+
35
+ role :web, domain
36
+ role :app, domain
37
+ role :db, domain, :primary => true
38
+
39
+ == AFTER:
40
+
41
+ set :domain, "zenspider.textdriven.com"
42
+ set :repository, "svn://svn.example.com/rubyholic/branches/stable"
43
+ set :deploy_to, "/users/home/zenspider/domains/new.rubyholic.com"
data/doco/perforce.txt ADDED
@@ -0,0 +1,5 @@
1
+ # Using Perforce
2
+
3
+ TODO: write real doco.
4
+
5
+ Set up a .p4config file and put it in the client's scm directories.
@@ -0,0 +1,78 @@
1
+
2
+ == Core Variables
3
+
4
+ repository:: REQUIRED: Repository path: e.g. http://repo.example.com/svn
5
+ deploy_to:: REQUIRED: Deploy path on target machines. e.g. /var/www/app
6
+ domain:: REQUIRED: Used for the common case of a single target
7
+ server. e.g. example.com
8
+ current_path:: The full path on the remote host that will be symlinked
9
+ as 'current'. Defaults to "#{deploy_to}/current".
10
+ current_release:: The full path to the current release's actual location.
11
+ Defaults to "#{releases_path}/#{releases.last}".
12
+ deploy_timestamped:: Create timestamped release directories instead of using
13
+ revision numbers. Defaults to true.
14
+ deploy_via:: Which SCM command should be used when deploying the app.
15
+ Defaults to "export".
16
+ latest_release:: The most recent release, which may not yet have been
17
+ symlinked. Defaults to release_path.
18
+ migrate_args:: Set this to change the RAILS_ENV that 'rake db:migrate'
19
+ will run under. Defaults to "".
20
+ migrate_target:: Set this if you need to specify a particular migration
21
+ 'VERSION' number. Defaults to "latest".
22
+ rails_env:: Specifies the RAILS_ENV environment variable that will
23
+ be used. Defaults to "production".
24
+ rake:: Set this if you need to specify an alternate path to
25
+ 'rake'. Defaults to "rake".
26
+ release_name:: Name of the release directory, if deploy_timestamped is
27
+ true. Defaults to timestamp: "YYYYMMDDHHMMSS".
28
+ release_path:: Path to this release, which may not have been created
29
+ yet. Defaults to "#{releases_path}/#{release_name}".
30
+ releases:: An array of all existing releases, oldest first.
31
+ Defaults to latest release directory name.
32
+ releases_path:: Full path to the 'releases' directory on the remote host.
33
+ Defaults to "#{deploy_to}/releases".
34
+ revision:: Revision to use for release. Defaults to 'head'.
35
+ rsync_cmd:: Path to rsync command. Defaults to "rsync".
36
+ rsync_flags:: Flags for rsync. Defaults to ['-azP', '--delete'].
37
+ scm_path:: Path on the remote host that will be used as 'working
38
+ space' for SCM tasks. Defaults to "#{deploy_to}/scm".
39
+ shared_path:: Full path to remote 'shared' directory, symlinked into
40
+ your app by default. Defaults to "#{deploy_to}/shared".
41
+ ssh_cmd:: Path to ssh. Defaults to "ssh".
42
+ ssh_flags:: Flags for ssh. Defaults to [].
43
+ sudo_cmd:: Path to sudo command. Defaults to "sudo".
44
+ sudo_flags:: Flogs for sudo. Defaults to nil.
45
+ sudo_password:: Asks for password when referenced.
46
+ umask:: Sets your umask value. Defaults to "02".
47
+
48
+ == Apache Web Variables:
49
+
50
+ web_command:: Command to execute when controlling the web server.
51
+ Defaults to "apachectl".
52
+
53
+ == Mongrel App Variables:
54
+
55
+ mongrel_address:: Defaults to "127.0.0.1"
56
+ mongrel_clean:: Defaults to false
57
+ mongrel_command:: Defaults to 'mongrel_rails'
58
+ mongrel_conf:: Defaults to "#{shared_path}/mongrel_cluster.conf"
59
+ mongrel_config_script:: Defaults to nil
60
+ mongrel_environment:: Defaults to "production"
61
+ mongrel_group:: Defaults to nil
62
+ mongrel_log_file:: Defaults to nil
63
+ mongrel_pid_file:: Defaults to nil
64
+ mongrel_port:: Defaults to 8000
65
+ mongrel_prefix:: Defaults to nil
66
+ mongrel_servers:: Defaults to 2
67
+ mongrel_user:: Defaults to nil
68
+
69
+ == Perforce SCM Variables:
70
+
71
+ p4_cmd:: The perforce command to use. Defaults to "p4"
72
+ source:: A perforce SCM worker instance.
73
+
74
+ == Subversion SCM Variables:
75
+
76
+ source:: A subversion SCM worker instance.
77
+ svn_cmd:: The subversion command to use. Defaults to "svn"
78
+
@@ -0,0 +1,595 @@
1
+ require 'rubygems'
2
+ require 'open4'
3
+ require 'rake'
4
+ require 'vlad'
5
+
6
+ $TESTING ||= false
7
+ $TRACE = Rake.application.options.trace
8
+
9
+ ##
10
+ # Declare a remote host and its roles. Equivalent to <tt>role</tt>,
11
+ # but shorter for multiple roles.
12
+ def host host_name, *roles
13
+ Rake::RemoteTask.host host_name, *roles
14
+ end
15
+
16
+ ##
17
+ # Copy a (usually generated) file to +remote_path+. Contents of block
18
+ # are copied to +remote_path+ and you may specify an optional
19
+ # base_name for the tempfile (aids in debugging).
20
+
21
+ def put remote_path, base_name = 'vlad.unknown'
22
+ Tempfile.open base_name do |fp|
23
+ fp.puts yield
24
+ fp.flush
25
+ rsync fp.path, remote_path
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Declare a Vlad task that will execute on all hosts by default. To
31
+ # limit that task to specific roles, use:
32
+ #
33
+ # remote_task :example, :arg1, :roles => [:app, :web] do
34
+ def remote_task name, *args_options, &b
35
+ Rake::RemoteTask.remote_task name, *args_options, &b
36
+ end
37
+
38
+ ##
39
+ # Declare a role and assign a remote host to it. Equivalent to the
40
+ # <tt>host</tt> method; provided for capistrano compatibility.
41
+ def role role_name, host = nil, args = {}
42
+ if block_given? then
43
+ raise ArgumentError, 'host not allowed with block' unless host.nil?
44
+
45
+ begin
46
+ Rake::RemoteTask.current_roles << role_name
47
+ yield
48
+ ensure
49
+ Rake::RemoteTask.current_roles.delete role_name
50
+ end
51
+ else
52
+ raise ArgumentError, 'host required' if host.nil?
53
+ Rake::RemoteTask.role role_name, host, args
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Execute the given command on the <tt>target_host</tt> for the
59
+ # current task.
60
+ def run *args, &b
61
+ Thread.current[:task].run(*args, &b)
62
+ end
63
+
64
+ # rsync the given files to <tt>target_host</tt>.
65
+ def rsync local, remote
66
+ Thread.current[:task].rsync local, remote
67
+ end
68
+
69
+ # Declare a variable called +name+ and assign it a value. A
70
+ # globally-visible method with the name of the variable is defined.
71
+ # If a block is given, it will be called when the variable is first
72
+ # accessed. Subsequent references to the variable will always return
73
+ # the same value. Raises <tt>ArgumentError</tt> if the +name+ would
74
+ # conflict with an existing method.
75
+ def set name, val = nil, &b
76
+ Rake::RemoteTask.set name, val, &b
77
+ end
78
+
79
+ # Returns the name of the host that the current task is executing on.
80
+ # <tt>target_host</tt> can uniquely identify a particular task/host
81
+ # combination.
82
+ def target_host
83
+ Thread.current[:task].target_host
84
+ end
85
+
86
+ if Gem::Version.new(RAKEVERSION) < Gem::Version.new('0.8') then
87
+ class Rake::Task
88
+ alias vlad_original_execute execute
89
+
90
+ def execute(args = nil)
91
+ vlad_original_execute
92
+ end
93
+ end
94
+ end
95
+
96
+ ##
97
+ # Rake::RemoteTask is a subclass of Rake::Task that adds
98
+ # remote_actions that execute in parallel on multiple hosts via ssh.
99
+
100
+ class Rake::RemoteTask < Rake::Task
101
+
102
+ @@current_roles = []
103
+
104
+ include Open4
105
+
106
+ ##
107
+ # Options for execution of this task.
108
+
109
+ attr_accessor :options
110
+
111
+ ##
112
+ # The host this task is running on during execution.
113
+
114
+ attr_accessor :target_host
115
+
116
+ ##
117
+ # An Array of Actions this host will perform during execution. Use
118
+ # enhance to add new actions to a task.
119
+
120
+ attr_reader :remote_actions
121
+
122
+ def self.current_roles
123
+ @@current_roles
124
+ end
125
+
126
+ ##
127
+ # Create a new task named +task_name+ attached to Rake::Application +app+.
128
+
129
+ def initialize(task_name, app)
130
+ super
131
+ @remote_actions = []
132
+ end
133
+
134
+ ##
135
+ # Add a local action to this task. This calls Rake::Task#enhance.
136
+
137
+ alias_method :original_enhance, :enhance
138
+
139
+ ##
140
+ # Add remote action +block+ to this task with dependencies +deps+. See
141
+ # Rake::Task#enhance.
142
+
143
+ def enhance(deps=nil, &block)
144
+ original_enhance(deps) # can't use super because block passed regardless.
145
+ @remote_actions << Action.new(self, block) if block_given?
146
+ self
147
+ end
148
+
149
+ ##
150
+ # Execute this action. Local actions will be performed first, then remote
151
+ # actions will be performed in parallel on each host configured for this
152
+ # RemoteTask.
153
+
154
+ def execute(args = nil)
155
+ raise(Vlad::ConfigurationError,
156
+ "No target hosts specified on task #{self.name} for roles #{options[:roles].inspect}") if
157
+ ! defined_target_hosts?
158
+
159
+ super args
160
+
161
+ @remote_actions.each { |act| act.execute(target_hosts, self, args) }
162
+ end
163
+
164
+ ##
165
+ # Use rsync to send +local+ to +remote+ on target_host.
166
+
167
+ def rsync local, remote
168
+ cmd = [rsync_cmd, rsync_flags, local, "#{@target_host}:#{remote}"]
169
+ cmd = cmd.flatten.compact
170
+
171
+ warn cmd.join(' ') if $TRACE
172
+
173
+ success = system(*cmd)
174
+
175
+ unless success then
176
+ raise Vlad::CommandFailedError, "execution failed: #{cmd.join ' '}"
177
+ end
178
+ end
179
+
180
+ ##
181
+ # Use ssh to execute +command+ on target_host. If +command+ uses sudo, the
182
+ # sudo password will be prompted for then saved for subsequent sudo commands.
183
+
184
+ def run command
185
+ cmd = [ssh_cmd, ssh_flags, target_host, command].flatten
186
+ result = []
187
+
188
+ warn cmd.join(' ') if $TRACE
189
+
190
+ pid, inn, out, err = popen4(*cmd)
191
+
192
+ inn.sync = true
193
+ streams = [out, err]
194
+ out_stream = {
195
+ out => $stdout,
196
+ err => $stderr,
197
+ }
198
+
199
+ # Handle process termination ourselves
200
+ status = nil
201
+ Thread.start do
202
+ status = Process.waitpid2(pid).last
203
+ end
204
+
205
+ until streams.empty? do
206
+ # don't busy loop
207
+ selected, = select streams, nil, nil, 0.1
208
+
209
+ next if selected.nil? or selected.empty?
210
+
211
+ selected.each do |stream|
212
+ if stream.eof? then
213
+ streams.delete stream if status # we've quit, so no more writing
214
+ next
215
+ end
216
+
217
+ data = stream.readpartial(1024)
218
+ out_stream[stream].write data
219
+
220
+ if stream == err and data =~ /^Password:/ then
221
+ inn.puts sudo_password
222
+ data << "\n"
223
+ $stderr.write "\n"
224
+ end
225
+
226
+ result << data
227
+ end
228
+ end
229
+
230
+ unless status.success? then
231
+ raise(Vlad::CommandFailedError,
232
+ "execution failed with status #{status.exitstatus}: #{cmd.join ' '}")
233
+ end
234
+
235
+ result.join
236
+ end
237
+
238
+ ##
239
+ # Returns an Array with every host configured.
240
+
241
+ def self.all_hosts
242
+ hosts_for(roles.keys)
243
+ end
244
+
245
+ ##
246
+ # The default environment values. Used for resetting (mostly for
247
+ # tests).
248
+
249
+ def self.default_env
250
+ @@default_env
251
+ end
252
+
253
+ ##
254
+ # The vlad environment.
255
+
256
+ def self.env
257
+ @@env
258
+ end
259
+
260
+ ##
261
+ # Fetches environment variable +name+ from the environment using
262
+ # default +default+.
263
+
264
+ def self.fetch name, default = nil
265
+ name = name.to_s if Symbol === name
266
+ if @@env.has_key? name then
267
+ protect_env(name) do
268
+ v = @@env[name]
269
+ v = @@env[name] = v.call if Proc === v
270
+ v
271
+ end
272
+ elsif default
273
+ v = @@env[name] = default
274
+ else
275
+ raise Vlad::FetchError
276
+ end
277
+ end
278
+
279
+ ##
280
+ # Add host +host_name+ that belongs to +roles+. Extra arguments may
281
+ # be specified for the host as a hash as the last argument.
282
+ #
283
+ # host is the inversion of role:
284
+ #
285
+ # host 'db1.example.com', :db, :master_db
286
+ #
287
+ # Is equivalent to:
288
+ #
289
+ # role :db, 'db1.example.com'
290
+ # role :master_db, 'db1.example.com'
291
+
292
+ def self.host host_name, *roles
293
+ opts = Hash === roles.last ? roles.pop : {}
294
+
295
+ roles.each do |role_name|
296
+ role role_name, host_name, opts.dup
297
+ end
298
+ end
299
+
300
+ ##
301
+ # Returns an Array of all hosts in +roles+.
302
+
303
+ def self.hosts_for *roles
304
+ roles.flatten.map { |r|
305
+ self.roles[r].keys
306
+ }.flatten.uniq.sort
307
+ end
308
+
309
+ def self.mandatory name, desc # :nodoc:
310
+ self.set(name) do
311
+ raise(Vlad::ConfigurationError,
312
+ "Please specify the #{desc} via the #{name.inspect} variable")
313
+ end
314
+ end
315
+
316
+ ##
317
+ # Ensures exclusive access to +name+.
318
+
319
+ def self.protect_env name # :nodoc:
320
+ @@env_locks[name].synchronize do
321
+ yield
322
+ end
323
+ end
324
+
325
+ ##
326
+ # Adds a remote task named +name+ with options +options+ that will
327
+ # execute +block+.
328
+
329
+ def self.remote_task name, *args, &block
330
+ options = (Hash === args.last) ? args.pop : {}
331
+ t = Rake::RemoteTask.define_task(name, *args, &block)
332
+ options[:roles] = Array options[:roles]
333
+ options[:roles] |= @@current_roles
334
+ t.options = options
335
+ t
336
+ end
337
+
338
+ ##
339
+ # Ensures +name+ does not conflict with an existing method.
340
+
341
+ def self.reserved_name? name # :nodoc:
342
+ !@@env.has_key?(name.to_s) && self.respond_to?(name)
343
+ end
344
+
345
+ ##
346
+ # Resets vlad, restoring all roles, tasks and environment variables
347
+ # to the defaults.
348
+
349
+ def self.reset
350
+ @@def_role_hash = {} # official default role value
351
+ @@roles = Hash.new { |h,k| h[k] = @@def_role_hash }
352
+ @@env = {}
353
+ @@tasks = {}
354
+ @@env_locks = Hash.new { |h,k| h[k] = Mutex.new }
355
+
356
+ @@default_env.each do |k,v|
357
+ case v
358
+ when Symbol, Fixnum, nil, true, false, 42 then # ummmm... yeah. bite me.
359
+ @@env[k] = v
360
+ else
361
+ @@env[k] = v.dup
362
+ end
363
+ end
364
+ end
365
+
366
+ ##
367
+ # Adds role +role_name+ with +host+ and +args+ for that host.
368
+
369
+ def self.role role_name, host, args = {}
370
+ [*host].each do |hst|
371
+ raise ArgumentError, "invalid host: #{hst}" if hst.nil? or hst.empty?
372
+ end
373
+ @@roles[role_name] = {} if @@def_role_hash.eql? @@roles[role_name]
374
+ @@roles[role_name][host] = args
375
+ end
376
+
377
+ ##
378
+ # The configured roles.
379
+
380
+ def self.roles
381
+ host domain, :app, :web, :db if @@roles.empty?
382
+
383
+ @@roles
384
+ end
385
+
386
+ ##
387
+ # Set environment variable +name+ to +value+ or +default_block+.
388
+ #
389
+ # If +default_block+ is defined, the block will be executed the
390
+ # first time the variable is fetched, and the value will be used for
391
+ # every subsequent fetch.
392
+
393
+ def self.set name, value = nil, &default_block
394
+ raise ArgumentError, "cannot provide both a value and a block" if
395
+ value and default_block
396
+ raise ArgumentError, "cannot set reserved name: '#{name}'" if
397
+ Rake::RemoteTask.reserved_name?(name) unless $TESTING
398
+
399
+ Rake::RemoteTask.default_env[name.to_s] = Rake::RemoteTask.env[name.to_s] =
400
+ value || default_block
401
+
402
+ Object.send :define_method, name do
403
+ Rake::RemoteTask.fetch name
404
+ end
405
+ end
406
+
407
+ ##
408
+ # Sets all the default values. Should only be called once. Use reset
409
+ # if you need to restore values.
410
+
411
+ def self.set_defaults
412
+ @@default_env ||= {}
413
+ self.reset
414
+
415
+ mandatory :repository, "repository path"
416
+ mandatory :deploy_to, "deploy path"
417
+ mandatory :domain, "server domain"
418
+
419
+ simple_set(:deploy_timestamped, true,
420
+ :deploy_via, :export,
421
+ :keep_releases, 5,
422
+ :migrate_args, "",
423
+ :migrate_target, :latest,
424
+ :rails_env, "production",
425
+ :rake_cmd, "rake",
426
+ :revision, "head",
427
+ :rsync_cmd, "rsync",
428
+ :rsync_flags, ['-azP', '--delete'],
429
+ :ssh_cmd, "ssh",
430
+ :ssh_flags, [],
431
+ :sudo_cmd, "sudo",
432
+ :sudo_flags, nil,
433
+ :umask, '02')
434
+
435
+ set(:current_release) { File.join(releases_path, releases[-1]) }
436
+ set(:latest_release) { deploy_timestamped ?release_path: current_release }
437
+ set(:previous_release) { File.join(releases_path, releases[-2]) }
438
+ set(:release_name) { Time.now.utc.strftime("%Y%m%d%H%M%S") }
439
+ set(:release_path) { File.join(releases_path, release_name) }
440
+ set(:releases) { task.run("ls -x #{releases_path}").split.sort }
441
+
442
+ set_path :current_path, "current"
443
+ set_path :releases_path, "releases"
444
+ set_path :scm_path, "scm"
445
+ set_path :shared_path, "shared"
446
+
447
+ set(:sudo_password) do
448
+ state = `stty -g`
449
+
450
+ raise Vlad::Error, "stty(1) not found" unless $?.success?
451
+
452
+ begin
453
+ system "stty -echo"
454
+ $stdout.print "sudo password: "
455
+ $stdout.flush
456
+ sudo_password = $stdin.gets
457
+ $stdout.puts
458
+ ensure
459
+ system "stty #{state}"
460
+ end
461
+ sudo_password
462
+ end
463
+ end
464
+
465
+ def self.set_path(name, subdir) # :nodoc:
466
+ set(name) { File.join(deploy_to, subdir) }
467
+ end
468
+
469
+ def self.simple_set(*args) # :nodoc:
470
+ args = Hash[*args]
471
+ args.each do |k, v|
472
+ set k, v
473
+ end
474
+ end
475
+
476
+ ##
477
+ # The Rake::RemoteTask executing in this Thread.
478
+
479
+ def self.task
480
+ Thread.current[:task]
481
+ end
482
+
483
+ ##
484
+ # The configured Rake::RemoteTasks.
485
+
486
+ def self.tasks
487
+ @@tasks
488
+ end
489
+
490
+ ##
491
+ # Execute +command+ under sudo using run.
492
+
493
+ def sudo command
494
+ run [sudo_cmd, sudo_flags, command].compact.join(" ")
495
+ end
496
+
497
+ ##
498
+ # The hosts this task will execute on. The hosts are determined from
499
+ # the role this task belongs to.
500
+ #
501
+ # The target hosts may be overridden by providing a comma-separated
502
+ # list of commands to the HOSTS environment variable:
503
+ #
504
+ # rake my_task HOSTS=app1.example.com,app2.example.com
505
+
506
+ def target_hosts
507
+ if hosts = ENV["HOSTS"] then
508
+ hosts.strip.gsub(/\s+/, '').split(",")
509
+ else
510
+ roles = Array options[:roles]
511
+
512
+ if roles.empty? then
513
+ Rake::RemoteTask.all_hosts
514
+ else
515
+ Rake::RemoteTask.hosts_for roles
516
+ end
517
+ end
518
+ end
519
+
520
+ ##
521
+ # Similar to target_hosts, but returns true if user defined any hosts, even
522
+ # an empty list.
523
+
524
+ def defined_target_hosts?
525
+ return true if ENV["HOSTS"]
526
+ roles = Array options[:roles]
527
+ return true if roles.empty?
528
+ # borrowed from hosts_for:
529
+ roles.flatten.each { |r|
530
+ return true unless @@def_role_hash.eql? Rake::RemoteTask.roles[r]
531
+ }
532
+ return false
533
+ end
534
+
535
+ ##
536
+ # Action is used to run a task's remote_actions in parallel on each
537
+ # of its hosts. Actions are created automatically in
538
+ # Rake::RemoteTask#enhance.
539
+
540
+ class Action
541
+
542
+ ##
543
+ # The task this action is attached to.
544
+
545
+ attr_reader :task
546
+
547
+ ##
548
+ # The block this action will execute.
549
+
550
+ attr_reader :block
551
+
552
+ ##
553
+ # An Array of threads, one for each host this action executes on.
554
+
555
+ attr_reader :workers
556
+
557
+ ##
558
+ # Creates a new Action that will run +block+ for +task+.
559
+
560
+ def initialize task, block
561
+ @task = task
562
+ @block = block
563
+ @workers = []
564
+ end
565
+
566
+ def == other # :nodoc:
567
+ return false unless Action === other
568
+ block == other.block && task == other.task
569
+ end
570
+
571
+ ##
572
+ # Execute this action on +hosts+ in parallel. Returns when block
573
+ # has completed for each host.
574
+
575
+ def execute hosts, task, args
576
+ hosts.each do |host|
577
+ t = task.clone
578
+ t.target_host = host
579
+ thread = Thread.new(t) do |task|
580
+ Thread.current[:task] = task
581
+ case block.arity
582
+ when 1
583
+ block.call task
584
+ else
585
+ block.call task, args
586
+ end
587
+ end
588
+ @workers << thread
589
+ end
590
+ @workers.each { |w| w.join }
591
+ end
592
+ end
593
+ end
594
+
595
+ Rake::RemoteTask.set_defaults