#!/usr/bin/env ruby # sisu-install - Monolithic rant script, autogenerated by rant-import 0.5.8. # # Copyright (C) 2005 Stefan Lang # # 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 =~ /^#$/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