From: Marek Fišera Date: Wed, 28 Jun 2023 03:57:02 +0000 (+0200) Subject: [browser] Unify boot config schema and output layout (#86255) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055536~1355 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f6230892a0a0678c8e78163d13e4ea231584044c;p=platform%2Fupstream%2Fdotnet%2Fruntime.git [browser] Unify boot config schema and output layout (#86255) - Update `WasmAppBuilder` to produce the same boot config schema as `GenerateWasmBootJson` - Update AppBundle layout to match blazor layout. - All the runtime files (js, wasm, icu) are deployed to `_framework` folder. - This behavior can be overriden by setting `` property. The empty value is overriden to default `_framework`, but `./` can be used to flatten the output structure. - WBT test with flat output - The only remaining difference is `AppBundle` vs `wwwrooot` folder name. - `JSHost.ImportAsync` now requires the same relative paths as in blazor (`./module.js` => `../module.js`) Contributes to https://github.com/dotnet/runtime/issues/70762 --------- Co-authored-by: Ankit Jain --- diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/TimerTests.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/TimerTests.cs index 3a12086..9536de4 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/TimerTests.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.Legacy.UnitTests/System/Runtime/InteropServices/JavaScript/TimerTests.cs @@ -90,7 +90,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests { if (_module == null) { - _module = await JSHost.ImportAsync("Timers", "./timers.mjs"); + _module = await JSHost.ImportAsync("Timers", "../timers.mjs"); } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs index a66720c..5f16d50 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportExportTest.cs @@ -22,8 +22,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests [Fact] public async Task MultipleImportAsync() { - var first = await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs"); - var second = await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs"); + var first = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs"); + var second = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs"); Assert.NotNull(first); Assert.NotNull(second); Assert.Equal("object", first.GetTypeOfProperty("instance")); @@ -36,12 +36,12 @@ namespace System.Runtime.InteropServices.JavaScript.Tests public async Task CancelableImportAsync() { var cts = new CancellationTokenSource(); - var exTask = Assert.ThrowsAsync(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs", cts.Token)); + var exTask = Assert.ThrowsAsync(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs", cts.Token)); cts.Cancel(); var actualEx2 = await exTask; Assert.Equal("OperationCanceledException", actualEx2.Message); - var actualEx = await Assert.ThrowsAsync(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs", new CancellationToken(true))); + var actualEx = await Assert.ThrowsAsync(async () => await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs", new CancellationToken(true))); Assert.Equal("OperationCanceledException", actualEx.Message); } @@ -300,7 +300,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests yield return new object[] { new object[] { "JSData" } }; // special cased, so we call createData in the test itself yield return new object[] { new object[] { new byte[] { }, new int[] { }, new double[] { }, new string[] { }, new object[] { } } }; yield return new object[] { new object[] { new byte[] { 1, 2, 3 }, new int[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new string[] { "a", "b", "c" }, new object[] { } } }; - yield return new object[] { new object[] { new object[] { new byte[] { 1, 2, 3 }, new int[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new string[] { "a", "b", "c" } , new object(), new SomethingRef(), new SomethingStruct(), new Exception("test") } } }; + yield return new object[] { new object[] { new object[] { new byte[] { 1, 2, 3 }, new int[] { 1, 2, 3 }, new double[] { 1, 2, 3 }, new string[] { "a", "b", "c" }, new object(), new SomethingRef(), new SomethingStruct(), new Exception("test") } } }; yield return new object[] { new object[] { } }; yield return new object[] { null }; } @@ -317,10 +317,10 @@ namespace System.Runtime.InteropServices.JavaScript.Tests Assert.Equal(expected, actual); if (expected != null) for (int i = 0; i < expected.Length; i++) - { - var actualI = JavaScriptTestHelper.store_ObjectArray(expected, i); - Assert.Equal(expected[i], actualI); - } + { + var actualI = JavaScriptTestHelper.store_ObjectArray(expected, i); + Assert.Equal(expected[i], actualI); + } } public static IEnumerable MarshalObjectArrayCasesToDouble() @@ -362,7 +362,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests yield return new object[] { new object[] { (ushort)0 } }; yield return new object[] { new object[] { new SomethingStruct[] { } } }; yield return new object[] { new object[] { new SomethingRef[] { }, } }; - yield return new object[] { new object[] { new ArraySegment(new byte[] { 11 }) , } }; + yield return new object[] { new object[] { new ArraySegment(new byte[] { 11 }), } }; } delegate void dummyDelegate(); static void dummyDelegateA() diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index 2a06708..fea5cc4 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -68,7 +68,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests [JSExport] public static void Optimized1V(int a1) { - optimizedReached+= a1; + optimizedReached += a1; } [JSImport("invoke1V", "JavaScriptTestHelper")] public static partial void invoke1V(int a1); @@ -85,8 +85,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests [JSExport] public static int Optimized2R(int a1, int a2) { - optimizedReached += a1+ a2; - return a1 + a2 +1; + optimizedReached += a1 + a2; + return a1 + a2 + 1; } [JSImport("invoke2R", "JavaScriptTestHelper")] public static partial int invoke2R(int a1, int a2); @@ -997,7 +997,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests if (_module == null) { // Log("JavaScriptTestHelper.mjs importing"); - _module = await JSHost.ImportAsync("JavaScriptTestHelper", "./JavaScriptTestHelper.mjs"); + _module = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs"); await Setup(); // Log("JavaScriptTestHelper.mjs imported"); } @@ -1010,7 +1010,7 @@ namespace JavaScriptTestHelperNamespace public partial class JavaScriptTestHelper { [System.Runtime.InteropServices.JavaScript.JSExport] - public static string EchoString(string message) + public static string EchoString(string message) { return message + "11"; } @@ -1019,7 +1019,7 @@ namespace JavaScriptTestHelperNamespace { [System.Runtime.InteropServices.JavaScript.JSExport] public static string EchoString(string message) => message + "12"; - + private partial class DoubleNestedClass { [System.Runtime.InteropServices.JavaScript.JSExport] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.cs index 6b65ff6..7985ec9 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.cs @@ -12,9 +12,9 @@ namespace System.Runtime.InteropServices.JavaScript.Tests public partial class SecondRuntimeTest { [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowserDomSupportedOrNodeJS))] - public static async Task RunSecondRuntimeAndTestStaticState() + public static async Task RunSecondRuntimeAndTestStaticState() { - await JSHost.ImportAsync("SecondRuntimeTest", "./SecondRuntimeTest.js"); + await JSHost.ImportAsync("SecondRuntimeTest", "../SecondRuntimeTest.js"); Interop.State = 42; var state2 = await Interop.RunSecondRuntimeAndTestStaticState(); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js index fa0b5d9..4230d51 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js @@ -1,5 +1,5 @@ export async function runSecondRuntimeAndTestStaticState() { - const { dotnet: dotnet2 } = await import('./dotnet.js?instance=2'); + const { dotnet: dotnet2 } = await import('./_framework/dotnet.js?instance=2'); const runtime2 = await dotnet2 .withStartupMemoryCache(false) .withConfig({ diff --git a/src/mono/sample/mbr/browser/main.js b/src/mono/sample/mbr/browser/main.js index ff6a9d0..a3ffba9 100644 --- a/src/mono/sample/mbr/browser/main.js +++ b/src/mono/sample/mbr/browser/main.js @@ -1,4 +1,4 @@ -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' try { const { getAssemblyExports } = await dotnet @@ -8,7 +8,7 @@ try { const exports = await getAssemblyExports("WasmDelta.dll"); const update = exports.Sample.Test.Update; const testMeaning = exports.Sample.Test.TestMeaning; - + const outElement = document.getElementById("out"); document.getElementById("update").addEventListener("click", function () { update(); diff --git a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj index d37b174..6875be3 100644 --- a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj +++ b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj @@ -14,6 +14,9 @@ <_ServeHeaders>$(_ServeHeaders) -h "Content-Security-Policy: default-src 'self' 'wasm-unsafe-eval'" browser; + + + ./ diff --git a/src/mono/sample/wasm/browser-advanced/index.html b/src/mono/sample/wasm/browser-advanced/index.html index b0fd27b..eb1032f 100644 --- a/src/mono/sample/wasm/browser-advanced/index.html +++ b/src/mono/sample/wasm/browser-advanced/index.html @@ -9,7 +9,7 @@ - + diff --git a/src/mono/sample/wasm/browser-advanced/main.js b/src/mono/sample/wasm/browser-advanced/main.js index 82be8cb..38a7236 100644 --- a/src/mono/sample/wasm/browser-advanced/main.js +++ b/src/mono/sample/wasm/browser-advanced/main.js @@ -31,7 +31,7 @@ try { // here we show how emscripten could be further configured // It is preferred to use specific 'with***' methods instead in all other cases. .withModuleConfig({ - configSrc: "./_framework/blazor.boot.json", + configSrc: "./blazor.boot.json", onConfigLoaded: (config) => { // This is called during emscripten `dotnet.wasm` instantiation, after we fetched config. console.log('user code Module.onConfigLoaded'); diff --git a/src/mono/sample/wasm/browser-bench/appstart-frame.html b/src/mono/sample/wasm/browser-bench/appstart-frame.html index 684acbc..697f7ac 100644 --- a/src/mono/sample/wasm/browser-bench/appstart-frame.html +++ b/src/mono/sample/wasm/browser-bench/appstart-frame.html @@ -8,7 +8,7 @@ - + diff --git a/src/mono/sample/wasm/browser-bench/frame-main.js b/src/mono/sample/wasm/browser-bench/frame-main.js index 667f5ee..c104292 100644 --- a/src/mono/sample/wasm/browser-bench/frame-main.js +++ b/src/mono/sample/wasm/browser-bench/frame-main.js @@ -3,7 +3,7 @@ "use strict"; -import { dotnet, exit } from './dotnet.js' +import { dotnet, exit } from './_framework/dotnet.js' class FrameApp { async init({ getAssemblyExports }) { diff --git a/src/mono/sample/wasm/browser-bench/main.js b/src/mono/sample/wasm/browser-bench/main.js index 28d9613..eb9080e 100644 --- a/src/mono/sample/wasm/browser-bench/main.js +++ b/src/mono/sample/wasm/browser-bench/main.js @@ -3,7 +3,7 @@ "use strict"; -import { dotnet, exit } from './dotnet.js' +import { dotnet, exit } from './_framework/dotnet.js' let runBenchmark; let setTasks; diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index 683c076..e4f4c94 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet, exit } from "./dotnet.js"; +import { dotnet, exit } from "./_framework/dotnet.js"; const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) diff --git a/src/mono/sample/wasm/browser-profile/main.js b/src/mono/sample/wasm/browser-profile/main.js index 455702e..aa31ff5 100644 --- a/src/mono/sample/wasm/browser-profile/main.js +++ b/src/mono/sample/wasm/browser-profile/main.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet, exit } from './dotnet.js' +import { dotnet, exit } from './_framework/dotnet.js' function saveProfile(aotProfileData) { if (!aotProfileData) { diff --git a/src/mono/sample/wasm/browser-threads-minimal/Program.cs b/src/mono/sample/wasm/browser-threads-minimal/Program.cs index f86891e..c6a48fa 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/Program.cs +++ b/src/mono/sample/wasm/browser-threads-minimal/Program.cs @@ -99,7 +99,7 @@ namespace Sample [JSImport("globalThis.console.log")] private static partial void GlobalThisConsoleLog(string text); - const string fetchhelper = "./fetchelper.js"; + const string fetchhelper = "../fetchhelper.js"; [JSImport("responseText", fetchhelper)] private static partial Task FetchHelperResponseText(JSObject response, int delayMs); @@ -111,7 +111,7 @@ namespace Sample internal static Task TestHelloWebWorker() { Console.WriteLine($"smoke: TestHelloWebWorker 1 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - Task t= WebWorker.RunAsync(() => + Task t = WebWorker.RunAsync(() => { Console.WriteLine($"smoke: TestHelloWebWorker 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); GlobalThisConsoleLog($"smoke: TestHelloWebWorker 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); @@ -121,7 +121,8 @@ namespace Sample return t.ContinueWith(Gogo); } - private static void Gogo(Task t){ + private static void Gogo(Task t) + { Console.WriteLine($"smoke: TestHelloWebWorker 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); } @@ -153,9 +154,9 @@ namespace Sample internal static void StartTimerFromWorker() { Console.WriteLine("smoke: StartTimerFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime()); - WebWorker.RunAsync(async () => + WebWorker.RunAsync(async () => { - while (!_timerDone) + while (!_timerDone) { await Task.Delay(1 * 1000); Console.WriteLine("smoke: StartTimerFromWorker 2 utc {0}", DateTime.UtcNow.ToUniversalTime()); @@ -168,19 +169,19 @@ namespace Sample internal static void StartAllocatorFromWorker() { Console.WriteLine("smoke: StartAllocatorFromWorker 1 utc {0}", DateTime.UtcNow.ToUniversalTime()); - WebWorker.RunAsync(async () => + WebWorker.RunAsync(async () => { - while (!_timerDone) + while (!_timerDone) { await Task.Delay(1 * 100); var x = new List(); for (int i = 0; i < 1000; i++) { - var v=new int[1000]; + var v = new int[1000]; v[i] = i; x.Add(v); } - Console.WriteLine("smoke: StartAllocatorFromWorker 2 utc {0} {1} {2}", DateTime.UtcNow.ToUniversalTime(),x[1][1], GC.GetTotalAllocatedBytes()); + Console.WriteLine("smoke: StartAllocatorFromWorker 2 utc {0} {1} {2}", DateTime.UtcNow.ToUniversalTime(), x[1][1], GC.GetTotalAllocatedBytes()); } Console.WriteLine("smoke: StartAllocatorFromWorker done utc {0}", DateTime.UtcNow.ToUniversalTime()); }); @@ -196,7 +197,7 @@ namespace Sample public static async Task TestCallSetTimeoutOnWorker() { await WebWorker.RunAsync(() => TimeOutThenComplete()); - Console.WriteLine ($"XYZ: Main Thread caught task tid:{Thread.CurrentThread.ManagedThreadId}"); + Console.WriteLine($"XYZ: Main Thread caught task tid:{Thread.CurrentThread.ManagedThreadId}"); } private static async Task HttpClientGet(string name, string url) @@ -274,7 +275,7 @@ namespace Sample var ctx = SynchronizationContext.Current; Console.WriteLine($"smoke: FetchBackground 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); - var x=JSHost.ImportAsync(fetchhelper, "./fetchhelper.js"); + var x = JSHost.ImportAsync(fetchhelper, "../fetchhelper.js"); Console.WriteLine($"smoke: FetchBackground 3A ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); // using var import = await x.ConfigureAwait(false); Console.WriteLine($"smoke: FetchBackground 3B ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); @@ -285,11 +286,11 @@ namespace Sample Console.WriteLine($"smoke: FetchBackground 5 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); if (ok) { - #if DEBUG +#if DEBUG var text = await FetchHelperResponseText(r, 5000); - #else +#else var text = await FetchHelperResponseText(r, 25000); - #endif +#endif Console.WriteLine($"smoke: FetchBackground 6 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); return text; } @@ -313,7 +314,7 @@ namespace Sample { Console.WriteLine($"smoke {meaning}: TestTLS 2 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); meaning = 41; - await JSHost.ImportAsync(fetchhelper, "./fetchhelper.js"); + await JSHost.ImportAsync(fetchhelper, "../fetchhelper.js"); Console.WriteLine($"smoke {meaning}: TestTLS 3 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); meaning = 43; Console.WriteLine($"smoke {meaning}: TestTLS 4 ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}, SynchronizationContext: {SynchronizationContext.Current?.GetType().FullName ?? "null"}"); @@ -327,21 +328,23 @@ namespace Sample private static async Task TimeOutThenComplete() { var tcs = new TaskCompletionSource(); - Console.WriteLine ($"smoke: Task running tid:{Thread.CurrentThread.ManagedThreadId}"); - GlobalThisSetTimeout(() => { + Console.WriteLine($"smoke: Task running tid:{Thread.CurrentThread.ManagedThreadId}"); + GlobalThisSetTimeout(() => + { tcs.SetResult(); - Console.WriteLine ($"smoke: Timeout fired tid:{Thread.CurrentThread.ManagedThreadId}"); + Console.WriteLine($"smoke: Timeout fired tid:{Thread.CurrentThread.ManagedThreadId}"); }, 250); - Console.WriteLine ($"smoke: Task sleeping tid:{Thread.CurrentThread.ManagedThreadId}"); + Console.WriteLine($"smoke: Task sleeping tid:{Thread.CurrentThread.ManagedThreadId}"); await tcs.Task; - Console.WriteLine ($"smoke: Task resumed tid:{Thread.CurrentThread.ManagedThreadId}"); + Console.WriteLine($"smoke: Task resumed tid:{Thread.CurrentThread.ManagedThreadId}"); } [JSExport] public static async Task RunBackgroundThreadCompute() { var tcs = new TaskCompletionSource(); - var t = new Thread(() => { + var t = new Thread(() => + { var n = CountingCollatzTest(); tcs.SetResult(n); }); @@ -353,7 +356,8 @@ namespace Sample public static async Task RunBackgroundLongRunningTaskCompute() { var factory = new TaskFactory(); - var t = factory.StartNew (() => { + var t = factory.StartNew(() => + { var n = CountingCollatzTest(); return n; }, TaskCreationOptions.LongRunning); @@ -363,17 +367,19 @@ namespace Sample [JSExport] public static async Task RunBackgroundTaskRunCompute() { - var t1 = Task.Run (() => { + var t1 = Task.Run(() => + { var n = CountingCollatzTest(); return n; }); - var t2 = Task.Run (() => { + var t2 = Task.Run(() => + { var n = CountingCollatzTest(); return n; }); - var rs = await Task.WhenAll (new [] { t1, t2 }); + var rs = await Task.WhenAll(new[] { t1, t2 }); if (rs[0] != rs[1]) - throw new Exception ($"Results from two tasks {rs[0]}, {rs[1]}, differ"); + throw new Exception($"Results from two tasks {rs[0]}, {rs[1]}, differ"); return rs[0]; } @@ -392,8 +398,9 @@ namespace Sample int bigly = 0; int hugely = 0; int maxSteps = 0; - for (int n = 1; n < maxInput; n++) { - int steps = CountingCollatz ((long)n, limit); + for (int n = 1; n < maxInput; n++) + { + int steps = CountingCollatz((long)n, limit); if (steps > maxSteps) maxSteps = steps; if (steps > 120) @@ -402,7 +409,7 @@ namespace Sample hugely++; } - Console.WriteLine ($"Bigly: {bigly}, Hugely: {hugely}, maxSteps: {maxSteps}"); + Console.WriteLine($"Bigly: {bigly}, Hugely: {hugely}, maxSteps: {maxSteps}"); if (bigly == 86187 && hugely == 0 && maxSteps == 382) return 524; @@ -411,11 +418,12 @@ namespace Sample } - private static int CountingCollatz (long n, int limit) + private static int CountingCollatz(long n, int limit) { int steps = 0; - while (n > 1) { - n = Collatz1 (n); + while (n > 1) + { + n = Collatz1(n); steps++; if (steps >= limit) break; @@ -423,7 +431,7 @@ namespace Sample return steps; } - private static long Collatz1 (long n) + private static long Collatz1(long n) { if (n <= 0) throw new Exception("Unexpected non-positive input"); diff --git a/src/mono/sample/wasm/browser-threads-minimal/main.js b/src/mono/sample/wasm/browser-threads-minimal/main.js index 59a0f4b..8ff5ab2 100644 --- a/src/mono/sample/wasm/browser-threads-minimal/main.js +++ b/src/mono/sample/wasm/browser-threads-minimal/main.js @@ -1,13 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet, exit } from './dotnet.js' +import { dotnet, exit } from './_framework/dotnet.js' const assemblyName = "Wasm.Browser.Threads.Minimal.Sample.dll"; try { - const { setModuleImports, getAssemblyExports, runMain } = await dotnet + const resolveUrl = (relativeUrl) => (new URL(relativeUrl, window.location.href)).toString() + + const { getAssemblyExports, runMain } = await dotnet //.withEnvironmentVariable("MONO_LOG_LEVEL", "debug") //.withDiagnosticTracing(true) .withConfig({ @@ -79,7 +81,7 @@ try { /* ActiveIssue https://github.com/dotnet/runtime/issues/88057 console.log("smoke: running FetchBackground(blurst.txt)"); - let s = await exports.Sample.Test.FetchBackground("./blurst.txt"); + let s = await exports.Sample.Test.FetchBackground(resolveUrl("./blurst.txt")); console.log("smoke: FetchBackground(blurst.txt) done"); if (!s.startsWith("It was the best of times, it was the blurst of times.")) { const msg = `Unexpected FetchBackground result ${s}`; @@ -88,7 +90,7 @@ try { } console.log("smoke: running FetchBackground(missing)"); - s = await exports.Sample.Test.FetchBackground("./missing.txt"); + s = await exports.Sample.Test.FetchBackground(resolveUrl("./missing.txt")); console.log("smoke: FetchBackground(missing) done"); if (s !== "not-ok") { const msg = `Unexpected FetchBackground(missing) result ${s}`; diff --git a/src/mono/sample/wasm/browser-threads/main.js b/src/mono/sample/wasm/browser-threads/main.js index 9b739d4..c97e380 100644 --- a/src/mono/sample/wasm/browser-threads/main.js +++ b/src/mono/sample/wasm/browser-threads/main.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet, exit } from './dotnet.js' +import { dotnet, exit } from './_framework/dotnet.js' let progressElement = null; diff --git a/src/mono/sample/wasm/browser-webpack/Wasm.Browser.WebPack.Sample.csproj b/src/mono/sample/wasm/browser-webpack/Wasm.Browser.WebPack.Sample.csproj index bb694f2..0459f51 100644 --- a/src/mono/sample/wasm/browser-webpack/Wasm.Browser.WebPack.Sample.csproj +++ b/src/mono/sample/wasm/browser-webpack/Wasm.Browser.WebPack.Sample.csproj @@ -9,18 +9,18 @@ - - - - - + + + + diff --git a/src/mono/sample/wasm/browser/main.js b/src/mono/sample/wasm/browser/main.js index 2778308..ba84cca 100644 --- a/src/mono/sample/wasm/browser/main.js +++ b/src/mono/sample/wasm/browser/main.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet, exit } from './dotnet.js' +import { dotnet, exit } from './_framework/dotnet.js' function displayMeaning(meaning) { document.getElementById("out").innerHTML = `${meaning}`; diff --git a/src/mono/sample/wasm/console-node/main.mjs b/src/mono/sample/wasm/console-node/main.mjs index 3b9741a..d3cfafe 100644 --- a/src/mono/sample/wasm/console-node/main.mjs +++ b/src/mono/sample/wasm/console-node/main.mjs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' await dotnet .withDiagnosticTracing(false) diff --git a/src/mono/sample/wasm/console-v8/main.mjs b/src/mono/sample/wasm/console-v8/main.mjs index b336fe2..4350b81 100644 --- a/src/mono/sample/wasm/console-v8/main.mjs +++ b/src/mono/sample/wasm/console-v8/main.mjs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' await dotnet .withDiagnosticTracing(false) diff --git a/src/mono/sample/wasm/node-webpack/Wasm.Node.WebPack.Sample.csproj b/src/mono/sample/wasm/node-webpack/Wasm.Node.WebPack.Sample.csproj index b10e782..88216d3 100644 --- a/src/mono/sample/wasm/node-webpack/Wasm.Node.WebPack.Sample.csproj +++ b/src/mono/sample/wasm/node-webpack/Wasm.Node.WebPack.Sample.csproj @@ -4,18 +4,18 @@ main.mjs - - - - - + + + + diff --git a/src/mono/sample/wasm/simple-raytracer/main.js b/src/mono/sample/wasm/simple-raytracer/main.js index b0f685a..decdf645 100644 --- a/src/mono/sample/wasm/simple-raytracer/main.js +++ b/src/mono/sample/wasm/simple-raytracer/main.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' function renderCanvas(rgbaView) { const canvas = document.getElementById("out"); diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 51cba2b..8e0f235 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -386,9 +386,15 @@ namespace Wasm.Build.Tests options.InitProject?.Invoke(); File.WriteAllText(Path.Combine(_projectDir, $"{buildArgs.ProjectName}.csproj"), buildArgs.ProjectFileContents); - File.Copy(Path.Combine(AppContext.BaseDirectory, - options.TargetFramework == "net8.0" ? "test-main.js" : "data/test-main-7.0.js"), - Path.Combine(_projectDir, "test-main.js")); + File.Copy( + Path.Combine( + AppContext.BaseDirectory, + string.IsNullOrEmpty(options.TargetFramework) || options.TargetFramework == "net8.0" + ? "test-main.js" + : "data/test-main-7.0.js" + ), + Path.Combine(_projectDir, "test-main.js") + ); File.WriteAllText(Path.Combine(_projectDir!, "index.html"), @""); } @@ -676,11 +682,11 @@ namespace Wasm.Build.Tests var filesToExist = new List() { mainJS, - "dotnet.native.wasm", + "_framework/dotnet.native.wasm", "_framework/blazor.boot.json", - "dotnet.js", - "dotnet.native.js", - "dotnet.runtime.js" + "_framework/dotnet.js", + "_framework/dotnet.native.js", + "_framework/dotnet.runtime.js" }; if (isBrowserProject) @@ -691,7 +697,7 @@ namespace Wasm.Build.Tests AssertFilesExist(bundleDir, new[] { "run-v8.sh" }, expectToExist: hasV8Script); AssertIcuAssets(); - string managedDir = Path.Combine(bundleDir, "managed"); + string managedDir = Path.Combine(bundleDir, "_framework"); string bundledMainAppAssembly = useWebcil ? $"{projectName}{WebcilInWasmExtension}" : $"{projectName}.dll"; AssertFilesExist(managedDir, new[] { bundledMainAppAssembly }); @@ -732,7 +738,7 @@ namespace Wasm.Build.Tests case GlobalizationMode.PredefinedIcu: if (string.IsNullOrEmpty(predefinedIcudt)) throw new ArgumentException("WasmBuildTest is invalid, value for predefinedIcudt is required when GlobalizationMode=PredefinedIcu."); - AssertFilesExist(bundleDir, new[] { predefinedIcudt }, expectToExist: true); + AssertFilesExist(bundleDir, new[] { Path.Combine("_framework", predefinedIcudt) }, expectToExist: true); // predefined ICU name can be identical with the icu files from runtime pack switch (predefinedIcudt) { @@ -757,30 +763,32 @@ namespace Wasm.Build.Tests expectNOCJK = true; break; } - AssertFilesExist(bundleDir, new[] { "icudt.dat" }, expectToExist: expectFULL); - AssertFilesExist(bundleDir, new[] { "icudt_EFIGS.dat" }, expectToExist: expectEFIGS); - AssertFilesExist(bundleDir, new[] { "icudt_CJK.dat" }, expectToExist: expectCJK); - AssertFilesExist(bundleDir, new[] { "icudt_no_CJK.dat" }, expectToExist: expectNOCJK); - AssertFilesExist(bundleDir, new[] { "icudt_hybrid.dat" }, expectToExist: expectHYBRID); + + var frameworkDir = Path.Combine(bundleDir, "_framework"); + AssertFilesExist(frameworkDir, new[] { "icudt.dat" }, expectToExist: expectFULL); + AssertFilesExist(frameworkDir, new[] { "icudt_EFIGS.dat" }, expectToExist: expectEFIGS); + AssertFilesExist(frameworkDir, new[] { "icudt_CJK.dat" }, expectToExist: expectCJK); + AssertFilesExist(frameworkDir, new[] { "icudt_no_CJK.dat" }, expectToExist: expectNOCJK); + AssertFilesExist(frameworkDir, new[] { "icudt_hybrid.dat" }, expectToExist: expectHYBRID); } } protected static void AssertDotNetWasmJs(string bundleDir, bool fromRuntimePack, string targetFramework) { AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.wasm"), - Path.Combine(bundleDir, "dotnet.native.wasm"), + Path.Combine(bundleDir, "_framework/dotnet.native.wasm"), "Expected dotnet.native.wasm to be same as the runtime pack", same: fromRuntimePack); AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.js"), - Path.Combine(bundleDir, "dotnet.native.js"), + Path.Combine(bundleDir, "_framework/dotnet.native.js"), "Expected dotnet.native.js to be same as the runtime pack", same: fromRuntimePack); } protected static void AssertDotNetJsSymbols(string bundleDir, bool fromRuntimePack, string targetFramework) => AssertFile(Path.Combine(s_buildEnv.GetRuntimeNativeDir(targetFramework), "dotnet.native.js.symbols"), - Path.Combine(bundleDir, "dotnet.native.js.symbols"), + Path.Combine(bundleDir, "_framework/dotnet.native.js.symbols"), same: fromRuntimePack); protected static void AssertFilesDontExist(string dir, string[] filenames, string? label = null) diff --git a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs index 76067e4..8a797ef 100644 --- a/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/HostRunner/V8HostRunner.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; public class V8HostRunner : IHostRunner { - private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace --engine-arg=--experimental-wasm-simd"; + private string GetXharnessArgs(string jsRelativePath) => $"--js-file={jsRelativePath} --engine=V8 -v trace --engine-arg=--experimental-wasm-simd --engine-arg=--module"; public string GetTestCommand() => "wasm test"; public string GetXharnessArgsWindowsOS(XHarnessArgsOptions options) => GetXharnessArgs(options.jsRelativePath); diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index d31f990..d190dc2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -164,8 +164,8 @@ namespace Wasm.Build.NativeRebuild.Tests Path.Combine(paths.ObjWasmDir, "pinvoke-table.h"), Path.Combine(paths.ObjWasmDir, "driver-gen.c"), - Path.Combine(paths.BundleDir, "dotnet.native.wasm"), - Path.Combine(paths.BundleDir, "dotnet.native.js"), + Path.Combine(paths.BundleDir, "_framework", "dotnet.native.wasm"), + Path.Combine(paths.BundleDir, "_framework", "dotnet.native.js"), }; if (buildArgs.AOT) @@ -185,8 +185,8 @@ namespace Wasm.Build.NativeRebuild.Tests dict[Path.GetFileName(file)] = (file, unchanged); // those files do not change on re-link - dict["dotnet.js"]=(Path.Combine(paths.BundleDir, "dotnet.js"), true); - dict["dotnet.runtime.js"]=(Path.Combine(paths.BundleDir, "dotnet.runtime.js"), true); + dict["dotnet.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.js"), true); + dict["dotnet.runtime.js"]=(Path.Combine(paths.BundleDir, "_framework", "dotnet.runtime.js"), true); return dict; } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs index 8b58299..88af535 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmTemplateTests.cs @@ -36,7 +36,9 @@ namespace Wasm.Build.Tests File.WriteAllText(path, text); } - private void UpdateBrowserMainJs(string targetFramework) + private const string DefaultRuntimeAssetsRelativePath = "./_framework/"; + + private void UpdateBrowserMainJs(string targetFramework, string runtimeAssetsRelativePath = DefaultRuntimeAssetsRelativePath) { string mainJsPath = Path.Combine(_projectDir!, "main.js"); string mainJsContent = File.ReadAllText(mainJsPath); @@ -46,6 +48,9 @@ namespace Wasm.Build.Tests targetFramework == "net8.0" ? ".withConsoleForwarding().withElementOnExit().withExitCodeLogging().withExitOnUnhandledError().create()" : ".withConsoleForwarding().withElementOnExit().withExitCodeLogging().create()"); + + mainJsContent = mainJsContent.Replace("from './_framework/dotnet.js'", $"from '{runtimeAssetsRelativePath}dotnet.js'"); + File.WriteAllText(mainJsPath, mainJsContent); } @@ -423,21 +428,23 @@ namespace Wasm.Build.Tests } [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] - [InlineData("", BuildTestBase.DefaultTargetFramework)] + [InlineData("", BuildTestBase.DefaultTargetFramework, DefaultRuntimeAssetsRelativePath)] + [InlineData("", BuildTestBase.DefaultTargetFramework, "./")] // [ActiveIssue("https://github.com/dotnet/runtime/issues/79313")] // [InlineData("-f net7.0", "net7.0")] - [InlineData("-f net8.0", "net8.0")] - public async Task BrowserBuildAndRun(string extraNewArgs, string targetFramework) + [InlineData("-f net8.0", "net8.0", DefaultRuntimeAssetsRelativePath)] + [InlineData("-f net8.0", "net8.0", "./")] + public async Task BrowserBuildAndRun(string extraNewArgs, string targetFramework, string runtimeAssetsRelativePath) { string config = "Debug"; string id = $"browser_{config}_{Path.GetRandomFileName()}"; CreateWasmTemplateProject(id, "wasmbrowser", extraNewArgs); - UpdateBrowserMainJs(targetFramework); + UpdateBrowserMainJs(targetFramework, runtimeAssetsRelativePath); new DotNetCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(_projectDir!) - .Execute($"build -c {config} -bl:{Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog")}") + .Execute($"build -c {config} -bl:{Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog")} {(runtimeAssetsRelativePath != DefaultRuntimeAssetsRelativePath ? "-p:WasmRuntimeAssetsLocation=" + runtimeAssetsRelativePath : "")}") .EnsureSuccessful(); using var runCommand = new RunCommand(s_buildEnv, _testOutput) diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 9290896..e76fd80 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -77,6 +77,12 @@ - $(WasmAllowUndefinedSymbols) - Controls whether undefined symbols are allowed or not, if true, appends 'allow-undefined' and sets 'ERROR_ON_UNDEFINED_SYMBOLS=0' as arguments for wasm-ld, if false (default), removes 'allow-undefined' and sets 'ERROR_ON_UNDEFINED_SYMBOLS=1'. + - $(WasmRuntimeAssetsLocation) - Allows to override a location for build generated files. + Defaults to '_framework', if you want to put framework files in the same directory as user files, use './' value. + Output structure + - AppBundle directly contains user files + - AppBundle/_framework contains generated files (dlls, runtime scripts, icu) + - AppBundle/_content contains web files from nuget packages (css, js, etc) Public items: - @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir). @@ -135,6 +141,8 @@ true + + _framework @@ -402,6 +410,9 @@ IncludeThreadsWorker="$(_WasmAppIncludeThreadsWorker)" PThreadPoolSize="$(_WasmPThreadPoolSize)" UseWebcil="$(WasmEnableWebcil)" + WasmIncludeFullIcuData="$(WasmIncludeFullIcuData)" + WasmIcuDataFileName="$(WasmIcuDataFileName)" + RuntimeAssetsLocation="$(WasmRuntimeAssetsLocation)" > diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs index 46e054e..9e365c0 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs @@ -94,8 +94,8 @@ namespace DebuggerTests public async Task DuplicateAssemblyLoadedEventForAssemblyFromBundle(bool load_pdb, int expected_count) => await AssemblyLoadedEventTest( "debugger-test", - Path.Combine(DebuggerTestAppPath, "managed/debugger-test.dll"), - load_pdb ? Path.Combine(DebuggerTestAppPath, "managed/debugger-test.pdb") : null, + Path.Combine(DebuggerTestAppPath, "_framework/debugger-test.dll"), + load_pdb ? Path.Combine(DebuggerTestAppPath, "_framework/debugger-test.pdb") : null, "/debugger-test.cs", expected_count ); diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-main.js b/src/mono/wasm/debugger/tests/debugger-test/debugger-main.js index 6f39603..cb6273e 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-main.js +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-main.js @@ -3,7 +3,7 @@ "use strict"; -import { dotnet, exit } from './dotnet.js' +import { dotnet, exit } from './_framework/dotnet.js' try { const runtime = await dotnet diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 7e055b7..50c6324 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -315,6 +315,11 @@ interface BootJsonData { readonly icuDataMode: ICUDataMode; readonly startupMemoryCache: boolean | undefined; readonly runtimeOptions: string[] | undefined; + readonly environmentVariables?: { + [name: string]: string; + }; + readonly diagnosticTracing?: boolean; + readonly pthreadPoolSize: number; modifiableAssemblies: string | null; aspnetCoreBrowserTools: string | null; } @@ -322,6 +327,7 @@ type BootJsonDataExtension = { [extensionName: string]: ResourceList; }; interface ResourceGroups { + readonly hash?: string; readonly assembly: ResourceList; readonly lazyAssembly: ResourceList; readonly pdb?: ResourceList; @@ -332,6 +338,9 @@ interface ResourceGroups { readonly libraryInitializers?: ResourceList; readonly extensions?: BootJsonDataExtension; readonly runtimeAssets: ExtendedResourceList; + readonly vfs?: { + [virtualPath: string]: ResourceList; + }; } type ResourceList = { [name: string]: string; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 61b2565..32ca65a 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -5,7 +5,7 @@ import type { DotnetModuleInternal, MonoConfigInternal } from "../../types/inter import type { AssetBehaviours, AssetEntry, LoadingResource, WebAssemblyBootResourceType, WebAssemblyStartOptions } from "../../types"; import type { BootJsonData } from "../../types/blazor"; -import { INTERNAL, loaderHelpers } from "../globals"; +import { ENVIRONMENT_IS_WEB, INTERNAL, loaderHelpers } from "../globals"; import { BootConfigResult } from "./BootConfig"; import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; import { hasDebuggingEnabled } from "./_Polyfill"; @@ -22,7 +22,10 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, startupOptions?: Partial) { INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, startupOptions ?? {}); mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment); - setupModuleForBlazor(module); + + if (ENVIRONMENT_IS_WEB) { + setupModuleForBlazor(module); + } } let resourcesLoaded = 0; @@ -34,8 +37,9 @@ const behaviorByName = (name: string): AssetBehaviours | "other" => { : (name.startsWith("dotnet.native") && name.endsWith(".js")) ? "js-module-native" : (name.startsWith("dotnet.runtime") && name.endsWith(".js")) ? "js-module-runtime" : (name.startsWith("dotnet") && name.endsWith(".js")) ? "js-module-dotnet" - : name.startsWith("icudt") ? "icu" - : "other"; + : (name.startsWith("dotnet.native") && name.endsWith(".symbols")) ? "symbols" + : name.startsWith("icudt") ? "icu" + : "other"; }; const monoToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType | undefined } = { @@ -71,30 +75,52 @@ export function setupModuleForBlazor(module: DotnetModuleInternal) { }; loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned - module.disableDotnet6Compatibility = false; +} + +function appendUniqueQuery(attemptUrl: string): string { + if (loaderHelpers.assetUniqueQuery) { + attemptUrl = attemptUrl + loaderHelpers.assetUniqueQuery; + } + + return attemptUrl; } export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, applicationEnvironment: string) { const resources = resourceLoader.bootConfig.resources; const assets: AssetEntry[] = []; - const environmentVariables: any = {}; + const environmentVariables: any = { + // From boot config + ...(resourceLoader.bootConfig.environmentVariables || {}), + // From JavaScript + ...(moduleConfig.environmentVariables || {}) + }; moduleConfig.applicationEnvironment = applicationEnvironment; + moduleConfig.remoteSources = (resourceLoader.bootConfig.resources as any).remoteSources; + moduleConfig.assetsHash = resourceLoader.bootConfig.resources.hash; moduleConfig.assets = assets; moduleConfig.globalizationMode = "icu"; - moduleConfig.environmentVariables = environmentVariables; - moduleConfig.debugLevel = hasDebuggingEnabled(resourceLoader.bootConfig) ? 1 : 0; - moduleConfig.maxParallelDownloads = 1000000; // disable throttling parallel downloads - moduleConfig.enableDownloadRetry = false; // disable retry downloads + moduleConfig.debugLevel = hasDebuggingEnabled(resourceLoader.bootConfig) ? resourceLoader.bootConfig.debugLevel : 0; moduleConfig.mainAssemblyName = resourceLoader.bootConfig.entryAssembly; + const anyBootConfig = (resourceLoader.bootConfig as any); + for (const key in resourceLoader.bootConfig) { + if (Object.prototype.hasOwnProperty.call(anyBootConfig, key)) { + if (anyBootConfig[key] === null) { + delete anyBootConfig[key]; + } + } + } + // FIXME this mix of both formats is ugly temporary hack Object.assign(moduleConfig, { ...resourceLoader.bootConfig, }); + moduleConfig.environmentVariables = environmentVariables; + if (resourceLoader.bootConfig.startupMemoryCache !== undefined) { moduleConfig.startupMemoryCache = resourceLoader.bootConfig.startupMemoryCache; } @@ -107,13 +133,13 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl for (const name in resources.runtimeAssets) { const asset = resources.runtimeAssets[name] as AssetEntry; asset.name = name; - asset.resolvedUrl = loaderHelpers.locateFile(name); + asset.resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(name)); assets.push(asset); } for (const name in resources.assembly) { const asset: AssetEntry = { name, - resolvedUrl: loaderHelpers.locateFile(name), + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name)), hash: resources.assembly[name], behavior: "assembly", }; @@ -123,18 +149,19 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl for (const name in resources.pdb) { const asset: AssetEntry = { name, - resolvedUrl: loaderHelpers.locateFile(name), + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name)), hash: resources.pdb[name], behavior: "pdb", }; assets.push(asset); } } - const applicationCulture = resourceLoader.startOptions.applicationCulture || (navigator.languages && navigator.languages[0]); - const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, applicationCulture); + const applicationCulture = resourceLoader.startOptions.applicationCulture || ENVIRONMENT_IS_WEB ? (navigator.languages && navigator.languages[0]) : Intl.DateTimeFormat().resolvedOptions().locale; + const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, moduleConfig, applicationCulture); let hasIcuData = false; for (const name in resources.runtime) { const behavior = behaviorByName(name) as any; + let loadRemote = false; if (behavior === "icu") { if (resourceLoader.bootConfig.icuDataMode === ICUDataMode.Invariant) { continue; @@ -142,6 +169,7 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl if (name !== icuDataResourceName) { continue; } + loadRemote = true; hasIcuData = true; } else if (behavior === "js-module-dotnet") { continue; @@ -149,26 +177,54 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl continue; } - const resolvedUrl = loaderHelpers.locateFile(name); + const resolvedUrl = appendUniqueQuery(loaderHelpers.locateFile(name)); const asset: AssetEntry = { name, resolvedUrl, hash: resources.runtime[name], behavior, + loadRemote }; assets.push(asset); } + + if (moduleConfig.loadAllSatelliteResources && resources.satelliteResources) { + for (const culture in resources.satelliteResources) { + for (const name in resources.satelliteResources[culture]) { + assets.push({ + name, + culture, + behavior: "resource", + hash: resources.satelliteResources[culture][name], + }); + } + } + } + for (let i = 0; i < resourceLoader.bootConfig.config.length; i++) { const config = resourceLoader.bootConfig.config[i]; if (config === "appsettings.json" || config === `appsettings.${applicationEnvironment}.json`) { assets.push({ name: config, - resolvedUrl: (document ? document.baseURI : "/") + config, + resolvedUrl: appendUniqueQuery((document ? document.baseURI : "/") + config), behavior: "vfs", }); } } + for (const virtualPath in resources.vfs) { + for (const name in resources.vfs[virtualPath]) { + const asset: AssetEntry = { + name, + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(name)), + hash: resources.vfs[virtualPath][name], + behavior: "vfs", + virtualPath + }; + assets.push(asset); + } + } + if (!hasIcuData) { moduleConfig.globalizationMode = "invariant"; } @@ -192,24 +248,26 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl } } -function getICUResourceName(bootConfig: BootJsonData, culture: string | undefined): string { +function getICUResourceName(bootConfig: BootJsonData, moduleConfig: MonoConfigInternal, culture: string | undefined): string { if (bootConfig.icuDataMode === ICUDataMode.Custom) { const icuFiles = Object .keys(bootConfig.resources.runtime) .filter(n => n.startsWith("icudt") && n.endsWith(".dat")); if (icuFiles.length === 1) { + moduleConfig.globalizationMode = "icu"; const customIcuFile = icuFiles[0]; return customIcuFile; } } - if (bootConfig.icuDataMode === ICUDataMode.Hybrid) - { + if (bootConfig.icuDataMode === ICUDataMode.Hybrid) { + moduleConfig.globalizationMode = "hybrid"; const reducedICUResourceName = "icudt_hybrid.dat"; return reducedICUResourceName; } if (!culture || bootConfig.icuDataMode === ICUDataMode.All) { + moduleConfig.globalizationMode = "icu"; const combinedICUResourceName = "icudt.dat"; return combinedICUResourceName; } diff --git a/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts b/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts index ea9b13b..efe700b 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Polyfill.ts @@ -13,9 +13,10 @@ export function toAbsoluteUri(relativeUri: string): string { export function hasDebuggingEnabled(bootConfig: BootJsonData): boolean { // Copied from blazor MonoDebugger.ts/attachDebuggerHotkey + if (!globalThis.navigator) { + return false; + } const hasReferencedPdbs = !!bootConfig.resources.pdb; - const debugBuild = bootConfig.debugBuild; - - return (hasReferencedPdbs || debugBuild) && (loaderHelpers.isChromium || loaderHelpers.isFirefox); + return (hasReferencedPdbs || bootConfig.debugBuild || bootConfig.debugLevel != 0) && (loaderHelpers.isChromium || loaderHelpers.isFirefox); } \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 695911e..4d997a1 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -94,6 +94,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi const loadedAnyConfig: any = (await configResponse.json()) || {}; if (loadedAnyConfig.resources) { // If we found boot config schema + normalizeConfig(); await initializeBootConfig(BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment), module, loaderHelpers.config.startupOptions); } else { // Otherwise we found mono config schema @@ -118,7 +119,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi } loaderHelpers.afterConfigLoaded.promise_control.resolve(loaderHelpers.config); } catch (err) { - const errMessage = `Failed to load config file ${configFilePath} ${err}`; + const errMessage = `Failed to load config file ${configFilePath} ${err} ${(err as Error)?.stack}`; loaderHelpers.config = module.config = { message: errMessage, error: err, isError: true }; loaderHelpers.abort_startup(errMessage, true); throw err; diff --git a/src/mono/wasm/runtime/loader/polyfills.ts b/src/mono/wasm/runtime/loader/polyfills.ts index 3c4315b..ae59010 100644 --- a/src/mono/wasm/runtime/loader/polyfills.ts +++ b/src/mono/wasm/runtime/loader/polyfills.ts @@ -73,7 +73,10 @@ export async function fetch_like(url: string, init?: RequestInit): Promise{ ok: true, - headers: [], + headers: { + length: 0, + get: () => null + }, url, arrayBuffer: () => arrayBuffer, json: () => JSON.parse(arrayBuffer), @@ -89,6 +92,10 @@ export async function fetch_like(url: string, init?: RequestInit): Promise{ ok: true, url, + headers: { + length: 0, + get: () => null + }, arrayBuffer: () => { return new Uint8Array(read(url, "binary")); }, @@ -104,6 +111,10 @@ export async function fetch_like(url: string, init?: RequestInit): Promise null + }, statusText: "ERR28: " + e, arrayBuffer: () => { throw e; }, json: () => { throw e; }, diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index a147f9f..6d8b480 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -395,13 +395,7 @@ async function createEmscriptenMain(): Promise { if (!module.configSrc && (!module.config || Object.keys(module.config).length === 0 || !module.config.assets)) { // if config file location nor assets are provided - if (loaderHelpers.scriptDirectory.indexOf("/_framework") == -1) { - // we are not inside _framework (= wasm template) - module.configSrc = "./_framework/blazor.boot.json"; - } else { - // blazor app - module.configSrc = "./blazor.boot.json"; - } + module.configSrc = "./blazor.boot.json"; } // download config diff --git a/src/mono/wasm/runtime/pthreads/browser/index.ts b/src/mono/wasm/runtime/pthreads/browser/index.ts index 1369a3f..b9dbdae 100644 --- a/src/mono/wasm/runtime/pthreads/browser/index.ts +++ b/src/mono/wasm/runtime/pthreads/browser/index.ts @@ -117,7 +117,7 @@ export function afterLoadWasmModuleToWorker(worker: Worker): void { export function preAllocatePThreadWorkerPool(defaultPthreadPoolSize: number, config: MonoConfig): void { const poolSizeSpec = config?.pthreadPoolSize; let n: number; - if (poolSizeSpec === undefined) { + if (poolSizeSpec === undefined || poolSizeSpec === null) { n = defaultPthreadPoolSize; } else { mono_assert(typeof poolSizeSpec === "number", "pthreadPoolSize must be a number"); diff --git a/src/mono/wasm/runtime/types/blazor.ts b/src/mono/wasm/runtime/types/blazor.ts index b0accc8..20cfb1a 100644 --- a/src/mono/wasm/runtime/types/blazor.ts +++ b/src/mono/wasm/runtime/types/blazor.ts @@ -7,12 +7,16 @@ export interface BootJsonData { readonly resources: ResourceGroups; /** Gets a value that determines if this boot config was produced from a non-published build (i.e. dotnet build or dotnet run) */ readonly debugBuild: boolean; + readonly debugLevel: number; readonly linkerEnabled: boolean; readonly cacheBootResources: boolean; readonly config: string[]; readonly icuDataMode: ICUDataMode; readonly startupMemoryCache: boolean | undefined; readonly runtimeOptions: string[] | undefined; + readonly environmentVariables?: { [name: string]: string }; + readonly diagnosticTracing?: boolean; + readonly pthreadPoolSize: number; // These properties are tacked on, and not found in the boot.json file modifiableAssemblies: string | null; @@ -22,6 +26,7 @@ export interface BootJsonData { export type BootJsonDataExtension = { [extensionName: string]: ResourceList }; export interface ResourceGroups { + readonly hash?: string; readonly assembly: ResourceList; readonly lazyAssembly: ResourceList; readonly pdb?: ResourceList; @@ -30,6 +35,7 @@ export interface ResourceGroups { readonly libraryInitializers?: ResourceList, readonly extensions?: BootJsonDataExtension readonly runtimeAssets: ExtendedResourceList; + readonly vfs?: { [virtualPath: string]: ResourceList }; } export type ResourceList = { [name: string]: string }; diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 992fea2..735e7c4 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -76,7 +76,8 @@ export type MonoConfigInternal = MonoConfig & { forwardConsoleLogsToWS?: boolean, asyncFlushOnExit?: boolean exitAfterSnapshot?: number, - startupOptions?: Partial + startupOptions?: Partial, + loadAllSatelliteResources?: boolean }; export type RunArguments = { diff --git a/src/mono/wasm/templates/templates/browser/main.js b/src/mono/wasm/templates/templates/browser/main.js index 6d9bd43..a073fc9 100644 --- a/src/mono/wasm/templates/templates/browser/main.js +++ b/src/mono/wasm/templates/templates/browser/main.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withDiagnosticTracing(false) diff --git a/src/mono/wasm/templates/templates/console/main.mjs b/src/mono/wasm/templates/templates/console/main.mjs index 6dd163a..ea131be 100644 --- a/src/mono/wasm/templates/templates/console/main.mjs +++ b/src/mono/wasm/templates/templates/console/main.mjs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withDiagnosticTracing(false) diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 1c82380..a6cd146 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -4,7 +4,7 @@ // // Run runtime tests under a JS shell or a browser // -import { dotnet, exit } from './dotnet.js'; +import { dotnet, exit } from './_framework/dotnet.js'; /***************************************************************************** @@ -258,7 +258,10 @@ function configureRuntime(dotnet, runArgs) { .withDiagnosticTracing(runArgs.diagnosticTracing) .withExitOnUnhandledError() .withExitCodeLogging() - .withElementOnExit(); + .withElementOnExit() + .withConfig({ + loadAllSatelliteResources: true + }); if (is_node) { dotnet @@ -296,7 +299,7 @@ async function dry_run(runArgs) { try { console.log("Silently starting separate runtime instance as another ES6 module to populate caches..."); // this separate instance of the ES6 module, in which we just populate the caches - const { dotnet } = await import('./dotnet.js?dry_run=true'); + const { dotnet } = await import('./_framework/dotnet.js?dry_run=true'); configureRuntime(dotnet, runArgs); // silent minimal startup await dotnet.withConfig({ diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 04068ec..bf4beb0 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -7,6 +7,8 @@ using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary /// Defines the structure of a Blazor boot JSON file /// @@ -40,6 +42,11 @@ public class BootJsonData public bool debugBuild { get; set; } /// + /// Gets a value that determines what level of debugging is configured. + /// + public int debugLevel { get; set; } + + /// /// Gets a value that determines if the linker is enabled. /// public bool linkerEnabled { get; set; } @@ -68,11 +75,31 @@ public class BootJsonData /// Gets or sets configuration extensions. /// public Dictionary> extensions { get; set; } + + /// + /// Gets or sets environment variables. + /// + public object environmentVariables { get; set; } + + /// + /// Gets or sets diagnostic tracing. + /// + public object diagnosticTracing { get; set; } + + /// + /// Gets or sets pthread pool size. + /// + public int? pthreadPoolSize { get; set; } } public class ResourcesData { /// + /// Gets a hash of all resources + /// + public string hash { get; set; } + + /// /// .NET Wasm runtime resources (dotnet.wasm, dotnet.js) etc. /// public ResourceHashesByNameDictionary runtime { get; set; } = new ResourceHashesByNameDictionary(); @@ -119,6 +146,12 @@ public class ResourcesData [DataMember(EmitDefaultValue = false)] public Dictionary runtimeAssets { get; set; } + [DataMember(EmitDefaultValue = false)] + public Dictionary vfs { get; set; } + + [DataMember(EmitDefaultValue = false)] + public List remoteSources { get; set; } + } public enum ICUDataMode : int @@ -155,8 +188,8 @@ public enum ICUDataMode : int public class AdditionalAsset { [DataMember(Name = "hash")] - public string Hash { get; set; } + public string hash { get; set; } [DataMember(Name = "behavior")] - public string Behavior { get; set; } + public string behavior { get; set; } } diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 699d346..9690097 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -103,6 +103,7 @@ public class GenerateWasmBootJson : Task entryAssembly = entryAssemblyName, cacheBootResources = CacheBootResources, debugBuild = DebugBuild, + debugLevel = DebugBuild ? 1 : 0, linkerEnabled = LinkerEnabled, resources = new ResourcesData(), config = new List(), @@ -334,8 +335,8 @@ public class GenerateWasmBootJson : Task Log.LogMessage(MessageImportance.Low, "Added resource '{0}' to the list of additional assets in the manifest.", resource.ItemSpec); additionalResources.Add(resourceName, new AdditionalAsset { - Hash = $"sha256-{resource.GetMetadata("FileHash")}", - Behavior = behavior + hash = $"sha256-{resource.GetMetadata("FileHash")}", + behavior = behavior }); } } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 8f18345..88cf5fa 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -13,6 +13,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using Microsoft.NET.Sdk.WebAssembly; namespace Microsoft.WebAssembly.Build.Tasks; @@ -22,6 +23,9 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask public bool IncludeThreadsWorker { get; set; } public int PThreadPoolSize { get; set; } public bool UseWebcil { get; set; } + public bool WasmIncludeFullIcuData { get; set; } + public string? WasmIcuDataFileName { get; set; } + public string? RuntimeAssetsLocation { get; set; } // // Extra json elements to add to _framework/blazor.boot.json @@ -37,107 +41,6 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask // public ITaskItem[]? ExtraConfig { get; set; } - private sealed class WasmAppConfig - { - [JsonPropertyName("mainAssemblyName")] - public string? MainAssemblyName { get; set; } - [JsonPropertyName("assemblyRootFolder")] - public string AssemblyRootFolder { get; set; } = "managed"; - [JsonPropertyName("debugLevel")] - public int DebugLevel { get; set; } = 0; - [JsonPropertyName("assets")] - public List Assets { get; } = new List(); - [JsonPropertyName("remoteSources")] - public List RemoteSources { get; set; } = new List(); - [JsonPropertyName("globalizationMode")] - public string? GlobalizationMode { get; set; } - [JsonExtensionData] - public Dictionary Extra { get; set; } = new(); - [JsonPropertyName("assetsHash")] - public string AssetsHash { get; set; } = "none"; - } - - private class AssetEntry - { - protected AssetEntry(string name, string hash, string behavior) - { - Name = name; - Behavior = behavior; - Hash = hash; - } - [JsonPropertyName("behavior")] - public string Behavior { get; init; } - [JsonPropertyName("name")] - public string Name { get; init; } - [JsonPropertyName("hash")] - public string? Hash { get; set; } - } - - private sealed class WasmEntry : AssetEntry - { - public WasmEntry(string name, string hash) : base(name, hash, "dotnetwasm") { } - } - - private sealed class LoaderJsEntry : AssetEntry - { - public LoaderJsEntry(string name, string hash) : base(name, hash, "js-module-dotnet") { } - } - - private sealed class NativeJsEntry : AssetEntry - { - public NativeJsEntry(string name, string hash) : base(name, hash, "js-module-native") { } - } - - private sealed class RuntimeJsEntry : AssetEntry - { - public RuntimeJsEntry(string name, string hash) : base(name, hash, "js-module-runtime") { } - } - - private sealed class ThreadsWorkerEntry : AssetEntry - { - public ThreadsWorkerEntry(string name, string hash) : base(name, hash, "js-module-threads") { } - } - - private sealed class AssemblyEntry : AssetEntry - { - public AssemblyEntry(string name, string hash) : base(name, hash, "assembly") { } - } - - private sealed class PdbEntry : AssetEntry - { - public PdbEntry(string name, string hash) : base(name, hash, "pdb") { } - } - - private sealed class SatelliteAssemblyEntry : AssetEntry - { - public SatelliteAssemblyEntry(string name, string hash, string culture) : base(name, hash, "resource") - { - CultureName = culture; - } - - [JsonPropertyName("culture")] - public string CultureName { get; set; } - } - - private sealed class VfsEntry : AssetEntry - { - public VfsEntry(string name, string hash) : base(name, hash, "vfs") { } - [JsonPropertyName("virtualPath")] - public string? VirtualPath { get; set; } - } - - private sealed class IcuData : AssetEntry - { - public IcuData(string name, string hash) : base(name, hash, "icu") { } - [JsonPropertyName("loadRemote")] - public bool LoadRemote { get; set; } - } - - private sealed class SymbolsData : AssetEntry - { - public SymbolsData(string name, string hash) : base(name, hash, "symbols") { } - } - protected override bool ValidateArguments() { if (!base.ValidateArguments()) @@ -155,6 +58,28 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask return true; } + private ICUDataMode GetICUDataMode() + { + // Invariant has always precedence + if (InvariantGlobalization) + return ICUDataMode.Invariant; + + // If user provided a path to a custom ICU data file, use it + if (!string.IsNullOrEmpty(WasmIcuDataFileName)) + return ICUDataMode.Custom; + + // Hybrid mode + if (HybridGlobalization) + return ICUDataMode.Hybrid; + + // If user requested to include full ICU data, use it + if (WasmIncludeFullIcuData) + return ICUDataMode.All; + + // Otherwise, use sharded mode + return ICUDataMode.Sharded; + } + protected override bool ExecuteInternal() { if (!ValidateArguments()) @@ -168,18 +93,26 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask } MainAssemblyName = Path.GetFileName(MainAssemblyName); - var config = new WasmAppConfig() + var bootConfig = new BootJsonData() { - MainAssemblyName = MainAssemblyName, - GlobalizationMode = InvariantGlobalization ? "invariant" : HybridGlobalization ? "hybrid" : "icu" + config = new(), + entryAssembly = MainAssemblyName, + icuDataMode = GetICUDataMode() }; // Create app - var asmRootPath = Path.Combine(AppDir, config.AssemblyRootFolder); + var runtimeAssetsPath = !string.IsNullOrEmpty(RuntimeAssetsLocation) + ? Path.Combine(AppDir, RuntimeAssetsLocation) + : AppDir; + + Log.LogMessage(MessageImportance.Low, $"Runtime assets output path {runtimeAssetsPath}"); + Directory.CreateDirectory(AppDir!); - Directory.CreateDirectory(asmRootPath); + Directory.CreateDirectory(runtimeAssetsPath); + if (UseWebcil) Log.LogMessage(MessageImportance.Normal, "Converting assemblies to Webcil"); + foreach (var assembly in _assemblies) { if (UseWebcil) @@ -187,7 +120,7 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask var tmpWebcil = Path.GetTempFileName(); var webcilWriter = Microsoft.WebAssembly.Build.Tasks.WebcilConverter.FromPortableExecutable(inputPath: assembly, outputPath: tmpWebcil, logger: Log); webcilWriter.ConvertToWebcil(); - var finalWebcil = Path.Combine(asmRootPath, Path.ChangeExtension(Path.GetFileName(assembly), Utils.WebcilInWasmExtension)); + var finalWebcil = Path.Combine(runtimeAssetsPath, Path.ChangeExtension(Path.GetFileName(assembly), Utils.WebcilInWasmExtension)); if (Utils.CopyIfDifferent(tmpWebcil, finalWebcil, useHash: true)) Log.LogMessage(MessageImportance.Low, $"Generated {finalWebcil} ."); else @@ -196,47 +129,42 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask } else { - FileCopyChecked(assembly, Path.Combine(asmRootPath, Path.GetFileName(assembly)), "Assemblies"); + FileCopyChecked(assembly, Path.Combine(runtimeAssetsPath, Path.GetFileName(assembly)), "Assemblies"); } if (DebugLevel != 0) { var pdb = assembly; pdb = Path.ChangeExtension(pdb, ".pdb"); if (File.Exists(pdb)) - FileCopyChecked(pdb, Path.Combine(asmRootPath, Path.GetFileName(pdb)), "Assemblies"); + FileCopyChecked(pdb, Path.Combine(runtimeAssetsPath, Path.GetFileName(pdb)), "Assemblies"); } } foreach (ITaskItem item in NativeAssets) { var name = Path.GetFileName(item.ItemSpec); - var dest = Path.Combine(AppDir!, name); + var dest = Path.Combine(runtimeAssetsPath, name); if (!FileCopyChecked(item.ItemSpec, dest, "NativeAssets")) return false; - if (name == "dotnet.js") - { - config.Assets.Add(new LoaderJsEntry (name, Utils.ComputeIntegrity(item.ItemSpec)) ); - } - else if (name == "dotnet.native.wasm") - { - config.Assets.Add(new WasmEntry(name, Utils.ComputeIntegrity(item.ItemSpec))); - } - else if (name == "dotnet.native.js") - { - config.Assets.Add(new NativeJsEntry (name, Utils.ComputeIntegrity(item.ItemSpec)) ); - } - else if (name == "dotnet.runtime.js") - { - config.Assets.Add(new RuntimeJsEntry (name, Utils.ComputeIntegrity(item.ItemSpec)) ); - } - else if (IncludeThreadsWorker && name == "dotnet.native.worker.js") - { - config.Assets.Add(new ThreadsWorkerEntry(name, Utils.ComputeIntegrity(item.ItemSpec))); - } - else if (name == "dotnet.native.js.symbols") + + if (!IncludeThreadsWorker && name == "dotnet.native.worker.js") + continue; + + var itemHash = Utils.ComputeIntegrity(item.ItemSpec); + + if (name.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetExtension(name), ".wasm", StringComparison.OrdinalIgnoreCase)) { - config.Assets.Add(new SymbolsData(name, Utils.ComputeIntegrity(item.ItemSpec))); + if (bootConfig.resources.runtimeAssets == null) + bootConfig.resources.runtimeAssets = new(); + + bootConfig.resources.runtimeAssets[name] = new() + { + hash = itemHash, + behavior = "dotnetwasm" + }; } + + bootConfig.resources.runtime[name] = itemHash; } string packageJsonPath = Path.Combine(AppDir, "package.json"); @@ -259,57 +187,74 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask { if (UseWebcil) { - assemblyPath = Path.Combine(asmRootPath, Path.ChangeExtension(Path.GetFileName(assembly), Utils.WebcilInWasmExtension)); + assemblyPath = Path.Combine(runtimeAssetsPath, Path.ChangeExtension(Path.GetFileName(assembly), Utils.WebcilInWasmExtension)); // For the hash, read the bytes from the webcil file, not the dll file. bytes = File.ReadAllBytes(assemblyPath); } - config.Assets.Add(new AssemblyEntry(Path.GetFileName(assemblyPath), Utils.ComputeIntegrity(bytes))); + bootConfig.resources.assembly[Path.GetFileName(assemblyPath)] = Utils.ComputeIntegrity(bytes); if (DebugLevel != 0) { - var pdb = assembly; - pdb = Path.ChangeExtension(pdb, ".pdb"); + var pdb = Path.ChangeExtension(assembly, ".pdb"); if (File.Exists(pdb)) - config.Assets.Add(new PdbEntry(Path.GetFileName(pdb), Utils.ComputeIntegrity(pdb))); + { + if (bootConfig.resources.pdb == null) + bootConfig.resources.pdb = new(); + + bootConfig.resources.pdb[Path.GetFileName(pdb)] = Utils.ComputeIntegrity(pdb); + } } } } - config.DebugLevel = DebugLevel; + bootConfig.debugBuild = DebugLevel > 0; + bootConfig.debugLevel = DebugLevel; ProcessSatelliteAssemblies(args => { + if (bootConfig.resources.satelliteResources == null) + bootConfig.resources.satelliteResources = new(); + string name = Path.GetFileName(args.fullPath); - string directory = Path.Combine(AppDir, config.AssemblyRootFolder, args.culture); - Directory.CreateDirectory(directory); + string cultureDirectory = Path.Combine(runtimeAssetsPath, args.culture); + Directory.CreateDirectory(cultureDirectory); if (UseWebcil) { var tmpWebcil = Path.GetTempFileName(); var webcilWriter = Microsoft.WebAssembly.Build.Tasks.WebcilConverter.FromPortableExecutable(inputPath: args.fullPath, outputPath: tmpWebcil, logger: Log); webcilWriter.ConvertToWebcil(); - var finalWebcil = Path.Combine(directory, Path.ChangeExtension(name, Utils.WebcilInWasmExtension)); + var finalWebcil = Path.Combine(cultureDirectory, Path.ChangeExtension(name, Utils.WebcilInWasmExtension)); if (Utils.CopyIfDifferent(tmpWebcil, finalWebcil, useHash: true)) Log.LogMessage(MessageImportance.Low, $"Generated {finalWebcil} ."); else Log.LogMessage(MessageImportance.Low, $"Skipped generating {finalWebcil} as the contents are unchanged."); _fileWrites.Add(finalWebcil); - config.Assets.Add(new SatelliteAssemblyEntry(Path.GetFileName(finalWebcil), Utils.ComputeIntegrity(finalWebcil), args.culture)); + + if (!bootConfig.resources.satelliteResources.TryGetValue(args.culture, out var cultureSatelliteResources)) + bootConfig.resources.satelliteResources[args.culture] = cultureSatelliteResources = new(); + + cultureSatelliteResources[Path.GetFileName(finalWebcil)] = Utils.ComputeIntegrity(finalWebcil); } else { - var satellitePath = Path.Combine(directory, name); + var satellitePath = Path.Combine(cultureDirectory, name); FileCopyChecked(args.fullPath, satellitePath, "SatelliteAssemblies"); - config.Assets.Add(new SatelliteAssemblyEntry(name, Utils.ComputeIntegrity(satellitePath), args.culture)); + + if (!bootConfig.resources.satelliteResources.TryGetValue(args.culture, out var cultureSatelliteResources)) + bootConfig.resources.satelliteResources[args.culture] = cultureSatelliteResources = new(); + + cultureSatelliteResources[name] = Utils.ComputeIntegrity(satellitePath); } }); if (FilesToIncludeInFileSystem.Length > 0) { - string supportFilesDir = Path.Combine(AppDir, "supportFiles"); + string supportFilesDir = Path.Combine(runtimeAssetsPath, "supportFiles"); Directory.CreateDirectory(supportFilesDir); var i = 0; StringDictionary targetPathTable = new(); + var vfs = new Dictionary>(); foreach (var item in FilesToIncludeInFileSystem) { string? targetPath = item.GetMetadata("TargetPath"); @@ -340,12 +285,14 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask var vfsPath = Path.Combine(supportFilesDir, generatedFileName); FileCopyChecked(item.ItemSpec, vfsPath, "FilesToIncludeInFileSystem"); - var asset = new VfsEntry($"supportFiles/{generatedFileName}", Utils.ComputeIntegrity(vfsPath)) + vfs[targetPath] = new() { - VirtualPath = targetPath + [$"supportFiles/{generatedFileName}"] = Utils.ComputeIntegrity(vfsPath) }; - config.Assets.Add(asset); } + + if (vfs.Count > 0) + bootConfig.resources.vfs = vfs; } if (!InvariantGlobalization) @@ -358,25 +305,29 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask Log.LogError($"Expected the file defined as ICU resource: {idfn} to exist but it does not."); return false; } - config.Assets.Add(new IcuData(Path.GetFileName(idfn), Utils.ComputeIntegrity(idfn)) { LoadRemote = loadRemote }); + + bootConfig.resources.runtime[Path.GetFileName(idfn)] = Utils.ComputeIntegrity(idfn); } } if (RemoteSources?.Length > 0) { + bootConfig.resources.remoteSources = new(); foreach (var source in RemoteSources) if (source != null && source.ItemSpec != null) - config.RemoteSources.Add(source.ItemSpec); + bootConfig.resources.remoteSources.Add(source.ItemSpec); } + var extraConfiguration = new Dictionary(); + if (PThreadPoolSize < -1) { throw new LogAsErrorException($"PThreadPoolSize must be -1, 0 or positive, but got {PThreadPoolSize}"); } else if (PThreadPoolSize > -1) { - config.Extra["pthreadPoolSize"] = PThreadPoolSize; + bootConfig.pthreadPoolSize = PThreadPoolSize; } foreach (ITaskItem extra in ExtraConfig ?? Enumerable.Empty()) @@ -385,28 +336,67 @@ public class WasmAppBuilder : WasmAppBuilderBaseTask if (!TryParseExtraConfigValue(extra, out object? valueObject)) return false; - config.Extra[name] = valueObject; + if (string.Equals(name, nameof(BootJsonData.environmentVariables), StringComparison.OrdinalIgnoreCase)) + { + bootConfig.environmentVariables = valueObject; + } + else if (string.Equals(name, nameof(BootJsonData.diagnosticTracing), StringComparison.OrdinalIgnoreCase)) + { + if (valueObject is bool boolValue || (valueObject is string stringValue && bool.TryParse(stringValue, out boolValue))) + bootConfig.diagnosticTracing = boolValue; + else + throw new LogAsErrorException($"Unsupported value '{valueObject}' of type '{valueObject?.GetType()?.FullName}' for extra config 'diagnosticTracing'."); + } + else + { + extraConfiguration[name] = valueObject; + } + } + + if (extraConfiguration.Count > 0) + { + bootConfig.extensions = new() + { + ["extra"] = extraConfiguration + }; } string tmpMonoConfigPath = Path.GetTempFileName(); using (var sw = File.CreateText(tmpMonoConfigPath)) { var sb = new StringBuilder(); - foreach (AssetEntry asset in config.Assets) + + static void AddDictionary(StringBuilder sb, Dictionary res) { - sb.Append(asset.Hash); + foreach (var asset in res) + sb.Append(asset.Value); } - config.AssetsHash = Utils.ComputeTextIntegrity(sb.ToString()); - var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }); + AddDictionary(sb, bootConfig.resources.assembly); + AddDictionary(sb, bootConfig.resources.runtime); + + if (bootConfig.resources.lazyAssembly != null) + AddDictionary(sb, bootConfig.resources.lazyAssembly); + + if (bootConfig.resources.satelliteResources != null) + { + foreach (var culture in bootConfig.resources.satelliteResources) + AddDictionary(sb, culture.Value); + } + + if (bootConfig.resources.vfs != null) + { + foreach (var entry in bootConfig.resources.vfs) + AddDictionary(sb, entry.Value); + } + + bootConfig.resources.hash = Utils.ComputeTextIntegrity(sb.ToString()); + + var json = JsonSerializer.Serialize(bootConfig, new JsonSerializerOptions { WriteIndented = true }); sw.Write(json); } - string monoConfigDir = Path.Combine(AppDir, "_framework"); - if (!Directory.Exists(monoConfigDir)) - Directory.CreateDirectory(monoConfigDir); - - string monoConfigPath = Path.Combine(monoConfigDir, "blazor.boot.json"); // TODO: Unify with Wasm SDK + string monoConfigPath = Path.Combine(runtimeAssetsPath, "blazor.boot.json"); // TODO: Unify with Wasm SDK Utils.CopyIfDifferent(tmpMonoConfigPath, monoConfigPath, useHash: false); _fileWrites.Add(monoConfigPath); diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 320586d..a873b4d 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -19,6 +19,7 @@ + diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/main.js b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/main.js index ed7d0f2..b1ca242 100644 --- a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/main.js +++ b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/main.js @@ -1,4 +1,4 @@ -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' function wasm_exit(exit_code) { var tests_done_elem = document.createElement("label"); diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/main.js b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/main.js index 249aa44..7ca0a21 100644 --- a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/main.js +++ b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/main.js @@ -1,4 +1,4 @@ -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' function wasm_exit(exit_code) { var tests_done_elem = document.createElement("label"); diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/StartupHook/main.js b/src/tests/FunctionalTests/WebAssembly/Browser/StartupHook/main.js index a7323e1..10858c7 100644 --- a/src/tests/FunctionalTests/WebAssembly/Browser/StartupHook/main.js +++ b/src/tests/FunctionalTests/WebAssembly/Browser/StartupHook/main.js @@ -1,4 +1,4 @@ -import { dotnet } from './dotnet.js' +import { dotnet } from './_framework/dotnet.js' function wasm_exit(exit_code) { var tests_done_elem = document.createElement("label");