281
####################################################################
282
# TaskAguments manage the arguments passed to a task.
289
def initialize(names, values, parent=nil)
293
names.each_with_index { |name, i|
294
@hash[name.to_sym] = values[i]
298
# Create a new argument scope using the prerequisite argument
301
values = names.collect { |n| self[n] }
302
self.class.new(names, values, self)
305
# Find an argument value by name or index.
314
def method_missing(sym, *args, &block)
333
if @hash.has_key?(name)
335
elsif ENV.has_key?(name.to_s)
337
elsif ENV.has_key?(name.to_s.upcase)
338
ENV[name.to_s.upcase]
345
####################################################################
346
# InvocationChain tracks the chain of task invocations to detect
347
# circular dependencies.
348
class InvocationChain
349
def initialize(value, tail)
355
@value == obj || @tail.member?(obj)
360
fail RuntimeError, "Circular dependency detected: #{to_s} => #{value}"
362
self.class.new(value, self)
369
def self.append(value, chain)
379
class EmptyInvocationChain
384
InvocationChain.new(value, self)
391
EMPTY = EmptyInvocationChain.new
393
end # class InvocationChain
332
449
@prerequisites = FileList[]
334
451
@already_invoked = false
336
454
@lock = Mutex.new
337
455
@application = app
338
456
@scope = app.current_scope
341
460
# Enhance a task with prerequisites or actions. Returns self.
342
461
def enhance(deps=nil, &block)
343
462
@prerequisites |= deps if deps
344
463
@actions << block if block_given?
348
467
# Name of the task, including any namespace qualifiers.
472
# Name of task with argument list description.
473
def name_with_args # :nodoc:
475
"#{name}#{arg_description}"
481
# Argument description (nil if none).
482
def arg_description # :nodoc:
483
@arg_names ? "[#{(arg_names || []).join(',')}]" : nil
486
# Name of arguments for this task.
353
491
# Invoke the task if it is needed. Prerequites are invoked first.
493
task_args = TaskArguments.new(arg_names, args)
494
invoke_with_call_chain(task_args, InvocationChain::EMPTY)
497
# Same as invoke, but explicitly pass a call chain to detect
498
# circular dependencies.
499
def invoke_with_call_chain(task_args, invocation_chain)
500
new_chain = InvocationChain.append(self, invocation_chain)
355
501
@lock.synchronize do
356
502
if application.options.trace
357
503
puts "** Invoke #{name} #{format_trace_flags}"
359
505
return if @already_invoked
360
506
@already_invoked = true
507
invoke_prerequisites(task_args, new_chain)
508
execute(task_args) if needed?
511
protected :invoke_with_call_chain
366
513
# Invoke all the prerequisites of a task.
367
def invoke_prerequisites
514
def invoke_prerequisites(task_args, invocation_chain)
368
515
@prerequisites.each { |n|
369
application[n, @scope].invoke
516
prereq = application[n, @scope]
517
prereq_args = task_args.new_scope(prereq.arg_names)
518
prereq.invoke_with_call_chain(prereq_args, invocation_chain)
389
538
puts "** Execute #{name}"
391
540
application.enhance_with_matching_rule(name) if @actions.empty?
392
@actions.each { |act| result = act.call(self) }
541
@actions.each do |act|
395
551
# Is this task needed?
400
556
# Timestamp for this task. Basic tasks return the current time for their
401
557
# time stamp. Other tasks can be more sophisticated.
403
559
@prerequisites.collect { |p| application[p].timestamp }.max || Time.now
562
# Add a description to the task. The description can consist of an option
563
# argument list (enclosed brackets) and an optional comment.
564
def add_description(description)
565
return if ! description
566
comment = description.strip
567
add_comment(comment) if comment && ! comment.empty?
570
# Writing to the comment attribute is the same as adding a description.
571
def comment=(description)
572
add_description(description)
406
575
# Add a comment to the task. If a comment alread exists, separate
407
576
# the new comment with " / ".
408
577
def add_comment(comment)
579
@full_comment << " / "
583
@full_comment << comment
584
if @full_comment =~ /\A([^.]+?\.)( |$)/
587
@comment = @full_comment
592
# Set the names of the arguments for this task. +args+ should be
593
# an array of symbols, one for each argument name.
594
def set_arg_names(args)
595
@arg_names = args.map { |a| a.to_sym }
418
598
# Return a string describing the internal state of a task. Useful for
420
600
def investigation
421
601
result = "------------------------------\n"
422
result << "Investigating #{name}\n"
602
result << "Investigating #{name}\n"
423
603
result << "class: #{self.class}\n"
424
604
result << "task needed: #{needed?}\n"
425
605
result << "timestamp: #{timestamp}\n"
458
638
def [](task_name)
459
639
Rake.application[task_name]
462
642
# TRUE if the task name is already defined.
463
643
def task_defined?(task_name)
464
644
Rake.application.lookup(task_name) != nil
467
647
# Define a task given +args+ and an option block. If a rule with the
468
648
# given name already exists, the prerequisites and actions are added to
469
649
# the existing task. Returns the defined task.
470
def define_task(args, &block)
471
Rake.application.define_task(self, args, &block)
650
def define_task(*args, &block)
651
Rake.application.define_task(self, *args, &block)
474
# Define a rule for synthesizing tasks.
475
def create_rule(args, &block)
476
Rake.application.create_rule(args, &block)
654
# Define a rule for synthesizing tasks.
655
def create_rule(*args, &block)
656
Rake.application.create_rule(*args, &block)
479
659
# Apply the scope to the task name according to the rules for
1423
1583
@task_manager.tasks
1428
1588
####################################################################
1429
# The TaskManager module is a mixin for managing tasks.
1589
# The TaskManager module is a mixin for managing tasks.
1430
1590
module TaskManager
1431
1591
# Track the last comment made in the Rakefile.
1432
attr_accessor :last_comment
1592
attr_accessor :last_description
1593
alias :last_comment :last_description # Backwards compatibility
1436
1597
@tasks = Hash.new
1437
1598
@rules = Array.new
1438
1599
@scope = Array.new
1600
@last_description = nil
1442
def create_rule(args, &block)
1443
pattern, deps = resolve_args(args)
1603
def create_rule(*args, &block)
1604
pattern, arg_names, deps = resolve_args(args)
1444
1605
pattern = Regexp.new(Regexp.quote(pattern) + '$') if String === pattern
1445
1606
@rules << [pattern, deps, block]
1448
def define_task(task_class, args, &block)
1449
task_name, deps = resolve_args(args)
1609
def define_task(task_class, *args, &block)
1610
task_name, arg_names, deps = resolve_args(args)
1450
1611
task_name = task_class.scope_name(@scope, task_name)
1451
1612
deps = [deps] unless deps.respond_to?(:to_ary)
1452
1613
deps = deps.collect {|d| d.to_s }
1453
1614
task = intern(task_class, task_name)
1454
task.add_comment(@last_comment)
1615
task.set_arg_names(arg_names) unless arg_names.empty?
1616
task.add_description(@last_description)
1617
@last_description = nil
1456
1618
task.enhance(deps, &block)
1476
1638
return nil unless File.exist?(task_name)
1477
1639
define_task(Rake::FileTask, task_name)
1480
# Resolve the arguments for a task/rule.
1642
# Resolve the arguments for a task/rule. Returns a triplet of
1643
# [task_name, arg_name_list, prerequisites].
1481
1644
def resolve_args(args)
1484
fail "Too Many Task Names: #{args.keys.join(' ')}" if args.size > 1
1485
fail "No Task Name Given" if args.size < 1
1486
task_name = args.keys[0]
1487
deps = args[task_name]
1488
deps = [deps] if (String===deps) || (Regexp===deps) || (Proc===deps)
1645
task_name = args.shift
1646
arg_names = args #.map { |a| a.to_sym }
1648
if task_name.is_a?(Hash)
1650
task_name = hash.keys[0]
1651
needs = hash[task_name]
1653
if arg_names.last.is_a?(Hash)
1654
hash = arg_names.pop
1655
needs = hash[:needs]
1656
fail "Unrecognized keys in task hash: #{hash.keys.inspect}" if hash.size > 1
1658
needs = [needs] unless needs.respond_to?(:to_ary)
1659
[task_name, arg_names, needs]
1496
1662
# If a rule can be found that matches the task name, enhance the
1497
1663
# task with the prerequisites and actions from the rule. Set the
1498
1664
# source attribute of the task appropriately for the rule. Return
1636
1806
# The original directory where rake was invoked.
1637
1807
attr_reader :original_dir
1639
1809
# Name of the actual rakefile used.
1640
1810
attr_reader :rakefile
1642
1812
# List of the top level task names (task names from the command line).
1643
1813
attr_reader :top_level_tasks
1645
1815
DEFAULT_RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'].freeze
1647
1817
OPTIONS = [ # :nodoc:
1648
['--dry-run', '-n', GetoptLong::NO_ARGUMENT,
1649
"Do a dry run without executing actions."],
1650
['--help', '-H', GetoptLong::NO_ARGUMENT,
1818
['--classic-namespace', '-C', GetoptLong::NO_ARGUMENT,
1819
"Put Task and FileTask in the top level namespace"],
1820
['--describe', '-D', GetoptLong::OPTIONAL_ARGUMENT,
1821
"Describe the tasks (matching optional PATTERN), then exit."],
1822
['--rakefile', '-f', GetoptLong::OPTIONAL_ARGUMENT,
1823
"Use FILE as the rakefile."],
1824
['--help', '-h', '-H', GetoptLong::NO_ARGUMENT,
1651
1825
"Display this help message."],
1652
1826
['--libdir', '-I', GetoptLong::REQUIRED_ARGUMENT,
1653
1827
"Include LIBDIR in the search path for required modules."],
1654
['--rakelibdir', '-R', GetoptLong::REQUIRED_ARGUMENT,
1655
"Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')"],
1828
['--dry-run', '-n', GetoptLong::NO_ARGUMENT,
1829
"Do a dry run without executing actions."],
1656
1830
['--nosearch', '-N', GetoptLong::NO_ARGUMENT,
1657
1831
"Do not search parent directories for the Rakefile."],
1658
1832
['--prereqs', '-P', GetoptLong::NO_ARGUMENT,
1659
1833
"Display the tasks and dependencies, then exit."],
1660
1834
['--quiet', '-q', GetoptLong::NO_ARGUMENT,
1661
1835
"Do not log messages to standard output."],
1662
['--rakefile', '-f', GetoptLong::OPTIONAL_ARGUMENT,
1663
"Use FILE as the rakefile."],
1664
1836
['--require', '-r', GetoptLong::REQUIRED_ARGUMENT,
1665
1837
"Require MODULE before executing rakefile."],
1838
['--rakelibdir', '-R', GetoptLong::REQUIRED_ARGUMENT,
1839
"Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')"],
1666
1840
['--silent', '-s', GetoptLong::NO_ARGUMENT,
1667
1841
"Like --quiet, but also suppresses the 'in directory' announcement."],
1668
1842
['--tasks', '-T', GetoptLong::OPTIONAL_ARGUMENT,
1669
1843
"Display the tasks (matching optional PATTERN) with descriptions, then exit."],
1670
1844
['--trace', '-t', GetoptLong::NO_ARGUMENT,
1671
1845
"Turn on invoke/execute tracing, enable full backtrace."],
1672
['--usage', '-h', GetoptLong::NO_ARGUMENT,
1674
1846
['--verbose', '-v', GetoptLong::NO_ARGUMENT,
1675
1847
"Log message to standard output (default)."],
1676
1848
['--version', '-V', GetoptLong::NO_ARGUMENT,
1677
1849
"Display the program version."],
1678
['--classic-namespace', '-C', GetoptLong::NO_ARGUMENT,
1679
"Put Task and FileTask in the top level namespace"],
1682
1852
# Initialize a Rake::Application object.
1809
1994
printf " %s\n", desc
1813
1998
# Display the tasks and dependencies.
1814
1999
def display_tasks_and_comments
1815
2000
displayable_tasks = tasks.select { |t|
1816
2001
t.comment && t.name =~ options.show_task_pattern
1818
width = displayable_tasks.collect { |t| t.name.length }.max
1819
displayable_tasks.each do |t|
1820
printf "#{name} %-#{width}s # %s\n", t.name, t.comment
2003
if options.full_description
2004
displayable_tasks.each do |t|
2005
puts "rake #{t.name_with_args}"
2006
t.full_comment.split("\n").each do |line|
2012
width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10
2013
max_column = 80 - name.size - width - 7
2014
displayable_tasks.each do |t|
2015
printf "#{name} %-#{width}s # %s\n",
2016
t.name_with_args, truncate(t.comment, max_column)
2021
def truncate(string, width)
2022
if string.length <= width
2025
string[0, width-3] + "..."
1824
2029
# Display the tasks and prerequisites
1825
2030
def display_prerequisites
1826
2031
tasks.each do |t|