FsAutoComplete icon indicating copy to clipboard operation
FsAutoComplete copied to clipboard

Detect environment for snap installs

Open danieljsummers opened this issue 3 years ago • 10 comments

(from a conversation in the F# Slack "general" channel September 20th)

The current VS Code / Ionide combination is not detecting the .NET Core environment if that environment comes from a Linux snap package. As snaps are the preferred method of acquiring the environment (per Microsoft), these projects should be able to detect and run regardless of where the framework is installed.

(The below is my extraction from snap implementation on Ubuntu (20.04); as Canonical is the company behind both snaps and Ubuntu, this could easily be considered the reference implementation.)

When snap support is installed, /snap/bin is added to the path (after /usr/bin, so "normal" packages win if there's a conflict). The snap package install creates an executable dotnet-sdk.dotnet; on initial install of the package, the command dotnet does nothing. There's a recommended post-install step sudo snap alias dotnet-sdk.dotnet dotnet, which creates a dotnet executable in /snap/bin. When I do which dotnet, the result is /snap/bin/dotnet; which dotnet-sdk.dotnet returns /snap/bin/dotnet-sdk.dotnet. For completeness, detection of dotnet may want to also check for this permutation.

Within /snap/dotnet-sdk/current (which is a symlink to the currently-effective snap), here is the directory tree: (under sdk, only top-level directories are listed)

├── etc
│   ├── gss
│   │   └── mech.d
│   └── ldap
├── host
│   └── fxr
│       └── 5.0.0-rc.1.20451.14
├── lib
│   └── x86_64-linux-gnu
├── meta
├── packs
│   ├── Microsoft.AspNetCore.App.Ref
│   │   └── 5.0.0-rc.1.20451.17
│   │       ├── data
│   │       └── ref
│   │           └── net5.0
│   ├── Microsoft.NETCore.App.Host.linux-x64
│   │   └── 5.0.0-rc.1.20451.14
│   │       └── runtimes
│   │           └── linux-x64
│   │               └── native
│   ├── Microsoft.NETCore.App.Ref
│   │   └── 5.0.0-rc.1.20451.14
│   │       ├── data
│   │       └── ref
│   │           └── net5.0
│   └── NETStandard.Library.Ref
│       └── 2.1.0
│           ├── data
│           └── ref
│               └── netstandard2.1
├── sdk
│   └── 5.0.100-rc.1.20452.10
│       ├── AppHostTemplate
│       ├── cs
│       ├── Current
│       ├── de
│       ├── DotnetTools
│       ├── en
│       ├── es
│       ├── Extensions
│       ├── fr
│       ├── FSharp
│       ├── it
│       ├── ja
│       ├── ko
│       ├── Microsoft
│       ├── pl
│       ├── pt-BR
│       ├── ref
│       ├── Roslyn
│       ├── ru
│       ├── runtimes
│       ├── SdkResolvers
│       ├── Sdks
│       ├── TestHost
│       ├── tr
│       ├── zh-Hans
│       └── zh-Hant
├── shared
│   ├── Microsoft.AspNetCore.App
│   │   └── 5.0.0-rc.1.20451.17
│   └── Microsoft.NETCore.App
│       └── 5.0.0-rc.1.20451.14
├── snap
│   └── command-chain
├── templates
│   └── 5.0.0-rc.1.20452.10
├── testing
└── usr
    ├── lib
    │   ├── gcc
    │   │   └── x86_64-linux-gnu
    │   │       └── 8
    │   ├── sasl2
    │   └── x86_64-linux-gnu
    │       ├── krb5
    │       │   └── plugins
    │       │       └── libkrb5
    │       ├── openssl-1.0.0
    │       │   └── engines
    │       └── sasl2
    └── share
        ├── doc
        │   ... all the docs ...
        ├── gcc-8
        │   └── python
        │       └── libstdcxx
        │           └── v6
        ├── gdb
        │   └── auto-load
        │       └── usr
        │           └── lib
        │               └── x86_64-linux-gnu
        ├── lintian
        │   └── overrides
        └── man
            └── man5

I'd be happy to provide any more information as required - just let me know.

danieljsummers avatar Oct 06 '20 14:10 danieljsummers

A core problem here is an assumption that ionide and FSAC make about the relationship between the dotnet binary and the dotnet sdk install directory. Currently, the relationship is that the binary and the sdk install directory are siblings, but this is not necessarily the case.

If the two were independently settable (eg DOTNET_HOST and DOTNET_SDK_ROOT or something like that), then the tools would be able to use the correct value for each use case. the defaults could be set to establish the current relationships (which work for most default installs on non-snap platforms) but still retain enough flexibility for snap users to set them in their user or workspace vscode configs.

@Krzysztof-Cieslak thoughts here?

baronfel avatar Nov 13 '20 20:11 baronfel

This isn't the only project with an issue like this (from OminSharp)

danieljsummers avatar Nov 18 '20 00:11 danieljsummers

The appropriately-numbered #666 is aiming to tackle this, but I could use some feedback from you @danieljsummers (and any others that might be having this issue).

First, a bit of background on snaps in general:

  • apps seem to be run by the snapd daemon
  • installed apps are installed to isolated directories
  • symlinks for shortcuts to binaries are installed to /snap/bin but the symlinks point to the snapd binary, which detects the service to be run by the name of the symlink invoked (eg /snap/bin/dotnet -> /snap/bin/snapd (logically speaking))

This impacts us in a few places in FSAC, all related to how we spawn dotnet processes:

  • when performing dotnet cli actions like new, install, listing templates, etc
  • when starting the background services subprocess component of the FSAC system
  • when running the FSAC LSP tests (we perform a dotnet restore as part of test setup in some.many cases)

Spawning these symlinked processes is hard because:

  • System.IO.File APIs fail hard at following/resolving symlinks
  • System.Diagnostics.Process APIs will only follow the symlinks if you set UseShellExecute to true, which is bad because
  • Processes started with UseShellExecute set to true cannot have their input and output redirected for programmatic manipulation, only buffered for review after the process is terminated. This is bad because
  • We spawn all of our dotnet processes with UseShellExecute set to false, in order to redirect stdin/stdout for all of these cases, but primarily for the backgroundservice child process, where were send notifications to it over stdin in the LSP format.

That PR currently enables broad usage of FSAC on snap-based systems (and we can prove that out by seeing that the tests on a snap-installed ubuntu 20.04 image run) by adding a knowledge of where snap installed binaries by default. This is merely a convenience, because users were always able to set the dotnet root manually, which would have done similar things. The difficulty in this case would be in knowing to set the dotnet root to /snap/dotnet-sdk/current instead of the root path for the dotnet binary symlink.

What this enables is almost all functionality, excepting those problem areas above. Everything except the dotnet sdk interactions should work if the user disables background checking. My question to @danieljsummers et al is:

  • Is this good enough for a first pass of snap support?

If not, we can keep baking on this.

A proper solution for this would likely need to:

  • UseShellExecute set to true for all cases (trivial for dotnet sdk usages because we can also read the buffered output after command execution and that would serve our purposes fine),
  • Find another way to communicate with a long-running subprocess other than redirected stdin/stdout. On linux it might be possible to manually open read/write streams to the file descriptors in /proc/<prodid>/fd/0 and 1, respectively, but a similar mechanism would need to be found for Windows. Alternatively named pipes might be an option.

baronfel avatar Nov 23 '20 15:11 baronfel

I'll dig in more this evening, but just looking over things quickly - while you're right about /snap/bin's symlinks, /snap/dotnet-sdk/current/dotnet is not a wrapper for snap (at least on my machine); it's an executable. Could you directly run that without shell-executing to follow the /snap/bin/dotnet (or, by default, /snap/bin/dotnet-sdk.dotnet) symlinks?

danieljsummers avatar Nov 23 '20 16:11 danieljsummers

That's an interesting avenue to try out. The difficulty would be resolving that path somehow in a non-ad-hoc (which I guess is just a 'hoc') manner. Right now what we do to run the tests on snap is:

  • see if dotnet is in the PATH by probing the PATH's folders
  • if found, see if the probed dotnet is a symlink. if so, resolve that symlink else just use the probed dotnet

so we'd need to be able to discover some kind of link between /snap/bin/dotnet and /snap/dotnet-sdk/current/dotnet. Ideally some principled link, but I'm ok with a bit of hackiness here too :D

baronfel avatar Nov 23 '20 16:11 baronfel

Oh I see what's going on there: the dotnet at /snap/dotnet-sdk/current/dotnet is the actual dotnet binary from the SDK install, which comes from being virtually mounted from the snap at /var/lib/snapd/snaps/dotnet-sdk_<some_version>.snap. There is no direct relationship between the two locations at all (aside from the post-hoc one we know of by virtue of experimentation)

baronfel avatar Nov 23 '20 19:11 baronfel

I think that the /snap/dotnet-sdk/current/dotnet (apart from current being a symlink to the latest install) is the path across snaps. (The goal is that there aren't differences across the various distros.) Maybe @cartermp can tell us from within Microsoft if this path would be fine to hard-code if the resolved dotnet executable is /snap/bin/dotnet.

danieljsummers avatar Nov 23 '20 19:11 danieljsummers

Just a question - is there a solution top this issue? Please link it here.

stela2502 avatar Jul 01 '22 10:07 stela2502

I'd say that not using Snap sounds like a pretty good solution

Krzysztof-Cieslak avatar Jul 01 '22 15:07 Krzysztof-Cieslak

Yes, but that is not a solution, just a bug-fix. I hope the SNAP developer come up with a real solution.

stela2502 avatar Jul 04 '22 07:07 stela2502