From 2c240a877888117865e6331b0575e43671ba6c8e Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Wed, 11 Mar 2026 20:26:29 -0700 Subject: [PATCH] Prefer DOTNET_ROOT over directory traversal when finding muxer The Muxer constructor walks up two directories from AppContext.BaseDirectory to find the dotnet host. The runtime resolves symlinks on that path, so on systems that compose multiple SDK versions into one directory via symlinks (Nix, Guix), it ends up in the wrong root and can only see one runtime version. Move the DOTNET_HOST_PATH and DOTNET_ROOT checks ahead of the directory traversal so package managers can set the root explicitly. When neither variable is set, the old heuristic still runs. Also stops _muxerPath from being set to a non-muxer process path (e.g. testhost) when all other lookups fail. Ref: NixOS/nixpkgs#464575 Ref: #51693 --- src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs | 61 +++++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs index 960e162a04..5268cd5543 100644 --- a/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs +++ b/src/sdk/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs @@ -36,45 +36,58 @@ public string MuxerPath public Muxer() { - // Most scenarios are running dotnet.dll as the app - // Root directory with muxer should be two above app base: /sdk/ - string? rootPath = Path.GetDirectoryName(Path.GetDirectoryName(AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar))); - if (rootPath is not null) + // Check environment variables first to allow package managers and + // other tools to explicitly set the dotnet root. This is needed + // when the SDK is installed via symlinks (e.g. Nix, Guix) where + // the directory-traversal heuristic below would resolve symlinks + // and find the wrong root directory. + string? dotnetHostPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); + if (dotnetHostPath is not null && File.Exists(dotnetHostPath)) { - string muxerPathMaybe = Path.Combine(rootPath, $"{MuxerName}{FileNameSuffixes.CurrentPlatform.Exe}"); - if (File.Exists(muxerPathMaybe)) + _muxerPath = dotnetHostPath; + } + + if (_muxerPath is null) + { + var dotnetRoot = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + if (dotnetRoot is not null) { - _muxerPath = muxerPathMaybe; + string rootMuxer = Path.Combine(dotnetRoot, $"dotnet{Constants.ExeSuffix}"); + if (File.Exists(rootMuxer)) + { + _muxerPath = rootMuxer; + } } } if (_muxerPath is null) { - // Best-effort search for muxer. - // SDK sets DOTNET_HOST_PATH as absolute path to current dotnet executable + // Most scenarios are running dotnet.dll as the app + // Root directory with muxer should be two above app base: /sdk/ + string? rootPath = Path.GetDirectoryName(Path.GetDirectoryName(AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar))); + if (rootPath is not null) + { + string muxerPathMaybe = Path.Combine(rootPath, $"{MuxerName}{FileNameSuffixes.CurrentPlatform.Exe}"); + if (File.Exists(muxerPathMaybe)) + { + _muxerPath = muxerPathMaybe; + } + } + } + + if (_muxerPath is null) + { + // Last resort: if the current process is the dotnet muxer itself, use its path. #if NET6_0_OR_GREATER string? processPath = Environment.ProcessPath; #else string processPath = Process.GetCurrentProcess().MainModule.FileName; #endif - // The current process should be dotnet in most normal scenarios except when dotnet.dll is loaded in a custom host like the testhost - if (processPath is not null && !Path.GetFileNameWithoutExtension(processPath).Equals("dotnet", StringComparison.OrdinalIgnoreCase)) + if (processPath is not null && Path.GetFileNameWithoutExtension(processPath).Equals("dotnet", StringComparison.OrdinalIgnoreCase)) { - // SDK sets DOTNET_HOST_PATH as absolute path to current dotnet executable - processPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); - if (processPath is null) - { - // fallback to DOTNET_ROOT which typically holds some dotnet executable - var root = Environment.GetEnvironmentVariable("DOTNET_ROOT"); - if (root is not null) - { - processPath = Path.Combine(root, $"dotnet{Constants.ExeSuffix}"); - } - } + _muxerPath = processPath; } - - _muxerPath = processPath; } } -- 2.53.0