未验证 提交 d574b032 编写于 作者: A Ankit Jain 提交者: GitHub

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

上级 a587fa4b
......@@ -18,6 +18,7 @@
</_WasmBuildNativeCoreDependsOn>
<WasmBuildNativeOnlyDependsOn>
_InitializeCommonProperties;
_PrepareForWasmBuildNativeOnly;
_WasmBuildNativeCore;
</WasmBuildNativeOnlyDependsOn>
......@@ -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>
......@@ -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>
......@@ -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>
......@@ -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" />
......@@ -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" />
......@@ -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">
......@@ -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'" />
......@@ -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>
......@@ -7,6 +7,7 @@
<WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == ''">Publish</WasmBuildAppAfterThisTarget>
<WasmBuildAppDependsOn>
_InitializeCommonProperties;
_BeforeWasmBuildApp;
_WasmResolveReferences;
_WasmAotCompileApp;
......
......@@ -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>
......@@ -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)" />
......@@ -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" />
......@@ -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)" />
......
......@@ -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<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.
......@@ -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("};");
......@@ -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)
......
......@@ -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 @@ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture bu
[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 @@ protected string GetBinDir(string config, string targetFramework=s_targetFramewo
var lastLines = outputBuilder.ToString().Split('\r', '\n').TakeLast(20);
throw new XunitException($"Process timed out, output: {string.Join(Environment.NewLine, lastLines)}");
}
}
lock (syncObj)
......
......@@ -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),
......
// 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);
}
}
}
......@@ -2,7 +2,6 @@
<PropertyGroup>
<WasmBuildAppDependsOn>PrepareForWasmBuild;$(WasmBuildAppDependsOn)</WasmBuildAppDependsOn>
<_MicrosoftNetCoreAppRefDir>$(AppRefDir)\</_MicrosoftNetCoreAppRefDir>
<LocalFrameworkOverrideName>Microsoft.NETCore.App</LocalFrameworkOverrideName>
</PropertyGroup>
<Target Name="PrepareForWasmBuild">
......
// 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);
}
}
// 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;
}
// 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_
// 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;
}
}
此差异由.gitattributes 抑制。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册