Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
+69
View File
@@ -0,0 +1,69 @@
# frozen_string_literal: true
#--
# Copyright 2003-2010 by Jim Weirich (jim.weirich@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#++
module Rake; end
require_relative "rake/version"
require "rbconfig"
require "fileutils"
require "singleton"
require "monitor"
require "optparse"
require_relative "rake/ext/string"
require_relative "rake/win32"
require_relative "rake/linked_list"
require_relative "rake/cpu_counter"
require_relative "rake/scope"
require_relative "rake/task_argument_error"
require_relative "rake/rule_recursion_overflow_error"
require_relative "rake/rake_module"
require_relative "rake/trace_output"
require_relative "rake/pseudo_status"
require_relative "rake/options"
require_relative "rake/task_arguments"
require_relative "rake/invocation_chain"
require_relative "rake/task"
require_relative "rake/file_task"
require_relative "rake/file_creation_task"
require_relative "rake/multi_task"
require_relative "rake/dsl_definition"
require_relative "rake/file_utils_ext"
require_relative "rake/file_list"
require_relative "rake/default_loader"
require_relative "rake/early_time"
require_relative "rake/late_time"
require_relative "rake/name_space"
require_relative "rake/task_manager"
require_relative "rake/application"
require_relative "rake/backtrace"
# :stopdoc:
#
# Some top level Constants.
FileList = Rake::FileList
RakeFileUtils = Rake::FileUtilsExt
@@ -0,0 +1,847 @@
# frozen_string_literal: true
require "optparse"
require_relative "options"
require_relative "task_manager"
require_relative "file_list"
require_relative "thread_pool"
require_relative "thread_history_display"
require_relative "trace_output"
require_relative "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
ensure
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 ||= Options.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=$LOADED_FEATURES) # :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 ||= ENV["RAKE_SYSTEM"] || standard_system_dir
end
# The standard directory containing system wide rake files.
def standard_system_dir #:nodoc:
if windows?
File.join(Dir.home, "Rake")
else
File.join(Dir.home, ".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
+78
View File
@@ -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_relative "../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,122 @@
# 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
# Get-CimInstance introduced in PowerShell 3 or earlier: https://learn.microsoft.com/en-us/previous-versions/powershell/module/cimcmdlets/get-ciminstance?view=powershell-3.0
result = run_win32(
'powershell -command "Get-CimInstance -ClassName Win32_Processor -Property NumberOfCores ' \
'| Select-Object -Property NumberOfCores"'
)
if !result || $?.exitstatus != 0
# fallback to deprecated wmic for older systems
result = run_win32("wmic cpu get NumberOfCores")
end
# powershell: "\nNumberOfCores\n-------------\n 4\n\n\n"
# wmic: "NumberOfCores \n\n4 \n\n\n\n"
result.scan(/\d+/).map(&:to_i).reduce(:+) if result
rescue StandardError
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 run_win32(command, *args)
IO.popen(command, &:read)
rescue Errno::ENOENT
nil
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_relative "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
+26
View File
@@ -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
+176
View File
@@ -0,0 +1,176 @@
# frozen_string_literal: true
require_relative "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_relative "file_task"
require_relative "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
+435
View File
@@ -0,0 +1,435 @@
# frozen_string_literal: true
require_relative "cloneable"
require_relative "file_utils_ext"
require_relative "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_relative "task"
require_relative "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
+137
View File
@@ -0,0 +1,137 @@
# 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, options) 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, options = nil) # :nodoc:
cmd = cmd.dup
if Hash === cmd.first
env = cmd.first
env = env.map { |name, value| "#{name}=#{value}" }.join " "
cmd[0] = env
end
cmd = cmd.join " "
if options and chdir = options[:chdir]
"(cd #{chdir} && #{cmd})"
else
cmd
end
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,135 @@
# frozen_string_literal: true
require_relative "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
+31
View File
@@ -0,0 +1,31 @@
# frozen_string_literal: true
module Rake
##
# Options used by the Rake command line application.
#
class Options
attr_accessor :always_multitask
attr_accessor :backtrace
attr_accessor :build_all
attr_accessor :dryrun
attr_accessor :ignore_deprecate
attr_accessor :ignore_system
attr_accessor :job_stats
attr_accessor :load_system
attr_accessor :nosearch
attr_accessor :rakelib
attr_accessor :show_all_tasks
attr_accessor :show_prereqs
attr_accessor :show_task_pattern
attr_accessor :show_tasks
attr_accessor :silent
attr_accessor :suppress_backtrace_pattern
attr_accessor :thread_pool_size
attr_accessor :trace
attr_accessor :trace_output
attr_accessor :trace_rules
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_relative "../rake"
require_relative "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
+16
View File
@@ -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_relative "../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
+100
View File
@@ -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_relative "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
+43
View File
@@ -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
+434
View File
@@ -0,0 +1,434 @@
# frozen_string_literal: true
require_relative "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,113 @@
# 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
def deconstruct_keys(keys)
keys ? @hash.slice(*keys) : to_hash
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,333 @@
# 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)
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 Pathname
Rake.from_pathname(ext)
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
desc = @last_description
@last_description = nil
desc
end
class << self
attr_accessor :record_task_metadata # :nodoc:
TaskManager.record_task_metadata = false
end
end
end
+12
View File
@@ -0,0 +1,12 @@
# frozen_string_literal: true
require_relative "../rake"
module Rake
# Base class for Task Libraries.
class TaskLib
include Cloneable
include Rake::DSL
end
end
+192
View File
@@ -0,0 +1,192 @@
# frozen_string_literal: true
require_relative "../rake"
require_relative "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
effective_verbose = @verbose || FileUtilsExt.verbose_flag == true
FileUtilsExt.verbose(effective_verbose) do
args =
"#{ruby_opts_string} #{run_code} " +
"#{file_list_string} #{option_list(verbose: effective_verbose)}"
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(verbose: @verbose) # :nodoc:
opts = ENV["TESTOPTS"] ||
ENV["TESTOPT"] ||
ENV["TEST_OPTS"] ||
ENV["TEST_OPT"] ||
@options ||
""
if verbose && !opts.split.include?("-v")
opts = opts.empty? ? "-v" : "#{opts} -v"
end
opts
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"].split(",")]
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_relative "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,157 @@
# frozen_string_literal: true
require_relative "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 start_thread # :nodoc:
@threads_mon.synchronize do
next unless @threads.count < @max_active_threads
t = Thread.new do
begin
loop do
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
+10
View File
@@ -0,0 +1,10 @@
# frozen_string_literal: true
module Rake
VERSION = "13.4.2"
module Version # :nodoc: all
MAJOR, MINOR, BUILD, *OTHER = Rake::VERSION.split "."
NUMBERS = [MAJOR, MINOR, BUILD, *OTHER]
end
end
+17
View File
@@ -0,0 +1,17 @@
# 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
class << self
# True if running on a windows system.
def windows?
RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw|[Ww]indows)!
end
end
end
end