diff --git a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs index cc7c11d5..9a9cedfc 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/BindingTest.cs @@ -24,6 +24,40 @@ public void InteropFunctionsImported () """); } + [Fact] + public void WhenDebugEnabledEmitsAndUsesExportImportHelpers () + { + Task.Debug = true; + AddAssembly(WithClass( + """ + [JSInvokable] public static Task InvAsync () => Task.FromResult(0); + [JSFunction] public static void Fun () {} + """ + )); + Execute(); + Contains("function getExport"); + Contains("function getImport"); + Contains("""getExport("Class_InvAsync")"""); + Contains("""getImport(this.funHandler, this.funSerializedHandler, "Class.fun")"""); + } + + [Fact] + public void WhenDebugDisabledDoesntEmitAndDoesntUseExportImportHelpers () + { + Task.Debug = false; + AddAssembly(WithClass( + """ + [JSInvokable] public static Task InvAsync () => Task.FromResult(0); + [JSFunction] public static void Fun () {} + """ + )); + Execute(); + DoesNotContain("function getExport"); + DoesNotContain("function getImport"); + DoesNotContain("""getExport("Class_InvAsync")"""); + DoesNotContain("""getImport(this.funHandler, this.funSerializedHandler, "Class.fun")"""); + } + [Fact] public void BindingForInvokableMethodIsGenerated () { @@ -34,7 +68,7 @@ public void BindingForInvokableMethodIsGenerated () export const Foo = { Bar: { Class: { - nya: () => getExports().Foo_Bar_Class_Nya() + nya: () => exports.Foo_Bar_Class_Nya() } } }; @@ -53,7 +87,7 @@ public void BindingForFunctionMethodIsGenerated () Class: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Foo.Bar.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } } } }; @@ -90,7 +124,7 @@ public void LibraryExportsNamespaceObject () """ export const Foo = { Class: { - bar: () => getExports().Foo_Class_Bar() + bar: () => exports.Foo_Class_Bar() } }; """); @@ -107,7 +141,7 @@ public void WhenSpaceContainDotsObjectCreatedForEachPart () Bar: { Nya: { Class: { - bar: () => getExports().Foo_Bar_Nya_Class_Bar() + bar: () => exports.Foo_Bar_Nya_Class_Bar() } } } @@ -129,13 +163,13 @@ public void WhenMultipleSpacesEachGetItsOwnObject () Class: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Bar.Nya.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } } } }; export const Foo = { Class: { - foo: () => getExports().Foo_Class_Foo() + foo: () => exports.Foo_Class_Foo() } }; """); @@ -148,12 +182,12 @@ public void WhenMultipleAssembliesWithEqualSpaceObjectDeclaredOnlyOnce () AddAssembly(WithClass("Foo", "[JSFunction] public static void Fun () {}")); Execute(); Once("export const Foo"); - Contains("bar: () => getExports().Foo_Class_Bar()"); + Contains("bar: () => exports.Foo_Class_Bar()"); Contains( """ get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Foo.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } """); } @@ -171,12 +205,12 @@ public void DifferentSpacesWithSameRootAssignedUnderSameObject () Class: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Nya.Bar.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } } }, Foo: { Class: { - foo: () => getExports().Nya_Foo_Class_Foo() + foo: () => exports.Nya_Foo_Class_Foo() } } }; @@ -195,13 +229,13 @@ public void DifferentSpacesStartingEquallyAreNotAssignedToSameObject () """ export const Foo = { Class: { - method: () => getExports().Foo_Class_Method() + method: () => exports.Foo_Class_Method() } }; export const FooBar = { Baz: { Class: { - method: () => getExports().FooBar_Baz_Class_Method() + method: () => exports.FooBar_Baz_Class_Method() } } }; @@ -221,13 +255,13 @@ public void BindingsFromMultipleSpacesAssignedToRespectiveObjects () Class: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Bar.Nya.Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } } } }; export const Foo = { Class: { - foo: () => getExports().Foo_Class_Foo() + foo: () => exports.Foo_Class_Foo() } }; """); @@ -243,12 +277,12 @@ public void BindingsFromMultipleClassesAssignedToRespectiveObjects () Contains( """ export const ClassA = { - inv: () => getExports().ClassA_Inv() + inv: () => exports.ClassA_Inv() }; export const ClassB = { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "ClassB.fun"); } + get funSerialized() { return this.funSerializedHandler; } }; """); } @@ -263,10 +297,10 @@ public void WhenNoSpaceBindingsAreAssignedToClassObject () Contains( """ export const Class = { - nya: () => getExports().Class_Nya(), + nya: () => exports.Class_Nya(), get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = () => this.funHandler(); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Class.fun"); } + get funSerialized() { return this.funSerializedHandler; } }; """); } @@ -279,7 +313,7 @@ public void VariablesConflictingWithJSTypesAreRenamed () Contains( """ export const Class = { - fun: (fn) => getExports().Class_Fun(fn) + fun: (fn) => exports.Class_Fun(fn) }; """); } @@ -297,10 +331,10 @@ public void SerializesUserType () Contains( """ export const Class = { - foo: (i) => deserialize(getExports().Class_Foo(serialize(i, Info)), Info), + foo: (i) => deserialize(exports.Class_Foo(serialize(i, Info)), Info), get bar() { return this.barHandler; }, set bar(handler) { this.barHandler = handler; this.barSerializedHandler = (i) => serialize(this.barHandler(deserialize(i, Info)), Info); }, - get barSerialized() { return getImport(this.barHandler, this.barSerializedHandler, "Class.bar"); }, + get barSerialized() { return this.barSerializedHandler; }, baz: new Event(), bazSerialized: (i) => Class.baz.broadcast(deserialize(i, InfoArray) ?? undefined), yaz: new Event(), @@ -404,14 +438,14 @@ public void AwaitsWhenSerializingInAsyncFunctions () Contains( """ export const Class = { - foo: async (i) => deserialize(await getExports().Class_Foo(serialize(i, Info)), Info), + foo: async (i) => deserialize(await exports.Class_Foo(serialize(i, Info)), Info), get bar() { return this.barHandler; }, set bar(handler) { this.barHandler = handler; this.barSerializedHandler = async (i) => serialize(await this.barHandler(deserialize(i, Info)), Info); }, - get barSerialized() { return getImport(this.barHandler, this.barSerializedHandler, "Class.bar"); }, - baz: async () => deserialize(await getExports().Class_Baz(), System_Collections_Generic_IReadOnlyList_Of_Info), + get barSerialized() { return this.barSerializedHandler; }, + baz: async () => deserialize(await exports.Class_Baz(), System_Collections_Generic_IReadOnlyList_Of_Info), get yaz() { return this.yazHandler; }, set yaz(handler) { this.yazHandler = handler; this.yazSerializedHandler = async () => serialize(await this.yazHandler(), System_Collections_Generic_IReadOnlyList_Of_Info); }, - get yazSerialized() { return getImport(this.yazHandler, this.yazSerializedHandler, "Class.yaz"); } + get yazSerialized() { return this.yazSerializedHandler; } }; """); } @@ -427,7 +461,7 @@ public void ExportedEnumsAreDeclaredInJS () """ export const n = { Class: { - getFoo: () => deserialize(getExports().n_Class_GetFoo(), n_Foo), + getFoo: () => deserialize(exports.n_Class_GetFoo(), n_Foo), Foo: { "0": "A", "1": "B", "A": 0, "B": 1 } } }; @@ -460,7 +494,7 @@ public void CustomEnumIndexesArePreservedInJS () export const n = { Foo: { "1": "A", "6": "B", "A": 1, "B": 6 }, Class: { - getFoo: () => deserialize(getExports().n_Class_GetFoo(), n_Foo) + getFoo: () => deserialize(exports.n_Class_GetFoo(), n_Foo) } }; """); @@ -485,12 +519,12 @@ public void RespectsSpacePreference () Class: { get onFun() { return this.onFunHandler; }, set onFun(handler) { this.onFunHandler = handler; this.onFunSerializedHandler = () => this.onFunHandler(); }, - get onFunSerialized() { return getImport(this.onFunHandler, this.onFunSerializedHandler, "Fun.Class.onFun"); } + get onFunSerialized() { return this.onFunSerializedHandler; } } }; export const Nya = { Class: { - getNya: () => getExports().Foo_Bar_Nya_Class_GetNya() + getNya: () => exports.Foo_Bar_Nya_Class_GetNya() } }; """); @@ -508,7 +542,7 @@ public void RespectsFunctionPreference () """ export const Space = { Class: { - foo: () => getExports().Space_Class_Inv() + foo: () => exports.Space_Class_Inv() } }; """); @@ -548,12 +582,12 @@ public interface IImported { void Fun (string s, Enum e); void NotifyEvt (string export const Space = { Enum: { "0": "A", "1": "B", "A": 0, "B": 1 }, Exported: { - inv: (s, e) => getExports().Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e, Space_Enum)) + inv: (s, e) => exports.Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e, Space_Enum)) }, Imported: { get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = (s, e) => this.funHandler(s, deserialize(e, Space_Enum)); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Space.Imported.fun"); }, + get funSerialized() { return this.funSerializedHandler; }, onEvt: new Event(), onEvtSerialized: (s, e) => Space.Imported.onEvt.broadcast(s, deserialize(e, Space_Enum)) } @@ -581,10 +615,10 @@ public interface IImported { void Fun (string s, Enum e); void NotifyEvt (string Contains( """ export const Foo = { - inv: (s, e) => getExports().Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e, Space_Enum)), + inv: (s, e) => exports.Bootsharp_Generated_Exports_Space_JSExported_Inv(s, serialize(e, Space_Enum)), get fun() { return this.funHandler; }, set fun(handler) { this.funHandler = handler; this.funSerializedHandler = (s, e) => this.funHandler(s, deserialize(e, Space_Enum)); }, - get funSerialized() { return getImport(this.funHandler, this.funSerializedHandler, "Foo.fun"); }, + get funSerialized() { return this.funSerializedHandler; }, onEvt: new Event(), onEvtSerialized: (s, e) => Foo.onEvt.broadcast(s, deserialize(e, Space_Enum)), Enum: { "0": "A", "1": "B", "A": 0, "B": 1 } @@ -629,20 +663,20 @@ class JSExported { Contains( """ export const Class = { - getExported: async (inst) => new Space_JSExported(await getExports().Class_GetExported(registerInstance(inst))), + getExported: async (inst) => new Space_JSExported(await exports.Class_GetExported(registerInstance(inst))), get getImported() { return this.getImportedHandler; }, set getImported(handler) { this.getImportedHandler = handler; this.getImportedSerializedHandler = async (inst) => registerInstance(await this.getImportedHandler(new JSExported(inst))); }, - get getImportedSerialized() { return getImport(this.getImportedHandler, this.getImportedSerializedHandler, "Class.getImported"); } + get getImportedSerialized() { return this.getImportedSerializedHandler; } }; export const Exported = { - inv: (_id, str) => deserialize(getExports().Bootsharp_Generated_Exports_JSExported_Inv(_id, str), Enum) + inv: (_id, str) => deserialize(exports.Bootsharp_Generated_Exports_JSExported_Inv(_id, str), Enum) }; export const Imported = { onEvtSerialized: (_id, str) => getInstance(_id).onEvt.broadcast(str) }; export const Space = { Exported: { - inv: (_id, en) => getExports().Bootsharp_Generated_Exports_Space_JSExported_Inv(_id, serialize(en, Enum)) + inv: (_id, en) => exports.Bootsharp_Generated_Exports_Space_JSExported_Inv(_id, serialize(en, Enum)) }, Imported: { funSerialized: (_id, en) => serialize(getInstance(_id).fun(deserialize(en, Enum)), Enum) diff --git a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs index 896f09e3..5a236306 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs @@ -40,12 +40,13 @@ protected override void AddAssembly (string assemblyName, params MockSource[] so private BootsharpPack CreateTask () => new() { BuildDirectory = Project.Root, + DebugDirectory = Project.Root, InspectedDirectory = Project.Root, EntryAssemblyName = "System.Runtime.dll", BuildEngine = Engine, - TrimmingEnabled = false, EmbedBinaries = false, Threading = false, - LLVM = false + LLVM = false, + Debug = false }; } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs index af2edb48..c4bc34b5 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/ResourceTest.cs @@ -31,4 +31,28 @@ public void BinariesNotEmbeddedWhenDisabled () Contains("""wasm: { name: "dotnet.native.wasm", content: undefined },"""); Contains("""{ name: "Foo.wasm", content: undefined"""); } + + [Fact] + public void WhenDebugEnabledDebugArtifactsIncluded () + { + Task.Debug = true; + AddAssembly("Foo.dll"); + Project.WriteFile("Foo.pdb", "MockPdbContent"); + Project.WriteFile("dotnet.native.js.symbols", "MockSymbolsContent"); + Execute(); + Contains("""{ name: "Foo.pdb", content: undefined }"""); + Contains("""{ name: "dotnet.native.js.symbols", content: undefined }"""); + } + + [Fact] + public void WhenDebugDisabledDebugArtifactsNotIncluded () + { + Task.Debug = false; + AddAssembly("Foo.dll"); + Project.WriteFile("Foo.pdb", "MockPdbContent"); + Project.WriteFile("dotnet.native.js.symbols", "MockSymbolsContent"); + Execute(); + DoesNotContain("""{ name: "Foo.pdb", content: undefined }"""); + DoesNotContain("""{ name: "dotnet.native.js.symbols", content: undefined }"""); + } } diff --git a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs index b9b6d111..f52e73e2 100644 --- a/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/BindingGenerator/BindingGenerator.cs @@ -2,7 +2,7 @@ namespace Bootsharp.Publish; -internal sealed class BindingGenerator (Preferences prefs) +internal sealed class BindingGenerator (Preferences prefs, bool debug) { private record Binding (MethodMeta? Method, Type? Enum, string Namespace); @@ -35,6 +35,12 @@ public string Generate (SolutionInspection inspection) EmitImports(); builder.Append("\n\n"); + if (debug) + { + EmitDebugHelpers(); + builder.Append("\n\n"); + } + builder.Append(serdeGenerator.Generate(inspection.Serialized)); builder.Append('\n'); @@ -54,9 +60,30 @@ private void EmitImports () import { Event } from "./event"; import { registerInstance, getInstance, disposeOnFinalize } from "./instances"; import { serialize, deserialize, binary, types } from "./serialization"; + """ + ); + } - function getExports() { if (exports == null) throw Error("Boot the runtime before invoking C# APIs."); return exports; } - function getImport(handler, serializedHandler, name) { if (typeof handler !== "function") throw Error(`Failed to invoke '${name}' from C#. Make sure to assign the function in JavaScript.`); return serializedHandler; } + private void EmitDebugHelpers () + { + builder.Append( + """ + function getExport(name) { + return (...args) => { + if (exports == null) throw Error("Boot the runtime before invoking C# APIs."); + let result; + try { result = exports[name](...args); } + catch (error) { throw Error(`${error.message}\n${error.stack}`); } + if (typeof result?.then === "function") + return result.catch(error => { throw Error(`${error.message}\n${error.stack}`); }); + return result; + }; + } + + function getImport(handler, serializedHandler, name) { + if (typeof handler !== "function") throw Error(`Failed to invoke '${name}' from C#. Make sure to assign the function in JavaScript.`); + return serializedHandler; + } """ ); } @@ -123,7 +150,8 @@ private void EmitInvokable (MethodMeta method) { var instanced = IsInstanced(method); var wait = ShouldWait(method); - var endpoint = $"getExports().{method.Space.Replace('.', '_')}_{method.Name}"; + var fn = $"{method.Space.Replace('.', '_')}_{method.Name}"; + var endpoint = debug ? $"""getExport("{fn}")""" : $"exports.{fn}"; var funcArgs = string.Join(", ", method.Arguments.Select(a => a.JSName)); if (instanced) funcArgs = PrependInstanceIdArgName(funcArgs); var invArgs = string.Join(", ", method.Arguments.Select(BuildInvArg)); @@ -158,11 +186,11 @@ private void EmitFunction (MethodMeta method) if (instanced) builder.Append($"{Break()}{name}Serialized: {serdeHandler}"); else { - var set = $"{handler} = handler; this.{name}SerializedHandler = {serdeHandler};"; - var serde = $"return getImport({handler}, this.{name}SerializedHandler, \"{binding.Namespace}.{name}\");"; + var serde = $"this.{name}SerializedHandler"; + var serdeExp = debug ? $"getImport({handler}, {serde}, \"{binding.Namespace}.{name}\")" : serde; builder.Append($"{Break()}get {name}() {{ return {handler}; }}"); - builder.Append($"{Break()}set {name}(handler) {{ {set} }}"); - builder.Append($"{Break()}get {name}Serialized() {{ {serde} }}"); + builder.Append($"{Break()}set {name}(handler) {{ {handler} = handler; {serde} = {serdeHandler}; }}"); + builder.Append($"{Break()}get {name}Serialized() {{ return {serdeExp}; }}"); } string BuildInvArg (ArgumentMeta arg) diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index b17eb1d4..6db1da68 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -6,12 +6,13 @@ namespace Bootsharp.Publish; public sealed class BootsharpPack : Microsoft.Build.Utilities.Task { public required string BuildDirectory { get; set; } + public required string DebugDirectory { get; set; } public required string InspectedDirectory { get; set; } public required string EntryAssemblyName { get; set; } - public required bool TrimmingEnabled { get; set; } public required bool EmbedBinaries { get; set; } public required bool Threading { get; set; } public required bool LLVM { get; set; } + public required bool Debug { get; set; } public override bool Execute () { @@ -49,7 +50,7 @@ IEnumerable GetFiles () private void GenerateBindings (Preferences prefs, SolutionInspection inspection) { - var generator = new BindingGenerator(prefs); + var generator = new BindingGenerator(prefs, Debug); var content = generator.Generate(inspection); File.WriteAllText(Path.Combine(BuildDirectory, "bindings.g.js"), content); } @@ -63,8 +64,8 @@ private void GenerateDeclarations (Preferences prefs, SolutionInspection inspect private void GenerateResources (SolutionInspection inspection) { - var generator = new ResourceGenerator(EntryAssemblyName, EmbedBinaries); - var content = generator.Generate(BuildDirectory); + var generator = new ResourceGenerator(EntryAssemblyName, EmbedBinaries, Debug); + var content = generator.Generate(BuildDirectory, DebugDirectory); File.WriteAllText(Path.Combine(BuildDirectory, "resources.g.js"), content); } diff --git a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs index 9c928b42..2806eab5 100644 --- a/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs +++ b/src/cs/Bootsharp.Publish/Pack/ResourceGenerator.cs @@ -1,15 +1,24 @@ namespace Bootsharp.Publish; -internal sealed class ResourceGenerator (string entryAssemblyName, bool embed) +internal sealed class ResourceGenerator (string entryAssemblyName, bool embed, bool debug) { private readonly List assemblies = []; + private readonly List symbols = []; + private readonly List pdb = []; private string wasm = null!; - public string Generate (string buildDir) + public string Generate (string buildDir, string debugDir) { - foreach (var path in Directory.GetFiles(buildDir, "*.wasm")) + foreach (var path in Directory.GetFiles(buildDir, "*.wasm").Order()) if (path.EndsWith("dotnet.native.wasm")) wasm = BuildBin(path); else assemblies.Add(BuildBin(path)); + if (debug) + { + foreach (var path in Directory.GetFiles(debugDir, "*.symbols").Order()) + symbols.Add(BuildBin(path)); + foreach (var path in Directory.GetFiles(debugDir, "*.pdb").Order()) + pdb.Add(BuildBin(path)); + } return $$""" export default { @@ -17,6 +26,12 @@ public string Generate (string buildDir) assemblies: [ {{JoinLines(assemblies, 2, ",\n")}} ], + symbols: [ + {{JoinLines(symbols, 2, ",\n")}} + ], + pdb: [ + {{JoinLines(pdb, 2, ",\n")}} + ], entryAssemblyName: "{{entryAssemblyName}}" }; """; diff --git a/src/cs/Bootsharp/Build/Bootsharp.props b/src/cs/Bootsharp/Build/Bootsharp.props index 422d84dd..98fcbeab 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.props +++ b/src/cs/Bootsharp/Build/Bootsharp.props @@ -30,11 +30,13 @@ + + $([MSBuild]::ValueOrDefault('$(Configuration)', '').Equals('Debug')) - $([MSBuild]::ValueOrDefault('$(Configuration)', '').Equals('Release')) + !$(BsDebug) - + true $(EmccFlags) -O3 @@ -77,7 +79,7 @@ false - + false false @@ -94,6 +96,6 @@ + Condition="$(BsLlvm)"/> diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index 25ecb60d..27800cbd 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -1,37 +1,37 @@ - $(MSBuildThisFileDirectory)../ - $(BootsharpRoot)/js - $(BootsharpRoot)/llvm - $(BootsharpRoot)/tasks/Bootsharp.Publish.dll - $(IntermediateOutputPath)bootsharp - $(BootsharpIntermediateDirectory)/Interfaces.g.cs - $(BootsharpIntermediateDirectory)/Dependencies.g.cs - $(BootsharpIntermediateDirectory)/Serializer.g.cs - $(BootsharpIntermediateDirectory)/Interop.g.cs - $(AssemblyName).dll - CopyNativeBinary - WasmNestedPublishApp + $(MSBuildThisFileDirectory)../ + $(BsRoot)/js + $(BsRoot)/llvm + $(BsRoot)/tasks/Bootsharp.Publish.dll + $(IntermediateOutputPath)bootsharp + $(BsIntermediateDir)/Interfaces.g.cs + $(BsIntermediateDir)/Dependencies.g.cs + $(BsIntermediateDir)/Serializer.g.cs + $(BsIntermediateDir)/Interop.g.cs + $(AssemblyName).dll + CopyNativeBinary + WasmNestedPublishApp - - + + + Include="$(BsLlvmDir)/runtime.win-x64.microsoft.dotnet.ilcompiler.llvm"> runtime.win-x64.Microsoft.DotNet.ILCompiler.LLVM - $(BootsharpLlvmDirectory)/runtime.win-x64.microsoft.dotnet.ilcompiler.llvm + $(BsLlvmDir)/runtime.win-x64.microsoft.dotnet.ilcompiler.llvm + Include="$(BsLlvmDir)/runtime.linux-x64.microsoft.dotnet.ilcompiler.llvm"> runtime.linux-x64.Microsoft.DotNet.ILCompiler.LLVM - $(BootsharpLlvmDirectory)/runtime.linux-x64.microsoft.dotnet.ilcompiler.llvm + $(BsLlvmDir)/runtime.linux-x64.microsoft.dotnet.ilcompiler.llvm - + runtime.browser-wasm.Microsoft.DotNet.ILCompiler.LLVM - $(BootsharpLlvmDirectory)/runtime.browser-wasm.microsoft.dotnet.ilcompiler.llvm + $(BsLlvmDir)/runtime.browser-wasm.microsoft.dotnet.ilcompiler.llvm @@ -41,147 +41,143 @@ + TaskName="Bootsharp.Publish.BootsharpEmit" AssemblyFile="$(BsPublishAssembly)"/> + TaskName="Bootsharp.Publish.BootsharpPack" AssemblyFile="$(BsPublishAssembly)"/> - - - - $(DefineConstants);BOOTSHARP_EMITTED - + - + + EntryAssemblyName="$(BsEntryAssembly)" + InterfacesFilePath="$(BsInterfacesFilePath)" + DependenciesFilePath="$(BsDependenciesFilePath)" + SerializerFilePath="$(BsSerializerFilePath)" + InteropFilePath="$(BsInteropFilePath)"/> - - - - - - - - + + + + + + + + - + - + - $(PublishDir) - $(WasmAppDir)/$(WasmRuntimeAssetsLocation) - $(BaseOutputPath.Replace('\', '/')) - $(BootSharpBaseOutputPath)$(BootsharpName) + $(PublishDir) + $(WasmAppDir)/$(WasmRuntimeAssetsLocation) + $(BaseOutputPath.Replace('\', '/')) + $(BsBaseOutputPath)$(BootsharpName) $(BootsharpPublishDirectory)/types $(BootsharpPublishDirectory)/bin $(BootsharpPublishDirectory) - npx --yes rollup index.js -o index.mjs -f es -e process,module,fs/promises --output.inlineDynamicImports - true - false - false + true + false + false + --sourcemap + npx --yes rollup index.js -o index.mjs -f es -e process,module,fs/promises --output.inlineDynamicImports $(BsBundleMapsArg) - + - + - - + - - + Threading="$(BsThreading)" + LLVM="$(BsLlvm)" + Debug="$(BsDebug)"/> - - - - - - - + + + + + + + - - - - - - - - + - - - + + + - - - + + + - + { const embed = root == null; const mt = !embed && (await import("./dotnet.g")).mt; - const [wasm, native, runtime, assemblies] = await Promise.all([ + const [wasm, native, runtime, assemblies, symbols, pdb] = await Promise.all([ resolveWasm(), resolveModule("dotnet.native.js", embed ? getNative : undefined), resolveModule("dotnet.runtime.js", embed ? getRuntime : undefined), - Promise.all(resources.assemblies.map(resolveAssembly)) + Promise.all(resources.assemblies.map(resolveAssembly)), + Promise.all(resources.symbols.map(resolveSymbols)), + Promise.all(resources.pdb.map(resolvePdb)) ]); return { resources: { @@ -20,26 +22,29 @@ export async function buildConfig(resources: BootResources, root?: string): Prom jsModuleNative: [native], jsModuleRuntime: [runtime], jsModuleWorker: mt ? [await resolveModule("dotnet.native.worker.mjs")] : undefined, - assembly: assemblies + assembly: assemblies, + wasmSymbols: symbols, + pdb: pdb }, - mainAssemblyName: resources.entryAssemblyName + mainAssemblyName: resources.entryAssemblyName, + debugLevel: resources.symbols.length > 0 ? -1 : undefined }; - async function resolveWasm(): Promise { + async function resolveWasm(): Promise { return { name: resources.wasm.name, buffer: await resolveBuffer(resources.wasm) }; } - async function resolveModule(name: string, embed?: () => Promise): Promise { + async function resolveModule(name: string, embed?: () => Promise): Promise { return { name, moduleExports: embed ? await embed() : undefined }; } - async function resolveAssembly(res: BinaryResource): Promise { + async function resolveAssembly(res: BinaryResource): Promise { return { name: res.name, virtualPath: res.name, @@ -47,6 +52,27 @@ export async function buildConfig(resources: BootResources, root?: string): Prom }; } + async function resolvePdb(res: BinaryResource): Promise { + return { + name: res.name, + virtualPath: res.name, + buffer: await resolveBuffer(res) + }; + } + + async function resolveSymbols(res: BinaryResource): Promise { + // Use buffer similar to the other assets once https://github.com/dotnet/runtime/pull/127087 is merged. + const txt = new TextDecoder("utf-8").decode(await resolveBuffer(res)); + return { + name: res.name, + pendingDownload: { + name: res.name, + url: res.name, + response: Promise.resolve(new Response(txt, { status: 200 })) + } + }; + } + async function resolveBuffer(res: BinaryResource): Promise { if (typeof res.content === "string") return decodeBase64(res.content); if (res.content !== undefined) return res.content.buffer; diff --git a/src/js/src/modules.ts b/src/js/src/modules.ts index 4a7bc9d2..02435911 100644 --- a/src/js/src/modules.ts +++ b/src/js/src/modules.ts @@ -3,9 +3,11 @@ import type { ModuleAPI, MonoConfig } from "./dotnet.g.d.ts"; export type * from "./dotnet.g.d.ts"; export type RuntimeConfig = MonoConfig; export type RuntimeResources = NonNullable; -export type RuntimeWasm = NonNullable[number]; -export type RuntimeModule = NonNullable[number]; -export type RuntimeAssembly = NonNullable[number]; +export type WasmAsset = NonNullable[number]; +export type ModuleAsset = NonNullable[number]; +export type AssemblyAsset = NonNullable[number]; +export type PdbAsset = NonNullable[number]; +export type SymbolsAsset = NonNullable[number]; /** Fetches the main dotnet module (dotnet.js). */ export async function getMain(root?: string): Promise { diff --git a/src/js/src/resources.ts b/src/js/src/resources.ts index 3229c5f9..8a75280d 100644 --- a/src/js/src/resources.ts +++ b/src/js/src/resources.ts @@ -6,6 +6,10 @@ export type BootResources = { readonly wasm: BinaryResource; /** Compiled .NET assemblies. */ readonly assemblies: BinaryResource[]; + /** WASM debug symbols. */ + readonly symbols: BinaryResource[]; + /** PDB debug artifacts. */ + readonly pdb: BinaryResource[]; /** Name of the entry (main) assembly, with .dll extension. */ readonly entryAssemblyName: string; } diff --git a/src/js/test/cs/Test/Test.csproj b/src/js/test/cs/Test/Test.csproj index 89f1fe9f..7b4734a3 100644 --- a/src/js/test/cs/Test/Test.csproj +++ b/src/js/test/cs/Test/Test.csproj @@ -7,7 +7,7 @@ true bin/codegen false - npx --yes rollup index.js -d ./ -f es -e process,module,fs/promises --output.preserveModules --entryFileNames [name].mjs + npx --yes rollup index.js -d ./ -f es -e process,module,fs/promises --output.preserveModules --entryFileNames [name].mjs --sourcemap diff --git a/src/js/test/spec/boot.spec.ts b/src/js/test/spec/boot.spec.ts index cbb7cd0f..9cc0ab0d 100644 --- a/src/js/test/spec/boot.spec.ts +++ b/src/js/test/spec/boot.spec.ts @@ -37,6 +37,17 @@ describe("boot", () => { expect(config.resources!.jsModuleNative[0].moduleExports).toBeDefined(); expect(config.resources!.jsModuleRuntime[0].moduleExports).toBeDefined(); }); + it("enables debugging when debugging resources exist", async () => { + const { side: { bootsharp }, root } = await setup(); + const config = await bootsharp.dotnet.buildConfig(bootsharp.resources, root); + expect(config.debugLevel).not.toBeUndefined(); + }); + it("doesn't enable debugging when missing debug artifacts", async () => { + const { side: { bootsharp }, root } = await setup(); + const resources = { ...bootsharp.resources, symbols: [], pdb: [] }; + const config = await bootsharp.dotnet.buildConfig(resources, root); + expect(config.debugLevel).toBeUndefined(); + }); it("throws when missing boot resource", async () => { const { side: { bootsharp } } = await setup(); await expect(bootsharp.dotnet.buildConfig(bootsharp.resources))