Fix hostfxr resolution failure on musl when HOSTFXR_PATH is not set#55270
Conversation
When dnx runs via `dotnet exec dotnet.dll dnx`, the host treats it as an app launch rather than a first-class SDK command and omits the HOSTFXR_PATH runtime property. On musl the NativeWrapper hostfxr resolver then throws HostFxrRuntimePropertyNotSetException. Add a fallback that locates the hostfxr library under the .NET installation root. The resolution logic is shared via source (src/Common/HostFxrPathResolver.cs) between the NativeWrapper resolver and the Native AOT dn host so the two cannot diverge. It parses the numeric core of prerelease version directory names (e.g. 11.0.0-preview.6.26359.118), which System.Version cannot parse, prefers stable over prerelease of the same core, and orders prerelease segments numerically so preview.10 sorts after preview.6. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d75dbb68-3527-49b4-9af6-d64469b45708
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
|
||
| [TestMethod] | ||
| public void ResolveHostfxrPath_SkipsPrereleaseDirectories() | ||
| public void ResolveHostfxrPath_FindsPrereleaseVersionDirectory() |
There was a problem hiding this comment.
Reviewers: Note this change in behavior. It wasn't clear to me that skipping prerelease versions was intentional or just a bug that AI created a test case to match its behavior. So I'm proposing that prerelease versions be handled.
There was a problem hiding this comment.
Interesting catch - I think it's reasonable to support preview versions of hostfxr, but I think it might be most-compatible if the hostfxr location took the 'allow prerelease' setting into account, via:
- DOTNET_ROLL_FORWARD_TO_PRERELEASE
- global.json's sdk.allowPrerelease
Actually, it might be best to align with the way the runtime locates hostfxr - @elinor-fung do you know how/when the runtime will look for preview hostfxrs?
There was a problem hiding this comment.
I checked the native host's get_latest_fxr implementation: it allows prerelease versions and selects the highest valid SemVer. Neither DOTNET_ROLL_FORWARD_TO_PRERELEASE nor sdk.allowPrerelease affects hostfxr selection. I updated the fallback to use NuGet.Versioning.SemanticVersion and retain that behavior in c38bdd3.
There was a problem hiding this comment.
Awesome, thanks for doing that!
There was a problem hiding this comment.
Pull request overview
This PR addresses a musl-specific crash when the SDK is invoked via dotnet exec dotnet.dll <command> (e.g., dnx), where the host does not publish the HOSTFXR_PATH runtime property. The fix adds a filesystem-based fallback to locate and load the highest-versioned hostfxr from <dotnetRoot>/host/fxr/<version>/, and source-shares the version-selection logic so the NativeWrapper resolver and the Native AOT dn host cannot diverge.
Changes:
- Add a fallback in
Microsoft.DotNet.NativeWrapperto locatehostfxrunder the running .NET root whenHOSTFXR_PATHis missing, instead of crashing on musl. - Extract and source-share hostfxr path/version selection into
src/Common/HostFxrPathResolver.cs, used by both NativeWrapper anddn. - Add/extend unit coverage for prerelease directory parsing and numeric prerelease segment ordering (e.g.
preview.10>preview.6).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| test/dotnet-aot.Tests/HostFxrLocatorTests.cs | Adds unit tests for NativeWrapper’s hostfxr fallback locator, including musl/prerelease scenarios. |
| test/dotnet-aot.Tests/DotnetRootResolverTests.cs | Updates/extends tests to validate prerelease hostfxr directory selection and numeric prerelease ordering. |
| src/Resolvers/Microsoft.DotNet.NativeWrapper/Microsoft.DotNet.NativeWrapper.csproj | Source-includes shared resolver logic and exposes internals to dotnet-aot.Tests for testing. |
| src/Resolvers/Microsoft.DotNet.NativeWrapper/Interop.cs | Uses the fallback locator when HOSTFXR_PATH is absent, preventing musl crashes. |
| src/Resolvers/Microsoft.DotNet.NativeWrapper/HostFxrLocator.cs | Introduces NativeWrapper entry point that delegates to the shared resolver logic. |
| src/Common/HostFxrPathResolver.cs | Implements shared hostfxr path resolution + version/prerelease ordering rules. |
| src/Cli/dn/DotnetRootResolver.cs | Replaces local hostfxr resolution logic with the shared resolver implementation. |
| src/Cli/dn/dn.csproj | Source-includes the shared hostfxr resolver logic into the AOT dn host build. |
MSBuildSdkResolver source-globs the entire NativeWrapper tree, which now references the shared HostFxrPathResolver type in src/Common. That file is outside the glob, so add it to the project's existing src/Common includes to fix the net472 CS0234 compile break. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d75dbb68-3527-49b4-9af6-d64469b45708
Mirror the native host's version parsing and precedence when selecting the latest hostfxr directory. Valid prerelease versions remain eligible, build metadata does not affect precedence, and malformed SemVer directories are ignored. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d75dbb68-3527-49b4-9af6-d64469b45708
Replace the custom hostfxr version parser with NuGet's strict SemanticVersion implementation. Keep the fallback and dependency on modern .NET targets because the net472 resolver does not execute this code path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d75dbb68-3527-49b4-9af6-d64469b45708
Symptoms
On musl-based distros (e.g. Alpine), running the SDK crashes with
HostFxrRuntimePropertyNotSetException. It surfaced throughdnx --help(exit 134), butdnxis only one trigger — not the actual bug. The same crash happens on any path that reaches the NativeWrapper hostfxr resolver on musl withoutHOSTFXR_PATHset. glibc is unaffected.Root cause
The NativeWrapper hostfxr resolver depends on the host publishing the
HOSTFXR_PATHruntime property; when it's absent, it falls back to a baredlopen("hostfxr"). On glibc that bare load succeeds; on musl it fails, so the resolver throws.HOSTFXR_PATHis only published when the SDK runs as a first-class SDK command — not when it's launched as an app viadotnet exec dotnet.dll <command>.dnxdoes exactly that, but this is a fundamental gap in the resolver, not something specific todnx.Fix
When
HOSTFXR_PATHis not set, locate the highest-versionedhostfxrunder<dotnet root>/host/fxr/<version>/and load it explicitly. Version selection parses the numeric core of prerelease directory names (e.g.11.0.0-preview.6..., whichSystem.Versioncan't parse), prefers stable over prerelease of the same major version, and orders prerelease segments numerically (preview.10afterpreview.6).The logic previously existed — with the same latent bug — in both the NativeWrapper resolver and the Native AOT
dnhost. It's now extracted into a single source-sharedsrc/Common/HostFxrPathResolver.cscompiled into both, so they can't diverge.Fixes #55238