asdf: bug: VS Code fails to initialize workspace with Cargo (Rust Alanyzer)

Describe the Bug

I am using the rust-analyzer extension for Visual Studio Code and when I open a Cargo project I see:

rust-analyzer failed to load workspace: "cargo" "--version" failed: No such file or directory (os error 2)

I have had nothing but problems trying to get other similar tools to find the shims from asdf. There has to be a method, as it is everything that I try to use with asdf-managed tools is simply inoperable.

I cannot find a setting in Code that allows me to change the path to the toolchain.

Steps to Reproduce

  1. asdf plugin add rust
  2. asdf install rust latest
  3. asdf global rust latest
  4. Test VS Code, see it can’t find Cargo.
  5. asdf reshim
  6. Test again, error again.
  7. rm -rf ~/.asdf/shims
  8. asdf reshim
  9. Test again, error again.
  10. ???

Expected Behaviour

The IDEs and other tools should be able to find software managed by asdf. I have tried adding the initialization script to both .zshrc and .profile (ZSH is my login shell).

# Source the asdf version manager
[ -f '/opt/asdf-vm/asdf.sh' ] &&
  source '/opt/asdf-vm/asdf.sh'

Everything works fine from the command line.

Actual Behaviour

No tooling I try (specifically VS code in this case) can ever find binaries from ~/.asdf.

Environment

OS:
Linux jacob-thinkpad 5.17.0-2-MANJARO #1 SMP PREEMPT Fri Feb 25 11:28:23 UTC 2022 x86_64 GNU/Linux

SHELL:
zsh 5.8.1 (x86_64-pc-linux-gnu)

ASDF VERSION:
v0.9.0

ASDF ENVIRONMENT VARIABLES:
ASDF_DIR=/opt/asdf-vm

ASDF INSTALLED PLUGINS:
flutter                      https://github.com/oae/asdf-flutter.git master 1f5b9e2
java                         https://github.com/halcyon/asdf-java.git master b0341fd
nodejs                       https://github.com/asdf-vm/asdf-nodejs.git master 364c078
python                       https://github.com/danhper/asdf-python.git master 57a4d72
ruby                         https://github.com/asdf-vm/asdf-ruby.git master de49662
rust                         https://github.com/code-lever/asdf-rust.git master 0c88f99

asdf plugins affected (if relevant)

rust

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 9
  • Comments: 25 (7 by maintainers)

Most upvoted comments

Lots of questions to understand what is actually happening before we can recommend any solution. You have done some great digging, thanks @Rotwang If you have more time to further investigate that would be appreciated.

@jthegedus

There are two questions to be answered in my mind: Question “A” why does the ~/.bash_profile make vscode work while ~/.bashrc doesn’t (analogous to zshell’s rc files). Question “B” what environment do the graphical applications run with (ones that don’t go near shell when being executed)

A) Actually vscode tends to launch it’s children through a login shell, here is strace output (my initial findings were not quite correct):

[pid  6886] execve("/bin/bash", ["/bin/bash", "-ilc", "'/usr/share/code/code' --ms-enable-electron-run-as-node -p '\"dc5562613985\" + JSON.stringify(process.env) + \"dc5562613985\"'"],  ...

What you can see here is vscode starting a login shell. Excerpt from bash manpage:

When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. […] When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc and ~/.bashrc, if these files exist.

So here is the reason, I had my . $HOME/.asdf/asdf.sh in .bashrc and the .bash_profile was separate from it (i.e. it was empty) so vscode couldn’t get the right path since it starts the login shell.

When I changed my user’s shell (chsh -s /bin/sh myuser) to sh then the vscode would exec /bin/sh -ilc instead (which would in turn source its own login files (/etc/profile and .profile if they exist).

B) It’s difficult to tell on linux because there are so many different ways you can set up your graphical environment. There are many desktop managers (login things that make you enter password and they spin up your gnome/kde/fluxbox/etc. for you) that can be set up in a different way. I’m using ubuntu 20.04 with lightdm and what it does once it authenticates you and runs your session is it runs a wrapper script (located in /usr/sbin/lightdm-session) that sources ~/.profile and ~/.xprofile, GDM seems to be following suit with its /etc/gdm3/Xsession. You end up with variables from /etc/profile and $HOME/.{profile,xsession}

A note on PAM we shouldn’t forget that on linux the authentication is done by PAM which is going to provide its own variables from /etc/environment and ~/.pam_environment by default but it depends on PAM’s configuration.

Having said all that what I did in the end was:

  • Put the . $HOME/.asdf/asdf.sh into the ~/.bashrc
  • Source .bashrc in the ~/.profile (courtesy of ubuntu’s defaults):
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
	. "$HOME/.bashrc"
    fi
fi
  • Got rid of ~/.bash_profile since the ~/.profile seems to be the common denominator here (both for graphical and shell sessions).

I hope that helps.

edit: typo and minor semantic mistake

path only works with zsh, if you are using bash do it like this: export PATH=$HOME/.asdf/shims:$PATH

Hey, @spikespaz. I found a quick fix for this problem. Add the shims folder to the env vars of your .bash_profile (or .zprofile, if you are using Zsh):

# Import the default profile
if [ -f "/etc/profile" ]; then
  source "/etc/profile"
fi

# Set the list of directories that Bash/Zsh searches for programs
path=(
  $HOME/.asdf/shims
  $path
)

I want to mention my gratitude to @baliestri, who helped me solve this problem.

This issue happens to me with some tools and not others. I have experienced this with Rust Analyzer myself, but didn’t stick with Rust long enough to resolve or investigate in any meaningful way.

Given the unpredictable nature of this, I expect that this depends on how the plugin itself tries to find tools and what it does in the background to resolve missing tools. For instance, I use the shellcheck VSCode plugin, but it can’t find my asdf installed Shellcheck version (I am unsure how it searches for it) and then proceeds to download and install a version to somewhere on my machine. Again, since this happens with some plugins and not others I expect it is because of the way the tool is located by the plugin. Happy to be corrected, because if this is an easy fix for us, then great.

The asdf.sh script which is sourced in your shell (as part of step 3 of the setup guide) should source .asdf/shims, so it should already be in your PATH and not needed to be added manually.

Is sourcing it in the "profile" file different to VSCode? Why would this be?

Perhaps I got lucky. I had the below behaviour:

~/.tool-versions         < empty
~/somedir/.tool-versions < deno version here
  • Open VSCode, select directory to open from landing page UI, open in ~/somedir. Correctly resolves Deno version
  • Open integrated terminal in ~/somedir & run code ~
  • VSCode(window 2) opens in ~ and correctly errors trying to resolve Deno version. The Output tab shows the asdf error when the Extension tried to start the Deno Language Server:
No version is set for command deno
Consider adding one of the following versions in your config file at ~/.tool-versions
deno 1.22.1

So it is correctly trying to resolve the tool from the directory it was opened in and not from. At least for me, and with this particular Extension. The Deno extension might just be super resilient. As for my setup while testing this:

  • Windows11 with WSL2 Ubuntu 20.22
  • default shell is Nushell with asdf configured
  • but no nushell was output in strace, it seemed to resolve /usr/bin/sh, and the default Ubuntu ~/.profile does source ~/.bashrc however I do not have asdf configured with in any config other than Nushell.

Extension variance is definitely a factor in this, but not a predictable one and certainly not solvable by us.

User shell setup is also a nightmare, hence why we didn’t want to get into the game of automatically modifying configs.


Additionally, I just got VSCode@1.69 which has unrelated, but further Shell integrations

This release we improved integration with bash-preexec

So VSCode are actively working in this area and can potentially introduce changes which would affect our advice. We must remain aware.


I would like to not get stuck in the weeds of comparing each others Shell setups if we agree on the “what we need to do” items I outlined above.

AFAIK, .bash_profile is always loaded, .bashrc is only loaded for interactive sessions. Analagous for zsh.

Not quite, if you look up the INVOCATION section of the bash 5 manual you can find that for a login shell (login shell can be interactive but doesn’t have to be) following files are being sourced: /etc/profile, ~/.bash_profile, ~/.bash_login, and ~/.profile (in that order). For an interactive bash session that is not a login shell /etc/bash.bashrc and ~/.bashrc, are being read.

Conclusion is that ~/.bash_profile and ~/.bashrc are independent of each other and being read at different times. A lot of people will source their ~/.bashrc inside ~/.bash_profile or ~/.profile and that’s why it looks like ~/.bashrc is being always read (but it doesn’t have to be true).

This feels relevant: https://github.com/microsoft/vscode/issues/130493

But, I don’t really know how asdf works. I need to do some reading, But, feels like if the node extension host (which runs per-workspace) were launched with the right environment and/or ran the asdf hook (?) first then everything would be happy.

So my conclusion is that vscode will abide by the ~/.bash_profile or .zprofile

Is this captured in VSCode documentation anywhere? Before we recommend to all users in the docs I want to understand the issue completely. Is VSCode strictly tied to your Shell? A specific Shell, or the default Shell? If I use Fish will VSCode run that Shell’s equivalent of profile?

Lots of questions to understand what is actually happening before we can recommend any solution. You have done some great digging, thanks @Rotwang If you have more time to further investigate that would be appreciated.

it’s such a fundamental issue for users of asdf, like it’s one of the first things I stumbled on

I am not sure what we can do about it though.

Copying shims to /usr/bin or /usr/sbin type locations might work, but we can’t guess which location each VSCode plugin would decide to use, so we would have to copy to ALL of the possible locations, which does not feel like the right thing to do.

@jthegedus it’s such a fundamental issue for users of asdf, like it’s one of the first things I stumbled on. Right now the best solution for me is to run code from the terminal session. It would be good if there was at least some kind of infrormation/doc pointing to the solution.

edit: I know it’s not asdf issue per se but people think it is, for example look here: https://elixirforum.com/t/failed-to-run-elixir-command-error-in-vs-code/31047/6

edit2: so in my case when I have been sourcing $HOME/.asdf/asdf.sh in ~/.bashrc then it broke but when I added that source to ~/.bash_profile then it worked. So my conclusion is that vscode will abide by the ~/.bash_profile or .zprofile (confirming what @bitterteriyaki have said already)

edit3: I’ve straced vscode and it doesn’t look to me like it’s directly sourcing the files, my assumption is that something launching it has already done that.

@spikespaz As you say in the initial issue:

The IDEs and other tools should be able to find software managed by asdf

They can if they look in the right place.

I would say it’s a problem with the extensions that share this behavior, but considering that they are looking in standardized locations

Standardized locations for tools installed at the system level, non-version managed.

ASDF is not Nix.

Yes, it is not Nix. Our comments on that are here https://asdf-vm.com/guide/introduction.html

Can someone speak as to why @bitterteriyaki method supposedly works?

I am inclined to mark this as unfixable by asdf core and close the Issue.

Only if you want you can write to the .zshrc file.

I use it as follows:

path=(
  $HOME/.asdf/shims
  $HOME/.local/{bin,sbin}
  /usr/{bin,sbin}
  /usr/local/{bin,sbin}
  $path
)