diff --git a/.travis.yml b/.travis.yml index 15a86ed5..493afb05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,39 +1,37 @@ language: ruby dist: trusty -os: - - linux - - osx - -rvm: - - 1.8.7 - - 1.9.3 - - 2.0.0 - - 2.1.10 - - 2.2.8 - - 2.3.8 - - 2.4.6 - - 2.5.5 - - 2.6.3 - - ruby-head - matrix: fast_finish: true - allow_failures: - - rvm: ruby-head - - exclude: + include: + - os: linux + rvm: 3.0.1 - os: osx - rvm: 1.9.3 + rvm: 3.0.1 + - os: linux + rvm: 2.7.3 - os: osx - rvm: 2.0.0 - + rvm: 2.7.3 + - os: linux + rvm: 2.6.7 + - os: osx + rvm: 2.6.7 + - os: linux + rvm: 2.5.9 - os: osx + rvm: 2.5.9 + - os: linux + rvm: 2.4.10 + - os: osx + rvm: 2.4.10 + - os: linux + rvm: 2.3.8 + - os: linux + rvm: 2.2.10 + - os: linux + rvm: 2.1.10 + - os: linux + rvm: 2.0.0 + - os: linux + rvm: 1.9.3 + - os: linux rvm: 1.8.7 - -# include: -# - os: osx -# rvm: 1.9.3 -# before_script: rvm install ruby-1.9.3 # not binary -# - os: osx -# rvm: 2.0.0 -# before_script: rvm install ruby-2.0.0 # not binary diff --git a/README.md b/README.md index d0522d6e..df075152 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,31 @@ +[![Gem Version](https://badge.fury.io/rb/ruby-debug-ide.svg)][gem] +[![Build Status](https://travis-ci.org/ruby-debug/ruby-debug-ide.svg?branch=master)](https://travis-ci.org/ruby-debug/ruby-debug-ide) + [gem]: https://rubygems.org/gems/ruby-debug-ide # ruby-debug-ide -An interface which glues ruby-debug to IDEs like Eclipse (RDT), NetBeans and RubyMine. -[![official JetBrains project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) -[![Gem Version](https://badge.fury.io/rb/ruby-debug-ide.svg)][gem] -[![Build Status](https://travis-ci.org/ruby-debug/ruby-debug-ide.svg?branch=master)](https://travis-ci.org/ruby-debug/ruby-debug-ide) +The 'ruby-debug-ide' gem provides the protocol to establish communication between the debugger engine (such as [debase](https://rubygems.org/gems/debase) or [ruby-debug-base](https://rubygems.org/gems/ruby-debug-base)) and IDEs (for example, RubyMine, Visual Studio Code, or Eclipse). 'ruby-debug-ide' redirect commands from the IDE to the debugger engine. Then, it returns answers/events received from the debugger engine to the IDE. To learn more about a communication protocol, see the following document: [ruby-debug-ide protocol](protocol-spec.md). + +## Install debugging gems +Depending on the used Ruby version, you need to add/install the following debugging gems to the Gemfile: +- Ruby 2.x - [ruby-debug-ide](https://rubygems.org/gems/ruby-debug-ide) and [debase](https://rubygems.org/gems/debase) +- Ruby 1.9.x - [ruby-debug-ide](https://rubygems.org/gems/ruby-debug-ide) and [ruby-debug-base19x](https://rubygems.org/gems/ruby-debug-base19x) +- jRuby or Ruby 1.8.x - [ruby-debug-ide](https://rubygems.org/gems/ruby-debug-ide) and [ruby-debug-base](https://rubygems.org/gems/ruby-debug-base) +> For Windows, make sure that the Ruby [DevKit](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit) is installed. + +## Start debugging session +To start the debugging session for a Rails application, run the following command: +```shell +rdebug-ide --host 0.0.0.0 --port 1234 --dispatcher-port 1234 -- bin/rails s +``` +If you want to debug a Rails application run using Docker Compose, you need to start the Rails server from the Docker in the following way: +```yaml +command: bundle exec rdebug-ide --host 0.0.0.0 --port 1234 -- bin/rails s -p 3000 -b 0.0.0.0 +volumes: + - .:/sample_rails_app +ports: + - "1234:1234" + - "3000:3000" + - "26162:26162" +``` +Note that all ports above should be exposed in the Dockerfile. \ No newline at end of file diff --git a/Rakefile b/Rakefile index a169aeec..7a74d18c 100644 --- a/Rakefile +++ b/Rakefile @@ -39,4 +39,55 @@ task :changelog, :since_c, :until_c do |t,args| end puts changelog_content +end + +desc "Generates travis.yaml" +task :gen_travis do + versions = [] + + def versions.add(major:, minor:, include_macos: true) + self << { major: major, minor: [minor], include_macos: include_macos } + end + + versions.add major: '3.0', minor: 1 + versions.add major: '2.7', minor: 3 + versions.add major: '2.6', minor: 7 + versions.add major: '2.5', minor: 9 + versions.add major: '2.4', minor: 10 + versions.add major: '2.3', minor: 8, include_macos: false + versions.add major: '2.2', minor: 10, include_macos: false + versions.add major: '2.1', minor: 10, include_macos: false + versions.add major: '2.0', minor: 0, include_macos: false + versions.add major: '1.9', minor: 3, include_macos: false + versions.add major: '1.8', minor: 7, include_macos: false + + puts < 1234, 'stop' => false, 'tracing' => false, + 'skip_wait_for_start' => false, 'int_handler' => true, 'dispatcher_port' => -1, 'evaluation_timeout' => 10, @@ -28,7 +29,8 @@ options = OpenStruct.new( 'value_as_nested_element' => false, 'attach_mode' => false, 'cli_debug' => false, - 'key_value_mode' => false + 'key_value_mode' => false, + 'socket_path' => nil ) opts = OptionParser.new do |opts| @@ -67,6 +69,7 @@ EOB opts.on('--stop', 'stop when the script is loaded') {options.stop = true} opts.on("-x", "--trace", "turn on line tracing") {options.tracing = true} + opts.on("--skip_wait_for_start", "skip wait for 'start' command") {options.skip_wait_for_start = true} opts.on("-l", "--load-mode", "load mode (experimental)") {options.load_mode = true} opts.on("-d", "--debug", "Debug self - prints information for debugging ruby-debug itself") do Debugger.cli_debug = true @@ -98,6 +101,9 @@ EOB opts.on("--value-as-nested-element", "Allow to pass variable's value as nested element instead of attribute") do options.value_as_nested_element = true end + opts.on("--socket-path PATH", "Listen for debugger on the given UNIX domain socket path") do |path| + options.socket_path = path + end opts.separator "" opts.separator "Common options:" opts.on_tail("-v", "--version", "Show version") do diff --git a/ext/mkrf_conf.rb b/ext/mkrf_conf.rb index 478dca61..d92b7611 100644 --- a/ext/mkrf_conf.rb +++ b/ext/mkrf_conf.rb @@ -2,11 +2,6 @@ jruby = defined?(JRUBY_VERSION) || (defined?(RUBY_ENGINE) && 'jruby' == RUBY_ENGINE) rbx = defined?(RUBY_ENGINE) && 'rbx' == RUBY_ENGINE -def already_installed(dep) - !Gem::DependencyInstaller.new(:domain => :local).find_gems_with_sources(dep).empty? || - !Gem::DependencyInstaller.new(:domain => :local,:prerelease => true).find_gems_with_sources(dep).empty? -end - unless jruby || rbx require 'rubygems' require 'rubygems/command.rb' @@ -15,14 +10,14 @@ def already_installed(dep) begin Gem::Command.build_args = ARGV - rescue NoMethodError + rescue NoMethodError end if RUBY_VERSION < "1.9" dep = Gem::Dependency.new("ruby-debug-base", '>=0.10.4') elsif RUBY_VERSION < '2.0' dep = Gem::Dependency.new("ruby-debug-base19x", '>=0.11.30.pre15') - else + else dep = Gem::Dependency.new("debase", '> 0') end @@ -39,7 +34,7 @@ def already_installed(dep) puts e.backtrace.join "\n " exit(1) end - end unless dep.nil? || already_installed(dep) + end unless dep.nil? || dep.matching_specs.any? end # create dummy rakefile to indicate success diff --git a/lib/ruby-debug-ide.rb b/lib/ruby-debug-ide.rb index 10f70f43..bc61c90e 100644 --- a/lib/ruby-debug-ide.rb +++ b/lib/ruby-debug-ide.rb @@ -74,23 +74,29 @@ def interrupt_last end def start_server(host = nil, port = 1234, notify_dispatcher = false) - return if started? - start - start_control(host, port, notify_dispatcher) + _start_server_common(host, port, nil, notify_dispatcher) + end + + def start_server_unix(socket_path, notify_dispatcher = false) + _start_server_common(nil, 0, socket_path, notify_dispatcher) end def prepare_debugger(options) @mutex = Mutex.new @proceed = ConditionVariable.new - start_server(options.host, options.port, options.notify_dispatcher) + if options.socket_path.nil? + start_server(options.host, options.port, options.notify_dispatcher) + else + start_server_unix(options.socket_path, options.notify_dispatcher) + end raise "Control thread did not start (#{@control_thread}}" unless @control_thread && @control_thread.alive? # wait for 'start' command @mutex.synchronize do @proceed.wait(@mutex) - end + end unless options.skip_wait_for_start end def debug_program(options) @@ -112,24 +118,53 @@ def run_prog_script end def start_control(host, port, notify_dispatcher) + _start_control_common(host, port, nil, notify_dispatcher) + end + + def start_control_unix(socket_path, notify_dispatcher) + _start_control_common(nil, 0, socket_path, notify_dispatcher) + end + + private + + def _start_server_common(host, port, socket_path, notify_dispatcher) + return if started? + start + _start_control_common(host, port, socket_path, notify_dispatcher) + end + + def _start_control_common(host, port, socket_path, notify_dispatcher) raise "Debugger is not started" unless started? return if @control_thread @control_thread = DebugThread.new do begin - # 127.0.0.1 seemingly works with all systems and with IPv6 as well. - # "localhost" and nil have problems on some systems. - host ||= '127.0.0.1' - - server = notify_dispatcher_if_needed(host, port, notify_dispatcher) do |real_port, port_changed| - s = TCPServer.new(host, real_port) - print_greeting_msg $stderr, host, real_port, port_changed ? "Subprocess" : "Fast" if defined? IDE_VERSION - s + if socket_path.nil? + # 127.0.0.1 seemingly works with all systems and with IPv6 as well. + # "localhost" and nil have problems on some systems. + host ||= '127.0.0.1' + + server = notify_dispatcher_if_needed(host, port, notify_dispatcher) do |real_port, port_changed| + s = TCPServer.new(host, real_port) + print_greeting_msg $stderr, host, real_port, port_changed ? "Subprocess" : "Fast" if defined? IDE_VERSION + s + end + else + raise "Cannot specify host and socket_file at the same time" if host + File.delete(socket_path) if File.exist?(socket_path) + server = UNIXServer.new(socket_path) + print_greeting_msg $stderr, nil, nil, "Fast", socket_path if defined? IDE_VERSION end return unless server while (session = server.accept) - $stderr.puts "Connected from #{session.peeraddr[2]}" if Debugger.cli_debug + if Debugger.cli_debug + if session.peeraddr == 'AF_INET' + $stderr.puts "Connected from #{session.peeraddr[2]}" + else + $stderr.puts "Connected from local client" + end + end dispatcher = ENV['IDE_PROCESS_DISPATCHER'] if dispatcher ENV['IDE_PROCESS_DISPATCHER'] = "#{session.peeraddr[2]}:#{dispatcher}" unless dispatcher.include?(":") @@ -153,8 +188,6 @@ def start_control(host, port, notify_dispatcher) end end - private - def notify_dispatcher_if_needed(host, port, need_notify) return yield port unless need_notify diff --git a/lib/ruby-debug-ide/commands/condition.rb b/lib/ruby-debug-ide/commands/condition.rb index f0bcaa74..d1890bde 100644 --- a/lib/ruby-debug-ide/commands/condition.rb +++ b/lib/ruby-debug-ide/commands/condition.rb @@ -23,7 +23,7 @@ def execute return unless pos breakpoints.each do |b| if b.id == pos - b.expr = @match[2].empty? ? nil : @match[2] + b.expr = @match[2].empty? ? nil : Command.unescape_incoming(@match[2]) print_contdition_set(b.id) break end diff --git a/lib/ruby-debug-ide/greeter.rb b/lib/ruby-debug-ide/greeter.rb index 7e3dd93f..f47bde3b 100644 --- a/lib/ruby-debug-ide/greeter.rb +++ b/lib/ruby-debug-ide/greeter.rb @@ -10,7 +10,7 @@ module Debugger class << self - def print_greeting_msg(stream, host, port, debugger_name = "Fast") + def print_greeting_msg(stream, host, port, debugger_name = "Fast", socket_path = nil) base_gem_name = if defined?(JRUBY_VERSION) || RUBY_VERSION < '1.9.0' 'ruby-debug-base' elsif RUBY_VERSION < '2.0.0' @@ -27,6 +27,8 @@ def print_greeting_msg(stream, host, port, debugger_name = "Fast") if host && port listens_on = " listens on #{host}:#{port}\n" + elsif socket_path + listens_on = " listens on #{socket_path}\n" else listens_on = "\n" end @@ -37,4 +39,4 @@ def print_greeting_msg(stream, host, port, debugger_name = "Fast") end end -end \ No newline at end of file +end diff --git a/lib/ruby-debug-ide/thread_alias.rb b/lib/ruby-debug-ide/thread_alias.rb index 4e10a7bd..4b2054d5 100644 --- a/lib/ruby-debug-ide/thread_alias.rb +++ b/lib/ruby-debug-ide/thread_alias.rb @@ -8,7 +8,7 @@ def do_thread_alias end Object.const_set :OldThread, ::Thread - Object.send :remove_const, :Thread + Object.__send__ :remove_const, :Thread Object.const_set :Thread, ::Debugger::DebugThread end @@ -18,9 +18,9 @@ def undo_thread_alias return end - Object.send :remove_const, :Thread + Object.__send__ :remove_const, :Thread Object.const_set :Thread, ::OldThread - Object.send :remove_const, :OldThread + Object.__send__ :remove_const, :OldThread end end end diff --git a/lib/ruby-debug-ide/version.rb b/lib/ruby-debug-ide/version.rb index 43d918bb..cc001a56 100755 --- a/lib/ruby-debug-ide/version.rb +++ b/lib/ruby-debug-ide/version.rb @@ -1,3 +1,3 @@ module Debugger - IDE_VERSION='0.7.0' + IDE_VERSION='0.7.3' end diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 416be25d..9dcf0c9d 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -161,6 +161,11 @@ def do_print_hash(hash) else name = exec_with_allocation_control(k, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) end + + if k.nil? + name = 'nil' + end + print_variable(name, v, 'instance') } end @@ -203,13 +208,13 @@ def exec_with_timeout(sec, error_message) end def exec_with_allocation_control(value, exec_method, overflow_message_type) - return value.send exec_method unless Debugger.trace_to_s + return value.__send__ exec_method unless Debugger.trace_to_s memory_limit = Debugger.debugger_memory_limit time_limit = Debugger.inspect_time_limit if defined?(JRUBY_VERSION) || RUBY_VERSION < '2.0' || memory_limit <= 0 - return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") { value.send exec_method } + return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") { value.__send__ exec_method } end require 'objspace' @@ -240,7 +245,7 @@ def exec_with_allocation_control(value, exec_method, overflow_message_type) end end trace_point.enable - result = value.send exec_method + result = value.__send__ exec_method trace_queue << result trace_point.disable end diff --git a/protocol-spec.md b/protocol-spec.md new file mode 100644 index 00000000..b6f42c0a --- /dev/null +++ b/protocol-spec.md @@ -0,0 +1,406 @@ +ruby-debug-ide protocol +======================= + +* * * + +Next: [Summary](#Summary), Previous: [(dir)](#dir), Up: [(dir)](#dir) + +_ruby-debug-ide_ protocol +------------------------- + +This file contains specification of the protocol used by _ruby-debug-ide_. + +* [Summary](#Summary) +* [Specification](#Specification) +* [Changes](#Changes) + +* * * + +Next: [Specification](#Specification), Previous: [Top](#Top), Up: [Top](#Top) + +1 Summary +--------- + +This document describes protocol used by _ruby-debug-ide_ for communication between debugger engine and a frontend. It is a work in progress and might, and very likely will, change in the future. If you have any comments or questions please [send me](mailto:martin.krauskopf@gmail.com) an email. + +The communication has two parts/sides. First ones are _commands_ sent from a frontend to the debugger engine and the second is the opposite way, _answers_ and _events_ sent from the debugger engine to the frontend. + +_commands_ are almost the same as the ones used by CLI ruby-debug. So you might want to contact [the _ruby-debug-ide_ document](http://bashdb.sourceforge.net/ruby-debug.html). + +_answers_ and _events_ are sent in XML format described in the specification [below](#Specification). + +**Specification is far from complete.** Will be completed as time permits. In the meantime, source code is always the best spec. + +* * * + +Next: [Changes](#Changes), Previous: [Summary](#Summary), Up: [Top](#Top) + +2 Specification +--------------- + +* [Commands](#Commands) +* [Events](#Events) + +Terms: + +* _Command_ is what frontend sends to the debugger engine +* _Answer_ is what debugger engine sends back to the frontend +* _Example_ shows simple example + +* * * + +Next: [Events](#Events), Up: [Specification](#Specification) + +### 2.1 Commands + +* [Adding Breakpoint](#Adding-Breakpoint) +* [Deleting Breakpoint](#Deleting-Breakpoint) +* [Enabling Breakpoint](#Enabling-Breakpoint) +* [Disabling Breakpoint](#Disabling-Breakpoint) +* [Condition](#Condition) +* [Catchpoint](#Catchpoint) +* [Threads](#Threads) +* [Frames](#Frames) +* [Variables](#Variables) + +* * * + +Next: [Deleting Breakpoint](#Deleting-Breakpoint), Up: [Commands](#Commands) + +#### 2.1.1 Adding Breakpoint + +Command: + + break