123456789_123456789_123456789_123456789_123456789_

Class: DEBUGGER__::Config

Relationships & Source Files
Inherits: Object
Defined in: lib/debug/config.rb

Class Method Summary

Instance Method Summary

Constructor Details

.new(argv) ⇒ Config

[ GitHub ]

  
# File 'lib/debug/config.rb', line 69

def initialize argv
  if self.class.config
    raise 'Can not make multiple configurations in one process'
  end

  config = self.class.parse_argv(argv)

  # apply defaults
  CONFIG_SET.each do |k, config_detail|
    unless config.key?(k)
      default_value = config_detail[3]
      config[k] = parse_config_value(k, default_value)
    end
  end

  update config
end

Class Method Details

.config

[ GitHub ]

  
# File 'lib/debug/config.rb', line 65

def self.config
  @config
end

.config_to_env_hash(config)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 442

def self.config_to_env_hash config
  CONFIG_MAP.each_with_object({}){|(key, evname), env|
    unless config[key].nil?
      case CONFIG_SET[key][2]
      when :path
        valstr = config[key].map{|e| e.kind_of?(Regexp) ? e.inspect : e}.join(':')
      when :path_map
        valstr = config[key].map{|e| e.join(':')}.join(',')
      else
        valstr = config[key].to_s
      end
      env[evname] = valstr
    end
  }
end

.parse_argv(argv)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 267

def self.parse_argv argv
  config = {
    mode: :start,
    no_color: (nc = ENV['NO_COLOR']) && !nc.empty?,
  }
  CONFIG_MAP.each{|key, evname|
    if val = ENV[evname]
      config[key] = parse_config_value(key, val)
    end
  }
  return config if !argv || argv.empty?

  if argv.kind_of? String
    require 'shellwords'
    argv = Shellwords.split(argv)
  end

  require 'optparse'
  require_relative 'version'

  have_shown_version = false

  opt = OptionParser.new do |o|
    o.banner = "#{$0} [options] -- [debuggee options]"
    o.separator ''
    o.version = ::DEBUGGER__::VERSION

    o.separator 'Debug console mode:'
    o.on('-n', '--nonstop', 'Do not stop at the beginning of the script.') do
      config[:nonstop] = '1'
    end

    o.on('-e DEBUG_COMMAND', 'Execute debug command at the beginning of the script.') do |cmd|
      config[:commands] ||= ''
      config[:commands] += cmd + ';;'
    end

    o.on('-x FILE', '--init-script=FILE', 'Execute debug command in the FILE.') do |file|
      config[:init_script] = file
    end
    o.on('--no-rc', 'Ignore ~/.rdbgrc') do
      config[:no_rc] = true
    end
    o.on('--no-color', 'Disable colorize') do
      config[:no_color] = true
    end
    o.on('--no-sigint-hook', 'Disable to trap SIGINT') do
      config[:no_sigint_hook] = true
    end

    o.on('-c', '--command', 'Enable command mode.',
                            'The first argument should be a command name in $PATH.',
                            'Example: \'rdbg -c bundle exec rake test\'') do
      config[:command] = true
    end

    o.separator ''

    o.on('-O', '--open=[FRONTEND]', 'Start remote debugging with opening the network port.',
                                    'If TCP/IP options are not given, a UNIX domain socket will be used.',
                                    'If FRONTEND is given, prepare for the FRONTEND.',
                                    'Now rdbg, vscode and chrome is supported.') do |f|

      case f # some format patterns are not documented yet
      when nil
        config[:open] = true
      when /\A\d\z/
        config[:open] = true
        config[:port] = f.to_i
      when /\A(\S):(\d)\z/
        config[:open] = true
        config[:host] = $1
        config[:port] = $2.to_i
      when 'tcp'
        config[:open] = true
        config[:port] ||= 0
      when 'vscode', 'chrome', 'cdp'
        config[:open] = f&.downcase
      else
        raise "Unknown option for --open: #{f}"
      end
    end
    o.on('--sock-path=SOCK_PATH', 'UNIX Domain socket path') do |path|
      config[:sock_path] = path
    end
    o.on('--port=PORT', 'Listening TCP/IP port') do |port|
      config[:port] = port
    end
    o.on('--host=HOST', 'Listening TCP/IP host') do |host|
      config[:host] = host
    end
    o.on('--cookie=COOKIE', 'Set a cookie for connection') do |c|
      config[:cookie] = c
    end
    o.on('--session-name=NAME', 'Session name') do |name|
      config[:session_name] = name
    end

    rdbg = 'rdbg'

    o.separator ''
    o.separator '  Debug console mode runs Ruby program with the debug console.'
    o.separator ''
    o.separator "  '#{rdbg} target.rb foo bar'                starts like 'ruby target.rb foo bar'."
    o.separator "  '#{rdbg} -- -r foo -e bar'                 starts like 'ruby -r foo -e bar'."
    o.separator "  '#{rdbg} -c rake test'                     starts like 'rake test'."
    o.separator "  '#{rdbg} -c -- rake test -t'               starts like 'rake test -t'."
    o.separator "  '#{rdbg} -c bundle exec rake test'         starts like 'bundle exec rake test'."
    o.separator "  '#{rdbg} -O target.rb foo bar'             starts and accepts attaching with UNIX domain socket."
    o.separator "  '#{rdbg} -O --port 1234 target.rb foo bar' starts accepts attaching with TCP/IP localhost:1234."
    o.separator "  '#{rdbg} -O --port 1234 -- -r foo -e bar'  starts accepts attaching with TCP/IP localhost:1234."
    o.separator "  '#{rdbg} target.rb -O chrome --port 1234'  starts and accepts connecting from Chrome Devtools with localhost:1234."

    o.separator ''
    o.separator 'Attach mode:'
    o.on('-A', '--attach', 'Attach to debuggee process.') do
      config[:mode] = :attach
    end

    o.separator ''
    o.separator '  Attach mode attaches the remote debug console to the debuggee process.'
    o.separator ''
    o.separator "  '#{rdbg} -A'           tries to connect via UNIX domain socket."
    o.separator "  #{' ' * rdbg.size}                If there are multiple processes are waiting for the"
    o.separator "  #{' ' * rdbg.size}                debugger connection, list possible debuggee names."
    o.separator "  '#{rdbg} -A path'      tries to connect via UNIX domain socket with given path name."
    o.separator "  '#{rdbg} -A port'      tries to connect to localhost:port via TCP/IP."
    o.separator "  '#{rdbg} -A host port' tries to connect to host:port via TCP/IP."

    o.separator ''
    o.separator 'Other options:'

    o.on('-v', 'Show version number') do
      puts o.ver
      have_shown_version = true
    end

    o.on('--version', 'Show version number and exit') do
      puts o.ver
      exit
    end

    o.on("-h", "--help", "Print help") do
      puts o
      exit
    end

    o.on('--util=NAME', 'Utility mode (used by tools)') do |name|
      require_relative 'client'
      Client.util(name)
      exit
    end

    o.on('--stop-at-load', 'Stop immediately when the debugging feature is loaded.') do
      config[:stop_at_load] = true
    end

    o.separator ''
    o.separator 'NOTE'
    o.separator '  All messages communicated between a debugger and a debuggee are *NOT* encrypted.'
    o.separator '  Please use the remote debugging feature carefully.'
  end

  opt.parse!(argv)

  if argv.empty?
    case
    when have_shown_version && config[:mode] == :start
      exit
    end
  end

  config
end

.parse_config_value(name, valstr)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 226

def self.parse_config_value name, valstr
  return valstr unless valstr.kind_of? String

  case CONFIG_SET[name][2]
  when :bool
    case valstr
    when '1', 'true', 'TRUE', 'T'
      true
    else
      false
    end
  when :int
    valstr.to_i
  when :loglevel
    if DEBUGGER__::LOG_LEVELS[s = valstr.to_sym]
      s
    else
      raise "Unknown loglevel: #{valstr}"
    end
  when :forkmode
    case sym = valstr.to_sym
    when :parent, :child, :both, nil
      sym
    else
      raise "unknown fork mode: #{sym}"
    end
  when :path # array of String
    valstr.split(/:/).map{|e|
      if /\A\/(.+)\/\z/ =~ e
        Regexp.compile $1
      else
        e
      end
    }
  when :path_map
    valstr.split(',').map{|e| e.split(':')}
  else
    valstr
  end
end

Instance Method Details

#[](key)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 91

def [](key)
  config[key]
end

#[]=(key, val)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 95

def []=(key, val)
  set_config(key => val)
end

#append_config(key, val)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 112

def append_config key, val
  conf = config.dup

  if CONFIG_SET[key]
    if CONFIG_SET[key][2] == :path
      conf[key] = [*conf[key], *parse_config_value(key, val)];
    else
      raise "not an Array type: #{key}"
    end
  else
    raise "Unknown configuration: #{key}"
  end

  update conf
end

#config (private)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 218

private def config
  self.class.config
end

#disable_sigdump(old_sig) (private)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 200

private def disable_sigdump old_sig
  trap(old_sig, @sigdump_sig_prev)
  @sigdump_sig_prev = nil
end

#enable_sigdump(sig) (private)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 184

private def enable_sigdump sig
  @sigdump_sig_prev = trap(sig) do
    str = []
    str << "Simple sigdump on #{Process.pid}"
    Thread.list.each{|th|
      str << "Thread: #{th}"
      th.backtrace.each{|loc|
        str << "  #{loc}"
      }
      str << ''
    }

    STDERR.puts str
  end
end

#if_updated(old_conf, new_conf, key) {|old, new| ... } (private)

Yields:

[ GitHub ]

  
# File 'lib/debug/config.rb', line 179

private def if_updated old_conf, new_conf, key
  old, new = old_conf[key], new_conf[key]
  yield old, new if old != new
end

#inspect

[ GitHub ]

  
# File 'lib/debug/config.rb', line 87

def inspect
  config.inspect
end

#parse_config_value(name, valstr) (private)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 222

private def parse_config_value name, valstr
  self.class.parse_config_value name, valstr
end

#set_config(**kw)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 99

def set_config(**kw)
  conf = config.dup
  kw.each{|k, v|
    if CONFIG_MAP[k]
      conf[k] = parse_config_value(k, v) # TODO: ractor support
    else
      raise "Unknown configuration: #{k}"
    end
  }

  update conf
end

#setup_sigdump(old_sig = nil, sig = ) (private)

emergency simple sigdump. Use sigdump gem for more rich features.

[ GitHub ]

  
# File 'lib/debug/config.rb', line 207

private def setup_sigdump old_sig = nil, sig = CONFIG[:sigdump_sig]
  if !old_sig && sig
    enable_sigdump sig
  elsif old_sig && !sig
    disable_sigdump old_sig
  elsif old_sig && sig
    disable_sigdump old_sig
    enable_sigdump sig
  end
end

#update(conf)

[ GitHub ]

  
# File 'lib/debug/config.rb', line 128

def update conf
  old_conf = self.class.instance_variable_get(:@config) || {}

  # TODO: Use Ractor.make_shareable(conf)
  self.class.instance_variable_set(:@config, conf.freeze)

  # Post process
  if_updated old_conf, conf, :keep_alloc_site do |old, new|
    if new
      require 'objspace'
      ObjectSpace.trace_object_allocations_start
    end

    if old && !new
      ObjectSpace.trace_object_allocations_stop
    end
  end

  if_updated old_conf, conf, :postmortem do |_, new_p|
    if defined?(SESSION)
      SESSION.postmortem = new_p
    end
  end

  if_updated old_conf, conf, :sigdump_sig do |old_sig, new_sig|
    setup_sigdump old_sig, new_sig
  end

  if_updated old_conf, conf, :no_sigint_hook do |old, new|
    if defined?(SESSION)
      SESSION.set_no_sigint_hook old, new
    end
  end

  if_updated old_conf, conf, :irb_console do |old, new|
    if defined?(SESSION) && SESSION.active?
      # irb_console is switched from true to false
      if old
        SESSION.deactivate_irb_integration
      # irb_console is switched from false to true
      else
        if CONFIG[:open]
          SESSION.instance_variable_get(:@ui).puts "\nIRB is not supported on the remote console."
        else
          SESSION.activate_irb_integration
        end
      end
    end
  end
end