This commit is contained in:
@@ -0,0 +1,861 @@
|
||||
# frozen_string_literal: true
|
||||
require "optparse"
|
||||
|
||||
require "rake/task_manager"
|
||||
require "rake/file_list"
|
||||
require "rake/thread_pool"
|
||||
require "rake/thread_history_display"
|
||||
require "rake/trace_output"
|
||||
require "rake/win32"
|
||||
|
||||
module Rake
|
||||
|
||||
CommandLineOptionError = Class.new(StandardError)
|
||||
|
||||
##
|
||||
# Rake main application object. When invoking +rake+ from the
|
||||
# command line, a Rake::Application object is created and run.
|
||||
|
||||
class Application
|
||||
include TaskManager
|
||||
include TraceOutput
|
||||
|
||||
# The name of the application (typically 'rake')
|
||||
attr_reader :name
|
||||
|
||||
# The original directory where rake was invoked.
|
||||
attr_reader :original_dir
|
||||
|
||||
# Name of the actual rakefile used.
|
||||
attr_reader :rakefile
|
||||
|
||||
# Number of columns on the terminal
|
||||
attr_accessor :terminal_columns
|
||||
|
||||
# List of the top level task names (task names from the command line).
|
||||
attr_reader :top_level_tasks
|
||||
|
||||
# Override the detected TTY output state (mostly for testing)
|
||||
attr_writer :tty_output
|
||||
|
||||
DEFAULT_RAKEFILES = [
|
||||
"rakefile",
|
||||
"Rakefile",
|
||||
"rakefile.rb",
|
||||
"Rakefile.rb"
|
||||
].freeze
|
||||
|
||||
# Initialize a Rake::Application object.
|
||||
def initialize
|
||||
super
|
||||
@name = "rake"
|
||||
@rakefiles = DEFAULT_RAKEFILES.dup
|
||||
@rakefile = nil
|
||||
@pending_imports = []
|
||||
@imported = []
|
||||
@loaders = {}
|
||||
@default_loader = Rake::DefaultLoader.new
|
||||
@original_dir = Dir.pwd
|
||||
@top_level_tasks = []
|
||||
add_loader("rb", DefaultLoader.new)
|
||||
add_loader("rf", DefaultLoader.new)
|
||||
add_loader("rake", DefaultLoader.new)
|
||||
@tty_output = STDOUT.tty?
|
||||
@terminal_columns = ENV["RAKE_COLUMNS"].to_i
|
||||
|
||||
set_default_options
|
||||
end
|
||||
|
||||
# Run the Rake application. The run method performs the following
|
||||
# three steps:
|
||||
#
|
||||
# * Initialize the command line options (+init+).
|
||||
# * Define the tasks (+load_rakefile+).
|
||||
# * Run the top level tasks (+top_level+).
|
||||
#
|
||||
# If you wish to build a custom rake command, you should call
|
||||
# +init+ on your application. Then define any tasks. Finally,
|
||||
# call +top_level+ to run your top level tasks.
|
||||
def run(argv = ARGV)
|
||||
standard_exception_handling do
|
||||
init "rake", argv
|
||||
load_rakefile
|
||||
top_level
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize the command line parameters and app name.
|
||||
def init(app_name="rake", argv = ARGV)
|
||||
standard_exception_handling do
|
||||
@name = app_name
|
||||
begin
|
||||
args = handle_options argv
|
||||
rescue ArgumentError
|
||||
# Backward compatibility for capistrano
|
||||
args = handle_options
|
||||
end
|
||||
load_debug_at_stop_feature
|
||||
collect_command_line_tasks(args)
|
||||
end
|
||||
end
|
||||
|
||||
def load_debug_at_stop_feature
|
||||
return unless ENV["RAKE_DEBUG"]
|
||||
require "debug/session"
|
||||
DEBUGGER__::start no_sigint_hook: true, nonstop: true
|
||||
Rake::Task.prepend Module.new {
|
||||
def execute(*)
|
||||
exception = DEBUGGER__::SESSION.capture_exception_frames(/(exe|bin|lib)\/rake/) do
|
||||
super
|
||||
end
|
||||
|
||||
if exception
|
||||
STDERR.puts exception.message
|
||||
DEBUGGER__::SESSION.enter_postmortem_session exception
|
||||
raise exception
|
||||
end
|
||||
end
|
||||
}
|
||||
rescue LoadError
|
||||
end
|
||||
private :load_debug_at_stop_feature
|
||||
|
||||
# Find the rakefile and then load it and any pending imports.
|
||||
def load_rakefile
|
||||
standard_exception_handling do
|
||||
raw_load_rakefile
|
||||
end
|
||||
end
|
||||
|
||||
# Run the top level tasks of a Rake application.
|
||||
def top_level
|
||||
run_with_threads do
|
||||
if options.show_tasks
|
||||
display_tasks_and_comments
|
||||
elsif options.show_prereqs
|
||||
display_prerequisites
|
||||
else
|
||||
top_level_tasks.each { |task_name| invoke_task(task_name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Run the given block with the thread startup and shutdown.
|
||||
def run_with_threads
|
||||
thread_pool.gather_history if options.job_stats == :history
|
||||
|
||||
yield
|
||||
|
||||
thread_pool.join if defined?(@thread_pool)
|
||||
if options.job_stats
|
||||
stats = thread_pool.statistics
|
||||
puts "Maximum active threads: #{stats[:max_active_threads]} + main"
|
||||
puts "Total threads in play: #{stats[:total_threads_in_play]} + main"
|
||||
end
|
||||
ThreadHistoryDisplay.new(thread_pool.history).show if
|
||||
options.job_stats == :history
|
||||
end
|
||||
|
||||
# Add a loader to handle imported files ending in the extension
|
||||
# +ext+.
|
||||
def add_loader(ext, loader)
|
||||
ext = ".#{ext}" unless ext =~ /^\./
|
||||
@loaders[ext] = loader
|
||||
end
|
||||
|
||||
# Application options from the command line
|
||||
def options
|
||||
@options ||= Struct.new(
|
||||
:always_multitask, :backtrace, :build_all, :dryrun,
|
||||
:ignore_deprecate, :ignore_system, :job_stats, :load_system,
|
||||
:nosearch, :rakelib, :show_all_tasks, :show_prereqs,
|
||||
:show_task_pattern, :show_tasks, :silent, :suppress_backtrace_pattern,
|
||||
:thread_pool_size, :trace, :trace_output, :trace_rules
|
||||
).new
|
||||
end
|
||||
|
||||
# Return the thread pool used for multithreaded processing.
|
||||
def thread_pool # :nodoc:
|
||||
@thread_pool ||= ThreadPool.new(options.thread_pool_size || Rake.suggested_thread_count-1)
|
||||
end
|
||||
|
||||
# internal ----------------------------------------------------------------
|
||||
|
||||
# Invokes a task with arguments that are extracted from +task_string+
|
||||
def invoke_task(task_string) # :nodoc:
|
||||
name, args = parse_task_string(task_string)
|
||||
t = self[name]
|
||||
t.invoke(*args)
|
||||
end
|
||||
|
||||
def parse_task_string(string) # :nodoc:
|
||||
/^([^\[]+)(?:\[(.*)\])$/ =~ string.to_s
|
||||
|
||||
name = $1
|
||||
remaining_args = $2
|
||||
|
||||
return string, [] unless name
|
||||
return name, [] if remaining_args.empty?
|
||||
|
||||
args = []
|
||||
|
||||
begin
|
||||
/\s*((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args
|
||||
|
||||
remaining_args = $2
|
||||
args << $1.gsub(/\\(.)/, '\1')
|
||||
end while remaining_args
|
||||
|
||||
return name, args
|
||||
end
|
||||
|
||||
# Provide standard exception handling for the given block.
|
||||
def standard_exception_handling # :nodoc:
|
||||
yield
|
||||
rescue SystemExit
|
||||
# Exit silently with current status
|
||||
raise
|
||||
rescue OptionParser::InvalidOption => ex
|
||||
$stderr.puts ex.message
|
||||
exit(false)
|
||||
rescue Exception => ex
|
||||
# Exit with error message
|
||||
display_error_message(ex)
|
||||
exit_because_of_exception(ex)
|
||||
end
|
||||
|
||||
# Exit the program because of an unhandled exception.
|
||||
# (may be overridden by subclasses)
|
||||
def exit_because_of_exception(ex) # :nodoc:
|
||||
exit(false)
|
||||
end
|
||||
|
||||
# Display the error message that caused the exception.
|
||||
def display_error_message(ex) # :nodoc:
|
||||
trace "#{name} aborted!"
|
||||
display_exception_details(ex)
|
||||
trace "Tasks: #{ex.chain}" if has_chain?(ex)
|
||||
trace "(See full trace by running task with --trace)" unless
|
||||
options.backtrace
|
||||
end
|
||||
|
||||
def display_exception_details(ex) # :nodoc:
|
||||
display_exception_details_seen << ex
|
||||
|
||||
display_exception_message_details(ex)
|
||||
display_exception_backtrace(ex) if ex.backtrace
|
||||
display_cause_details(ex.cause) if has_cause?(ex)
|
||||
end
|
||||
|
||||
def display_cause_details(ex) # :nodoc:
|
||||
return if display_exception_details_seen.include? ex
|
||||
|
||||
trace "\nCaused by:"
|
||||
display_exception_details(ex)
|
||||
end
|
||||
|
||||
def display_exception_details_seen # :nodoc:
|
||||
Thread.current[:rake_display_exception_details_seen] ||= []
|
||||
end
|
||||
|
||||
def has_cause?(ex) # :nodoc:
|
||||
ex.respond_to?(:cause) && ex.cause
|
||||
end
|
||||
|
||||
def display_exception_message_details(ex) # :nodoc:
|
||||
if ex.instance_of?(RuntimeError)
|
||||
trace ex.message
|
||||
elsif ex.respond_to?(:detailed_message)
|
||||
trace "#{ex.class.name}: #{ex.detailed_message(highlight: false)}"
|
||||
else
|
||||
trace "#{ex.class.name}: #{ex.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def display_exception_backtrace(ex) # :nodoc:
|
||||
if options.backtrace
|
||||
trace ex.backtrace.join("\n")
|
||||
else
|
||||
trace Backtrace.collapse(ex.backtrace).join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
# Warn about deprecated usage.
|
||||
#
|
||||
# Example:
|
||||
# Rake.application.deprecate("import", "Rake.import", caller.first)
|
||||
#
|
||||
def deprecate(old_usage, new_usage, call_site) # :nodoc:
|
||||
unless options.ignore_deprecate
|
||||
$stderr.puts "WARNING: '#{old_usage}' is deprecated. " +
|
||||
"Please use '#{new_usage}' instead.\n" +
|
||||
" at #{call_site}"
|
||||
end
|
||||
end
|
||||
|
||||
# Does the exception have a task invocation chain?
|
||||
def has_chain?(exception) # :nodoc:
|
||||
exception.respond_to?(:chain) && exception.chain
|
||||
end
|
||||
private :has_chain?
|
||||
|
||||
# True if one of the files in RAKEFILES is in the current directory.
|
||||
# If a match is found, it is copied into @rakefile.
|
||||
def have_rakefile # :nodoc:
|
||||
@rakefiles.each do |fn|
|
||||
if File.exist?(fn)
|
||||
others = FileList.glob(fn, File::FNM_CASEFOLD)
|
||||
return others.size == 1 ? others.first : fn
|
||||
elsif fn == ""
|
||||
return fn
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# True if we are outputting to TTY, false otherwise
|
||||
def tty_output? # :nodoc:
|
||||
@tty_output
|
||||
end
|
||||
|
||||
# We will truncate output if we are outputting to a TTY or if we've been
|
||||
# given an explicit column width to honor
|
||||
def truncate_output? # :nodoc:
|
||||
tty_output? || @terminal_columns.nonzero?
|
||||
end
|
||||
|
||||
# Display the tasks and comments.
|
||||
def display_tasks_and_comments # :nodoc:
|
||||
displayable_tasks = tasks.select { |t|
|
||||
(options.show_all_tasks || t.comment) &&
|
||||
t.name =~ options.show_task_pattern
|
||||
}
|
||||
case options.show_tasks
|
||||
when :tasks
|
||||
width = displayable_tasks.map { |t| t.name_with_args.length }.max || 10
|
||||
if truncate_output?
|
||||
max_column = terminal_width - name.size - width - 7
|
||||
else
|
||||
max_column = nil
|
||||
end
|
||||
|
||||
displayable_tasks.each do |t|
|
||||
printf("#{name} %-#{width}s # %s\n",
|
||||
t.name_with_args,
|
||||
max_column ? truncate(t.comment, max_column) : t.comment)
|
||||
end
|
||||
when :describe
|
||||
displayable_tasks.each do |t|
|
||||
puts "#{name} #{t.name_with_args}"
|
||||
comment = t.full_comment || ""
|
||||
comment.split("\n").each do |line|
|
||||
puts " #{line}"
|
||||
end
|
||||
puts
|
||||
end
|
||||
when :lines
|
||||
displayable_tasks.each do |t|
|
||||
t.locations.each do |loc|
|
||||
printf "#{name} %-30s %s\n", t.name_with_args, loc
|
||||
end
|
||||
end
|
||||
else
|
||||
fail "Unknown show task mode: '#{options.show_tasks}'"
|
||||
end
|
||||
end
|
||||
|
||||
def terminal_width # :nodoc:
|
||||
if @terminal_columns.nonzero?
|
||||
result = @terminal_columns
|
||||
else
|
||||
result = unix? ? dynamic_width : 80
|
||||
end
|
||||
(result < 10) ? 80 : result
|
||||
rescue
|
||||
80
|
||||
end
|
||||
|
||||
# Calculate the dynamic width of the
|
||||
def dynamic_width # :nodoc:
|
||||
@dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
|
||||
end
|
||||
|
||||
def dynamic_width_stty # :nodoc:
|
||||
%x{stty size 2>/dev/null}.split[1].to_i
|
||||
end
|
||||
|
||||
def dynamic_width_tput # :nodoc:
|
||||
%x{tput cols 2>/dev/null}.to_i
|
||||
end
|
||||
|
||||
def unix? # :nodoc:
|
||||
RbConfig::CONFIG["host_os"] =~
|
||||
/(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
|
||||
end
|
||||
|
||||
def windows? # :nodoc:
|
||||
Win32.windows?
|
||||
end
|
||||
|
||||
def truncate(string, width) # :nodoc:
|
||||
if string.nil?
|
||||
""
|
||||
elsif string.length <= width
|
||||
string
|
||||
else
|
||||
(string[0, width - 3] || "") + "..."
|
||||
end
|
||||
end
|
||||
|
||||
# Display the tasks and prerequisites
|
||||
def display_prerequisites # :nodoc:
|
||||
tasks.each do |t|
|
||||
puts "#{name} #{t.name}"
|
||||
t.prerequisites.each { |pre| puts " #{pre}" }
|
||||
end
|
||||
end
|
||||
|
||||
def trace(*strings) # :nodoc:
|
||||
options.trace_output ||= $stderr
|
||||
trace_on(options.trace_output, *strings)
|
||||
end
|
||||
|
||||
def sort_options(options) # :nodoc:
|
||||
options.sort_by { |opt|
|
||||
opt.select { |o| o.is_a?(String) && o =~ /^-/ }.map(&:downcase).sort.reverse
|
||||
}
|
||||
end
|
||||
private :sort_options
|
||||
|
||||
# A list of all the standard options used in rake, suitable for
|
||||
# passing to OptionParser.
|
||||
def standard_rake_options # :nodoc:
|
||||
sort_options(
|
||||
[
|
||||
["--all", "-A",
|
||||
"Show all tasks, even uncommented ones (in combination with -T or -D)",
|
||||
lambda { |value|
|
||||
options.show_all_tasks = value
|
||||
}
|
||||
],
|
||||
["--backtrace=[OUT]",
|
||||
"Enable full backtrace. OUT can be stderr (default) or stdout.",
|
||||
lambda { |value|
|
||||
options.backtrace = true
|
||||
select_trace_output(options, "backtrace", value)
|
||||
}
|
||||
],
|
||||
["--build-all", "-B",
|
||||
"Build all prerequisites, including those which are up-to-date.",
|
||||
lambda { |value|
|
||||
options.build_all = true
|
||||
}
|
||||
],
|
||||
["--comments",
|
||||
"Show commented tasks only",
|
||||
lambda { |value|
|
||||
options.show_all_tasks = !value
|
||||
}
|
||||
],
|
||||
["--describe", "-D [PATTERN]",
|
||||
"Describe the tasks (matching optional PATTERN), then exit.",
|
||||
lambda { |value|
|
||||
select_tasks_to_show(options, :describe, value)
|
||||
}
|
||||
],
|
||||
["--directory", "-C [DIRECTORY]",
|
||||
"Change to DIRECTORY before doing anything.",
|
||||
lambda { |value|
|
||||
Dir.chdir value
|
||||
@original_dir = Dir.pwd
|
||||
}
|
||||
],
|
||||
["--dry-run", "-n",
|
||||
"Do a dry run without executing actions.",
|
||||
lambda { |value|
|
||||
Rake.verbose(true)
|
||||
Rake.nowrite(true)
|
||||
options.dryrun = true
|
||||
options.trace = true
|
||||
}
|
||||
],
|
||||
["--execute", "-e CODE",
|
||||
"Execute some Ruby code and exit.",
|
||||
lambda { |value|
|
||||
eval(value)
|
||||
exit
|
||||
}
|
||||
],
|
||||
["--execute-print", "-p CODE",
|
||||
"Execute some Ruby code, print the result, then exit.",
|
||||
lambda { |value|
|
||||
puts eval(value)
|
||||
exit
|
||||
}
|
||||
],
|
||||
["--execute-continue", "-E CODE",
|
||||
"Execute some Ruby code, " +
|
||||
"then continue with normal task processing.",
|
||||
lambda { |value| eval(value) }
|
||||
],
|
||||
["--jobs", "-j [NUMBER]",
|
||||
"Specifies the maximum number of tasks to execute in parallel. " +
|
||||
"(default is number of CPU cores + 4)",
|
||||
lambda { |value|
|
||||
if value.nil? || value == ""
|
||||
value = Float::INFINITY
|
||||
elsif value =~ /^\d+$/
|
||||
value = value.to_i
|
||||
else
|
||||
value = Rake.suggested_thread_count
|
||||
end
|
||||
value = 1 if value < 1
|
||||
options.thread_pool_size = value - 1
|
||||
}
|
||||
],
|
||||
["--job-stats [LEVEL]",
|
||||
"Display job statistics. " +
|
||||
"LEVEL=history displays a complete job list",
|
||||
lambda { |value|
|
||||
if value =~ /^history/i
|
||||
options.job_stats = :history
|
||||
else
|
||||
options.job_stats = true
|
||||
end
|
||||
}
|
||||
],
|
||||
["--libdir", "-I LIBDIR",
|
||||
"Include LIBDIR in the search path for required modules.",
|
||||
lambda { |value| $:.push(value) }
|
||||
],
|
||||
["--multitask", "-m",
|
||||
"Treat all tasks as multitasks.",
|
||||
lambda { |value| options.always_multitask = true }
|
||||
],
|
||||
["--no-search", "--nosearch",
|
||||
"-N", "Do not search parent directories for the Rakefile.",
|
||||
lambda { |value| options.nosearch = true }
|
||||
],
|
||||
["--prereqs", "-P",
|
||||
"Display the tasks and dependencies, then exit.",
|
||||
lambda { |value| options.show_prereqs = true }
|
||||
],
|
||||
["--quiet", "-q",
|
||||
"Do not log messages to standard output.",
|
||||
lambda { |value| Rake.verbose(false) }
|
||||
],
|
||||
["--rakefile", "-f [FILENAME]",
|
||||
"Use FILENAME as the rakefile to search for.",
|
||||
lambda { |value|
|
||||
value ||= ""
|
||||
@rakefiles.clear
|
||||
@rakefiles << value
|
||||
}
|
||||
],
|
||||
["--rakelibdir", "--rakelib", "-R RAKELIBDIR",
|
||||
"Auto-import any .rake files in RAKELIBDIR. " +
|
||||
"(default is 'rakelib')",
|
||||
lambda { |value|
|
||||
options.rakelib = value.split(File::PATH_SEPARATOR)
|
||||
}
|
||||
],
|
||||
["--require", "-r MODULE",
|
||||
"Require MODULE before executing rakefile.",
|
||||
lambda { |value|
|
||||
begin
|
||||
require value
|
||||
rescue LoadError => ex
|
||||
begin
|
||||
rake_require value
|
||||
rescue LoadError
|
||||
raise ex
|
||||
end
|
||||
end
|
||||
}
|
||||
],
|
||||
["--rules",
|
||||
"Trace the rules resolution.",
|
||||
lambda { |value| options.trace_rules = true }
|
||||
],
|
||||
["--silent", "-s",
|
||||
"Like --quiet, but also suppresses the " +
|
||||
"'in directory' announcement.",
|
||||
lambda { |value|
|
||||
Rake.verbose(false)
|
||||
options.silent = true
|
||||
}
|
||||
],
|
||||
["--suppress-backtrace PATTERN",
|
||||
"Suppress backtrace lines matching regexp PATTERN. " +
|
||||
"Ignored if --trace is on.",
|
||||
lambda { |value|
|
||||
options.suppress_backtrace_pattern = Regexp.new(value)
|
||||
}
|
||||
],
|
||||
["--system", "-g",
|
||||
"Using system wide (global) rakefiles " +
|
||||
"(usually '~/.rake/*.rake').",
|
||||
lambda { |value| options.load_system = true }
|
||||
],
|
||||
["--no-system", "--nosystem", "-G",
|
||||
"Use standard project Rakefile search paths, " +
|
||||
"ignore system wide rakefiles.",
|
||||
lambda { |value| options.ignore_system = true }
|
||||
],
|
||||
["--tasks", "-T [PATTERN]",
|
||||
"Display the tasks (matching optional PATTERN) " +
|
||||
"with descriptions, then exit. " +
|
||||
"-AT combination displays all the tasks, including those without descriptions.",
|
||||
lambda { |value|
|
||||
select_tasks_to_show(options, :tasks, value)
|
||||
}
|
||||
],
|
||||
["--trace=[OUT]", "-t",
|
||||
"Turn on invoke/execute tracing, enable full backtrace. " +
|
||||
"OUT can be stderr (default) or stdout.",
|
||||
lambda { |value|
|
||||
options.trace = true
|
||||
options.backtrace = true
|
||||
select_trace_output(options, "trace", value)
|
||||
Rake.verbose(true)
|
||||
}
|
||||
],
|
||||
["--verbose", "-v",
|
||||
"Log message to standard output.",
|
||||
lambda { |value| Rake.verbose(true) }
|
||||
],
|
||||
["--version", "-V",
|
||||
"Display the program version.",
|
||||
lambda { |value|
|
||||
puts "rake, version #{Rake::VERSION}"
|
||||
exit
|
||||
}
|
||||
],
|
||||
["--where", "-W [PATTERN]",
|
||||
"Describe the tasks (matching optional PATTERN), then exit.",
|
||||
lambda { |value|
|
||||
select_tasks_to_show(options, :lines, value)
|
||||
options.show_all_tasks = true
|
||||
}
|
||||
],
|
||||
["--no-deprecation-warnings", "-X",
|
||||
"Disable the deprecation warnings.",
|
||||
lambda { |value|
|
||||
options.ignore_deprecate = true
|
||||
}
|
||||
],
|
||||
])
|
||||
end
|
||||
|
||||
def select_tasks_to_show(options, show_tasks, value) # :nodoc:
|
||||
options.show_tasks = show_tasks
|
||||
options.show_task_pattern = Regexp.new(value || "")
|
||||
Rake::TaskManager.record_task_metadata = true
|
||||
end
|
||||
private :select_tasks_to_show
|
||||
|
||||
def select_trace_output(options, trace_option, value) # :nodoc:
|
||||
value = value.strip unless value.nil?
|
||||
case value
|
||||
when "stdout"
|
||||
options.trace_output = $stdout
|
||||
when "stderr", nil
|
||||
options.trace_output = $stderr
|
||||
else
|
||||
fail CommandLineOptionError,
|
||||
"Unrecognized --#{trace_option} option '#{value}'"
|
||||
end
|
||||
end
|
||||
private :select_trace_output
|
||||
|
||||
# Read and handle the command line options. Returns the command line
|
||||
# arguments that we didn't understand, which should (in theory) be just
|
||||
# task names and env vars.
|
||||
def handle_options(argv) # :nodoc:
|
||||
set_default_options
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "#{Rake.application.name} [-f rakefile] {options} targets..."
|
||||
opts.separator ""
|
||||
opts.separator "Options are ..."
|
||||
|
||||
opts.on_tail("-h", "--help", "-H", "Display this help message.") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
|
||||
standard_rake_options.each { |args| opts.on(*args) }
|
||||
opts.environment("RAKEOPT")
|
||||
end.parse(argv)
|
||||
end
|
||||
|
||||
# Similar to the regular Ruby +require+ command, but will check
|
||||
# for *.rake files in addition to *.rb files.
|
||||
def rake_require(file_name, paths=$LOAD_PATH, loaded=$") # :nodoc:
|
||||
fn = file_name + ".rake"
|
||||
return false if loaded.include?(fn)
|
||||
paths.each do |path|
|
||||
full_path = File.join(path, fn)
|
||||
if File.exist?(full_path)
|
||||
Rake.load_rakefile(full_path)
|
||||
loaded << fn
|
||||
return true
|
||||
end
|
||||
end
|
||||
fail LoadError, "Can't find #{file_name}"
|
||||
end
|
||||
|
||||
def find_rakefile_location # :nodoc:
|
||||
here = Dir.pwd
|
||||
until (fn = have_rakefile)
|
||||
Dir.chdir("..")
|
||||
return nil if Dir.pwd == here || options.nosearch
|
||||
here = Dir.pwd
|
||||
end
|
||||
[fn, here]
|
||||
ensure
|
||||
Dir.chdir(Rake.original_dir)
|
||||
end
|
||||
|
||||
def print_rakefile_directory(location) # :nodoc:
|
||||
$stderr.puts "(in #{Dir.pwd})" unless
|
||||
options.silent or original_dir == location
|
||||
end
|
||||
|
||||
def raw_load_rakefile # :nodoc:
|
||||
rakefile, location = find_rakefile_location
|
||||
if (!options.ignore_system) &&
|
||||
(options.load_system || rakefile.nil?) &&
|
||||
system_dir && File.directory?(system_dir)
|
||||
print_rakefile_directory(location)
|
||||
glob("#{system_dir}/*.rake") do |name|
|
||||
add_import name
|
||||
end
|
||||
else
|
||||
fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if
|
||||
rakefile.nil?
|
||||
@rakefile = rakefile
|
||||
Dir.chdir(location)
|
||||
print_rakefile_directory(location)
|
||||
Rake.load_rakefile(File.expand_path(@rakefile)) if
|
||||
@rakefile && @rakefile != ""
|
||||
options.rakelib.each do |rlib|
|
||||
glob("#{rlib}/*.rake") do |name|
|
||||
add_import name
|
||||
end
|
||||
end
|
||||
end
|
||||
load_imports
|
||||
end
|
||||
|
||||
def glob(path, &block) # :nodoc:
|
||||
FileList.glob(path.tr("\\", "/")).each(&block)
|
||||
end
|
||||
private :glob
|
||||
|
||||
# The directory path containing the system wide rakefiles.
|
||||
def system_dir # :nodoc:
|
||||
@system_dir ||=
|
||||
begin
|
||||
if ENV["RAKE_SYSTEM"]
|
||||
ENV["RAKE_SYSTEM"]
|
||||
else
|
||||
standard_system_dir
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The standard directory containing system wide rake files.
|
||||
if Win32.windows?
|
||||
def standard_system_dir #:nodoc:
|
||||
Win32.win32_system_dir
|
||||
end
|
||||
else
|
||||
def standard_system_dir #:nodoc:
|
||||
File.join(File.expand_path("~"), ".rake")
|
||||
end
|
||||
end
|
||||
private :standard_system_dir
|
||||
|
||||
# Collect the list of tasks on the command line. If no tasks are
|
||||
# given, return a list containing only the default task.
|
||||
# Environmental assignments are processed at this time as well.
|
||||
#
|
||||
# `args` is the list of arguments to peruse to get the list of tasks.
|
||||
# It should be the command line that was given to rake, less any
|
||||
# recognised command-line options, which OptionParser.parse will
|
||||
# have taken care of already.
|
||||
def collect_command_line_tasks(args) # :nodoc:
|
||||
@top_level_tasks = []
|
||||
args.each do |arg|
|
||||
if arg =~ /^(\w+)=(.*)$/m
|
||||
ENV[$1] = $2
|
||||
else
|
||||
@top_level_tasks << arg unless arg =~ /^-/
|
||||
end
|
||||
end
|
||||
@top_level_tasks.push(default_task_name) if @top_level_tasks.empty?
|
||||
end
|
||||
|
||||
# Default task name ("default").
|
||||
# (May be overridden by subclasses)
|
||||
def default_task_name # :nodoc:
|
||||
"default"
|
||||
end
|
||||
|
||||
# Add a file to the list of files to be imported.
|
||||
def add_import(fn) # :nodoc:
|
||||
@pending_imports << fn
|
||||
end
|
||||
|
||||
# Load the pending list of imported files.
|
||||
def load_imports # :nodoc:
|
||||
while fn = @pending_imports.shift
|
||||
next if @imported.member?(fn)
|
||||
fn_task = lookup(fn) and fn_task.invoke
|
||||
ext = File.extname(fn)
|
||||
loader = @loaders[ext] || @default_loader
|
||||
loader.load(fn)
|
||||
if fn_task = lookup(fn) and fn_task.needed?
|
||||
fn_task.reenable
|
||||
fn_task.invoke
|
||||
loader.load(fn)
|
||||
end
|
||||
@imported << fn
|
||||
end
|
||||
end
|
||||
|
||||
def rakefile_location(backtrace=caller) # :nodoc:
|
||||
backtrace.map { |t| t[/([^:]+):/, 1] }
|
||||
|
||||
re = /^#{@rakefile}$/
|
||||
re = /#{re.source}/i if windows?
|
||||
|
||||
backtrace.find { |str| str =~ re } || ""
|
||||
end
|
||||
|
||||
def set_default_options # :nodoc:
|
||||
options.always_multitask = false
|
||||
options.backtrace = false
|
||||
options.build_all = false
|
||||
options.dryrun = false
|
||||
options.ignore_deprecate = false
|
||||
options.ignore_system = false
|
||||
options.job_stats = false
|
||||
options.load_system = false
|
||||
options.nosearch = false
|
||||
options.rakelib = %w[rakelib]
|
||||
options.show_all_tasks = false
|
||||
options.show_prereqs = false
|
||||
options.show_task_pattern = nil
|
||||
options.show_tasks = nil
|
||||
options.silent = false
|
||||
options.suppress_backtrace_pattern = nil
|
||||
options.thread_pool_size = Rake.suggested_thread_count
|
||||
options.trace = false
|
||||
options.trace_output = $stderr
|
||||
options.trace_rules = false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
module Backtrace # :nodoc: all
|
||||
SYS_KEYS = RbConfig::CONFIG.keys.grep(/(?:[a-z]prefix|libdir)\z/)
|
||||
SYS_PATHS = RbConfig::CONFIG.values_at(*SYS_KEYS).uniq +
|
||||
[ File.join(File.dirname(__FILE__), "..") ]
|
||||
|
||||
SUPPRESSED_PATHS = SYS_PATHS.
|
||||
map { |s| s.tr("\\", "/") }.
|
||||
map { |f| File.expand_path(f) }.
|
||||
reject { |s| s.nil? || s =~ /^ *$/ }
|
||||
SUPPRESSED_PATHS_RE = SUPPRESSED_PATHS.map { |f| Regexp.quote(f) }.join("|")
|
||||
SUPPRESSED_PATHS_RE << "|^<internal:\\w+>"
|
||||
SUPPRESSED_PATHS_RE << "|^org\\/jruby\\/\\w+\\.java" if
|
||||
Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == "jruby"
|
||||
|
||||
SUPPRESS_PATTERN = %r!(\A(#{SUPPRESSED_PATHS_RE})|bin/rake:\d+)!i
|
||||
|
||||
def self.collapse(backtrace)
|
||||
pattern = Rake.application.options.suppress_backtrace_pattern ||
|
||||
SUPPRESS_PATTERN
|
||||
backtrace.reject { |elem| elem =~ pattern }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
# frozen_string_literal: true
|
||||
# The 'rake/clean' file defines two file lists (CLEAN and CLOBBER) and
|
||||
# two rake tasks (:clean and :clobber).
|
||||
#
|
||||
# [:clean] Clean up the project by deleting scratch files and backup
|
||||
# files. Add files to the CLEAN file list to have the :clean
|
||||
# target handle them.
|
||||
#
|
||||
# [:clobber] Clobber all generated and non-source files in a project.
|
||||
# The task depends on :clean, so all the clean files will
|
||||
# be deleted as well as files in the CLOBBER file list.
|
||||
# The intent of this task is to return a project to its
|
||||
# pristine, just unpacked state.
|
||||
|
||||
require "rake"
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
module Rake
|
||||
module Cleaner
|
||||
extend FileUtils
|
||||
|
||||
module_function
|
||||
|
||||
def cleanup_files(file_names)
|
||||
file_names.each do |file_name|
|
||||
cleanup(file_name)
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup(file_name, **opts)
|
||||
begin
|
||||
opts = { verbose: Rake.application.options.trace }.merge(opts)
|
||||
rm_r file_name, **opts
|
||||
rescue StandardError => ex
|
||||
puts "Failed to remove #{file_name}: #{ex}" unless file_already_gone?(file_name)
|
||||
end
|
||||
end
|
||||
|
||||
def file_already_gone?(file_name)
|
||||
return false if File.exist?(file_name)
|
||||
|
||||
path = file_name
|
||||
prev = nil
|
||||
|
||||
while path = File.dirname(path)
|
||||
return false if cant_be_deleted?(path)
|
||||
break if [prev, "."].include?(path)
|
||||
prev = path
|
||||
end
|
||||
true
|
||||
end
|
||||
private_class_method :file_already_gone?
|
||||
|
||||
def cant_be_deleted?(path_name)
|
||||
File.exist?(path_name) &&
|
||||
(!File.readable?(path_name) || !File.executable?(path_name))
|
||||
end
|
||||
private_class_method :cant_be_deleted?
|
||||
end
|
||||
end
|
||||
|
||||
CLEAN = ::Rake::FileList["**/*~", "**/*.bak", "**/core"]
|
||||
CLEAN.clear_exclude.exclude { |fn|
|
||||
fn.pathmap("%f").downcase == "core" && File.directory?(fn)
|
||||
}
|
||||
|
||||
desc "Remove any temporary products."
|
||||
task :clean do
|
||||
Rake::Cleaner.cleanup_files(CLEAN)
|
||||
end
|
||||
|
||||
CLOBBER = ::Rake::FileList.new
|
||||
|
||||
desc "Remove any generated files."
|
||||
task clobber: [:clean] do
|
||||
Rake::Cleaner.cleanup_files(CLOBBER)
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
##
|
||||
# Mixin for creating easily cloned objects.
|
||||
|
||||
module Cloneable # :nodoc:
|
||||
# The hook that is invoked by 'clone' and 'dup' methods.
|
||||
def initialize_copy(source)
|
||||
super
|
||||
source.instance_variables.each do |var|
|
||||
src_value = source.instance_variable_get(var)
|
||||
value = src_value.clone rescue src_value
|
||||
instance_variable_set(var, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# Based on a script at:
|
||||
# http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed
|
||||
class CpuCounter # :nodoc: all
|
||||
def self.count
|
||||
new.count_with_default
|
||||
end
|
||||
|
||||
def count_with_default(default=4)
|
||||
count || default
|
||||
rescue StandardError
|
||||
default
|
||||
end
|
||||
|
||||
begin
|
||||
require "etc"
|
||||
rescue LoadError
|
||||
else
|
||||
if Etc.respond_to?(:nprocessors)
|
||||
def count
|
||||
return Etc.nprocessors
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Rake::CpuCounter.method_defined?(:count)
|
||||
Rake::CpuCounter.class_eval <<-'end;', __FILE__, __LINE__+1
|
||||
require 'rbconfig'
|
||||
|
||||
def count
|
||||
if RUBY_PLATFORM == 'java'
|
||||
count_via_java_runtime
|
||||
else
|
||||
case RbConfig::CONFIG['host_os']
|
||||
when /linux/
|
||||
count_via_cpuinfo
|
||||
when /darwin|bsd/
|
||||
count_via_sysctl
|
||||
when /mswin|mingw/
|
||||
count_via_win32
|
||||
else
|
||||
# Try everything
|
||||
count_via_win32 ||
|
||||
count_via_sysctl ||
|
||||
count_via_cpuinfo
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def count_via_java_runtime
|
||||
Java::Java.lang.Runtime.getRuntime.availableProcessors
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
|
||||
def count_via_win32
|
||||
require 'win32ole'
|
||||
wmi = WIN32OLE.connect("winmgmts://")
|
||||
cpu = wmi.ExecQuery("select NumberOfCores from Win32_Processor") # TODO count hyper-threaded in this
|
||||
cpu.to_enum.first.NumberOfCores
|
||||
rescue StandardError, LoadError
|
||||
nil
|
||||
end
|
||||
|
||||
def count_via_cpuinfo
|
||||
open('/proc/cpuinfo') { |f| f.readlines }.grep(/processor/).size
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
|
||||
def count_via_sysctl
|
||||
run 'sysctl', '-n', 'hw.ncpu'
|
||||
end
|
||||
|
||||
def run(command, *args)
|
||||
cmd = resolve_command(command)
|
||||
if cmd
|
||||
IO.popen [cmd, *args] do |io|
|
||||
io.read.to_i
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_command(command)
|
||||
look_for_command("/usr/sbin", command) ||
|
||||
look_for_command("/sbin", command) ||
|
||||
in_path_command(command)
|
||||
end
|
||||
|
||||
def look_for_command(dir, command)
|
||||
path = File.join(dir, command)
|
||||
File.exist?(path) ? path : nil
|
||||
end
|
||||
|
||||
def in_path_command(command)
|
||||
IO.popen ['which', command] do |io|
|
||||
io.eof? ? nil : command
|
||||
end
|
||||
end
|
||||
end;
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# Default Rakefile loader used by +import+.
|
||||
class DefaultLoader
|
||||
|
||||
##
|
||||
# Loads a rakefile into the current application from +fn+
|
||||
|
||||
def load(fn)
|
||||
Rake.load_rakefile(File.expand_path(fn))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,196 @@
|
||||
# frozen_string_literal: true
|
||||
# Rake DSL functions.
|
||||
require "rake/file_utils_ext"
|
||||
|
||||
module Rake
|
||||
|
||||
##
|
||||
# DSL is a module that provides #task, #desc, #namespace, etc. Use this
|
||||
# when you'd like to use rake outside the top level scope.
|
||||
#
|
||||
# For a Rakefile you run from the command line this module is automatically
|
||||
# included.
|
||||
|
||||
module DSL
|
||||
|
||||
#--
|
||||
# Include the FileUtils file manipulation functions in the top
|
||||
# level module, but mark them private so that they don't
|
||||
# unintentionally define methods on other objects.
|
||||
#++
|
||||
|
||||
include FileUtilsExt
|
||||
private(*FileUtils.instance_methods(false))
|
||||
private(*FileUtilsExt.instance_methods(false))
|
||||
|
||||
private
|
||||
|
||||
# :call-seq:
|
||||
# task(task_name)
|
||||
# task(task_name: dependencies)
|
||||
# task(task_name, arguments => dependencies)
|
||||
#
|
||||
# Declare a basic task. The +task_name+ is always the first argument. If
|
||||
# the task name contains a ":" it is defined in that namespace.
|
||||
#
|
||||
# The +dependencies+ may be a single task name or an Array of task names.
|
||||
# The +argument+ (a single name) or +arguments+ (an Array of names) define
|
||||
# the arguments provided to the task.
|
||||
#
|
||||
# The task, argument and dependency names may be either symbols or
|
||||
# strings.
|
||||
#
|
||||
# A task with a single dependency:
|
||||
#
|
||||
# task clobber: %w[clean] do
|
||||
# rm_rf "html"
|
||||
# end
|
||||
#
|
||||
# A task with an argument and a dependency:
|
||||
#
|
||||
# task :package, [:version] => :test do |t, args|
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# To invoke this task from the command line:
|
||||
#
|
||||
# $ rake package[1.2.3]
|
||||
#
|
||||
def task(*args, &block) # :doc:
|
||||
Rake::Task.define_task(*args, &block)
|
||||
end
|
||||
|
||||
# Declare a file task.
|
||||
#
|
||||
# Example:
|
||||
# file "config.cfg" => ["config.template"] do
|
||||
# open("config.cfg", "w") do |outfile|
|
||||
# open("config.template") do |infile|
|
||||
# while line = infile.gets
|
||||
# outfile.puts line
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def file(*args, &block) # :doc:
|
||||
Rake::FileTask.define_task(*args, &block)
|
||||
end
|
||||
|
||||
# Declare a file creation task.
|
||||
# (Mainly used for the directory command).
|
||||
def file_create(*args, &block)
|
||||
Rake::FileCreationTask.define_task(*args, &block)
|
||||
end
|
||||
|
||||
# Declare a set of files tasks to create the given directories on
|
||||
# demand.
|
||||
#
|
||||
# Example:
|
||||
# directory "testdata/doc"
|
||||
#
|
||||
def directory(*args, &block) # :doc:
|
||||
args = args.flat_map { |arg| arg.is_a?(FileList) ? arg.to_a.flatten : arg }
|
||||
result = file_create(*args, &block)
|
||||
dir, _ = *Rake.application.resolve_args(args)
|
||||
dir = Rake.from_pathname(dir)
|
||||
Rake.each_dir_parent(dir) do |d|
|
||||
file_create d do |t|
|
||||
mkdir_p t.name unless File.exist?(t.name)
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Declare a task that performs its prerequisites in
|
||||
# parallel. Multitasks does *not* guarantee that its prerequisites
|
||||
# will execute in any given order (which is obvious when you think
|
||||
# about it)
|
||||
#
|
||||
# Example:
|
||||
# multitask deploy: %w[deploy_gem deploy_rdoc]
|
||||
#
|
||||
def multitask(*args, &block) # :doc:
|
||||
Rake::MultiTask.define_task(*args, &block)
|
||||
end
|
||||
|
||||
# Create a new rake namespace and use it for evaluating the given
|
||||
# block. Returns a NameSpace object that can be used to lookup
|
||||
# tasks defined in the namespace.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# ns = namespace "nested" do
|
||||
# # the "nested:run" task
|
||||
# task :run
|
||||
# end
|
||||
# task_run = ns[:run] # find :run in the given namespace.
|
||||
#
|
||||
# Tasks can also be defined in a namespace by using a ":" in the task
|
||||
# name:
|
||||
#
|
||||
# task "nested:test" do
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
def namespace(name=nil, &block) # :doc:
|
||||
name = name.to_s if name.kind_of?(Symbol)
|
||||
name = name.to_str if name.respond_to?(:to_str)
|
||||
unless name.kind_of?(String) || name.nil?
|
||||
raise ArgumentError, "Expected a String or Symbol for a namespace name"
|
||||
end
|
||||
Rake.application.in_namespace(name, &block)
|
||||
end
|
||||
|
||||
# Declare a rule for auto-tasks.
|
||||
#
|
||||
# Example:
|
||||
# rule '.o' => '.c' do |t|
|
||||
# sh 'cc', '-c', '-o', t.name, t.source
|
||||
# end
|
||||
#
|
||||
def rule(*args, &block) # :doc:
|
||||
Rake::Task.create_rule(*args, &block)
|
||||
end
|
||||
|
||||
# Describes the next rake task. Duplicate descriptions are discarded.
|
||||
# Descriptions are shown with <code>rake -T</code> (up to the first
|
||||
# sentence) and <code>rake -D</code> (the entire description).
|
||||
#
|
||||
# Example:
|
||||
# desc "Run the Unit Tests"
|
||||
# task test: [:build] do
|
||||
# # ... run tests
|
||||
# end
|
||||
#
|
||||
def desc(description) # :doc:
|
||||
Rake.application.last_description = description
|
||||
end
|
||||
|
||||
# Import the partial Rakefiles +fn+. Imported files are loaded
|
||||
# _after_ the current file is completely loaded. This allows the
|
||||
# import statement to appear anywhere in the importing file, and yet
|
||||
# allowing the imported files to depend on objects defined in the
|
||||
# importing file.
|
||||
#
|
||||
# A common use of the import statement is to include files
|
||||
# containing dependency declarations.
|
||||
#
|
||||
# See also the --rakelibdir command line option.
|
||||
#
|
||||
# Example:
|
||||
# import ".depend", "my_rules"
|
||||
#
|
||||
def import(*fns) # :doc:
|
||||
fns.each do |fn|
|
||||
Rake.application.add_import(fn)
|
||||
end
|
||||
end
|
||||
end
|
||||
extend FileUtilsExt
|
||||
end
|
||||
|
||||
# Extend the main object with the DSL commands. This allows top-level
|
||||
# calls to task, etc. to work from a Rakefile without polluting the
|
||||
# object inheritance tree.
|
||||
self.extend Rake::DSL
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# EarlyTime is a fake timestamp that occurs _before_ any other time value.
|
||||
class EarlyTime
|
||||
include Comparable
|
||||
include Singleton
|
||||
|
||||
##
|
||||
# The EarlyTime always comes before +other+!
|
||||
|
||||
def <=>(other)
|
||||
-1
|
||||
end
|
||||
|
||||
def to_s # :nodoc:
|
||||
"<EARLY TIME>"
|
||||
end
|
||||
end
|
||||
|
||||
EARLY = EarlyTime.instance
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
class Module
|
||||
# Check for an existing method in the current class before extending. If
|
||||
# the method already exists, then a warning is printed and the extension is
|
||||
# not added. Otherwise the block is yielded and any definitions in the
|
||||
# block will take effect.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# class String
|
||||
# rake_extension("xyz") do
|
||||
# def xyz
|
||||
# ...
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def rake_extension(method) # :nodoc:
|
||||
if method_defined?(method)
|
||||
$stderr.puts "WARNING: Possible conflict with Rake extension: " +
|
||||
"#{self}##{method} already exists"
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,176 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake/ext/core"
|
||||
|
||||
class String
|
||||
|
||||
rake_extension("ext") do
|
||||
# Replace the file extension with +newext+. If there is no extension on
|
||||
# the string, append the new extension to the end. If the new extension
|
||||
# is not given, or is the empty string, remove any existing extension.
|
||||
#
|
||||
# +ext+ is a user added method for the String class.
|
||||
#
|
||||
# This String extension comes from Rake
|
||||
def ext(newext="")
|
||||
return self.dup if [".", ".."].include? self
|
||||
if newext != ""
|
||||
newext = "." + newext unless newext =~ /^\./
|
||||
end
|
||||
self.chomp(File.extname(self)) << newext
|
||||
end
|
||||
end
|
||||
|
||||
rake_extension("pathmap") do
|
||||
# Explode a path into individual components. Used by +pathmap+.
|
||||
#
|
||||
# This String extension comes from Rake
|
||||
def pathmap_explode
|
||||
head, tail = File.split(self)
|
||||
return [self] if head == self
|
||||
return [tail] if head == "." || tail == "/"
|
||||
return [head, tail] if head == "/"
|
||||
return head.pathmap_explode + [tail]
|
||||
end
|
||||
protected :pathmap_explode
|
||||
|
||||
# Extract a partial path from the path. Include +n+ directories from the
|
||||
# front end (left hand side) if +n+ is positive. Include |+n+|
|
||||
# directories from the back end (right hand side) if +n+ is negative.
|
||||
#
|
||||
# This String extension comes from Rake
|
||||
def pathmap_partial(n)
|
||||
dirs = File.dirname(self).pathmap_explode
|
||||
partial_dirs =
|
||||
if n > 0
|
||||
dirs[0...n]
|
||||
elsif n < 0
|
||||
dirs.reverse[0...-n].reverse
|
||||
else
|
||||
"."
|
||||
end
|
||||
File.join(partial_dirs)
|
||||
end
|
||||
protected :pathmap_partial
|
||||
|
||||
# Perform the pathmap replacement operations on the given path. The
|
||||
# patterns take the form 'pat1,rep1;pat2,rep2...'.
|
||||
#
|
||||
# This String extension comes from Rake
|
||||
def pathmap_replace(patterns, &block)
|
||||
result = self
|
||||
patterns.split(";").each do |pair|
|
||||
pattern, replacement = pair.split(",")
|
||||
pattern = Regexp.new(pattern)
|
||||
if replacement == "*" && block_given?
|
||||
result = result.sub(pattern, &block)
|
||||
elsif replacement
|
||||
result = result.sub(pattern, replacement)
|
||||
else
|
||||
result = result.sub(pattern, "")
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
protected :pathmap_replace
|
||||
|
||||
# Map the path according to the given specification. The specification
|
||||
# controls the details of the mapping. The following special patterns are
|
||||
# recognized:
|
||||
#
|
||||
# <tt>%p</tt> :: The complete path.
|
||||
# <tt>%f</tt> :: The base file name of the path, with its file extension,
|
||||
# but without any directories.
|
||||
# <tt>%n</tt> :: The file name of the path without its file extension.
|
||||
# <tt>%d</tt> :: The directory list of the path.
|
||||
# <tt>%x</tt> :: The file extension of the path. An empty string if there
|
||||
# is no extension.
|
||||
# <tt>%X</tt> :: Everything *but* the file extension.
|
||||
# <tt>%s</tt> :: The alternate file separator if defined, otherwise use #
|
||||
# the standard file separator.
|
||||
# <tt>%%</tt> :: A percent sign.
|
||||
#
|
||||
# The <tt>%d</tt> specifier can also have a numeric prefix (e.g. '%2d').
|
||||
# If the number is positive, only return (up to) +n+ directories in the
|
||||
# path, starting from the left hand side. If +n+ is negative, return (up
|
||||
# to) +n+ directories from the right hand side of the path.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# 'a/b/c/d/file.txt'.pathmap("%2d") => 'a/b'
|
||||
# 'a/b/c/d/file.txt'.pathmap("%-2d") => 'c/d'
|
||||
#
|
||||
# Also the <tt>%d</tt>, <tt>%p</tt>, <tt>%f</tt>, <tt>%n</tt>,
|
||||
# <tt>%x</tt>, and <tt>%X</tt> operators can take a pattern/replacement
|
||||
# argument to perform simple string substitutions on a particular part of
|
||||
# the path. The pattern and replacement are separated by a comma and are
|
||||
# enclosed by curly braces. The replacement spec comes after the %
|
||||
# character but before the operator letter. (e.g. "%{old,new}d").
|
||||
# Multiple replacement specs should be separated by semi-colons (e.g.
|
||||
# "%{old,new;src,bin}d").
|
||||
#
|
||||
# Regular expressions may be used for the pattern, and back refs may be
|
||||
# used in the replacement text. Curly braces, commas and semi-colons are
|
||||
# excluded from both the pattern and replacement text (let's keep parsing
|
||||
# reasonable).
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# "src/org/onestepback/proj/A.java".pathmap("%{^src,class}X.class")
|
||||
#
|
||||
# returns:
|
||||
#
|
||||
# "class/org/onestepback/proj/A.class"
|
||||
#
|
||||
# If the replacement text is '*', then a block may be provided to perform
|
||||
# some arbitrary calculation for the replacement.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# "/path/to/file.TXT".pathmap("%X%{.*,*}x") { |ext|
|
||||
# ext.downcase
|
||||
# }
|
||||
#
|
||||
# Returns:
|
||||
#
|
||||
# "/path/to/file.txt"
|
||||
#
|
||||
# This String extension comes from Rake
|
||||
def pathmap(spec=nil, &block)
|
||||
return self if spec.nil?
|
||||
result = "".dup
|
||||
spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag|
|
||||
case frag
|
||||
when "%f"
|
||||
result << File.basename(self)
|
||||
when "%n"
|
||||
result << File.basename(self).ext
|
||||
when "%d"
|
||||
result << File.dirname(self)
|
||||
when "%x"
|
||||
result << File.extname(self)
|
||||
when "%X"
|
||||
result << self.ext
|
||||
when "%p"
|
||||
result << self
|
||||
when "%s"
|
||||
result << (File::ALT_SEPARATOR || File::SEPARATOR)
|
||||
when "%-"
|
||||
# do nothing
|
||||
when "%%"
|
||||
result << "%"
|
||||
when /%(-?\d+)d/
|
||||
result << pathmap_partial($1.to_i)
|
||||
when /^%\{([^}]*)\}(\d*[dpfnxX])/
|
||||
patterns, operator = $1, $2
|
||||
result << pathmap("%" + operator).pathmap_replace(patterns, &block)
|
||||
when /^%/
|
||||
fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'"
|
||||
else
|
||||
result << frag
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake/file_task"
|
||||
require "rake/early_time"
|
||||
|
||||
module Rake
|
||||
|
||||
# A FileCreationTask is a file task that when used as a dependency will be
|
||||
# needed if and only if the file has not been created. Once created, it is
|
||||
# not re-triggered if any of its dependencies are newer, nor does trigger
|
||||
# any rebuilds of tasks that depend on it whenever it is updated.
|
||||
#
|
||||
class FileCreationTask < FileTask
|
||||
# Is this file task needed? Yes if it doesn't exist.
|
||||
def needed?
|
||||
!File.exist?(name)
|
||||
end
|
||||
|
||||
# Time stamp for file creation task. This time stamp is earlier
|
||||
# than any other time stamp.
|
||||
def timestamp
|
||||
Rake::EARLY
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,435 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake/cloneable"
|
||||
require "rake/file_utils_ext"
|
||||
require "rake/ext/string"
|
||||
|
||||
module Rake
|
||||
|
||||
##
|
||||
# A FileList is essentially an array with a few helper methods defined to
|
||||
# make file manipulation a bit easier.
|
||||
#
|
||||
# FileLists are lazy. When given a list of glob patterns for possible files
|
||||
# to be included in the file list, instead of searching the file structures
|
||||
# to find the files, a FileList holds the pattern for latter use.
|
||||
#
|
||||
# This allows us to define a number of FileList to match any number of
|
||||
# files, but only search out the actual files when then FileList itself is
|
||||
# actually used. The key is that the first time an element of the
|
||||
# FileList/Array is requested, the pending patterns are resolved into a real
|
||||
# list of file names.
|
||||
#
|
||||
class FileList
|
||||
|
||||
include Cloneable
|
||||
|
||||
# == Method Delegation
|
||||
#
|
||||
# The lazy evaluation magic of FileLists happens by implementing all the
|
||||
# array specific methods to call +resolve+ before delegating the heavy
|
||||
# lifting to an embedded array object (@items).
|
||||
#
|
||||
# In addition, there are two kinds of delegation calls. The regular kind
|
||||
# delegates to the @items array and returns the result directly. Well,
|
||||
# almost directly. It checks if the returned value is the @items object
|
||||
# itself, and if so will return the FileList object instead.
|
||||
#
|
||||
# The second kind of delegation call is used in methods that normally
|
||||
# return a new Array object. We want to capture the return value of these
|
||||
# methods and wrap them in a new FileList object. We enumerate these
|
||||
# methods in the +SPECIAL_RETURN+ list below.
|
||||
|
||||
# List of array methods (that are not in +Object+) that need to be
|
||||
# delegated.
|
||||
ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map(&:to_s)
|
||||
|
||||
# List of additional methods that must be delegated.
|
||||
MUST_DEFINE = %w[inspect <=>]
|
||||
|
||||
# List of methods that should not be delegated here (we define special
|
||||
# versions of them explicitly below).
|
||||
MUST_NOT_DEFINE = %w[to_a to_ary partition * <<]
|
||||
|
||||
# List of delegated methods that return new array values which need
|
||||
# wrapping.
|
||||
SPECIAL_RETURN = %w[
|
||||
map collect sort sort_by select find_all reject grep
|
||||
compact flatten uniq values_at
|
||||
+ - & |
|
||||
]
|
||||
|
||||
DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).map(&:to_s).sort.uniq
|
||||
|
||||
# Now do the delegation.
|
||||
DELEGATING_METHODS.each do |sym|
|
||||
if SPECIAL_RETURN.include?(sym)
|
||||
ln = __LINE__ + 1
|
||||
class_eval %{
|
||||
def #{sym}(*args, &block)
|
||||
resolve
|
||||
result = @items.send(:#{sym}, *args, &block)
|
||||
self.class.new.import(result)
|
||||
end
|
||||
}, __FILE__, ln
|
||||
else
|
||||
ln = __LINE__ + 1
|
||||
class_eval %{
|
||||
def #{sym}(*args, &block)
|
||||
resolve
|
||||
result = @items.send(:#{sym}, *args, &block)
|
||||
result.object_id == @items.object_id ? self : result
|
||||
end
|
||||
}, __FILE__, ln
|
||||
end
|
||||
end
|
||||
|
||||
GLOB_PATTERN = %r{[*?\[\{]}
|
||||
|
||||
# Create a file list from the globbable patterns given. If you wish to
|
||||
# perform multiple includes or excludes at object build time, use the
|
||||
# "yield self" pattern.
|
||||
#
|
||||
# Example:
|
||||
# file_list = FileList.new('lib/**/*.rb', 'test/test*.rb')
|
||||
#
|
||||
# pkg_files = FileList.new('lib/**/*') do |fl|
|
||||
# fl.exclude(/\bCVS\b/)
|
||||
# end
|
||||
#
|
||||
def initialize(*patterns)
|
||||
@pending_add = []
|
||||
@pending = false
|
||||
@exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
|
||||
@exclude_procs = DEFAULT_IGNORE_PROCS.dup
|
||||
@items = []
|
||||
patterns.each { |pattern| include(pattern) }
|
||||
yield self if block_given?
|
||||
end
|
||||
|
||||
# Add file names defined by glob patterns to the file list. If an array
|
||||
# is given, add each element of the array.
|
||||
#
|
||||
# Example:
|
||||
# file_list.include("*.java", "*.cfg")
|
||||
# file_list.include %w( math.c lib.h *.o )
|
||||
#
|
||||
def include(*filenames)
|
||||
# TODO: check for pending
|
||||
filenames.each do |fn|
|
||||
if fn.respond_to? :to_ary
|
||||
include(*fn.to_ary)
|
||||
else
|
||||
@pending_add << Rake.from_pathname(fn)
|
||||
end
|
||||
end
|
||||
@pending = true
|
||||
self
|
||||
end
|
||||
alias :add :include
|
||||
|
||||
# Register a list of file name patterns that should be excluded from the
|
||||
# list. Patterns may be regular expressions, glob patterns or regular
|
||||
# strings. In addition, a block given to exclude will remove entries that
|
||||
# return true when given to the block.
|
||||
#
|
||||
# Note that glob patterns are expanded against the file system. If a file
|
||||
# is explicitly added to a file list, but does not exist in the file
|
||||
# system, then an glob pattern in the exclude list will not exclude the
|
||||
# file.
|
||||
#
|
||||
# Examples:
|
||||
# FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
|
||||
# FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c']
|
||||
#
|
||||
# If "a.c" is a file, then ...
|
||||
# FileList['a.c', 'b.c'].exclude("a.*") => ['b.c']
|
||||
#
|
||||
# If "a.c" is not a file, then ...
|
||||
# FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
|
||||
#
|
||||
def exclude(*patterns, &block)
|
||||
patterns.each do |pat|
|
||||
if pat.respond_to? :to_ary
|
||||
exclude(*pat.to_ary)
|
||||
else
|
||||
@exclude_patterns << Rake.from_pathname(pat)
|
||||
end
|
||||
end
|
||||
@exclude_procs << block if block_given?
|
||||
resolve_exclude unless @pending
|
||||
self
|
||||
end
|
||||
|
||||
# Clear all the exclude patterns so that we exclude nothing.
|
||||
def clear_exclude
|
||||
@exclude_patterns = []
|
||||
@exclude_procs = []
|
||||
self
|
||||
end
|
||||
|
||||
# A FileList is equal through array equality.
|
||||
def ==(array)
|
||||
to_ary == array
|
||||
end
|
||||
|
||||
# Return the internal array object.
|
||||
def to_a
|
||||
resolve
|
||||
@items
|
||||
end
|
||||
|
||||
# Return the internal array object.
|
||||
def to_ary
|
||||
to_a
|
||||
end
|
||||
|
||||
# Lie about our class.
|
||||
def is_a?(klass)
|
||||
klass == Array || super(klass)
|
||||
end
|
||||
alias kind_of? is_a?
|
||||
|
||||
# Redefine * to return either a string or a new file list.
|
||||
def *(other)
|
||||
result = @items * other
|
||||
case result
|
||||
when Array
|
||||
self.class.new.import(result)
|
||||
else
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def <<(obj)
|
||||
resolve
|
||||
@items << Rake.from_pathname(obj)
|
||||
self
|
||||
end
|
||||
|
||||
# Resolve all the pending adds now.
|
||||
def resolve
|
||||
if @pending
|
||||
@pending = false
|
||||
@pending_add.each do |fn| resolve_add(fn) end
|
||||
@pending_add = []
|
||||
resolve_exclude
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def resolve_add(fn) # :nodoc:
|
||||
case fn
|
||||
when GLOB_PATTERN
|
||||
add_matching(fn)
|
||||
else
|
||||
self << fn
|
||||
end
|
||||
end
|
||||
private :resolve_add
|
||||
|
||||
def resolve_exclude # :nodoc:
|
||||
reject! { |fn| excluded_from_list?(fn) }
|
||||
self
|
||||
end
|
||||
private :resolve_exclude
|
||||
|
||||
# Return a new FileList with the results of running +sub+ against each
|
||||
# element of the original list.
|
||||
#
|
||||
# Example:
|
||||
# FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o']
|
||||
#
|
||||
def sub(pat, rep)
|
||||
inject(self.class.new) { |res, fn| res << fn.sub(pat, rep) }
|
||||
end
|
||||
|
||||
# Return a new FileList with the results of running +gsub+ against each
|
||||
# element of the original list.
|
||||
#
|
||||
# Example:
|
||||
# FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
|
||||
# => ['lib\\test\\file', 'x\\y']
|
||||
#
|
||||
def gsub(pat, rep)
|
||||
inject(self.class.new) { |res, fn| res << fn.gsub(pat, rep) }
|
||||
end
|
||||
|
||||
# Same as +sub+ except that the original file list is modified.
|
||||
def sub!(pat, rep)
|
||||
each_with_index { |fn, i| self[i] = fn.sub(pat, rep) }
|
||||
self
|
||||
end
|
||||
|
||||
# Same as +gsub+ except that the original file list is modified.
|
||||
def gsub!(pat, rep)
|
||||
each_with_index { |fn, i| self[i] = fn.gsub(pat, rep) }
|
||||
self
|
||||
end
|
||||
|
||||
# Apply the pathmap spec to each of the included file names, returning a
|
||||
# new file list with the modified paths. (See String#pathmap for
|
||||
# details.)
|
||||
def pathmap(spec=nil, &block)
|
||||
collect { |fn| fn.pathmap(spec, &block) }
|
||||
end
|
||||
|
||||
# Return a new FileList with <tt>String#ext</tt> method applied to
|
||||
# each member of the array.
|
||||
#
|
||||
# This method is a shortcut for:
|
||||
#
|
||||
# array.collect { |item| item.ext(newext) }
|
||||
#
|
||||
# +ext+ is a user added method for the Array class.
|
||||
def ext(newext="")
|
||||
collect { |fn| fn.ext(newext) }
|
||||
end
|
||||
|
||||
# Grep each of the files in the filelist using the given pattern. If a
|
||||
# block is given, call the block on each matching line, passing the file
|
||||
# name, line number, and the matching line of text. If no block is given,
|
||||
# a standard emacs style file:linenumber:line message will be printed to
|
||||
# standard out. Returns the number of matched items.
|
||||
def egrep(pattern, *options)
|
||||
matched = 0
|
||||
each do |fn|
|
||||
begin
|
||||
File.open(fn, "r", *options) do |inf|
|
||||
count = 0
|
||||
inf.each do |line|
|
||||
count += 1
|
||||
if pattern.match(line)
|
||||
matched += 1
|
||||
if block_given?
|
||||
yield fn, count, line
|
||||
else
|
||||
puts "#{fn}:#{count}:#{line}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue StandardError => ex
|
||||
$stderr.puts "Error while processing '#{fn}': #{ex}"
|
||||
end
|
||||
end
|
||||
matched
|
||||
end
|
||||
|
||||
# Return a new file list that only contains file names from the current
|
||||
# file list that exist on the file system.
|
||||
def existing
|
||||
select { |fn| File.exist?(fn) }.uniq
|
||||
end
|
||||
|
||||
# Modify the current file list so that it contains only file name that
|
||||
# exist on the file system.
|
||||
def existing!
|
||||
resolve
|
||||
@items = @items.select { |fn| File.exist?(fn) }.uniq
|
||||
self
|
||||
end
|
||||
|
||||
# FileList version of partition. Needed because the nested arrays should
|
||||
# be FileLists in this version.
|
||||
def partition(&block) # :nodoc:
|
||||
resolve
|
||||
result = @items.partition(&block)
|
||||
[
|
||||
self.class.new.import(result[0]),
|
||||
self.class.new.import(result[1]),
|
||||
]
|
||||
end
|
||||
|
||||
# Convert a FileList to a string by joining all elements with a space.
|
||||
def to_s
|
||||
resolve
|
||||
self.join(" ")
|
||||
end
|
||||
|
||||
# Add matching glob patterns.
|
||||
def add_matching(pattern)
|
||||
self.class.glob(pattern).each do |fn|
|
||||
self << fn unless excluded_from_list?(fn)
|
||||
end
|
||||
end
|
||||
private :add_matching
|
||||
|
||||
# Should the given file name be excluded from the list?
|
||||
#
|
||||
# NOTE: This method was formerly named "exclude?", but Rails
|
||||
# introduced an exclude? method as an array method and setup a
|
||||
# conflict with file list. We renamed the method to avoid
|
||||
# confusion. If you were using "FileList#exclude?" in your user
|
||||
# code, you will need to update.
|
||||
def excluded_from_list?(fn)
|
||||
return true if @exclude_patterns.any? do |pat|
|
||||
case pat
|
||||
when Regexp
|
||||
fn =~ pat
|
||||
when GLOB_PATTERN
|
||||
flags = File::FNM_PATHNAME
|
||||
# Ruby <= 1.9.3 does not support File::FNM_EXTGLOB
|
||||
flags |= File::FNM_EXTGLOB if defined? File::FNM_EXTGLOB
|
||||
File.fnmatch?(pat, fn, flags)
|
||||
else
|
||||
fn == pat
|
||||
end
|
||||
end
|
||||
@exclude_procs.any? { |p| p.call(fn) }
|
||||
end
|
||||
|
||||
DEFAULT_IGNORE_PATTERNS = [
|
||||
/(^|[\/\\])CVS([\/\\]|$)/,
|
||||
/(^|[\/\\])\.svn([\/\\]|$)/,
|
||||
/\.bak$/,
|
||||
/~$/
|
||||
]
|
||||
DEFAULT_IGNORE_PROCS = [
|
||||
proc { |fn| fn =~ /(^|[\/\\])core$/ && !File.directory?(fn) }
|
||||
]
|
||||
|
||||
def import(array) # :nodoc:
|
||||
@items = array
|
||||
self
|
||||
end
|
||||
|
||||
class << self
|
||||
# Create a new file list including the files listed. Similar to:
|
||||
#
|
||||
# FileList.new(*args)
|
||||
def [](*args)
|
||||
new(*args)
|
||||
end
|
||||
|
||||
# Get a sorted list of files matching the pattern. This method
|
||||
# should be preferred to Dir[pattern] and Dir.glob(pattern) because
|
||||
# the files returned are guaranteed to be sorted.
|
||||
def glob(pattern, *args)
|
||||
Dir.glob(pattern, *args).sort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Rake
|
||||
class << self
|
||||
|
||||
# Yield each file or directory component.
|
||||
def each_dir_parent(dir) # :nodoc:
|
||||
old_length = nil
|
||||
while dir != "." && dir.length != old_length
|
||||
yield(dir)
|
||||
old_length = dir.length
|
||||
dir = File.dirname(dir)
|
||||
end
|
||||
end
|
||||
|
||||
# Convert Pathname and Pathname-like objects to strings;
|
||||
# leave everything else alone
|
||||
def from_pathname(path) # :nodoc:
|
||||
path = path.to_path if path.respond_to?(:to_path)
|
||||
path = path.to_str if path.respond_to?(:to_str)
|
||||
path
|
||||
end
|
||||
end
|
||||
end # module Rake
|
||||
@@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake/task"
|
||||
require "rake/early_time"
|
||||
|
||||
module Rake
|
||||
|
||||
# A FileTask is a task that includes time based dependencies. If any of a
|
||||
# FileTask's prerequisites have a timestamp that is later than the file
|
||||
# represented by this task, then the file must be rebuilt (using the
|
||||
# supplied actions).
|
||||
#
|
||||
class FileTask < Task
|
||||
|
||||
# Is this file task needed? Yes if it doesn't exist, or if its time stamp
|
||||
# is out of date.
|
||||
def needed?
|
||||
begin
|
||||
out_of_date?(File.mtime(name)) || @application.options.build_all
|
||||
rescue Errno::ENOENT
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Time stamp for file task.
|
||||
def timestamp
|
||||
begin
|
||||
File.mtime(name)
|
||||
rescue Errno::ENOENT
|
||||
Rake::LATE
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Are there any prerequisites with a later time than the given time stamp?
|
||||
def out_of_date?(stamp)
|
||||
all_prerequisite_tasks.any? { |prereq|
|
||||
prereq_task = application[prereq, @scope]
|
||||
if prereq_task.instance_of?(Rake::FileTask)
|
||||
prereq_task.timestamp > stamp || @application.options.build_all
|
||||
else
|
||||
prereq_task.timestamp > stamp
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Task class methods.
|
||||
#
|
||||
class << self
|
||||
# Apply the scope to the task name according to the rules for this kind
|
||||
# of task. File based tasks ignore the scope when creating the name.
|
||||
def scope_name(scope, task_name)
|
||||
Rake.from_pathname(task_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,132 @@
|
||||
# frozen_string_literal: true
|
||||
require "rbconfig"
|
||||
require "fileutils"
|
||||
|
||||
#--
|
||||
# This a FileUtils extension that defines several additional commands to be
|
||||
# added to the FileUtils utility functions.
|
||||
module FileUtils
|
||||
# Path to the currently running Ruby program
|
||||
RUBY = ENV["RUBY"] || File.join(
|
||||
RbConfig::CONFIG["bindir"],
|
||||
RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]).
|
||||
sub(/.*\s.*/m, '"\&"')
|
||||
|
||||
# Run the system command +cmd+. If multiple arguments are given the command
|
||||
# is run directly (without the shell, same semantics as Kernel::exec and
|
||||
# Kernel::system).
|
||||
#
|
||||
# It is recommended you use the multiple argument form over interpolating
|
||||
# user input for both usability and security reasons. With the multiple
|
||||
# argument form you can easily process files with spaces or other shell
|
||||
# reserved characters in them. With the multiple argument form your rake
|
||||
# tasks are not vulnerable to users providing an argument like
|
||||
# <code>; rm # -rf /</code>.
|
||||
#
|
||||
# If a block is given, upon command completion the block is called with an
|
||||
# OK flag (true on a zero exit status) and a Process::Status object.
|
||||
# Without a block a RuntimeError is raised when the command exits non-zero.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# sh 'ls -ltr'
|
||||
#
|
||||
# sh 'ls', 'file with spaces'
|
||||
#
|
||||
# # check exit status after command runs
|
||||
# sh %{grep pattern file} do |ok, res|
|
||||
# if !ok
|
||||
# puts "pattern not found (status = #{res.exitstatus})"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def sh(*cmd, &block)
|
||||
options = (Hash === cmd.last) ? cmd.pop : {}
|
||||
shell_runner = block_given? ? block : create_shell_runner(cmd)
|
||||
|
||||
set_verbose_option(options)
|
||||
verbose = options.delete :verbose
|
||||
noop = options.delete(:noop) || Rake::FileUtilsExt.nowrite_flag
|
||||
|
||||
Rake.rake_output_message sh_show_command cmd if verbose
|
||||
|
||||
unless noop
|
||||
res = (Hash === cmd.last) ? system(*cmd) : system(*cmd, options)
|
||||
status = $?
|
||||
status = Rake::PseudoStatus.new(1) if !res && status.nil?
|
||||
shell_runner.call(res, status)
|
||||
end
|
||||
end
|
||||
|
||||
def create_shell_runner(cmd) # :nodoc:
|
||||
show_command = sh_show_command cmd
|
||||
lambda do |ok, status|
|
||||
ok or
|
||||
fail "Command failed with status (#{status.exitstatus}): " +
|
||||
"[#{show_command}]"
|
||||
end
|
||||
end
|
||||
private :create_shell_runner
|
||||
|
||||
def sh_show_command(cmd) # :nodoc:
|
||||
cmd = cmd.dup
|
||||
|
||||
if Hash === cmd.first
|
||||
env = cmd.first
|
||||
env = env.map { |name, value| "#{name}=#{value}" }.join " "
|
||||
cmd[0] = env
|
||||
end
|
||||
|
||||
cmd.join " "
|
||||
end
|
||||
private :sh_show_command
|
||||
|
||||
def set_verbose_option(options) # :nodoc:
|
||||
unless options.key? :verbose
|
||||
options[:verbose] =
|
||||
(Rake::FileUtilsExt.verbose_flag == Rake::FileUtilsExt::DEFAULT) ||
|
||||
Rake::FileUtilsExt.verbose_flag
|
||||
end
|
||||
end
|
||||
private :set_verbose_option
|
||||
|
||||
# Run a Ruby interpreter with the given arguments.
|
||||
#
|
||||
# Example:
|
||||
# ruby %{-pe '$_.upcase!' <README}
|
||||
#
|
||||
def ruby(*args, **options, &block)
|
||||
if args.length > 1
|
||||
sh(RUBY, *args, **options, &block)
|
||||
else
|
||||
sh("#{RUBY} #{args.first}", **options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
LN_SUPPORTED = [true]
|
||||
|
||||
# Attempt to do a normal file link, but fall back to a copy if the link
|
||||
# fails.
|
||||
def safe_ln(*args, **options)
|
||||
if LN_SUPPORTED[0]
|
||||
begin
|
||||
return options.empty? ? ln(*args) : ln(*args, **options)
|
||||
rescue StandardError, NotImplementedError
|
||||
LN_SUPPORTED[0] = false
|
||||
end
|
||||
end
|
||||
options.empty? ? cp(*args) : cp(*args, **options)
|
||||
end
|
||||
|
||||
# Split a file path into individual directory names.
|
||||
#
|
||||
# Example:
|
||||
# split_all("a/b/c") => ['a', 'b', 'c']
|
||||
#
|
||||
def split_all(path)
|
||||
head, tail = File.split(path)
|
||||
return [tail] if head == "." || tail == "/"
|
||||
return [head, tail] if head == "/"
|
||||
return split_all(head) + [tail]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,134 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake/file_utils"
|
||||
|
||||
module Rake
|
||||
#
|
||||
# FileUtilsExt provides a custom version of the FileUtils methods
|
||||
# that respond to the <tt>verbose</tt> and <tt>nowrite</tt>
|
||||
# commands.
|
||||
#
|
||||
module FileUtilsExt
|
||||
include FileUtils
|
||||
|
||||
class << self
|
||||
attr_accessor :verbose_flag, :nowrite_flag
|
||||
end
|
||||
|
||||
DEFAULT = Object.new
|
||||
|
||||
FileUtilsExt.verbose_flag = DEFAULT
|
||||
FileUtilsExt.nowrite_flag = false
|
||||
|
||||
FileUtils.commands.each do |name|
|
||||
opts = FileUtils.options_of name
|
||||
default_options = []
|
||||
if opts.include?("verbose")
|
||||
default_options << "verbose: FileUtilsExt.verbose_flag"
|
||||
end
|
||||
if opts.include?("noop")
|
||||
default_options << "noop: FileUtilsExt.nowrite_flag"
|
||||
end
|
||||
|
||||
next if default_options.empty?
|
||||
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
def #{name}(*args, **options, &block)
|
||||
super(*args,
|
||||
#{default_options.join(', ')},
|
||||
**options, &block)
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
# Get/set the verbose flag controlling output from the FileUtils
|
||||
# utilities. If verbose is true, then the utility method is
|
||||
# echoed to standard output.
|
||||
#
|
||||
# Examples:
|
||||
# verbose # return the current value of the
|
||||
# # verbose flag
|
||||
# verbose(v) # set the verbose flag to _v_.
|
||||
# verbose(v) { code } # Execute code with the verbose flag set
|
||||
# # temporarily to _v_. Return to the
|
||||
# # original value when code is done.
|
||||
def verbose(value=nil)
|
||||
oldvalue = FileUtilsExt.verbose_flag
|
||||
FileUtilsExt.verbose_flag = value unless value.nil?
|
||||
if block_given?
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
FileUtilsExt.verbose_flag = oldvalue
|
||||
end
|
||||
end
|
||||
FileUtilsExt.verbose_flag
|
||||
end
|
||||
|
||||
# Get/set the nowrite flag controlling output from the FileUtils
|
||||
# utilities. If verbose is true, then the utility method is
|
||||
# echoed to standard output.
|
||||
#
|
||||
# Examples:
|
||||
# nowrite # return the current value of the
|
||||
# # nowrite flag
|
||||
# nowrite(v) # set the nowrite flag to _v_.
|
||||
# nowrite(v) { code } # Execute code with the nowrite flag set
|
||||
# # temporarily to _v_. Return to the
|
||||
# # original value when code is done.
|
||||
def nowrite(value=nil)
|
||||
oldvalue = FileUtilsExt.nowrite_flag
|
||||
FileUtilsExt.nowrite_flag = value unless value.nil?
|
||||
if block_given?
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
FileUtilsExt.nowrite_flag = oldvalue
|
||||
end
|
||||
end
|
||||
oldvalue
|
||||
end
|
||||
|
||||
# Use this function to prevent potentially destructive ruby code
|
||||
# from running when the :nowrite flag is set.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# when_writing("Building Project") do
|
||||
# project.build
|
||||
# end
|
||||
#
|
||||
# The following code will build the project under normal
|
||||
# conditions. If the nowrite(true) flag is set, then the example
|
||||
# will print:
|
||||
#
|
||||
# DRYRUN: Building Project
|
||||
#
|
||||
# instead of actually building the project.
|
||||
#
|
||||
def when_writing(msg=nil)
|
||||
if FileUtilsExt.nowrite_flag
|
||||
$stderr.puts "DRYRUN: #{msg}" if msg
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
# Send the message to the default rake output (which is $stderr).
|
||||
def rake_output_message(message)
|
||||
$stderr.puts(message)
|
||||
end
|
||||
|
||||
# Check that the options do not contain options not listed in
|
||||
# +optdecl+. An ArgumentError exception is thrown if non-declared
|
||||
# options are found.
|
||||
def rake_check_options(options, *optdecl)
|
||||
h = options.dup
|
||||
optdecl.each do |name|
|
||||
h.delete name
|
||||
end
|
||||
raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless
|
||||
h.empty?
|
||||
end
|
||||
|
||||
extend self
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# InvocationChain tracks the chain of task invocations to detect
|
||||
# circular dependencies.
|
||||
class InvocationChain < LinkedList
|
||||
|
||||
# Is the invocation already in the chain?
|
||||
def member?(invocation)
|
||||
head == invocation || tail.member?(invocation)
|
||||
end
|
||||
|
||||
# Append an invocation to the chain of invocations. It is an error
|
||||
# if the invocation already listed.
|
||||
def append(invocation)
|
||||
if member?(invocation)
|
||||
fail RuntimeError, "Circular dependency detected: #{to_s} => #{invocation}"
|
||||
end
|
||||
conj(invocation)
|
||||
end
|
||||
|
||||
# Convert to string, ie: TOP => invocation => invocation
|
||||
def to_s
|
||||
"#{prefix}#{head}"
|
||||
end
|
||||
|
||||
# Class level append.
|
||||
def self.append(invocation, chain)
|
||||
chain.append(invocation)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prefix
|
||||
"#{tail} => "
|
||||
end
|
||||
|
||||
# Null object for an empty chain.
|
||||
class EmptyInvocationChain < LinkedList::EmptyLinkedList
|
||||
@parent = InvocationChain
|
||||
|
||||
def member?(obj)
|
||||
false
|
||||
end
|
||||
|
||||
def append(invocation)
|
||||
conj(invocation)
|
||||
end
|
||||
|
||||
def to_s
|
||||
"TOP"
|
||||
end
|
||||
end
|
||||
|
||||
EMPTY = EmptyInvocationChain.new
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
module InvocationExceptionMixin
|
||||
# Return the invocation chain (list of Rake tasks) that were in
|
||||
# effect when this exception was detected by rake. May be null if
|
||||
# no tasks were active.
|
||||
def chain
|
||||
@rake_invocation_chain ||= nil
|
||||
end
|
||||
|
||||
# Set the invocation chain in effect when this exception was
|
||||
# detected.
|
||||
def chain=(value)
|
||||
@rake_invocation_chain = value
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
# LateTime is a fake timestamp that occurs _after_ any other time value.
|
||||
class LateTime
|
||||
include Comparable
|
||||
include Singleton
|
||||
|
||||
def <=>(other)
|
||||
1
|
||||
end
|
||||
|
||||
def to_s
|
||||
"<LATE TIME>"
|
||||
end
|
||||
end
|
||||
|
||||
LATE = LateTime.instance
|
||||
end
|
||||
@@ -0,0 +1,112 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# Polylithic linked list structure used to implement several data
|
||||
# structures in Rake.
|
||||
class LinkedList
|
||||
include Enumerable
|
||||
attr_reader :head, :tail
|
||||
|
||||
# Polymorphically add a new element to the head of a list. The
|
||||
# type of head node will be the same list type as the tail.
|
||||
def conj(item)
|
||||
self.class.cons(item, self)
|
||||
end
|
||||
|
||||
# Is the list empty?
|
||||
# .make guards against a list being empty making any instantiated LinkedList
|
||||
# object not empty by default
|
||||
# You should consider overriding this method if you implement your own .make method
|
||||
def empty?
|
||||
false
|
||||
end
|
||||
|
||||
# Lists are structurally equivalent.
|
||||
def ==(other)
|
||||
current = self
|
||||
while !current.empty? && !other.empty?
|
||||
return false if current.head != other.head
|
||||
current = current.tail
|
||||
other = other.tail
|
||||
end
|
||||
current.empty? && other.empty?
|
||||
end
|
||||
|
||||
# Convert to string: LL(item, item...)
|
||||
def to_s
|
||||
items = map(&:to_s).join(", ")
|
||||
"LL(#{items})"
|
||||
end
|
||||
|
||||
# Same as +to_s+, but with inspected items.
|
||||
def inspect
|
||||
items = map(&:inspect).join(", ")
|
||||
"LL(#{items})"
|
||||
end
|
||||
|
||||
# For each item in the list.
|
||||
def each
|
||||
current = self
|
||||
while !current.empty?
|
||||
yield(current.head)
|
||||
current = current.tail
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# Make a list out of the given arguments. This method is
|
||||
# polymorphic
|
||||
def self.make(*args)
|
||||
# return an EmptyLinkedList if there are no arguments
|
||||
return empty if !args || args.empty?
|
||||
|
||||
# build a LinkedList by starting at the tail and iterating
|
||||
# through each argument
|
||||
# inject takes an EmptyLinkedList to start
|
||||
args.reverse.inject(empty) do |list, item|
|
||||
list = cons(item, list)
|
||||
list # return the newly created list for each item in the block
|
||||
end
|
||||
end
|
||||
|
||||
# Cons a new head onto the tail list.
|
||||
def self.cons(head, tail)
|
||||
new(head, tail)
|
||||
end
|
||||
|
||||
# The standard empty list class for the given LinkedList class.
|
||||
def self.empty
|
||||
self::EMPTY
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def initialize(head, tail=EMPTY)
|
||||
@head = head
|
||||
@tail = tail
|
||||
end
|
||||
|
||||
# Represent an empty list, using the Null Object Pattern.
|
||||
#
|
||||
# When inheriting from the LinkedList class, you should implement
|
||||
# a type specific Empty class as well. Make sure you set the class
|
||||
# instance variable @parent to the associated list class (this
|
||||
# allows conj, cons and make to work polymorphically).
|
||||
class EmptyLinkedList < LinkedList
|
||||
@parent = LinkedList
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
def empty?
|
||||
true
|
||||
end
|
||||
|
||||
def self.cons(head, tail)
|
||||
@parent.cons(head, tail)
|
||||
end
|
||||
end
|
||||
|
||||
EMPTY = EmptyLinkedList.new
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# Makefile loader to be used with the import file loader. Use this to
|
||||
# import dependencies from make dependency tools:
|
||||
#
|
||||
# require 'rake/loaders/makefile'
|
||||
#
|
||||
# file ".depends.mf" => [SRC_LIST] do |t|
|
||||
# sh "makedepend -f- -- #{CFLAGS} -- #{t.prerequisites} > #{t.name}"
|
||||
# end
|
||||
#
|
||||
# import ".depends.mf"
|
||||
#
|
||||
# See {Importing Dependencies}[link:doc/rakefile_rdoc.html#label-Importing+Dependencies]
|
||||
# for further details.
|
||||
|
||||
class MakefileLoader
|
||||
include Rake::DSL
|
||||
|
||||
SPACE_MARK = "\0" # :nodoc:
|
||||
|
||||
# Load the makefile dependencies in +fn+.
|
||||
def load(fn) # :nodoc:
|
||||
lines = File.read fn
|
||||
lines.gsub!(/\\ /, SPACE_MARK)
|
||||
lines.gsub!(/#[^\n]*\n/m, "")
|
||||
lines.gsub!(/\\\n/, " ")
|
||||
lines.each_line do |line|
|
||||
process_line(line)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Process one logical line of makefile data.
|
||||
def process_line(line) # :nodoc:
|
||||
file_tasks, args = line.split(":", 2)
|
||||
return if args.nil?
|
||||
dependents = args.split.map { |d| respace(d) }
|
||||
file_tasks.scan(/\S+/) do |file_task|
|
||||
file_task = respace(file_task)
|
||||
file file_task => dependents
|
||||
end
|
||||
end
|
||||
|
||||
def respace(str) # :nodoc:
|
||||
str.tr SPACE_MARK, " "
|
||||
end
|
||||
end
|
||||
|
||||
# Install the handler
|
||||
Rake.application.add_loader("mf", MakefileLoader.new)
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# Same as a regular task, but the immediate prerequisites are done in
|
||||
# parallel using Ruby threads.
|
||||
#
|
||||
class MultiTask < Task
|
||||
private
|
||||
|
||||
def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
|
||||
invoke_prerequisites_concurrently(task_args, invocation_chain)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
##
|
||||
# The NameSpace class will lookup task names in the scope defined by a
|
||||
# +namespace+ command.
|
||||
|
||||
class Rake::NameSpace
|
||||
|
||||
##
|
||||
# Create a namespace lookup object using the given task manager
|
||||
# and the list of scopes.
|
||||
|
||||
def initialize(task_manager, scope_list)
|
||||
@task_manager = task_manager
|
||||
@scope = scope_list.dup
|
||||
end
|
||||
|
||||
##
|
||||
# Lookup a task named +name+ in the namespace.
|
||||
|
||||
def [](name)
|
||||
@task_manager.lookup(name, @scope)
|
||||
end
|
||||
|
||||
##
|
||||
# The scope of the namespace (a LinkedList)
|
||||
|
||||
def scope
|
||||
@scope.dup
|
||||
end
|
||||
|
||||
##
|
||||
# Return the list of tasks defined in this and nested namespaces.
|
||||
|
||||
def tasks
|
||||
@task_manager.tasks_in_scope(@scope)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,222 @@
|
||||
# frozen_string_literal: true
|
||||
# Define a package task library to aid in the definition of
|
||||
# redistributable package files.
|
||||
|
||||
require "rake"
|
||||
require "rake/tasklib"
|
||||
|
||||
module Rake
|
||||
|
||||
# Create a packaging task that will package the project into
|
||||
# distributable files (e.g zip archive or tar files).
|
||||
#
|
||||
# The PackageTask will create the following targets:
|
||||
#
|
||||
# +:package+ ::
|
||||
# Create all the requested package files.
|
||||
#
|
||||
# +:clobber_package+ ::
|
||||
# Delete all the package files. This target is automatically
|
||||
# added to the main clobber target.
|
||||
#
|
||||
# +:repackage+ ::
|
||||
# Rebuild the package files from scratch, even if they are not out
|
||||
# of date.
|
||||
#
|
||||
# <tt>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tgz"</tt> ::
|
||||
# Create a gzipped tar package (if <em>need_tar</em> is true).
|
||||
#
|
||||
# <tt>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tar.gz"</tt> ::
|
||||
# Create a gzipped tar package (if <em>need_tar_gz</em> is true).
|
||||
#
|
||||
# <tt>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tar.bz2"</tt> ::
|
||||
# Create a bzip2'd tar package (if <em>need_tar_bz2</em> is true).
|
||||
#
|
||||
# <tt>"<em>package_dir</em>/<em>name</em>-<em>version</em>.zip"</tt> ::
|
||||
# Create a zip package archive (if <em>need_zip</em> is true).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Rake::PackageTask.new("rake", "1.2.3") do |p|
|
||||
# p.need_tar = true
|
||||
# p.package_files.include("lib/**/*.rb")
|
||||
# end
|
||||
#
|
||||
class PackageTask < TaskLib
|
||||
# Name of the package (from the GEM Spec).
|
||||
attr_accessor :name
|
||||
|
||||
# Version of the package (e.g. '1.3.2').
|
||||
attr_accessor :version
|
||||
|
||||
# Directory used to store the package files (default is 'pkg').
|
||||
attr_accessor :package_dir
|
||||
|
||||
# True if a gzipped tar file (tgz) should be produced (default is
|
||||
# false).
|
||||
attr_accessor :need_tar
|
||||
|
||||
# True if a gzipped tar file (tar.gz) should be produced (default
|
||||
# is false).
|
||||
attr_accessor :need_tar_gz
|
||||
|
||||
# True if a bzip2'd tar file (tar.bz2) should be produced (default
|
||||
# is false).
|
||||
attr_accessor :need_tar_bz2
|
||||
|
||||
# True if a xz'd tar file (tar.xz) should be produced (default is false)
|
||||
attr_accessor :need_tar_xz
|
||||
|
||||
# True if a zip file should be produced (default is false)
|
||||
attr_accessor :need_zip
|
||||
|
||||
# List of files to be included in the package.
|
||||
attr_accessor :package_files
|
||||
|
||||
# Tar command for gzipped or bzip2ed archives. The default is 'tar'.
|
||||
attr_accessor :tar_command
|
||||
|
||||
# Zip command for zipped archives. The default is 'zip'.
|
||||
attr_accessor :zip_command
|
||||
|
||||
# True if parent directory should be omitted (default is false)
|
||||
attr_accessor :without_parent_dir
|
||||
|
||||
# Create a Package Task with the given name and version. Use +:noversion+
|
||||
# as the version to build a package without a version or to provide a
|
||||
# fully-versioned package name.
|
||||
|
||||
def initialize(name=nil, version=nil)
|
||||
init(name, version)
|
||||
yield self if block_given?
|
||||
define unless name.nil?
|
||||
end
|
||||
|
||||
# Initialization that bypasses the "yield self" and "define" step.
|
||||
def init(name, version)
|
||||
@name = name
|
||||
@version = version
|
||||
@package_files = Rake::FileList.new
|
||||
@package_dir = "pkg"
|
||||
@need_tar = false
|
||||
@need_tar_gz = false
|
||||
@need_tar_bz2 = false
|
||||
@need_tar_xz = false
|
||||
@need_zip = false
|
||||
@tar_command = "tar"
|
||||
@zip_command = "zip"
|
||||
@without_parent_dir = false
|
||||
end
|
||||
|
||||
# Create the tasks defined by this task library.
|
||||
def define
|
||||
fail "Version required (or :noversion)" if @version.nil?
|
||||
@version = nil if :noversion == @version
|
||||
|
||||
desc "Build all the packages"
|
||||
task :package
|
||||
|
||||
desc "Force a rebuild of the package files"
|
||||
task repackage: [:clobber_package, :package]
|
||||
|
||||
desc "Remove package products"
|
||||
task :clobber_package do
|
||||
rm_r package_dir rescue nil
|
||||
end
|
||||
|
||||
task clobber: [:clobber_package]
|
||||
|
||||
[
|
||||
[need_tar, tgz_file, "z"],
|
||||
[need_tar_gz, tar_gz_file, "z"],
|
||||
[need_tar_bz2, tar_bz2_file, "j"],
|
||||
[need_tar_xz, tar_xz_file, "J"]
|
||||
].each do |need, file, flag|
|
||||
if need
|
||||
task package: ["#{package_dir}/#{file}"]
|
||||
file "#{package_dir}/#{file}" =>
|
||||
[package_dir_path] + package_files do
|
||||
chdir(working_dir) { sh @tar_command, "#{flag}cvf", file, target_dir }
|
||||
mv "#{package_dir_path}/#{target_dir}", package_dir if without_parent_dir
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if need_zip
|
||||
task package: ["#{package_dir}/#{zip_file}"]
|
||||
file "#{package_dir}/#{zip_file}" =>
|
||||
[package_dir_path] + package_files do
|
||||
chdir(working_dir) { sh @zip_command, "-r", zip_file, target_dir }
|
||||
mv "#{package_dir_path}/#{zip_file}", package_dir if without_parent_dir
|
||||
end
|
||||
end
|
||||
|
||||
directory package_dir_path => @package_files do
|
||||
@package_files.each do |fn|
|
||||
f = File.join(package_dir_path, fn)
|
||||
fdir = File.dirname(f)
|
||||
mkdir_p(fdir) unless File.exist?(fdir)
|
||||
if File.directory?(fn)
|
||||
mkdir_p(f)
|
||||
else
|
||||
rm_f f
|
||||
safe_ln(fn, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# The name of this package
|
||||
|
||||
def package_name
|
||||
@version ? "#{@name}-#{@version}" : @name
|
||||
end
|
||||
|
||||
# The directory this package will be built in
|
||||
|
||||
def package_dir_path
|
||||
"#{package_dir}/#{package_name}"
|
||||
end
|
||||
|
||||
# The package name with .tgz added
|
||||
|
||||
def tgz_file
|
||||
"#{package_name}.tgz"
|
||||
end
|
||||
|
||||
# The package name with .tar.gz added
|
||||
|
||||
def tar_gz_file
|
||||
"#{package_name}.tar.gz"
|
||||
end
|
||||
|
||||
# The package name with .tar.bz2 added
|
||||
|
||||
def tar_bz2_file
|
||||
"#{package_name}.tar.bz2"
|
||||
end
|
||||
|
||||
# The package name with .tar.xz added
|
||||
|
||||
def tar_xz_file
|
||||
"#{package_name}.tar.xz"
|
||||
end
|
||||
|
||||
# The package name with .zip added
|
||||
|
||||
def zip_file
|
||||
"#{package_name}.zip"
|
||||
end
|
||||
|
||||
def working_dir
|
||||
without_parent_dir ? package_dir_path : package_dir
|
||||
end
|
||||
|
||||
# target directory relative to working_dir
|
||||
def target_dir
|
||||
without_parent_dir ? "." : package_name
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
# Defines a :phony task that you can use as a dependency. This allows
|
||||
# file-based tasks to use non-file-based tasks as prerequisites
|
||||
# without forcing them to rebuild.
|
||||
#
|
||||
# See FileTask#out_of_date? and Task#timestamp for more info.
|
||||
|
||||
require "rake"
|
||||
|
||||
task :phony
|
||||
|
||||
Rake::Task[:phony].tap do |task|
|
||||
def task.timestamp # :nodoc:
|
||||
Time.at 0
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# Include PrivateReader to use +private_reader+.
|
||||
module PrivateReader # :nodoc: all
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Declare a list of private accessors
|
||||
def private_reader(*names)
|
||||
attr_reader(*names)
|
||||
private(*names)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# A Promise object represents a promise to do work (a chore) in the
|
||||
# future. The promise is created with a block and a list of
|
||||
# arguments for the block. Calling value will return the value of
|
||||
# the promised chore.
|
||||
#
|
||||
# Used by ThreadPool.
|
||||
#
|
||||
class Promise # :nodoc: all
|
||||
NOT_SET = Object.new.freeze # :nodoc:
|
||||
|
||||
attr_accessor :recorder
|
||||
|
||||
# Create a promise to do the chore specified by the block.
|
||||
def initialize(args, &block)
|
||||
@mutex = Mutex.new
|
||||
@result = NOT_SET
|
||||
@error = NOT_SET
|
||||
@args = args
|
||||
@block = block
|
||||
end
|
||||
|
||||
# Return the value of this promise.
|
||||
#
|
||||
# If the promised chore is not yet complete, then do the work
|
||||
# synchronously. We will wait.
|
||||
def value
|
||||
unless complete?
|
||||
stat :sleeping_on, item_id: object_id
|
||||
@mutex.synchronize do
|
||||
stat :has_lock_on, item_id: object_id
|
||||
chore
|
||||
stat :releasing_lock_on, item_id: object_id
|
||||
end
|
||||
end
|
||||
error? ? raise(@error) : @result
|
||||
end
|
||||
|
||||
# If no one else is working this promise, go ahead and do the chore.
|
||||
def work
|
||||
stat :attempting_lock_on, item_id: object_id
|
||||
if @mutex.try_lock
|
||||
stat :has_lock_on, item_id: object_id
|
||||
chore
|
||||
stat :releasing_lock_on, item_id: object_id
|
||||
@mutex.unlock
|
||||
else
|
||||
stat :bailed_on, item_id: object_id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Perform the chore promised
|
||||
def chore
|
||||
if complete?
|
||||
stat :found_completed, item_id: object_id
|
||||
return
|
||||
end
|
||||
stat :will_execute, item_id: object_id
|
||||
begin
|
||||
@result = @block.call(*@args)
|
||||
rescue Exception => e
|
||||
@error = e
|
||||
end
|
||||
stat :did_execute, item_id: object_id
|
||||
discard
|
||||
end
|
||||
|
||||
# Do we have a result for the promise
|
||||
def result?
|
||||
!@result.equal?(NOT_SET)
|
||||
end
|
||||
|
||||
# Did the promise throw an error
|
||||
def error?
|
||||
!@error.equal?(NOT_SET)
|
||||
end
|
||||
|
||||
# Are we done with the promise
|
||||
def complete?
|
||||
result? || error?
|
||||
end
|
||||
|
||||
# free up these items for the GC
|
||||
def discard
|
||||
@args = nil
|
||||
@block = nil
|
||||
end
|
||||
|
||||
# Record execution statistics if there is a recorder
|
||||
def stat(*args)
|
||||
@recorder.call(*args) if @recorder
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
##
|
||||
# Exit status class for times the system just gives us a nil.
|
||||
class PseudoStatus # :nodoc: all
|
||||
attr_reader :exitstatus
|
||||
|
||||
def initialize(code=0)
|
||||
@exitstatus = code
|
||||
end
|
||||
|
||||
def to_i
|
||||
@exitstatus << 8
|
||||
end
|
||||
|
||||
def >>(n)
|
||||
to_i >> n
|
||||
end
|
||||
|
||||
def stopped?
|
||||
false
|
||||
end
|
||||
|
||||
def exited?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake/application"
|
||||
|
||||
module Rake
|
||||
|
||||
class << self
|
||||
# Current Rake Application
|
||||
def application
|
||||
@application ||= Rake::Application.new
|
||||
end
|
||||
|
||||
# Set the current Rake application object.
|
||||
def application=(app)
|
||||
@application = app
|
||||
end
|
||||
|
||||
def suggested_thread_count # :nodoc:
|
||||
@cpu_count ||= Rake::CpuCounter.count
|
||||
@cpu_count + 4
|
||||
end
|
||||
|
||||
# Return the original directory where the Rake application was started.
|
||||
def original_dir
|
||||
application.original_dir
|
||||
end
|
||||
|
||||
# Load a rakefile.
|
||||
def load_rakefile(path)
|
||||
load(path)
|
||||
end
|
||||
|
||||
# Add files to the rakelib list
|
||||
def add_rakelib(*files)
|
||||
application.options.rakelib ||= []
|
||||
application.options.rakelib.concat(files)
|
||||
end
|
||||
|
||||
# Make +block_application+ the default rake application inside a block so
|
||||
# you can load rakefiles into a different application.
|
||||
#
|
||||
# This is useful when you want to run rake tasks inside a library without
|
||||
# running rake in a sub-shell.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Dir.chdir 'other/directory'
|
||||
#
|
||||
# other_rake = Rake.with_application do |rake|
|
||||
# rake.load_rakefile
|
||||
# end
|
||||
#
|
||||
# puts other_rake.tasks
|
||||
|
||||
def with_application(block_application = Rake::Application.new)
|
||||
orig_application = Rake.application
|
||||
|
||||
Rake.application = block_application
|
||||
|
||||
yield block_application
|
||||
|
||||
block_application
|
||||
ensure
|
||||
Rake.application = orig_application
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rake/file_list"
|
||||
|
||||
# Load the test files from the command line.
|
||||
argv = ARGV.select do |argument|
|
||||
case argument
|
||||
when /^-/ then
|
||||
argument
|
||||
when /\*/ then
|
||||
Rake::FileList[argument].to_a.each do |file|
|
||||
require File.expand_path file
|
||||
end
|
||||
|
||||
false
|
||||
else
|
||||
path = File.expand_path argument
|
||||
|
||||
abort "\nFile does not exist: #{path}\n\n" unless File.exist?(path)
|
||||
|
||||
require path
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
ARGV.replace argv
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# Error indicating a recursion overflow error in task selection.
|
||||
class RuleRecursionOverflowError < StandardError
|
||||
def initialize(*args)
|
||||
super
|
||||
@targets = []
|
||||
end
|
||||
|
||||
def add_target(target)
|
||||
@targets << target
|
||||
end
|
||||
|
||||
def message
|
||||
super + ": [" + @targets.reverse.join(" => ") + "]"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
class Scope < LinkedList # :nodoc: all
|
||||
|
||||
# Path for the scope.
|
||||
def path
|
||||
map(&:to_s).reverse.join(":")
|
||||
end
|
||||
|
||||
# Path for the scope + the named path.
|
||||
def path_with_task_name(task_name)
|
||||
"#{path}:#{task_name}"
|
||||
end
|
||||
|
||||
# Trim +n+ innermost scope levels from the scope. In no case will
|
||||
# this trim beyond the toplevel scope.
|
||||
def trim(n)
|
||||
result = self
|
||||
while n > 0 && !result.empty?
|
||||
result = result.tail
|
||||
n -= 1
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Scope lists always end with an EmptyScope object. See Null
|
||||
# Object Pattern)
|
||||
class EmptyScope < EmptyLinkedList
|
||||
@parent = Scope
|
||||
|
||||
def path
|
||||
""
|
||||
end
|
||||
|
||||
def path_with_task_name(task_name)
|
||||
task_name
|
||||
end
|
||||
end
|
||||
|
||||
# Singleton null object for an empty scope.
|
||||
EMPTY = EmptyScope.new
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,434 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake/invocation_exception_mixin"
|
||||
|
||||
module Rake
|
||||
|
||||
##
|
||||
# A Task is the basic unit of work in a Rakefile. Tasks have associated
|
||||
# actions (possibly more than one) and a list of prerequisites. When
|
||||
# invoked, a task will first ensure that all of its prerequisites have an
|
||||
# opportunity to run and then it will execute its own actions.
|
||||
#
|
||||
# Tasks are not usually created directly using the new method, but rather
|
||||
# use the +file+ and +task+ convenience methods.
|
||||
#
|
||||
class Task
|
||||
# List of prerequisites for a task.
|
||||
attr_reader :prerequisites
|
||||
alias prereqs prerequisites
|
||||
|
||||
# List of order only prerequisites for a task.
|
||||
attr_reader :order_only_prerequisites
|
||||
|
||||
# List of actions attached to a task.
|
||||
attr_reader :actions
|
||||
|
||||
# Application owning this task.
|
||||
attr_accessor :application
|
||||
|
||||
# Array of nested namespaces names used for task lookup by this task.
|
||||
attr_reader :scope
|
||||
|
||||
# File/Line locations of each of the task definitions for this
|
||||
# task (only valid if the task was defined with the detect
|
||||
# location option set).
|
||||
attr_reader :locations
|
||||
|
||||
# Has this task already been invoked? Already invoked tasks
|
||||
# will be skipped unless you reenable them.
|
||||
attr_reader :already_invoked
|
||||
|
||||
# Return task name
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
def inspect # :nodoc:
|
||||
"<#{self.class} #{name} => [#{prerequisites.join(', ')}]>"
|
||||
end
|
||||
|
||||
# List of sources for task.
|
||||
attr_writer :sources
|
||||
def sources
|
||||
if defined?(@sources)
|
||||
@sources
|
||||
else
|
||||
prerequisites
|
||||
end
|
||||
end
|
||||
|
||||
# List of prerequisite tasks
|
||||
def prerequisite_tasks
|
||||
(prerequisites + order_only_prerequisites).map { |pre| lookup_prerequisite(pre) }
|
||||
end
|
||||
|
||||
def lookup_prerequisite(prerequisite_name) # :nodoc:
|
||||
scoped_prerequisite_task = application[prerequisite_name, @scope]
|
||||
if scoped_prerequisite_task == self
|
||||
unscoped_prerequisite_task = application[prerequisite_name]
|
||||
end
|
||||
unscoped_prerequisite_task || scoped_prerequisite_task
|
||||
end
|
||||
private :lookup_prerequisite
|
||||
|
||||
# List of all unique prerequisite tasks including prerequisite tasks'
|
||||
# prerequisites.
|
||||
# Includes self when cyclic dependencies are found.
|
||||
def all_prerequisite_tasks
|
||||
seen = {}
|
||||
collect_prerequisites(seen)
|
||||
seen.values
|
||||
end
|
||||
|
||||
def collect_prerequisites(seen) # :nodoc:
|
||||
prerequisite_tasks.each do |pre|
|
||||
next if seen[pre.name]
|
||||
seen[pre.name] = pre
|
||||
pre.collect_prerequisites(seen)
|
||||
end
|
||||
end
|
||||
protected :collect_prerequisites
|
||||
|
||||
# First source from a rule (nil if no sources)
|
||||
def source
|
||||
sources.first
|
||||
end
|
||||
|
||||
# Create a task named +task_name+ with no actions or prerequisites. Use
|
||||
# +enhance+ to add actions and prerequisites.
|
||||
def initialize(task_name, app)
|
||||
@name = task_name.to_s
|
||||
@prerequisites = []
|
||||
@actions = []
|
||||
@already_invoked = false
|
||||
@comments = []
|
||||
@lock = Monitor.new
|
||||
@application = app
|
||||
@scope = app.current_scope
|
||||
@arg_names = nil
|
||||
@locations = []
|
||||
@invocation_exception = nil
|
||||
@order_only_prerequisites = []
|
||||
end
|
||||
|
||||
# Enhance a task with prerequisites or actions. Returns self.
|
||||
def enhance(deps=nil, &block)
|
||||
@prerequisites |= deps if deps
|
||||
@actions << block if block_given?
|
||||
self
|
||||
end
|
||||
|
||||
# Name of the task, including any namespace qualifiers.
|
||||
def name
|
||||
@name.to_s
|
||||
end
|
||||
|
||||
# Name of task with argument list description.
|
||||
def name_with_args # :nodoc:
|
||||
if arg_description
|
||||
"#{name}#{arg_description}"
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
|
||||
# Argument description (nil if none).
|
||||
def arg_description # :nodoc:
|
||||
@arg_names ? "[#{arg_names.join(',')}]" : nil
|
||||
end
|
||||
|
||||
# Name of arguments for this task.
|
||||
def arg_names
|
||||
@arg_names || []
|
||||
end
|
||||
|
||||
# Reenable the task, allowing its tasks to be executed if the task
|
||||
# is invoked again.
|
||||
def reenable
|
||||
@already_invoked = false
|
||||
@invocation_exception = nil
|
||||
end
|
||||
|
||||
# Clear the existing prerequisites, actions, comments, and arguments of a rake task.
|
||||
def clear
|
||||
clear_prerequisites
|
||||
clear_actions
|
||||
clear_comments
|
||||
clear_args
|
||||
self
|
||||
end
|
||||
|
||||
# Clear the existing prerequisites of a rake task.
|
||||
def clear_prerequisites
|
||||
prerequisites.clear
|
||||
self
|
||||
end
|
||||
|
||||
# Clear the existing actions on a rake task.
|
||||
def clear_actions
|
||||
actions.clear
|
||||
self
|
||||
end
|
||||
|
||||
# Clear the existing comments on a rake task.
|
||||
def clear_comments
|
||||
@comments = []
|
||||
self
|
||||
end
|
||||
|
||||
# Clear the existing arguments on a rake task.
|
||||
def clear_args
|
||||
@arg_names = nil
|
||||
self
|
||||
end
|
||||
|
||||
# Invoke the task if it is needed. Prerequisites are invoked first.
|
||||
def invoke(*args)
|
||||
task_args = TaskArguments.new(arg_names, args)
|
||||
invoke_with_call_chain(task_args, InvocationChain::EMPTY)
|
||||
end
|
||||
|
||||
# Same as invoke, but explicitly pass a call chain to detect
|
||||
# circular dependencies.
|
||||
#
|
||||
# If multiple tasks depend on this
|
||||
# one in parallel, they will all fail if the first execution of
|
||||
# this task fails.
|
||||
def invoke_with_call_chain(task_args, invocation_chain)
|
||||
new_chain = Rake::InvocationChain.append(self, invocation_chain)
|
||||
@lock.synchronize do
|
||||
begin
|
||||
if application.options.trace
|
||||
application.trace "** Invoke #{name} #{format_trace_flags}"
|
||||
end
|
||||
|
||||
if @already_invoked
|
||||
if @invocation_exception
|
||||
if application.options.trace
|
||||
application.trace "** Previous invocation of #{name} failed #{format_trace_flags}"
|
||||
end
|
||||
raise @invocation_exception
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
@already_invoked = true
|
||||
|
||||
invoke_prerequisites(task_args, new_chain)
|
||||
execute(task_args) if needed?
|
||||
rescue Exception => ex
|
||||
add_chain_to(ex, new_chain)
|
||||
@invocation_exception = ex
|
||||
raise ex
|
||||
end
|
||||
end
|
||||
end
|
||||
protected :invoke_with_call_chain
|
||||
|
||||
def add_chain_to(exception, new_chain) # :nodoc:
|
||||
exception.extend(InvocationExceptionMixin) unless
|
||||
exception.respond_to?(:chain)
|
||||
exception.chain = new_chain if exception.chain.nil?
|
||||
end
|
||||
private :add_chain_to
|
||||
|
||||
# Invoke all the prerequisites of a task.
|
||||
def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
|
||||
if application.options.always_multitask
|
||||
invoke_prerequisites_concurrently(task_args, invocation_chain)
|
||||
else
|
||||
prerequisite_tasks.each { |p|
|
||||
prereq_args = task_args.new_scope(p.arg_names)
|
||||
p.invoke_with_call_chain(prereq_args, invocation_chain)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Invoke all the prerequisites of a task in parallel.
|
||||
def invoke_prerequisites_concurrently(task_args, invocation_chain)# :nodoc:
|
||||
futures = prerequisite_tasks.map do |p|
|
||||
prereq_args = task_args.new_scope(p.arg_names)
|
||||
application.thread_pool.future(p) do |r|
|
||||
r.invoke_with_call_chain(prereq_args, invocation_chain)
|
||||
end
|
||||
end
|
||||
# Iterate in reverse to improve performance related to thread waiting and switching
|
||||
futures.reverse_each(&:value)
|
||||
end
|
||||
|
||||
# Format the trace flags for display.
|
||||
def format_trace_flags
|
||||
flags = []
|
||||
flags << "first_time" unless @already_invoked
|
||||
flags << "not_needed" unless needed?
|
||||
flags.empty? ? "" : "(" + flags.join(", ") + ")"
|
||||
end
|
||||
private :format_trace_flags
|
||||
|
||||
# Execute the actions associated with this task.
|
||||
def execute(args=nil)
|
||||
args ||= EMPTY_TASK_ARGS
|
||||
if application.options.dryrun
|
||||
application.trace "** Execute (dry run) #{name}"
|
||||
return
|
||||
end
|
||||
application.trace "** Execute #{name}" if application.options.trace
|
||||
application.enhance_with_matching_rule(name) if @actions.empty?
|
||||
if opts = Hash.try_convert(args) and !opts.empty?
|
||||
@actions.each { |act| act.call(self, args, **opts) }
|
||||
else
|
||||
@actions.each { |act| act.call(self, args) }
|
||||
end
|
||||
end
|
||||
|
||||
# Is this task needed?
|
||||
def needed?
|
||||
true
|
||||
end
|
||||
|
||||
# Timestamp for this task. Basic tasks return the current time for their
|
||||
# time stamp. Other tasks can be more sophisticated.
|
||||
def timestamp
|
||||
Time.now
|
||||
end
|
||||
|
||||
# Add a description to the task. The description can consist of an option
|
||||
# argument list (enclosed brackets) and an optional comment.
|
||||
def add_description(description)
|
||||
return unless description
|
||||
comment = description.strip
|
||||
add_comment(comment) if comment && !comment.empty?
|
||||
end
|
||||
|
||||
def comment=(comment) # :nodoc:
|
||||
add_comment(comment)
|
||||
end
|
||||
|
||||
def add_comment(comment) # :nodoc:
|
||||
return if comment.nil?
|
||||
@comments << comment unless @comments.include?(comment)
|
||||
end
|
||||
private :add_comment
|
||||
|
||||
# Full collection of comments. Multiple comments are separated by
|
||||
# newlines.
|
||||
def full_comment
|
||||
transform_comments("\n")
|
||||
end
|
||||
|
||||
# First line (or sentence) of all comments. Multiple comments are
|
||||
# separated by a "/".
|
||||
def comment
|
||||
transform_comments(" / ") { |c| first_sentence(c) }
|
||||
end
|
||||
|
||||
# Transform the list of comments as specified by the block and
|
||||
# join with the separator.
|
||||
def transform_comments(separator, &block)
|
||||
if @comments.empty?
|
||||
nil
|
||||
else
|
||||
block ||= lambda { |c| c }
|
||||
@comments.map(&block).join(separator)
|
||||
end
|
||||
end
|
||||
private :transform_comments
|
||||
|
||||
# Get the first sentence in a string. The sentence is terminated
|
||||
# by the first period, exclamation mark, or the end of the line.
|
||||
# Decimal points do not count as periods.
|
||||
def first_sentence(string)
|
||||
string.split(/(?<=\w)(\.|!)[ \t]|(\.$|!)|\n/).first
|
||||
end
|
||||
private :first_sentence
|
||||
|
||||
# Set the names of the arguments for this task. +args+ should be
|
||||
# an array of symbols, one for each argument name.
|
||||
def set_arg_names(args)
|
||||
@arg_names = args.map(&:to_sym)
|
||||
end
|
||||
|
||||
# Return a string describing the internal state of a task. Useful for
|
||||
# debugging.
|
||||
def investigation
|
||||
result = "------------------------------\n".dup
|
||||
result << "Investigating #{name}\n"
|
||||
result << "class: #{self.class}\n"
|
||||
result << "task needed: #{needed?}\n"
|
||||
result << "timestamp: #{timestamp}\n"
|
||||
result << "pre-requisites: \n"
|
||||
prereqs = prerequisite_tasks
|
||||
prereqs.sort! { |a, b| a.timestamp <=> b.timestamp }
|
||||
prereqs.each do |p|
|
||||
result << "--#{p.name} (#{p.timestamp})\n"
|
||||
end
|
||||
latest_prereq = prerequisite_tasks.map(&:timestamp).max
|
||||
result << "latest-prerequisite time: #{latest_prereq}\n"
|
||||
result << "................................\n\n"
|
||||
return result
|
||||
end
|
||||
|
||||
# Format dependencies parameter to pass to task.
|
||||
def self.format_deps(deps)
|
||||
deps = [deps] unless deps.respond_to?(:to_ary)
|
||||
deps.map { |d| Rake.from_pathname(d).to_s }
|
||||
end
|
||||
|
||||
# Add order only dependencies.
|
||||
def |(deps)
|
||||
@order_only_prerequisites |= Task.format_deps(deps) - @prerequisites
|
||||
self
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Rake Module Methods
|
||||
#
|
||||
class << self
|
||||
|
||||
# Clear the task list. This cause rake to immediately forget all the
|
||||
# tasks that have been assigned. (Normally used in the unit tests.)
|
||||
def clear
|
||||
Rake.application.clear
|
||||
end
|
||||
|
||||
# List of all defined tasks.
|
||||
def tasks
|
||||
Rake.application.tasks
|
||||
end
|
||||
|
||||
# Return a task with the given name. If the task is not currently
|
||||
# known, try to synthesize one from the defined rules. If no rules are
|
||||
# found, but an existing file matches the task name, assume it is a file
|
||||
# task with no dependencies or actions.
|
||||
def [](task_name)
|
||||
Rake.application[task_name]
|
||||
end
|
||||
|
||||
# TRUE if the task name is already defined.
|
||||
def task_defined?(task_name)
|
||||
Rake.application.lookup(task_name) != nil
|
||||
end
|
||||
|
||||
# Define a task given +args+ and an option block. If a rule with the
|
||||
# given name already exists, the prerequisites and actions are added to
|
||||
# the existing task. Returns the defined task.
|
||||
def define_task(*args, &block)
|
||||
Rake.application.define_task(self, *args, &block)
|
||||
end
|
||||
|
||||
# Define a rule for synthesizing tasks.
|
||||
def create_rule(*args, &block)
|
||||
Rake.application.create_rule(*args, &block)
|
||||
end
|
||||
|
||||
# Apply the scope to the task name according to the rules for
|
||||
# this kind of task. Generic tasks will accept the scope as
|
||||
# part of the name.
|
||||
def scope_name(scope, task_name)
|
||||
scope.path_with_task_name(task_name)
|
||||
end
|
||||
|
||||
end # class << Rake::Task
|
||||
end # class Rake::Task
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# Error indicating an ill-formed task declaration.
|
||||
class TaskArgumentError < ArgumentError
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,109 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
##
|
||||
# TaskArguments manage the arguments passed to a task.
|
||||
#
|
||||
class TaskArguments
|
||||
include Enumerable
|
||||
|
||||
# Argument names
|
||||
attr_reader :names
|
||||
|
||||
# Create a TaskArgument object with a list of argument +names+ and a set
|
||||
# of associated +values+. +parent+ is the parent argument object.
|
||||
def initialize(names, values, parent=nil)
|
||||
@names = names
|
||||
@parent = parent
|
||||
@hash = {}
|
||||
@values = values
|
||||
names.each_with_index { |name, i|
|
||||
next if values[i].nil? || values[i] == ""
|
||||
@hash[name.to_sym] = values[i]
|
||||
}
|
||||
end
|
||||
|
||||
# Retrieve the complete array of sequential values
|
||||
def to_a
|
||||
@values.dup
|
||||
end
|
||||
|
||||
# Retrieve the list of values not associated with named arguments
|
||||
def extras
|
||||
@values[@names.length..-1] || []
|
||||
end
|
||||
|
||||
# Create a new argument scope using the prerequisite argument
|
||||
# names.
|
||||
def new_scope(names)
|
||||
values = names.map { |n| self[n] }
|
||||
self.class.new(names, values + extras, self)
|
||||
end
|
||||
|
||||
# Find an argument value by name or index.
|
||||
def [](index)
|
||||
lookup(index.to_sym)
|
||||
end
|
||||
|
||||
# Specify a hash of default values for task arguments. Use the
|
||||
# defaults only if there is no specific value for the given
|
||||
# argument.
|
||||
def with_defaults(defaults)
|
||||
@hash = defaults.merge(@hash)
|
||||
end
|
||||
|
||||
# Enumerates the arguments and their values
|
||||
def each(&block)
|
||||
@hash.each(&block)
|
||||
end
|
||||
|
||||
# Extracts the argument values at +keys+
|
||||
def values_at(*keys)
|
||||
keys.map { |k| lookup(k) }
|
||||
end
|
||||
|
||||
# Returns the value of the given argument via method_missing
|
||||
def method_missing(sym, *args)
|
||||
lookup(sym.to_sym)
|
||||
end
|
||||
|
||||
# Returns a Hash of arguments and their values
|
||||
def to_hash
|
||||
@hash.dup
|
||||
end
|
||||
|
||||
def to_s # :nodoc:
|
||||
inspect
|
||||
end
|
||||
|
||||
def inspect # :nodoc:
|
||||
inspection = @hash.map do |k,v|
|
||||
"#{k.to_s}: #{v.to_s}"
|
||||
end.join(", ")
|
||||
|
||||
"#<#{self.class} #{inspection}>"
|
||||
end
|
||||
|
||||
# Returns true if +key+ is one of the arguments
|
||||
def has_key?(key)
|
||||
@hash.has_key?(key)
|
||||
end
|
||||
alias key? has_key?
|
||||
|
||||
def fetch(*args, &block)
|
||||
@hash.fetch(*args, &block)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def lookup(name) # :nodoc:
|
||||
if @hash.has_key?(name)
|
||||
@hash[name]
|
||||
elsif @parent
|
||||
@parent.lookup(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EMPTY_TASK_ARGS = TaskArguments.new([], []) # :nodoc:
|
||||
end
|
||||
@@ -0,0 +1,331 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
|
||||
# The TaskManager module is a mixin for managing tasks.
|
||||
module TaskManager
|
||||
# Track the last comment made in the Rakefile.
|
||||
attr_accessor :last_description
|
||||
|
||||
def initialize # :nodoc:
|
||||
super
|
||||
@tasks = Hash.new
|
||||
@rules = Array.new
|
||||
@scope = Scope.make
|
||||
@last_description = nil
|
||||
end
|
||||
|
||||
def create_rule(*args, &block) # :nodoc:
|
||||
pattern, args, deps, order_only = resolve_args(args)
|
||||
pattern = Regexp.new(Regexp.quote(pattern) + "$") if String === pattern
|
||||
@rules << [pattern, args, deps, order_only, block]
|
||||
end
|
||||
|
||||
def define_task(task_class, *args, &block) # :nodoc:
|
||||
task_name, arg_names, deps, order_only = resolve_args(args)
|
||||
|
||||
original_scope = @scope
|
||||
if String === task_name and
|
||||
not task_class.ancestors.include? Rake::FileTask
|
||||
task_name, *definition_scope = *(task_name.split(":").reverse)
|
||||
@scope = Scope.make(*(definition_scope + @scope.to_a))
|
||||
end
|
||||
|
||||
task_name = task_class.scope_name(@scope, task_name)
|
||||
task = intern(task_class, task_name)
|
||||
task.set_arg_names(arg_names) unless arg_names.empty?
|
||||
if Rake::TaskManager.record_task_metadata
|
||||
add_location(task)
|
||||
task.add_description(get_description(task))
|
||||
end
|
||||
task.enhance(Task.format_deps(deps), &block)
|
||||
task | order_only unless order_only.nil?
|
||||
task
|
||||
ensure
|
||||
@scope = original_scope
|
||||
end
|
||||
|
||||
# Lookup a task. Return an existing task if found, otherwise
|
||||
# create a task of the current type.
|
||||
def intern(task_class, task_name)
|
||||
@tasks[task_name.to_s] ||= task_class.new(task_name, self)
|
||||
end
|
||||
|
||||
# Find a matching task for +task_name+.
|
||||
def [](task_name, scopes=nil)
|
||||
task_name = task_name.to_s
|
||||
self.lookup(task_name, scopes) or
|
||||
enhance_with_matching_rule(task_name) or
|
||||
synthesize_file_task(task_name) or
|
||||
fail generate_message_for_undefined_task(task_name)
|
||||
end
|
||||
|
||||
def generate_message_for_undefined_task(task_name)
|
||||
message = "Don't know how to build task '#{task_name}' "\
|
||||
"(See the list of available tasks with `#{Rake.application.name} --tasks`)"
|
||||
message + generate_did_you_mean_suggestions(task_name)
|
||||
end
|
||||
|
||||
def generate_did_you_mean_suggestions(task_name)
|
||||
return "" unless defined?(::DidYouMean::SpellChecker)
|
||||
|
||||
suggestions = ::DidYouMean::SpellChecker.new(dictionary: @tasks.keys).correct(task_name.to_s)
|
||||
if ::DidYouMean.respond_to?(:formatter)# did_you_mean v1.2.0 or later
|
||||
::DidYouMean.formatter.message_for(suggestions)
|
||||
elsif defined?(::DidYouMean::Formatter) # before did_you_mean v1.2.0
|
||||
::DidYouMean::Formatter.new(suggestions).to_s
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def synthesize_file_task(task_name) # :nodoc:
|
||||
return nil unless File.exist?(task_name)
|
||||
define_task(Rake::FileTask, task_name)
|
||||
end
|
||||
|
||||
# Resolve the arguments for a task/rule. Returns a tuple of
|
||||
# [task_name, arg_name_list, prerequisites, order_only_prerequisites].
|
||||
def resolve_args(args)
|
||||
if args.last.is_a?(Hash)
|
||||
deps = args.pop
|
||||
resolve_args_with_dependencies(args, deps)
|
||||
else
|
||||
resolve_args_without_dependencies(args)
|
||||
end
|
||||
end
|
||||
|
||||
# Resolve task arguments for a task or rule when there are no
|
||||
# dependencies declared.
|
||||
#
|
||||
# The patterns recognized by this argument resolving function are:
|
||||
#
|
||||
# task :t
|
||||
# task :t, [:a]
|
||||
#
|
||||
def resolve_args_without_dependencies(args)
|
||||
task_name = args.shift
|
||||
if args.size == 1 && args.first.respond_to?(:to_ary)
|
||||
arg_names = args.first.to_ary
|
||||
else
|
||||
arg_names = args
|
||||
end
|
||||
[task_name, arg_names, [], nil]
|
||||
end
|
||||
private :resolve_args_without_dependencies
|
||||
|
||||
# Resolve task arguments for a task or rule when there are
|
||||
# dependencies declared.
|
||||
#
|
||||
# The patterns recognized by this argument resolving function are:
|
||||
#
|
||||
# task :t, order_only: [:e]
|
||||
# task :t => [:d]
|
||||
# task :t => [:d], order_only: [:e]
|
||||
# task :t, [a] => [:d]
|
||||
# task :t, [a] => [:d], order_only: [:e]
|
||||
#
|
||||
def resolve_args_with_dependencies(args, hash) # :nodoc:
|
||||
fail "Task Argument Error" if
|
||||
hash.size != 1 &&
|
||||
(hash.size != 2 || !hash.key?(:order_only))
|
||||
order_only = hash.delete(:order_only)
|
||||
key, value = hash.map { |k, v| [k, v] }.first
|
||||
if args.empty?
|
||||
task_name = key
|
||||
arg_names = []
|
||||
deps = value || []
|
||||
else
|
||||
task_name = args.shift
|
||||
arg_names = key || args.shift|| []
|
||||
deps = value || []
|
||||
end
|
||||
deps = [deps] unless deps.respond_to?(:to_ary)
|
||||
[task_name, arg_names, deps, order_only]
|
||||
end
|
||||
private :resolve_args_with_dependencies
|
||||
|
||||
# If a rule can be found that matches the task name, enhance the
|
||||
# task with the prerequisites and actions from the rule. Set the
|
||||
# source attribute of the task appropriately for the rule. Return
|
||||
# the enhanced task or nil of no rule was found.
|
||||
def enhance_with_matching_rule(task_name, level=0)
|
||||
fail Rake::RuleRecursionOverflowError,
|
||||
"Rule Recursion Too Deep" if level >= 16
|
||||
@rules.each do |pattern, args, extensions, order_only, block|
|
||||
if pattern && pattern.match(task_name)
|
||||
task = attempt_rule(task_name, pattern, args, extensions, block, level)
|
||||
task | order_only unless order_only.nil?
|
||||
return task if task
|
||||
end
|
||||
end
|
||||
nil
|
||||
rescue Rake::RuleRecursionOverflowError => ex
|
||||
ex.add_target(task_name)
|
||||
fail ex
|
||||
end
|
||||
|
||||
# List of all defined tasks in this application.
|
||||
def tasks
|
||||
@tasks.values.sort_by { |t| t.name }
|
||||
end
|
||||
|
||||
# List of all the tasks defined in the given scope (and its
|
||||
# sub-scopes).
|
||||
def tasks_in_scope(scope)
|
||||
prefix = scope.path
|
||||
tasks.select { |t|
|
||||
/^#{prefix}:/ =~ t.name
|
||||
}
|
||||
end
|
||||
|
||||
# Clear all tasks in this application.
|
||||
def clear
|
||||
@tasks.clear
|
||||
@rules.clear
|
||||
end
|
||||
|
||||
# Lookup a task, using scope and the scope hints in the task name.
|
||||
# This method performs straight lookups without trying to
|
||||
# synthesize file tasks or rules. Special scope names (e.g. '^')
|
||||
# are recognized. If no scope argument is supplied, use the
|
||||
# current scope. Return nil if the task cannot be found.
|
||||
def lookup(task_name, initial_scope=nil)
|
||||
initial_scope ||= @scope
|
||||
task_name = task_name.to_s
|
||||
if task_name =~ /^rake:/
|
||||
scopes = Scope.make
|
||||
task_name = task_name.sub(/^rake:/, "")
|
||||
elsif task_name =~ /^(\^+)/
|
||||
scopes = initial_scope.trim($1.size)
|
||||
task_name = task_name.sub(/^(\^+)/, "")
|
||||
else
|
||||
scopes = initial_scope
|
||||
end
|
||||
lookup_in_scope(task_name, scopes)
|
||||
end
|
||||
|
||||
# Lookup the task name
|
||||
def lookup_in_scope(name, scope)
|
||||
loop do
|
||||
tn = scope.path_with_task_name(name)
|
||||
task = @tasks[tn]
|
||||
return task if task
|
||||
break if scope.empty?
|
||||
scope = scope.tail
|
||||
end
|
||||
nil
|
||||
end
|
||||
private :lookup_in_scope
|
||||
|
||||
# Return the list of scope names currently active in the task
|
||||
# manager.
|
||||
def current_scope
|
||||
@scope
|
||||
end
|
||||
|
||||
# Evaluate the block in a nested namespace named +name+. Create
|
||||
# an anonymous namespace if +name+ is nil.
|
||||
def in_namespace(name)
|
||||
name ||= generate_name
|
||||
@scope = Scope.new(name, @scope)
|
||||
ns = NameSpace.new(self, @scope)
|
||||
yield(ns)
|
||||
ns
|
||||
ensure
|
||||
@scope = @scope.tail
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Add a location to the locations field of the given task.
|
||||
def add_location(task)
|
||||
loc = find_location
|
||||
task.locations << loc if loc
|
||||
task
|
||||
end
|
||||
|
||||
# Find the location that called into the dsl layer.
|
||||
def find_location
|
||||
locations = caller
|
||||
i = 0
|
||||
while locations[i]
|
||||
return locations[i + 1] if locations[i] =~ /rake\/dsl_definition.rb/
|
||||
i += 1
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Generate an anonymous namespace name.
|
||||
def generate_name
|
||||
@seed ||= 0
|
||||
@seed += 1
|
||||
"_anon_#{@seed}"
|
||||
end
|
||||
|
||||
def trace_rule(level, message) # :nodoc:
|
||||
options.trace_output.puts "#{" " * level}#{message}" if
|
||||
Rake.application.options.trace_rules
|
||||
end
|
||||
|
||||
# Attempt to create a rule given the list of prerequisites.
|
||||
def attempt_rule(task_name, task_pattern, args, extensions, block, level)
|
||||
sources = make_sources(task_name, task_pattern, extensions)
|
||||
prereqs = sources.map { |source|
|
||||
trace_rule level, "Attempting Rule #{task_name} => #{source}"
|
||||
if File.exist?(source) || Rake::Task.task_defined?(source)
|
||||
trace_rule level, "(#{task_name} => #{source} ... EXIST)"
|
||||
source
|
||||
elsif parent = enhance_with_matching_rule(source, level + 1)
|
||||
trace_rule level, "(#{task_name} => #{source} ... ENHANCE)"
|
||||
parent.name
|
||||
else
|
||||
trace_rule level, "(#{task_name} => #{source} ... FAIL)"
|
||||
return nil
|
||||
end
|
||||
}
|
||||
task = FileTask.define_task(task_name, { args => prereqs }, &block)
|
||||
task.sources = prereqs
|
||||
task
|
||||
end
|
||||
|
||||
# Make a list of sources from the list of file name extensions /
|
||||
# translation procs.
|
||||
def make_sources(task_name, task_pattern, extensions)
|
||||
result = extensions.map { |ext|
|
||||
case ext
|
||||
when /%/
|
||||
task_name.pathmap(ext)
|
||||
when %r{/}
|
||||
ext
|
||||
when /^\./
|
||||
source = task_name.sub(task_pattern, ext)
|
||||
source == ext ? task_name.ext(ext) : source
|
||||
when String, Symbol
|
||||
ext.to_s
|
||||
when Proc, Method
|
||||
if ext.arity == 1
|
||||
ext.call(task_name)
|
||||
else
|
||||
ext.call
|
||||
end
|
||||
else
|
||||
fail "Don't know how to handle rule dependent: #{ext.inspect}"
|
||||
end
|
||||
}
|
||||
result.flatten
|
||||
end
|
||||
|
||||
# Return the current description, clearing it in the process.
|
||||
def get_description(task)
|
||||
desc = @last_description
|
||||
@last_description = nil
|
||||
desc
|
||||
end
|
||||
|
||||
class << self
|
||||
attr_accessor :record_task_metadata # :nodoc:
|
||||
TaskManager.record_task_metadata = false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake"
|
||||
|
||||
module Rake
|
||||
|
||||
# Base class for Task Libraries.
|
||||
class TaskLib
|
||||
include Cloneable
|
||||
include Rake::DSL
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,189 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake"
|
||||
require "rake/tasklib"
|
||||
|
||||
module Rake
|
||||
|
||||
# Create a task that runs a set of tests.
|
||||
#
|
||||
# Example:
|
||||
# require "rake/testtask"
|
||||
#
|
||||
# Rake::TestTask.new do |t|
|
||||
# t.libs << "test"
|
||||
# t.test_files = FileList['test/test*.rb']
|
||||
# t.verbose = true
|
||||
# end
|
||||
#
|
||||
# If rake is invoked with a "TEST=filename" command line option,
|
||||
# then the list of test files will be overridden to include only the
|
||||
# filename specified on the command line. This provides an easy way
|
||||
# to run just one test.
|
||||
#
|
||||
# If rake is invoked with a "TESTOPTS=options" command line option,
|
||||
# then the given options are passed to the test process after a
|
||||
# '--'. This allows Test::Unit options to be passed to the test
|
||||
# suite.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# rake test # run tests normally
|
||||
# rake test TEST=just_one_file.rb # run just one test file.
|
||||
# rake test TESTOPTS="-v" # run in verbose mode
|
||||
# rake test TESTOPTS="--runner=fox" # use the fox test runner
|
||||
#
|
||||
class TestTask < TaskLib
|
||||
|
||||
# Name of test task. (default is :test)
|
||||
attr_accessor :name
|
||||
|
||||
# List of directories added to $LOAD_PATH before running the
|
||||
# tests. (default is 'lib')
|
||||
attr_accessor :libs
|
||||
|
||||
# True if verbose test output desired. (default is false)
|
||||
attr_accessor :verbose
|
||||
|
||||
# Test options passed to the test suite. An explicit
|
||||
# TESTOPTS=opts on the command line will override this. (default
|
||||
# is NONE)
|
||||
attr_accessor :options
|
||||
|
||||
# Request that the tests be run with the warning flag set.
|
||||
# E.g. warning=true implies "ruby -w" used to run the tests.
|
||||
# (default is true)
|
||||
attr_accessor :warning
|
||||
|
||||
# Glob pattern to match test files. (default is 'test/test*.rb')
|
||||
attr_accessor :pattern
|
||||
|
||||
# Style of test loader to use. Options are:
|
||||
#
|
||||
# * :rake -- Rake provided test loading script (default).
|
||||
# * :testrb -- Ruby provided test loading script.
|
||||
# * :direct -- Load tests using command line loader.
|
||||
#
|
||||
attr_accessor :loader
|
||||
|
||||
# Array of command line options to pass to ruby when running test loader.
|
||||
attr_accessor :ruby_opts
|
||||
|
||||
# Description of the test task. (default is 'Run tests')
|
||||
attr_accessor :description
|
||||
|
||||
# Task prerequisites.
|
||||
attr_accessor :deps
|
||||
|
||||
# Explicitly define the list of test files to be included in a
|
||||
# test. +list+ is expected to be an array of file names (a
|
||||
# FileList is acceptable). If both +pattern+ and +test_files+ are
|
||||
# used, then the list of test files is the union of the two.
|
||||
def test_files=(list)
|
||||
@test_files = list
|
||||
end
|
||||
|
||||
# Create a testing task.
|
||||
def initialize(name=:test)
|
||||
@name = name
|
||||
@libs = ["lib"]
|
||||
@pattern = nil
|
||||
@options = nil
|
||||
@test_files = nil
|
||||
@verbose = false
|
||||
@warning = true
|
||||
@loader = :rake
|
||||
@ruby_opts = []
|
||||
@description = "Run tests" + (@name == :test ? "" : " for #{@name}")
|
||||
@deps = []
|
||||
if @name.is_a?(Hash)
|
||||
@deps = @name.values.first
|
||||
@name = @name.keys.first
|
||||
end
|
||||
yield self if block_given?
|
||||
@pattern = "test/test*.rb" if @pattern.nil? && @test_files.nil?
|
||||
define
|
||||
end
|
||||
|
||||
# Create the tasks defined by this task lib.
|
||||
def define
|
||||
desc @description
|
||||
task @name => Array(deps) do
|
||||
FileUtilsExt.verbose(@verbose) do
|
||||
puts "Use TESTOPTS=\"--verbose\" to pass --verbose" \
|
||||
", etc. to runners." if ARGV.include? "--verbose"
|
||||
args =
|
||||
"#{ruby_opts_string} #{run_code} " +
|
||||
"#{file_list_string} #{option_list}"
|
||||
ruby args do |ok, status|
|
||||
if !ok && status.respond_to?(:signaled?) && status.signaled?
|
||||
raise SignalException.new(status.termsig)
|
||||
elsif !ok
|
||||
status = "Command failed with status (#{status.exitstatus})"
|
||||
details = ": [ruby #{args}]"
|
||||
message =
|
||||
if Rake.application.options.trace or @verbose
|
||||
status + details
|
||||
else
|
||||
status
|
||||
end
|
||||
|
||||
fail message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def option_list # :nodoc:
|
||||
(ENV["TESTOPTS"] ||
|
||||
ENV["TESTOPT"] ||
|
||||
ENV["TEST_OPTS"] ||
|
||||
ENV["TEST_OPT"] ||
|
||||
@options ||
|
||||
"")
|
||||
end
|
||||
|
||||
def ruby_opts_string # :nodoc:
|
||||
opts = @ruby_opts.dup
|
||||
opts.unshift("-I\"#{lib_path}\"") unless @libs.empty?
|
||||
opts.unshift("-w") if @warning
|
||||
opts.join(" ")
|
||||
end
|
||||
|
||||
def lib_path # :nodoc:
|
||||
@libs.join(File::PATH_SEPARATOR)
|
||||
end
|
||||
|
||||
def file_list_string # :nodoc:
|
||||
file_list.map { |fn| "\"#{fn}\"" }.join(" ")
|
||||
end
|
||||
|
||||
def file_list # :nodoc:
|
||||
if ENV["TEST"]
|
||||
FileList[ENV["TEST"]]
|
||||
else
|
||||
result = []
|
||||
result += @test_files.to_a if @test_files
|
||||
result += FileList[@pattern].to_a if @pattern
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def ruby_version # :nodoc:
|
||||
RUBY_VERSION
|
||||
end
|
||||
|
||||
def run_code # :nodoc:
|
||||
case @loader
|
||||
when :direct
|
||||
"-e \"ARGV.each{|f| require f}\""
|
||||
when :testrb
|
||||
"-S testrb"
|
||||
when :rake
|
||||
"#{__dir__}/rake_test_loader.rb"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
require "rake/private_reader"
|
||||
|
||||
module Rake
|
||||
|
||||
class ThreadHistoryDisplay # :nodoc: all
|
||||
include Rake::PrivateReader
|
||||
|
||||
private_reader :stats, :items, :threads
|
||||
|
||||
def initialize(stats)
|
||||
@stats = stats
|
||||
@items = { _seq_: 1 }
|
||||
@threads = { _seq_: "A" }
|
||||
end
|
||||
|
||||
def show
|
||||
puts "Job History:"
|
||||
stats.each do |stat|
|
||||
stat[:data] ||= {}
|
||||
rename(stat, :thread, threads)
|
||||
rename(stat[:data], :item_id, items)
|
||||
rename(stat[:data], :new_thread, threads)
|
||||
rename(stat[:data], :deleted_thread, threads)
|
||||
printf("%8d %2s %-20s %s\n",
|
||||
(stat[:time] * 1_000_000).round,
|
||||
stat[:thread],
|
||||
stat[:event],
|
||||
stat[:data].map do |k, v| "#{k}:#{v}" end.join(" "))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rename(hash, key, renames)
|
||||
if hash && hash[key]
|
||||
original = hash[key]
|
||||
value = renames[original]
|
||||
unless value
|
||||
value = renames[:_seq_]
|
||||
renames[:_seq_] = renames[:_seq_].succ
|
||||
renames[original] = value
|
||||
end
|
||||
hash[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,163 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rake/promise"
|
||||
require "set"
|
||||
|
||||
module Rake
|
||||
|
||||
class ThreadPool # :nodoc: all
|
||||
|
||||
# Creates a ThreadPool object. The +thread_count+ parameter is the size
|
||||
# of the pool.
|
||||
def initialize(thread_count)
|
||||
@max_active_threads = [thread_count, 0].max
|
||||
@threads = Set.new
|
||||
@threads_mon = Monitor.new
|
||||
@queue = Queue.new
|
||||
@join_cond = @threads_mon.new_cond
|
||||
|
||||
@history_start_time = nil
|
||||
@history = []
|
||||
@history_mon = Monitor.new
|
||||
@total_threads_in_play = 0
|
||||
end
|
||||
|
||||
# Creates a future executed by the +ThreadPool+.
|
||||
#
|
||||
# The args are passed to the block when executing (similarly to
|
||||
# Thread#new) The return value is an object representing
|
||||
# a future which has been created and added to the queue in the
|
||||
# pool. Sending #value to the object will sleep the
|
||||
# current thread until the future is finished and will return the
|
||||
# result (or raise an exception thrown from the future)
|
||||
def future(*args, &block)
|
||||
promise = Promise.new(args, &block)
|
||||
promise.recorder = lambda { |*stats| stat(*stats) }
|
||||
|
||||
@queue.enq promise
|
||||
stat :queued, item_id: promise.object_id
|
||||
start_thread
|
||||
promise
|
||||
end
|
||||
|
||||
# Waits until the queue of futures is empty and all threads have exited.
|
||||
def join
|
||||
@threads_mon.synchronize do
|
||||
begin
|
||||
stat :joining
|
||||
@join_cond.wait unless @threads.empty?
|
||||
stat :joined
|
||||
rescue Exception => e
|
||||
stat :joined
|
||||
$stderr.puts e
|
||||
$stderr.print "Queue contains #{@queue.size} items. " +
|
||||
"Thread pool contains #{@threads.count} threads\n"
|
||||
$stderr.print "Current Thread #{Thread.current} status = " +
|
||||
"#{Thread.current.status}\n"
|
||||
$stderr.puts e.backtrace.join("\n")
|
||||
@threads.each do |t|
|
||||
$stderr.print "Thread #{t} status = #{t.status}\n"
|
||||
$stderr.puts t.backtrace.join("\n")
|
||||
end
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Enable the gathering of history events.
|
||||
def gather_history #:nodoc:
|
||||
@history_start_time = Time.now if @history_start_time.nil?
|
||||
end
|
||||
|
||||
# Return a array of history events for the thread pool.
|
||||
#
|
||||
# History gathering must be enabled to be able to see the events
|
||||
# (see #gather_history). Best to call this when the job is
|
||||
# complete (i.e. after ThreadPool#join is called).
|
||||
def history # :nodoc:
|
||||
@history_mon.synchronize { @history.dup }.
|
||||
sort_by { |i| i[:time] }.
|
||||
each { |i| i[:time] -= @history_start_time }
|
||||
end
|
||||
|
||||
# Return a hash of always collected statistics for the thread pool.
|
||||
def statistics # :nodoc:
|
||||
{
|
||||
total_threads_in_play: @total_threads_in_play,
|
||||
max_active_threads: @max_active_threads,
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# processes one item on the queue. Returns true if there was an
|
||||
# item to process, false if there was no item
|
||||
def process_queue_item #:nodoc:
|
||||
return false if @queue.empty?
|
||||
|
||||
# Even though we just asked if the queue was empty, it
|
||||
# still could have had an item which by this statement
|
||||
# is now gone. For this reason we pass true to Queue#deq
|
||||
# because we will sleep indefinitely if it is empty.
|
||||
promise = @queue.deq(true)
|
||||
stat :dequeued, item_id: promise.object_id
|
||||
promise.work
|
||||
return true
|
||||
|
||||
rescue ThreadError # this means the queue is empty
|
||||
false
|
||||
end
|
||||
|
||||
def safe_thread_count
|
||||
@threads_mon.synchronize do
|
||||
@threads.count
|
||||
end
|
||||
end
|
||||
|
||||
def start_thread # :nodoc:
|
||||
@threads_mon.synchronize do
|
||||
next unless @threads.count < @max_active_threads
|
||||
|
||||
t = Thread.new do
|
||||
begin
|
||||
while safe_thread_count <= @max_active_threads
|
||||
break unless process_queue_item
|
||||
end
|
||||
ensure
|
||||
@threads_mon.synchronize do
|
||||
@threads.delete Thread.current
|
||||
stat :ended, thread_count: @threads.count
|
||||
@join_cond.broadcast if @threads.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@threads << t
|
||||
stat(
|
||||
:spawned,
|
||||
new_thread: t.object_id,
|
||||
thread_count: @threads.count)
|
||||
@total_threads_in_play = @threads.count if
|
||||
@threads.count > @total_threads_in_play
|
||||
end
|
||||
end
|
||||
|
||||
def stat(event, data=nil) # :nodoc:
|
||||
return if @history_start_time.nil?
|
||||
info = {
|
||||
event: event,
|
||||
data: data,
|
||||
time: Time.now,
|
||||
thread: Thread.current.object_id,
|
||||
}
|
||||
@history_mon.synchronize { @history << info }
|
||||
end
|
||||
|
||||
# for testing only
|
||||
|
||||
def __queue__ # :nodoc:
|
||||
@queue
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
module TraceOutput # :nodoc: all
|
||||
|
||||
# Write trace output to output stream +out+.
|
||||
#
|
||||
# The write is done as a single IO call (to print) to lessen the
|
||||
# chance that the trace output is interrupted by other tasks also
|
||||
# producing output.
|
||||
def trace_on(out, *strings)
|
||||
sep = $\ || "\n"
|
||||
if strings.empty?
|
||||
output = sep
|
||||
else
|
||||
output = strings.map { |s|
|
||||
next if s.nil?
|
||||
s.end_with?(sep) ? s : s + sep
|
||||
}.join
|
||||
end
|
||||
out.print(output)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
module Rake
|
||||
VERSION = "13.2.1"
|
||||
|
||||
module Version # :nodoc: all
|
||||
MAJOR, MINOR, BUILD, *OTHER = Rake::VERSION.split "."
|
||||
|
||||
NUMBERS = [MAJOR, MINOR, BUILD, *OTHER]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
require "rbconfig"
|
||||
|
||||
module Rake
|
||||
# Win 32 interface methods for Rake. Windows specific functionality
|
||||
# will be placed here to collect that knowledge in one spot.
|
||||
module Win32 # :nodoc: all
|
||||
|
||||
# Error indicating a problem in locating the home directory on a
|
||||
# Win32 system.
|
||||
class Win32HomeError < RuntimeError
|
||||
end
|
||||
|
||||
class << self
|
||||
# True if running on a windows system.
|
||||
def windows?
|
||||
RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw|[Ww]indows)!
|
||||
end
|
||||
|
||||
# The standard directory containing system wide rake files on
|
||||
# Win 32 systems. Try the following environment variables (in
|
||||
# order):
|
||||
#
|
||||
# * HOME
|
||||
# * HOMEDRIVE + HOMEPATH
|
||||
# * APPDATA
|
||||
# * USERPROFILE
|
||||
#
|
||||
# If the above are not defined, the return nil.
|
||||
def win32_system_dir #:nodoc:
|
||||
win32_shared_path = ENV["HOME"]
|
||||
if win32_shared_path.nil? && ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
|
||||
win32_shared_path = ENV["HOMEDRIVE"] + ENV["HOMEPATH"]
|
||||
end
|
||||
|
||||
win32_shared_path ||= ENV["APPDATA"]
|
||||
win32_shared_path ||= ENV["USERPROFILE"]
|
||||
raise Win32HomeError,
|
||||
"Unable to determine home path environment variable." if
|
||||
win32_shared_path.nil? or win32_shared_path.empty?
|
||||
normalize(File.join(win32_shared_path, "Rake"))
|
||||
end
|
||||
|
||||
# Normalize a win32 path so that the slashes are all forward slashes.
|
||||
def normalize(path)
|
||||
path.gsub(/\\/, "/")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user