byebug-dap 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9695c31c89057519d92567d4b14faf182a108aeaad4994d92104733c966f1e32
4
- data.tar.gz: d030b0a78edd8010d05f3a8af5e6adbc88ab553671f429babdc1f14390a222c1
3
+ metadata.gz: cdcb974e222d9a64fea6f7f070e0352dc746dfd6f331d4b7230aad278ba563db
4
+ data.tar.gz: 91af303a92685b001c2fee3039f47cba119b67f3edefdeb11b9f28d54d6e2608
5
5
  SHA512:
6
- metadata.gz: e37dd536e602234f326c4d86b179046f1896fc8913979bbe193646a0387a829975abe6f4e7cad1d42d1615fff02436242d7caeaff030e34ec1472c82547e9cdc
7
- data.tar.gz: f9efa4e3ca7cec53cd8c2b9dc89212640006ac1ffd841b753a72bbb3540a842fa7622ae010c55d6fee005d631e6bd4d15b36108517a22a0935465ed564a629cf
6
+ metadata.gz: c29328a148dc9e690452fdb09acbb5f63a6612a85f1e5c5cfa7a14b72ed5517b99d0fca4077ca3001d9d4cfbe0b25afa9f3475d65d21ee01369cb623af8462a4
7
+ data.tar.gz: 79f5f2dcfb469ab62250543620473f34c9dce69204eef8bb27cfeb1b636041716effb1f1486cd50ebb1c72e2770b627be5361112e2e64a6b97fd4d93fa9768d4
@@ -0,0 +1,19 @@
1
+ # Change Log
2
+
3
+ ## 0.1.2
4
+
5
+ - Fix possible failure when a breakpoint is hit but can't be resolved
6
+ - Fix possible failure when frame arguments can't be evaluated
7
+ - Exit on disconnect when started by 'launch'
8
+ - Expose `Server#wait_for_client` instead of passing a block
9
+ - Expose `Interface#stop!` to allow the debugee to stop
10
+ - Support specifying a start sequence with `--on-start CODE`
11
+ - Support for child processes
12
+
13
+ ## 0.1.1
14
+
15
+ - Improve error handling in `Byebug::DAP::Controller`
16
+
17
+ ## 0.1.0
18
+
19
+ - Initial release
data/README.md CHANGED
@@ -2,3 +2,11 @@
2
2
 
3
3
  This gem adds [Debug Adapter
4
4
  Protocol](https://microsoft.github.io/debug-adapter-protocol) support to Byebug.
5
+
6
+ ## TODO
7
+
8
+ - Multi-process support
9
+ - Stdout/stderr
10
+ - In STDIO mode, spawn with extra FDs and use those instead of 0/1?
11
+ - Many DAP features are already supported by Byebug and just need to be
12
+ implemented in DAP mode
@@ -26,6 +26,7 @@ OptionParser.new do |opts|
26
26
  opts.on("-f", "--[no-]force", "When listening on a unix socket, delete the socket if it exists") { |v| options[:force] = v }
27
27
  opts.on("--debug-protocol", "Debug DAP") { |v| Byebug::DAP::Debug.protocol = true if v }
28
28
  opts.on("--debug-evaluate", "Debug variable evaluation") { |v| Byebug::DAP::Debug.evaluate = true if v }
29
+ opts.on("--on-start CODE", "Code to print once the debugger is available") { |v| options[:start_code] = v }
29
30
  end.parse!
30
31
 
31
32
  program = next_arg
@@ -59,16 +60,22 @@ else
59
60
  end
60
61
 
61
62
  begin
62
- STDERR.puts "Starting DAP"
63
+ STDERR.print "Starting DAP... " unless options[:start_code]
64
+
65
+ server = Byebug.start_dap(host, port)
66
+
67
+ STDERR.puts options[:start_code] if options[:start_code]
63
68
  STDERR.flush
64
69
 
65
70
  if options[:wait]
66
- Byebug.start_dap(host, port) { require File.realpath(program) }
67
- else
68
- Byebug.start_dap(host, port)
69
- require File.realpath(program)
71
+ STDERR.print "waiting for debugger... " unless options[:start_code]
72
+ server.wait_for_client
70
73
  end
71
74
 
75
+ STDERR.puts "ok" unless options[:start_code]
76
+
77
+ require File.realpath(program)
78
+
72
79
  ensure
73
80
  File.delete(port) if File.exist?(port) if host == :unix
74
81
  end
@@ -4,6 +4,7 @@ require 'byebug/core'
4
4
  require 'byebug/remote'
5
5
 
6
6
  require_relative 'dap/channel'
7
+ require_relative 'dap/child_spawned_event_body'
7
8
  require_relative 'dap/handles'
8
9
  require_relative 'dap/invalid_request_argument_error'
9
10
  require_relative 'dap/safe_helpers'
@@ -0,0 +1,11 @@
1
+ module Byebug
2
+ module DAP
3
+ class ChildSpawnedEventBody < ::DAP::Base
4
+ ::DAP::Event.bodies[:childSpawned] = self
5
+
6
+ property :name
7
+ property :pid
8
+ property :socket
9
+ end
10
+ end
11
+ end
@@ -103,12 +103,10 @@ module Byebug
103
103
  def stopped!
104
104
  case context.stop_reason
105
105
  when :breakpoint
106
- number = Byebug.breakpoints.index(@at_breakpoint) + 1
107
-
108
106
  args = {
109
107
  reason: 'breakpoint',
110
108
  description: 'Hit breakpoint',
111
- text: "Stopped by breakpoint #{number} at #{context.frame.file}:#{context.frame.line}",
109
+ text: "Stopped by breakpoint at #{context.frame.file}:#{context.frame.line}",
112
110
  }
113
111
 
114
112
  when :catchpoint
@@ -1,11 +1,9 @@
1
1
  module Byebug
2
2
  module DAP
3
3
  class Controller
4
- class DisconnectError < StandardError; end
5
-
6
- def initialize(interface, signal_start = nil)
4
+ def initialize(interface, &block)
7
5
  @interface = interface
8
- @signal_start = signal_start
6
+ @on_configured = block
9
7
 
10
8
  @trace = TracePoint.new(:thread_begin, :thread_end) { |t| process_trace t }
11
9
  end
@@ -13,16 +11,17 @@ module Byebug
13
11
  def run
14
12
  loop do
15
13
  @request = @interface.receive
16
- process_command @request
14
+ result = process_command @request
15
+ return if result == :stop
17
16
  end
18
17
 
19
- rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED, DisconnectError
18
+ rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
20
19
  STDERR.puts "\nClient disconnected"
21
20
 
22
21
  ensure
23
- Byebug.mode = :off
24
- Byebug.stop
25
- @interface.socket.close
22
+ exit if @exit_on_disconnect
23
+
24
+ @interface.stop!
26
25
  @trace.disable
27
26
  end
28
27
 
@@ -45,6 +44,19 @@ module Byebug
45
44
  end
46
45
 
47
46
  def process_command(request)
47
+ case request.command
48
+ when 'attach', 'launch'
49
+ if Byebug.started?
50
+ respond! success: false, message: "Cannot #{request.command} - debugger is already running"
51
+ return
52
+ end
53
+ when 'pause', 'next', 'stepIn', 'stepOut', 'continue', 'evaluate', 'variables', 'scopes', 'threads', 'stackTrace'
54
+ unless Byebug.started?
55
+ respond! success: false, message: "Cannot #{request.command} - debugger is not running"
56
+ return
57
+ end
58
+ end
59
+
48
60
  case request.command
49
61
  when 'initialize'
50
62
  # "The ‘initialize’ request is sent as the first request from the client to the debug adapter
@@ -67,7 +79,7 @@ module Byebug
67
79
  # "This behavior can be controlled with the ‘terminateDebuggee’ argument (if supported by the debug adapter).
68
80
 
69
81
  respond!
70
- raise DisconnectError
82
+ return :stop
71
83
 
72
84
  when 'attach'
73
85
  # "The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running.
@@ -76,8 +88,6 @@ module Byebug
76
88
  Byebug.start
77
89
  @trace.enable
78
90
 
79
- @signal_start.call(:attach) if @signal_start
80
-
81
91
  respond!
82
92
  return
83
93
 
@@ -90,7 +100,7 @@ module Byebug
90
100
  @trace.enable
91
101
  end
92
102
 
93
- @signal_start.call(:launch) if @signal_start
103
+ @exit_on_disconnect = true
94
104
 
95
105
  respond!
96
106
  return
@@ -98,16 +108,11 @@ module Byebug
98
108
  when 'configurationDone'
99
109
  # "This optional request indicates that the client has finished initialization of the debug adapter.
100
110
 
101
- respond!
102
- return
103
- end
104
111
 
105
- unless Byebug.started?
106
- respond! success: false, message: "Debugger is not running"
112
+ @on_configured&.call
113
+ respond!
107
114
  return
108
- end
109
115
 
110
- case request.command
111
116
  when 'pause', 'next', 'stepIn', 'stepOut', 'continue'
112
117
  ctx = @interface.find_thread(request.arguments.threadId)
113
118
  ctx.interrupt if request.command == 'pause'
@@ -3,26 +3,58 @@ module Byebug
3
3
  class Interface
4
4
  include SafeHelpers
5
5
 
6
+ @@children = []
7
+
8
+ def self.child_spawned(name, pid, socket)
9
+ child = ChildSpawnedEventBody.new(name: name, pid: pid, socket: socket)
10
+ @@children << child
11
+
12
+ interface = Context.interface
13
+ interface.event! child if interface.is_a?(Byebug::DAP::Interface)
14
+
15
+ return true
16
+
17
+ rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
18
+ return false
19
+ end
20
+
6
21
  attr_reader :socket
7
22
 
8
23
  def initialize(socket)
9
24
  @socket = socket
25
+
26
+ begin
27
+ @@children.each { |c| event! c }
28
+ rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
29
+ end
30
+ end
31
+
32
+ def stop!
33
+ Byebug.mode = :off
34
+ Byebug.stop
35
+ socket.close
10
36
  end
11
37
 
12
38
  def <<(message)
13
- STDERR.puts "> #{message.to_wire}" if Debug.protocol
39
+ STDERR.puts "#{Process.pid} > #{message.to_wire}" if Debug.protocol
14
40
  message.validate!
15
41
  socket.write ::DAP::Encoding.encode(message)
16
42
  end
17
43
 
18
44
  def event!(event, **values)
19
- body = ::DAP.const_get("#{event[0].upcase}#{event[1..]}EventBody").new(values) unless values.empty?
45
+ if (cls = event.class.name.split('::').last) && cls.end_with?('EventBody')
46
+ body, event = event, cls[0].downcase + cls[1...-9]
47
+
48
+ elsif event.is_a?(String) && !values.empty?
49
+ body = ::DAP.const_get("#{event[0].upcase}#{event[1..]}EventBody").new(values)
50
+ end
51
+
20
52
  self << ::DAP::Event.new(event: event, body: body)
21
53
  end
22
54
 
23
55
  def receive
24
56
  m = ::DAP::Encoding.decode(socket)
25
- STDERR.puts "< #{m.to_wire}" if Debug.protocol
57
+ STDERR.puts "#{Process.pid} < #{m.to_wire}" if Debug.protocol
26
58
  m
27
59
  end
28
60
 
@@ -78,7 +110,7 @@ module Byebug
78
110
  frame = ::Byebug::Frame.new(ctx, i)
79
111
  ::DAP::StackFrame.new(
80
112
  id: frame_ids << [ctx.thnum, i],
81
- name: frame.deco_call,
113
+ name: frame_name(frame),
82
114
  source: ::DAP::Source.new(
83
115
  name: File.basename(frame.file),
84
116
  path: File.expand_path(frame.file)),
@@ -194,6 +226,12 @@ module Byebug
194
226
 
195
227
  private
196
228
 
229
+ def frame_name(frame)
230
+ frame.deco_call
231
+ rescue
232
+ frame.deco_block + frame.deco_class + frame.deco_method + "(?)"
233
+ end
234
+
197
235
  def describe_thread(context)
198
236
  if context.thread.name
199
237
  "##{context.thnum} (#{context.thread.name})"
@@ -20,13 +20,11 @@ module Byebug
20
20
  public :<<, :bytes, :chars, :close_read, :close_write, :codepoints, :each, :each_byte, :each_char, :each_codepoint, :each_line, :getbyte, :getc, :gets, :lines, :pread, :print, :printf, :putc, :puts, :pwrite, :read, :read_nonblock, :readbyte, :readchar, :readline, :readlines, :readpartial, :sysread, :syswrite, :ungetbyte, :ungetc, :write, :write_nonblock
21
21
  end
22
22
 
23
- def initialize(&block)
23
+ def initialize
24
24
  @started = false
25
- if block_given?
26
- @on_start = block
27
- @mu = Mutex.new
28
- @cond = ConditionVariable.new
29
- end
25
+ @mu = Mutex.new
26
+ @cond = ConditionVariable.new
27
+ @configured = false
30
28
  end
31
29
 
32
30
  def start(host, port = 0)
@@ -61,6 +59,16 @@ module Byebug
61
59
  launch STDIO.new
62
60
  end
63
61
 
62
+ def wait_for_client
63
+ @mu.synchronize do
64
+ loop do
65
+ return if @configured
66
+
67
+ @cond.wait(@mu)
68
+ end
69
+ end
70
+ end
71
+
64
72
  private
65
73
 
66
74
  def launch(server)
@@ -74,19 +82,19 @@ module Byebug
74
82
  end
75
83
  end
76
84
 
77
- return unless defined?(@on_start)
78
-
79
- @mu.synchronize { @cond.wait(@mu) }
80
-
81
- @on_start.call
85
+ self
82
86
  end
83
87
 
84
88
  def debug(session)
85
89
  Context.interface = Byebug::DAP::Interface.new(session)
86
90
  Context.processor = Byebug::DAP::CommandProcessor
87
91
 
88
- signal_start = ->(_) { @mu.synchronize { @cond.signal } } if defined?(@on_start)
89
- Byebug::DAP::Controller.new(Context.interface, signal_start).run
92
+ Byebug::DAP::Controller.new(Context.interface) do
93
+ @mu.synchronize do
94
+ @configured = true
95
+ @cond.broadcast
96
+ end
97
+ end.run
90
98
  end
91
99
  end
92
100
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: byebug-dap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan Reesor
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-08 00:00:00.000000000 Z
11
+ date: 2020-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.1.0
33
+ version: 0.1.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.1.0
40
+ version: 0.1.2
41
41
  description: Implements a Debug Adapter Protocol interface for Byebug
42
42
  email: ethan.reesor@gmail.com
43
43
  executables:
@@ -46,11 +46,13 @@ extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
48
  - AUTHORS
49
+ - CHANGELOG.md
49
50
  - LICENSE
50
51
  - README.md
51
52
  - bin/byebug-dap
52
53
  - lib/byebug/dap.rb
53
54
  - lib/byebug/dap/channel.rb
55
+ - lib/byebug/dap/child_spawned_event_body.rb
54
56
  - lib/byebug/dap/command_processor.rb
55
57
  - lib/byebug/dap/controller.rb
56
58
  - lib/byebug/dap/handles.rb