From 4c48ea7cd6911320c1a6c25a73ab59c22bdfc0c2 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 7 Jul 2022 05:20:18 +0900 Subject: [PATCH] local_fs_map for DAP On DAP via TCP/IP connection, all the source code are detected as remote files (without `localfs:true` in launch.json for VSCode). In this case, DAP clients (VSCode, ...) receive the contents of the file from DAP server and open an editor window in readonly mode. `local_fs_map` configuration can set remote->local path translation. For example, ``` RUBY_DEBUG_LOCAL_FS_MAP=/remote/src/:/local/src/ rdbg -O --port=... ``` On this case, if remote path is `/remote/src/is/foo.rb`, then DAP returns `/local/src/is/foo.rb` and open the local file as writable mode. You can specify multiple path maps with ',' character like ``` RUBY_DEBUG_LOCAL_FS_MAP=/r1/:/l1/,/r2/:/l2/ ... ``` and also you can specify this map within launch.json for VSCode like: ``` { "type": "rdbg", "name": "Attach with rdbg (TCP/IP 12345)", "request": "attach", "debugPort": "localhost:12345", "localfsMap": "/remote/src/:/local/src/" } ``` If both are provided, launch.json setting is used. fix https://github.com/ruby/debug/issues/463 fix https://github.com/ruby/vscode-rdbg/issues/32 --- lib/debug/config.rb | 5 +++++ lib/debug/server_dap.rb | 47 ++++++++++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/lib/debug/config.rb b/lib/debug/config.rb index 8b176ae09..7e86576ec 100644 --- a/lib/debug/config.rb +++ b/lib/debug/config.rb @@ -42,6 +42,7 @@ module DEBUGGER__ host: ['RUBY_DEBUG_HOST', "REMOTE: TCP/IP remote debugging: host", :string, "127.0.0.1"], sock_path: ['RUBY_DEBUG_SOCK_PATH', "REMOTE: UNIX Domain Socket remote debugging: socket path"], sock_dir: ['RUBY_DEBUG_SOCK_DIR', "REMOTE: UNIX Domain Socket remote debugging: socket directory"], + local_fs_map: ['RUBY_DEBUG_LOCAL_FS_MAP', "REMOTE: Specify local fs map", :path_map], cookie: ['RUBY_DEBUG_COOKIE', "REMOTE: Cookie for negotiation"], open_frontend: ['RUBY_DEBUG_OPEN_FRONTEND',"REMOTE: frontend used by open command (vscode, chrome, default: rdbg)."], chrome_path: ['RUBY_DEBUG_CHROME_PATH', "REMOTE: Platform dependent path of Chrome (For more information, See [here](https://github.com/ruby/debug/pull/334/files#diff-5fc3d0a901379a95bc111b86cf0090b03f857edfd0b99a0c1537e26735698453R55-R64))"], @@ -238,6 +239,8 @@ def self.parse_config_value name, valstr e end } + when :path_map + valstr.split(',').map{|e| e.split(':')} else valstr end @@ -384,6 +387,8 @@ def self.config_to_env_hash config 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 diff --git a/lib/debug/server_dap.rb b/lib/debug/server_dap.rb index cbb10fe40..8754945c9 100644 --- a/lib/debug/server_dap.rb +++ b/lib/debug/server_dap.rb @@ -70,14 +70,39 @@ def show_protocol dir, msg end end - @local_fs = false + # true: all localfs + # Array: part of localfs + # nil: no localfs + @local_fs_map = nil + + def self.local_fs_map_path path + case @local_fs_map + when nil + false + when true + path + else # Array + @local_fs_map.each do |(remote_path_prefix, local_path_prefix)| + if path.start_with? remote_path_prefix + return path.sub(remote_path_prefix){ local_path_prefix } + end + end - def self.local_fs - @local_fs + nil + end end - def self.local_fs_set - @local_fs = true + def self.local_fs_map_set map + return if @local_fs_map # already setup + + case map + when String + @local_fs_map = map.split(',').map{|e| e.split(':')} + when true + @local_fs_map = map + when nil + @local_fs_map = CONFIG[:local_fs_map] + end end def dap_setup bytes @@ -86,7 +111,7 @@ def dap_setup bytes case self when UI_UnixDomainServer - UI_DAP.local_fs_set + UI_DAP.local_fs_map_set true when UI_TcpServer # TODO: loopback address can be used to connect other FS env, like Docker containers # UI_DAP.local_fs_set if @local_addr.ipv4_loopback? || @local_addr.ipv6_loopback? @@ -228,12 +253,12 @@ def process when 'launch' send_response req @is_attach = false - UI_DAP.local_fs_set if req.dig('arguments', 'localfs') + UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap') when 'attach' send_response req Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid) @is_attach = true - UI_DAP.local_fs_set if req.dig('arguments', 'localfs') + UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap') when 'setBreakpoints' path = args.dig('source', 'path') SESSION.clear_line_breakpoints path @@ -637,7 +662,9 @@ def process_dap args path = frame.realpath || frame.path source_name = path ? File.basename(path) : frame.location.to_s - if !UI_DAP.local_fs || !(path && File.exist?(path)) + if (path && File.exist?(path)) && (local_path = UI_DAP.local_fs_map_path(path)) + # ok + else ref = frame.file_lines end @@ -648,7 +675,7 @@ def process_dap args column: 1, source: { name: source_name, - path: path, + path: (local_path || path), sourceReference: ref, }, }