Skip to content

dotnet format forwarding is broken with aot #55919

Description

@tmds
$ dotnet format --help
The application to execute does not exist: '/tmp/dotnet-ms/DotnetTools/dotnet-format/dotnet-format.dll'

This probably regressed when enabling native aot.

FormatForwardingApp uses AppContext.BaseDirectory to locate dotnet-format.dll:

private static string GetForwardApplicationPath()
=> Path.Combine(AppContext.BaseDirectory, "DotnetTools/dotnet-format/dotnet-format.dll");
private static string GetDepsFilePath()
=> Path.Combine(AppContext.BaseDirectory, "DotnetTools/dotnet-format/dotnet-format.deps.json");
private static string GetRuntimeConfigPath()
=> Path.Combine(AppContext.BaseDirectory, "DotnetTools/dotnet-format/dotnet-format.runtimeconfig.json");

The fix should be to use SdkPaths.SdkDirectory instead of AppContext.BaseDirectory as described in:

## Resolving the versioned SDK root (do NOT use BCL path APIs)
The muxer loads `dotnet-aot.dll` directly from the versioned SDK directory (e.g. `.../sdk/11.0.100/`),
but inside that process the BCL "where am I" APIs do **not** point there:
- `AppContext.BaseDirectory`, `Environment.ProcessPath`, `Process.GetCurrentProcess().MainModule` -> the
**muxer / install root**.
- `Assembly.Location` -> the **empty string** (ILC hard-errors with `IL3000`).
So deriving an SDK-relative path (`MSBuild.dll`, `Sdks/`, `DotnetTools/`, targets) from
`AppContext.BaseDirectory` or a dll path is **wrong** in the AOT bubble. Instead:
- **In-repo:** read `SdkPaths.SdkDirectory` (in `Microsoft.DotNet.Cli.CoreUtils`), which resolves the
`Microsoft.DotNet.Sdk.Root` AppContext value -> SDK assembly directory -> `AppContext.BaseDirectory`
(once, cached).
- `NativeEntryPoint.ExecuteCore` resolves the SDK directory once (host `sdk_dir`, else self-locating the
`dotnet-aot` module via `SdkRootLocator`) and **publishes it as the `Microsoft.DotNet.Sdk.Root`
AppContext value** for the compiled-in assemblies.
- **Out-of-repo code** (MSBuild tasks, NuGet, runtime - no `Cli.Utils` reference) replicates the contract
inline: read the `Microsoft.DotNet.Sdk.Root` AppContext value first, else the existing BCL logic.
```csharp
string sdkDirectory =
AppContext.GetData("Microsoft.DotNet.Sdk.Root") is string sdkRoot && sdkRoot.Length > 0
? sdkRoot
: /* existing logic, e.g. AppContext.BaseDirectory */;
```
When bringing a command into AOT, switch any `AppContext.BaseDirectory` / `Assembly.Location` used as
"the SDK directory" to the above contract. Search current call sites rather than maintaining a static
list in this skill; update `SdkRootResolution.md` when the contract or ownership changes.

cc @baronfel

Metadata

Metadata

Assignees

No one assigned

    Labels

    untriagedRequest triage from a team member

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions