Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dap): support dynamically compiled executable (#64) #72

Merged
merged 3 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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