Vim is my go to editor for editing text. I wanted to setup vim for rust library project. The language server setup was straight forward with vim-ale. But setup for debugging had a certain amount of challenges.
Challenge 1: Finding the test executable
In library based project in rust can only be debugged using test cases. Cargo generates a test case executable which can be used to run the test. The issue is these binary generated have random hash attached to the filename. Hence the setup for Vimspector has to run binary whose path is non deterministic.
This made it hard to write generic configuration which works for any developer using vim. This issue has plagued others as well and can be checkout on Github. Fortunately there is a way to extract the path from the json output.
1 | cargo test --no-run --message-format=json -q | grep -Poi 'executable":"\\K([^"]*)(?=")' |
Vimspector allows runtime variables to be used in configuration.
An example for this can be found in readme, where process id is found at runtime.
The same logic is used to find the test executable and is set to variable test_exe
.
1 2 3 4 5 6 7 8 9 | { "test_exe": { "shell": [ "bash", "-c", "cargo test --no-run --message-format=json -q | grep -Poi 'executable\":\"\\K([^\"]*)(?=\")'" ] } } |
Challenge 2: Setting up standard libraries source to debug.
By setting up the test_exe
, the test could be debugged.
But, whenever the code stepped into standard library call, the debugged would show machine instruction.
The source file for the symbol seemed to be starting with /rustc/<hash>/
.
This issue was encounter with rust-tools plugins as well.
The solution seems to be adding a source map configuration.
Source map directs /rustc/<hash>/
to <rust-toolchain-src>/lib/rustlib/src/rust/
.
This allows the debugger to find the symbols correctly.
1 2 3 | "sourceMap": { "/rustc/db9d1b20bba1968c1ec1fc49616d4742c1725b4b/" : "${rust_std}/lib/rustlib/src/rust/" } |
The variable rust_std
point to rust tool chain source files.
It could be determined at runtime using following command
1 | rustc --print sysroot |
Challenge 3: Getting rid of the hard code hash
Hash in source map key points to rustc
commit used to compile the project.
This can be determined at runtime using command below.
But as this is on key side of the equation the configuration would not expand the variables.
This seemed like a dead end as there was no way to expand the variable in key.
1 | rustc -Vv | grep -Poi 'commit-hash: \K(.*)' |
On reaching out to the developer of vimspector, there was a hack of #json
pointed out.
This key would coerces the string value from to json type(boolean/number/list/map).
This allowed me to coerces the value of sourceMap
to map using variable in the key.
1 2 3 | { "sourceMap#json": "{\"/rustc/${rustc_commit}/\" : \"${rust_std}/lib/rustlib/src/rust/\"}" } |
Conclusion
To setup vimspector for rust library (test case), there were multiple challenges.
- Test executable name has to be determined at runtime.
- Rust standard library symbol need to be mapped using source map.
- Utilize Vimspector type coerce to allow dynamic key for source map.
Final configuration looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | { "configurations": { "launch": { "adapter": "CodeLLDB", "variables": { "rustc_commit": { "shell": [ "bash", "-c", "rustc -Vv | grep -Poi 'commit-hash: \\K(.*)'" ] }, "rust_std": { "shell": [ "rustc", "--print", "sysroot" ] }, "test_exe": { "shell": [ "bash", "-c", "cargo test --no-run --message-format=json -q | grep -Poi 'executable\":\"\\K([^\"]*)(?=\")'" ] } }, "configuration": { "sourceMap#json": "{\"/rustc/${rustc_commit}/\" : \"${rust_std}/lib/rustlib/src/rust/\"}", "request": "launch", "program": "${test_exe}" }, "args": [ "*${args}" ], "breakpoints": { "exception": { "cpp_throw": "", "cpp_catch": "" } } } } } |