[browser] Native build on CoreCLR in Wasm.Build.Tests#127073
[browser] Native build on CoreCLR in Wasm.Build.Tests#127073
Conversation
Wire up the CoreCLR browser WBT Helix leg so that per-app native relink can run on Helix workers: - Add a dedicated HelixCorrelationPayload ItemGroup for the CoreCLR flavor that ships eng/native.wasm.targets and eng/AcquireEmscriptenSdk.targets next to BrowserBuildTargetsDir (BrowserWasmApp.CoreCLR.targets' import chain needs them). - Hoist Microsoft.NET.Sdk.WebAssembly.Pack (WasmSdkTargets/WasmSdkTools) out of the Mono-only block into the shared NeedsEMSDK block so 'dotnet new wasmbrowser' resolves on CoreCLR workers too. - Export REPOSITORY_ENGINEERING_DIR, EMSDK_PATH, WASM_BUILD_SUPPORT_DIR and WASM_APP_BUILDER_DIR from the generated RunScriptTemplate so the CoreCLR relink targets can locate their tool payloads on Helix. - Resolve and bridge those values through BuildEnvironment + EnvironmentVariables so local 'dotnet test' runs without Helix still find the locally built targets/emsdk. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All WBT tests tagged [TestCategory("native")] are excluded on CoreCLR
by Wasm.Build.Tests.csproj's trait filter. Now that the CoreCLR native
relink payload ships to Helix, seed a small CoreCLR-enabled subset via
a new trait category:
- Replace class-level [TestCategory("native")] with
[TestCategory("native-coreclr")] on
Wasm.Build.Templates.Tests.NativeBuildTests (both methods are
non-AOT and exercise native relink via the wasmbrowser template).
- Add Wasm.Build.Templates.Tests.NativeBuildTests to the CoreCLR WBT
jobs list so xharness discovers and schedules it.
xunit trait filter semantics: '-notrait category=native' excludes any
test that carries the 'native' trait on its class or method. Class+
method traits merge, so dual-tagging does not work -- tags must be
replaced, not added, to enable a test on CoreCLR. The existing
'-notrait category=native -notrait category=mono -notrait
category=workload' filter already allows 'native-coreclr' through, so
no csproj change is required.
Mono coverage is preserved: Mono's xunit invocation does not filter on
category=native*, so retagging leaves the tests running on Mono
unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Scratch pipeline to accelerate iteration on WBT changes. Keeps only: Build stage: - browser_wasm / browser_wasm_win CoreCLR runtime pack build (nameSuffix: CoreCLR, publishes WBT artifacts) - browser-wasm-coreclr-build-tests runner (alwaysRun: true) - browser_wasm / browser_wasm_win Mono runtime pack builds (SingleThreaded + MultiThreaded, publishes WBT artifacts) - browser-wasm-build-tests runner (alwaysRun: true) Removed: all other product builds, NativeAOT, runtime tests, library tests, installer tests, Android/iOS/MacCatalyst/Mono interp/Minijit test legs. Path-filter conditions on the remaining WBT build jobs are simplified so everything runs unconditionally on PR. Revert this commit before merging to main. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
CoreCLR_WasmBuildTests failed in 'Send to Helix' with
sendtohelixhelp.proj(367,5): error : Could not find emsdk at ,
needed to provision for running tests on helix
because $(EMSDK_PATH) was empty when
StageDependenciesForHelix evaluated
<HelixDependenciesToStage SourcePath="$(EMSDK_PATH)" />.
Mono WBT agents have EMSDK_PATH set as a side-effect of the wasm
runtime build; CoreCLR WBT agents only build
Wasm.Build.Tests.csproj and do not provision emsdk.
Mirror the sendtohelix-wasi.targets pattern: wrap the emsdk
HelixDependenciesToStage item in a target that runs
BeforeTargets=StageDependenciesForHelix with
DependsOnTargets=AcquireEmscriptenSdkUnconditional so that emsdk
is provisioned from NuGet packages (already referenced by
AcquireEmscriptenSdk.targets) when EMSDK_PATH is not already set.
No-op when EMSDK_PATH is already provided by the agent, so Mono
WBT behavior is unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
The CoreCLR WBT pipeline splits build and send-to-helix across two agents. The upstream build job acquires emsdk on its own agent via src/coreclr/runtime.proj's BuildRuntimeDependsOnTargets, but that state is not shipped as a build artifact, so when sendtohelix.proj runs on the WBT agent, src/mono/browser/emsdk/ is missing. The IncludeEmsdkForHelixStaging target then falls through to _AcquireLocalEmscriptenSdk inside sendtohelix.proj whose @(PackageReference) set is polluted by Arcade, producing a bogus emsdk folder and a 'Correlation Payload not found' Helix error. Fix: add a 'Provision emsdk' preBuildStep to browser-wasm-coreclr-build-tests.yml that invokes the existing `provision.emsdk` subset, which drives eng/AcquireEmscriptenSdk.proj. That project's @(PackageReference) set contains only the four emsdk packages, so it produces a clean src/mono/browser/emsdk/. After this step the WBT agent matches a Mono WBT agent post-runtime-build: EMSDK_PATH is set on import, _AcquireLocalEmscriptenSdk is Inputs/Outputs-skipped, and IncludeEmsdkForHelixStaging stages the real emsdk. Also fix eng/AcquireEmscriptenSdk.proj: its <Target Name="Build" DependsOnTargets="..." /> was being shadowed by Microsoft.Build.NoTargets' own Build target, so `./build.sh -s provision.emsdk` never actually invoked AcquireEmscriptenSdkUnconditional. Hook via BeforeTargets="Build" instead. The subset had no other consumers in CI so this has no regression surface. Verified locally: ./build.sh -s provision.emsdk -os browser -c Release populates src/mono/browser/emsdk/ with the expected layout (bin/, emscripten/, node/, lib/, emsdk_env.sh, .emscripten) and zero bogus package files. A follow-up invocation is a 357 ms no-op. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
StageDependenciesForHelix guards its body with a condition over @(HelixDependenciesToStage) that is evaluated before any BeforeTargets hook can populate the item. A BeforeTargets-scoped ItemGroup therefore runs, but the target body itself is skipped and nothing is staged. Move the emsdk HelixDependenciesToStage item to project-evaluation scope (matching the existing wasmtime pattern) so it is visible when StageDependenciesForHelix evaluates its condition. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The runtime pack is acquired on the Helix worker via the downloaded nupkg, so shipping the raw $(MicrosoftNetCoreAppRuntimePackDir) layout as a correlation payload is unnecessary — and fails on the CoreCLR WBT agent, which doesn't have that folder (the upstream runtime-pack build job only publishes packages/**, not bin/microsoft.netcore.app.runtime.browser-wasm/**). Move the payload into the Mono-only block alongside the other Mono-only build tooling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
HelixCorrelationPayload pointing at individual .targets files causes
the Helix client to try unzipping them ("File is not a zip file"
retries + DownloadError). Copy native.wasm.targets and
AcquireEmscriptenSdk.targets into an intermediate staging directory
and ship that directory as build/eng.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When running without workloads, the browser/console WASM templates are not pre-installed. Add EnsureWasmTemplatesInstalled() which installs them from the built nugets path via 'dotnet new install' before CreateWasmTemplateProject calls 'dotnet new'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These tests fail on the CoreCLR browser-wasm path for product-level reasons tracked in #127073: - ProjectWithDllImportsRequiringMarshalIlGen_ArrayTypeParameter: getAssemblyExports() call in the default wasmbrowser main.js throws TypeLoad on CoreCLR when the user assembly has no [JSExport], because JSHostImplementation.CoreCLR.BindAssemblyExports uses throwOnError:true while the Mono native path is tolerant. - BuildWithUndefinedNativeSymbol(allowUndefined:false): WasmAllowUndefinedSymbols=false is not honored on the CoreCLR native build path; the build succeeds instead of failing. Gate both on IsMonoRuntime until the CoreCLR product gaps are addressed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Note This comment was generated with GitHub Copilot assistance. @pavelsavara — summary of CoreCLR 1.
|
| Path | When assembly has no JSExports | |
|---|---|---|
| Mono | JSHostImplementation.Mono.cs → Interop.Runtime.BindAssemblyExports (native) |
Tolerant, no-ops |
| CoreCLR | JSHostImplementation.CoreCLR.cs:23 → assembly.GetType("…__GeneratedInitializer", throwOnError: true) |
Throws TypeLoad |
Proposed fix
In src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.CoreCLR.cs, use throwOnError: false and early-return if the type is null (matching Mono semantics — legitimate for assemblies with no [JSExport]):
Type? generatedInitializerType = assembly.GetType(
"System.Runtime.InteropServices.JavaScript.__GeneratedInitializer",
throwOnError: false);
if (generatedInitializerType is null)
return Task.CompletedTask;Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Restore eng/pipelines/runtime.yml and eng/pipelines/coreclr/ci.yml to their state on origin/main, reverting the two scratch commits: - e0b758c [DO NOT MERGE] runtime.yml: trim to WBT-only for PR testing - fb69e23 [DO NOT MERGE] Disable runtime-coreclr pipeline for PR testing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…k, pass harness env to template install - RunScriptTemplate.cmd: remove 'set VAR=%%VAR%%' no-op block — the vars are inherited from the parent environment into the setlocal scope automatically. - Local.Directory.Build.targets / .props: normalize env-var-sourced paths via GetFullPath + EnsureTrailingSlash so imports are robust to missing trailing separators. - WasmTemplateTestsBase.EnsureWasmTemplatesInstalled: propagate BuildEnvironment.EnvVars to the 'dotnet new install' process so templates install under the same isolated SDK/NuGet config used by the rest of the test run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The class is now excluded on CoreCLR via the existing '-notrait category=native' xunit filter (and was already removed from BuildWasmAppsJobsListCoreCLR.txt in 101fcae), so the per-method ConditionalTheory(IsMonoRuntime) gating is no longer needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…install - Add a 120s timeout to 'dotnet new install' in EnsureWasmTemplatesInstalled; terminate the process tree on timeout and include captured stdout/stderr in the failure message so a hang doesn't stall the entire test run. - Set DOTNET_CLI_HOME (sibling of NUGET_PACKAGES when available, else next to the template nupkg) so template state doesn't leak into the default user profile and can't collide across Helix agents or jobs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| <ItemGroup Condition="('$(NeedsEMSDK)' == 'true' or '$(IncludeNodePayload)' == 'true') and '$(EMSDK_PATH)' != ''"> | ||
| <HelixDependenciesToStage | ||
| Name="emsdk" | ||
| Include="$(EmSdkDirForHelixPayload)" | ||
| Condition="'$(NeedsEMSDK)' == 'true' or '$(IncludeNodePayload)' == 'true'" | ||
| SourcePath="$(EMSDK_PATH)" | ||
| /> | ||
| SourcePath="$(EMSDK_PATH)" /> | ||
| </ItemGroup> |
There was a problem hiding this comment.
The HelixDependenciesToStage ItemGroup is now conditioned on $(EMSDK_PATH) != ''. If NeedsEMSDK is true but EMSDK_PATH isn’t set (and the provisioned emsdk directory isn’t present), this will skip staging entirely and can lead to harder-to-diagnose Helix payload failures later. Consider keeping the item unconditionally when NeedsEMSDK/IncludeNodePayload is true and letting StageDependenciesForHelix emit its clear error, or add an explicit <Error> when NeedsEMSDK is true and $(EMSDK_PATH) is empty.
🤖 Copilot Code Review — PR #127073Note This review was generated by Copilot and reflects automated analysis — not a human reviewer. Models contributing: Claude Opus 4.6 (primary), Claude Sonnet 4.5. Holistic AssessmentMotivation: Justified and well-scoped. CoreCLR is gaining WASM support and needs the same build-test coverage that Mono already has. Extending the existing WBT infrastructure to CoreCLR is the right time to do this — before the test gap widens. Approach: Sound. The PR extends the existing Mono WBT framework rather than duplicating it, with clean Summary: ✅ LGTM. No blocking issues found. The changes are well-documented, defensively coded, and properly gated. The Detailed Findings✅ AcquireEmscriptenSdk.proj Fix — CorrectThe old ✅ Helix Payload Separation — Well-StructuredThe split of The eng/*.targets staging into a directory (rather than individual files) correctly avoids the Helix client's "unzip as archive" behavior — good catch. ✅ EMSDK Evaluation-Time Staging — CorrectMoving
✅ Windows Parity — No Gap
✅ NativeBuildTests Exclusion — CleanThe tests are excluded on CoreCLR via the existing ✅ Template Installation — Well-Implemented
💡 Missing Trailing Newline —
|
Note
This PR description was AI-generated (GitHub Copilot CLI).
Enables per-app native relink of Wasm apps built on CoreCLR inside the
Wasm.Build.Testssuite on Helix, and seeds a small subset of native tests that exercise the new plumbing.What changes
1. Pack CoreCLR WBT native-build files as Helix payload (
2bdd66268d8)eng/native.wasm.targetsandeng/AcquireEmscriptenSdk.targetsalongsideBrowserBuildTargetsDirso theBrowserWasmApp.CoreCLR.targetsimport chain resolves on Helix workers.Microsoft.NET.Sdk.WebAssembly.Pack(WasmSdkTargets/WasmSdkTools) out of the Mono-only correlation-payload block into the sharedNeedsEMSDKblock — thewasmbrowsertemplate needs the WebAssembly SDK regardless of runtime flavor.REPOSITORY_ENGINEERING_DIR,EMSDK_PATH,WASM_BUILD_SUPPORT_DIR,WASM_APP_BUILDER_DIRfrom the generatedRunScriptTemplate.{sh,cmd}and bridge them throughBuildEnvironment/EnvironmentVariablesso localdotnet testruns outside Helix still find the locally built targets/emsdk.2. Introduce
native-coreclrWBT category and enable first test class (1c405ae1e7f)All tests tagged
[TestCategory("native")]are excluded on CoreCLR byWasm.Build.Tests.csproj's trait filter (-notrait category=native -notrait category=mono -notrait category=workload). Adding the same test to[TestCategory("native-coreclr")]does not work because class + method trait values merge —-notrait category=nativestill excludes a test that has both tags.[TestCategory("native")]with[TestCategory("native-coreclr")]onWasm.Build.Templates.Tests.NativeBuildTests(2[Theory]methods, allaot: false, exercising native relink via thewasmbrowsertemplate).eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt.native-coreclrthrough naturally, and Mono's xunit invocation has nocategory=native*filter, so Mono coverage is unchanged.3.
[DO NOT MERGE]runtime.yml: trim to WBT-only for PR testing (e0b758c7bc3)Scratch pipeline change to accelerate iteration. Keeps only the 5 jobs needed for Mono + CoreCLR WBT (CoreCLR runtime-pack build, CoreCLR WBT runner, Mono SingleThreaded + MultiThreaded build-only, Mono WBT runner) with
alwaysRun: true. Revert before merging.Validation
./dotnet.sh build src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj -c Release /p:TargetOS=browser /p:RuntimeFlavor=CoreCLR /p:Scenario=BuildWasmApps→ 0W/0E.RunTests.shinspected: xunit trait arg still-notrait category=native …(excludesnative, letsnative-coreclrthrough); env-var exports present with concrete paths.stringsonWasm.Build.Tests.dllconfirms bothnativeandnative-coreclrcategory literals are baked in.Follow-up (not in this PR)
nativetests on CoreCLR by retagging non-AOT methods in mixed AOT/non-AOT classes (Wasm.Build.Tests.NativeBuildTests,NativeLibraryTests,PInvokeTableGeneratorTests,DllImportTests).