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