</_WasmBuildNativeCoreDependsOn>
<WasmBuildNativeOnlyDependsOn>
+ _InitializeCommonProperties;
_PrepareForWasmBuildNativeOnly;
_WasmBuildNativeCore;
</WasmBuildNativeOnlyDependsOn>
<Target Name="WasmBuildNativeOnly" DependsOnTargets="$(WasmBuildNativeOnlyDependsOn)" Condition="'$(WasmBuildNative)' == 'true'" />
<Target Name="_PrepareForWasmBuildNativeOnly">
+ <MakeDir Directories="$(_WasmIntermediateOutputPath)" />
+
<ItemGroup>
<_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
<_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" />
</ItemGroup>
</Target>
-
<Target Name="_SetupEmscripten">
<PropertyGroup>
<_EMSDKMissingPaths Condition="'$(_EMSDKMissingPaths)' == '' and ('$(EmscriptenSdkToolsPath)' == '' or !Exists('$(EmscriptenSdkToolsPath)'))">%24(EmscriptenSdkToolsPath)=$(EmscriptenSdkToolsPath) </_EMSDKMissingPaths>
<PropertyGroup>
<WasmBuildNative Condition="'$(RunAOTCompilation)' == 'true'">true</WasmBuildNative>
- <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(PublishTrimmed)' != 'true'">false</WasmBuildNative>
- <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(Configuration)' == 'Release'">true</WasmBuildNative>
+ <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and @(NativeFileReference->Count()) > 0" >true</WasmBuildNative>
+ <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(PublishTrimmed)' != 'true'" >false</WasmBuildNative>
+ <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(Configuration)' == 'Release'" >true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == ''">false</WasmBuildNative>
</PropertyGroup>
<EmccCompileOptimizationFlag Condition="'$(EmccCompileOptimizationFlag)' == ''">$(_EmccOptimizationFlagDefault)</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag Condition="'$(EmccLinkOptimizationFlag)' == ''" >-O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault)</EmccLinkOptimizationFlag>
+
+ <_EmccCompileRsp>$(_WasmIntermediateOutputPath)emcc-compile.rsp</_EmccCompileRsp>
</PropertyGroup>
<ItemGroup>
<_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" />
+
+ <!-- Adding optimization flag at the top, so it gets precedence -->
+ <_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)" />
</ItemGroup>
- <ItemGroup>
- <_DotnetJSSrcFile Include="$(_WasmRuntimePackSrcDir)\*.js" />
- </ItemGroup>
+ <Error Text="Could not find NativeFileReference %(NativeFileReference.Identity)" Condition="'%(NativeFileReference.Identity)' != '' and !Exists(%(NativeFileReference.Identity))" />
</Target>
<Target Name="_GeneratePInvokeTable">
<ItemGroup>
+ <_WasmPInvokeModules Include="%(_WasmNativeFileForLinking.FileName)" Condition="'%(_WasmNativeFileForLinking.ScanForPInvokes)' != 'false'" />
+
<_WasmPInvokeModules Include="libSystem.Native" />
<_WasmPInvokeModules Include="libSystem.IO.Compression.Native" />
<_WasmPInvokeModules Include="libSystem.Globalization.Native" />
<Target Name="_WasmCompileNativeFiles">
<ItemGroup>
- <_EmccIncludePaths Include="$(_WasmIntermediateOutputPath.TrimEnd('\/'))" />
- <_EmccIncludePaths Include="$(_WasmRuntimePackIncludeDir)mono-2.0" />
- <_EmccIncludePaths Include="$(_WasmRuntimePackIncludeDir)wasm" />
-
- <!-- Adding optimization flag at the top, so it gets precedence -->
- <_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)" />
</ItemGroup>
<PropertyGroup>
<_EmBuilder Condition="$([MSBuild]::IsOSPlatform('WINDOWS'))">embuilder.bat</_EmBuilder>
<_EmBuilder Condition="!$([MSBuild]::IsOSPlatform('WINDOWS'))">embuilder.py</_EmBuilder>
- <_EmccCompileRsp>$(_WasmIntermediateOutputPath)emcc-compile.rsp</_EmccCompileRsp>
</PropertyGroup>
<WriteLinesToFile Lines="@(_EmccCFlags)" File="$(_EmccCompileRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
<Message Text="Compiling native assets with emcc. This may take a while ..." Importance="High" />
<EmccCompile SourceFiles="@(_WasmSourceFileToCompile)" Arguments='"@$(_EmccDefaultFlagsRsp)" "@$(_EmccCompileRsp)"' EnvironmentVariables="@(EmscriptenEnvVars)" />
+
+ <ItemGroup>
+ <WasmNativeAsset Include="%(_WasmSourceFileToCompile.ObjectFile)" />
+ </ItemGroup>
</Target>
<ItemGroup Condition="'$(Configuration)' == 'Debug' and '@(_MonoComponent->Count())' == 0">
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'" />
<ParameterGroup>
<EmccProperties ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" Output="true" />
</ParameterGroup>
- </UsingTask>
-
+ </UsingTask>
</Project>
<WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == ''">Publish</WasmBuildAppAfterThisTarget>
<WasmBuildAppDependsOn>
+ _InitializeCommonProperties;
_BeforeWasmBuildApp;
_WasmResolveReferences;
_WasmAotCompileApp;
<!--<WasmStripAOTAssemblies Condition="'$(AOTMode)' == 'LLVMOnlyInterp'">false</WasmStripAOTAssemblies>-->
<!--<WasmStripAOTAssemblies Condition="'$(WasmStripAOTAssemblies)' == ''">$(RunAOTCompilation)</WasmStripAOTAssemblies>-->
<WasmStripAOTAssemblies>false</WasmStripAOTAssemblies>
+
+ <!-- emcc, and mono-aot-cross don't like relative paths for output files -->
+ <_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm'))</_WasmIntermediateOutputPath>
<_BeforeWasmBuildAppDependsOn />
</PropertyGroup>
<Target Name="_WasmCoreBuild" BeforeTargets="WasmBuildApp" DependsOnTargets="$(WasmBuildAppDependsOn)" />
- <Target Name="_BeforeWasmBuildApp" DependsOnTargets="$(_BeforeWasmBuildAppDependsOn)">
+ <Target Name="_InitializeCommonProperties">
<Error Condition="'$(MicrosoftNetCoreAppRuntimePackDir)' == '' and ('%(ResolvedRuntimePack.PackageDirectory)' == '' or !Exists(%(ResolvedRuntimePack.PackageDirectory)))"
Text="Could not find %25(ResolvedRuntimePack.PackageDirectory)=%(ResolvedRuntimePack.PackageDirectory)" />
<MicrosoftNetCoreAppRuntimePackRidDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir)))</MicrosoftNetCoreAppRuntimePackRidDir>
<MicrosoftNetCoreAppRuntimePackRidNativeDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidDir), 'native'))</MicrosoftNetCoreAppRuntimePackRidNativeDir>
- <!-- FIXME: confirm that this won't get used before this -->
<_WasmRuntimePackIncludeDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'include'))</_WasmRuntimePackIncludeDir>
<_WasmRuntimePackSrcDir>$([MSBuild]::NormalizeDirectory($(MicrosoftNetCoreAppRuntimePackRidNativeDir), 'src'))</_WasmRuntimePackSrcDir>
</PropertyGroup>
+ </Target>
+ <Target Name="_BeforeWasmBuildApp" DependsOnTargets="$(_BeforeWasmBuildAppDependsOn)">
<Error Condition="'$(IntermediateOutputPath)' == ''" Text="%24(IntermediateOutputPath) property needs to be set" />
<Error Condition="!Exists('$(MicrosoftNetCoreAppRuntimePackRidDir)')" Text="MicrosoftNetCoreAppRuntimePackRidDir=$(MicrosoftNetCoreAppRuntimePackRidDir) doesn't exist" />
<Error Condition="@(WasmAssembliesToBundle->Count()) == 0" Text="WasmAssembliesToBundle item is empty. No assemblies to process" />
<WasmMainAssemblyFileName Condition="'$(WasmMainAssemblyFileName)' == ''">$(TargetFileName)</WasmMainAssemblyFileName>
<WasmAppDir>$([MSBuild]::NormalizeDirectory($(WasmAppDir)))</WasmAppDir>
- <!-- emcc, and mono-aot-cross don't like relative paths for output files -->
- <_WasmIntermediateOutputPath>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'wasm'))</_WasmIntermediateOutputPath>
</PropertyGroup>
<MakeDir Directories="$(_WasmIntermediateOutputPath)" />
[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}'.");
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.
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("};");
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)
public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
{
+ Console.WriteLine($"{Environment.NewLine}-------- New test --------{Environment.NewLine}");
_buildContext = buildContext;
_testOutput = output;
_logPath = s_buildEnv.LogRootPath; // FIXME:
[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);
var lastLines = outputBuilder.ToString().Split('\r', '\n').TakeLast(20);
throw new XunitException($"Process timed out, output: {string.Join(Environment.NewLine, lastLines)}");
}
-
}
lock (syncObj)
{
string projectName = $"{projectNamePrefix}_{buildArgs.Config}_{buildArgs.AOT}";
- buildArgs = buildArgs with { ProjectName = projectName, ProjectFileContents = projectContents };
+ buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
- Console.WriteLine ($"-- args: {buildArgs}, name: {projectName}");
BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), projectContents),
--- /dev/null
+// 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: "<NativeFileReference Include=\"native-lib.o\" />");
+
+ 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: @$"
+ <PackageReference Include=""SkiaSharp"" Version=""2.80.3"" />
+ <PackageReference Include=""SkiaSharp.NativeAssets.WebAssembly"" Version=""2.80.3"" />
+
+ <NativeFileReference Include=""$(SkiaSharpStaticLibraryPath)\2.0.9\*.a"" />
+ <WasmFilesToIncludeInFileSystem Include=""{Path.Combine(BuildEnvironment.TestAssetsPath, "mono.png")}"" />
+ ");
+
+ 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);
+ }
+ }
+}
<PropertyGroup>
<WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
<_MicrosoftNetCoreAppRefDir>$(AppRefDir)\</_MicrosoftNetCoreAppRefDir>
- <LocalFrameworkOverrideName>Microsoft.NETCore.App</LocalFrameworkOverrideName>
</PropertyGroup>
<Target Name="PrepareForWasmBuild">
--- /dev/null
+// 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);
+ }
+}
--- /dev/null
+// 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 <stdio.h>
+
+int print_line(int x)
+{
+ printf("print_line: %d\n", x);
+ return 42 + x;
+}
--- /dev/null
+// 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_
--- /dev/null
+// 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;
+ }
+}