diff --git a/.github/skills/add-dotnet-aot-command/SKILL.md b/.github/skills/add-dotnet-aot-command/SKILL.md index e6779a02e910..52ad92183708 100644 --- a/.github/skills/add-dotnet-aot-command/SKILL.md +++ b/.github/skills/add-dotnet-aot-command/SKILL.md @@ -1,56 +1,65 @@ --- name: add-dotnet-aot-command description: > - Include a dotnet CLI command or feature in the Native AOT CLI (src/Cli/dotnet-aot) - and prove it works. USE FOR: enabling a command/option in dotnet-aot, adding source - files to AotSourceFiles.props, gating AOT-incompatible code with #if CLI_AOT, building - and NativeAOT-publishing dotnet-aot, writing/updating the AOT parser + integration - tests, running the local dn harness in AOT mode and comparing it to the managed CLI. - DO NOT USE FOR: resolving IL trim/AOT analyzer warnings (use dotnet-aot-compat), - running dotnet.Tests incrementally (use incremental-test), or pure managed CLI work. + Add, enable, or review a dotnet CLI command or feature in the Native AOT CLI + (src/Cli/dotnet-aot) and prove its compatibility. USE FOR: migrating a command or + option to AOT, reviewing an AOT migration PR, defining conservative eligibility and + managed fallback, changing AotSourceFiles.props or AotDependencies.props, validating + AOT/managed parity, NativeAOT-publishing tests, checking binary-size impact, or using + the dn harness and separated SDK layout. DO NOT USE FOR: resolving IL trim/AOT + analyzer warnings alone (use dotnet-aot-compat), running dotnet.Tests incrementally + (use incremental-test), or pure managed CLI work. license: MIT --- # add-dotnet-aot-command -How to include a `dotnet` command or feature in the Native AOT CLI (`src/Cli/dotnet-aot`), keep the -AOT surface small, validate it, and run it through the local `dn` harness. +Use this workflow to author or review a Native AOT CLI migration. The goal is not merely to make a +command compile in the native library. The goal is to preserve the managed CLI contract, fall back +before the AOT path commits unsupported work, and prove the actual native artifact and layout. -> Paths use `$(SdkTargetFramework)` = `net11.0` and `win-x64`; adjust for other TFMs/RIDs. +Before changing or reviewing code, verify the current behavior in the owning source and tests. Then read: -## When to use +- `src/Cli/dotnet-aot/DESIGN.md` for the current host and fallback architecture. +- `src/Cli/dotnet-aot/SdkRootResolution.md` when code resolves SDK-relative content. -- Make `dotnet ` work in dotnet-aot, or enable an option/section in the AOT path. -- Add source files to `AotSourceFiles.props`. -- Run `dn` in AOT mode, or compare AOT vs managed output. -- Diagnose why `dotnet test` fails for `dotnet-aot.Tests`. +Commands below assume PowerShell from the repository root. Adjust the RID for the host. Do not hard-code +the target framework; the projects and harness discover `$(SdkTargetFramework)`. -**Not for:** IL trim/AOT warnings (use **dotnet-aot-compat**); managed `dotnet.Tests` runs (use -**incremental-test**); managed-only changes with no AOT impact. +## Non-negotiable contracts + +1. **One CLI, two execution bubbles.** Reuse shared definitions, implementations, helpers, and + resources. Do not create an AOT-only parser or command copy when the managed owner can be linked. +2. **Eligibility is an allowlist.** Unknown options, ambiguous operands, dynamic parser extensions, + and unsupported combinations fall back to managed execution. +3. **Fallback has a commit point.** Complete every fallback predicate before command output, command + telemetry ownership, file/cache mutation, restore, build, or process launch. Split probing from + execution or buffer output if necessary. +4. **Precedence is behavior.** Preserve ordering among built-ins, external/tool commands, projects, + explicit files, positional files, and shorthand forms. +5. **The native artifact is the test subject.** Managed builds and in-process tests do not replace a + clean Native AOT publish, native test run, assembled `dn` run, or platform run. +6. **Size is part of the change.** Report the native-library delta and largest rooted dependencies for + meaningful closure changes. ## How the AOT CLI is assembled -`dotnet-aot` does **not** reference `dotnet.csproj`. It is a shared native library (`NativeLib=Shared`, -`PublishAot=true`, `IsAotCompatible=true`) that **cherry-picks source files** from `src/Cli/dotnet/` via -`src/Cli/dotnet-aot/AotSourceFiles.props`. That `.props` is imported by **both** `dotnet-aot.csproj` and -`test/dotnet-aot.Tests/dotnet-aot.Tests.csproj`, so the tests compile the exact same command surface as -the shipping binary. +`dotnet-aot` is a Native AOT shared library (`NativeLib=Shared`) loaded by the host. It exports +`dotnet_execute`; `NativeEntryPoint` either invokes shared CLI code in-process or hosts the existing +managed `dotnet.dll` through hostfxr. `DOTNET_CLI_ENABLEAOT=false` is the explicit opt-out. Unsupported +command shapes use `CommandNotAvailableInAotException` or an earlier entry-point decision to request +managed fallback. -Compile constants (both projects): `CLI_AOT` gates AOT-only vs managed-only code in shared files (`#if -CLI_AOT` / `#if !CLI_AOT`); `DotnetCsproj` is also defined and can pull in extra closure (see Gotchas). +The closure has two owners: -Dispatch: `dotnet-aot/NativeEntryPoint.cs` (`dotnet_execute`) is P/Invoked by the `dn` host (`src/Cli/dn`): +- `AotSourceFiles.props`: linked source files and generated/embedded resources. It is imported by the + product and test projects so they compile the same command surface. +- `AotDependencies.props`: package/project references, framework references, runtime feature switches, + and dependency-owned target imports shared by product and tests. -- `DOTNET_CLI_ENABLEAOT=true`: parse in-process, run `FirstRunExperience.Setup`, and if - `parseResult.CanBeInvoked()` run the command **in-process**. A command still needing the managed CLI - throws `CommandNotAvailableInAotException` to fall through. -- `run` has an intentionally narrow AOT path: launch-only reuse through explicit `--file`, - positional discovery, or implicit shorthand. `--no-build` can use prior synthetic output; - build-enabled and no-build runs can use unchanged validated replayed/MSBuild `RunProperties`. - Project profiles decorate cached launches, while no-build Executable profiles can bypass cache. - Positional discovery defers when the current directory contains a project; shorthand first - preserves external resolution. Stale, ambiguous, or build-required shapes defer. -- Otherwise / on fall-through: host `{sdkDir}/dotnet.dll` via hostfxr (same source, JIT-compiled). +Keep `dotnet-aot.csproj` focused on native compiler/linker and project configuration. `CLI_AOT` gates +the smallest incompatible portions of shared files; `#if !CLI_AOT` must leave the managed path intact. +`DotnetCsproj` is also defined and can expose an unexpected transitive source closure. Types already available (do **not** re-add their sources): `Microsoft.DotNet.Cli.Utils`, `Microsoft.DotNet.Configurer`, `Microsoft.DotNet.Cli.Definitions`, `Microsoft.DotNet.ProjectTools`, @@ -88,92 +97,266 @@ So deriving an SDK-relative path (`MSBuild.dll`, `Sdks/`, `DotnetTools/`, target ``` When bringing a command into AOT, switch any `AppContext.BaseDirectory` / `Assembly.Location` used as -"the SDK directory" to the above. Not-yet-routed sites: `FormatForwardingApp`, `FsiForwardingApp`, -`VSTestForwardingApp`, `ProjectFactory` / `ProjectToolsCommandResolver`, `VBCSCompilerServer`, -`CSharpCompilerCommand`, `MSBuildForwardingAppWithoutLogging`, `DotnetFiles.SdkRootFolder`. Details: -`src/Cli/dotnet-aot/SdkRootResolution.md`. - -## Procedure - -1. **Find the call site** in shared source (a command parser, `Parser.cs`, `ParserOptionActions.cs`) and - remove/narrow its `#if !CLI_AOT` guard. -2. **Add the source closure** to `AotSourceFiles.props` in a labeled per-command `` (follow the - file's header rules; reuse the "Common AOT scaffolding" group). Only add files under `src/Cli/dotnet/` - that aren't already in a referenced assembly or the `.props`. Windows/COM files go in a - `Condition="'$(TargetOS)' == 'windows'"` group. -3. **Add package references** the command needs (in `AotSourceFiles.props` if both binary and tests need - them; confirm the runtime asset flows - see the `Microsoft.Build` gotcha). -4. **Build managed dotnet-aot first** - fast, and surfaces `CS0246`/`CS0103` closure gaps without ILC. Let - the compiler drive the closure: `.\.dotnet\dotnet build src\Cli\dotnet-aot\dotnet-aot.csproj -c Debug` -5. **Publish as NativeAOT** to surface IL warnings (resolve per **dotnet-aot-compat**): - `.\.dotnet\dotnet publish src\Cli\dotnet-aot\dotnet-aot.csproj -r win-x64 -c Debug`. ILC only analyzes - the reachable closure - don't preemptively suppress warnings that never appear. -6. **Keep the AOT surface small.** Gate heavy subsystems (workload installer, NuGet engine, MSI/COM IPC) - under `#if CLI_AOT` and build only the read-only path you need (mirror `WorkloadInstallDetector`, which - builds the record repository directly with no installer). Gate installer-coupled interfaces under - `#if !CLI_AOT`, with an AOT-only construction path under `#if CLI_AOT`. -7. **Confirm the managed CLI still builds** (the `#else` branches must stay intact): - `.\.dotnet\dotnet build src\Cli\dotnet\dotnet.csproj -c Debug` - -## Gotchas - -- **MSBuild XML comments can't contain `--`** (`MSB4024`). Reword; never end a comment with `-`. -- **The `Microsoft.Build` runtime asset doesn't flow transitively** - `Cli.Utils` references it - `ExcludeAssets="runtime" PrivateAssets="all"`, so dotnet-aot has no `Microsoft.Build.dll` at ILC time. - If you reach a `Microsoft.Build.*` API, add `` to the AOT - closure. -- **`DotnetCsproj` is defined for dotnet-aot**, so adding a shared file can pull in extra `#if DotnetCsproj` - closure. Inline the small helper you need under `#if CLI_AOT` instead. -- **Don't pass `-noRestore` with `-getItem`** - the response file already appends it (`MSB1001`). -- **`dotnet test` does NOT work for `dotnet-aot.Tests`** (Microsoft.Testing.Platform, not VSTest). Run the - built `.exe` directly (see below). -- **Existing tests may assert AOT _exclusions_** - enabling a feature can mean **inverting** a - `DoesNotContain` assertion. Search the test project first. -- **PowerShell git/gh quoting:** single-quote messages/titles containing backticks or `$(...)`. - -## Validate & test - -Tests live in `test/dotnet-aot.Tests`: `AotParserTests` (in-process parser/command behavior) and -`AotIntegrationTests` (end-to-end against the real `dn`; skips if `dn` isn't in the layout). - -Run the suite as a native AOT binary (the real ILC / COM / P-Invoke check) with -`test/dotnet-aot.Tests/run-aot-tests.ps1`. To iterate on one test, build the test project and run -`dotnet-aot.Tests.exe --filter "FullyQualifiedName~"` (MTP runs as an executable; `dotnet test` -doesn't work). `IL3053` rollups for test-only assemblies (FluentAssertions, TestPlatform.ObjectModel, -DataContractSerialization) are not product warnings. - -Assert real **values**, not just headers, so a trim regression that blanks a line is caught - e.g. -`stdout.Should().MatchRegex(@"MSBuild version:\s+\S");`. - -## Run the local `dn` harness in AOT mode - -Use `src/Cli/dn/run-dn.ps1` (don't inline the steps). It publishes `dotnet-aot` (NativeAOT) and `dn`, -builds the managed `dotnet` CLI, assembles them into the `dn` publish dir, points `DOTNET_ROOT` at the -repo's `.dotnet`, and runs `dn ` with `DOTNET_CLI_ENABLEAOT` toggled. **Tell the user these -steps** so they can reproduce it. +"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. + +## Author workflow + +### 1. Measure the managed contract + +Run the managed CLI before editing and capture a behavior matrix: + +- Successful, help, malformed, missing, and ambiguous forms. +- Every option/operand shape intended for AOT, one unknown option, and nearby managed-only forms. +- Exit code, stdout, stderr, created/changed/deleted files, cache state, restore/build invocations, and + child processes. +- External-command, project, explicit-file, positional-file, and shorthand collisions where relevant. +- First-run, telemetry opt-out, cancellation, SDK selection, and existing-artifact state where relevant. + +Measure behavior; do not infer it from option names or a reading of the implementation. + +### 2. Define eligibility before implementation + +Write a table in the PR notes or tests for every supported and unsupported shape. Name the exact signal +available before output or mutation: + +| Shape | Result | Pre-commit signal | Required evidence | +| --- | --- | --- | --- | +| Supported | Handle in AOT | Complete allowlisted parse plus supported input/service state | Native success and managed parity | +| Known unsupported | Fall back | Explicit option/input/service predicate | Entry-point fallback assertion and real managed result | +| Unknown or ambiguous | Fall back | Not in the allowlist | Negative parser/entry-point test | +| Failure after commit | Return AOT result/error | Eligibility already established | Native failure/output/side-effect parity | + +If the signal requires output-producing or mutating work, introduce a probe/execute split or buffer the +output until AOT commits. Do not use a broad catch to replay an invocation after work begins. + +### 3. Reuse the managed owner + +Trace the definition to the code that directly computes or mutates behavior. Prefer, in order: + +1. Existing referenced assembly/API. +2. Existing shared source and resources. +3. A pure helper extracted without changing its contract. +4. A narrow `CLI_AOT` guard around the incompatible member or action. +5. Explicit managed fallback for dynamic/reflection-heavy or otherwise unsupported behavior. + +Keep fallback local to the owning command action. Do not remove definitions from the command tree or +teach `NativeEntryPoint` command-specific semantics when `CommandNotAvailableInAotException` suffices. + +### 4. Extend the closure in the owning props file + +- Add source and resources to `AotSourceFiles.props` in the existing owner group or a labeled + command-specific group. Reuse common scaffolding; duplicate `Compile`/`EmbeddedResource` items fail. +- Add package/project/framework references, runtime feature switches, and dependency target imports to + `AotDependencies.props`. Normal package versions remain centrally managed. +- Put Windows-only source/dependencies under `Condition="'$(TargetOS)' == 'windows'"`. +- Keep native compiler/linker configuration in `dotnet-aot.csproj`. + +Inspect each dependency for trim/AOT warnings, reflection/dynamic code, serialization contracts, +resources/localization, build assets, static initialization, process-global state, native libraries, +and binary-size contribution. + +### 5. Preserve both preprocessor views + +Condition the smallest incompatible block. Compare the managed and AOT views for lost comments, +changed accessibility, relaxed nullable/analyzer settings, altered resources, and unrelated cleanup. +Run the existing managed tests for every shared implementation changed. + +### 6. Add tests that identify the selected path + +Add the smallest applicable layers: + +1. Parser/definition tests for accepted and rejected syntax. +2. Entry-point tests that observe handled versus managed fallback before a real host transition. +3. Shared command tests for host-independent semantics. +4. Native-published tests for the actual AOT closure. +5. `dn` integration for native loading, hostfxr fallback, output, and process state. +6. Separated-layout tests for SDK-root consumers. +7. Managed/AOT parity tests for output, exit code, side effects, and work performed. + +Make the fixture capable of reaching the asserted branch. Mutate an eligibility predicate or the input +shape and confirm the focused test fails for the intended reason. A green skipped native test is not +native evidence. + +### 7. Update contracts and report evidence + +Update `DESIGN.md`, `SdkRootResolution.md`, resources/help, tests, and this skill when the change alters +a documented invariant. Prefer decision rules over static command inventories. + +The completion report must name supported/unsupported shapes, commit point, shared files, new dependency +roots, warning treatment, exact validation executed, skipped tests, parity results, platform coverage, +and binary-size delta. + +## Closure and implementation gotchas + +- **MSBuild XML comments cannot contain `--`** (`MSB4024`) or end with `-`. +- **`Microsoft.Build` does not flow transitively from `Cli.Utils`** because its reference excludes the + runtime asset. Keep required direct MSBuild packages and target imports in `AotDependencies.props`. +- **`DotnetCsproj` is defined for dotnet-aot.** A newly linked file can expose extra conditional code. + Inspect that closure and narrow the owning conditional; do not copy a helper merely to avoid tracing it. +- **Do not pass `-noRestore` with `-getItem`.** The response file already appends it (`MSB1001`). +- **`dotnet-aot.Tests` uses Microsoft.Testing.Platform.** `dotnet test` is not the runner; invoke the + built executable for a focused managed iteration or use `run-aot-tests.ps1` for native execution. +- **Existing tests may assert exclusions.** Search before enabling a command; an intentional + `DoesNotContain` can need a carefully justified inversion. +- **A Roslyn pragma is not native-publish evidence.** ILC can report the same trim/AOT warning during + publish. Use **dotnet-aot-compat**, keep suppression scope narrow, and reference an existing tracking + issue for temporary dependency warnings. Ask the user before creating a new issue. +- **A shared Native AOT library is not a standalone AOT app.** On each affected OS, check interaction + with native libraries and process-global state already loaded by the host. +- **Flat layouts can hide SDK-root defects.** Use `-Layout Separated`; add `-SelfLocate` to exercise the + native-module fallback. + +## Reviewer workflow + +For a review, also follow the repository **code-review** skill's findings-first output rules. Use this +rubric to find AOT-specific defects. + +### 1. Reconstruct both execution paths + +Trace host arguments and SDK root, parser/action selection, every eligibility predicate, first observable +effect, AOT execution/telemetry ownership, and managed host transition. If any fallback predicate follows +stdout/stderr, mutation, restore/build, cache work, or process launch, flag the commit-point violation. + +### 2. Compare with the managed owner + +Look for copied definitions, bodies, validators, resources, path logic, diagnostics, and exception +mapping. Require a concrete reason not to share. Review both sides of every changed `CLI_AOT` block for +lost managed behavior, comments, tests, or analyzer coverage. + +### 3. Attack the allowlist + +Try an unknown option, a managed-only option, wrong operand counts, ambiguous discovery, dynamic help, +existing artifacts, and command/project/file/external collisions. Require clean managed fallback before +AOT output or mutation, and require tests to observe which path ran. + +### 4. Audit the closure and size + +Verify source/resources and dependencies are in their respective props files. Check package build assets, +feature switches, reflection, serializers, direct P/Invoke, platform-native libraries, static state, and +warning suppressions. Request clean-build and native-publish evidence. For size growth, inspect rooted +dependency paths rather than only changed source lines. + +### 5. Validate the tests + +Confirm native artifacts existed and ran, skipped counts are reported, process streams cannot deadlock, +paths are platform-neutral, process state is restored, fixtures reach the branch, and assertions inspect +real values rather than headers. Check pre-existing managed tests when code moved behind conditionals. + +### 6. Challenge claims with the validation matrix + +Compilation does not prove ILC compatibility. Native publish does not prove native tests ran. Flat `dn` +does not prove SDK-root behavior. `-Mode Compare` compares captured output but does not by itself prove +exit-code or side-effect parity. One host RID does not prove other platforms. Require evidence matching +each claim and label unresolved questions as questions rather than defects. + +Common blockers: + +- Denylist eligibility or unknown options handled in AOT. +- Fallback after output, telemetry ownership, or non-idempotent work. +- AOT-only copies of managed behavior or resources. +- Package/project references in `AotSourceFiles.props`. +- Incremental-only build evidence after closure changes. +- Native tests skipped because `dn` or the published executable was absent. +- Flat-layout SDK-root claims. +- Unexplained warning suppression, feature switch, or binary-size increase. +- Open-PR behavior described as current `main` behavior. + +## Validation ladder + +Run the cheapest discriminating check after each edit, then broaden. Use the **targeted-test** skill when +the request explicitly asks to select or run narrow SDK tests. + +### 1. Focused parser/entry-point test + +Use the targeted-test runner so it builds the project, resolves the evaluated `TargetPath`, and invokes +the Microsoft.Testing.Platform application without assuming its generated executable is on `PATH`: + +```powershell +.\.dotnet\dotnet.exe .github\skills\targeted-test\scripts\RunTargetedTests.cs -- ` + --project test\dotnet-aot.Tests\dotnet-aot.Tests.csproj ` + --filter "FullyQualifiedName~" +``` + +Record whether the test asserts AOT handling, fallback, or semantics. Do not call this a native run. + +### 2. Managed and clean closure builds + +```powershell +.\.dotnet\dotnet.exe build src\Cli\dotnet\dotnet.csproj -c Debug +.\.dotnet\dotnet.exe clean src\Cli\dotnet-aot\dotnet-aot.csproj -c Debug +.\.dotnet\dotnet.exe clean test\dotnet-aot.Tests\dotnet-aot.Tests.csproj -c Debug +.\.dotnet\dotnet.exe build src\Cli\dotnet-aot\dotnet-aot.csproj -c Debug +.\.dotnet\dotnet.exe build test\dotnet-aot.Tests\dotnet-aot.Tests.csproj -c Debug +``` + +The clean builds are mandatory after source/resource/dependency closure changes; stale intermediates have +previously hidden missing includes. + +### 3. Product Native AOT publish ```powershell -src\Cli\dn\run-dn.ps1 -Command "--info" # through the AOT binary -src\Cli\dn\run-dn.ps1 -Command "--info" -Mode Compare # AOT vs managed diff (parity) -src\Cli\dn\run-dn.ps1 -Command "workload --info" -NoBuild # reuse the assembled layout -src\Cli\dn\run-dn.ps1 -Command "run --file C:\tmp\app.cs --no-build --no-launch-profile" -NoBuild -src\Cli\dn\run-dn.ps1 -Command "run C:\tmp\app.cs --no-build --no-launch-profile" -NoBuild -src\Cli\dn\run-dn.ps1 -Command "C:\tmp\app.cs --no-build --no-launch-profile" -NoBuild -src\Cli\dn\run-dn.ps1 -Command "run --file C:\tmp\app.cs --no-build --launch-profile MyProfile" -NoBuild +.\.dotnet\dotnet.exe publish src\Cli\dotnet-aot\dotnet-aot.csproj -r win-x64 -c Debug ``` -- `DOTNET_CLI_ENABLEAOT=true` runs in-process in the platform-specific `dotnet-aot` native library; - `DOTNET_CLI_ENABLEAOT=false` makes `dn` host the copied `dotnet.dll`. `-Mode Compare` diffs the - captured output (`artifacts/log/dn-aot.txt`, `dn-managed.txt`). -- `dn` finds the .NET root from `DOTNET_ROOT` (set to `.dotnet`); the publish dir isn't a full SDK. -- The AOT path runs `FirstRunExperience.Setup` first; if it can't complete, it defers to the managed CLI. -- For focused integration tests against an assembled harness, set - `DOTNET_AOT_TEST_DN_PATH` to the full `dn` executable path. -- `Commit` and workloads reflect the `DOTNET_ROOT` layout - both paths read the same root, so they agree. +This is the ILC/linker check. Investigate product warnings separately from known test-only rollups. Run +on each affected OS rather than cross-publishing and assuming native execution. + +### 4. Native-published test suite + +```powershell +.\test\dotnet-aot.Tests\run-aot-tests.ps1 -Trx +``` + +The script publishes the MTP test application with Native AOT, verifies the executable exists, supplies +the SDK/host paths, runs it, and can emit a TRX. Report executed, passed, failed, and skipped counts. + +### 5. Real `dn` integration and parity + +Use `run-dn.ps1`; do not duplicate its publish/copy logic: + +```powershell +.\src\Cli\dn\run-dn.ps1 -Command "" -Mode Compare +.\src\Cli\dn\run-dn.ps1 -Command "" -Mode Aot -NoBuild +``` + +The script publishes `dotnet-aot` and `dn`, builds/copies managed `dotnet.dll`, sets `DOTNET_ROOT`, and +toggles `DOTNET_CLI_ENABLEAOT`. `Compare` writes `artifacts/log/dn-aot.txt` and `dn-managed.txt` and diffs +captured output. Separately compare exit code, files/cache, restore/build count, and child processes when +the command can affect them. Set `DOTNET_AOT_TEST_DN_PATH` for focused integration tests against the +assembled harness. + +### 6. Separated SDK-root layout + +```powershell +.\src\Cli\dn\run-dn.ps1 -Command "" -Mode Compare -Layout Separated +.\src\Cli\dn\run-dn.ps1 -Command "" -Mode Compare -Layout Separated -SelfLocate +``` + +The first exercises the host-provided versioned SDK directory. The second makes `dn` pass an empty +`sdk_dir` so `dotnet-aot` must self-locate its native module. Use this for every new SDK-relative path. + +### 7. Size and platform evidence + +Record the before/after native library byte size and inspect the dependency graph for meaningful changes. +Use the AOT size-analysis workflow when available. Run native publish, native tests, and relevant `dn` +scenarios on each affected supported OS/architecture; cross-OS publish is not an execution result. + +## Completion report + +Report: -The VS Code tasks `publish-and-copy-dn-aot` + `copy-all-deps` do the same build/assemble. +- Supported and deliberately unsupported shapes. +- Eligibility/fallback signal and first commit point. +- Shared source/resources and dependency roots changed. +- Focused, managed, clean-build, native-publish, native-test, `dn`, separated-layout, and platform checks + actually run, including skipped counts. +- Managed/AOT differences in output, exit code, side effects, and work performed. +- Native binary size delta, major contributors, warning/feature-switch treatment, and remaining risk. ## Related skills -- **dotnet-aot-compat** - resolve the IL trim/AOT warnings this surfaces. -- **incremental-test** - run the managed `dotnet.Tests` against the redist SDK layout. +- **code-review** - findings-first review presentation for PR or local changes. +- **dotnet-aot-compat** - resolve IL trim/AOT warnings surfaced by native publish. +- **incremental-test** - run managed `dotnet.Tests` against the redist SDK layout. +- **targeted-test** - select and run the smallest relevant SDK tests with retained diagnostics. diff --git a/src/Cli/dn/run-dn.ps1 b/src/Cli/dn/run-dn.ps1 index 8d15069397a6..27a8471c7575 100644 --- a/src/Cli/dn/run-dn.ps1 +++ b/src/Cli/dn/run-dn.ps1 @@ -135,67 +135,96 @@ $dnExe = Join-Path $dnPublishDir $dnExeName if (-not (Test-Path $dnExe)) { throw "dn host not found at '$dnExe'. Run without -NoBuild first." } $argList = $Command.Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries) -$env:DOTNET_ROOT = (Join-Path $repoRoot ".dotnet") -$env:DOTNET_CLI_TELEMETRY_OPTOUT = "1" - -# Emulate the deployed non-flat layout: tell dn to load dotnet-aot from (and pass as sdk_dir) the -# versioned SDK subfolder, optionally forcing the self-locate fallback by blanking sdk_dir. -if ($Layout -eq "Separated") { - $env:DOTNET_AOT_SDK_DIR = (Join-Path $dnPublishDir "sdk/11.0.100") - Write-Host "Layout: Separated (dotnet-aot in $($env:DOTNET_AOT_SDK_DIR))" -ForegroundColor Cyan -} -else { - Remove-Item Env:\DOTNET_AOT_SDK_DIR -ErrorAction SilentlyContinue -} -if ($SelfLocate) { - $env:DOTNET_AOT_BLANK_SDKDIR = "1" - Write-Host "Self-locate: enabled (dn passes empty sdk_dir; dotnet-aot self-locates)" -ForegroundColor Cyan -} -else { - Remove-Item Env:\DOTNET_AOT_BLANK_SDKDIR -ErrorAction SilentlyContinue +$environmentVariableNames = @( + "DOTNET_ROOT", + "DOTNET_CLI_TELEMETRY_OPTOUT", + "DOTNET_AOT_SDK_DIR", + "DOTNET_AOT_BLANK_SDKDIR", + "DOTNET_CLI_ENABLEAOT" +) +$previousEnvironment = @{} +foreach ($variableName in $environmentVariableNames) { + $environmentVariable = Get-Item "Env:\$variableName" -ErrorAction SilentlyContinue + $previousEnvironment[$variableName] = [pscustomobject]@{ + Exists = $null -ne $environmentVariable + Value = $environmentVariable.Value + } } -function Invoke-Dn([bool]$enableAot) { - if ($enableAot) { - $env:DOTNET_CLI_ENABLEAOT = "true" +try { + $env:DOTNET_ROOT = (Join-Path $repoRoot ".dotnet") + $env:DOTNET_CLI_TELEMETRY_OPTOUT = "1" + + # Emulate the deployed non-flat layout: tell dn to load dotnet-aot from (and pass as sdk_dir) the + # versioned SDK subfolder, optionally forcing the self-locate fallback by blanking sdk_dir. + if ($Layout -eq "Separated") { + $env:DOTNET_AOT_SDK_DIR = (Join-Path $dnPublishDir "sdk/11.0.100") + Write-Host "Layout: Separated (dotnet-aot in $($env:DOTNET_AOT_SDK_DIR))" -ForegroundColor Cyan } else { - $env:DOTNET_CLI_ENABLEAOT = "false" + Remove-Item Env:\DOTNET_AOT_SDK_DIR -ErrorAction SilentlyContinue } - & $dnExe @argList 2>&1 -} - -switch ($Mode) { - "Aot" { - Write-Host "===== AOT (DOTNET_CLI_ENABLEAOT=true) =====" -ForegroundColor Green - Invoke-Dn $true + if ($SelfLocate) { + $env:DOTNET_AOT_BLANK_SDKDIR = "1" + Write-Host "Self-locate: enabled (dn passes empty sdk_dir; dotnet-aot self-locates)" -ForegroundColor Cyan } - "Managed" { - Write-Host "===== Managed (DOTNET_CLI_ENABLEAOT=false) =====" -ForegroundColor Green - Invoke-Dn $false + else { + Remove-Item Env:\DOTNET_AOT_BLANK_SDKDIR -ErrorAction SilentlyContinue } - "Compare" { - $logDir = Join-Path $repoRoot "artifacts/log" - New-Item -ItemType Directory -Force -Path $logDir | Out-Null - - $aotOut = Invoke-Dn $true - $managedOut = Invoke-Dn $false - $aotOut | Set-Content (Join-Path $logDir "dn-aot.txt") - $managedOut | Set-Content (Join-Path $logDir "dn-managed.txt") - - Write-Host "===== AOT (DOTNET_CLI_ENABLEAOT=true) =====" -ForegroundColor Green - $aotOut | Write-Output - Write-Host "===== Managed (fallback) =====" -ForegroundColor Green - $managedOut | Write-Output - - $diff = Compare-Object $aotOut $managedOut - Write-Host "" - if ($diff) { - Write-Host "DIFFERENCES (AOT vs managed):" -ForegroundColor Yellow - $diff | Format-Table -AutoSize + + function Invoke-Dn([bool]$enableAot) { + if ($enableAot) { + $env:DOTNET_CLI_ENABLEAOT = "true" } else { - Write-Host "IDENTICAL: AOT and managed output match line-for-line." -ForegroundColor Green + $env:DOTNET_CLI_ENABLEAOT = "false" + } + & $dnExe @argList 2>&1 + } + + switch ($Mode) { + "Aot" { + Write-Host "===== AOT (DOTNET_CLI_ENABLEAOT=true) =====" -ForegroundColor Green + Invoke-Dn $true + } + "Managed" { + Write-Host "===== Managed (DOTNET_CLI_ENABLEAOT=false) =====" -ForegroundColor Green + Invoke-Dn $false + } + "Compare" { + $logDir = Join-Path $repoRoot "artifacts/log" + New-Item -ItemType Directory -Force -Path $logDir | Out-Null + + $aotOut = Invoke-Dn $true + $managedOut = Invoke-Dn $false + $aotOut | Set-Content (Join-Path $logDir "dn-aot.txt") + $managedOut | Set-Content (Join-Path $logDir "dn-managed.txt") + + Write-Host "===== AOT (DOTNET_CLI_ENABLEAOT=true) =====" -ForegroundColor Green + $aotOut | Write-Output + Write-Host "===== Managed (fallback) =====" -ForegroundColor Green + $managedOut | Write-Output + + $diff = Compare-Object $aotOut $managedOut + Write-Host "" + if ($diff) { + Write-Host "DIFFERENCES (AOT vs managed):" -ForegroundColor Yellow + $diff | Format-Table -AutoSize + } + else { + Write-Host "IDENTICAL: AOT and managed output match line-for-line." -ForegroundColor Green + } + } + } +} +finally { + foreach ($variableName in $environmentVariableNames) { + $previousValue = $previousEnvironment[$variableName] + if ($previousValue.Exists) { + Set-Item "Env:\$variableName" -Value $previousValue.Value + } + elseif (Test-Path "Env:\$variableName") { + Remove-Item "Env:\$variableName" } } } diff --git a/src/Cli/dotnet-aot/AotSourceFiles.props b/src/Cli/dotnet-aot/AotSourceFiles.props index 0da8aa1def61..55d801d9dae5 100644 --- a/src/Cli/dotnet-aot/AotSourceFiles.props +++ b/src/Cli/dotnet-aot/AotSourceFiles.props @@ -13,7 +13,8 @@ group, otherwise the duplicate / items will fail the build once both changes merge. * Each command contributes its own clearly-labeled group below, referencing only - its command-specific sources and dependencies. + its command-specific sources and resources. Add package and project dependencies + to AotDependencies.props so the product and tests consume the same closure. NativeEntryPoint.cs and ManagedHost.cs are added separately by each consumer because ManagedHost.cs requires InternalsVisibleTo access to NativeWrapper and the test