Skip to content

Commit

Permalink
feat: added commands to attach/detach debugger
Browse files Browse the repository at this point in the history
Resolves #274
  • Loading branch information
wojciech-kulik committed Jan 18, 2025
1 parent 93b349f commit d8f972c
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test:
# Installs dependencies for plugin usage
install:
brew update --quiet
brew install --quiet xcbeautify xcode-build-server pipx
brew install --quiet xcode-build-server xcbeautify ruby pipx rg jq
pipx install pymobiledevice3 --quiet
gem install --quiet xcodeproj

Expand Down
45 changes: 43 additions & 2 deletions doc/xcodebuild.txt
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,15 @@ External tools
- `Xcodeproj` to manage project files within Neovim.
- `Ruby` to use `Xcodeproj` gem.
- `pymobiledevice3` to debug on physical devices and/or run apps on devices below iOS 17.
- `jq` to get some information from `pymobiledevice3`.
- `xcode-build-server` to make LSP work properly with xcodeproj/xcworkspace.
- `codelldb` to debug applications.
- `ripgrep` to find matching test files while using Swift Testing framework.
- `Xcode` to build, run, and test apps. Make sure that `xcodebuild` and `xcrun simctl` work correctly. Tested with Xcode 15.

Installation
>bash
brew install xcode-build-server xcbeautify ruby pipx rg
brew install xcode-build-server xcbeautify ruby pipx rg jq
gem install xcodeproj
pipx install pymobiledevice3
<
Expand Down Expand Up @@ -478,6 +479,13 @@ Testing
| `XcodebuildTestRepeat` | Repeat the last test run |
| `XcodebuildFailingSnapshots` | Show a picker with failing snapshot tests |

Debugging

| Command | Description |
| -------------------------- | ----------------------------------------- |
| `XcodebuildAttachDebugger` | Attach debugger to the running process |
| `XcodebuildDetachDebugger` | Detach debugger without killing the process |

Code Coverage

| Command | Description |
Expand Down Expand Up @@ -2499,6 +2507,17 @@ M.find_app_path({destination}, {bundleId})
(string|nil) app path


*xcodebuild.platform.device_proxy.find_app_pid*
M.find_app_pid({processName})
Returns the PID of the application with the provided name.

Parameters: ~
{processName} (string)

Returns: ~
(string|nil) pid


*xcodebuild.platform.device_proxy.start_secure_server*
M.start_secure_server({destination}, {rsd})
Starts the secure server.
Expand Down Expand Up @@ -3535,6 +3554,19 @@ See:
https://github.com/vadimcn/codelldb


*xcodebuild.integrations.dap.detach_debugger*
M.detach_debugger()
Detaches the debugger from the running application.


*xcodebuild.integrations.dap.attach_and_debug*
M.attach_and_debug({callback})
Attaches the debugger to the running application.

Parameters: ~
{callback} (function|nil)


*xcodebuild.integrations.dap.build_and_debug*
M.build_and_debug({callback})
Builds, installs and runs the project. Also, it starts the debugger.
Expand Down Expand Up @@ -3697,6 +3729,14 @@ M.get_actions() *xcodebuild.integrations.dap.get_actions*
@return table<{name:string,action:function}>


*xcodebuild.integrations.dap.register_user_commands*
M.register_user_commands()
Registers user commands for the `nvim-dap` plugin integration.
Commands:
- `XcodebuildAttachDebugger` - starts the debugger session.
- `XcodebuildDetachDebugger` - disconnects the debugger session.


*xcodebuild.integrations.dap.setup*
M.setup({codelldbPath}, {loadBreakpoints})
Sets up the adapter and configuration for the `nvim-dap` plugin.
Expand Down Expand Up @@ -3748,10 +3788,11 @@ M.set_mode({mode})


*xcodebuild.integrations.remote_debugger.start_remote_debugger*
M.start_remote_debugger({callback})
M.start_remote_debugger({opts}, {callback})
Starts the remote debugger based on the mode.

Parameters: ~
{opts} ({attach:boolean}|nil)
{callback} (function|nil)


Expand Down
7 changes: 7 additions & 0 deletions lua/xcodebuild/docs/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@
--- | `XcodebuildTestRepeat` | Repeat the last test run |
--- | `XcodebuildFailingSnapshots` | Show a picker with failing snapshot tests |
---
---Debugging
---
--- | Command | Description |
--- | -------------------------- | ----------------------------------------- |
--- | `XcodebuildAttachDebugger` | Attach debugger to the running process |
--- | `XcodebuildDetachDebugger` | Detach debugger without killing the process |
---
---Code Coverage
---
--- | Command | Description |
Expand Down
3 changes: 2 additions & 1 deletion lua/xcodebuild/docs/requirements.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
--- - `Xcodeproj` to manage project files within Neovim.
--- - `Ruby` to use `Xcodeproj` gem.
--- - `pymobiledevice3` to debug on physical devices and/or run apps on devices below iOS 17.
--- - `jq` to get some information from `pymobiledevice3`.
--- - `xcode-build-server` to make LSP work properly with xcodeproj/xcworkspace.
--- - `codelldb` to debug applications.
--- - `ripgrep` to find matching test files while using Swift Testing framework.
--- - `Xcode` to build, run, and test apps. Make sure that `xcodebuild` and `xcrun simctl` work correctly. Tested with Xcode 15.
---
---Installation
--->bash
--- brew install xcode-build-server xcbeautify ruby pipx rg
--- brew install xcode-build-server xcbeautify ruby pipx rg jq
--- gem install xcodeproj
--- pipx install pymobiledevice3
---<
Expand Down
87 changes: 85 additions & 2 deletions lua/xcodebuild/integrations/dap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,76 @@ local function stop_session()
end
end

---Disconnects the current `nvim-dap` session.
local function disconnect_session()
local loadedDap, dap = pcall(require, "dap")
if not loadedDap then
return
end

if not dap.session() then
return
end

local isDevice = constants.is_device(projectConfig.settings.platform)
if isDevice then
dap.repl.execute("process detach")
else
dap.disconnect()
end
end

---Detaches the debugger from the running application.
function M.detach_debugger()
disconnect_session()
end

---Attaches the debugger to the running application.
---@param callback function|nil
function M.attach_and_debug(callback)
local loadedDap, _ = pcall(require, "dap")
if not loadedDap then
notifications.send_error("Could not load nvim-dap plugin")
return
end

if not helpers.validate_project({ requiresApp = true }) then
return
end

stop_session()

local xcode = require("xcodebuild.core.xcode")
local isDevice = constants.is_device(projectConfig.settings.platform)
local productName = projectConfig.settings.productName
local pid

if not productName then
notifications.send_error("You must build the application first")
return
end

if isDevice then
pid = require("xcodebuild.platform.device_proxy").find_app_pid(productName)
else
pid = xcode.get_app_pid(productName, projectConfig.settings.platform)
end

if not pid or pid == "" then
notifications.send_error("The application is not running. Could not attach the debugger.")
return
end

notifications.send("Attaching debugger...")

if isDevice then
set_remote_debugger_mode()
remoteDebugger.start_remote_debugger({ attach = true }, callback)
else
start_dap()
end
end

---Builds, installs and runs the project. Also, it starts the debugger.
---@param callback function|nil
function M.build_and_debug(callback)
Expand Down Expand Up @@ -123,7 +193,7 @@ function M.build_and_debug(callback)
if isDevice then
device.install_app(function()
set_remote_debugger_mode()
remoteDebugger.start_remote_debugger(callback)
remoteDebugger.start_remote_debugger({}, callback)
end)
else
if isSimulator then
Expand All @@ -150,7 +220,7 @@ function M.debug_without_build(callback)
if isDevice then
device.install_app(function()
set_remote_debugger_mode()
remoteDebugger.start_remote_debugger(callback)
remoteDebugger.start_remote_debugger({}, callback)
end)
else
device.kill_app()
Expand Down Expand Up @@ -513,10 +583,21 @@ function M.get_actions()
{ name = "Debug Without Building", action = M.debug_without_build },
{ name = "Debug Tests", action = M.debug_tests },
{ name = "Debug Current Test Class", action = M.debug_class_tests },
{ name = "Attach Debugger", action = M.attach_and_debug },
{ name = "Detach Debugger", action = disconnect_session },
{ name = "Clear DAP Console", action = M.clear_console },
}
end

---Registers user commands for the `nvim-dap` plugin integration.
---Commands:
--- - `XcodebuildAttachDebugger` - starts the debugger session.
--- - `XcodebuildDetachDebugger` - disconnects the debugger session.
function M.register_user_commands()
vim.api.nvim_create_user_command("XcodebuildAttachDebugger", M.attach_and_debug, { nargs = 0 })
vim.api.nvim_create_user_command("XcodebuildDetachDebugger", M.detach_debugger, { nargs = 0 })
end

---Sets up the adapter and configuration for the `nvim-dap` plugin.
---{codelldbPath} - path to the `codelldb` binary.
---
Expand All @@ -530,6 +611,8 @@ function M.setup(codelldbPath, loadBreakpoints)
dap.adapters.codelldb = M.get_codelldb_adapter(codelldbPath)
dap.defaults.fallback.exception_breakpoints = {}

M.register_user_commands()

if loadBreakpoints ~= false then
vim.api.nvim_create_autocmd({ "BufReadPost" }, {
group = vim.api.nvim_create_augroup("xcodebuild-integrations-dap", { clear = true }),
Expand Down
48 changes: 36 additions & 12 deletions lua/xcodebuild/integrations/remote_debugger.lua
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ local function setup_connection_listeners()
end

---Starts `nvim-dap` debug session. It connects to `codelldb`.
local function start_dap()
---@param opts {attach: boolean}|nil
local function start_dap(opts)
opts = opts or {}

local success, dap = pcall(require, "dap")
if not success then
error("xcodebuild.nvim: Could not load nvim-dap plugin")
Expand Down Expand Up @@ -140,11 +143,11 @@ local function start_dap()
deviceProxy.find_app_path(projectConfig.settings.destination, projectConfig.settings.bundleId)

if not appPath then
error("xcodebuild.nvim: Failed to find the app path on the device.")
return nil
notifications.send_error("Failed to find the app path on the device.")
return "platform status"
end

update_console({ "App path: " .. appPath, "" })
update_console({ "App path: " .. appPath })

return "script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec('" .. appPath .. "'))"
end,
Expand All @@ -157,7 +160,25 @@ local function start_dap()
end
end,

"process launch",
function()
if opts.attach then
local pid = deviceProxy.find_app_pid(projectConfig.settings.productName)
if not pid or pid == "" then
notifications.send_error("Failed to find the app PID on the device.")
return "platform status"
end
update_console({ "App PID: " .. pid })

return "process attach --pid " .. pid
else
return "process launch"
end
end,

function()
update_console({ "" })
return opts.attach and "continue" or "process status"
end,
},
})
end
Expand All @@ -171,8 +192,9 @@ end

---Starts legacy server without trusted channel.
---After the server is started, it starts the debug session.
---@param opts {attach: boolean}|nil
---@param callback function|nil
local function start_legacy_server(callback)
local function start_legacy_server(opts, callback)
local config = require("xcodebuild.core.config")

M.debug_server_job = deviceProxy.start_server(
Expand All @@ -185,7 +207,7 @@ local function start_legacy_server(callback)
"Connecting to " .. connection_string:gsub("process connect connect://", ""),
})
setup_terminate_listeners()
start_dap()
start_dap(opts)

util.call(callback)
end
Expand All @@ -194,22 +216,24 @@ end

---Starts secured tunnel with trusted channel.
---After the tunnel is established, it starts the debug session.
---@param opts {attach: boolean}|nil
---@param callback function|nil
local function start_secured_tunnel(callback)
local function start_secured_tunnel(opts, callback)
M.debug_server_job = deviceProxy.create_secure_tunnel(projectConfig.settings.destination, function(rsdParam)
M.rsd_param = rsdParam

update_console({ "Connecting to " .. rsdParam:gsub("%-%-rsd ", "") })
setup_terminate_listeners()
start_dap()
start_dap(opts)

util.call(callback)
end)
end

---Starts the remote debugger based on the mode.
---@param opts {attach: boolean}|nil
---@param callback function|nil
function M.start_remote_debugger(callback)
function M.start_remote_debugger(opts, callback)
if not deviceProxy.validate_installation() then
return
end
Expand All @@ -220,9 +244,9 @@ function M.start_remote_debugger(callback)
require("xcodebuild.project.appdata").clear_app_logs()

if M.mode == M.LEGACY_MODE then
start_legacy_server(callback)
start_legacy_server(opts, callback)
else
start_secured_tunnel(callback)
start_secured_tunnel(opts, callback)
end
end

Expand Down
Loading

0 comments on commit d8f972c

Please sign in to comment.