Skip to content

[browser] CoreCLR in-tree relink#125607

Draft
maraf wants to merge 40 commits intomainfrom
maraf/WasmCoreCLRNativeBuild
Draft

[browser] CoreCLR in-tree relink#125607
maraf wants to merge 40 commits intomainfrom
maraf/WasmCoreCLRNativeBuild

Conversation

@maraf
Copy link
Copy Markdown
Member

@maraf maraf commented Mar 16, 2026

Contributes to #123670
Contributes to #126100

maraf and others added 11 commits February 24, 2026 09:16
Implement per-app native relinking via emcc for CoreCLR browser-wasm,
matching the capability Mono already has. Unlike Mono, CoreCLR has no C
source files in the runtime pack, so the native build is link-only.

Key changes:
- BrowserWasmApp.CoreCLR.targets: Complete implementation replacing stubs
  - Emscripten toolchain setup (_CoreCLRSetupEmscripten, _CoreCLRSetupToolchain)
  - Build native defaults (WasmBuildNative=false for build, true for publish)
  - emcc link target with all CoreCLR .a libraries + JS library files
  - Heap size calculation (default 128MB matching CMakeLists.txt)
  - WasmBuildApp/WasmTriggerPublishApp/WasmNestedPublishApp target chain
  - wasm-opt post-link optimization
- corehost.proj: Copy libSystem.Native.Browser.extpost.js to runtime pack

The implementation is self-contained and does not import WasmApp.Common.targets
or BrowserWasmApp.targets. It reuses MSBuild task assemblies via UsingTask.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When WasmBuildNative=true, re-link dotnet.native.wasm from CoreCLR static
libraries using emcc, allowing apps to include custom native code via
NativeFileReference items.

The implementation:
- Imports eng/native.wasm.targets for shared Emscripten/ICU/export properties
- Sets up Emscripten toolchain and environment
- Compiles user native sources (NativeFileReference .c/.cpp) with EmccCompile
- Links all CoreCLR .a libraries + user objects into dotnet.native.wasm
- Handles both build and publish (nested publish) flows
- Link flags mirror src/native/corehost/browserhost/CMakeLists.txt
- Gates ICU libraries on InvariantGlobalization
- Supports WasmEnableExceptionHandling, WasmEnableSIMD, custom EmccFlags

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Integrate the CoreCLR ManagedToNativeGenerator MSBuild task into the
native re-link pipeline. This generates P/Invoke dispatch tables,
reverse P/Invoke tables, and interpreter-to-native thunks that are
compiled and linked into dotnet.native.wasm.

Key changes:
- Add _CoreCLRGenerateManagedToNative target that scans managed
  assemblies for P/Invoke signatures and generates C++ source files
- Generate coreclr_compat.h shim header providing type/macro stubs
  (MethodDesc, PCODE, ULONG, LOG, PORTABILITY_ASSERT) so generated
  files compile outside the full CoreCLR build context
- Use .cpp extensions for generated files (they contain extern "C",
  namespaces, and other C++ constructs)
- Fix CoreLib discovery to check native/ dir first (CoreCLR WASM
  ships CoreLib there, not in lib/net11.0/)
- Fix UsingTask assembly paths: EmccCompile and ManagedToNativeGenerator
  both live in WasmAppBuilder.dll, not WasmBuildTasks.dll

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Don't compile reverse-pinvoke-table.cpp separately to avoid duplicate
  symbols with callhelpers-reverse.cpp.o in libcoreclr_static.a
- Condition LLD_REPORT_UNDEFINED and ERROR_ON_UNDEFINED_SYMBOLS to Debug
  only, matching CMakeLists.txt behavior
- Add Dependencies metadata to generated source files for incremental builds
- Use node$(_ExeExt) for cross-platform compat
- Reorder link libraries: libBrowserHost.a first, matching CMakeLists.txt

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t string

- Remove MAXIMUM_MEMORY from _EmccCommonFlags (compile+link shared flags);
  it only belongs in the link step's _EmccLinkStepArgs
- Fix PORTABILITY_ASSERT fprintf format: use %25s MSBuild encoding to
  produce correct %s printf format specifier

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…jection

- Add SimpleNativeBuildForCoreCLR test that publishes WasmBrowserRunMainOnly
  with WasmBuildNative=true, verifying the CoreCLR native build pipeline works
- Add CoreCLR property injection to CreateWasmTemplateProject (was missing,
  only CopyTestAsset had it), enabling template-based tests on CoreCLR
- Add UsingBrowserRuntimeWorkload=false to both CoreCLR injection blocks to
  bypass the wasm-tools workload check (NETSDK1147) that fires when
  WasmBuildNative=true triggers _WasmNativeWorkloadNeeded

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@maraf maraf added this to the 11.0.0 milestone Mar 16, 2026
@maraf maraf self-assigned this Mar 16, 2026
@maraf maraf added arch-wasm WebAssembly architecture area-Build-mono labels Mar 16, 2026
Copilot AI review requested due to automatic review settings March 16, 2026 08:40
@maraf maraf added the os-browser Browser variant of arch-wasm label Mar 16, 2026

This comment was marked as off-topic.

Copilot AI review requested due to automatic review settings March 16, 2026 10:42

This comment was marked as off-topic.

On Windows, the EmccCompile task's CompilerBinaryPath was set to bare
'emcc', which relies on the PATH environment variable to resolve. However,
the PATH constructed in _CoreCLRSetupEmscripten was fragmented by MSBuild
item-separator splitting on semicolons, so only the emsdk root directory
ended up in PATH — not the emscripten subdirectory where emcc lives.

Fix by setting WasmClang to an absolute path using
EmscriptenUpstreamEmscriptenPath, matching the existing pattern in the
Mono targets (BrowserWasmApp.targets line 173). Also update the link
step Exec command to use $(WasmClang) instead of bare 'emcc'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated no new comments.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Member Author

maraf commented Apr 1, 2026

Note

This comment was generated with the assistance of GitHub Copilot.

Investigation: Delivering CoreCLR Native Build Targets to Wasm.Build.Tests

As part of enabling template-based native tests (IcuTests, NativeBuildTests, etc.) in the CoreCLR CI pipeline, I investigated how to make BrowserWasmApp.CoreCLR.targets available to standalone test projects created by dotnet new wasmbrowser.

The Problem

Test projects created during Wasm.Build.Tests are standalone — they live in temp directories and use the SDK targets (or Local.Directory.Build.targets in no-workload mode). Currently, BrowserWasmApp.CoreCLR.targets is only imported via WasmApp.InTree.targets for in-tree repo builds. When a test project sets <NativeFileReference>, the old WorkloadManifest.targets just warns (because WasmNativeWorkloadAvailable != true) and native relinking never happens.

This means tests like BuildWithUndefinedNativeSymbol and ProjectWithDllImportsRequiringMarshalIlGen pass/fail for the wrong reasons — native build is silently skipped.

Root Cause: Missing Import Chain

BrowserWasmApp.CoreCLR.targets depends on repo-level paths that don't exist in standalone test projects:

BrowserWasmApp.CoreCLR.targets
  ├── Import: $(RepositoryEngineeringDir)native.wasm.targets   ← repo path
  ├── UsingTask: $(WasmAppBuilderTasksAssemblyPath)             ← task DLL path
  └── Import: EmSdkRepo.Defaults.props (needs EMSDK_PATH)      ← emscripten

How It Works Today

Context How targets are delivered
In-tree builds WasmApp.InTree.targets imports BrowserWasmApp.CoreCLR.targets when RuntimeFlavor=CoreCLR
Mono workload wasm-tools workload installs all targets + tasks into the SDK
Helix CI (CoreCLR) Correlation payload ships build/wasm/, build/wasm-shared/, build/WasmAppBuilder/, build/emsdk/; run script sets env vars

Proposed Approach

Wire up BuildEnvironment.cs + Local.Directory.Build.targets to mirror the Helix correlation payload pattern for local runs:

  1. BuildEnvironment.cs (CoreCLR mode): Set environment/MSBuild properties pointing to repo artifacts:

    • RepositoryEngineeringDir{repoRoot}/eng/
    • WasmAppBuilderTasksAssemblyPathartifacts/bin/WasmAppBuilder/...
    • EMSDK_PATH → from system or artifacts/obj/emsdk/
  2. Local.Directory.Build.targets: Add conditional import of BrowserWasmApp.CoreCLR.targets when RuntimeFlavor=CoreCLR

  3. AddCoreClrProjectProperties(): Set <RuntimeFlavor>CoreCLR</RuntimeFlavor> in each test project csproj

This keeps the same code paths for local dev and Helix CI — just different values for the path properties.

What's Already Done (This PR Branch)

  • ✅ Changed test traits from nativecoreclr-native (5 test classes)
  • ✅ Added EnsureWasmTemplatesInstalled() to auto-install WASM templates (mirrors xharness pattern)
  • ✅ Added test classes to BuildWasmAppsJobsListCoreCLR.txt
  • ✅ Verified template install + project creation works end-to-end

What Remains

  • Implement the target delivery mechanism described above
  • Verify BuildWithUndefinedNativeSymbol and ProjectWithDllImportsRequiringMarshalIlGen actually perform native linking
  • Handle AOT-dependent tests (need skip conditions for no-workload CoreCLR mode)
  • Handle ICU sharding tests (need native build for ICU data replacement)

Copy link
Copy Markdown
Member Author

maraf commented Apr 10, 2026

Note

This comment was generated with the assistance of GitHub Copilot.

Implementation Plan: Pack CoreCLR Native Build Files as Helix Payload for WBT

Problem

CoreCLR WBT native tests (NativeBuildTests, IcuTests, etc.) create standalone projects via dotnet new wasmbrowser. These projects need BrowserWasmApp.CoreCLR.targets for native re-linking via emcc, but on Helix these files aren't available. The import chain is:

BrowserWasmApp.CoreCLR.targets         (src/mono/browser/build/)
  +-- Import: $(RepositoryEngineeringDir)native.wasm.targets    (eng/)
  |     +-- Import: $(RepositoryEngineeringDir)AcquireEmscriptenSdk.targets  (eng/)
  +-- Import: EmSdkRepo.Defaults.props  (same dir, via MSBuildThisFileDirectory)
  +-- UsingTask: WasmAppBuilder.dll     (via $(WasmAppBuilderTasksAssemblyPath))

For Mono WBT, the wasm-tools workload provides these files. For CoreCLR WBT (TestUsingWorkloads=false), there's no workload -- test projects use Local.Directory.Build.targets/props which currently lack any CoreCLR native build support.

Approach

Mirror the existing Mono Helix correlation payload pattern, adding CoreCLR-specific items and wiring up the test project templates to import the CoreCLR targets.

Changes (8 files)

1. src/libraries/sendtohelix-browser.targets -- Enable Helix payload for CoreCLR WBT

The EMSDK + build tools payload is currently gated on NeedsEMSDK == true (Mono AOT only).

  • Set NeedsEMSDK=true when RuntimeFlavor==CoreCLR and Scenario==BuildWasmApps (CoreCLR native builds invoke emcc)
  • Add eng/native.wasm.targets and eng/AcquireEmscriptenSdk.targets to payload as build/eng/
  • The existing NeedsEMSDK==true block already ships BrowserBuildTargetsDir -> build/wasm (contains BrowserWasmApp.CoreCLR.targets + EmSdkRepo.Defaults.props) and WasmAppBuilderDir -> build/WasmAppBuilder (contains WasmAppBuilder.dll) -- enabling NeedsEMSDK automatically includes 3 of 4 files
  • Add HelixPreCommand to export REPOSITORY_ENGINEERING_DIR pointing to extracted build/eng/

2. Wasm.Build.Tests.csproj -- Set RunScript env vars

Add RunScriptCommands to export REPOSITORY_ENGINEERING_DIR for both CI (Helix) and local runs.

3. EnvironmentVariables.cs -- Add new env var

Add RepositoryEngineeringDir field reading REPOSITORY_ENGINEERING_DIR.

4. BuildEnvironment.cs -- Wire up CoreCLR env vars

For CoreCLR mode, propagate RepositoryEngineeringDir and EMSDK_PATH to test project builds via EnvVars dict.

5. Local.Directory.Build.props -- Set CoreCLR properties

When RuntimeFlavor==CoreCLR, set RepositoryEngineeringDir, WasmAppBuilderTasksAssemblyPath, and RuntimeFlavor from env vars.

6. Local.Directory.Build.targets -- Import CoreCLR targets

Add conditional import of BrowserWasmApp.CoreCLR.targets when RuntimeFlavor==CoreCLR.

7-8. RunScriptTemplate.sh / .cmd -- Propagate env vars

In set_env_vars, propagate REPOSITORY_ENGINEERING_DIR from Helix payload.

Key Design Decisions

  1. Reuse existing payload mechanism: Enable NeedsEMSDK for CoreCLR to activate the existing payload that already ships BrowserBuildTargetsDir and WasmAppBuilderDir. Only build/eng/ is new.

  2. RepositoryEngineeringDir as the bridge: The only truly missing files are native.wasm.targets + AcquireEmscriptenSdk.targets. By shipping them to build/eng/ and setting RepositoryEngineeringDir, the existing import chain in BrowserWasmApp.CoreCLR.targets works unmodified.

  3. Env vars as transport: Consistent with existing WBT infra (BUILT_NUGETS_PATH, CHROME_PATH_FOR_TESTS, etc.).

Risks / Considerations

  • AcquireEmscriptenSdk.targets references $(MonoProjectRoot) for provisioning, but on Helix with pre-provisioned EMSDK these targets are no-ops ($(EMSDK_PATH) is set before import).
  • Mono-specific payloads (MonoAOTCompilerDir, MonoAotCrossDir) are also shipped under NeedsEMSDK -- unnecessary for CoreCLR but functionally harmless. Can be gated on RuntimeFlavor==Mono as a follow-up optimization.
  • The WasmApp.LocalBuild import chain is Mono-designed. For CoreCLR, we bypass it with a direct import of BrowserWasmApp.CoreCLR.targets.

Copilot AI review requested due to automatic review settings April 10, 2026 08:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

<Message Text="Linking CoreCLR WASM with initial memory $(WasmInitialHeapSize) bytes." Importance="High" />
<Message Text="Linking with emcc $(EmccLinkOptimizationFlag). This may take a while ..." Importance="High" />

<Exec Command='"$(WasmClang)" $(_WasmDefaultFlags) "@$(_WasmLinkRsp)"'
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The Exec Command value includes literal leading/trailing single-quote characters (Command='"$(WasmClang)" ...'), which will be passed to the command interpreter and can break execution/quoting (notably on Windows). Use XML attribute quoting only (as done elsewhere) and avoid embedding extra quote characters in the command string.

Suggested change
<Exec Command='"$(WasmClang)" $(_WasmDefaultFlags) "@$(_WasmLinkRsp)"'
<Exec Command="&quot;$(WasmClang)&quot; $(_WasmDefaultFlags) &quot;@$(_WasmLinkRsp)&quot;"

Copilot uses AI. Check for mistakes.
return;

string? templateNupkg = Directory.GetFiles(s_buildEnv.BuiltNuGetsPath, "Microsoft.NET.Runtime.WebAssembly.Templates.*.nupkg")
.Where(f => !f.EndsWith(".symbols.nupkg", StringComparison.OrdinalIgnoreCase))
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

Directory.GetFiles(...).FirstOrDefault() makes the selected template nupkg non-deterministic when multiple matching packages exist in BUILT_NUGETS_PATH (GetFiles ordering is unspecified). Consider selecting the intended version explicitly (e.g., match the built version) or sorting and picking the newest to avoid flaky installs.

Suggested change
.Where(f => !f.EndsWith(".symbols.nupkg", StringComparison.OrdinalIgnoreCase))
.Where(f => !f.EndsWith(".symbols.nupkg", StringComparison.OrdinalIgnoreCase))
.OrderByDescending(File.GetLastWriteTimeUtc)
.ThenByDescending(f => f, StringComparer.OrdinalIgnoreCase)

Copilot uses AI. Check for mistakes.
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The dotnet new install process is started without the standard s_buildEnv.EnvVars (DOTNET_ROOT/PATH/etc) that other test dotnet invocations use. This can reintroduce the repo/machine environment contamination that BuildEnvironment is explicitly trying to avoid; consider applying s_buildEnv.EnvVars to psi.Environment (not just DOTNET_SKIP_FIRST_TIME_EXPERIENCE).

Suggested change
};
};
foreach (KeyValuePair<string, string> envVar in s_buildEnv.EnvVars)
psi.Environment[envVar.Key] = envVar.Value;

Copilot uses AI. Check for mistakes.
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

dotnet tool restore is invoked via ProcessStartInfo without applying s_buildEnv.EnvVars (DOTNET_ROOT/PATH/etc), which other test dotnet invocations rely on to isolate from the repo/machine environment. Consider reusing the existing DotNetCommand/ToolCommand infrastructure or at least merging s_buildEnv.EnvVars into psi.Environment for consistent behavior across runs.

Suggested change
};
};
foreach (KeyValuePair<string, string> envVar in s_buildEnv.EnvVars)
psi.Environment[envVar.Key] = envVar.Value;

Copilot uses AI. Check for mistakes.
maraf and others added 3 commits April 10, 2026 08:51
Remove all TestCategory additions (native, coreclr-native, workload,
mono) from Wasm.Build.Tests, delete BuildWasmAppsJobsListCoreCLR.txt,
and revert the xunit trait filter changes in sendtohelix-browser.targets
and Wasm.Build.Tests.csproj back to the original CoreCLR filter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Change all [TestCategory("coreclr-native")] back to
[TestCategory("native")] and revert the 5 test class entries
added to BuildWasmAppsJobsListCoreCLR.txt.

The native/coreclr-native split and expanded job list will be
re-introduced in a follow-up PR with proper Helix payload support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 10, 2026 09:01
maraf and others added 3 commits April 10, 2026 09:04
…to main

These WBT files should not be changed in this PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove class-level [TestCategory("native")] that should not be in this PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment on lines +322 to +328
<!-- Locate System.Private.CoreLib in runtime pack if not already bundled -->
<PropertyGroup>
<_HasCoreLib Condition="'%(_WasmManagedAssemblies.FileName)%(_WasmManagedAssemblies.Extension)' == 'System.Private.CoreLib.dll'">true</_HasCoreLib>
<!-- CoreCLR WASM ships CoreLib in the native directory, not the lib directory -->
<_CoreLibPath Condition="'$(_HasCoreLib)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)System.Private.CoreLib.dll</_CoreLibPath>
<_CoreLibPath Condition="'$(_HasCoreLib)' != 'true' and !Exists('$(_CoreLibPath)')">$([System.IO.Path]::Combine($(MicrosoftNetCoreAppRuntimePackRidLibTfmDir), 'System.Private.CoreLib.dll'))</_CoreLibPath>
</PropertyGroup>
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

_HasCoreLib is computed by comparing '%(_WasmManagedAssemblies.FileName)%(_WasmManagedAssemblies.Extension)' to a single filename, but when multiple assemblies are present this expression becomes a semicolon-separated list and the condition will never be true. This makes the "if not already bundled" logic ineffective and can lead to duplicate CoreLib entries in the scan list. Consider computing this via an ItemGroup filter + Count() check (or similar) instead.

Copilot uses AI. Check for mistakes.
@github-actions

This comment has been minimized.

Copilot AI review requested due to automatic review settings April 10, 2026 09:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs:373

  • EnsureXHarnessAvailable() can run an expensive dotnet tool restore, but RunHost.DotnetRun doesn’t invoke xharness (it runs dotnet run). Calling it unconditionally adds avoidable overhead and can fail in environments where tool restore isn’t possible even though xharness isn’t needed. Consider invoking EnsureXHarnessAvailable() only for hosts that actually use xharness (e.g., the WebServer branch).
    {
        if (EnvironmentVariables.UseJavascriptBundler)
        {
            runOptions = runOptions with { CustomBundleDir = Path.GetFullPath(Path.Combine(GetBinFrameworkDir(runOptions.Configuration, forPublish: true), "..", "public")) };
        }

        EnsureXHarnessAvailable();

        return runOptions.Host switch
        {
            RunHost.DotnetRun =>
                    await BrowserRunTest($"run -c {runOptions.Configuration} --no-build", _projectDir, runOptions),

            RunHost.WebServer =>
                    await BrowserRunTest($"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files",
                        string.IsNullOrEmpty(runOptions.CustomBundleDir) ?
                            Path.GetFullPath(Path.Combine(GetBinFrameworkDir(runOptions.Configuration, forPublish: true), "..")) :
                            runOptions.CustomBundleDir,
                         runOptions),

Comment on lines +164 to +170
<!-- Resolve managed assembly TFM directory -->
<ItemGroup Condition="'$(MicrosoftNetCoreAppRuntimePackRidLibTfmDir)' == ''">
<_SystemRuntimePathItem Include="$(MicrosoftNetCoreAppRuntimePackRidDir)lib$(WasmDirSep)net*$(WasmDirSep)System.Runtime.dll" />
</ItemGroup>

<PropertyGroup Condition="'$(MicrosoftNetCoreAppRuntimePackRidLibTfmDir)' == '' and @(_SystemRuntimePathItem->Count()) > 0">
<MicrosoftNetCoreAppRuntimePackRidLibTfmDir>$([System.IO.Path]::GetDirectoryName(%(_SystemRuntimePathItem.Identity)))</MicrosoftNetCoreAppRuntimePackRidLibTfmDir>
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

_CoreCLRWasmInitialize uses $(WasmDirSep) when globbing for System.Runtime.dll, but WasmDirSep doesn’t appear to be defined anywhere else in the repo. If it expands to an empty string, the fallback path becomes invalid and MicrosoftNetCoreAppRuntimePackRidLibTfmDir may never be resolved. Use a standard separator (e.g., \ like other wasm targets in this repo, or $(DirectorySeparatorChar) / NormalizePath) instead of $(WasmDirSep).

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +143
<Error Condition="'$(MicrosoftNetCoreAppRuntimePackDir)' == '' and ('%(ResolvedRuntimePack.PackageDirectory)' == '' or !Exists(%(ResolvedRuntimePack.PackageDirectory)))"
Text="%24(MicrosoftNetCoreAppRuntimePackDir) is not set and cannot find %25(ResolvedRuntimePack.PackageDirectory). One of these must be a valid path." />
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The Exists(%(ResolvedRuntimePack.PackageDirectory)) call is missing quotes around the path. If the runtime pack directory contains spaces, MSBuild condition parsing can fail or evaluate incorrectly. Wrap the metadata in quotes (e.g., Exists('%(ResolvedRuntimePack.PackageDirectory)')) in both the Exists and the error message to make the check robust.

Suggested change
<Error Condition="'$(MicrosoftNetCoreAppRuntimePackDir)' == '' and ('%(ResolvedRuntimePack.PackageDirectory)' == '' or !Exists(%(ResolvedRuntimePack.PackageDirectory)))"
Text="%24(MicrosoftNetCoreAppRuntimePackDir) is not set and cannot find %25(ResolvedRuntimePack.PackageDirectory). One of these must be a valid path." />
<Error Condition="'$(MicrosoftNetCoreAppRuntimePackDir)' == '' and ('%(ResolvedRuntimePack.PackageDirectory)' == '' or !Exists('%(ResolvedRuntimePack.PackageDirectory)'))"
Text="%24(MicrosoftNetCoreAppRuntimePackDir) is not set and cannot find '%25(ResolvedRuntimePack.PackageDirectory)'. One of these must be a valid path." />

Copilot uses AI. Check for mistakes.
<ItemGroup>
<_WasmSourceFileToCompile Remove="@(_WasmSourceFileToCompile)" />
<_WasmSourceFileToCompile Include="@(NativeFileReference)" Condition="'%(Extension)' == '.c' or '%(Extension)' == '.cpp'" />
<_WasmSourceFileToCompile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" />
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

_WasmSourceFileToCompile sets ObjectFile to $(_WasmIntermediateOutputPath)%(FileName).o. This will collide (and overwrite) when two NativeFileReference sources share the same filename in different directories, producing incorrect links. Include additional disambiguation in the object file path (e.g., %(RecursiveDir) or a hashed/normalized identity) to guarantee uniqueness.

Suggested change
<_WasmSourceFileToCompile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" />
<_WasmSourceFileToCompile ObjectFile="$(_WasmIntermediateOutputPath)%(RecursiveDir)%(FileName).o" />

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #125607

Note

This review was generated by GitHub Copilot.

Holistic Assessment

Motivation: Justified. This PR cleans up the test categorization from the CoreCLR WASM native relink work. The coreclr-native category was an intermediate distinction that is no longer needed — consolidating to native simplifies the test infrastructure. The EnsureWasmTemplatesInstalled dead code removal and .research/ file cleanup are appropriate hygiene for a maturing branch.

Approach: Correct. The mechanical rename from coreclr-native to native is consistent with the Helix filtering infrastructure in sendtohelix-browser.targets (line 140: -notrait category=native for CoreCLR). Hoisting method-level [TestCategory("native")] to class-level where all methods share the same category is good practice.

Summary: ✅ LGTM. The test categorization changes are internally consistent — every test class removed from BuildWasmAppsJobsListCoreCLR.txt has all its test methods tagged with categories that are excluded by the CoreCLR xUnit trait filter. The dead code removal is clean and complete with no remaining references. No tests are accidentally lost.


Detailed Findings

✅ Test Category Consolidation — coreclr-native to native

I verified the filtering infrastructure in src/libraries/sendtohelix-browser.targets:140:

<_XUnitTraitArg Condition="'$(RuntimeFlavor)' == 'CoreCLR'">-notrait category=native -notrait category=mono -notrait category=workload</_XUnitTraitArg>

Under CoreCLR, tests tagged native are excluded. Previously coreclr-native was NOT excluded (no -notrait for it). The consolidation means these tests will no longer run under CoreCLR. This is intentional per the commit messages ("Revert coreclr-native categorization (defer to follow-up)").

For Mono runs, the native category is not filtered, so these tests continue to run. No change in Mono test coverage.

✅ CoreCLR Job List Removals — Consistent

I verified each class removed from BuildWasmAppsJobsListCoreCLR.txt:

Removed Class Methods All Excluded?
IcuShardingTests 2 methods, both native ✅ Yes
IcuShardingTests2 1 method, native ✅ Yes
IcuTests 2 native + 1 workload ✅ Yes (all filtered by CoreCLR traits)
NativeBuildTests Class-level native ✅ Yes
Wasm.Build.Templates.Tests.NativeBuildTests Class-level native ✅ Yes

Classes NOT in the CoreCLR list (InvariantGlobalizationTests, InvariantTimezoneTests, MemoryTests) were never in it, so the category rename has no CoreCLR impact.

Blazor tests (BuildPublishTests, MiscTests, SimpleRunTests) also aren't in the CoreCLR job list, so the method-level native tags only affect Mono runs (where native is not filtered).

✅ Dead Code Removal — EnsureWasmTemplatesInstalled

The removed EnsureWasmTemplatesInstalled method, along with s_wasmTemplatesInstalled and s_wasmTemplatesLock, has zero remaining references. The using System.Diagnostics import was only needed by this method. Clean removal.

✅ Class-Level Category Hoisting — DllImportTests, NativeBuildTests, PInvokeTableGeneratorTests

For these three classes, ALL test methods previously had individual [TestCategory("native")] or [TestCategory("coreclr-native")] attributes. Hoisting to class-level is a correct simplification — xUnit applies class-level trait attributes to all methods.

✅ Research Files Cleanup

The .research/ files only exist on this branch (no history on main). Proper cleanup of development artifacts before merge.

Generated by Code Review for issue #125607 ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Build-mono os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants