#!/usr/bin/env ruby
# sisu-install - Monolithic rant script, autogenerated by rant-import 0.5.8.
#
# Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
#
# This program is free software.
# You can distribute/modify this program under the terms of
# the GNU LGPL, Lesser General Public License version 2.1.
require 'getoptlong'
require 'rbconfig'
unless Process::Status.method_defined?(:success?) # new in 1.8.2
class Process::Status
def success?; exitstatus == 0; end
end
end
unless Regexp.respond_to? :union # new in 1.8.1
def Regexp.union(*patterns)
return /(?!)/ if patterns.empty?
Regexp.new(patterns.join("|"))
end
end
if RUBY_VERSION < "1.8.2"
class Array
undef_method :flatten, :flatten!
def flatten
cp = self.dup
cp.flatten!
cp
end
def flatten!
res = []
flattened = false
self.each { |e|
if e.respond_to? :to_ary
res.concat(e.to_ary)
flattened = true
else
res << e
end
}
if flattened
replace(res)
flatten!
self
end
end
end
end
class String
def _rant_sub_ext(ext, new_ext = nil)
if new_ext
self.sub(/#{Regexp.escape ext}$/, new_ext)
else
self.sub(/(\.[^.]*$)|$/, ".#{ext}")
end
end
end
module Rant
VERSION = '0.5.8'
@__rant_no_value__ = Object.new.freeze
def self.__rant_no_value__
@__rant_no_value__
end
module Env
OS = ::Config::CONFIG['target']
RUBY = ::Config::CONFIG['ruby_install_name']
RUBY_BINDIR = ::Config::CONFIG['bindir']
RUBY_EXE = File.join(RUBY_BINDIR, RUBY + ::Config::CONFIG["EXEEXT"])
@@zip_bin = false
@@tar_bin = false
if OS =~ /mswin/i
def on_windows?; true; end
else
def on_windows?; false; end
end
def have_zip?
if @@zip_bin == false
@@zip_bin = find_bin "zip"
end
!@@zip_bin.nil?
end
def have_tar?
if @@tar_bin == false
@@tar_bin = find_bin "tar"
end
!@@tar_bin.nil?
end
def pathes
path = ENV[on_windows? ? "Path" : "PATH"]
return [] unless path
path.split(on_windows? ? ";" : ":")
end
def find_bin bin_name
if on_windows?
bin_name_exe = nil
if bin_name !~ /\.[^\.]{1,3}$/i
bin_name_exe = bin_name + ".exe"
end
pathes.each { |dir|
file = File.join(dir, bin_name)
return file if test(?f, file)
if bin_name_exe
file = File.join(dir, bin_name_exe)
return file if test(?f, file)
end
}
else
pathes.each { |dir|
file = File.join(dir, bin_name)
return file if test(?x, file)
}
end
nil
end
def shell_path path
if on_windows?
path = path.tr("/", "\\")
if path.include? ' '
'"' + path + '"'
else
path
end
else
if path.include? ' '
"'" + path + "'"
else
path
end
end
end
extend self
end # module Env
module Sys
def sp(arg)
if arg.respond_to? :to_ary
arg.to_ary.map{ |e| sp e }.join(' ')
else
_escaped_path arg
end
end
def escape(arg)
if arg.respond_to? :to_ary
arg.to_ary.map{ |e| escape e }.join(' ')
else
_escaped arg
end
end
if Env.on_windows?
def _escaped_path(path)
_escaped(path.to_s.tr("/", "\\"))
end
def _escaped(arg)
sarg = arg.to_s
return sarg unless sarg.include?(" ")
sarg << "\\" if sarg[-1].chr == "\\"
"\"#{sarg}\""
end
def regular_filename(fn)
fn.to_str.tr("\\", "/").gsub(%r{/{2,}}, "/")
end
else
def _escaped_path(path)
path.to_s.gsub(/(?=\s)/, "\\")
end
alias _escaped _escaped_path
def regular_filename(fn)
fn.to_str.gsub(%r{/{2,}}, "/")
end
end
private :_escaped_path
private :_escaped
def split_all(path)
names = regular_filename(path).split(%r{/})
names[0] = "/" if names[0] && names[0].empty?
names
end
extend self
end # module Sys
ROOT_RANTFILE = "root.rant"
SUB_RANTFILE = "sub.rant"
RANTFILES = [ "Rantfile", "rantfile", ROOT_RANTFILE ]
CODE_IMPORTS = []
class RantAbortException < StandardError
end
class RantDoneException < StandardError
end
class Error < StandardError
end
module Generators
end
module RantVar
class Error < Rant::Error
end
class ConstraintError < Error
attr_reader :constraint, :val
def initialize(constraint, val, msg = nil)
@msg = msg
@constraint = constraint
@val = val
end
def message
val_desc = @val.inspect
val_desc[7..-1] = "..." if val_desc.length > 10
"#{val_desc} doesn't match constraint: #@constraint"
end
end
class NotAConstraintFactoryError < Error
attr_reader :obj
def initialize(obj, msg = nil)
@msg = msg
@obj = obj
end
def message
obj_desc = @obj.inspect
obj_desc[7..-1] = "..." if obj_desc.length > 10
"#{obj_desc} is not a valid constraint factory"
end
end
class InvalidVidError < Error
def initialize(vid, msg = nil)
@msg = msg
@vid = vid
end
def message
vid_desc = @vid.inspect
vid_desc[7..-1] = "..." if vid_desc.length > 10
"#{vid_desc} is not a valid var identifier"
end
end
class InvalidConstraintError < Error
end
class QueryError < Error
end
class Space
@@env_ref = Object.new
def initialize
@store = {}
@constraints = {}
end
def query(*args, &block)
case args.size
when 0
raise QueryError, "no arguments", caller
when 1
arg = args.first
if Hash === arg
if arg.size == 1
arg.each { |k,v|
self[k] = v if self[k].nil?
}
self
else
init_all arg
end
else
self[arg]
end
when 2, 3
vid, cf, val = *args
constrain vid,
get_factory(cf).rant_constraint
self[vid] = val if val
else
raise QueryError, "too many arguments"
end
end
def restrict vid, ct, *ct_args
if vid.respond_to? :to_ary
vid.to_ary.each { |v| restrict(v, ct, *ct_args) }
else
constrain vid,
get_factory(ct).rant_constraint(*ct_args)
end
self
end
def get_factory id
if String === id || Symbol === id
id = Constraints.const_get(id) rescue nil
end
unless id.respond_to? :rant_constraint
raise NotAConstraintFactoryError.new(id), caller
end
id
end
private :get_factory
def [](vid)
vid = RantVar.valid_vid vid
val = @store[vid]
val.equal?(@@env_ref) ? ENV[vid] : val
end
def []=(vid, val)
vid = RantVar.valid_vid(vid)
c = @constraints[vid]
if @store[vid] == @@env_ref
ENV[vid] = c ? c.filter(val) : val
else
@store[vid] = c ? c.filter(val) : val
end
end
def env(*vars)
vars.flatten.each { |var|
vid = RantVar.valid_vid(var)
cur_val = @store[vid]
next if cur_val == @@env_ref
ENV[vid] = cur_val unless cur_val.nil?
@store[vid] = @@env_ref
}
nil
end
def set_all hash
unless Hash === hash
raise QueryError,
"set_all argument has to be a hash"
end
hash.each_pair { |k, v|
self[k] = v
}
end
def init_all hash
unless Hash === hash
raise QueryError,
"init_all argument has to be a hash"
end
hash.each_pair { |k, v|
self[k] = v if self[k].nil?
}
end
def constrain vid, constraint
vid = RantVar.valid_vid(vid)
unless RantVar.valid_constraint? constraint
raise InvalidConstraintError, constraint
end
@constraints[vid] = constraint
if @store.member? vid
begin
val = @store[vid]
@store[vid] = constraint.filter(@store[vid])
rescue
@store[vid] = constraint.default
raise ConstraintError.new(constraint, val)
end
else
@store[vid] = constraint.default
end
end
def has_var?(vid)
!self[vid].nil?
end
def _set(vid, val) #:nodoc:
@store[vid] = val
end
def _get(vid) #:nodoc:
@store[vid]
end
def _init(vid, val) #:nodoc:
@store[vid] ||= val
end
end # class Space
module Constraint
def matches? val
filter val
true
rescue
return false
end
end
def valid_vid(obj)
case obj
when String; obj
when Symbol; obj.to_s
else
if obj.respond_to? :to_str
obj.to_str
else
raise InvalidVidError.new(obj)
end
end
end
def valid_constraint?(obj)
obj.respond_to?(:filter) &&
obj.respond_to?(:matches?) &&
obj.respond_to?(:default)
end
module_function :valid_constraint?, :valid_vid
module Constraints
class AutoList
include Constraint
class << self
alias rant_constraint new
end
def filter(val)
if val.respond_to? :to_ary
val.to_ary
elsif val.nil?
raise ConstraintError.new(self, val)
else
[val]
end
end
def default
[]
end
def to_s
"list or single, non-nil value"
end
end
end # module Constraints
end # module RantVar
end # module Rant
require 'fileutils'
module Rant
def FileList(arg)
if arg.respond_to?(:to_rant_filelist)
arg.to_rant_filelist
elsif arg.respond_to?(:to_ary)
FileList.new(arg.to_ary)
else
raise TypeError,
"cannot convert #{arg.class} into Rant::FileList"
end
end
module_function :FileList
class FileList
include Enumerable
ESC_SEPARATOR = Regexp.escape(File::SEPARATOR)
ESC_ALT_SEPARATOR = File::ALT_SEPARATOR ?
Regexp.escape(File::ALT_SEPARATOR) : nil
class << self
def [](*patterns)
new.hide_dotfiles.include(*patterns)
end
def glob(*patterns)
fl = new.hide_dotfiles.ignore(".", "..").include(*patterns)
if block_given? then yield fl else fl end
end
def glob_all(*patterns)
fl = new.ignore(".", "..").include(*patterns)
if block_given? then yield fl else fl end
end
end
def initialize(store = [])
@pending = false
@def_glob_dotfiles = true
@items = store
@ignore_rx = nil
@keep = {}
@actions = []
end
alias _object_dup dup
private :_object_dup
def dup
c = _object_dup
c.items = @items.dup
c.actions = @actions.dup
c.ignore_rx = @ignore_rx.dup if @ignore_rx
c.instance_variable_set(:@keep, @keep.dup)
c
end
def copy
c = _object_dup
c.items = @items.map { |entry| entry.dup }
c.actions = @actions.dup
c.ignore_rx = @ignore_rx.dup if @ignore_rx
h_keep = {}
@keep.each_key { |entry| h_keep[entry] = true }
c.instance_variable_set(:@keep, h_keep)
c
end
def glob_dotfiles?
@def_glob_dotfiles
end
def glob_dotfiles=(flag)
@def_glob_dotfiles = flag ? true : false
end
def hide_dotfiles
@def_glob_dotfiles = false
self
end
def glob_dotfiles
@def_glob_dotfiles = true
self
end
protected
attr_accessor :actions, :items
attr_accessor :pending
attr_accessor :ignore_rx
public
def each(&block)
resolve if @pending
@items.each(&block)
self
end
def to_ary
resolve if @pending
@items
end
alias to_a to_ary
alias entries to_ary # entries: defined in Enumerable
def to_rant_filelist
self
end
def +(other)
if other.respond_to? :to_rant_filelist
c = other.to_rant_filelist.dup
c.actions.concat(@actions)
c.items.concat(@items)
c.pending = !c.actions.empty?
c
elsif other.respond_to? :to_ary
c = dup
c.actions <<
[:apply_ary_method_1, :concat, other.to_ary.dup]
c.pending = true
c
else
raise TypeError,
"cannot add #{other.class} to Rant::FileList"
end
end
def <<(file)
@actions << [:apply_ary_method_1, :push, file]
@keep[file] = true
@pending = true
self
end
def keep(entry)
@keep[entry] = true
@items << entry
self
end
def concat(ary)
if @pending
ary = ary.to_ary.dup
@actions << [:apply_ary_method_1, :concat, ary]
else
ix = ignore_rx and ary = ary.to_ary.reject { |f| f =~ ix }
@items.concat(ary)
end
self
end
def size
resolve if @pending
@items.size
end
alias length size
def empty?
resolve if @pending
@items.empty?
end
def join(sep = ' ')
resolve if @pending
@items.join(sep)
end
def pop
resolve if @pending
@items.pop
end
def push(entry)
resolve if @pending
@items.push(entry) if entry !~ ignore_rx
self
end
def shift
resolve if @pending
@items.shift
end
def unshift(entry)
resolve if @pending
@items.unshift(entry) if entry !~ ignore_rx
self
end
if Object.method_defined?(:fcall) || Object.method_defined?(:funcall) # in Ruby 1.9 like __send__
@@__send_private__ = Object.method_defined?(:fcall) ? :fcall : :funcall
def resolve
@pending = false
@actions.each{ |action| self.__send__(@@__send_private__, *action) }.clear
ix = ignore_rx
if ix
@items.reject! { |f| f =~ ix && !@keep[f] }
end
self
end
else
def resolve
@pending = false
@actions.each{ |action| self.__send__(*action) }.clear
ix = ignore_rx
if ix
@items.reject! { |f| f =~ ix && !@keep[f] }
end
self
end
end
def include(*pats)
@def_glob_dotfiles ? glob_all(*pats) : glob_unix(*pats)
end
alias glob include
def glob_unix(*patterns)
patterns.flatten.each { |pat|
@actions << [:apply_glob_unix, pat]
}
@pending = true
self
end
def glob_all(*patterns)
patterns.flatten.each { |pat|
@actions << [:apply_glob_all, pat]
}
@pending = true
self
end
if RUBY_VERSION < "1.8.2"
FN_DOTFILE_RX_ = ESC_ALT_SEPARATOR ?
/(^|(#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+)\..*
((#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+|$)/x :
/(^|#{ESC_SEPARATOR}+)\..* (#{ESC_SEPARATOR}+|$)/x
def apply_glob_unix(pattern)
inc_files = Dir.glob(pattern)
unless pattern =~ /(^|\/)\./
inc_files.reject! { |fn| fn =~ FN_DOTFILE_RX_ }
end
@items.concat(inc_files)
end
else
def apply_glob_unix(pattern)
@items.concat(Dir.glob(pattern))
end
end
private :apply_glob_unix
def apply_glob_all(pattern)
@items.concat(Dir.glob(pattern, File::FNM_DOTMATCH))
end
private :apply_glob_all
def exclude(*patterns)
patterns.each { |pat|
if Regexp === pat
@actions << [:apply_exclude_rx, pat]
else
@actions << [:apply_exclude, pat]
end
}
@pending = true
self
end
def ignore(*patterns)
patterns.each { |pat|
add_ignore_rx(Regexp === pat ? pat : mk_all_rx(pat))
}
@pending = true
self
end
def add_ignore_rx(rx)
@ignore_rx =
if @ignore_rx
Regexp.union(@ignore_rx, rx)
else
rx
end
end
private :add_ignore_rx
def apply_exclude(pattern)
@items.reject! { |elem|
File.fnmatch?(pattern, elem, File::FNM_DOTMATCH) && !@keep[elem]
}
end
private :apply_exclude
def apply_exclude_rx(rx)
@items.reject! { |elem|
elem =~ rx && !@keep[elem]
}
end
private :apply_exclude_rx
def exclude_name(*names)
names.each { |name|
@actions << [:apply_exclude_rx, mk_all_rx(name)]
}
@pending = true
self
end
alias shun exclude_name
if File::ALT_SEPARATOR
def mk_all_rx(file)
/(^|(#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+)#{Regexp.escape(file)}
((#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+|$)/x
end
else
def mk_all_rx(file)
/(^|#{ESC_SEPARATOR}+)#{Regexp.escape(file)}
(#{ESC_SEPARATOR}+|$)/x
end
end
private :mk_all_rx
def exclude_path(*patterns)
patterns.each { |pat|
@actions << [:apply_exclude_path, pat]
}
@pending = true
self
end
def apply_exclude_path(pattern)
flags = File::FNM_DOTMATCH|File::FNM_PATHNAME
@items.reject! { |elem|
File.fnmatch?(pattern, elem, flags) && !@keep[elem]
}
end
private :apply_exclude
def select(&block)
d = dup
d.actions << [:apply_select, block]
d.pending = true
d
end
alias find_all select
def apply_select blk
@items = @items.select(&blk)
end
private :apply_select
def map(&block)
d = dup
d.actions << [:apply_ary_method, :map!, block]
d.pending = true
d
end
alias collect map
def sub_ext(ext, new_ext=nil)
map { |f| f._rant_sub_ext ext, new_ext }
end
def ext(ext_str)
sub_ext(ext_str)
end
def arglist
Rant::Sys.sp to_ary
end
alias to_s arglist
alias object_inspect inspect
def uniq!
@actions << [:apply_ary_method, :uniq!]
@pending = true
self
end
def sort!
@actions << [:apply_ary_method, :sort!]
@pending = true
self
end
def map!(&block)
@actions << [:apply_ary_method, :map!, block]
@pending = true
self
end
def reject!(&block)
@actions << [:apply_ary_method, :reject!, block]
@pending = true
self
end
private
def apply_ary_method(meth, block=nil)
@items.send meth, &block
end
def apply_ary_method_1(meth, arg1, block=nil)
@items.send meth, arg1, &block
end
end # class FileList
end # module Rant
if RUBY_VERSION == "1.8.3"
module FileUtils
METHODS = singleton_methods - %w(private_module_function
commands options have_option? options_of collect_method)
module Verbose
class << self
public(*::FileUtils::METHODS)
end
public(*::FileUtils::METHODS)
end
end
end
if RUBY_VERSION < "1.8.1"
module FileUtils
undef_method :fu_list
def fu_list(arg)
arg.respond_to?(:to_ary) ? arg.to_ary : [arg]
end
end
end
module Rant
class RacFileList < FileList
attr_reader :subdir
attr_reader :basedir
def initialize(rac, store = [])
super(store)
@rac = rac
@subdir = @rac.current_subdir
@basedir = Dir.pwd
@ignore_hash = nil
@add_ignore_args = []
update_ignore_rx
end
def dup
c = super
c.instance_variable_set(
:@add_ignore_args, @add_ignore_args.dup)
c
end
def copy
c = super
c.instance_variable_set(
:@add_ignore_args, @add_ignore_args.map { |e| e.dup })
c
end
alias filelist_ignore ignore
def ignore(*patterns)
@add_ignore_args.concat patterns
self
end
def ignore_rx
update_ignore_rx
@ignore_rx
end
alias filelist_resolve resolve
def resolve
Sys.cd(@basedir) { filelist_resolve }
end
def each_cd(&block)
old_pwd = Dir.pwd
Sys.cd(@basedir)
filelist_resolve if @pending
@items.each(&block)
ensure
Sys.cd(old_pwd)
end
private
def update_ignore_rx
ri = @rac.var[:ignore]
ri = ri ? (ri + @add_ignore_args) : @add_ignore_args
rh = ri.hash
unless rh == @ignore_hash
@ignore_rx = nil
filelist_ignore(*ri)
@ignore_hash = rh
end
end
end # class RacFileList
class MultiFileList
attr_reader :cur_list
def initialize(rac)
@rac = rac
@cur_list = RacFileList.new(@rac)
@lists = [@cur_list]
end
def each_entry(&block)
@lists.each { |list|
list.each_cd(&block)
}
end
def add(filelist)
@cur_list = filelist
@lists << filelist
self
end
def method_missing(sym, *args, &block)
if @cur_list && @cur_list.respond_to?(sym)
if @cur_list.subdir == @rac.current_subdir
@cur_list.send(sym, *args, &block)
else
add(RacFileList.new(@rac))
@cur_list.send(sym, *args, &block)
end
else
super
end
end
end # class MultiFileList
class CommandError < StandardError
attr_reader :cmd
attr_reader :status
def initialize(cmd, status=nil, msg=nil)
@msg = msg
@cmd = cmd
@status = status
end
def message
if !@msg && cmd
if status
"Command failed with status #{status.exitstatus}:\n" +
"[#{cmd}]"
else
"Command failed:\n[#{cmd}]"
end
else
@msg
end
end
end
module Sys
include ::FileUtils::Verbose
@symlink_supported = true
class << self
attr_accessor :symlink_supported
end
def fu_output_message(msg) #:nodoc:
end
private :fu_output_message
def fu_each_src_dest(src, *rest)
src = src.to_ary if src.respond_to? :to_ary
super(src, *rest)
end
private :fu_each_src_dest
def sh(*cmd_args, &block)
cmd_args.flatten!
cmd = cmd_args.join(" ")
fu_output_message cmd
success = system(*cmd_args)
if block_given?
block[$?]
elsif !success
raise CommandError.new(cmd, $?)
end
end
def ruby(*args, &block)
if args.empty?
sh(Env::RUBY_EXE, '', &block)
else
sh(args.unshift(Env::RUBY_EXE), &block)
end
end
def cd(dir, &block)
fu_output_message "cd #{dir}"
orig_pwd = Dir.pwd
Dir.chdir dir
if block
begin
block.arity == 0 ? block.call : block.call(Dir.pwd)
ensure
fu_output_message "cd -"
Dir.chdir orig_pwd
end
else
self
end
end
def safe_ln(src, dest)
dest = dest.to_str
src = src.respond_to?(:to_ary) ? src.to_ary : src.to_str
unless Sys.symlink_supported
cp(src, dest)
else
begin
ln(src, dest)
rescue Exception # SystemCallError # Errno::EOPNOTSUPP
Sys.symlink_supported = false
cp(src, dest)
end
end
end
def ln_f(src, dest)
ln(src, dest, :force => true)
end
def split_path(str)
str.split(Env.on_windows? ? ";" : ":")
end
if Env.on_windows?
def root_dir?(path)
path == "/" || path == "\\" ||
path =~ %r{\A[a-zA-Z]+:(\\|/)\Z}
end
def absolute_path?(path)
path =~ %r{\A([a-zA-Z]+:)?(/|\\)}
end
else
def root_dir?(path)
path == "/"
end
def absolute_path?(path)
path =~ %r{\A/}
end
end
extend self
if RUBY_VERSION >= "1.8.4" # needed by 1.9.0, too
class << self
public(*::FileUtils::METHODS)
end
public(*::FileUtils::METHODS)
end
end # module Sys
class SysObject
include Sys
def initialize(rant)
@rant = rant or
raise ArgumentError, "rant application required"
end
def ignore(*patterns)
@rant.var[:ignore].concat(patterns)
nil
end
def filelist(arg = Rant.__rant_no_value__)
if Rant.__rant_no_value__.equal?(arg)
RacFileList.new(@rant)
elsif arg.respond_to?(:to_rant_filelist)
arg.to_rant_filelist
elsif arg.respond_to?(:to_ary)
RacFileList.new(@rant, arg.to_ary)
else
raise TypeError,
"cannot convert #{arg.class} into Rant::FileList"
end
end
def [](*patterns)
RacFileList.new(@rant).hide_dotfiles.include(*patterns)
end
def glob(*patterns, &block)
fl = RacFileList.new(@rant).hide_dotfiles.include(*patterns)
fl.ignore(".", "..")
if block_given? then yield fl else fl end
end
def glob_all(*patterns, &block)
fl = RacFileList.new(@rant).include(*patterns)
fl.ignore(".", "..") # use case: "*.*" as pattern
if block_given? then yield fl else fl end
end
def expand_path(path)
File.expand_path(@rant.project_to_fs_path(path))
end
private
def fu_output_message(cmd)
@rant.cmd_msg cmd
end
end
class TaskFail < StandardError
def initialize(task, orig, msg)
@task = task
@orig = orig
@msg = msg
end
def exception
self
end
def task
@task
end
def tname
@task ? @task.name : nil
end
def orig
@orig
end
def msg
@msg
end
end
class Rantfile
attr_reader :tasks, :path
attr_accessor :project_subdir
def initialize(path)
@path = path or raise ArgumentError, "path required"
@tasks = []
@project_subdir = nil
end
alias to_s path
alias to_str path
end # class Rantfile
module Node
INVOKE_OPT = {}.freeze
T0 = Time.at(0).freeze
attr_reader :name
attr_reader :rac
attr_accessor :description
attr_accessor :rantfile
attr_accessor :line_number
attr_accessor :project_subdir
def initialize
@description = nil
@rantfile = nil
@line_number = nil
@run = false
@project_subdir = ""
@success = nil
end
def reference_name
sd = rac.current_subdir
case sd
when ""; full_name
when project_subdir; name
else "@#{full_name}".sub(/^@#{Regexp.escape sd}\//, '')
end
end
alias to_s reference_name
alias to_rant_target name
def full_name
sd = project_subdir
sd.empty? ? name : File.join(sd, name)
end
def ch
{:file => rantfile.to_str, :ln => line_number}
end
def goto_task_home
@rac.goto_project_dir project_subdir
end
def file_target?
false
end
def done?
@success
end
def needed?
invoke(:needed? => true)
end
def run?
@run
end
def invoke(opt = INVOKE_OPT)
return circular_dep if run?
@run = true
begin
return !done? if opt[:needed?]
self.run if !done?
@success = true
ensure
@run = false
end
end
def fail msg = nil, orig = nil
raise TaskFail.new(self, orig, msg)
end
def each_target
end
def has_actions?
defined? @block and @block
end
def dry_run
text = "Executing #{name.dump}"
text << " [NOOP]" unless has_actions?
@rac.cmd_msg text
action_descs.each { |ad|
@rac.cmd_print " - "
@rac.cmd_msg ad.sub(/\n$/, '').gsub(/\n/, "\n ")
}
end
private
def run
goto_task_home
return if @rac.running_task(self)
return unless has_actions?
@receiver.pre_run(self) if defined? @receiver and @receiver
@block.arity == 0 ? @block.call : @block[self] if @block
end
def action_descs
descs = []
if defined? @receiver and @receiver
descs.concat(@receiver.pre_action_descs)
end
@block ? descs << action_block_desc : descs
end
def action_block_desc
@block.inspect =~ /^#<Proc:[\da-z]+@(.+):(\d+)>$/i
fn, ln = $1, $2
"Ruby Proc at #{fn.sub(/^#{Regexp.escape @rac.rootdir}\//, '')}:#{ln}"
end
def circular_dep
rac.warn_msg "Circular dependency on task `#{full_name}'."
false
end
end # module Node
def self.init_import_nodes__default(rac, *rest)
rac.node_factory = DefaultNodeFactory.new
end
class DefaultNodeFactory
def new_task(rac, name, pre, blk)
Task.new(rac, name, pre, &blk)
end
def new_file(rac, name, pre, blk)
FileTask.new(rac, name, pre, &blk)
end
def new_dir(rac, name, pre, blk)
DirTask.new(rac, name, pre, &blk)
end
def new_source(rac, name, pre, blk)
SourceNode.new(rac, name, pre, &blk)
end
def new_custom(rac, name, pre, blk)
UserTask.new(rac, name, pre, &blk)
end
def new_auto_subfile(rac, name, pre, blk)
AutoSubFileTask.new(rac, name, pre, &blk)
end
end
class Task
include Node
attr_accessor :receiver
def initialize(rac, name, prerequisites = [], &block)
super()
@rac = rac or raise ArgumentError, "rac not given"
@name = name or raise ArgumentError, "name not given"
@pre = prerequisites || []
@pre_resolved = false
@block = block
@run = false
@receiver = nil
end
def prerequisites
@pre.collect { |pre| pre.to_s }
end
alias deps prerequisites
def source
@pre.first.to_s
end
def has_actions?
@block or @receiver && @receiver.has_pre_action?
end
def <<(pre)
@pre_resolved = false
@pre << pre
end
def invoked?
!@success.nil?
end
def fail?
@success == false
end
def enhance(deps = nil, &blk)
if deps
@pre_resolved = false
@pre.concat deps
end
if @block
if blk
first_block = @block
@block = lambda { |t|
first_block[t]
blk[t]
}
end
else
@block = blk
end
end
def invoke(opt = INVOKE_OPT)
return circular_dep if @run
@run = true
begin
return if done?
internal_invoke opt
ensure
@run = false
end
end
def internal_invoke(opt, ud_init = true)
goto_task_home
update = ud_init || opt[:force]
dep = nil
uf = false
each_dep { |dep|
if dep.respond_to? :timestamp
handle_timestamped(dep, opt) && update = true
elsif Node === dep
handle_node(dep, opt) && update = true
else
dep, uf = handle_non_node(dep, opt)
uf && update = true
dep
end
}
if @receiver
goto_task_home
update = true if @receiver.update?(self)
end
return update if opt[:needed?]
run if update
@success = true
update
rescue StandardError => e
@success = false
self.fail(nil, e)
end
private :internal_invoke
def handle_node(dep, opt)
dep.invoke opt
end
def handle_timestamped(dep, opt)
dep.invoke opt
end
def handle_non_node(dep, opt)
@rac.err_msg "Unknown task `#{dep}',",
"referenced in `#{rantfile.path}', line #{@line_number}!"
self.fail
end
def each_dep
t = nil
if @pre_resolved
return @pre.each { |t| yield(t) }
end
my_full_name = full_name
my_project_subdir = project_subdir
@pre.map! { |t|
if Node === t
if t.full_name == my_full_name
nil
else
yield(t)
t
end
else
t = t.to_s if Symbol === t
if t == my_full_name #TODO
nil
else
selection = @rac.resolve t,
my_project_subdir
if selection.empty?
yield(t)
else
selection.each { |st| yield(st) }
selection
end
end
end
}
if @pre.kind_of? Rant::FileList
@pre.resolve
else
@pre.flatten!
@pre.compact!
end
@pre_resolved = true
end
end # class Task
class UserTask < Task
def initialize(*args)
super
@block = nil
@needed = nil
@target_files = nil
yield self if block_given?
end
def act(&block)
@block = block
end
def needed(&block)
@needed = block
end
def file_target?
@target_files and @target_files.include? @name
end
def each_target(&block)
goto_task_home
@target_files.each(&block) if @target_files
end
def file_target(*args)
args.flatten!
args << @name if args.empty?
if @target_files
@target_files.concat(args)
else
@target_files = args
end
end
def invoke(opt = INVOKE_OPT)
return circular_dep if @run
@run = true
begin
return if done?
internal_invoke(opt, ud_init_by_needed)
ensure
@run = false
end
end
private
def ud_init_by_needed
if @needed
goto_task_home
@needed.arity == 0 ? @needed.call : @needed[self]
end
end
end # class UserTask
class FileTask < Task
def initialize(*args)
super
@ts = T0
end
def file_target?
true
end
def invoke(opt = INVOKE_OPT)
return circular_dep if @run
@run = true
begin
return if done?
goto_task_home
if File.exist? @name
@ts = File.mtime @name
internal_invoke opt, false
else
@ts = T0
internal_invoke opt, true
end
ensure
@run = false
end
end
def timestamp(opt = INVOKE_OPT)
File.exist?(@name) ? File.mtime(@name) : T0
end
def handle_node(dep, opt)
return true if dep.file_target? && dep.invoke(opt)
if File.exist? dep.name
File.mtime(dep.name) > @ts
elsif !dep.file_target?
@rac.err_msg @rac.pos_text(rantfile.path, line_number),
"in prerequisites: no such file: `#{dep.full_name}'"
self.fail
end
end
def handle_timestamped(dep, opt)
return true if dep.invoke opt
dep.timestamp(opt) > @ts
end
def handle_non_node(dep, opt)
goto_task_home # !!??
unless File.exist? dep
@rac.err_msg @rac.pos_text(rantfile.path, line_number),
"in prerequisites: no such file or task: `#{dep}'"
self.fail
end
[dep, File.mtime(dep) > @ts]
end
def each_target
goto_task_home
yield name
end
end # class FileTask
module AutoInvokeDirNode
private
def run
goto_task_home
return if @rac.running_task(self)
dir = File.dirname(name)
@rac.build dir unless dir == "." || dir == "/"
return unless @block
@block.arity == 0 ? @block.call : @block[self]
end
end
class AutoSubFileTask < FileTask
include AutoInvokeDirNode
end
class DirTask < Task
def initialize(*args)
super
@ts = T0
@isdir = nil
end
def invoke(opt = INVOKE_OPT)
return circular_dep if @run
@run = true
begin
return if done?
goto_task_home
@isdir = test(?d, @name)
if @isdir
@ts = @block ? test(?M, @name) : Time.now
internal_invoke opt, false
else
@ts = T0
internal_invoke opt, true
end
ensure
@run = false
end
end
def file_target?
true
end
def handle_node(dep, opt)
return true if dep.file_target? && dep.invoke(opt)
if File.exist? dep.name
File.mtime(dep.name) > @ts
elsif !dep.file_target?
@rac.err_msg @rac.pos_text(rantfile.path, line_number),
"in prerequisites: no such file: `#{dep.full_name}'"
self.fail
end
end
def handle_timestamped(dep, opt)
return @block if dep.invoke opt
@block && dep.timestamp(opt) > @ts
end
def handle_non_node(dep, opt)
goto_task_home
unless File.exist? dep
@rac.err_msg @rac.pos_text(rantfile.path, line_number),
"in prerequisites: no such file or task: `#{dep}'"
self.fail
end
[dep, @block && File.mtime(dep) > @ts]
end
def run
return if @rac.running_task(self)
@rac.sys.mkdir @name unless @isdir
if @block
@block.arity == 0 ? @block.call : @block[self]
goto_task_home
@rac.sys.touch @name
end
end
def each_target
goto_task_home
yield name
end
end # class DirTask
class SourceNode
include Node
def initialize(rac, name, prerequisites = [])
super()
@rac = rac
@name = name or raise ArgumentError, "name not given"
@pre = prerequisites
@run = false
@ts = nil
end
def prerequisites
@pre
end
def timestamp(opt = INVOKE_OPT)
return @ts if @ts
goto_task_home
if File.exist?(@name)
@ts = File.mtime @name
else
rac.abort_at(ch, "SourceNode: no such file -- #@name")
end
sd = project_subdir
@pre.each { |f|
nodes = rac.resolve f, sd
if nodes.empty?
if File.exist? f
mtime = File.mtime f
@ts = mtime if mtime > @ts
else
rac.abort_at(ch,
"SourceNode: no such file -- #{f}")
end
else
nodes.each { |node|
node.invoke(opt)
if node.respond_to? :timestamp
node_ts = node.timestamp(opt)
goto_task_home
@ts = node_ts if node_ts > @ts
else
rac.abort_at(ch,
"SourceNode can't depend on #{node.name}")
end
}
end
}
@ts
end
def invoke(opt = INVOKE_OPT)
false
end
def related_sources
@pre
end
end # class SourceNode
module Generators
class Task
def self.rant_gen(rac, ch, args, &block)
unless args.size == 1
rac.abort("Task takes only one argument " +
"which has to be like one given to the " +
"`task' function")
end
rac.prepare_task(args.first, nil, ch) { |name,pre,blk|
rac.node_factory.new_custom(rac, name, pre, block)
}
end
end
class Directory
def self.rant_gen(rac, ch, args, &block)
case args.size
when 1
name, pre = rac.normalize_task_arg(args.first, ch)
self.task(rac, ch, name, pre, &block)
when 2
basedir = args.shift
if basedir.respond_to? :to_str
basedir = basedir.to_str
else
rac.abort_at(ch,
"Directory: basedir argument has to be a string.")
end
name, pre = rac.normalize_task_arg(args.first, ch)
self.task(rac, ch, name, pre, basedir, &block)
else
rac.abort_at(ch, "Directory takes one argument, " +
"which should be like one given to the `task' command.")
end
end
def self.task(rac, ch, name, prerequisites=[], basedir=nil, &block)
dirs = ::Rant::Sys.split_all(name)
if dirs.empty?
rac.abort_at(ch,
"Not a valid directory name: `#{name}'")
end
path = basedir
last_task = nil
task_block = nil
desc_for_last = rac.pop_desc
dirs.each { |dir|
pre = [path]
pre.compact!
if dir.equal?(dirs.last)
rac.cx.desc desc_for_last
dp = prerequisites.dup
pre.each { |elem| dp << elem }
pre = dp
task_block = block
end
path = path.nil? ? dir : File.join(path, dir)
last_task = rac.prepare_task({:__caller__ => ch,
path => pre}, task_block) { |name,pre,blk|
rac.node_factory.new_dir(rac, name, pre, blk)
}
}
last_task
end
end # class Directory
class SourceNode
def self.rant_gen(rac, ch, args)
unless args.size == 1
rac.abort_at(ch, "SourceNode takes one argument.")
end
if block_given?
rac.abort_at(ch, "SourceNode doesn't take a block.")
end
rac.prepare_task(args.first, nil, ch) { |name, pre, blk|
rac.node_factory.new_source(rac, name, pre, blk)
}
end
end
class Rule
def self.rant_gen(rac, ch, args, &block)
unless args.size == 1
rac.abort_at(ch, "Rule takes only one argument.")
end
rac.abort_at(ch, "Rule: block required.") unless block
arg = args.first
target = nil
src_arg = nil
if Symbol === arg
target = ".#{arg}"
elsif arg.respond_to? :to_str
target = arg.to_str
elsif Regexp === arg
target = arg
elsif Hash === arg && arg.size == 1
arg.each_pair { |target, src_arg| }
src_arg = src_arg.to_str if src_arg.respond_to? :to_str
target = target.to_str if target.respond_to? :to_str
src_arg = ".#{src_arg}" if Symbol === src_arg
target = ".#{target}" if Symbol === target
else
rac.abort_at(ch, "Rule argument " +
"has to be a hash with one key-value pair.")
end
esc_target = nil
target_rx = case target
when String
esc_target = Regexp.escape(target)
/#{esc_target}$/
when Regexp
target
else
rac.abort_at(ch, "rule target has " +
"to be a string or regular expression")
end
src_proc = case src_arg
when String, Array
unless String === target
rac.abort(ch, "rule target has to be " +
"a string if source is a string")
end
if src_arg.kind_of? String
lambda { |name|
name.sub(/#{esc_target}$/, src_arg)
}
else
lambda { |name|
src_arg.collect { |s_src|
s_src = ".#{s_src}" if Symbol === s_src
name.sub(/#{esc_target}$/, s_src)
}
}
end
when Proc; src_arg
when nil; lambda { |name| [] }
else
rac.abort_at(ch, "rule source has to be a " +
"String, Array or Proc")
end
rac.resolve_hooks <<
(block.arity == 2 ? Hook : FileHook).new(
rac, ch, target_rx, src_proc, block)
nil
end
class Hook
attr_accessor :target_rx
def initialize(rant, ch, target_rx, src_proc, block)
@rant = rant
@ch = ch
@target_rx = target_rx
@src_proc = src_proc
@block = block
end
def call(target, rel_project_dir)
if @target_rx =~ target
have_src = true
src = @src_proc[target]
if src.respond_to? :to_ary
have_src = src.to_ary.all? { |s|
have_src?(rel_project_dir, s)
}
else
have_src = have_src?(rel_project_dir, src)
end
if have_src
create_nodes(rel_project_dir, target, src)
end
end
end
alias [] call
private
def have_src?(rel_project_dir, name)
return true unless
@rant.rec_save_resolve(name, self, rel_project_dir).empty?
test(?e, @rant.abs_path(rel_project_dir, name))
end
def create_nodes(rel_project_dir, target, deps)
@rant.goto_project_dir rel_project_dir
case nodes = @block[target, deps]
when Array; nodes
when Node; [nodes]
else
@rant.abort_at(@ch, "Block has to " +
"return Node or array of Nodes.")
end
end
end
class FileHook < Hook
private
def have_src?(rel_project_dir, name)
test(?e, @rant.abs_path(rel_project_dir, name)) or
@rant.rec_save_resolve(name, self, rel_project_dir
).any? { |t| t.file_target? }
end
def create_nodes(rel_project_dir, target, deps)
@rant.goto_project_dir rel_project_dir
t = @rant.file(:__caller__ => @ch,
target => deps, &@block)
[t]
end
end
end # class Rule
class Action
def self.rant_gen(rac, ch, args, &block)
case args.size
when 0
unless (rac[:tasks] || rac[:stop_after_load])
yield
end
when 1
rx = args.first
unless rx.kind_of? Regexp
rac.abort_at(ch, "Action: argument has " +
"to be a regular expression.")
end
rac.resolve_hooks << self.new(rac, block, rx)
nil
else
rac.abort_at(ch, "Action: too many arguments.")
end
end
def initialize(rant, block, rx)
@rant = rant
@subdir = @rant.current_subdir
@block = block
@rx = rx
end
def call(target, rel_project_dir)
if target =~ @rx
@rant.resolve_hooks.delete(self)
@rant.goto_project_dir @subdir
@block.call
@rant.resolve(target, rel_project_dir)
end
end
alias [] call
end
end # module Generators
end # module Rant
Rant::MAIN_OBJECT = self
class String
alias sub_ext _rant_sub_ext
def to_rant_target
self
end
end
module Rant::Lib
def parse_caller_elem(elem)
return { :file => "", :ln => 0 } unless elem
if elem =~ /^(.+):(\d+)(?::|$)/
{ :file => $1, :ln => $2.to_i }
else
$stderr.puts "parse_caller_elem: #{elem.inspect}"
{ :file => elem, :ln => 0 }
end
end
module_function :parse_caller_elem
end # module Lib
module Rant::Console
RANT_PREFIX = "rant: "
ERROR_PREFIX = "[ERROR] "
WARN_PREFIX = "[WARNING] "
def msg_prefix
if defined? @msg_prefix and @msg_prefix
@msg_prefix
else
RANT_PREFIX
end
end
def msg(*text)
pre = msg_prefix
$stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
end
def vmsg(importance, *text)
msg(*text) if verbose >= importance
end
def err_msg(*text)
pre = msg_prefix + ERROR_PREFIX
$stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
end
def warn_msg(*text)
pre = msg_prefix + WARN_PREFIX
$stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
end
def ask_yes_no text
$stderr.print msg_prefix + text + " [y|n] "
case $stdin.readline
when /y|yes/i; true
when /n|no/i; false
else
$stderr.puts(' ' * msg_prefix.length +
"Please answer with `yes' or `no'")
ask_yes_no text
end
end
def prompt text
$stderr.print msg_prefix + text
input = $stdin.readline
input ? input.chomp : input
end
def option_listing opts
rs = ""
opts.each { |lopt, *opt_a|
if opt_a.size == 2
mode, desc = opt_a
else
sopt, mode, desc = opt_a
end
next unless desc # "private" option
optstr = ""
arg = nil
if mode != GetoptLong::NO_ARGUMENT
if desc =~ /(\b[A-Z_]{2,}\b)/
arg = $1
end
end
if lopt
optstr << lopt
if arg
optstr << " " << arg
end
optstr = optstr.ljust(30)
end
if sopt
optstr << " " unless optstr.empty?
optstr << sopt
if arg
optstr << " " << arg
end
end
rs << " #{optstr}\n"
rs << " #{desc.split("\n").join("\n ")}\n"
}
rs
end
extend self
end # module Rant::Console
module RantContext
include Rant::Generators
Env = Rant::Env
FileList = Rant::FileList
def task(targ, &block)
rant.task(targ, &block)
end
def file(targ, &block)
rant.file(targ, &block)
end
def enhance(targ, &block)
rant.enhance(targ, &block)
end
def desc(*args)
rant.desc(*args)
end
def gen(*args, &block)
rant.gen(*args, &block)
end
def import(*args, &block)
rant.import(*args, &block)
end
def plugin(*args, &block)
rant.plugin(*args, &block)
end
def subdirs(*args)
rant.subdirs(*args)
end
def source(opt, rantfile = nil)
rant.source(opt, rantfile)
end
def sys(*args, &block)
rant.sys(*args, &block)
end
def var(*args, &block)
rant.var(*args, &block)
end
def make(*args, &block)
rant.make(*args, &block)
end
end # module RantContext
class RantAppContext
include RantContext
def initialize(app)
@__rant__ = app
end
def rant
@__rant__
end
def method_missing(sym, *args)
Rant::MAIN_OBJECT.send(sym, *args)
rescue NoMethodError
raise NameError, "NameError: undefined local " +
"variable or method `#{sym}' for main:Object", caller
end
end
module Rant
@__rant__ = nil
class << self
def run(first_arg=nil, *other_args)
other_args = other_args.flatten
args = first_arg.nil? ? ARGV.dup : ([first_arg] + other_args)
if rant && !rant.run?
rant.run(args.flatten)
else
@__rant__ = Rant::RantApp.new
rant.run(args)
end
end
def rant
@__rant__
end
end
end # module Rant
class Rant::RantApp
include Rant::Console
class AutoLoadNodeFactory
def initialize(rant)
@rant = rant
end
def method_missing(sym, *args, &block)
@rant.import "nodes/default"
@rant.node_factory.send(sym, *args, &block)
end
end
OPTIONS = [
[ "--help", "-h", GetoptLong::NO_ARGUMENT,
"Print this help and exit." ],
[ "--version", "-V", GetoptLong::NO_ARGUMENT,
"Print version of Rant and exit." ],
[ "--verbose", "-v", GetoptLong::NO_ARGUMENT,
"Print more messages to stderr." ],
[ "--quiet", "-q", GetoptLong::NO_ARGUMENT,
"Don't print commands." ],
[ "--err-commands", GetoptLong::NO_ARGUMENT,
"Print failed commands and their exit status." ],
[ "--directory","-C", GetoptLong::REQUIRED_ARGUMENT,
"Run rant in DIRECTORY." ],
[ "--cd-parent","-c", GetoptLong::NO_ARGUMENT,
"Run rant in parent directory with Rantfile." ],
[ "--look-up", "-u", GetoptLong::NO_ARGUMENT,
"Look in parent directories for root Rantfile." ],
[ "--rantfile", "-f", GetoptLong::REQUIRED_ARGUMENT,
"Process RANTFILE instead of standard rantfiles.\n" +
"Multiple files may be specified with this option." ],
[ "--force-run","-a", GetoptLong::REQUIRED_ARGUMENT,
"Force rebuild of TARGET and all dependencies." ],
[ "--dry-run", "-n", GetoptLong::NO_ARGUMENT,
"Print info instead of actually executing actions." ],
[ "--tasks", "-T", GetoptLong::NO_ARGUMENT,
"Show a list of all described tasks and exit." ],
[ "--import", "-i", GetoptLong::REQUIRED_ARGUMENT, nil ],
[ "--stop-after-load", GetoptLong::NO_ARGUMENT, nil ],
[ "--trace-abort", GetoptLong::NO_ARGUMENT, nil ],
]
ROOT_DIR_ID = "@"
ESCAPE_ID = "\\"
attr_reader :args
attr_reader :rantfiles
attr_reader :force_targets
attr_reader :plugins
attr_reader :context
alias cx context
attr_reader :tasks
attr_reader :imports
attr_reader :current_subdir
attr_reader :resolve_hooks
attr_reader :rootdir
attr_accessor :node_factory
def initialize
@args = []
@context = RantAppContext.new(self)
@sys = ::Rant::SysObject.new(self)
@rantfiles = []
@tasks = {}
@opts = {
:verbose => 0,
:quiet => false,
}
@rootdir = Dir.pwd # root directory of project
@arg_rantfiles = [] # rantfiles given in args
@arg_targets = [] # targets given in args
@force_targets = [] # targets given with -a option
@run = false # run method was called at least once
@done = false # run method was successful
@plugins = []
@var = Rant::RantVar::Space.new
@var.query :ignore, :AutoList, []
@imports = []
@task_desc = nil
@last_build_subdir = ""
@current_subdir = ""
@resolve_hooks = []
@node_factory = AutoLoadNodeFactory.new(self)
end
def [](opt)
@opts[opt]
end
def []=(opt, val)
@opts[opt] = val
end
def expand_path(subdir, path)
case path
when nil; subdir.dup
when ""; subdir.dup
when /^@/; path.sub(/^@/, '')
else
path = path.sub(/^\\(?=@)/, '')
if subdir.empty?
path
else
File.join(subdir, path)
end
end
end
def resolve_root_ref(path)
return File.join(@rootdir, path[1..-1]) if path =~ /^@/
path.sub(/^\\(?=@)/, '')
end
def project_to_fs_path(path)
sub = expand_path(@current_subdir, path)
sub.empty? ? @rootdir : File.join(@rootdir, sub)
end
def abs_path(subdir, fn)
return fn if Rant::Sys.absolute_path?(fn)
path = File.join(@rootdir, subdir, fn)
path.gsub!(%r{/+}, "/")
path.sub!(%r{/$}, "") if path.length > 1
path
end
def goto(dir)
goto_project_dir(expand_path(@current_subdir, dir))
end
def goto_project_dir(dir='')
@current_subdir = dir
abs_path = @current_subdir.empty? ?
@rootdir : File.join(@rootdir, @current_subdir)
unless Dir.pwd == abs_path
Dir.chdir abs_path
vmsg 1, "in #{abs_path}"
end
end
def run?
@run
end
def done?
@done
end
def run(*args)
@run = true
@args.concat(args.flatten)
orig_pwd = @rootdir = Dir.pwd
process_args
Dir.chdir(@rootdir) rescue abort $!.message
load_rantfiles
raise Rant::RantDoneException if @opts[:stop_after_load]
@plugins.each { |plugin| plugin.rant_start }
if @opts[:tasks]
show_descriptions
raise Rant::RantDoneException
end
run_tasks
raise Rant::RantDoneException
rescue Rant::RantDoneException
@done = true
@plugins.each { |plugin| plugin.rant_done }
return 0
rescue Rant::RantAbortException
$stderr.puts "rant aborted!"
return 1
rescue Exception => e
ch = get_ch_from_backtrace(e.backtrace)
if ch && !@opts[:trace_abort]
err_msg(pos_text(ch[:file], ch[:ln]), e.message)
else
err_msg e.message, e.backtrace[0..4]
end
$stderr.puts "rant aborted!"
return 1
ensure
Dir.chdir @rootdir if test ?d, @rootdir
hooks = var._get("__at_return__")
hooks.each { |hook| hook.call } if hooks
@plugins.each { |plugin| plugin.rant_plugin_stop }
@plugins.each { |plugin| plugin.rant_quit }
Dir.chdir orig_pwd
end
def desc(*args)
if args.empty? || (args.size == 1 && args.first.nil?)
@task_desc = nil
else
@task_desc = args.join("\n")
end
end
def task(targ, &block)
prepare_task(targ, block) { |name,pre,blk|
@node_factory.new_task(self, name, pre, blk)
}
end
def file(targ, &block)
prepare_task(targ, block) { |name,pre,blk|
@node_factory.new_file(self, name, pre, blk)
}
end
def gen(*args, &block)
ch = Rant::Lib::parse_caller_elem(caller[1])
generator = args.shift
unless generator.respond_to? :rant_gen
abort_at(ch,
"gen: First argument has to be a task-generator.")
end
generator.rant_gen(self, ch, args, &block)
end
def import(*args, &block)
ch = Rant::Lib::parse_caller_elem(caller[1])
if block
warn_msg pos_text(ch[:file], ch[:ln]),
"import: ignoring block"
end
args.flatten.each { |arg|
unless String === arg
abort_at(ch, "import: only strings allowed as arguments")
end
unless @imports.include? arg
unless Rant::CODE_IMPORTS.include? arg
begin
vmsg 2, "import #{arg}"
require "rant/import/#{arg}"
rescue LoadError => e
abort_at(ch, "No such import - #{arg}")
end
Rant::CODE_IMPORTS << arg.dup
end
init_msg = "init_import_#{arg.gsub(/[^\w]/, '__')}"
Rant.send init_msg, self if Rant.respond_to? init_msg
@imports << arg.dup
end
}
end
def plugin(*args, &block)
clr = caller[1]
ch = Rant::Lib::parse_caller_elem(clr)
name = nil
pre = []
ln = ch[:ln] || 0
file = ch[:file]
pl_name = args.shift
pl_name = pl_name.to_str if pl_name.respond_to? :to_str
pl_name = pl_name.to_s if pl_name.is_a? Symbol
unless pl_name.is_a? String
abort(pos_text(file, ln),
"Plugin name has to be a string or symbol.")
end
lc_pl_name = pl_name.downcase
import_name = "plugin/#{lc_pl_name}"
unless Rant::CODE_IMPORTS.include? import_name
begin
require "rant/plugin/#{lc_pl_name}"
Rant::CODE_IMPORTS << import_name
rescue LoadError
abort(pos_text(file, ln),
"no such plugin library -- #{lc_pl_name}")
end
end
pl_class = nil
begin
pl_class = ::Rant::Plugin.const_get(pl_name)
rescue NameError, ArgumentError
abort(pos_text(file, ln),
"no such plugin -- #{pl_name}")
end
plugin = pl_class.rant_plugin_new(self, ch, *args, &block)
@plugins << plugin
vmsg 2, "Plugin `#{plugin.rant_plugin_name}' registered."
plugin.rant_plugin_init
plugin
end
def enhance(targ, &block)
prepare_task(targ, block) { |name,pre,blk|
t = resolve(name).last
if t
unless t.respond_to? :enhance
abort("Can't enhance task `#{name}'")
end
t.enhance(pre, &blk)
return t
end
warn_msg "enhance \"#{name}\": no such task",
"Generating a new file task with the given name."
@node_factory.new_file(self, name, pre, blk)
}
end
def source(opt, rantfile = nil)
unless rantfile
rantfile = opt
opt = nil
end
make_rf = opt != :n && opt != :now
rf, is_new = rantfile_for_path(rantfile)
return false unless is_new
make rantfile if make_rf
unless File.exist? rf.path
abort("source: No such file -- #{rantfile}")
end
load_file rf
end
def subdirs(*args)
args.flatten!
ch = Rant::Lib::parse_caller_elem(caller[1])
args.each { |arg|
if arg.respond_to? :to_str
arg = arg.to_str
else
abort_at(ch, "subdirs: arguments must be strings")
end
loaded = false
prev_subdir = @current_subdir
begin
goto arg
if test(?f, Rant::SUB_RANTFILE)
path = Rant::SUB_RANTFILE
else
path = rantfile_in_dir
end
if path
if defined? @initial_subdir and
@initial_subdir == @current_subdir
rf, is_new = rantfile_for_path(path, false)
@rantfiles.unshift rf if is_new
else
rf, is_new = rantfile_for_path(path)
end
load_file rf if is_new
elsif !@opts[:no_warn_subdir]
warn_msg(pos_text(ch[:file], ch[:ln]),
"subdirs: No Rantfile in subdir `#{arg}'.")
end
ensure
goto_project_dir prev_subdir
end
}
rescue SystemCallError => e
abort_at(ch, "subdirs: " + e.message)
end
def sys(*args, &block)
args.empty? ? @sys : @sys.sh(*args, &block)
end
def var(*args, &block)
args.empty? ? @var : @var.query(*args, &block)
end
def pop_desc
td = @task_desc
@task_desc = nil
td
end
def abort(*msg)
err_msg(msg) unless msg.empty?
$stderr.puts caller if @opts[:trace_abort]
raise Rant::RantAbortException
end
def abort_at(ch, *msg)
err_msg(pos_text(ch[:file], ch[:ln]), msg)
$stderr.puts caller if @opts[:trace_abort]
raise Rant::RantAbortException
end
def show_help
puts "rant [-f Rantfile] [Options] [targets]"
puts
puts "Options are:"
print option_listing(OPTIONS)
end
def show_descriptions
tlist = select_tasks { |t| t.description }
def_target = target_list.first
if tlist.empty?
puts "rant # => " + list_task_names(
resolve(def_target)).join(', ')
msg "No described tasks."
return
end
prefix = "rant "
infix = " # "
name_length = (tlist.map{ |t| t.to_s.length } << 7).max
cmd_length = prefix.length + name_length
unless tlist.first.to_s == def_target
defaults = list_task_names(
resolve(def_target)).join(', ')
puts "#{prefix}#{' ' * name_length}#{infix}=> #{defaults}"
end
tlist.each { |t|
print(prefix + t.to_s.ljust(name_length) + infix)
dt = t.description.sub(/\s+$/, "")
puts dt.gsub(/\n/, "\n" + ' ' * cmd_length + infix + " ")
}
true
end
def list_task_names(*tasks)
rsl = []
tasks.flatten.each { |t|
if t.respond_to?(:has_actions?) && t.has_actions?
rsl << t
elsif t.respond_to? :prerequisites
if t.prerequisites.empty?
rsl << t
else
t.prerequisites.each { |pre|
rsl.concat(list_task_names(
resolve(pre, t.project_subdir)))
}
end
else
rsl << t
end
}
rsl
end
private :list_task_names
def verbose
@opts[:verbose]
end
def quiet?
@opts[:quiet]
end
def pos_text(file, ln)
t = "in file `#{file}'"
t << ", line #{ln}" if ln && ln > 0
t << ": "
end
def cmd_msg(cmd)
puts cmd unless quiet?
end
def cmd_print(text)
print text unless quiet?
$stdout.flush
end
def cmd_targets
@force_targets + @arg_targets
end
def running_task(task)
if @current_subdir != @last_build_subdir
cmd_msg "(in #{@current_subdir.empty? ?
@rootdir : @current_subdir})"
@last_build_subdir = @current_subdir
end
if @opts[:dry_run]
task.dry_run
true
end
end
private
def have_any_task?
!@tasks.empty?
end
def target_list
if !have_any_task? && @resolve_hooks.empty?
abort("No tasks defined for this rant application!")
end
target_list = @force_targets + @arg_targets
if target_list.empty?
def_tasks = resolve "default"
unless def_tasks.empty?
target_list << "default"
else
@rantfiles.each { |f|
first = f.tasks.first
if first
target_list << first.reference_name
break
end
}
end
end
target_list
end
def run_tasks
target_list.each { |target|
if build(target) == 0
abort("Don't know how to make `#{target}'.")
end
}
end
def make(target, *args, &block)
ch = nil
if target.respond_to? :to_hash
targ = target.to_hash
ch = Rant::Lib.parse_caller_elem(caller[1])
abort_at(ch, "make: too many arguments") unless args.empty?
tn = nil
prepare_task(targ, block, ch) { |name,pre,blk|
tn = name
@node_factory.new_file(self, name, pre, blk)
}
build(tn)
elsif target.respond_to? :to_rant_target
rt = target.to_rant_target
opt = args.shift
unless args.empty?
ch ||= Rant::Lib.parse_caller_elem(caller[1])
abort_at(ch, "make: too many arguments")
end
if block
ch ||= Rant::Lib.parse_caller_elem(caller[1])
prepare_task(rt, block, ch) { |name,pre,blk|
@node_factory.new_file(self, name, pre, blk)
}
build(rt)
else
build(rt, opt||{})
end
elsif target.respond_to? :rant_gen
ch = Rant::Lib.parse_caller_elem(caller[1])
rv = target.rant_gen(self, ch, args, &block)
unless rv.respond_to? :to_rant_target
abort_at(ch, "make: invalid generator return value")
end
build(rv.to_rant_target)
rv
else
ch = Rant::Lib.parse_caller_elem(caller[1])
abort_at(ch,
"make: generator or target as first argument required.")
end
end
public :make
def build(target, opt = {})
opt[:force] = true if @force_targets.delete(target)
opt[:dry_run] = @opts[:dry_run]
matching_tasks = 0
old_subdir = @current_subdir
old_pwd = Dir.pwd
resolve(target).each { |t|
unless opt[:type] == :file && !t.file_target?
matching_tasks += 1
begin
t.invoke(opt)
rescue Rant::TaskFail => e
err_task_fail(e)
abort
end
end
}
@current_subdir = old_subdir
Dir.chdir old_pwd
matching_tasks
end
public :build
def resolve(task_name, rel_project_dir = @current_subdir)
s = @tasks[expand_path(rel_project_dir, task_name)]
case s
when nil
@resolve_hooks.each { |s|
s = s[task_name, rel_project_dir]
return s if s
}
[]
when Rant::Node; [s]
else # assuming list of tasks
s
end
end
public :resolve
def rec_save_resolve(task_name, excl_hook, rel_project_dir = @current_subdir)
s = @tasks[expand_path(rel_project_dir, task_name)]
case s
when nil
@resolve_hooks.each { |s|
next if s == excl_hook
s = s[task_name, rel_project_dir]
return s if s
}
[]
when Rant::Node; [s]
else
s
end
end
public :rec_save_resolve
def at_resolve(&block)
@resolve_hooks << block if block
end
public :at_resolve
def at_return(&block)
hooks = var._get("__at_return__")
if hooks
hooks << block
else
var._set("__at_return__", [block])
end
end
public :at_return
def select_tasks
selection = []
@rantfiles.each { |rf|
rf.tasks.each { |t|
selection << t if yield t
}
}
selection
end
public :select_tasks
def load_rantfiles
unless @arg_rantfiles.empty?
@arg_rantfiles.each { |fn|
if test(?f, fn)
rf, is_new = rantfile_for_path(fn)
load_file rf if is_new
else
abort "No such file -- #{fn}"
end
}
return
end
return if have_any_task?
fn = rantfile_in_dir
if @opts[:cd_parent]
old_root = @rootdir
until fn or @rootdir == "/"
@rootdir = File.dirname(@rootdir)
fn = rantfile_in_dir(@rootdir)
end
if @rootdir != old_root and fn
Dir.chdir @rootdir
cmd_msg "(in #@rootdir)"
end
end
if fn
rf, is_new = rantfile_for_path(fn)
load_file rf if is_new
return
end
have_sub_rantfile = test(?f, Rant::SUB_RANTFILE)
if have_sub_rantfile || @opts[:look_up]
cur_dir = Dir.pwd
until cur_dir == "/"
cur_dir = File.dirname(cur_dir)
Dir.chdir cur_dir
fn = rantfile_in_dir
if fn
@initial_subdir = @rootdir.sub(
/^#{Regexp.escape cur_dir}\//, '')
@rootdir = cur_dir
cmd_msg "(root is #@rootdir, in #@initial_subdir)"
@last_build_subdir = @initial_subdir
rf, is_new = rantfile_for_path(fn)
load_file rf if is_new
goto_project_dir @initial_subdir
if have_sub_rantfile
rf, is_new = rantfile_for_path(
Rant::SUB_RANTFILE, false)
if is_new
@rantfiles.unshift rf
load_file rf
end
end
break
end
end
end
if @rantfiles.empty?
abort("No Rantfile found, looking for:",
Rant::RANTFILES.join(", "))
end
end
def load_file(rantfile)
vmsg 1, "source #{rantfile}"
@context.instance_eval(File.read(rantfile), rantfile)
end
private :load_file
def rantfile_in_dir(dir=nil)
::Rant::RANTFILES.each { |rfn|
path = dir ? File.join(dir, rfn) : rfn
return path if test ?f, path
}
nil
end
def process_args
old_argv = ARGV.dup
ARGV.replace(@args.dup)
cmd_opts = GetoptLong.new(*OPTIONS.collect { |lst| lst[0..-2] })
cmd_opts.quiet = true
cmd_opts.each { |opt, value|
case opt
when "--verbose"; @opts[:verbose] += 1
when "--version"
puts "rant #{Rant::VERSION}"
raise Rant::RantDoneException
when "--help"
show_help
raise Rant::RantDoneException
when "--directory"
@rootdir = File.expand_path(value)
when "--rantfile"
@arg_rantfiles << value
when "--force-run"
@force_targets << value
when "--import"
import value
else
@opts[opt.sub(/^--/, '').tr('-', "_").to_sym] = true
end
}
rescue GetoptLong::Error => e
abort(e.message)
ensure
rem_args = ARGV.dup
ARGV.replace(old_argv)
rem_args.each { |ra|
if ra =~ /(^[^=]+)=([^=]+)$/
vmsg 2, "var: #$1=#$2"
@var[$1] = $2
else
@arg_targets << ra
end
}
end
def prepare_task(targ, block, clr = caller[2])
if targ.is_a? Hash
targ.reject! { |k, v| clr = v if k == :__caller__ }
end
ch = Hash === clr ? clr : Rant::Lib::parse_caller_elem(clr)
name, pre = normalize_task_arg(targ, ch)
file, is_new = rantfile_for_path(ch[:file])
nt = yield(name, pre, block)
nt.rantfile = file
nt.project_subdir = @current_subdir
nt.line_number = ch[:ln]
nt.description = @task_desc
@task_desc = nil
file.tasks << nt
hash_task nt
nt
end
public :prepare_task
def hash_task(task)
n = task.full_name
et = @tasks[n]
case et
when nil
@tasks[n] = task
when Rant::Node
mt = [et, task]
@tasks[n] = mt
else # assuming list of tasks
et << task
end
end
def normalize_task_arg(targ, ch)
name = nil
pre = []
if targ.is_a? Hash
if targ.empty?
abort_at(ch, "Empty hash as task argument, " +
"task name required.")
end
if targ.size > 1
abort_at(ch, "Too many hash elements, " +
"should only be one.")
end
targ.each_pair { |k,v|
name = normalize_task_name(k, ch)
pre = v
}
unless ::Rant::FileList === pre
if pre.respond_to? :to_ary
pre = pre.to_ary.dup
pre.map! { |elem|
normalize_task_name(elem, ch)
}
else
pre = [normalize_task_name(pre, ch)]
end
end
else
name = normalize_task_name(targ, ch)
end
[name, pre]
end
public :normalize_task_arg
def normalize_task_name(arg, ch)
return arg if arg.is_a? String
if Symbol === arg
arg.to_s
elsif arg.respond_to? :to_str
arg.to_str
else
abort_at(ch, "Task name has to be a string or symbol.")
end
end
def rantfile_for_path(path, register=true)
abs_path = File.expand_path(path)
file = @rantfiles.find { |rf| rf.path == abs_path }
if file
[file, false]
else
file = Rant::Rantfile.new abs_path
file.project_subdir = @current_subdir
@rantfiles << file if register
[file, true]
end
end
def get_ch_from_backtrace(backtrace)
backtrace.each { |clr|
ch = ::Rant::Lib.parse_caller_elem(clr)
if ::Rant::Env.on_windows?
return ch if @rantfiles.any? { |rf|
rf.path.tr("\\", "/").sub(/^\w\:/, '') ==
ch[:file].tr("\\", "/").sub(/^\w\:/, '')
}
else
return ch if @rantfiles.any? { |rf|
rf.path == ch[:file]
}
end
}
nil
end
def err_task_fail(e)
msg = []
t_msg = ["Task `#{e.tname}' fail."]
orig = e
loop { orig = orig.orig; break unless Rant::TaskFail === orig }
if orig && orig != e && !(Rant::RantAbortException === orig)
ch = get_ch_from_backtrace(orig.backtrace)
msg << pos_text(ch[:file], ch[:ln]) if ch
unless Rant::CommandError === orig && !@opts[:err_commands]
msg << orig.message
msg << orig.backtrace[0..4] unless ch
end
end
if e.msg && !e.msg.empty?
ch = get_ch_from_backtrace(e.backtrace)
t_msg.unshift(e.msg)
t_msg.unshift(pos_text(ch[:file], ch[:ln])) if ch
end
err_msg msg unless msg.empty?
err_msg t_msg
end
end # class Rant::RantApp
$".concat(['rant/rantlib.rb', 'rant/init.rb', 'rant/rantvar.rb', 'rant/rantsys.rb', 'rant/import/filelist/core.rb', 'rant/node.rb', 'rant/import/nodes/default.rb', 'rant/coregen.rb'])
Rant::CODE_IMPORTS.concat %w(nodes/default
)
# Catch a `require "rant"', sad...
alias require_backup_by_rant require
def require libf
if libf == "rant"
# TODO: needs rework! look at lib/rant.rb
self.class.instance_eval { include Rant }
else
begin
require_backup_by_rant libf
rescue
raise $!, caller
end
end
end
exit Rant.run