Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm] Add support for using custom native libraries #55797

Merged
merged 6 commits into from
Jul 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 45 additions & 34 deletions src/mono/wasm/build/WasmApp.Native.targets
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
</_WasmBuildNativeCoreDependsOn>

<WasmBuildNativeOnlyDependsOn>
_InitializeCommonProperties;
_PrepareForWasmBuildNativeOnly;
_WasmBuildNativeCore;
</WasmBuildNativeOnlyDependsOn>
Expand All @@ -38,13 +39,14 @@
<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>
Expand Down Expand Up @@ -115,8 +117,9 @@

<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>

Expand Down Expand Up @@ -153,6 +156,8 @@

<EmccCompileOptimizationFlag Condition="'$(EmccCompileOptimizationFlag)' == ''">$(_EmccOptimizationFlagDefault)</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag Condition="'$(EmccLinkOptimizationFlag)' == ''" >-O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault)</EmccLinkOptimizationFlag>

<_EmccCompileRsp>$(_WasmIntermediateOutputPath)emcc-compile.rsp</_EmccCompileRsp>
</PropertyGroup>

<ItemGroup>
Expand All @@ -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" />

<!-- 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="&quot;-I%(_EmccIncludePaths.Identity)&quot;" />
<_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" />
Expand All @@ -194,38 +225,13 @@

<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="&quot;-I%(_EmccIncludePaths.Identity)&quot;" />
<_EmccCFlags Include="-g" Condition="'$(WasmNativeDebugSymbols)' == 'true'" />
<_EmccCFlags Include="-s EXPORTED_FUNCTIONS='[@(_ExportedFunctions->'&quot;%(Identity)&quot;', ',')]'" 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" />
Expand All @@ -235,6 +241,10 @@

<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">
Expand Down Expand Up @@ -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 &quot;%(_DotnetJSSrcFile.Identity)&quot;" />
<_EmccLinkStepArgs Include="--js-library &quot;%(_WasmExtraJSFile.Identity)&quot;" Condition="'%(_WasmExtraJSFile.Kind)' == 'js-library'" />
Expand Down Expand Up @@ -483,6 +495,5 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
<ParameterGroup>
<EmccProperties ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="false" Output="true" />
</ParameterGroup>
</UsingTask>

</UsingTask>
</Project>
1 change: 1 addition & 0 deletions src/mono/wasm/build/WasmApp.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == ''">Publish</WasmBuildAppAfterThisTarget>
<WasmBuildAppDependsOn>
_InitializeCommonProperties;
_BeforeWasmBuildApp;
_WasmResolveReferences;
_WasmAotCompileApp;
Expand Down
10 changes: 6 additions & 4 deletions src/mono/wasm/build/WasmApp.targets
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
<!--<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>

Expand All @@ -90,7 +93,7 @@

<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)" />

Expand All @@ -100,11 +103,12 @@
<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" />
Expand All @@ -115,8 +119,6 @@
<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)" />
Expand Down
18 changes: 16 additions & 2 deletions src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}'.");
Expand Down Expand Up @@ -101,7 +103,7 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> 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.
Expand All @@ -120,7 +122,7 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> 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("};");
Expand All @@ -130,6 +132,18 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary<string, string> 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)
Expand Down
5 changes: 3 additions & 2 deletions src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<WasmBuildNative>true</WasmBuildNative>");
Console.WriteLine ($"-- args: {buildArgs}, name: {projectName}");

BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), projectContents),
Expand Down
96 changes: 96 additions & 0 deletions src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs
Original file line number Diff line number Diff line change
@@ -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: "<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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<PropertyGroup>
<WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
<_MicrosoftNetCoreAppRefDir>$(AppRefDir)\</_MicrosoftNetCoreAppRefDir>
<LocalFrameworkOverrideName>Microsoft.NETCore.App</LocalFrameworkOverrideName>
</PropertyGroup>

<Target Name="PrepareForWasmBuild">
Expand Down
Loading