Skip to content

Resolve the versioned SDK directory in the Native AOT CLI#55110

Merged
JeremyKuhne merged 5 commits into
dotnet:mainfrom
JeremyKuhne:aot-sdk-root-resolution
Jul 6, 2026
Merged

Resolve the versioned SDK directory in the Native AOT CLI#55110
JeremyKuhne merged 5 commits into
dotnet:mainfrom
JeremyKuhne:aot-sdk-root-resolution

Conversation

@JeremyKuhne

@JeremyKuhne JeremyKuhne commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

In a deployed SDK the muxer loads dotnet-aot.dll from 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, and Process.MainModule.FileName resolve to the muxer / install root.
  • Assembly.Location returns "" (and ILC errors with IL3000).

So SDK-relative resolution (MSBuild, DotnetTools/, forwarders) that keys off AppContext.BaseDirectory breaks in the AOT CLI. This gives the AOT bubble a single, correct notion of the versioned SDK directory.

Changes

  • Microsoft.DotNet.Cli.Utils.SdkPathsSdkDirectory resolves the Microsoft.DotNet.Sdk.Root AppContext value → the SDK assembly directory → AppContext.BaseDirectory (resolved once and cached).
  • SdkRootLocator (dotnet-aot) – prefers the host-provided sdk_dir; otherwise self-locates the dotnet-aot module (GetModuleHandleEx + GetModuleFileName via CsWin32 on Windows, dladdr on Unix).
  • NativeEntryPoint.ExecuteCore publishes the resolved directory as the process-local Microsoft.DotNet.Sdk.Root AppContext 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. a runtimeconfig.json configProperties entry) 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's Base Path read SdkPaths.
  • BufferScope / TypeInfo helpers (ported from dotnet/msbuild) back the self-locate long-path grow loop.
  • Localized the new diagnostics via CliStrings (+ xlf).
  • The dn harness gains a -Layout Separated / -SelfLocate mode (run-dn.ps1) that mirrors the deployed muxer, with integration + unit tests.
  • Design note src/Cli/dotnet-aot/SdkRootResolution.md, plus the add-dotnet-aot-command and dotnet-aot-compat agent skills.

Testing

  • Microsoft.DotNet.Cli.Utils builds clean (net-current + net472); dotnet.csproj and the managed dotnet-aot build clean.
  • dotnet-aot.Tests (26) and BufferScopeTests (34) pass.
  • Validated on the real AOT binary: --info Base Path resolves to the versioned SDK subfolder in both the passed-sdk_dir and self-locate cases.

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.
@JeremyKuhne JeremyKuhne marked this pull request as ready for review July 1, 2026 20:10
Copilot AI review requested due to automatic review settings July 1, 2026 20:10
@JeremyKuhne JeremyKuhne closed this Jul 1, 2026
@JeremyKuhne JeremyKuhne reopened this Jul 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 supporting BufferScope/TypeInfo) to provide a single canonical SDK-directory resolver shared by managed and AOT paths.
  • Implement SdkRootLocator to resolve sdk_dir (host-provided preferred; otherwise self-locate the loaded dotnet-aot module) and publish it via DOTNET_SDK_ROOT.
  • Update --info Base Path and SDK-relative command resolution to use SdkPaths, plus add unit/integration tests and a dn harness 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.

Comment thread src/Cli/dotnet-aot/SdkRootLocator.cs Outdated
Comment thread src/Cli/dn/run-dn.ps1
Comment thread src/Cli/dotnet-aot/SdkRootResolution.md
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.
Comment thread src/Cli/dotnet-aot/NativeEntryPoint.cs
Comment thread src/Cli/dn/run-dn.ps1

@baronfel baronfel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/Cli/dotnet-aot/SdkRootLocator.cs
Comment thread test/Microsoft.DotNet.Cli.Utils.Tests/BufferScopeTests.cs
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.
@JeremyKuhne JeremyKuhne merged commit 045b55c into dotnet:main Jul 6, 2026
32 checks passed
@JeremyKuhne JeremyKuhne deleted the aot-sdk-root-resolution branch July 6, 2026 18:47
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.
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants