From 9f27450b5311422027d01550017ad5d4a8b36c53 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Mon, 27 Nov 2023 22:13:46 +1300 Subject: [PATCH 1/3] feat(dap): support dynamically compiled executable (#64) Co-authored-by: Marc Jakobi Co-authored-by: Marc Jakobi --- lua/rustaceanvim/config/init.lua | 4 +- lua/rustaceanvim/config/internal.lua | 53 ++++++++++------ lua/rustaceanvim/dap.lua | 94 +++++++++++++++++++++++++--- lua/rustaceanvim/shell.lua | 9 ++- 4 files changed, 132 insertions(+), 28 deletions(-) diff --git a/lua/rustaceanvim/config/init.lua b/lua/rustaceanvim/config/init.lua index 05e50ea5..5c5fd2ef 100644 --- a/lua/rustaceanvim/config/init.lua +++ b/lua/rustaceanvim/config/init.lua @@ -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 diff --git a/lua/rustaceanvim/config/internal.lua b/lua/rustaceanvim/config/internal.lua index ef3dd2e6..4912c533 100644 --- a/lua/rustaceanvim/config/internal.lua +++ b/lua/rustaceanvim/config/internal.lua @@ -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 = { @@ -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 = { @@ -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, }, } diff --git a/lua/rustaceanvim/dap.lua b/lua/rustaceanvim/dap.lua index 8b5746aa..944bf4a6 100644 --- a/lua/rustaceanvim/dap.lua +++ b/lua/rustaceanvim/dap.lua @@ -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) @@ -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 @@ -131,16 +132,88 @@ local function generate_source_map() 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 = vim.tbl_extend('force', init_commands, { [workspace_root] = workspace_root_cmds }) + end) +end + +---@type string[] +local environment = {} + +-- 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 '' + table.insert(environment, '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 '' + table.insert( + environment, + 'DKLD_LIBRARY_PATH=' .. rustc_target_path .. sep .. target_path .. sep .. dkld_library_path + ) + else + local ld_library_path = os.getenv('LD_LIBRARY_PATH') or '' + table.insert( + environment, + 'LD_LIBRARY_PATH=' .. rustc_target_path .. sep .. target_path .. sep .. ld_library_path + ) + end + end) +end +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() 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) compat.system(cmd, { cwd = args.workspaceRoot }, function(sc) @@ -213,10 +286,17 @@ 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 + + final_config = next(source_map) ~= nil + and vim.tbl_deep_extend('force', final_config, { sourceMap = tbl_to_tuple_list(source_map) }) + or final_config + + 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) diff --git a/lua/rustaceanvim/shell.lua b/lua/rustaceanvim/shell.lua index da7f4a3f..6a0bbc5e 100644 --- a/lua/rustaceanvim/shell.lua +++ b/lua/rustaceanvim/shell.lua @@ -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 @@ -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 From c29f0289830b6e84fec59476d8e7df5c9f2f2246 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Mon, 27 Nov 2023 10:35:39 +0100 Subject: [PATCH 2/3] fix(#64): compatibility with both `lldb` and `codelldb` --- lua/rustaceanvim/dap.lua | 47 ++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/lua/rustaceanvim/dap.lua b/lua/rustaceanvim/dap.lua index 944bf4a6..17f5f884 100644 --- a/lua/rustaceanvim/dap.lua +++ b/lua/rustaceanvim/dap.lua @@ -116,23 +116,24 @@ 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 ----@type {[string]: string} +---@type {[string]: string[]} local init_commands = {} local function get_lldb_commands(workspace_root) @@ -150,12 +151,14 @@ local function get_lldb_commands(workspace_root) file:close() end table.insert(workspace_root_cmds, 1, script_import) - init_commands = vim.tbl_extend('force', init_commands, { [workspace_root] = workspace_root_cmds }) + init_commands[workspace_root] = workspace_root_cmds end) end ----@type string[] -local environment = {} +---@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 @@ -172,28 +175,32 @@ local function add_dynamic_library_paths(workspace_root) local win_sep = ';' if shell.is_windows() then local path = os.getenv('PATH') or '' - table.insert(environment, 'PATH=' .. rustc_target_path .. win_sep .. target_path .. win_sep .. path) + 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 '' - table.insert( - environment, - 'DKLD_LIBRARY_PATH=' .. rustc_target_path .. sep .. target_path .. sep .. dkld_library_path - ) + 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 '' - table.insert( - environment, - 'LD_LIBRARY_PATH=' .. rustc_target_path .. sep .. target_path .. sep .. ld_library_path - ) + 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) @@ -290,10 +297,12 @@ function M.start(args) 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 = tbl_to_tuple_list(source_map) }) + 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 From 7642e2f2825c438a6dfa301285587903cfea0649 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Mon, 27 Nov 2023 10:39:43 +0100 Subject: [PATCH 3/3] docs(changelog): update --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c015ebc7..af38dc6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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