Skip to content

Commit

Permalink
feat(dap): support dynamically compiled executable (#64) (#72)
Browse files Browse the repository at this point in the history
Co-authored-by: Rich Churcher <[email protected]>
  • Loading branch information
mrcjkb and bas-ie authored Nov 27, 2023
1 parent a0eaffc commit 773110e
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 34 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [3.7.0] - 2023-11-27

### Added

- DAP: Support dynamically compiled executables [[#64]https://github.com/mrcjkb/rustaceanvim/pull/64).
Thanks [@richchurcher](https://github.com/richchurcher)!
- Configures dynamic library paths by default (with the ability to disable)
- Loads Rust type information by default (with the ability to disable).

### Fixed

Expand Down
4 changes: 3 additions & 1 deletion lua/rustaceanvim/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ vim.g.rustaceanvim = vim.g.rustaceanvim

---@class RustaceanDapOpts
---@field adapter? DapExecutableConfig | DapServerConfig | disable | fun():(DapExecutableConfig | DapServerConfig | disable) Defaults to a `DapServerConfig` if `codelldb` is detected, and to a `DapExecutableConfig` if `lldb` is detected. Set to `false` to disable.
---@field auto_generate_source_map fun():boolean | boolean Whether to auto-generate a source map for the standard library.
---@field add_dynamic_library_paths? boolean | fun():boolean Accommodate dynamically-linked targets by passing library paths to lldb. Default: `true`.
---@field auto_generate_source_map? fun():boolean | boolean Whether to auto-generate a source map for the standard library.
---@field load_rust_types? fun():boolean | boolean Whether to get Rust types via initCommands (rustlib/etc/lldb_commands). Default: `true`.

---@alias disable false

Expand Down
53 changes: 35 additions & 18 deletions lua/rustaceanvim/config/internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ local RustaceanConfig
---@class RustAnalyzerInitializedStatusInternal : RustAnalyzerInitializedStatus
---@field health lsp_server_health_status
---@field quiescent boolean inactive?
---
---@param dap_adapter DapExecutableConfig | DapServerConfig | disable
---@return boolean
local function should_enable_dap_config_value(dap_adapter)
local adapter = types.evaluate(dap_adapter)
if adapter == false then
return false
end
return vim.fn.executable('rustc') == 1
end

---@class RustaceanConfig
local RustaceanDefaultConfig = {
Expand Down Expand Up @@ -194,12 +204,6 @@ local RustaceanDefaultConfig = {
adapter = function()
--- @type DapExecutableConfig | DapServerConfig | disable
local result = false
---@type DapExecutableConfig
local lldb_vscode = {
type = 'executable',
command = 'lldb-vscode',
name = 'lldb',
}
if vim.fn.executable('codelldb') == 1 then
---@cast result DapServerConfig
result = {
Expand All @@ -211,23 +215,36 @@ local RustaceanDefaultConfig = {
args = { '--port', '${port}' },
},
}
elseif vim.fn.executable('lldb-vscode') == 1 then
result = lldb_vscode
elseif vim.fn.executable('lldb-dap') == 1 then
-- On some distributions, it may still have the old name
result = lldb_vscode
result.command = 'lldb-dap'
else
local has_lldb_dap = vim.fn.executable('lldb-dap') == 1
local has_lldb_vscode = vim.fn.executable('lldb-vscode') == 1
if not has_lldb_dap and not has_lldb_vscode then
return result
end
local command = has_lldb_dap and 'lldb-dap' or 'lldb-vscode'
---@cast result DapExecutableConfig
result = {
type = 'executable',
command = command,
name = 'lldb',
}
end
return result
end,
--- Whether to auto-generate a source map for the standard library.
--- Accommodate dynamically-linked targets by passing library paths to lldb.
---@type boolean | fun():boolean
add_dynamic_library_paths = function()
return should_enable_dap_config_value(RustaceanConfig.dap.adapter)
end,
--- Auto-generate a source map for the standard library.
---@type boolean | fun():boolean
auto_generate_source_map = function()
local adapter = types.evaluate(RustaceanConfig.dap.adapter)
if adapter == false then
return false
end
return vim.fn.executable('rustc') == 1
return should_enable_dap_config_value(RustaceanConfig.dap.adapter)
end,
--- Get Rust types via initCommands (rustlib/etc/lldb_commands).
---@type boolean | fun():boolean
load_rust_types = function()
return should_enable_dap_config_value(RustaceanConfig.dap.adapter)
end,
},
}
Expand Down
113 changes: 101 additions & 12 deletions lua/rustaceanvim/dap.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local config = require('rustaceanvim.config.internal')
local compat = require('rustaceanvim.compat')
local shell = require('rustaceanvim.shell')
local types = require('rustaceanvim.types.internal')

local function scheduled_error(err)
Expand Down Expand Up @@ -85,7 +86,7 @@ local function get_rustc_sysroot(callback)
if sc.code ~= 0 or result == nil then
return
end
callback(result)
callback((result:gsub('\n$', '')))
end)
end

Expand Down Expand Up @@ -115,31 +116,110 @@ local function format_source_map(tbl)
return tbl_to_tuple_list(tbl)
end

---@type DapSourceMap
local source_map = {}
---@type {[string]: DapSourceMap}
local source_maps = {}

---See https://github.com/vadimcn/codelldb/issues/204
local function generate_source_map()
---@param workspace_root string
local function generate_source_map(workspace_root)
get_rustc_commit_hash(function(commit_hash)
get_rustc_sysroot(function(rustc_sysroot)
---@type DapSourceMap
local new_map = {
[compat.joinpath('/rustc', commit_hash)] = compat.joinpath(rustc_sysroot, 'lib', 'rustlib', 'src', 'rust'),
}
source_map = vim.tbl_extend('force', source_map, new_map)
source_maps[workspace_root] = vim.tbl_extend('force', source_maps[workspace_root] or {}, new_map)
end)
end)
end

---@param args RADebuggableArgs
function M.start(args)
vim.notify('Compiling a debug build for debugging. This might take some time...')
---@type {[string]: string[]}
local init_commands = {}

local function get_lldb_commands(workspace_root)
get_rustc_sysroot(function(rustc_sysroot)
local script_import = 'command script import "'
.. compat.joinpath(rustc_sysroot, 'lib', 'rustlib', 'etc', 'lldb_lookup.py')
.. '"'
local commands_file = compat.joinpath(rustc_sysroot, 'lib', 'rustlib', 'etc', 'lldb_commands')
local file = io.open(commands_file, 'r')
local workspace_root_cmds = {}
if file then
for line in file:lines() do
table.insert(workspace_root_cmds, line)
end
file:close()
end
table.insert(workspace_root_cmds, 1, script_import)
init_commands[workspace_root] = workspace_root_cmds
end)
end

---@alias EnvironmentMap {[string]: string[]}

---@type {[string]: EnvironmentMap}
local environments = {}

-- Most succinct description: https://github.com/bevyengine/bevy/issues/2589#issuecomment-1753413600
---@param workspace_root string
local function add_dynamic_library_paths(workspace_root)
compat.system({ 'rustc', '--print', 'target-libdir' }, nil, function(sc)
---@cast sc vim.SystemCompleted
local result = sc.stdout
if sc.code ~= 0 or result == nil then
return
end
local rustc_target_path = (result:gsub('\n$', ''))
local target_path = compat.joinpath(workspace_root, 'target', 'debug', 'deps')
local sep = ':'
local win_sep = ';'
if shell.is_windows() then
local path = os.getenv('PATH') or ''
environments[workspace_root] = environments[workspace_root]
or {
PATH = rustc_target_path .. win_sep .. target_path .. win_sep .. path,
}
elseif shell.is_macos() then
local dkld_library_path = os.getenv('DKLD_LIBRARY_PATH') or ''
environments[workspace_root] = environments[workspace_root]
or {
DKLD_LIBRARY_PATH = rustc_target_path .. sep .. target_path .. sep .. dkld_library_path,
}
else
local ld_library_path = os.getenv('LD_LIBRARY_PATH') or ''
environments[workspace_root] = environments[workspace_root]
or {
LD_LIBRARY_PATH = rustc_target_path .. sep .. target_path .. sep .. ld_library_path,
}
end
end)
end

---@param args RADebuggableArgs
local function handle_configured_options(args)
local is_generate_source_map_enabled = types.evaluate(config.dap.auto_generate_source_map)
---@cast is_generate_source_map_enabled boolean
if is_generate_source_map_enabled then
generate_source_map()
generate_source_map(args.workspaceRoot)
end

local is_load_rust_types_enabled = types.evaluate(config.dap.load_rust_types)
---@cast is_load_rust_types_enabled boolean
if is_load_rust_types_enabled then
get_lldb_commands(args.workspaceRoot)
end

local is_add_dynamic_library_paths_enabled = types.evaluate(config.dap.add_dynamic_library_paths)
---@cast is_add_dynamic_library_paths_enabled boolean
if is_add_dynamic_library_paths_enabled then
add_dynamic_library_paths(args.workspaceRoot)
end
end

---@param args RADebuggableArgs
function M.start(args)
vim.notify('Compiling a debug build for debugging. This might take some time...')
handle_configured_options(args)

local cargo_args = get_cargo_args_from_runnables_args(args)
local cmd = vim.list_extend({ 'cargo' }, cargo_args)
Expand Down Expand Up @@ -213,10 +293,19 @@ function M.start(args)
-- https://www.kernel.org/doc/html/latest/admin-guide/LSM/Yama.html
runInTerminal = false,
}
local final_config = is_generate_source_map_enabled
and next(source_map) ~= nil
and vim.tbl_deep_extend('force', dap_config, { sourceMap = format_source_map(source_map) })
local final_config = next(init_commands) ~= nil
and vim.tbl_deep_extend('force', dap_config, { initCommands = init_commands[args.workspaceRoot] })
or dap_config

local source_map = source_maps[args.workspaceRoot]
final_config = next(source_map) ~= nil
and vim.tbl_deep_extend('force', final_config, { sourceMap = format_source_map(source_map) })
or final_config

local environment = environments[args.workspaceRoot]
final_config = next(environment) ~= nil and vim.tbl_deep_extend('force', final_config, { env = environment })
or final_config

-- start debugging
dap.run(final_config)
end)
Expand Down
9 changes: 7 additions & 2 deletions lua/rustaceanvim/shell.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
local M = {}

---@return boolean
local function is_windows()
function M.is_windows()
local sysname = vim.loop.os_uname().sysname
return sysname == 'Windows' or sysname == 'Windows_NT'
end

---@return boolean
function M.is_macos()
return vim.loop.os_uname().sysname == 'Darwin'
end

---@return boolean
local function is_nushell()
---@diagnostic disable-next-line: missing-parameter
Expand All @@ -20,7 +25,7 @@ end
---@param commands string[]
---@return string
function M.chain_commands(commands)
local separator = is_windows() and ' | ' or is_nushell() and ';' or ' && '
local separator = M.is_windows() and ' | ' or is_nushell() and ';' or ' && '
local ret = ''

for i, value in ipairs(commands) do
Expand Down

0 comments on commit 773110e

Please sign in to comment.