Add NativeAOT publish slice for Android + Apple and CI integration - #1597
Draft
matouskozak wants to merge 17 commits into
Draft
Add NativeAOT publish slice for Android + Apple and CI integration#1597matouskozak wants to merge 17 commits into
matouskozak wants to merge 17 commits into
Conversation
Introduces an opt-in '$(XHarnessAotNative)' build configuration that publishes the xharness CLI as a per-RID NativeAOT single-file binary covering only the Android and Apple command surfaces. The existing multi-TFM global tool build is unchanged when the property is not set. What the AOT slice does: * Targets a single TFM ($(NetCurrent)), enables PublishAot, defines the XHARNESS_AOT_NATIVE compile constant and turns off PackAsTool / IsPackable. * Drops the Selenium.WebDriver PackageReference and the Microsoft.AspNetCore.App FrameworkReference and excludes WASM/WASI source files (Commands/WASM, Commands/WASI, the matching CommandArguments folders and Commands/WebServer.cs). * Conditionally compiles the WASM/WASI command-set registrations, help-command branches and the Selenium-typed EnumPageLoadStrategyArgument away from the AOT build. * Adds a CopyAdbAndMlaunchForAotPublish target that runs after Publish to lay out adb and mlaunch under runtimes/any/native/... next to the native binary (the existing IncludeAdb / IncludeMlaunch hooks rely on DispatchToInnerBuilds / _GetPackageFiles which only fire for the multi-TFM / pack flow). AOT-compat fixes that apply to both builds: * Replace Assembly.Location with AppContext.BaseDirectory in MacOSProcessManager.DetectMlaunchPath and AdbRunner.GetCliAdbExePath, keeping the existing global-tool relative layout as a fallback so the global tool keeps working. * Guard XHarnessVersionCommand.GetAssemblyVersion against an empty Assembly.Location under single-file/AOT. * Replace the reflection-based System.Text.Json calls in AndroidStateCommand, AppleStateCommand and CommandDiagnostics with source-generated JsonSerializerContext partials so JSON output works without reflection metadata. Verified end to end: * dotnet publish -c Release -r osx-arm64 -p:XHarnessAotNative=true produces a 12 MB native binary; total publish dir (binary + bundled adb + bundled mlaunch) ~ 76 MB. * Smoke tests against the published binary: 'help' shows the expected Android + Apple command tree, 'apple state --json' returns real simulator data via bundled mlaunch, 'android adb -- version' invokes the bundled adb. * The default 4-TFM build (./build.sh) still succeeds with 0 warnings / 0 errors and all 429 unit tests (CLI, Common, Android, Apple, iOS.Shared) pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a Helix-backed E2E stage that runs the existing Apple simulator integration tests against a NativeAOT-published xharness instead of the global-tool install. This is the first concrete CI signal for the AOT slice introduced in the previous commit; other platforms will be added incrementally once this one is stable. Pieces: * azure-pipelines-public.yml: new 'Build_AOT_OSX' stage that runs ./build.sh --restore + 'dotnet publish ... -p:XHarnessAotNative=true -r osx-arm64' on a macOS-15 agent and uploads the publish dir as the 'XHarnessCli_AOT_osx-arm64' pipeline artifact. New 'E2E_Apple_Simulators_AOT' stage placed right after the existing E2E_Apple_Simulators (same testProject) using the new e2e-aot-test.yml template. * eng/e2e-aot-test.yml: clone of e2e-test.yml that downloads the AOT pipeline artifact and passes /p:UseXHarnessAotPayload=true /p:XHarnessAotPayloadPath=... when submitting to Helix. Depends on both Build_OSX (for parity with the existing E2E template's expectations) and Build_AOT_OSX. * tests/integration-tests/Directory.Build.props: new MSBuild branch gated on $(UseXHarnessAotPayload). When set, IncludeXHarnessCli is disabled (so the Helix SDK skips its dotnet-tool install path) and the AOT publish dir is registered as a HelixCorrelationPayload at 'xharness-aot'. HelixPreCommands then re-chmod +x the bundled adb and mlaunch (zip strips the executable bit), prepend the payload dir to PATH, set the usual XHARNESS_* env vars, and unset XHARNESS_CLI_PATH so nothing tries 'dotnet exec' on a native binary. The existing nupkg-based path is unchanged when the property is unset. * src/Microsoft.DotNet.XHarness.CLI/Microsoft.DotNet.XHarness.CLI.csproj: when XHarnessAotNative=true, override AssemblyName to 'xharness' so the published native binary matches what the Helix work-item scripts invoke from PATH. Local verification: * dotnet publish -r osx-arm64 -p:XHarnessAotNative=true produces a 12 MB binary named 'xharness' under publish/, with adb and mlaunch laid out at publish/runtimes/any/native/...; './xharness help' works from the publish dir. * Default 'dotnet build src/Microsoft.DotNet.XHarness.CLI' (4 TFMs) still completes with 0 warnings / 0 errors. * yaml.safe_load() round-trips all of azure-pipelines.yml, azure-pipelines-public.yml, eng/e2e-test.yml and eng/e2e-aot-test.yml cleanly. Not yet covered (intentional): Android / iOS-Devices / tvOS / Apple command / SimulatorInstaller AOT stages, Linux + Windows AOT publish, non-Apple Helix queues. These follow once the Apple Simulators AOT stage shows stable signal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ot-android-apple-poc # Conflicts: # src/Microsoft.DotNet.XHarness.Common/CommandDiagnostics.cs
…E -> XHARNESS_NATIVEAOT)
Pure rename for consistency with the NativeAOT terminology used
elsewhere in the .NET ecosystem ('NativeAOT', not 'AOT Native'):
MSBuild property: $(XHarnessAotNative) -> $(XHarnessNativeAot)
Compile constant: XHARNESS_AOT_NATIVE -> XHARNESS_NATIVEAOT
Touches:
* src/Microsoft.DotNet.XHarness.CLI/Microsoft.DotNet.XHarness.CLI.csproj
* src/Microsoft.DotNet.XHarness.CLI/Program.cs
* src/Microsoft.DotNet.XHarness.CLI/Commands/XHarnessHelpCommand.cs
* src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Argument.cs
* azure-pipelines-public.yml (Build_AOT_OSX 'dotnet publish' invocation)
No behavioural change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… AOT-safe Build_AOT_OSX failed in CI with NU1102 because the SDK pinned in global.json (11.0.100-preview.4.26210.111) was published to the 'dotnet11' nightly feed but its matching runtime packs (Microsoft.NETCore.App.Runtime.osx-arm64, Microsoft.AspNetCore.App.Runtime.osx-arm64, Microsoft.NETCore.App.Runtime.NativeAOT.osx-arm64, runtime.osx-x64.Microsoft.DotNet.ILCompiler) at the same version never made it to 'dotnet-public'. Only the .26230.115 build is on 'dotnet-public', and the SDK exact-version-matches its runtime packs during a self-contained / AOT publish, so 'dotnet publish ...' could not restore. This only affects the AOT publish - the regular global-tool build does not need RID-specific runtime packs and works fine on dotnet-public. Fixes: * NuGet.config: add the 'dotnet11' public nightly feed which has all four runtime packs at the exact pinned SDK version. Comment explains why it's needed for the AOT publish and not for the regular build. * src/Microsoft.DotNet.XHarness.Common/RunSummaryEmitter.cs: replace the two reflection-based JsonSerializer.Serialize<Dictionary<string, object?>>(...) calls with JsonObject / JsonArray + ToJsonString(). The previous code triggered IL2026 and IL3050 under PublishAot because STJ cannot statically know how to serialize an 'object'-typed dictionary value, and the data would actually fail to serialize correctly at runtime inside an AOT binary. JsonObject/JsonArray are first-class STJ DOM types that work without reflection. The JSON shape and ordering produced are identical to before, so existing InstrumentationRunnerSummaryTests keep passing as-is. Verified locally: * dotnet publish -r osx-arm64 -p:XHarnessNativeAot=true now succeeds. The four IL2026/IL3050 warnings from RunSummaryEmitter are gone; remaining warnings are pre-existing (XHarnessVersionCommand version metadata, Enum.GetValues, AddConsoleFormatter) and tracked for separate cleanup. * The published xharness binary runs end-to-end: 'apple state --json' returns real simulator data via bundled mlaunch. * Unit tests pass: Common (14), Android (33), Apple (82). * Default 'dotnet build src/Microsoft.DotNet.XHarness.CLI' clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The AOT publish currently produces an osx-arm64 binary only; sending it to the osx.15.amd64.open queue would fail at execution time because an arm64 Mach-O cannot run on amd64 hardware. Gate the amd64 queue on UseXHarnessAotPayload != 'true' so the existing JIT global-tool flow (which is architecture-neutral) keeps exercising both queues, while the new E2E_Apple_Simulators_AOT stage targets only osx.26.arm64.open. Observed in build 1429447: arm64 work items completed in ~62s; amd64 work items never picked up due to today's queue outage, and would have failed anyway because of the arch mismatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The first end-to-end AOT Helix run (build 1429962) failed all 6 work
items with 'dotnet: command not found' (exit 127). Investigation showed
the Helix SDK's xharness-runner.apple.sh defines an unconditional bash
alias:
function xharness() {
dotnet exec "$XHARNESS_CLI_PATH" "$@"
}
So no amount of prepending the native binary to PATH or unsetting
XHARNESS_CLI_PATH avoids the call to 'dotnet'. And because the AOT
flow sets IncludeXHarnessCli=false, the Helix SDK no longer also sets
IncludeDotNetCli=true, so there's no .NET runtime installed on the
worker either.
Surgical fix that doesn't require touching the Helix SDK: ship a tiny
'dotnet' bash shim alongside the native binary that translates
'dotnet exec <managed-dll> <args>' into a direct invocation of the
native 'xharness' binary with the same args. The shim is placed first
on PATH so it shadows any real dotnet (there isn't one anyway). Any
other dotnet invocation fails fast with a clear message — the shim is
intentionally not a general-purpose dotnet replacement.
Pieces:
* eng/aot/dotnet-shim.sh: the new shim script (committed; the publish
target copies it into the publish dir as 'dotnet').
* src/Microsoft.DotNet.XHarness.CLI/Microsoft.DotNet.XHarness.CLI.csproj:
CopyAdbAndMlaunchForAotPublish now also copies the shim into the
publish dir as 'dotnet' and chmod +x's it.
* tests/integration-tests/Directory.Build.props (AOT branch):
- Re-chmod the shim under HelixPreCommands (zip strips +x).
- Stop unsetting XHARNESS_CLI_PATH; instead point it at the native
binary. The Helix SDK xharness() alias will pass it to the shim,
which ignores the value and invokes the native binary directly.
Local verification:
* dotnet publish -r osx-arm64 -p:XHarnessNativeAot=true now produces
publish/{xharness, dotnet, runtimes/...}.
* Both 'xharness help' and 'dotnet exec /fake.dll help' through the
shim print the same Android+Apple command tree end-to-end.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hing else through
The previous shim intercepted any 'dotnet ...' invocation and either
translated 'dotnet exec <dll> <args>' into a native xharness call or
errored out. That was safe but brittle: anything else on a Helix
worker that ever needed a real 'dotnet' (e.g. dotnet-trace,
dotnet-counters, dotnet test, dotnet exec of some other dll) would
hit our error message instead.
New behaviour:
1. 'dotnet exec <...Microsoft.DotNet.XHarness.CLI.dll> <args>'
-> exec the native xharness binary with <args>. Tightened from
the previous 'any dotnet exec' match so unrelated dotnet exec
calls fall through.
2. Any other invocation -> forward to the next real 'dotnet' on
PATH, with the shim's own directory removed first so it cannot
recursively re-enter itself. If no real dotnet is present (which
is the case in today's AOT Helix flow), fail fast with a clear
message.
Verified locally against the published AOT artifact in five scenarios:
* Helix pattern with no real dotnet -> runs native xharness.
* Other 'dotnet exec' with no real dotnet -> clear error.
* 'dotnet --info' with no real dotnet -> clear error.
* 'dotnet --info' with a real dotnet on PATH -> prints SDK info
(forwarded to the real dotnet).
* Helix pattern with a real dotnet on PATH -> still routes to native
xharness (interception takes precedence over pass-through).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After tightening the shim in bfdfa85 to only intercept calls of the form 'dotnet exec *Microsoft.DotNet.XHarness.CLI.dll <args>', the AOT Helix flow regressed (build 1435574 'E2E Apple - Simulators (NativeAOT)' failed). The reason: in the AOT flow XHARNESS_CLI_PATH is set to the native binary path (.../xharness-aot/xharness) - there is no managed CLI dll. The Helix SDK xharness() bash alias unconditionally runs 'dotnet exec "$XHARNESS_CLI_PATH" "$@"', so the call becomes 'dotnet exec .../xharness-aot/xharness <args>', which no longer matched the case pattern and fell through to the 'no real dotnet on PATH' error path. Broaden the case to also match '*/xharness' and bare 'xharness'. Keeps the explicit JIT shape ('*Microsoft.DotNet.XHarness.CLI.dll') because future flows might point XHARNESS_CLI_PATH at the managed assembly even when the shim is on PATH. Caught by code review (all three review models flagged it). The right long-term fix is for the Helix SDK to learn about NativeAOT xharness payloads directly, but until then this keeps the shim honest about its contract: it intercepts calls that target our payload (managed or native), and forwards everything else to a real dotnet on PATH. Local verification (sanitised PATH, no real dotnet): * Helix shape 'dotnet exec $XHARNESS_CLI_PATH help' -> native xharness. * JIT shape 'dotnet exec /any/...XHarness.CLI.dll help' -> native xharness. * Unrelated 'dotnet exec /some/other(.dll)' -> clear error (no real dotnet). * 'dotnet --info' with a real dotnet on PATH -> forwarded. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lVersion) FileVersionInfo.ProductVersion on a Mach-O / ELF binary is empty because there is no Win32 VS_VERSION_INFO resource embedded. The previous AOT fallback through Environment.ProcessPath produced a non-null FileVersionInfo but an empty ProductVersion, so 'xharness version' printed a blank line under the NativeAOT slice. Switch to reading AssemblyInformationalVersionAttribute (set by Arcade at build time and present in the managed metadata bundled inside both JIT assemblies and AOT images), with AssemblyFileVersion and FileVersionInfo as fall-backs for unusual deployment shapes. The native binary now prints: $ xharness version 11.0.0-dev $ xharness version -v XHarness version 11.0.0-dev InstalledDir: /.../publish Drive-by: * Delete the now-unused GetAssemblyVersion() helper. The two remaining callers (Program.cs, InstallCommand.cs) only ever read .ProductVersion off the returned FileVersionInfo, so they switch to the new GetProductVersion() string accessor cleanly. * Suppress IL3000 on the legitimate Assembly.Location single-file fall-back (the empty-string case is explicitly handled by reaching Environment.ProcessPath). All unit tests still pass (CLI 37, Common 11+3 skipped, Apple 82, Android 33). Found by GPT-5.4 code review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…atorInstaller
Extends the NativeAOT pilot from one Apple stage to three, exercising
two additional Apple-simulator integration test surfaces against the
same osx-arm64 NativeAOT xharness artifact produced by Build_AOT_OSX.
No new publish jobs, no new shim work, no changes to existing JIT
stages.
* tests/integration-tests/Apple/Simulator.Commands.Tests.proj:
- Add osx.26.arm64.open queue gated on UseXHarnessAotPayload=true.
- Mirror Simulator.Tests.proj's architecture-detection pattern so
TestArch (x64 vs arm64) and the iOS simulator app bundle URL
follow the queue.
- Leave iOSSimulatorVersionUnderTest=18.1 pinned only for the amd64
queue (its Xcode set is known). On the arm64 queue, let xharness
pick the simulator that ships with the worker's Xcode by
dropping the version suffix from TestTarget. This avoids chasing
image-version bumps in this proj.
* tests/integration-tests/Apple/SimulatorInstaller.Tests.proj:
- Add osx.26.arm64.open queue gated on UseXHarnessAotPayload=true.
- The helper script uses 'dotnet "$XHARNESS_CLI_PATH" apple
simulators ...' which the dotnet shim already routes to the
native binary - no script changes needed.
* azure-pipelines-public.yml:
- New stages E2E_Apple_Simulator_Commands_AOT and
E2E_Apple_Simulator_Mgmt_AOT, both referencing the existing
eng/e2e-aot-test.yml template and the existing
XHarnessCli_AOT_osx-arm64 pipeline artifact.
Local verification:
* Default 'dotnet build src/Microsoft.DotNet.XHarness.CLI' clean
(0 warn / 0 err).
* Both proj files msbuild-restore cleanly with UseXHarnessAotPayload=true
and HelixTargetQueue=osx.26.arm64.open set.
* yaml.safe_load(azure-pipelines-public.yml) confirms three AOT
stages now reference eng/e2e-aot-test.yml.
This is Phase 1 of the documented AOT-CI expansion plan (Apple arm64
sim breadth, zero new AOT publishes). Phase 2 (Linux x64 AOT for
Android scenarios) and Phase 3 (Windows AOT for Android devices) will
follow in separate PRs once Phase 1 stays stable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous run failed OSX Release on Microsoft.DotNet.XHarness.iOS.Shared.Tests.Hardware.TCCDatabaseTests.AgreeToPromptsAsyncSuccessTest. That suite is pure mock-based, passes locally (17/17), passed on OSX Debug in the same build (1439029), and is also flaky on main (see build 1404973). Retriggering with an empty commit. [skip ci - empty-retrigger] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… shape Build 1439245 revealed two bugs in the Phase 1 AOT expansion (the JIT Simulator Commands stage also regressed because the proj fix targets a shared code path). 1) tests/integration-tests/Apple/Simulator.Commands.Tests.proj: The previous edit copied the IsArm64Queue/IsAmd64Queue detection pattern from Simulator.Tests.proj, but that pattern works there because TestArch is consumed inside TestAppBundle.proj which the Helix SDK re-invokes per-queue with HelixTargetQueue set as a property. Simulator.Commands.Tests.proj builds the download URL directly in the outer project, where $(HelixTargetQueue) is the *property* (empty - only the @(HelixTargetQueue) ItemGroup is populated) at evaluation time. Result: TestArch was empty and the download URL had a double slash, producing 404s in both JIT and AOT runs. Fix: derive TestArch and the iOS sim version pin from UseXHarnessAotPayload directly. Since each queue maps 1:1 to a code path (AOT -> arm64-only; JIT -> the legacy amd64 setup) this is correct and avoids the property-vs-item evaluation pitfall. 2) eng/aot/dotnet-shim.sh: The Helix-payloads helper script (simulatorinstaller-integration-tests.sh) invokes xharness as 'dotnet "$XHARNESS_CLI_PATH" apple simulators list' - i.e. 'dotnet <path>' with no 'exec' keyword. The shim only intercepted 'dotnet exec <path>', so the call fell through to the 'no real dotnet on PATH' error and Sim Mgmt AOT failed. Broaden the shim to also intercept 'dotnet <…/xharness>' and 'dotnet <…/Microsoft.DotNet.XHarness.CLI.dll>' (without exec). Pass-through behaviour for unrelated calls is unchanged. Local verification: * msbuild -getProperty on Simulator.Commands.Tests.proj prints the correct TestArch + URL in both JIT and AOT shapes; both URLs HTTP 200. * Shim exercised against the published artifact: - 'dotnet exec /.../xharness …' -> native (Helix xharness() alias) - 'dotnet /.../xharness …' -> native (SimulatorInstaller script) - 'dotnet exec /.../XHarness.CLI.dll …' -> native (JIT shape) - 'dotnet --info' with real dotnet -> forwarded - 'dotnet --info' / 'dotnet other.dll' without real dotnet -> clear error Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…queue arch detection properly Build 1439245 fix (commit dd10fc6) used UseXHarnessAotPayload to pick TestArch. That made the JIT path lose the arm64 queue coverage it had just gained - JIT only ran on amd64, AOT only on arm64, with no overlap. The right shape is what Simulator.Tests.proj already does: JIT runs on both queues, AOT runs on arm64 only. Root cause of the original 1439245 404 was a misunderstanding of when $(HelixTargetQueue) is populated: * The Helix SDK MultiQueue.targets re-invokes the project per HelixTargetQueue item via 'MSBuild Projects="$(MSBuildProjectFile)" Targets="Test" Properties="HelixTargetQueue=%(Identity);..."' from inside the CoreTest target. * Anything that runs BEFORE CoreTest in the outer invocation (the TestApple target was wired with BeforeTargets="CoreTest") fires with HelixTargetQueue empty. * Anything that runs INSIDE the per-queue re-invocation (Test target, or anything chained to it) sees HelixTargetQueue populated. Fix: keep the original $(HelixTargetQueue).Contains('arm64') pattern (matches Simulator.Tests.proj), but move TestApple from BeforeTargets="CoreTest" to BeforeTargets="Test" so the download happens per-queue with HelixTargetQueue in scope. Also add an Error guard that catches the empty-TestArch case loudly instead of producing a silently-broken double-slash URL. Matrix verified via 'dotnet msbuild -getProperty': queue UseXHarnessAotPayload TestArch URL has /arch/ iOS pin osx.15.amd64.open (unset) x64 x64 18.1 osx.15.amd64.open true x64 x64 18.1 (n/a, queue filtered out) osx.26.arm64.open (unset) arm64 arm64 none osx.26.arm64.open true arm64 arm64 none Net behaviour: E2E_Apple_Simulator_Commands (JIT) -> osx.15.amd64.open + osx.26.arm64.open E2E_Apple_Simulator_Commands_AOT (AOT) -> osx.26.arm64.open Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build 1440410 surfaced two more issues: * The previous attempt (commit a0de383) wired TestApple to BeforeTargets="Test" so per-queue $(HelixTargetQueue) would be populated. But `eng/common/build.sh --test` only invokes the Test target - it does NOT call CoreBuild beforehand. Without CoreBuild, the MultiQueue per-queue dispatch never fires, so the project is evaluated only once with $(HelixTargetQueue) empty - same failure mode as the original 06637b1. * Even if CoreBuild had run, the Helix SDK's CreateAppleWorkItems fans every XHarnessAppBundleToTest item out to every queue (no per-queue affinity). So the previous approach of producing one item per queue would have ended up sending the arm64 bundle to the amd64 worker and vice versa. Replace the property-based arch detection with HelixTargetQueue metadata + a batched target. Each HelixTargetQueue item carries its own TestArch and (optional) iOSSimulatorVersionUnderTest metadata. The TestApple target is batched on HelixTargetQueue.Identity so it runs once per queue, downloads the matching app bundle, and registers a XHarnessAppBundleToTest item that the Helix SDK pairs with the right queue. Behaviour verified via 'dotnet msbuild -getItem': JIT (UseXHarnessAotPayload unset): 2 queues, 2 bundles - x64 with TestTarget=ios-simulator-64_18.1, arm64 with TestTarget=ios-simulator-64 AOT (UseXHarnessAotPayload=true): 1 queue, 1 bundle - arm64 with TestTarget=ios-simulator-64 This restores arm64 JIT coverage in the Sim Commands proj and preserves the AOT pilot's arm64-only target. JIT continues to exercise both queues; AOT exercises only the arm64 queue (the published binary is osx-arm64 only). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build 1440699 surfaced two regressions introduced by the previous
per-queue rework of Simulator.Commands.Tests.proj:
1. Work item duplication on the JIT amd64 job. The batched 'TestApple'
target with Outputs="%(HelixTargetQueue.Identity)" iterated over the
full HelixTargetQueue collection in both the outer pass AND the
inner per-queue MSBuild re-invocation. In the inner pass the proj
was still freshly evaluated with both queue items declared, so two
XHarnessAppBundleToTest items were registered and both ended up
dispatched to whichever queue's CoreTest was running - one with a
mismatched arch app bundle. Main's job sends 1 work item; this PR
was sending 2.
2. arm64 queue had no iOSSimulatorVersionUnderTest pinned, which made
the CustomCommands chain call 'xharness apple simulators install
ios-simulator-64' (no version suffix). SimulatorsCommand requires
the '_X.Y' suffix and throws ArgumentException: 'Failed to parse
simulator ios-simulator-64'. Reproduced locally against the AOT
binary - the truncated Helix stack trace looked AOT-shaped but was
actually this validation error.
Switch to the property-derivation pattern that Simulator.Tests.proj
already uses successfully:
- TestArch is derived from $(HelixTargetQueue).Contains('amd64'|'arm64')
at PropertyGroup evaluation. In the inner per-queue re-invocation
the property holds the single queue identity, so the selector
resolves to the correct arch with no batching needed.
- iOSSimulatorVersionUnderTest is pinned to 18.1 for both queues.
- The TestApple target is gated on Condition="$(HelixTargetQueue) != ''"
so it only runs in the inner pass. In the outer pass no work items
are registered; CreateAppleWorkItems is a no-op (it guards on
'@(XHarnessAppBundleToTest)' != '').
Verified locally with msbuild + a diagnostic AfterTargets:
- Outer: HelixTargetQueue='', XHarnessAppBundleToTest count=0
- Inner amd64: count=1, URL .../x64/..., target=ios-simulator-64_18.1
- Inner arm64 (UseXHarnessAotPayload=true): count=1, URL .../arm64/...,
target=ios-simulator-64_18.1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ot-android-apple-poc # Conflicts: # src/Microsoft.DotNet.XHarness.Common/CommandDiagnostics.cs
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.
What
Opt-in NativeAOT publish for
Microsoft.DotNet.XHarness.CLIcovering only the Android and Apple command surfaces, plus a new Helix-backed pipeline stage that exercises it end-to-end.The change is additive and gated: nothing happens unless
/p:XHarnessNativeAot=trueis passed. The existing multi-TFM global-tool build, the nupkg, and every existing E2E stage are byte-identical to before.Why
A per-RID single-file xharness lets us run the Android + Apple integration tests on Helix workers that don't have a .NET SDK installed, with faster startup and a simpler payload. WASM/WASI stay on the existing JIT path because their dependencies (
Microsoft.AspNetCore.App,Selenium.WebDriver, non-genericActivator.CreateInstance) aren't AOT-compatible without significant rework.How
-p:XHarnessNativeAot=trueflips a single MSBuild switch inMicrosoft.DotNet.XHarness.CLI.csprojthat:$(NetCurrent)), setsPublishAot=true,InvariantGlobalization=true.XHARNESS_NATIVEAOTso a handful of#ifblocks compile WASM/WASI command-set registrations, help branches, and the Selenium-typedEnumPageLoadStrategyArgumentout.Selenium.WebDriverandMicrosoft.AspNetCore.App.Commands/WASM,Commands/WASI,Commands/WebServer.cs,CommandArguments/WASM,CommandArguments/WASI,CommandArguments/Arguments/WebServer*.cs.xharnessso it sits onPATHunchanged.PublishCopy target that stages adb and the mlaunch tree underpublish/runtimes/any/native/...(the existingIncludeAdb/IncludeMlaunchtargets hookDispatchToInnerBuilds/_GetPackageFileswhich don't fire for a single-TFM publish).AOT-compat fixes applied to both builds (back-compatible with the global-tool layout):
MacOSProcessManager.DetectMlaunchPathandAdbRunner.GetCliAdbExePathnow useAppContext.BaseDirectory(valid in single-file) and probe the global-tool layout first, falling back to the flat publish layout.XHarnessVersionCommand.GetAssemblyVersionfalls back toEnvironment.ProcessPathwhenAssembly.Locationis empty.AndroidStateCommand,AppleStateCommand,CommandDiagnosticsswitch from reflection-basedSystem.Text.Jsonto[JsonSerializable]source-generated contexts.CI
Build_AOT_OSXstage publishes the AOT slice forosx-arm64and uploads it as theXHarnessCli_AOT_osx-arm64pipeline artifact.E2E_Apple_Simulators_AOTstage runs the existingtests/integration-tests/Apple/Simulator.Tests.projagainst that artifact through Helix. The integration-testsDirectory.Build.propsgrows a$(UseXHarnessAotPayload)branch that disablesIncludeXHarnessCli(so the Helix SDK skips itsdotnet tool install), registers the AOT publish dir as a correlation payload, re-chmods adb/mlaunch, prepends the payload toPATH, and unsetsXHARNESS_CLI_PATHso nothing triesdotnet execon a native binary. The nupkg path is untouched.eng/e2e-aot-test.ymlis a sibling ofeng/e2e-test.ymlthat downloads the AOT artifact and threads it through. Kept as a sibling rather than parameterising the existing template so the JIT stages stay byte-identical.Verified locally
dotnet publish -r osx-arm64 -p:XHarnessNativeAot=truexharnessbinary; 76 MB total publish dir (binary + bundled adb + bundled mlaunch)../xharness help,apple state --json,android adb -- versionon the published binaryapple state --jsonreturns real simulator data via bundled mlaunch../build.sh(4 TFMs)Common,CLI,Android,Apple,iOS.Shared)Out of scope
Both are intentional follow-ups so this PR keeps a focused signal — once
E2E_Apple_Simulators_AOTis stable, the same template extends to the other queues.Known AOT analyzer warnings (do not crash, tracked for separate cleanup)
Enum.GetValues(Type)in two spots inCommandArguments/Argument.cs(trivial to switch toEnum.GetValues<TEnum>()).AddConsoleFormatter<T,TOptions>registration (anMicrosoft.Extensions.LoggingAOT gap).TypeFromAssemblyArgumentreflection (only used by test-filter scenarios).xharness versionprints empty in the AOT binary because noAssemblyInformationalVersionis baked in.Opening as draft to let the new pipeline stages validate in CI before promoting.