-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[release/10.0.4xx] Fix hostfxr resolution failure on musl #55519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mthalman
merged 1 commit into
dotnet:release/10.0.4xx
from
mthalman:mthalman-backport-hostfxr-musl-fix
Jul 29, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using NuGet.Versioning; | ||
|
|
||
| namespace Microsoft.DotNet.HostFxr; | ||
|
|
||
| /// <summary> | ||
| /// Locates the native <c>hostfxr</c> library underneath a .NET installation root. | ||
| /// This is used as a fallback when the host does not publish the <c>HOSTFXR_PATH</c> | ||
| /// runtime property (for example when the SDK is launched via <c>dotnet exec dotnet.dll</c> | ||
| /// rather than as a first-class SDK command, which is how the <c>dnx</c> script works). | ||
| /// Dependencies are injected so the pure path-resolution logic can be unit tested. | ||
| /// </summary> | ||
| internal static class HostFxrPathResolver | ||
| { | ||
| /// <summary> | ||
| /// Finds the highest-versioned <c>hostfxr</c> library under | ||
| /// <paramref name="dotnetRoot"/>/host/fxr, returning its full path or | ||
| /// <see cref="string.Empty"/> if it cannot be found. | ||
| /// </summary> | ||
| internal static string ResolveHostFxrPath( | ||
| string? dotnetRoot, | ||
| bool isWindows, | ||
| bool isMacOS, | ||
| Func<string, bool> directoryExists, | ||
| Func<string, string[]> getDirectories, | ||
| Func<string, bool> fileExists) | ||
| { | ||
| if (string.IsNullOrEmpty(dotnetRoot)) | ||
| { | ||
| return string.Empty; | ||
| } | ||
|
|
||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| if (!directoryExists(fxrDir)) | ||
| { | ||
| return string.Empty; | ||
| } | ||
|
|
||
| // Match the native host's get_latest_fxr behavior: consider every valid | ||
| // semantic version, including prerelease versions, and select the highest. | ||
| // SDK and runtime roll-forward settings apply after hostfxr is loaded and | ||
| // do not affect hostfxr selection. | ||
| string? latestFxr = getDirectories(fxrDir) | ||
| .Select(path => | ||
| { | ||
| SemanticVersion.TryParse(Path.GetFileName(path), out SemanticVersion? version); | ||
| return new | ||
| { | ||
| Path = path, | ||
| Version = version | ||
| }; | ||
| }) | ||
| .Where(candidate => candidate.Version is not null) | ||
| .OrderByDescending(candidate => candidate.Version) | ||
| .Select(candidate => candidate.Path) | ||
| .FirstOrDefault(); | ||
|
|
||
| if (latestFxr is null) | ||
| { | ||
| return string.Empty; | ||
| } | ||
|
|
||
| string hostfxrName = isWindows | ||
| ? "hostfxr.dll" | ||
| : isMacOS | ||
| ? "libhostfxr.dylib" | ||
| : "libhostfxr.so"; | ||
|
|
||
| string hostfxrPath = Path.Combine(latestFxr, hostfxrName); | ||
| return fileExists(hostfxrPath) ? hostfxrPath : string.Empty; | ||
| } | ||
| } | ||
50 changes: 50 additions & 0 deletions
50
src/Resolvers/Microsoft.DotNet.NativeWrapper/HostFxrLocator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| #if NETCOREAPP | ||
| using Microsoft.DotNet.HostFxr; | ||
|
|
||
| namespace Microsoft.DotNet.NativeWrapper | ||
| { | ||
| /// <summary> | ||
| /// NativeWrapper-facing entry point for locating the native <c>hostfxr</c> library. | ||
| /// The actual path-resolution logic lives in <see cref="HostFxrPathResolver"/> | ||
| /// under <c>src/Common</c>. | ||
| /// </summary> | ||
| internal static class HostFxrLocator | ||
| { | ||
| /// <summary> | ||
| /// Finds the highest-versioned <c>hostfxr</c> library under | ||
| /// <paramref name="dotnetRoot"/>/host/fxr, returning its full path or | ||
| /// <see cref="string.Empty"/> if it cannot be found. | ||
| /// </summary> | ||
| internal static string ResolveHostFxrPath( | ||
| string? dotnetRoot, | ||
| bool isWindows, | ||
| bool isMacOS, | ||
| Func<string, bool> directoryExists, | ||
| Func<string, string[]> getDirectories, | ||
| Func<string, bool> fileExists) | ||
| => HostFxrPathResolver.ResolveHostFxrPath( | ||
| dotnetRoot, | ||
| isWindows, | ||
| isMacOS, | ||
| directoryExists, | ||
| getDirectories, | ||
| fileExists); | ||
|
|
||
| /// <summary> | ||
| /// Resolves the <c>hostfxr</c> path against the live filesystem, deriving the .NET | ||
| /// installation root from the current process (the running <c>dotnet</c> host). | ||
| /// </summary> | ||
| internal static string ResolveHostFxrPath() | ||
| => ResolveHostFxrPath( | ||
| EnvironmentProvider.GetDotnetExeDirectory(), | ||
| isWindows: OperatingSystem.IsWindows(), | ||
| isMacOS: OperatingSystem.IsMacOS(), | ||
| directoryExists: Directory.Exists, | ||
| getDirectories: Directory.GetDirectories, | ||
| fileExists: File.Exists); | ||
| } | ||
| } | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
227 changes: 227 additions & 0 deletions
227
test/dotnet.Tests/NativeWrapperTests/HostFxrLocatorTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Microsoft.DotNet.NativeWrapper; | ||
|
|
||
| namespace Microsoft.DotNet.Cli.Tests; | ||
|
|
||
| public class HostFxrLocatorTests | ||
| { | ||
| private static string BuildPath(bool isWindows, params string[] segments) | ||
| { | ||
| string root = isWindows ? @"C:\" : "/"; | ||
| return segments.Length == 0 ? root : Path.Combine(root, Path.Combine(segments)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_WithValidFxrDir_ReturnsPath() | ||
| { | ||
| string dotnetRoot = BuildPath(true, "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string fxrVersion = Path.Combine(fxrDir, "11.0.0"); | ||
| string expectedPath = Path.Combine(fxrVersion, "hostfxr.dll"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: path => path == fxrDir, | ||
| getDirectories: _ => [fxrVersion], | ||
| fileExists: path => path == expectedPath); | ||
|
|
||
| result.Should().Be(expectedPath); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_PicksHighestVersion() | ||
| { | ||
| string dotnetRoot = BuildPath(true, "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string v800 = Path.Combine(fxrDir, "8.0.0"); | ||
| string v901 = Path.Combine(fxrDir, "9.0.1"); | ||
| string v900 = Path.Combine(fxrDir, "9.0.0"); | ||
| string expectedPath = Path.Combine(v901, "hostfxr.dll"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: _ => true, | ||
| getDirectories: _ => [v800, v901, v900], | ||
| fileExists: _ => true); | ||
|
|
||
| result.Should().Be(expectedPath); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_NullOrEmptyDotnetRoot_ReturnsEmpty() | ||
| { | ||
| string resultNull = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: null, | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: _ => true, | ||
| getDirectories: _ => [], | ||
| fileExists: _ => true); | ||
|
|
||
| string resultEmpty = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: string.Empty, | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: _ => true, | ||
| getDirectories: _ => [], | ||
| fileExists: _ => true); | ||
|
|
||
| resultNull.Should().BeEmpty(); | ||
| resultEmpty.Should().BeEmpty(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_MissingFxrDirectory_ReturnsEmpty() | ||
| { | ||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: BuildPath(true, "dotnet"), | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: _ => false, | ||
| getDirectories: _ => [], | ||
| fileExists: _ => false); | ||
|
|
||
| result.Should().BeEmpty(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_FxrDirExistsButNoHostfxrFile_ReturnsEmpty() | ||
| { | ||
| string dotnetRoot = BuildPath(true, "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string fxrVersion = Path.Combine(fxrDir, "9.0.0"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: _ => true, | ||
| getDirectories: _ => [fxrVersion], | ||
| fileExists: _ => false); | ||
|
|
||
| result.Should().BeEmpty(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_OnMacOS_LooksForDylib() | ||
| { | ||
| string dotnetRoot = Path.Combine("/", "usr", "local", "share", "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string fxrVersion = Path.Combine(fxrDir, "9.0.0"); | ||
| string expectedPath = Path.Combine(fxrVersion, "libhostfxr.dylib"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: false, | ||
| isMacOS: true, | ||
| directoryExists: path => path == fxrDir, | ||
| getDirectories: _ => [fxrVersion], | ||
| fileExists: path => path == expectedPath); | ||
|
|
||
| result.Should().Be(expectedPath); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_OnLinux_LooksForSo() | ||
| { | ||
| string dotnetRoot = Path.Combine("/", "usr", "share", "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string fxrVersion = Path.Combine(fxrDir, "9.0.0"); | ||
| string expectedPath = Path.Combine(fxrVersion, "libhostfxr.so"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: false, | ||
| isMacOS: false, | ||
| directoryExists: path => path == fxrDir, | ||
| getDirectories: _ => [fxrVersion], | ||
| fileExists: path => path == expectedPath); | ||
|
|
||
| result.Should().Be(expectedPath); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_FindsPrereleaseVersionDirectory() | ||
| { | ||
| string dotnetRoot = Path.Combine("/", "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string fxrVersion = Path.Combine(fxrDir, "11.0.0-preview.6.26359.118"); | ||
| string expectedPath = Path.Combine(fxrVersion, "libhostfxr.so"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: false, | ||
| isMacOS: false, | ||
| directoryExists: path => path == fxrDir, | ||
| getDirectories: _ => [fxrVersion], | ||
| fileExists: path => path == expectedPath); | ||
|
|
||
| result.Should().Be(expectedPath); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_PicksHighestVersion_IncludingPrerelease() | ||
| { | ||
| string dotnetRoot = BuildPath(true, "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string v900 = Path.Combine(fxrDir, "9.0.0"); | ||
| string v11Preview = Path.Combine(fxrDir, "11.0.0-preview.6.26359.118"); | ||
| string expectedPath = Path.Combine(v11Preview, "hostfxr.dll"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: _ => true, | ||
| getDirectories: _ => [v900, v11Preview], | ||
| fileExists: _ => true); | ||
|
|
||
| result.Should().Be(expectedPath); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_PrefersStableOverPrereleaseOfSameCore() | ||
| { | ||
| string dotnetRoot = BuildPath(true, "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string stable = Path.Combine(fxrDir, "11.0.0"); | ||
| string preview = Path.Combine(fxrDir, "11.0.0-preview.6.26359.118"); | ||
| string expectedPath = Path.Combine(stable, "hostfxr.dll"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: _ => true, | ||
| getDirectories: _ => [preview, stable], | ||
| fileExists: _ => true); | ||
|
|
||
| result.Should().Be(expectedPath); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveHostFxrPath_OrdersPrereleaseSegmentsNumerically() | ||
| { | ||
| string dotnetRoot = BuildPath(true, "dotnet"); | ||
| string fxrDir = Path.Combine(dotnetRoot, "host", "fxr"); | ||
| string preview6 = Path.Combine(fxrDir, "11.0.0-preview.6.26359.118"); | ||
| string preview10 = Path.Combine(fxrDir, "11.0.0-preview.10.26400.1"); | ||
| string expectedPath = Path.Combine(preview10, "hostfxr.dll"); | ||
|
|
||
| string result = HostFxrLocator.ResolveHostFxrPath( | ||
| dotnetRoot: dotnetRoot, | ||
| isWindows: true, | ||
| isMacOS: false, | ||
| directoryExists: _ => true, | ||
| getDirectories: _ => [preview6, preview10], | ||
| fileExists: _ => true); | ||
|
|
||
| result.Should().Be(expectedPath); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.