diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 306cc52e987075..2d89733447d120 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -18,6 +18,7 @@ + _InitializeCommonProperties; _PrepareForWasmBuildNativeOnly; _WasmBuildNativeCore; @@ -38,13 +39,14 @@ + + <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" /> <_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" /> - <_EMSDKMissingPaths Condition="'$(_EMSDKMissingPaths)' == '' and ('$(EmscriptenSdkToolsPath)' == '' or !Exists('$(EmscriptenSdkToolsPath)'))">%24(EmscriptenSdkToolsPath)=$(EmscriptenSdkToolsPath) @@ -115,8 +117,9 @@ true - false - true + true + false + true false @@ -153,6 +156,8 @@ $(_EmccOptimizationFlagDefault) -O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault) + + <_EmccCompileRsp>$(_WasmIntermediateOutputPath)emcc-compile.rsp @@ -161,15 +166,41 @@ <_EmccCommonFlags Include="-s DISABLE_EXCEPTION_CATCHING=0" /> <_EmccCommonFlags Include="-g" Condition="'$(WasmNativeStrip)' == 'false'" /> <_EmccCommonFlags Include="-v" Condition="'$(EmccVerbose)' != 'false'" /> + + <_EmccIncludePaths Include="$(_WasmIntermediateOutputPath.TrimEnd('\/'))" /> + <_EmccIncludePaths Include="$(_WasmRuntimePackIncludeDir)mono-2.0" /> + <_EmccIncludePaths Include="$(_WasmRuntimePackIncludeDir)wasm" /> + + + <_EmccCFlags Include="$(EmccCompileOptimizationFlag)" /> + <_EmccCFlags Include="@(_EmccCommonFlags)" /> + + <_EmccCFlags Include="-DENABLE_AOT=1" Condition="'$(RunAOTCompilation)' == 'true'" /> + <_EmccCFlags Include="-DDRIVER_GEN=1" Condition="'$(RunAOTCompilation)' == 'true'" /> + <_EmccCFlags Include="-DINVARIANT_GLOBALIZATION=1" Condition="'$(InvariantGlobalization)' == 'true'" /> + <_EmccCFlags Include="-DLINK_ICALLS=1" Condition="'$(WasmLinkIcalls)' == 'true'" /> + <_EmccCFlags Include="-DCORE_BINDINGS" /> + <_EmccCFlags Include="-DGEN_PINVOKE=1" /> + + <_EmccCFlags Include=""-I%(_EmccIncludePaths.Identity)"" /> + <_EmccCFlags Include="-g" Condition="'$(WasmNativeDebugSymbols)' == 'true'" /> + + <_EmccCFlags Include="$(EmccExtraCFlags)" /> + + <_WasmRuntimePackSrcFile Include="$(_WasmRuntimePackSrcDir)*.c" /> + <_WasmRuntimePackSrcFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" /> + + <_DotnetJSSrcFile Include="$(_WasmRuntimePackSrcDir)\*.js" /> + <_WasmNativeFileForLinking Include="@(NativeFileReference)" /> - - <_DotnetJSSrcFile Include="$(_WasmRuntimePackSrcDir)\*.js" /> - + + <_WasmPInvokeModules Include="%(_WasmNativeFileForLinking.FileName)" Condition="'%(_WasmNativeFileForLinking.ScanForPInvokes)' != 'false'" /> + <_WasmPInvokeModules Include="libSystem.Native" /> <_WasmPInvokeModules Include="libSystem.IO.Compression.Native" /> <_WasmPInvokeModules Include="libSystem.Globalization.Native" /> @@ -194,38 +225,13 @@ - <_EmccIncludePaths Include="$(_WasmIntermediateOutputPath.TrimEnd('\/'))" /> - <_EmccIncludePaths Include="$(_WasmRuntimePackIncludeDir)mono-2.0" /> - <_EmccIncludePaths Include="$(_WasmRuntimePackIncludeDir)wasm" /> - - - <_EmccCFlags Include="$(EmccCompileOptimizationFlag)" /> - <_EmccCFlags Include="@(_EmccCommonFlags)" /> - - <_EmccCFlags Include="-DENABLE_AOT=1" Condition="'$(RunAOTCompilation)' == 'true'" /> - <_EmccCFlags Include="-DDRIVER_GEN=1" Condition="'$(RunAOTCompilation)' == 'true'" /> - <_EmccCFlags Include="-DINVARIANT_GLOBALIZATION=1" Condition="'$(InvariantGlobalization)' == 'true'" /> - <_EmccCFlags Include="-DLINK_ICALLS=1" Condition="'$(WasmLinkIcalls)' == 'true'" /> - <_EmccCFlags Include="-DCORE_BINDINGS" /> - <_EmccCFlags Include="-DGEN_PINVOKE=1" /> - <_EmccCFlags Include="-emit-llvm" /> - - <_EmccCFlags Include=""-I%(_EmccIncludePaths.Identity)"" /> - <_EmccCFlags Include="-g" Condition="'$(WasmNativeDebugSymbols)' == 'true'" /> - <_EmccCFlags Include="-s EXPORTED_FUNCTIONS='[@(_ExportedFunctions->'"%(Identity)"', ',')]'" Condition="@(_ExportedFunctions->Count()) > 0" /> - - <_EmccCFlags Include="$(EmccExtraCFlags)" /> - - <_WasmRuntimePackSrcFile Remove="@(_WasmRuntimePackSrcFile)" /> - <_WasmRuntimePackSrcFile Include="$(_WasmRuntimePackSrcDir)\*.c" /> - <_WasmRuntimePackSrcFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" /> + <_WasmSourceFileToCompile Remove="@(_WasmSourceFileToCompile)" /> <_WasmSourceFileToCompile Include="@(_WasmRuntimePackSrcFile)" /> <_EmBuilder Condition="$([MSBuild]::IsOSPlatform('WINDOWS'))">embuilder.bat <_EmBuilder Condition="!$([MSBuild]::IsOSPlatform('WINDOWS'))">embuilder.py - <_EmccCompileRsp>$(_WasmIntermediateOutputPath)emcc-compile.rsp @@ -235,6 +241,10 @@ + + + + @@ -271,6 +281,8 @@ Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)\*.a" Exclude="@(_MonoRuntimeComponentDontLink->'$(MicrosoftNetCoreAppRuntimePackRidNativeDir)\%(Identity)')" /> + <_WasmExtraJSFile Include="@(Content)" Condition="'%(Content.Extension)' == '.js'" /> + <_EmccLinkStepArgs Include="@(_EmccLDFlags)" /> <_EmccLinkStepArgs Include="--js-library "%(_DotnetJSSrcFile.Identity)"" /> <_EmccLinkStepArgs Include="--js-library "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' == 'js-library'" /> @@ -483,6 +495,5 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_ - - + diff --git a/src/mono/wasm/build/WasmApp.props b/src/mono/wasm/build/WasmApp.props index 10b939d6da73c0..bc45a6d54d73d8 100644 --- a/src/mono/wasm/build/WasmApp.props +++ b/src/mono/wasm/build/WasmApp.props @@ -7,6 +7,7 @@ Publish + _InitializeCommonProperties; _BeforeWasmBuildApp; _WasmResolveReferences; _WasmAotCompileApp; diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 10bd9ac7973c5e..534caf9025bf1a 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -80,6 +80,9 @@ false + + + <_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm')) <_BeforeWasmBuildAppDependsOn /> @@ -90,7 +93,7 @@ - + @@ -100,11 +103,12 @@ $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir))) $([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir), 'native')) - <_WasmRuntimePackIncludeDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'include')) <_WasmRuntimePackSrcDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'src')) + + @@ -115,8 +119,6 @@ $(TargetFileName) $([MSBuild]::NormalizeDirectory($(WasmAppDir))) - - <_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm')) diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs index bdd84c295ee7de..7ae4ca9920a2c4 100644 --- a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs @@ -22,6 +22,8 @@ public class PInvokeTableGenerator : Task [Required] public string? OutputPath { get; set; } + private static char[] s_charsToReplace = new[] { '.', '-', }; + public override bool Execute() { Log.LogMessage(MessageImportance.Normal, $"Generating pinvoke table to '{OutputPath}'."); @@ -101,7 +103,7 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules foreach (var module in modules.Keys) { - string symbol = module.Replace(".", "_") + "_imports"; + string symbol = ModuleNameToId(module) + "_imports"; w.WriteLine("static PinvokeImport " + symbol + " [] = {"); var assemblies_pinvokes = pinvokes. @@ -120,7 +122,7 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules w.Write("static void *pinvoke_tables[] = { "); foreach (var module in modules.Keys) { - string symbol = module.Replace(".", "_") + "_imports"; + string symbol = ModuleNameToId(module) + "_imports"; w.Write(symbol + ","); } w.WriteLine("};"); @@ -130,6 +132,18 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules w.Write("\"" + module + "\"" + ","); } w.WriteLine("};"); + + static string ModuleNameToId(string name) + { + if (name.IndexOfAny(s_charsToReplace) < 0) + return name; + + string fixedName = name; + foreach (char c in s_charsToReplace) + fixedName = fixedName.Replace(c, '_'); + + return fixedName; + } } private string MapType (Type t) diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index 7d211ae6189124..608aee17817159 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs @@ -69,6 +69,7 @@ static BuildTestBase() public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) { + Console.WriteLine($"{Environment.NewLine}-------- New test --------{Environment.NewLine}"); _buildContext = buildContext; _testOutput = output; _logPath = s_buildEnv.LogRootPath; // FIXME: @@ -216,7 +217,8 @@ protected static string RunWithXHarness(string testCommand, string testLogPath, [MemberNotNull(nameof(_projectDir), nameof(_logPath))] protected void InitPaths(string id) { - _projectDir = Path.Combine(AppContext.BaseDirectory, id); + if (_projectDir == null) + _projectDir = Path.Combine(AppContext.BaseDirectory, id); _logPath = Path.Combine(s_buildEnv.LogRootPath, id); Directory.CreateDirectory(_logPath); @@ -539,7 +541,6 @@ public static (int exitCode, string buildOutput) RunProcess(string path, var lastLines = outputBuilder.ToString().Split('\r', '\n').TakeLast(20); throw new XunitException($"Process timed out, output: {string.Join(Environment.NewLine, lastLines)}"); } - } lock (syncObj) diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs index 30e487f8171c07..989b90676ecdc8 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs @@ -31,9 +31,8 @@ private void NativeBuild(string projectNamePrefix, string projectContents, Build { string projectName = $"{projectNamePrefix}_{buildArgs.Config}_{buildArgs.AOT}"; - buildArgs = buildArgs with { ProjectName = projectName, ProjectFileContents = projectContents }; + buildArgs = buildArgs with { ProjectName = projectName }; buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "true"); - Console.WriteLine ($"-- args: {buildArgs}, name: {projectName}"); BuildProject(buildArgs, initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), projectContents), diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs new file mode 100644 index 00000000000000..e06c099b19c4bf --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class NativeLibraryTests : BuildTestBase + { + public NativeLibraryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Theory] + [BuildAndRun(aot: false)] + [BuildAndRun(aot: true)] + public void ProjectWithNativeReference(BuildArgs buildArgs, RunHost host, string id) + { + string projectName = $"AppUsingNativeLib-a"; + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, extraItems: ""); + + if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? _)) + { + InitPaths(id); + if (Directory.Exists(_projectDir)) + Directory.Delete(_projectDir, recursive: true); + + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "AppUsingNativeLib"), _projectDir); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", "native-lib.o"), Path.Combine(_projectDir, "native-lib.o")); + } + + BuildProject(buildArgs, + dotnetWasmFromRuntimePack: false, + id: id); + + string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, + test: output => {}, + host: host, id: id); + + Assert.Contains("print_line: 100", output); + Assert.Contains("from pinvoke: 142", output); + } + + [Theory] + [BuildAndRun(aot: false)] + [BuildAndRun(aot: true)] + public void ProjectUsingSkiaSharp(BuildArgs buildArgs, RunHost host, string id) + { + string projectName = $"AppUsingSkiaSharp"; + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, + extraItems: @$" + + + + + + "); + + string programText = @" +using System; +using SkiaSharp; + +public class Test +{ + public static int Main() + { + using SKFileStream skfs = new SKFileStream(""mono.png""); + using SKImage img = SKImage.FromEncodedData(skfs); + + Console.WriteLine ($""Size: {skfs.Length} Height: {img.Height}, Width: {img.Width}""); + return 0; + } +}"; + + BuildProject(buildArgs, + initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText), + dotnetWasmFromRuntimePack: false, + id: id); + + string output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 0, + test: output => {}, + host: host, id: id, + args: "mono.png"); + + Assert.Contains("Size: 26462 Height: 599, Width: 499", output); + } + } +} diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets index 19f795a01f2359..62e463bb199583 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/data/Workloads.Directory.Build.targets @@ -2,7 +2,6 @@ PrepareForWasmBuild;$(WasmBuildAppDependsOn) <_MicrosoftNetCoreAppRefDir>$(AppRefDir)\ - Microsoft.NETCore.App diff --git a/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/Program.cs b/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/Program.cs new file mode 100644 index 00000000000000..5134392c9d8ca3 --- /dev/null +++ b/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/Program.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System; +using System.Threading.Tasks; + +namespace SimpleConsole +{ + public class Test + { + public static int Main(string[] args) + { + Console.WriteLine ($"from pinvoke: {SimpleConsole.Test.print_line(100)}"); + return 0; + } + + [DllImport("native-lib")] + public static extern int print_line(int x); + } +} diff --git a/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/native-lib.cpp b/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/native-lib.cpp new file mode 100644 index 00000000000000..329a593279fe2d --- /dev/null +++ b/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/native-lib.cpp @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "native-lib.h" +#include + +int print_line(int x) +{ + printf("print_line: %d\n", x); + return 42 + x; +} diff --git a/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/native-lib.h b/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/native-lib.h new file mode 100644 index 00000000000000..826637b3a2d812 --- /dev/null +++ b/src/tests/BuildWasmApps/testassets/AppUsingNativeLib/native-lib.h @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef _NATIVELIB_H_ +#define _NATIVELIB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int print_line(int x); + +#ifdef __cplusplus +} +#endif + +#endif // _NATIVELIB_H_ diff --git a/src/tests/BuildWasmApps/testassets/AppUsingSkiaSharp/Program.cs b/src/tests/BuildWasmApps/testassets/AppUsingSkiaSharp/Program.cs new file mode 100644 index 00000000000000..0aeedffaf6d98a --- /dev/null +++ b/src/tests/BuildWasmApps/testassets/AppUsingSkiaSharp/Program.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using SkiaSharp; + +public class Test +{ + public static int Main() + { + using SKFileStream skfs = new SKFileStream("mono.png"); + using SKImage img = SKImage.FromEncodedData(skfs); + + Console.WriteLine ($"Size: {skfs.Length} Height: {img.Height}, Width: {img.Width}"); + return 0; + } +} diff --git a/src/tests/BuildWasmApps/testassets/mono.png b/src/tests/BuildWasmApps/testassets/mono.png new file mode 100644 index 00000000000000..7469aec9d1fcfa Binary files /dev/null and b/src/tests/BuildWasmApps/testassets/mono.png differ diff --git a/src/tests/BuildWasmApps/testassets/native-libs/native-lib.o b/src/tests/BuildWasmApps/testassets/native-libs/native-lib.o new file mode 100644 index 00000000000000..10ccf42c5ff239 Binary files /dev/null and b/src/tests/BuildWasmApps/testassets/native-libs/native-lib.o differ