Commit 998719b9 authored by David McFarland's avatar David McFarland
Browse files

dotnet-sdk_{10,11}: apply muxer path precedence patch

Fixes: #464575
parent 9ec199be
Loading
Loading
Loading
Loading
+115 −0
Original line number Diff line number Diff line
From 2c240a877888117865e6331b0575e43671ba6c8e Mon Sep 17 00:00:00 2001
From: Jamie Magee <jamie.magee@gmail.com>
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: <root>/sdk/<version>
-        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: <root>/sdk/<version>
+            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
+1 −0
Original line number Diff line number Diff line
@@ -149,6 +149,7 @@ stdenv.mkDerivation {
      ./fix-aspnetcore-portable-build.patch
      ./vmr-compiler-opt-v8.patch
    ]
    ++ lib.optional (lib.versionAtLeast version "10") ./Prefer-DOTNET_ROOT-over-directory-traversal-when-fin.patch
    ++ lib.optionals (lib.versionAtLeast version "11") (
      [
        ./fix-skiperroronprebuilts.patch