microsoft / vscode-dotnettools

This is a feedback repository to capture issues logged for the C# Dev Kit and related extensions from Visual Studio Code
Other
231 stars 13 forks source link

[BUG] Cannot find .NET SDK installation from PATH environment (macOS) #1471

Open jaraco opened 1 month ago

jaraco commented 1 month ago

Describe the Issue

When launching VS Code directly using the application launcher or Spotlight (and not from a shell environment), I see this error message:

Starting processing the solution file "/Users/jaraco/draft/HelloWorld/HelloWorld.sln" in Dev Kit server...
Failed to find dotnet from path with "which dotnet".
Cannot find .NET SDK installation from PATH environment. C# DevKit extension would not work without a proper installation of the .NET SDK accessible through PATH environment. Rebooting might be necessary in some cases. Check the PATH environment logged in the C# DevKit logging window. In some cases, it could be affected how VS code was started.
PATH=/usr/bin:/bin:/usr/sbin:/sbin

I've installed dotnet through Homebrew (brew install dotnet), and dotnet is available in the shell environment:

 ~ šŸš which dotnet
/opt/homebrew/bin/dotnet
 ~ šŸš dotnet --version
8.0.104

As you can see, however, the path that VS Code gets does not include even the paths found at /etc/paths:

 ~ šŸš cat /etc/paths
/usr/local/bin
/System/Cryptexes/App/usr/bin
/usr/bin
/bin
/usr/sbin
/sbin

Nor those found from the path helper:

 ~ šŸš /usr/libexec/path_helper -s
PATH="/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Users/jaraco/.local/bin:/opt/homebrew/opt/openssl@1.1/bin:/Applications/Firefox.app/Contents/MacOS:/Users/jaraco/.cargo/bin:/opt/python/bin:/opt/homebrew/bin:/Users/jaraco/Dropbox/bin/mac:/Users/jaraco/Dropbox/bin/scripts:/Applications/SourceTree.app/Contents/Resources/:/Applications/Visual Studio Code.app/Contents/Resources/app/bin:/Applications/Sublime Merge.app/Contents/SharedSupport/bin:/Applications/Sublime Text.app/Contents/SharedSupport/bin:/Users/jaraco/code/MestreLion/git-tools"; export PATH;

My default shell is not a standard shell, and I previously contributed to an issue where VS Code didn't recognize the shell environment. That issue has since been fixed and VS Code is able to resolve the shell environment, yet somehow the dot net tools are not resolving the PATH and are getting a very primitive PATH and thereafter failing to initialize dotnet.

Steps To Reproduce

  1. In VS Code, create a C# project using dotnet new console -n HelloWorld
  2. Close VS Code.
  3. Re-open VS Code (from Launchpad or Spotlight or Finder) and observe the error.

Expected Behavior

.Net should find the dotnet command on the path found from path_helper or by inspecting the xonsh or zsh shells (the same environment found by other parts of VS Code).

Environment Information

AbhitejJohn commented 1 month ago

Seems similar to this comment. Tagging @nagilson in case anything has changed since.

nagilson commented 1 month ago

Nothing in this area has changed yet, but I would expect it to in October. Do the workarounds in the above isue work for you @jaraco ?

jaraco commented 1 month ago

The workaround I've been using is to launch VS Code from a shell that has PATH defined.

BTW, the .NET SDK can be installed by C# DevKit now in the walkthrough section in the prerelease versions. For people facing issues on Mac, I would suggest they try that method of SDK installation. :)

I was hoping not to have to rely on C# Dev kit to install the SDK, because that gives me less control and visibility into where it's installed and how. Ideally, I'd prefer to manage packages through homebrew for consistency and visibility. For example, if I have the .NET Install tool install .NET, I'm not sure whether or how that configures the installation to be in the PATH (for other environments).

I did try applying the Shared Existing Dotnet Path:

image

(I also tried /opt/homebrew/bin/dotnet), but that had no effect (same error after restarting with the setting).

I then tried adding the setting specific to the csdevkit:

image

But it resulted in the same error.

Actually, I think I fixed it by adding this to my .zprofile:

export DOTNET_ROOT="$(dirname $(which dotnet))"

This workaround won't help me, as the shell environment isn't coming into play (I don't know where I'd put the export directive that doesn't require running bash, which we know already has the desired path). I did try creating ~/.zprofile with that content in it, but it had no effect on launching VS Code from outside the shell.

jaraco commented 1 month ago

Something else I noticed in the OUTPUT when the error occurs... right after it reports the error and the path it's searching, it emits a message that it's using a .NET runtime:

Cannot find .NET SDK installation from PATH environment...
PATH=/usr/bin:/bin:/usr/sbin:/sbin
Using local .NET runtime at "/Users/jaraco/Library/Application Support/Code/User/globalStorage/ms-dotnettools.vscode-dotnet-runtime/.dotnet/8.0.8~arm64~aspnetcore/dotnet"

Yet still, the errors are emitted during startup.

nagilson commented 1 month ago

The installation UI uses the .NET Installer. The .NET Installer edits the system machine wide. Which yes would impact the PATHs of your environments. That is a valid concern.

For the 2nd message, the reason that happens is the Install Tool downloaded a runtime for C# DevKit to run under as C# DevKit is really written in a mix of Typescript and C#. But it still couldnt find the SDK for your project to run under. The existingPath setting is only for the runtime for DevKit to run under, so it wouldnt fix this behavior. We are fixing the messaging for that setting this week.

nagilson commented 1 month ago

Anyway, I agree with your conclusion that the PATH lookup should use path_helper. Thank you for raising this concern with us and explaining your situation well. Frankly, I did not know about the path_helper, so this is rather useful and appreciated.

I don't actually work on the code that's related to this and it's part of the project system, except I am writing an API to replace that code which is supposed to do a better job at finding the dotnet on the PATH. So your timing is quite helpful as I will be sure to include this change in our API. After that is released, the DevKit and C# team will have to remove their code and move to our API.

nagilson commented 1 month ago

related https://github.com/dotnet/vscode-dotnet-runtime/issues/1824

vinycalves commented 1 month ago

I did this to resolve my problem sudo ln -s /home/$USER/.asdf/shims/dotnet /usr/bin/dotnet and it worked.

Spongman commented 1 month ago

the bug is that the script that looks for donet doesn't start a default login shell (it always just runs /bin/sh), so .bash_profile isn't run, and the PATH isn't configured correctly.

if the dotnet executable is installed in a non-system location (eg. ~/.dotnet), then it's never going to find it.

c:\> wsl -d rocky9 dotnet
/bin/bash: line 1: dotnet: command not found
c:\>wsl -d rocky9 --shell-type login dotnet

Usage: dotnet [options]
Usage: dotnet [path-to-application]

Options:
  -h|--help         Display help.
  --info            Display .NET information.
  --list-sdks       Display the installed SDKs.
  --list-runtimes   Display the installed runtimes.

path-to-application:
  The path to an application .dll file to execute.
nagilson commented 1 month ago

Thank you for the helpful comment. You're right, /bin/sh is the default shell for nodejs, we could add {shell: "/bin/bash"} to our calls to child_process. Perhaps we can switch this based on the $SHELL variable.

I think bash also looks at /etc/profile and ~/.profile first, which would match the behavior of sh. We might have to execute both approaches and try the other if one doesn't work... I would lean towards trying the existing behavior first since it would break less people... but not sure if that's the correct behavior, if bash also checks this path first. I couldn't find adoption statistics with bash vs sh usage.

Not sure if thatd help zsh though. Would that cover your case @jaraco? If not, One question I have that would be helpful is, with your suggested approach of using path_helper , how would we determine which things on the PATH are related to dotnet ? I dont see any option on path_helper to ask it to only return things linked to a certain string, such as dotnet. And in fact, if we scanned your path output from path_helper , there is no string with 'dotnet' in it. I can't think of any way to feed that path string output into the which command. So I'm not sure how that'd be elegantly done in a secure way.

jaraco commented 1 month ago

Would that cover your case @jaraco?

By "that", you mean to override the shell used? It might work. On my system, $SHELL is /Users/jaraco/.local/bin/xonsh. Since each shell has different syntax for invoking commands, it's a bit of an open-ended problem to support all users shells generally. Of course, the shellEnv code aims to do just that.

But yes, if dotnet could be executed under $SHELL, that would probably work.

I've generally tried to avoid using {shell: true} or {shell: "/some/path"} because that adds an extra layer of complexity. Instead, I find it cleaner to inspect the shell environment and then apply the settings to any processes I create. I typically work in Python, however, and the Node semantics might be different.

how would we determine which things on the PATH are related to dotnet ?

My feeling is - search the PATH or use a routine that does. I'm unsure for Node, but in Python, the shutil.which command takes a path argument, which can be used to search for executables in a specified path. It wouldn't be too difficult to implement that search in a bespoke function, something like:

const fs = require('fs');
const path = require('path');

function which(executable, paths) {
  // If no paths are provided, use the PATH environment variable
  if (!paths) {
    paths = process.env.PATH.split(path.delimiter);
  }

  // Iterate over each path in the list
  for (const pathDir of paths) {
    const fullPath = path.join(pathDir, executable);

    // Check if the executable exists and is a file
    try {
      if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
        return fullPath;
      }
    } catch (err) {
      // Ignore errors, continue to the next path
    }
  }

  // If no executable is found, return null
  return null;
}

module.exports = which;

Note that approach doesn't honor PATHEXT on Windows.

I see there is a which in ShellJS, but it also doesn't take as input a path.

If it were me, I'd:

  1. Use the aforementioned shellEnv.ts (or similar) to extract the PATH environment from the user's shell environment.
  2. Write a which function that accepts a path argument to resolve the executable.
  3. Explicitly resolve the executable to be run.

Alternatively, it does appear as if child_process does honor a custom environment for the search path, so you may be able to resolve the unix shell env using something like shellEnv.js, and then pass that to child_process:

const child = child_process('dotnet', { env } );

I do think you want to search for dotnet in the search path and not expect for the path itself to indicate that it contains dotnet.