diff --git a/src/BuiltInTools/Watch/Context/EnvironmentOptions.cs b/src/BuiltInTools/Watch/Context/EnvironmentOptions.cs index 0569229e3233..61eac70ca07d 100644 --- a/src/BuiltInTools/Watch/Context/EnvironmentOptions.cs +++ b/src/BuiltInTools/Watch/Context/EnvironmentOptions.cs @@ -73,7 +73,7 @@ public TimeSpan GetProcessCleanupTimeout(bool isHotReloadEnabled) private static string ValidateMuxerPath(string path) { - Debug.Assert(Path.GetFileNameWithoutExtension(path) == "dotnet"); + Debug.Assert(Path.GetFileName(path) == $"dotnet{PathUtilities.ExecutableExtension}"); return path; } diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs index 960e162a04b3..b305691f90e8 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs @@ -36,12 +36,14 @@ public string MuxerPath public Muxer() { + string muxerFileName = MuxerName + Constants.ExeSuffix; + // 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}"); + string muxerPathMaybe = Path.Combine(rootPath, muxerFileName); if (File.Exists(muxerPathMaybe)) { _muxerPath = muxerPathMaybe; @@ -58,8 +60,9 @@ public Muxer() 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)) + // The current process should be dotnet in most normal scenarios except when dotnet.dll is loaded in a custom host like the testhost. + // Use GetFileName (not GetFileNameWithoutExtension) to avoid false matches with dotnet-prefixed names like "dotnet.Tests". + if (processPath is not null && !Path.GetFileName(processPath).Equals(muxerFileName, StringComparison.OrdinalIgnoreCase)) { // SDK sets DOTNET_HOST_PATH as absolute path to current dotnet executable processPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); @@ -69,7 +72,7 @@ public Muxer() var root = Environment.GetEnvironmentVariable("DOTNET_ROOT"); if (root is not null) { - processPath = Path.Combine(root, $"dotnet{Constants.ExeSuffix}"); + processPath = Path.Combine(root, muxerFileName); } } } diff --git a/src/RazorSdk/Tool/ServerProtocol/ServerConnection.cs b/src/RazorSdk/Tool/ServerProtocol/ServerConnection.cs index eef5309911a8..afa8bd2b5b64 100644 --- a/src/RazorSdk/Tool/ServerProtocol/ServerConnection.cs +++ b/src/RazorSdk/Tool/ServerProtocol/ServerConnection.cs @@ -290,14 +290,15 @@ private static string FindDotNetExecutable() expectedPath = Process.GetCurrentProcess().MainModule.FileName; #endif - if ("dotnet".Equals(Path.GetFileNameWithoutExtension(expectedPath), StringComparison.Ordinal)) + // Use GetFileName (not GetFileNameWithoutExtension) to avoid false matches with dotnet-prefixed names like "dotnet.Tests". + var exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet"; + if (exeName.Equals(Path.GetFileName(expectedPath), StringComparison.Ordinal)) { return expectedPath; } // We were probably running from Visual Studio or Build Tools and found MSBuild instead of dotnet. Use the PATH... var paths = Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator); - var exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet"; foreach (string path in paths) { var dotnetPath = Path.Combine(path, exeName); diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs index ae38c8e2f61c..dfa1f29e516f 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs @@ -234,9 +234,7 @@ private sealed class CachedState minimumVSDefinedSDKVersion); } - string? fullPathToMuxer = - TryResolveMuxerFromSdkResolution(dotnetSdkDir) - ?? Path.Combine(dotnetRoot, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Constants.DotNetExe : Constants.DotNet); + string? fullPathToMuxer = TryResolveMuxerFromSdkResolution(dotnetSdkDir) ?? Path.Combine(dotnetRoot, Constants.DotNetFileName); if (File.Exists(fullPathToMuxer)) { // keeping this in until this component no longer needs to handle 17.14. @@ -349,10 +347,9 @@ private sealed class CachedState /// private static string? TryResolveMuxerFromSdkResolution(string resolvedSdkDirectory) { - var expectedFileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Constants.DotNetExe : Constants.DotNet; var currentDir = resolvedSdkDirectory; var expectedDotnetRoot = Path.GetDirectoryName(Path.GetDirectoryName(currentDir)); - var expectedMuxerPath = Path.Combine(expectedDotnetRoot, expectedFileName); + var expectedMuxerPath = Path.Combine(expectedDotnetRoot, Constants.DotNetFileName); if (File.Exists(expectedMuxerPath)) { return expectedMuxerPath; diff --git a/src/Resolvers/Microsoft.DotNet.NativeWrapper/Constants.cs b/src/Resolvers/Microsoft.DotNet.NativeWrapper/Constants.cs index 998663b58204..76cafdb8a425 100644 --- a/src/Resolvers/Microsoft.DotNet.NativeWrapper/Constants.cs +++ b/src/Resolvers/Microsoft.DotNet.NativeWrapper/Constants.cs @@ -7,13 +7,13 @@ internal static class Constants { public const string HostFxr = "hostfxr"; public const string DotNet = "dotnet"; - public const string DotNetExe = "dotnet.exe"; public const string PATH = "PATH"; public const string DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR = "DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR"; public static readonly string ExeSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; + public static readonly string DotNetFileName = DotNet + ExeSuffix; public static class RuntimeProperty { public const string HostFxrPath = "HOSTFXR_PATH"; diff --git a/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs b/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs index c1cf3391b3d7..0e192532108c 100644 --- a/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs +++ b/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs @@ -64,8 +64,8 @@ private IEnumerable SearchPaths // the current process path on .NET Framework. We are expected to find dotnet on PATH. dotnetExe = _getCurrentProcessPath(); - if (string.IsNullOrEmpty(dotnetExe) || !Path.GetFileNameWithoutExtension(dotnetExe) - .Equals(Constants.DotNet, StringComparison.InvariantCultureIgnoreCase)) + if (string.IsNullOrEmpty(dotnetExe) || !Path.GetFileName(dotnetExe) + .Equals(Constants.DotNetFileName, StringComparison.InvariantCultureIgnoreCase)) #endif { string? dotnetExeFromPath = GetCommandPath(Constants.DotNet); diff --git a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnEnvironmentForResolution.cs b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnEnvironmentForResolution.cs index be3c5bea5a74..c0efac103e0b 100644 --- a/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnEnvironmentForResolution.cs +++ b/test/Microsoft.DotNet.MSBuildSdkResolver.Tests/GivenAnEnvironmentForResolution.cs @@ -3,8 +3,12 @@ namespace Microsoft.DotNet.Cli.Utils.Tests { - public class GivenAnEnvironmentForResolution + public class GivenAnEnvironmentForResolution : SdkTest { + public GivenAnEnvironmentForResolution(ITestOutputHelper log) : base(log) + { + } + [Fact] public void ItIgnoresInvalidPath() { @@ -22,5 +26,26 @@ public void ItDoesNotReturnNullDotnetRootOnExtraPathSeparator() var result = NativeWrapper.EnvironmentProvider.GetDotnetExeDirectory(getPathEnvVarFunc); result.Should().NotBeNullOrWhiteSpace(); } + + [Fact] + public void ItDoesNotMistakeDotnetPrefixedProcessForDotnetHost() + { + // Use separate directories for the dotnet host and the dotnet-prefixed process + // to verify the resolver finds dotnet via PATH, not from the process path. + var dotnetDir = TestAssetsManager.CreateTestDirectory(identifier: "dotnetHost").Path; + var processDir = TestAssetsManager.CreateTestDirectory(identifier: "processDir").Path; + + var dotnetFileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty); + File.Create(Path.Combine(dotnetDir, dotnetFileName)).Close(); + + // Simulate a dotnet-prefixed process (e.g. dotnet.Tests from xunit v3) + Func getEnvVar = (input) => input.Equals("PATH") ? dotnetDir : null; + Func getProcessPath = () => Path.Combine(processDir, "dotnet.Tests"); + + var result = NativeWrapper.EnvironmentProvider.GetDotnetExeDirectory(getEnvVar, getProcessPath); + + // Should resolve via PATH to the real dotnet directory, not the process directory + result.Should().Be(dotnetDir); + } } }