Resolve the versioned SDK directory in the Native AOT CLI#55110
Merged
Conversation
In a deployed SDK the muxer loads dotnet-aot.dll from the versioned SDK directory (sdk/<version>), but inside that native bubble the BCL location APIs (AppContext.BaseDirectory, Environment.ProcessPath, Assembly.Location) resolve to the install root, not the SDK directory, which breaks SDK-relative resolution (MSBuild, DotnetTools, forwarders). Add Microsoft.DotNet.Cli.Utils.SdkPaths whose SdkDirectory resolves DOTNET_SDK_ROOT, then the SDK assembly directory, then AppContext.BaseDirectory (resolved once and cached). Add SdkRootLocator in dotnet-aot: prefer the host-provided sdk_dir, else self-locate the dotnet-aot module (GetModuleHandleEx + GetModuleFileName via CsWin32 on Windows, dladdr on Unix). NativeEntryPoint publishes the resolved directory in DOTNET_SDK_ROOT, and the AppBase/DotnetTools resolvers and the --info Base Path now read SdkPaths. Supporting changes: BufferScope/TypeInfo helpers for the self-locate grow loop, localized diagnostics via CliStrings, a dn separated/self-locate harness layout with tests, the SdkRootResolution design note, and the add-dotnet-aot-command and dotnet-aot-compat skills.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a consistent way for the NativeAOT CLI (dotnet-aot) and shared CLI code to resolve the versioned SDK directory (the sdk/<version>/ folder), even when BCL “where am I” APIs point at the install root due to the muxer directly loading the NativeAOT module.
Changes:
- Add
SdkPaths(and supportingBufferScope/TypeInfo) to provide a single canonical SDK-directory resolver shared by managed and AOT paths. - Implement
SdkRootLocatorto resolvesdk_dir(host-provided preferred; otherwise self-locate the loadeddotnet-aotmodule) and publish it viaDOTNET_SDK_ROOT. - Update
--info Base Pathand SDK-relative command resolution to useSdkPaths, plus add unit/integration tests and adnharness mode to emulate the deployed “separated” layout.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/Microsoft.DotNet.Cli.Utils.Tests/Microsoft.DotNet.Cli.Utils.Tests.csproj | Enables unsafe blocks for new buffer pinning tests. |
| test/Microsoft.DotNet.Cli.Utils.Tests/BufferScopeTests.cs | Adds MSTest coverage for BufferScope<T> behavior (including unsafe fixed). |
| test/dotnet-aot.Tests/NativeEntryPointTests.cs | Adds env restore for DOTNET_SDK_ROOT and tests publishing/resolution behavior. |
| test/dotnet-aot.Tests/dotnet-aot.Tests.csproj | Links SdkRootLocator.cs into test build to match product closure. |
| test/dotnet-aot.Tests/AotIntegrationTests.cs | Adds separated-layout --info Base Path integration tests (sdk_dir vs self-locate). |
| src/Cli/Microsoft.DotNet.Cli.Utils/TypeInfo.cs | Adds a helper to determine reference-containing types for ArrayPool clearing. |
| src/Cli/Microsoft.DotNet.Cli.Utils/SdkPaths.cs | Adds canonical SDK directory resolution (env var → assembly dir → AppContext). |
| src/Cli/Microsoft.DotNet.Cli.Utils/NativeMethods.txt | Extends CsWin32 surface for GetModuleFileName and MAX_PATH. |
| src/Cli/Microsoft.DotNet.Cli.Utils/BufferScope.cs | Adds pooled-buffer grow helper for long-path Win32 interop scenarios. |
| src/Cli/dotnet/xlf/CliStrings.zh-Hant.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.zh-Hans.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.tr.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.ru.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.pt-BR.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.pl.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.ko.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.ja.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.it.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.fr.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.es.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.de.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/xlf/CliStrings.cs.xlf | Adds localized entries for new diagnostics. |
| src/Cli/dotnet/ParserOptionActions.cs | Switches --info Base Path to SdkPaths.SdkDirectory. |
| src/Cli/dotnet/CommandFactory/CommandResolution/DotnetToolsCommandResolver.cs | Resolves DotnetTools/ relative to SdkPaths.SdkDirectory. |
| src/Cli/dotnet/CommandFactory/CommandResolution/AppBaseDllCommandResolver.cs | Resolves appbase .dll probing relative to SdkPaths.SdkDirectory. |
| src/Cli/dotnet/CommandFactory/CommandResolution/AppBaseCommandResolver.cs | Resolves appbase commands relative to SdkPaths.SdkDirectory. |
| src/Cli/dotnet/CliStrings.resx | Adds new diagnostics strings for SDK-dir and managed fallback resolution failures. |
| src/Cli/dotnet-aot/SdkRootResolution.md | Adds design note documenting SDK-root resolution strategy. |
| src/Cli/dotnet-aot/SdkRootLocator.cs | Implements host-preferred and self-locate fallback SDK directory resolution. |
| src/Cli/dotnet-aot/NativeEntryPoint.cs | Publishes resolved SDK directory via DOTNET_SDK_ROOT and uses it for fallback. |
| src/Cli/dotnet-aot/dotnet-aot.csproj | Includes SdkRootLocator.cs in the NativeAOT project build. |
| src/Cli/dn/run-dn.ps1 | Adds harness script supporting separated layout and self-locate testing. |
| src/Cli/dn/Program.cs | Adds env-driven SDK-dir override and optional “blank sdk_dir” test hook. |
| .github/skills/dotnet-aot-compat/SKILL.md | Adds an agent skill doc for resolving trim/AOT analyzer warnings. |
| .github/skills/dotnet-aot-compat/references/polyfills.md | Adds skill reference doc for polyfilling trim/AOT attributes on older TFMs. |
| .github/skills/add-dotnet-aot-command/SKILL.md | Adds an agent skill doc for adding features to dotnet-aot and validating them. |
SdkRootLocator: import dladdr under a sentinel library mapped by a DllImportResolver (libSystem on macOS, libdl then libc on Linux glibc/musl) so the self-locate fallback works beyond glibc. ParserOptionActions: gate the --info Base Path so the managed CLI keeps AppContext.BaseDirectory (unchanged output) while the AOT bubble reports the resolved SdkPaths.SdkDirectory. run-dn.ps1: derive the native library name (.dll/.so/.dylib) by OS so the separated and self-locate harness layouts work off Windows.
baronfel
reviewed
Jul 2, 2026
baronfel
reviewed
Jul 2, 2026
baronfel
reviewed
Jul 2, 2026
baronfel
reviewed
Jul 2, 2026
baronfel
left a comment
Member
There was a problem hiding this comment.
Left some feedback, we should talk about
- the usage of the DOTNET_SDK_ROOT env var w/ Daniel to preclear it
- env var vs appcontext
- removing some of the work I did for tool resolution in favor of using the sdk dir resolution you've got in this PR
joeloff
reviewed
Jul 2, 2026
joeloff
reviewed
Jul 2, 2026
Addresses PR review feedback: write to buffer[9] (top of the grown buffer) before the second grow and re-check it afterward, so the copy is verified at both the lower and upper bounds of the used range.
Addresses PR review feedback: smuggle the value via AppContext (like hostfxr) instead of an environment variable, which leaks to child processes. The Native AOT bridge now publishes the resolved versioned SDK directory as the process-local Microsoft.DotNet.Sdk.Root AppContext value, and SdkPaths reads it instead of the DOTNET_SDK_ROOT environment variable. A caller-provided value (e.g. a runtimeconfig configProperties entry) is honored, but the bridge errors out if it is set and does not point to an existing directory. Adds the SdkRootDirectoryDoesNotExist string (+ xlf), updates the docs, and adds tests for the error-out and honor-preset behaviors.
Addresses PR review feedback: on the AOT pathway the resolver is created via ForSdkRoot(sdkRoot) with an explicit DotnetTools path, so the SdkPaths.SdkDirectory fallback in the constructor is the managed-CLI default and is never hit on AOT. The old comment misattributed it to the NAOT case.
baronfel
approved these changes
Jul 6, 2026
JeremyKuhne
added a commit
to JeremyKuhne/msbuild
that referenced
this pull request
Jul 6, 2026
…ontext value When the .NET SDK hosts MSBuild in a trimmed / Native AOT process the BCL 'where am I' APIs point at the muxer / install root, not the versioned SDK directory that contains MSBuild. Following dotnet/sdk#55110, read the SDK-published 'Microsoft.DotNet.Sdk.Root' AppContext value (new DotNetSdkPaths helper) as an early step in BuildEnvironmentHelper resolution, before falling back to process/assembly discovery.
This was referenced Jul 7, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
In a deployed SDK the muxer loads
dotnet-aot.dllfrom the versioned SDK directory (sdk/<version>/), but inside that Native AOT bubble the BCL "where am I" APIs do not point at the SDK directory:AppContext.BaseDirectory,Environment.ProcessPath, andProcess.MainModule.FileNameresolve to the muxer / install root.Assembly.Locationreturns""(and ILC errors withIL3000).So SDK-relative resolution (MSBuild,
DotnetTools/, forwarders) that keys offAppContext.BaseDirectorybreaks in the AOT CLI. This gives the AOT bubble a single, correct notion of the versioned SDK directory.Changes
Microsoft.DotNet.Cli.Utils.SdkPaths–SdkDirectoryresolves theMicrosoft.DotNet.Sdk.RootAppContext value → the SDK assembly directory →AppContext.BaseDirectory(resolved once and cached).SdkRootLocator(dotnet-aot) – prefers the host-providedsdk_dir; otherwise self-locates thedotnet-aotmodule (GetModuleHandleEx+GetModuleFileNamevia CsWin32 on Windows,dladdron Unix).NativeEntryPoint.ExecuteCorepublishes the resolved directory as the process-localMicrosoft.DotNet.Sdk.RootAppContext value so the compiled-in assemblies find it – unlike an environment variable it is not inherited by child processes. A caller-provided value (e.g. aruntimeconfig.jsonconfigPropertiesentry) is honored, but the bridge errors out if it is set and does not point to an existing directory. The AppBase / DotnetTools resolvers and--info'sBase PathreadSdkPaths.BufferScope/TypeInfohelpers (ported from dotnet/msbuild) back the self-locate long-path grow loop.CliStrings(+ xlf).dnharness gains a-Layout Separated/-SelfLocatemode (run-dn.ps1) that mirrors the deployed muxer, with integration + unit tests.src/Cli/dotnet-aot/SdkRootResolution.md, plus theadd-dotnet-aot-commandanddotnet-aot-compatagent skills.Testing
Microsoft.DotNet.Cli.Utilsbuilds clean (net-current + net472);dotnet.csprojand the manageddotnet-aotbuild clean.dotnet-aot.Tests(26) andBufferScopeTests(34) pass.--infoBase Pathresolves to the versioned SDK subfolder in both the passed-sdk_dirand self-locate cases.