TOPLEVEL_BINDING = binding() module Rubinius class Loader def initialize @exit_code = 0 @process_id = Process.pid @load_paths = [] @requires = [] @evals = [] @script = nil @debugging = false @repl = true @printed_version = false @input_loop = false @input_loop_print = false @input_loop_split = false @simple_options = false @early_option_stop = false @check_syntax = false @load_profiler = false @enable_gems = true @load_gemfile = false @deprecations = false version = RUBY_VERSION.split(".").first(2).join(".") @gem_bins = ["#{version}/bin", "bin"].map do |dir| File.join Rubinius::GEMS_PATH, dir end end private :initialize def self.debugger @debugger_proc end def self.debugger=(prc) @debugger_proc = prc end # Finish setting up after loading the core library. def preamble set_program_name "rbx" @stage = "running Loader preamble" # Set the default visibility for the top level binding TOPLEVEL_BINDING.variables.method_visibility = :private $VERBOSE = false end # Set up $LOAD_PATH. def system_load_path @stage = "setting up system load path" @lib_bin = "#{Rubinius::CODEDB_PATH}/source/bin" $LOAD_PATH.unshift(Rubinius::ARCH_PATH, Rubinius::SITE_PATH, "#{Rubinius::SITE_PATH}/#{RUBY_VERSION}", Rubinius::VENDOR_PATH, "#{Rubinius::CODEDB_PATH}/source") if libs = ENV['RUBYLIB'] Rubinius::Logger.system.write "RUBYLIB: #{libs}" $LOAD_PATH.unshift(*libs.split(File::PATH_SEPARATOR).compact) end end # Load customization code: # /etc/rbxrc # $HOME/.rbxrc # $RBX_PRELOAD def preload @stage = "preloading rbxrc code" ['/etc/rbxrc',"#{ENV['HOME']}/.rbxrc",ENV['RBX_PRELOAD']].each do |file| begin load file if file and File.exist?(file) rescue LoadError nil end end end # Register signal handlers. def signals @stage = "registering signal handlers" # Set up a handler for SIGINT that raises Interrupt on the main thread Signal.trap("INT") do |sig| raise Interrupt, "Thread has been interrupted" end ["HUP", "QUIT", "TERM", "ALRM", "USR1", "USR2"].each do |signal| Signal.trap(signal) do |sig| raise SignalException, sig end end end def show_syntax_error(e) STDERR.puts "A syntax error has occurred:" STDERR.puts " #{e.message}" STDERR.puts "\nCode:\n#{e.code}" if e.column STDERR.puts((" " * (e.column - 1)) + "^") end end def show_syntax_errors(syns) syns.each do |e| STDERR.puts "#{e.file}:#{e.line}: #{e.reason}" end end # Checks if a subcommand with basename +base+ exists. Returns the full # path to the subcommand if it does; otherwise, returns nil. def find_subcommand(base) command = File.join @lib_bin, "#{base}.rb" return command if File.exist? command return nil end # Checks if a gem wrapper named +base+ exists. Returns the full path to # the gem wrapper if it does; otherwise, returns nil. def find_gem_wrapper(base) @gem_bins.each do |dir| wrapper = File.join dir, base return wrapper if File.exist? wrapper end return nil end # Detects if the Rubinius executable was aliased to a subcommand or a # rubygems executable. If so, changes ARGV as if Rubinius were invoked # to run the subcommand or rubygems executable. def detect_alias cmd = ARG0.split("/").last # ignore the common ones straight away return if cmd == "rbx" or cmd == "ruby" # Check if we are aliased to a Rubinius subcommand. if subcommand = find_subcommand(cmd) ARGV.unshift subcommand return end # Check if we are aliased to a rubygems executable. if gem_wrapper = find_gem_wrapper(cmd) ARGV.unshift "-S", gem_wrapper end end def handle_simple_options(argv) argv.delete_if do |x| if x[0] == ?- if equal = x.index("=") name = x.substring(1, equal-1) equal += 1 val = x.substring(equal, x.size - equal) else name = x.substring(1, x.size - 1) val = true end name.gsub! "-", "_" Rubinius::Globals[:"$#{name}"] = val true else false end end end def define_global_methods Kernel.module_eval do def gsub(pattern, rep=nil, &block) target = $_ raise TypeError, "$_ must be a String, but is #{target.inspect}" unless target.kind_of? String $_ = target.gsub(pattern, rep, &block) end module_function :gsub def sub(pattern, rep=nil, &block) target = $_ raise TypeError, "$_ must be a String, but is #{target.inspect}" unless target.kind_of? String $_ = target.sub(pattern, rep, &block) end module_function :sub def chomp(string=$/) raise TypeError, "$_ must be a String" unless $_.kind_of? String $_ = $_.chomp(string) end module_function :chomp def chop raise TypeError, "$_ must be a String" unless $_.kind_of? String $_ = $_.chop end module_function :chop end end # Process all command line arguments. def options(argv=ARGV) @stage = "processing command line arguments" options = Options.new "Usage: rbx [subcommand] [options] [--] [script] [arguments]", 25 options.left_align options.on_extra do |x| raise Options::ParseError, "Unrecognized option: #{x}" if x[0] == ?- if @script.nil? and @evals.empty? @script = x else argv.unshift x end options.stop_parsing end options.doc "Script is any valid Ruby source code file" options.doc "\nRuby options" options.on "-", "Read and evaluate code from STDIN" do @repl = false set_program_name "-" stdin = STDIN.dup script = STDIN.read STDIN.reopen(stdin) execute_script script end options.on "--", "Stop processing command line arguments" do @early_option_stop = true options.stop_parsing end options.on "-a", "Used with -n and -p, splits $_ into $F" do @input_loop_split = true Rubinius::Globals.set!(:$-a, true) end options.on "-c", "Only check the syntax" do @repl = false @check_syntax = true end options.on "-C", "DIR", "Change directory to DIR before running scripts" do |dir| @directory = dir end options.on "-d", "Enable debugging output and set $DEBUG to true" do $VERBOSE = true $DEBUG = true end options.on "--disable-gems", "Do not automatically load rubygems on startup" do @enable_gems = false end options.on "-e", "CODE", "Compile and execute CODE" do |code| @repl = false set_program_name "(eval)" @evals << code end options.on '-F', "PATTERN", "Set $; to PATTERN" do |pattern| Rubinius::Globals.set!(:$;, Regexp.new(pattern)) end options.on '-G', '--gemfile', 'Respect a Gemfile in the current path' do @load_gemfile = true end options.on "-h", "--help", "Display this help" do @repl = false puts options done end options.on "-i", "[EXT]", "Edit ARGV files in place, making backup with EXT" do |ext| # in place edit mode ext ||= '' $-i = ext end options.on "-I", "DIR1[:DIR2]", "Add directories to $LOAD_PATH" do |dir| @load_paths << dir end options.on "-K", "Ignored $KCODE option for compatibility" do msg = "The -K option is deprecated. Rubinius internal encoding is always UTF-8" STDERR.puts msg Rubinius::Logger.system.warn msg end options.on "-U", "Set Encoding.default_internal to UTF-8" do msg = "The -U option is deprecated. Rubinius internal encoding is always UTF-8" STDERR.puts msg Rubinius::Logger.system.warn msg end options.on "-E", "ENC", "Set external:internal character encoding to ENC" do |enc| external, internal = enc.split(":") Encoding.default_external = external if external and !external.empty? if internal and !internal.empty? msg = "The -E option setting internal encoding is deprecated. Rubinius internal encoding is always UTF-8" STDERR.puts msg Rubinius::Logger.system.warn msg end end options.on "--main", "PATH", "Load PATH directly from CodeDB" do |path| code = Rubinius::CoreDB.load_feature path, "", false, false if code code.create_script false Rubinius.run_script code exit 0 else puts "file not found: #{path}" exit 1 end end options.on "-n", "Wrap running code in 'while(gets()) ...'" do @input_loop = true define_global_methods end options.on "-p", "Same as -n, but also print $_" do @input_loop = true @input_loop_print = true Rubinius::Globals.set!(:$-p, true) define_global_methods end options.on "-r", "LIBRARY", "Require library before execution" do |file| @requires << file end options.on("-s", "Process options after the script into globals") do @simple_options = true end options.on("-S", "SCRIPT", "Run SCRIPT using PATH environment variable to find it") do |script| options.stop_parsing @repl = false # Load a gemfile now if we need to so that -S can see binstubs # internal to the Gemfile gemfile # First, check if any existing gem wrappers match. unless file = find_gem_wrapper(script) # Then, check if any Rubinius subcommands match. unless file = find_subcommand(script) # Finally, check if any file on PATH matches. search = ENV['PATH'].split(File::PATH_SEPARATOR) search.each do |d| path = File.join d, script if File.exist? path file = path break end end end end set_program_name script if file # if missing, let it die a natural death @script = file ? file : script end options.on "-v", "Display the version and set $VERBOSE to true" do @repl = false $VERBOSE = true unless @printed_version puts Rubinius.version @printed_version = true end end options.on "-w", "Enable warnings" do $VERBOSE = true end options.on("-W", "[level]", "Set warning level: 0=silence, 1=medium, 2=verbose (default)") do |l| case l when "0" $VERBOSE = nil when "1" $VERBOSE = false when nil $VERBOSE = true else # MRI -h says -W2 sets $VERBOSE to true, but behaviorally # any value >= 2 sets $VERBOSE to true. $VERBOSE = true end end options.on "--show-nil-location", "Show where nil value originated when NoMethodError on nil is raised" do $rbx_show_nil_location = true end options.on "--deprecated", "Display any deprecations" do @deprecations = true end options.on "--version", "Display the version" do @repl = false puts Rubinius.version end options.doc <<-DOC \nRubinius subcommands Rubinius provides subcommands that implement various facilities. Each subcommand provides its own help. For example: rbx compile -h Available subcommands: compile Run the bytecode compiler gem Run RubyGems 'gem' command irb Run IRB erb Run ERb DOC options.doc <<-DOC VM Options -X[=] This option is recognized by the VM before any ruby code is loaded. It is used to set VM configuration options. Use -Xhelp to see the list of options the VM recognizes. All variables, even ones that the VM doesn't understand, are available in Rubinius::Config. Options are parsed from these sources, in this order: 1. The file .rbxrc in the current working directory. 2. The RBXOPT environment variable. 3. The command line options. This order permits environment and command line options to override "application" configuration. Likewise, command line options can override environment configuration. A number of Rubinius features are driven by setting these variables. DOC options.parse ARGV handle_rubyopt(options) if @early_option_stop and @simple_options handle_simple_options(argv) end if Rubinius::Config['help'] STDOUT.puts "Rubinius configuration variables:" STDOUT.puts " These variables are normally set via -X and read via Rubinius::Config[var]." STDOUT.puts require 'rubinius/configuration' Rubinius::ConfigurationVariables.instance.show_help(STDOUT) exit 0 end exit 0 if Rubinius::Config["config.print"] if str = Rubinius::Config['tool.require'] begin require str rescue LoadError STDERR.puts "Unable to require file for tool: '#{str}'" end end if Rubinius::Config['profile'] || Rubinius::Config['jit.profile'] @load_profiler = true end if @check_syntax check_syntax end end # Sets $0 ($PROGRAM_NAME) without changing the process title def set_program_name(name) Rubinius::Globals.set! :$0, name end private :set_program_name def handle_rubyopt(options) if env_opts = ENV['RUBYOPT'] Rubinius::Logger.system.write "RUBYOPT: #{env_opts}" options.start_parsing env_opts = env_opts.strip.split(/\s+/) until env_opts.empty? entry = env_opts.shift unless entry[0] == ?- entry = "-#{entry}" end opt, arg, rest = options.split entry, 2 options.process env_opts, entry, opt, arg end end end # Update the load paths with any -I arguments. def load_paths @stage = "setting load paths" @load_paths.each do |path| path.split(":").reverse_each do |path| # We used to run expand_path on path first, but MRI # doesn't and it breaks some code if we do. $LOAD_PATH.unshift(path) end end end def deprecations @stage = "showing deprecations" return unless @deprecations if Rubinius::DEPRECATIONS.size > 0 Rubinius::DEPRECATIONS.each do |desc, alt| STDERR.puts <<-EOM Deprecation: #{desc} Alternative: #{alt} EOM end exit 1 else STDERR.puts "Rubinius #{Rubinius::VERSION} has no deprecation notices" exit 0 end end def load_compiler @stage = "loading the compiler" begin CodeLoader.load_compiler rescue LoadError => e message = "Unable to load the bytecode compiler." Rubinius::Logger.system.error message Rubinius::Logger.system.error( "Please run 'rake' or 'rake install' to rebuild the compiler.") Rubinius::Logger.log_exception message, e exit 1 end end def debugger @stage = "running the debugger" if Rubinius::Config['debug'] if custom = Loader.debugger custom.call else require 'rubinius/debugger' Rubinius::Debugger.start end end end def rubygems @stage = "loading Rubygems" CodeLoader.load_rubygems if @enable_gems end def gemfile @stage = "loading Gemfile" if @load_gemfile CodeLoader.load_rubygems require 'bundler/setup' @load_gemfile = false end end def profiler return unless @load_profiler @stage = "loading profiler" require 'profile' end # Require any -r arguments def requires @stage = "requiring command line files" @requires.each { |file| require file } end # Evaluate any -e arguments def evals return if @evals.empty? @repl = false @stage = "evaluating command line code" Dir.chdir(@directory) if @directory if @input_loop while gets $F = $_.split if @input_loop_split eval @evals.join("\n"), TOPLEVEL_BINDING, "-e", 1 print $_ if @input_loop_print end else eval @evals.join("\n"), TOPLEVEL_BINDING, "-e", 1 end end # Run the script passed on the command line def script return unless @script and @evals.empty? @repl = false handle_simple_options(ARGV) if @simple_options @stage = "running #{@script}" Dir.chdir @directory if @directory if File.exist? @script if IO.read(@script, 6) == "!RBIX\n" raise LoadError, "'#{@script}' is not a Ruby source file" end else if @script.suffix?(".rb") raise LoadError, "unable to find '#{@script}'" else command = File.join @lib_bin, "#{@script}.rb" unless File.exist? command raise LoadError, "unable to find Rubinius command '#{@script}'" else @script = command end end end set_program_name @script CodeLoader.new(@script).load_script end #Check Ruby syntax of source def check_syntax parser_class = Rubinius::ToolSets::Runtime::Melbourne if @script if File.exist?(@script) parser = parser_class.new @script, 1, [] parser.parse_file else puts "rbx: Unable to find file -- #{@script} (LoadError)" exit 1 end elsif not @evals.empty? parser = parser_class.new('-e', 1, []) parser.parse_string(@evals.join("\n")) else parser = parser_class.new('-', 1, []) parser.parse_string(STDIN.read) end puts "Syntax OK" exit 0 rescue SyntaxError => e show_syntax_errors(parser.syntax_errors) exit 1 end # Run IRB unless we were passed -e, -S arguments or a script to run. def repl return unless @repl @stage = "running IRB" # Check if a different REPL is set. repl = ENV["RBX_REPL"] || "irb" if Terminal load File.join(Rubinius::GEMS_PATH, "bin", repl) else set_program_name "(eval)" execute_script "p #{STDIN.read}" end end def execute_script(script) eval script, TOPLEVEL_BINDING end def run_at_exits until AtExit.empty? begin AtExit.shift.call rescue SystemExit => e @exit_code = e.status # We recurse down so that future at_exits # see the current exception run_at_exits end end end def flush_stdio STDOUT.flush unless STDOUT.closed? STDERR.flush unless STDERR.closed? end def exit_with_exception(e) @exit_code = 1 Rubinius::Logger.log_exception "An exception occurred #{@stage}", e end # Cleanup and at_exit processing. def epilogue @stage = "running at_exit handlers" begin Signal.run_handler Signal::Names["EXIT"] rescue SystemExit => e @exit_code = e.status end run_at_exits flush_stdio rescue Object => e exit_with_exception e end # Exit. def done # check that this is a valid exit rather than failing to process # unwinding properly. # # TODO: this code is pretty gross, nice object inspectors, please. thread_state = Rubinius.thread_state reason = thread_state[0] unless reason == :none STDERR.puts "\nERROR: the VM is exiting improperly #{@stage}" STDERR.puts "intended operation: #{reason.inspect}" STDERR.puts "associated value: #{thread_state[1].inspect}" destination = thread_state[2] if destination method = destination.method STDERR.puts "destination scope:" STDERR.puts " method: #{method.name} at #{method.file}:#{method.first_line}" STDERR.puts " module: #{destination.module.name}" STDERR.puts " block: #{destination.block}" if destination.block else STDERR.puts "destination scope: unknown" end if reason == :catch_throw STDERR.puts "throw destination: #{thread_state[4].inspect}" end if exception = thread_state[3] begin exception.render rescue Exception => e STDERR.puts "Unable to render backtrace: #{e.message} (#{e.class})" STDERR.puts "Raw backtrace data:" exception.locations.each do |loc| p [loc.file, loc.line] end end end @exit_code = 1 end Process.exit! @exit_code end def handle_exception(e) case e when SystemExit @exit_code = e.status when SyntaxError @exit_code = 1 STDERR.puts e.awesome_backtrace.show STDERR.puts show_syntax_error(e) when Interrupt exit_with_exception e when SignalException Signal.trap(e.signo, "SIG_DFL") Process.kill e.signo, Process.pid when nil # what? else exit_with_exception e end rescue Object => e exit_with_exception e ensure epilogue end # Orchestrate everything. def main preamble system_load_path signals load_compiler preload detect_alias options load_paths deprecations rubygems gemfile debugger profiler requires evals script repl rescue Object => e handle_exception e else # We do this, run epilogue both in the rescue blocks and also here, # so that at_exit{} hooks can read $!. epilogue ensure done end end end