[wasm] Re-enable source generator tests failing due to OOM (#60701)
authorAnkit Jain <radical@gmail.com>
Tue, 2 Nov 2021 20:16:25 +0000 (20:16 +0000)
committerGitHub <noreply@github.com>
Tue, 2 Nov 2021 20:16:25 +0000 (16:16 -0400)
This adds support for setting per-project optimization flags, and sets them for these projects so they don't OOM.

Microsoft.Extensions.Logging.Generators.Roslyn3.11.Tests
Microsoft.Extensions.Logging.Generators.Roslyn4.0.Tests
System.Text.Json.SourceGeneration.Roslyn3.11.Unit.Tests
System.Text.Json.SourceGeneration.Roslyn4.0.Unit.Tests
System.Text.RegularExpressions.Generators.Tests
Some individual ones are disabled due to
#58226, and #60899 .

Additionally, if a AOT build fails, then on linux it dumps the last few lines from dmesg, to help identify it was an oom-kill.

Fixes #51961 .

Co-authored-by: Larry Ewing <lewing@microsoft.com>
17 files changed:
eng/testing/WasmRunnerAOTTemplate.sh [new file with mode: 0644]
eng/testing/tests.targets
eng/testing/tests.wasm.targets
src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Microsoft.Extensions.Logging.Generators.Roslyn3.11.Tests.csproj
src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/Microsoft.Extensions.Logging.Generators.Roslyn4.0.Tests.csproj
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Unit.Tests.csproj
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/System.Text.Json.SourceGeneration.Roslyn4.0.Unit.Tests.csproj
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/TypeWrapperTests.cs
src/libraries/System.Text.RegularExpressions/tests/RegexCultureTests.cs
src/libraries/System.Text.RegularExpressions/tests/System.Text.RegularExpressions.Generators.Tests/System.Text.RegularExpressions.Generators.Tests.csproj
src/libraries/tests.proj
src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj
src/mono/wasm/build/WasmApp.Native.targets
src/tasks/AotCompilerTask/MonoAOTCompiler.cs
src/tasks/WasmAppBuilder/WasmAppBuilder.cs

diff --git a/eng/testing/WasmRunnerAOTTemplate.sh b/eng/testing/WasmRunnerAOTTemplate.sh
new file mode 100644 (file)
index 0000000..6922b55
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+EXECUTION_DIR=$(dirname $0)
+SCENARIO=$3
+
+cd $EXECUTION_DIR
+
+if [ -z "$HELIX_WORKITEM_UPLOAD_ROOT" ]; then
+       XHARNESS_OUT="$EXECUTION_DIR/xharness-output"
+else
+       XHARNESS_OUT="$HELIX_WORKITEM_UPLOAD_ROOT/xharness-output"
+fi
+
+if [ ! -z "$XHARNESS_CLI_PATH" ]; then
+       # When running in CI, we only have the .NET runtime available
+       # We need to call the XHarness CLI DLL directly via dotnet exec
+       HARNESS_RUNNER="dotnet exec $XHARNESS_CLI_PATH"
+else
+       HARNESS_RUNNER="dotnet xharness"
+fi
+
+if [ "$SCENARIO" == "WasmTestOnBrowser" ]; then
+       XHARNESS_COMMAND="test-browser"
+elif [ -z "$XHARNESS_COMMAND" ]; then
+       XHARNESS_COMMAND="test"
+fi
+
+function _buildAOTFunc()
+{
+       local projectFile=$1
+       local binLog=$2
+       shift 2
+
+       time dotnet msbuild $projectFile /bl:$binLog $*
+       local buildExitCode=$?
+
+       echo "\n** Performance summary for the build **\n"
+       dotnet msbuild $binLog -clp:PerformanceSummary -v:q -nologo
+       if [[ "$(uname -s)" == "Linux" && $buildExitCode -ne 0 ]]; then
+               echo "\nLast few messages from dmesg:\n"
+               dmesg | tail -n 20
+       fi
+
+       echo
+       echo
+
+       return $buildExitCode
+}
+
+# RunCommands defined in tests.mobile.targets
+[[RunCommands]]
+
+_exitCode=$?
+
+echo "XHarness artifacts: $XHARNESS_OUT"
+
+exit $_exitCode
index c59ecf9..1d14775 100644 (file)
@@ -5,7 +5,8 @@
     <RunScriptInputName Condition="'$(BuildTestsOnHelix)' == 'true' and '$(TargetsAppleMobile)' == 'true'">AppleHelixRunnerTemplate.sh</RunScriptInputName>
     <RunScriptInputName Condition="'$(BuildTestsOnHelix)' != 'true' and '$(TargetsAppleMobile)' == 'true'">AppleRunnerTemplate.sh</RunScriptInputName>
     <RunScriptInputName Condition="'$(TargetOS)' == 'Android'">AndroidRunnerTemplate.sh</RunScriptInputName>
-    <RunScriptInputName Condition="'$(TargetOS)' == 'Browser' and '$(OS)' != 'Windows_NT'">WasmRunnerTemplate.sh</RunScriptInputName>
+    <RunScriptInputName Condition="'$(TargetOS)' == 'Browser' and '$(OS)' != 'Windows_NT' and '$(BuildAOTTestsOnHelix)' == 'true'">WasmRunnerAOTTemplate.sh</RunScriptInputName>
+    <RunScriptInputName Condition="'$(TargetOS)' == 'Browser' and '$(OS)' != 'Windows_NT' and '$(BuildAOTTestsOnHelix)' != 'true'">WasmRunnerTemplate.sh</RunScriptInputName>
     <RunScriptInputName Condition="'$(TargetOS)' == 'Browser' and '$(OS)' == 'Windows_NT'">WasmRunnerTemplate.cmd</RunScriptInputName>
   </PropertyGroup>
 
index f68b4f9..00aa7b1 100644 (file)
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(BuildAOTTestsOnHelix)' == 'true'">
-    <_AOTBuildCommand>dotnet msbuild publish/ProxyProjectForAOTOnHelix.proj /bl:$XHARNESS_OUT/AOTBuild.binlog</_AOTBuildCommand>
+    <_AOTBuildCommand>_buildAOTFunc publish/ProxyProjectForAOTOnHelix.proj $XHARNESS_OUT/AOTBuild.binlog</_AOTBuildCommand>
 
     <!-- running aot-helix tests locally, so we can test with the same project file as CI -->
     <_AOTBuildCommand Condition="'$(ContinuousIntegrationBuild)' != 'true'">$(_AOTBuildCommand) /p:RuntimeSrcDir=$(RepoRoot) /p:RuntimeConfig=$(Configuration)</_AOTBuildCommand>
 
-    <_AOTBuildCommand>$(_AOTBuildCommand) /p:RunAOTCompilation=$(RunAOTCompilation) /p:EmccLinkOptimizationFlag='-Oz -Wl%252C-O0 -Wl%252C-lto-O0'</_AOTBuildCommand>
+    <_AOTBuildCommand>$(_AOTBuildCommand) /p:RunAOTCompilation=$(RunAOTCompilation)</_AOTBuildCommand>
     <_AOTBuildCommand>$(_AOTBuildCommand) &amp;&amp; cd wasm_build/AppBundle</_AOTBuildCommand>
 
     <RunScriptCommand Condition="'$(RunScriptCommand)' == ''">$(_AOTBuildCommand)</RunScriptCommand>
              AssemblyFile="$(WasmBuildTasksAssemblyPath)" />
 
   <Target Name="_BundleAOTTestWasmAppForHelix" DependsOnTargets="PrepareForWasmBuildApp">
+    <PropertyGroup Condition="'$(IsHighAotMemoryUsageTest)' == 'true' and '$(ContinuousIntegrationBuild)' == 'true'">
+      <DisableParallelEmccCompile Condition="'$(DisableParallelEmccCompile)' == ''">true</DisableParallelEmccCompile>
+      <EmccLinkOptimizationFlag Condition="'$(EmccLinkOptimizationFlag)' == ''">-O2</EmccLinkOptimizationFlag>
+    </PropertyGroup>
+
     <PropertyGroup>
       <_MainAssemblyPath Condition="'%(WasmAssembliesToBundle.FileName)' == $(AssemblyName) and '%(WasmAssembliesToBundle.Extension)' == '.dll'">%(WasmAssembliesToBundle.Identity)</_MainAssemblyPath>
       <RuntimeConfigFilePath>$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</RuntimeConfigFilePath>
+      <EmccLinkOptimizationFlag Condition="'$(EmccLinkOptimizationFlag)' == ''">-Oz -Wl%252C-O0 -Wl%252C-lto-O0</EmccLinkOptimizationFlag>
     </PropertyGroup>
 
     <Error Text="Item WasmAssembliesToBundle is empty. This is likely an authoring error." Condition="@(WasmAssembliesToBundle->Count()) == 0" />
     <!-- To recreate the original project on helix, we need to set the wasm properties also, same as the
          library test project. Eg. $(InvariantGlobalization) -->
     <ItemGroup>
-      <_WasmPropertyNames Include="InvariantGlobalization" />
       <_WasmPropertyNames Include="AOTMode" />
-      <_WasmPropertyNames Include="WasmDebugLevel" />
+      <_WasmPropertyNames Include="AssemblyName" />
+      <_WasmPropertyNames Include="DisableParallelAot" />
+      <_WasmPropertyNames Include="DisableParallelEmccCompile" />
+      <_WasmPropertyNames Include="EmccCompileOptimizationFlag" />
+      <_WasmPropertyNames Include="EmccLinkOptimizationFlag" />
+      <_WasmPropertyNames Include="IncludeSatelliteAssembliesInVFS" />
+      <_WasmPropertyNames Include="InvariantGlobalization" />
       <_WasmPropertyNames Include="WasmBuildNative" />
-      <_WasmPropertyNames Include="_WasmDevel" />
-      <_WasmPropertyNames Include="WasmLinkIcalls" />
+      <_WasmPropertyNames Include="WasmDebugLevel" />
       <_WasmPropertyNames Include="WasmDedup" />
-      <_WasmPropertyNames Include="IncludeSatelliteAssembliesInVFS" />
-      <_WasmPropertyNames Include="AssemblyName" />
+      <_WasmPropertyNames Include="WasmLinkIcalls" />
+      <_WasmPropertyNames Include="WasmNativeStrip" />
+      <_WasmPropertyNames Include="_WasmDevel" />
 
       <_WasmPropertiesToPass
         Include="$(%(_WasmPropertyNames.Identity))"
       <WasmDebugLevel Condition="'$(DebuggerSupport)' == 'true' and '$(WasmDebugLevel)' == ''">-1</WasmDebugLevel>
     </PropertyGroup>
 
-    <ItemGroup Condition="'$(IncludeSatelliteAssembliesInVFS)' == 'true'">
+    <ItemGroup Condition="'$(IncludeSatelliteAssembliesInVFS)' == 'true' and '$(BuildAOTTestsOnHelix)' != 'true'">
       <_SatelliteAssemblies Include="$(PublishDir)*\*.resources.dll" />
       <_SatelliteAssemblies CultureName="$([System.IO.Directory]::GetParent('%(Identity)').Name)" />
       <_SatelliteAssemblies TargetPath="%(CultureName)\%(FileName)%(Extension)" />
index 1c826c0..074da27 100644 (file)
@@ -3,6 +3,9 @@
   <PropertyGroup>
     <RoslynApiVersion>$(MicrosoftCodeAnalysisCSharpWorkspacesVersion)</RoslynApiVersion>
     <DefineConstants>$(DefineConstants);ROSLYN4_0_OR_GREATER</DefineConstants>
+    <IsHighAotMemoryUsageTest>true</IsHighAotMemoryUsageTest>
+    <EmccLinkOptimizationFlag Condition="'$(ContinuousIntegrationBuild)' == 'true'">-O1</EmccLinkOptimizationFlag>
+    <WasmNativeStrip>false</WasmNativeStrip>
   </PropertyGroup>
 
   <Import Project="Microsoft.Extensions.Logging.Generators.targets"/>
index 470c834..a25196a 100644 (file)
@@ -125,6 +125,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
         }
 
         [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
         public void NameClashSourceGeneration()
         {
             // Without resolution.
@@ -149,6 +150,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
         }
 
         [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
         public void ProgramsThatDontUseGeneratorCompile()
         {
             // No STJ usage.
@@ -192,6 +194,7 @@ public class Program
         }
 
         [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
         public void WarnOnClassesWithInitOnlyProperties()
         {
             Compilation compilation = CompilationHelper.CreateCompilationWithInitOnlyProperties();
@@ -206,6 +209,7 @@ public class Program
         }
 
         [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
         public void WarnOnClassesWithInaccessibleJsonIncludeProperties()
         {
             Compilation compilation = CompilationHelper.CreateCompilationWithInaccessibleJsonIncludeProperties();
index 105dc91..08519e1 100644 (file)
@@ -11,6 +11,7 @@ using Xunit;
 
 namespace System.Text.Json.SourceGeneration.UnitTests
 {
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
     public class GeneratorTests
     {
         [Fact]
index 073d9ca..709f667 100644 (file)
@@ -2,6 +2,7 @@
   <PropertyGroup>
     <RoslynApiVersion>$(MicrosoftCodeAnalysisCSharpWorkspacesVersion)</RoslynApiVersion>
     <DefineConstants>$(DefineConstants);ROSLYN4_0_OR_GREATER</DefineConstants>
+    <IsHighAotMemoryUsageTest>true</IsHighAotMemoryUsageTest>
   </PropertyGroup>
 
   <Import Project="System.Text.Json.SourceGeneration.Unit.Tests.targets" />
index 24a8613..ca6d9dc 100644 (file)
@@ -14,6 +14,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
     public class TypeWrapperTests
     {
         [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
         public void MetadataLoadFilePathHandle()
         {
             // Create a MetadataReference from new code.
@@ -79,6 +80,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
         }
 
         [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
         public void CanGetAttributes()
         {
             string source = @"
index ac0bc82..adcde90 100644 (file)
@@ -325,6 +325,7 @@ namespace System.Text.RegularExpressions.Tests
         public static IEnumerable<object[]> Match_In_Different_Cultures_CriticalCases_TestData() =>
             Match_In_Different_Cultures_CriticalCases_TestData_For(RegexOptions.None).Union(Match_In_Different_Cultures_CriticalCases_TestData_For(RegexOptions.Compiled));
 
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/60899", TestPlatforms.Browser)]
         [Theory]
         [MemberData(nameof(Match_In_Different_Cultures_TestData))]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/60697", TestPlatforms.iOS | TestPlatforms.tvOS)]
index d748cea..ce8b725 100644 (file)
@@ -14,9 +14,6 @@
   </PropertyGroup>
 
   <ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(BuildAOTTestsOnHelix)' == 'true' and '$(RunDisabledWasmTests)' != 'true' and '$(RunAOTCompilation)' == 'true'">
-    <!-- Exceeds VM resources in CI on compilation: https://github.com/dotnet/runtime/issues/51961 -->
-    <ProjectExclusions Include="$(MSBuildThisFileDirectory)Microsoft.Extensions.Logging.Abstractions\tests\Microsoft.Extensions.Logging.Generators.Tests\Microsoft.Extensions.Logging.Generators.Roslyn3.11.Tests.csproj" />
-    <ProjectExclusions Include="$(MSBuildThisFileDirectory)Microsoft.Extensions.Logging.Abstractions\tests\Microsoft.Extensions.Logging.Generators.Tests\Microsoft.Extensions.Logging.Generators.Roslyn4.0.Tests.csproj" />
   </ItemGroup>
 
   <!-- Projects that don't support code coverage measurement. -->
 
     <!-- https://github.com/dotnet/runtime/issues/58226 -->
     <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Runtime.InteropServices\tests\DllImportGenerator.UnitTests\DllImportGenerator.Unit.Tests.csproj" />
-    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.Json\tests\System.Text.Json.SourceGeneration.Unit.Tests\System.Text.Json.SourceGeneration.Roslyn3.11.Unit.Tests.csproj" />
-    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.Json\tests\System.Text.Json.SourceGeneration.Unit.Tests\System.Text.Json.SourceGeneration.Roslyn4.0.Unit.Tests.csproj" />
-
-    <!-- hhttps://github.com/dotnet/runtime/issues/60048 -->
-    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.RegularExpressions\tests\System.Text.RegularExpressions.Tests.csproj" />
-    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.RegularExpressions\tests\System.Text.RegularExpressions.Generators.Tests\System.Text.RegularExpressions.Generators.Tests.csproj" />
   </ItemGroup>
 
   <!-- Aggressive Trimming related failures -->
index 01b36c0..ff9f990 100644 (file)
@@ -1,6 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <WasmCopyAppZipToHelixTestDir Condition="'$(ArchiveTests)' == 'true'">true</WasmCopyAppZipToHelixTestDir>
+    <!-- don't need to run this on helix -->
+    <WasmCopyAppZipToHelixTestDir>false</WasmCopyAppZipToHelixTestDir>
     <WasmMainJSPath>runtime.js</WasmMainJSPath>
   </PropertyGroup>
 
index 69facc5..8476977 100644 (file)
       <_WasmPInvokeTablePath>$(_WasmIntermediateOutputPath)pinvoke-table.h</_WasmPInvokeTablePath>
       <_WasmPInvokeHPath>$(_WasmRuntimePackIncludeDir)wasm\pinvoke.h</_WasmPInvokeHPath>
       <_DriverGenCPath>$(_WasmIntermediateOutputPath)driver-gen.c</_DriverGenCPath>
+      <DisableParallelAot Condition="'$(DisableParallelAot)' == ''">false</DisableParallelAot>
+      <DisableParallelEmccCompile Condition="'$(DisableParallelEmccCompile)' == ''">$(DisableParallelAot)</DisableParallelEmccCompile>
 
       <_DriverGenCNeeded Condition="'$(_DriverGenCNeeded)' == '' and '$(_WasmShouldAOT)' == 'true'">true</_DriverGenCNeeded>
 
     <!-- warm up the cache -->
     <Exec Command="$(_EmBuilder) build MINIMAL" EnvironmentVariables="@(EmscriptenEnvVars)" StandardOutputImportance="Low" StandardErrorImportance="Low" />
 
-    <Message Text="Compiling native assets with emcc. This may take a while ..." Importance="High" />
+    <Message Text="Compiling native assets with emcc with $(EmccCompileOptimizationFlag). This may take a while ..." Importance="High" />
     <ItemGroup>
       <_WasmSourceFileToCompile Remove="@(_WasmSourceFileToCompile)" />
       <_WasmSourceFileToCompile Include="@(_WasmRuntimePackSrcFile)" Dependencies="%(_WasmRuntimePackSrcFile.Dependencies);$(_EmccDefaultFlagsRsp);$(_EmccCompileRsp)" />
           SourceFiles="@(_WasmSourceFileToCompile)"
           Arguments='"@$(_EmccDefaultFlagsRsp)" "@$(_EmccCompileRsp)"'
           EnvironmentVariables="@(EmscriptenEnvVars)"
+          DisableParallelCompile="$(DisableParallelEmccCompile)"
           OutputMessageImportance="$(_EmccCompileOutputMessageImportance)">
       <Output TaskParameter="OutputFiles" ItemName="FileWrites" />
     </EmccCompile>
       <_BitCodeFile Dependencies="%(_BitCodeFile.Dependencies);$(_EmccDefaultFlagsRsp);$(_EmccCompileBitcodeRsp)" />
     </ItemGroup>
 
-    <Message Text="Compiling assembly bitcode files..." Importance="High" Condition="@(_BitCodeFile->Count()) > 0" />
+    <Message Text="Compiling assembly bitcode files with $(EmccLinkOptimizationFlag) ..." Importance="High" Condition="@(_BitCodeFile->Count()) > 0" />
     <EmccCompile
           SourceFiles="@(_BitCodeFile)"
           Arguments="&quot;@$(_EmccDefaultFlagsRsp)&quot; &quot;@$(_EmccCompileBitcodeRsp)&quot;"
           EnvironmentVariables="@(EmscriptenEnvVars)"
+          DisableParallelCompile="$(DisableParallelEmccCompile)"
           OutputMessageImportance="$(_EmccCompileOutputMessageImportance)">
       <Output TaskParameter="OutputFiles" ItemName="FileWrites" />
     </EmccCompile>
           DependsOnTargets="_WasmSelectRuntimeComponentsForLinking;_WasmCompileAssemblyBitCodeFilesForAOT;_WasmWriteRspFilesForLinking"
           Returns="@(FileWrites)" >
 
-    <Message Text="Linking with emcc. This may take a while ..." Importance="High" />
+    <Message Text="Linking with emcc with $(EmccLinkOptimizationFlag). This may take a while ..." Importance="High" />
     <Message Text="Running emcc with @(_EmccLinkStepArgs->'%(Identity)', ' ')" Importance="Low" />
     <Exec Command='emcc "@$(_EmccDefaultFlagsRsp)" "@$(_EmccLinkRsp)"' EnvironmentVariables="@(EmscriptenEnvVars)" />
     <ItemGroup>
index e8c3680..dee6b53 100644 (file)
@@ -395,7 +395,6 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
                                             new ParallelOptions { MaxDegreeOfParallelism = allowedParallelism },
                                             (args, state) => PrecompileLibraryParallel(args, state));
 
-            Log.LogMessage(MessageImportance.High, $"result: {result.IsCompleted}");
             if (result.IsCompleted)
             {
                 int numUnchanged = _totalNumAssemblies - _numCompiled;
index e356291..4c3054c 100644 (file)
@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
@@ -222,6 +223,7 @@ public class WasmAppBuilder : Task
             Directory.CreateDirectory(supportFilesDir);
 
             var i = 0;
+            StringDictionary targetPathTable = new();
             foreach (var item in FilesToIncludeInFileSystem)
             {
                 string? targetPath = item.GetMetadata("TargetPath");
@@ -232,6 +234,21 @@ public class WasmAppBuilder : Task
 
                 // We normalize paths from `\` to `/` as MSBuild items could use `\`.
                 targetPath = targetPath.Replace('\\', '/');
+                if (targetPathTable.ContainsKey(targetPath))
+                {
+                    string firstPath = Path.GetFullPath(targetPathTable[targetPath]!);
+                    string secondPath = Path.GetFullPath(item.ItemSpec);
+
+                    if (firstPath == secondPath)
+                    {
+                        Log.LogWarning($"Found identical vfs mappings for target path: {targetPath}, source file: {firstPath}. Ignoring.");
+                        continue;
+                    }
+
+                    throw new LogAsErrorException($"Found more than one file mapping to the target VFS path: {targetPath}. Source files: {firstPath}, and {secondPath}");
+                }
+
+                targetPathTable[targetPath] = item.ItemSpec;
 
                 var generatedFileName = $"{i++}_{Path.GetFileName(item.ItemSpec)}";