[wasm] Build improvements, and fixes (#61581)
authorAnkit Jain <radical@gmail.com>
Sat, 4 Dec 2021 06:48:02 +0000 (06:48 +0000)
committerGitHub <noreply@github.com>
Sat, 4 Dec 2021 06:48:02 +0000 (01:48 -0500)
Includes:
- Better error logging, and handling
- Add @(NativeFileReference) to up-to-date check items for VS
- Add `WasmBuild.sln`
- Better fix up of symbol names for pinvokes, and callbacks, based on @lambdageek's suggestion in https://github.com/dotnet/runtime/pull/60814#discussion_r736903990

Fixes dotnet#60862

21 files changed:
eng/testing/tests.wasm.targets
src/libraries/tests.proj
src/mono/wasm/build/WasmApp.Native.targets
src/mono/wasm/build/WasmApp.targets
src/mono/wasm/sln/WasmBuild.sln [new file with mode: 0755]
src/mono/wasm/wasm.proj
src/tasks/AotCompilerTask/MonoAOTCompiler.cs
src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/BuildPublishTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/InvariantGlobalizationTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/LocalEMSDKTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/MainWithArgsTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/RebuildTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/SatelliteAssembliesTests.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs
src/tests/BuildWasmApps/Wasm.Build.Tests/WasmNativeDefaultsTests.cs

index ca2d51a..9f8b2ea 100644 (file)
@@ -13,6 +13,7 @@
     <!-- Run only if previous command succeeded -->
     <_ShellCommandSeparator Condition="'$(OS)' == 'Windows_NT'">&amp;&amp;</_ShellCommandSeparator>
     <_ShellCommandSeparator Condition="'$(OS)' != 'Windows_NT'">&amp;&amp;</_ShellCommandSeparator>
+    <WasmNativeStrip>false</WasmNativeStrip>
   </PropertyGroup>
 
   <PropertyGroup>
index 592a83a..1dfa559 100644 (file)
     <TestTrimming Condition="'$(TestTrimming)' == ''">false</TestTrimming>
   </PropertyGroup>
 
-  <ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(BuildAOTTestsOnHelix)' == 'true' and '$(RunDisabledWasmTests)' != 'true' and '$(RunAOTCompilation)' == 'true'">
+  <!-- Wasm aot on !windows -->
+  <ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(BuildAOTTestsOnHelix)' == 'true' and '$(RunDisabledWasmTests)' != 'true' and '$(RunAOTCompilation)' == 'true' and '$(BrowserHost)' != 'Windows'">
     <!-- Exceeds VM resources in CI on compilation: https://github.com/dotnet/runtime/issues/61339 -->
     <ProjectExclusions Include="$(MSBuildThisFileDirectory)Microsoft.Extensions.Logging.Abstractions\tests\Microsoft.Extensions.Logging.Generators.Tests\Microsoft.Extensions.Logging.Generators.Roslyn3.11.Tests.csproj" />
 
     <!-- https://github.com/dotnet/runtime/issues/61756 -->
     <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.RegularExpressions\tests\System.Text.RegularExpressions.Tests.csproj" />
+
+    <!-- https://github.com/dotnet/runtime/issues/62362 -->
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Collections.Immutable\tests\System.Collections.Immutable.Tests.csproj" />
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Collections\tests\System.Collections.Tests.csproj" />
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.IO.Pipelines\tests\System.IO.Pipelines.Tests.csproj" />
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Linq.Parallel\tests\System.Linq.Parallel.Tests.csproj" />
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.TypeExtensions\tests\System.Reflection.TypeExtensions.Tests.csproj" />
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.Json\tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj" />
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.Json\tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Roslyn4.0.Tests.csproj" />
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.Json\tests\System.Text.Json.Tests\System.Text.Json.Tests.csproj" />
+    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.ValueTuple\tests\System.ValueTuple.Tests.csproj" />
   </ItemGroup>
 
   <!-- Projects that don't support code coverage measurement. -->
index 9fdf3c3..ea282f9 100644 (file)
     <WasmUseEMSDK_PATH Condition="'$(WasmUseEMSDK_PATH)' == '' and '$(EMSDK_PATH)' != '' and Exists('$(MSBuildThisFileDirectory)WasmApp.InTree.targets')">true</WasmUseEMSDK_PATH>
   </PropertyGroup>
 
+  <ItemGroup>
+    <UpToDateCheckInput Include="@(NativeFileReference)" />
+  </ItemGroup>
+
   <ItemGroup Condition="'$(Configuration)' == 'Debug' and '@(_MonoComponent->Count())' == 0">
     <_MonoComponent Include="hot_reload;debugger" />
   </ItemGroup>
index 4bcaac9..ad4896e 100644 (file)
 
   <Target Name="_InitializeCommonProperties">
     <Error Condition="'$(MicrosoftNetCoreAppRuntimePackDir)' == '' and ('%(ResolvedRuntimePack.PackageDirectory)' == '' or !Exists(%(ResolvedRuntimePack.PackageDirectory)))"
-           Text="Could not find %25(ResolvedRuntimePack.PackageDirectory)=%(ResolvedRuntimePack.PackageDirectory)" />
+           Text="%24(MicrosoftNetCoreAppRuntimePackDir)='', and cannot find %25(ResolvedRuntimePack.PackageDirectory)=%(ResolvedRuntimePack.PackageDirectory). One of these need to be set to a valid path" />
     <Error Condition="'$(IntermediateOutputPath)' == ''" Text="%24(IntermediateOutputPath) property needs to be set" />
 
     <PropertyGroup>
diff --git a/src/mono/wasm/sln/WasmBuild.sln b/src/mono/wasm/sln/WasmBuild.sln
new file mode 100755 (executable)
index 0000000..10256d2
--- /dev/null
@@ -0,0 +1,73 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31722.452
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WasmBuildTasks", "..\..\..\tasks\WasmBuildTasks\WasmBuildTasks.csproj", "{D5BD9C0C-8A05-493E-BE45-13AF8286CD92}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WasmAppBuilder", "..\..\..\tasks\WasmAppBuilder\WasmAppBuilder.csproj", "{8DEBFDE2-B127-46D7-92CF-EEA6D1DA2554}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoAOTCompiler", "..\..\..\tasks\AotCompilerTask\MonoAOTCompiler.csproj", "{A9C02284-0387-42E7-BF78-47DF13656D5E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Build.Tests", "..\..\..\tests\BuildWasmApps\Wasm.Build.Tests\Wasm.Build.Tests.csproj", "{94E18644-B0E5-4DBB-9CE4-EA1515ACE4C2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DebuggerTestSuite", "..\debugger\DebuggerTestSuite\DebuggerTestSuite.csproj", "{4C0EE027-FC30-4167-B2CF-A6D18F00E08F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserDebugHost", "..\debugger\BrowserDebugHost\BrowserDebugHost.csproj", "{292A88FD-795F-467A-8801-B5B791CEF96E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserDebugProxy", "..\debugger\BrowserDebugProxy\BrowserDebugProxy.csproj", "{F5AE2AF5-3C30-45E3-A0C6-D962C51FE5E7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WasmAppHost", "..\host\WasmAppHost.csproj", "{C7099764-EC2E-4FAF-9057-0321893DE4F8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplyUpdateReferencedAssembly", "..\debugger\tests\ApplyUpdateReferencedAssembly\ApplyUpdateReferencedAssembly.csproj", "{75477B6F-DC8E-4002-88B8-017C992C568E}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|Any CPU = Debug|Any CPU
+               Release|Any CPU = Release|Any CPU
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {D5BD9C0C-8A05-493E-BE45-13AF8286CD92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {D5BD9C0C-8A05-493E-BE45-13AF8286CD92}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {D5BD9C0C-8A05-493E-BE45-13AF8286CD92}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {D5BD9C0C-8A05-493E-BE45-13AF8286CD92}.Release|Any CPU.Build.0 = Release|Any CPU
+               {8DEBFDE2-B127-46D7-92CF-EEA6D1DA2554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {8DEBFDE2-B127-46D7-92CF-EEA6D1DA2554}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {8DEBFDE2-B127-46D7-92CF-EEA6D1DA2554}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {8DEBFDE2-B127-46D7-92CF-EEA6D1DA2554}.Release|Any CPU.Build.0 = Release|Any CPU
+               {A9C02284-0387-42E7-BF78-47DF13656D5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {A9C02284-0387-42E7-BF78-47DF13656D5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {A9C02284-0387-42E7-BF78-47DF13656D5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {A9C02284-0387-42E7-BF78-47DF13656D5E}.Release|Any CPU.Build.0 = Release|Any CPU
+               {94E18644-B0E5-4DBB-9CE4-EA1515ACE4C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {94E18644-B0E5-4DBB-9CE4-EA1515ACE4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {94E18644-B0E5-4DBB-9CE4-EA1515ACE4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {94E18644-B0E5-4DBB-9CE4-EA1515ACE4C2}.Release|Any CPU.Build.0 = Release|Any CPU
+               {4C0EE027-FC30-4167-B2CF-A6D18F00E08F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {4C0EE027-FC30-4167-B2CF-A6D18F00E08F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {4C0EE027-FC30-4167-B2CF-A6D18F00E08F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {4C0EE027-FC30-4167-B2CF-A6D18F00E08F}.Release|Any CPU.Build.0 = Release|Any CPU
+               {292A88FD-795F-467A-8801-B5B791CEF96E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {292A88FD-795F-467A-8801-B5B791CEF96E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {292A88FD-795F-467A-8801-B5B791CEF96E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {292A88FD-795F-467A-8801-B5B791CEF96E}.Release|Any CPU.Build.0 = Release|Any CPU
+               {F5AE2AF5-3C30-45E3-A0C6-D962C51FE5E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {F5AE2AF5-3C30-45E3-A0C6-D962C51FE5E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {F5AE2AF5-3C30-45E3-A0C6-D962C51FE5E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {F5AE2AF5-3C30-45E3-A0C6-D962C51FE5E7}.Release|Any CPU.Build.0 = Release|Any CPU
+               {75477B6F-DC8E-4002-88B8-017C992C568E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {75477B6F-DC8E-4002-88B8-017C992C568E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {75477B6F-DC8E-4002-88B8-017C992C568E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {75477B6F-DC8E-4002-88B8-017C992C568E}.Release|Any CPU.Build.0 = Release|Any CPU
+               {C7099764-EC2E-4FAF-9057-0321893DE4F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {C7099764-EC2E-4FAF-9057-0321893DE4F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {C7099764-EC2E-4FAF-9057-0321893DE4F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {C7099764-EC2E-4FAF-9057-0321893DE4F8}.Release|Any CPU.Build.0 = Release|Any CPU
+       EndGlobalSection
+       GlobalSection(SolutionProperties) = preSolution
+               HideSolutionNode = FALSE
+       EndGlobalSection
+       GlobalSection(ExtensibilityGlobals) = postSolution
+               SolutionGuid = {2BDE8FDE-4261-4B4D-8B54-ACC88B06C8D1}
+       EndGlobalSection
+EndGlobal
index 0a8e59d..5340cef 100644 (file)
@@ -12,6 +12,7 @@
     <EmccCmd>emcc</EmccCmd>
     <WasmObjDir>$(ArtifactsObjDir)wasm</WasmObjDir>
     <_EmccDefaultsRspPath>$(NativeBinDir)src\emcc-default.rsp</_EmccDefaultsRspPath>
+    <WasmNativeStrip Condition="'$(ContinuousIntegrationBuild)' == 'true'">false</WasmNativeStrip>
   </PropertyGroup>
 
   <Target Name="CheckEnv">
index dee6b53..701edb6 100644 (file)
@@ -744,13 +744,13 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task
                 Log.LogMessage(importance, $"{msgPrefix}Exec (with response file contents expanded) in {args.WorkingDir}: {envStr}{CompilerBinaryPath} {File.ReadAllText(args.ResponseFilePath)}");
             }
 
-            Log.LogMessage(importance, output);
-
             if (exitCode != 0)
             {
-                Log.LogError($"Precompiling failed for {assembly}");
+                Log.LogError($"Precompiling failed for {assembly}.{Environment.NewLine}{output}");
                 return false;
             }
+
+            Log.LogMessage(importance, output);
         }
         catch (Exception ex)
         {
index 8170c0f..9526a60 100644 (file)
@@ -24,7 +24,7 @@ public class PInvokeTableGenerator : Task
     [Output]
     public string FileWrites { get; private set; } = string.Empty;
 
-    private static char[] s_charsToReplace = new[] { '.', '-', };
+    private static char[] s_charsToReplace = new[] { '.', '-', '+' };
 
     public override bool Execute()
     {
@@ -88,7 +88,21 @@ public class PInvokeTableGenerator : Task
 
     private void CollectPInvokes(List<PInvoke> pinvokes, List<PInvokeCallback> callbacks, Type type)
     {
-        foreach (var method in type.GetMethods(BindingFlags.DeclaredOnly|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance)) {
+        foreach (var method in type.GetMethods(BindingFlags.DeclaredOnly|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance))
+        {
+            try
+            {
+                CollectPInvokesForMethod(method);
+            }
+            catch (Exception ex)
+            {
+                Log.LogMessage(MessageImportance.Low, $"Could not get pinvoke, or callbacks for method {method.Name}: {ex}");
+                continue;
+            }
+        }
+
+        void CollectPInvokesForMethod(MethodInfo method)
+        {
             if ((method.Attributes & MethodAttributes.PinvokeImpl) != 0)
             {
                 var dllimport = method.CustomAttributes.First(attr => attr.AttributeType.Name == "DllImportAttribute");
@@ -164,7 +178,8 @@ public class PInvokeTableGenerator : Task
                 Where(l => l.Module == module && !l.Skip).
                 OrderBy(l => l.EntryPoint).
                 GroupBy(d => d.EntryPoint).
-                Select (l => "{\"" + l.Key + "\", " + l.Key + "}, // " + string.Join (", ", l.Select(c => c.Method.DeclaringType!.Module!.Assembly!.GetName ()!.Name!).Distinct().OrderBy(n => n)));
+                Select (l => "{\"" + FixupSymbolName(l.Key) + "\", " + FixupSymbolName(l.Key) + "}, " +
+                                "// " + string.Join (", ", l.Select(c => c.Method.DeclaringType!.Module!.Assembly!.GetName ()!.Name!).Distinct().OrderBy(n => n)));
 
             foreach (var pinvoke in assemblies_pinvokes) {
                 w.WriteLine (pinvoke);
@@ -216,6 +231,45 @@ public class PInvokeTableGenerator : Task
         }
     }
 
+    private static string FixupSymbolName(string name)
+    {
+        UTF8Encoding utf8 = new();
+        byte[] bytes = utf8.GetBytes(name);
+        StringBuilder sb = new();
+
+        foreach (byte b in bytes)
+        {
+            if ((b >= (byte)'0' && b <= (byte)'9') ||
+                (b >= (byte)'a' && b <= (byte)'z') ||
+                (b >= (byte)'A' && b <= (byte)'Z') ||
+                (b == (byte)'_'))
+            {
+                sb.Append((char) b);
+            }
+            else if (s_charsToReplace.Contains((char) b))
+            {
+                sb.Append('_');
+            }
+            else
+            {
+                sb.Append($"_{b:X}_");
+            }
+        }
+
+        return sb.ToString();
+    }
+
+    private static string SymbolNameForMethod(MethodInfo method)
+    {
+        StringBuilder sb = new();
+        Type? type = method.DeclaringType;
+        sb.Append($"{type!.Module!.Assembly!.GetName()!.Name!}_");
+        sb.Append($"{(type!.IsNested ? type!.FullName : type!.Name)}_");
+        sb.Append(method.Name);
+
+        return FixupSymbolName(sb.ToString());
+    }
+
     private string MapType (Type t)
     {
         string name = t.Name;
@@ -262,7 +316,7 @@ public class PInvokeTableGenerator : Task
         if (method.Name == "EnumCalendarInfo") {
             // FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types
             // https://github.com/dotnet/runtime/issues/43791
-            sb.Append($"int {pinvoke.EntryPoint} (int, int, int, int, int);");
+            sb.Append($"int {FixupSymbolName(pinvoke.EntryPoint)} (int, int, int, int, int);");
             return sb.ToString();
         }
 
@@ -274,7 +328,7 @@ public class PInvokeTableGenerator : Task
         }
 
         sb.Append(MapType(method.ReturnType));
-        sb.Append($" {pinvoke.EntryPoint} (");
+        sb.Append($" {FixupSymbolName(pinvoke.EntryPoint)} (");
         int pindex = 0;
         var pars = method.GetParameters();
         foreach (var p in pars) {
index 42eb966..1e25eb0 100644 (file)
@@ -33,11 +33,14 @@ namespace Wasm.Build.Tests
             // no relinking for build
             bool relinked = false;
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
-                        dotnetWasmFromRuntimePack: !relinked,
                         id: id,
-                        createProject: true,
-                        publish: false);
+                        new BuildProjectOptions(
+                        InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                        DotnetWasmFromRuntimePack: !relinked,
+                        CreateProject: true,
+                        Publish: false
+                        ));
+
 
             Run();
 
@@ -53,10 +56,11 @@ namespace Wasm.Build.Tests
             relinked = buildArgs.Config == "Release";
             BuildProject(buildArgs,
                         id: id,
-                        dotnetWasmFromRuntimePack: !relinked,
-                        createProject: false,
-                        publish: true,
-                        useCache: false);
+                        new BuildProjectOptions(
+                            DotnetWasmFromRuntimePack: !relinked,
+                            CreateProject: false,
+                            Publish: true,
+                            UseCache: false));
 
             Run();
 
@@ -79,12 +83,13 @@ namespace Wasm.Build.Tests
             // no relinking for build
             bool relinked = false;
             (_, string output) = BuildProject(buildArgs,
-                                    initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
-                                    dotnetWasmFromRuntimePack: !relinked,
-                                    id: id,
-                                    createProject: true,
-                                    publish: false,
-                                    label: "first_build");
+                                    id,
+                                    new BuildProjectOptions(
+                                        InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                        DotnetWasmFromRuntimePack: !relinked,
+                                        CreateProject: true,
+                                        Publish: false,
+                                        Label: "first_build"));
 
             BuildPaths paths = GetBuildPaths(buildArgs);
             var pathsDict = GetFilesTable(buildArgs, paths, unchanged: false);
@@ -109,11 +114,12 @@ namespace Wasm.Build.Tests
             // relink by default for Release+publish
             (_, output) = BuildProject(buildArgs,
                                     id: id,
-                                    dotnetWasmFromRuntimePack: false,
-                                    createProject: false,
-                                    publish: true,
-                                    useCache: false,
-                                    label: "first_publish");
+                                    new BuildProjectOptions(
+                                        DotnetWasmFromRuntimePack: false,
+                                        CreateProject: false,
+                                        Publish: true,
+                                        UseCache: false,
+                                        Label: "first_publish"));
 
             var publishStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath));
             Assert.True(publishStat["pinvoke.o"].Exists);
@@ -125,12 +131,13 @@ namespace Wasm.Build.Tests
 
             // second build
             (_, output) = BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
-                        dotnetWasmFromRuntimePack: !relinked,
-                        id: id,
-                        createProject: true,
-                        publish: false,
-                        label: "second_build");
+                                        id: id,
+                                        new BuildProjectOptions(
+                                            InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                            DotnetWasmFromRuntimePack: !relinked,
+                                            CreateProject: true,
+                                            Publish: false,
+                                            Label: "second_build"));
             var secondBuildStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath));
 
             // no relinking, or AOT
index cc4aa85..210ea11 100644 (file)
@@ -280,18 +280,10 @@ namespace Wasm.Build.Tests
 
         public (string projectDir, string buildOutput) BuildProject(BuildArgs buildArgs,
                                   string id,
-                                  Action? initProject = null,
-                                  bool? dotnetWasmFromRuntimePack = null,
-                                  bool hasIcudt = true,
-                                  bool useCache = true,
-                                  bool expectSuccess = true,
-                                  bool createProject = true,
-                                  bool publish = true,
-                                  string? verbosity=null,
-                                  string? label=null)
-        {
-            string msgPrefix = label != null ? $"[{label}] " : string.Empty;
-            if (useCache && _buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
+                                  BuildProjectOptions options)
+        {
+            string msgPrefix = options.Label != null ? $"[{options.Label}] " : string.Empty;
+            if (options.UseCache && _buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
             {
                 Console.WriteLine ($"Using existing build found at {product.ProjectDir}, with build log at {product.LogFile}");
 
@@ -303,33 +295,35 @@ namespace Wasm.Build.Tests
                 return (_projectDir, "FIXME");
             }
 
-            if (createProject)
+            if (options.CreateProject)
             {
                 InitPaths(id);
                 InitProjectDir(_projectDir);
-                initProject?.Invoke();
+                options.InitProject?.Invoke();
 
                 File.WriteAllText(Path.Combine(_projectDir, $"{buildArgs.ProjectName}.csproj"), buildArgs.ProjectFileContents);
                 File.Copy(Path.Combine(AppContext.BaseDirectory, "test-main.js"), Path.Combine(_projectDir, "test-main.js"));
             }
             else if (_projectDir is null)
             {
-                throw new Exception("_projectDir should be set, to use createProject=false");
+                throw new Exception("_projectDir should be set, to use options.createProject=false");
             }
 
             StringBuilder sb = new();
-            sb.Append(publish ? "publish" : "build");
+            sb.Append(options.Publish ? "publish" : "build");
+            if (options.Publish && options.BuildOnlyAfterPublish)
+                sb.Append(" -p:WasmBuildOnlyAfterPublish=true");
             sb.Append($" {s_buildEnv.DefaultBuildArgs}");
 
             sb.Append($" /p:Configuration={buildArgs.Config}");
 
-            string logFileSuffix = label == null ? string.Empty : label.Replace(' ', '_');
+            string logFileSuffix = options.Label == null ? string.Empty : options.Label.Replace(' ', '_');
             string logFilePath = Path.Combine(_logPath, $"{buildArgs.ProjectName}{logFileSuffix}.binlog");
             _testOutput.WriteLine($"-------- Building ---------");
             _testOutput.WriteLine($"Binlog path: {logFilePath}");
             Console.WriteLine($"Binlog path: {logFilePath}");
             sb.Append($" /bl:\"{logFilePath}\" /nologo");
-            sb.Append($" /fl /flp:\"v:diag,LogFile={logFilePath}.log\" /v:{verbosity ?? "minimal"}");
+            sb.Append($" /fl /flp:\"v:diag,LogFile={logFilePath}.log\" /v:{options.Verbosity ?? "minimal"}");
             if (buildArgs.ExtraBuildArgs != null)
                 sb.Append($" {buildArgs.ExtraBuildArgs} ");
 
@@ -338,26 +332,26 @@ namespace Wasm.Build.Tests
             (int exitCode, string buildOutput) result;
             try
             {
-                result = AssertBuild(sb.ToString(), id, expectSuccess: expectSuccess, envVars: s_buildEnv.EnvVars);
+                result = AssertBuild(sb.ToString(), id, expectSuccess: options.ExpectSuccess, envVars: s_buildEnv.EnvVars);
 
                 //AssertRuntimePackPath(result.buildOutput);
 
                 // check that we are using the correct runtime pack!
 
-                if (expectSuccess)
+                if (options.ExpectSuccess)
                 {
                     string bundleDir = Path.Combine(GetBinDir(config: buildArgs.Config), "AppBundle");
-                    AssertBasicAppBundle(bundleDir, buildArgs.ProjectName, buildArgs.Config, hasIcudt, dotnetWasmFromRuntimePack ?? !buildArgs.AOT);
+                    AssertBasicAppBundle(bundleDir, buildArgs.ProjectName, buildArgs.Config, options.HasIcudt, options.DotnetWasmFromRuntimePack ?? !buildArgs.AOT);
                 }
 
-                if (useCache)
+                if (options.UseCache)
                     _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir, logFilePath, true));
 
                 return (_projectDir, result.buildOutput);
             }
             catch
             {
-                if (useCache)
+                if (options.UseCache)
                     _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir, logFilePath, false));
                 throw;
             }
@@ -860,4 +854,18 @@ namespace Wasm.Build.Tests
     public record BuildProduct(string ProjectDir, string LogFile, bool Result);
     internal record FileStat (bool Exists, DateTime LastWriteTimeUtc, long Length, string FullPath);
     internal record BuildPaths(string ObjWasmDir, string ObjDir, string BinDir, string BundleDir);
- }
+
+    public record BuildProjectOptions
+    (
+        Action? InitProject               = null,
+        bool?   DotnetWasmFromRuntimePack = null,
+        bool    HasIcudt                  = true,
+        bool    UseCache                  = true,
+        bool    ExpectSuccess             = true,
+        bool    CreateProject             = true,
+        bool    Publish                   = true,
+        bool    BuildOnlyAfterPublish     = true,
+        string? Verbosity                 = null,
+        string? Label                     = null
+    );
+}
index 7d66bc3..f34cb9c 100644 (file)
@@ -74,10 +74,11 @@ namespace Wasm.Build.Tests
             ";
 
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
-                        id: id,
-                        dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
-                        hasIcudt: invariantGlobalization == null || invariantGlobalization.Value == false);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
+                                DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
+                                HasIcudt: invariantGlobalization == null || invariantGlobalization.Value == false));
 
             if (invariantGlobalization == true)
             {
index 30ed330..5a85ae9 100644 (file)
@@ -30,9 +30,10 @@ namespace Wasm.Build.Tests
             buildArgs = ExpandBuildArgs(buildArgs);
 
             (_, string buildOutput) = BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
                         id: id,
-                        expectSuccess: false);
+                        new BuildProjectOptions(
+                            InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                            ExpectSuccess: false));
 
             Assert.Matches(errorPattern, buildOutput);
         }
@@ -52,9 +53,10 @@ namespace Wasm.Build.Tests
             buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
 
             (_, string buildOutput) = BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
                         id: id,
-                        expectSuccess: false);
+                        new BuildProjectOptions(
+                            InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                            ExpectSuccess: false));
 
             Assert.Matches(errorPattern, buildOutput);
         }
index 73598d8..2e453ee 100644 (file)
@@ -86,9 +86,10 @@ namespace Wasm.Build.Tests
             Console.WriteLine ($"-- args: {buildArgs}, name: {projectName}");
 
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
-                        id: id,
-                        dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
+                                DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack));
 
             RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42 + args.Length, args: string.Join(' ', args),
                 test: output =>
index 3453f99..304161e 100644 (file)
@@ -32,9 +32,10 @@ namespace Wasm.Build.Tests
             buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
 
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
-                        dotnetWasmFromRuntimePack: false,
-                        id: id);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                DotnetWasmFromRuntimePack: false));
 
             RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42,
                         test: output => {},
@@ -57,10 +58,11 @@ namespace Wasm.Build.Tests
 
             (_, string output) = BuildProject(
                                     buildArgs,
-                                    initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
-                                    dotnetWasmFromRuntimePack: false,
                                     id: id,
-                                    expectSuccess: false);
+                                    new BuildProjectOptions(
+                                        InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                        DotnetWasmFromRuntimePack: false,
+                                        ExpectSuccess: false));
 
             Assert.Contains("Stopping after AOT", output);
         }
@@ -89,9 +91,10 @@ namespace Wasm.Build.Tests
             buildArgs = ExpandBuildArgs(buildArgs, insertAtEnd: printFileTypeTarget);
 
             (_, string output) = BuildProject(buildArgs,
-                                    initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
-                                    dotnetWasmFromRuntimePack: false,
-                                    id: id);
+                                    id: id,
+                                    new BuildProjectOptions(
+                                        InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                        DotnetWasmFromRuntimePack: false));
 
             if (!output.Contains("** wasm-dis exit code: 0"))
                 throw new XunitException($"Expected to successfully run wasm-dis on System.Private.CoreLib.dll.o ."
index eb30628..e8892a9 100644 (file)
@@ -37,8 +37,8 @@ namespace Wasm.Build.Tests
             }
 
             BuildProject(buildArgs,
-                        dotnetWasmFromRuntimePack: false,
-                        id: id);
+                            id: id,
+                            new BuildProjectOptions(DotnetWasmFromRuntimePack: false));
 
             string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0,
                                 test: output => {},
@@ -81,9 +81,10 @@ public class Test
 }";
 
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
-                        dotnetWasmFromRuntimePack: false,
-                        id: id);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
+                                DotnetWasmFromRuntimePack: false));
 
             string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0,
                                 test: output => {},
index cd0d3ac..da32a9f 100644 (file)
@@ -48,11 +48,12 @@ namespace Wasm.Build.NativeRebuild.Tests
         {
             buildArgs = GenerateProjectContents(buildArgs, nativeRelink, invariant, extraProperties);
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
-                        dotnetWasmFromRuntimePack: false,
-                        hasIcudt: !invariant,
-                        id: id,
-                        createProject: true);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
+                                DotnetWasmFromRuntimePack: false,
+                                HasIcudt: !invariant,
+                                CreateProject: true));
 
             RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: RunHost.V8, id: id);
             return (buildArgs, GetBuildPaths(buildArgs));
@@ -80,11 +81,12 @@ namespace Wasm.Build.NativeRebuild.Tests
             Console.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}");
             (_, string output) = BuildProject(buildArgs,
                                             id: id,
-                                            dotnetWasmFromRuntimePack: false,
-                                            hasIcudt: !invariant,
-                                            createProject: false,
-                                            useCache: false,
-                                            verbosity: verbosity);
+                                            new BuildProjectOptions(
+                                                DotnetWasmFromRuntimePack: false,
+                                                HasIcudt: !invariant,
+                                                CreateProject: false,
+                                                UseCache: false,
+                                                Verbosity: verbosity));
 
             return output;
         }
index 27783ae..feada6b 100644 (file)
@@ -123,15 +123,16 @@ namespace Wasm.Build.Tests
                                         extraProperties: "<AllowUnsafeBlocks>true</AllowUnsafeBlocks><_WasmDevel>true</_WasmDevel>");
 
             (_, string output) = BuildProject(buildArgs,
-                                        initProject: () =>
-                                        {
-                                            File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText);
-                                            File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", filename),
-                                                        Path.Combine(_projectDir!, filename));
-                                        },
-                                        publish: buildArgs.AOT,
                                         id: id,
-                                        dotnetWasmFromRuntimePack: false);
+                                        new BuildProjectOptions(
+                                            InitProject: () =>
+                                            {
+                                                File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText);
+                                                File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", filename),
+                                                            Path.Combine(_projectDir!, filename));
+                                            },
+                                            Publish: buildArgs.AOT,
+                                            DotnetWasmFromRuntimePack: false));
 
             return (buildArgs, output);
         }
index 961c141..ba65b24 100644 (file)
@@ -35,10 +35,11 @@ namespace Wasm.Build.Tests
             buildArgs = ExpandBuildArgs(buildArgs);
 
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
-                        dotnetWasmFromRuntimePack: true,
-                        id: id,
-                        createProject: true);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                DotnetWasmFromRuntimePack: true,
+                                CreateProject: true));
 
             Run();
 
@@ -52,9 +53,10 @@ namespace Wasm.Build.Tests
             // no-op Rebuild
             BuildProject(buildArgs,
                         id: id,
-                        dotnetWasmFromRuntimePack: true,
-                        createProject: false,
-                        useCache: false);
+                        new BuildProjectOptions(
+                            DotnetWasmFromRuntimePack: true,
+                            CreateProject: false,
+                            UseCache: false));
 
             Run();
 
index b65c274..7b6c4c7 100644 (file)
@@ -50,13 +50,14 @@ namespace Wasm.Build.Tests
                                         extraProperties: nativeRelink ? $"<WasmBuildNative>true</WasmBuildNative>" : string.Empty);
 
             BuildProject(buildArgs,
-                        initProject: () =>
-                        {
-                            Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "resx"), Path.Combine(_projectDir!, "resx"));
-                            CreateProgramForCultureTest(_projectDir!, $"{projectName}.resx.words", "TestClass");
-                        },
-                        dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
-                        id: id);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () =>
+                                {
+                                    Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "resx"), Path.Combine(_projectDir!, "resx"));
+                                    CreateProgramForCultureTest(_projectDir!, $"{projectName}.resx.words", "TestClass");
+                                },
+                                DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack));
 
             string output = RunAndTestWasmApp(
                                 buildArgs, expectedExitCode: 42,
@@ -88,17 +89,18 @@ namespace Wasm.Build.Tests
                                         extraItems: $"<ProjectReference Include=\"..\\LibraryWithResources\\LibraryWithResources.csproj\" />");
 
             BuildProject(buildArgs,
-                        dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
-                        id: id,
-                        initProject: () =>
-                        {
-                            string rootDir = _projectDir!;
-                            _projectDir = Path.Combine(rootDir, projectName);
-
-                            Directory.CreateDirectory(_projectDir);
-                            Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "SatelliteAssemblyFromProjectRef"), rootDir);
-                            CreateProgramForCultureTest(_projectDir, "LibraryWithResources.resx.words", "LibraryWithResources.Class1");
-                        });
+                            id: id,
+                            new BuildProjectOptions(
+                                DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
+                                InitProject: () =>
+                                {
+                                    string rootDir = _projectDir!;
+                                    _projectDir = Path.Combine(rootDir, projectName);
+
+                                    Directory.CreateDirectory(_projectDir);
+                                    Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "SatelliteAssemblyFromProjectRef"), rootDir);
+                                    CreateProgramForCultureTest(_projectDir, "LibraryWithResources.resx.words", "LibraryWithResources.Class1");
+                                }));
 
             string output = RunAndTestWasmApp(buildArgs,
                                               expectedExitCode: 42,
@@ -123,9 +125,10 @@ namespace Wasm.Build.Tests
                                         extraItems: $"<EmbeddedResource Include=\"{BuildEnvironment.RelativeTestAssetsPath}resx\\*\" />");
 
             BuildProject(buildArgs,
-                        initProject: () => CreateProgramForCultureTest(_projectDir!, $"{projectName}.words", "TestClass"),
-                        dotnetWasmFromRuntimePack: false,
-                        id: id);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () => CreateProgramForCultureTest(_projectDir!, $"{projectName}.words", "TestClass"),
+                                DotnetWasmFromRuntimePack: false));
 
             var bitCodeFileNames = Directory.GetFileSystemEntries(Path.Combine(_projectDir!, "obj"), "*.dll.bc", SearchOption.AllDirectories)
                                     .Select(path => Path.GetFileName(path))
index 68bc578..d6ae5ed 100644 (file)
@@ -112,13 +112,14 @@ namespace Wasm.Build.Tests
             }";
 
             BuildProject(buildArgs,
-                        initProject: () =>
-                        {
-                            File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText);
-                            File.WriteAllText(Path.Combine(_projectDir!, "runtimeconfig.template.json"), runtimeConfigTemplateJson);
-                        },
-                        id: id,
-                        dotnetWasmFromRuntimePack: !(buildArgs.AOT || buildArgs.Config == "Release"));
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () =>
+                                {
+                                    File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText);
+                                    File.WriteAllText(Path.Combine(_projectDir!, "runtimeconfig.template.json"), runtimeConfigTemplateJson);
+                                },
+                                DotnetWasmFromRuntimePack: !(buildArgs.AOT || buildArgs.Config == "Release")));
 
             RunAndTestWasmApp(buildArgs, expectedExitCode: 42,
                                 test: output => Assert.Contains("test_runtimeconfig_json: 25", output), host: host, id: id);
@@ -141,12 +142,13 @@ namespace Wasm.Build.Tests
             ";
 
             BuildProject(buildArgs,
-                        initProject: () =>
-                        {
-                            File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText);
-                        },
-                        id: id,
-                        dotnetWasmFromRuntimePack: !(buildArgs.AOT || buildArgs.Config == "Release"));
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () =>
+                                {
+                                    File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText);
+                                },
+                                DotnetWasmFromRuntimePack: !(buildArgs.AOT || buildArgs.Config == "Release")));
 
             RunAndTestWasmApp(buildArgs, expectedExitCode: 42,
                                 test: output => Assert.Contains("System.Threading.ThreadPool.MaxThreads: 20", output), host: host, id: id);
@@ -167,9 +169,10 @@ namespace Wasm.Build.Tests
                 dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release");
 
             BuildProject(buildArgs,
-                        initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
-                        id: id,
-                        dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack);
+                            id: id,
+                            new BuildProjectOptions(
+                                InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
+                                DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack));
 
             RunAndTestWasmApp(buildArgs, expectedExitCode: 42,
                                 test: output => Assert.Contains("Hello, World!", output), host: host, id: id);
index 64d0085..00674bf 100644 (file)
@@ -89,11 +89,13 @@ namespace Wasm.Build.Tests
                                         insertAtEnd: printValueTarget);
 
             (_, string output) = BuildProject(buildArgs,
-                                    initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
-                                    dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
-                                    id: Path.GetRandomFileName(),
-                                    expectSuccess: false,
-                                    useCache: false);
+                                                id: Path.GetRandomFileName(),
+                                                new BuildProjectOptions(
+                                                    InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
+                                                    DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
+                                                    ExpectSuccess: false,
+                                                    UseCache: false,
+                                                    BuildOnlyAfterPublish: false));
 
             return output;
         }