From 2b16e1cadad4ce5afef8c2036631e45282aff05e Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Mon, 26 Jul 2021 18:42:56 -0400 Subject: [PATCH] [release/6.0-preview7] [wasm] Add support for using custom native libraries (#56013) * [wasm] Add support for using custom native libraries (#55797) (cherry picked from commit d574b032793ae752387d32b97ff9840de17420a2) * [wasm] Use compile rsp instead of link, for compiling native files (#55848) .. and fix logging that broke recently. `tasks/Common/Utils.cs`: TaskLoggingHelper Utils.Logger is a static field, which must be set by task else any methods in Utils, eg. RunProcess, silently fail to log any messages. Also, this would be a problem when building multiple projects in parallel, since the logger is a task-specific one. Instead, we pass logger as an arg to all the methods. (cherry picked from commit 3301e9dc7288654d6458150bc220bab95ce92686) * Link with EmccCompileOptimizationFlag==-Oz by default in release (#55939) (cherry picked from commit 04072ff3de988de016a909597a65f29f0af5a4f9) * [wasm] Fix regression in compiling .bc -> .o files (#56063) * [wasm] Add back --emit-llvm that got removed mistakenly, in an earlier commit .. found thanks to Jerome Laban. * [wasm] Set EmccCompile's messages to MessageImportance.Low by default. .. and to MessageImportance.Normal if `$(EmccVerbose)==true`. * [wasm] Quote filenames passed to emcc compile command line * Add more blazorwasm tests - for debug/release, aot/relinking * Bump sdk for workload testing to 6.0.100-rc.1.21370.2 * [wasm] Fix regression in compiling bitcode -> .o The `-emit-llvm` arg has been incorrectly added, and removed from the args used for compiling .bc->.o . This commit fixes it, and adds a crude test for it, so we don't regress again. * Fix build (cherry picked from commit 1d8ad03abb72fe79c44030f2806a0109a7909d25) * [wasm] Bump sdk for workload testing to 6.0.100-preview.7.21372.19 Co-authored-by: Larry Ewing --- eng/Versions.props | 2 +- src/mono/wasm/build/WasmApp.Native.targets | 95 ++++++++++------- src/mono/wasm/build/WasmApp.props | 1 + src/mono/wasm/build/WasmApp.targets | 10 +- .../AndroidApkFileReplacerTask.cs | 3 +- .../AndroidAppBuilder/AndroidAppBuilder.cs | 4 +- src/tasks/AndroidAppBuilder/ApkBuilder.cs | 40 +++++--- src/tasks/AotCompilerTask/MonoAOTCompiler.cs | 18 +++- src/tasks/AppleAppBuilder/AppleAppBuilder.cs | 5 +- src/tasks/AppleAppBuilder/Xcode.cs | 24 +++-- src/tasks/Common/Utils.cs | 56 ++++------ src/tasks/WasmAppBuilder/EmccCompile.cs | 13 ++- .../WasmAppBuilder/PInvokeTableGenerator.cs | 18 +++- .../InstallWorkloadFromArtifacts.cs | 3 +- .../WorkloadBuildTasks/PackageInstaller.cs | 2 +- .../Wasm.Build.Tests/BlazorWasmTests.cs | 18 +++- .../Wasm.Build.Tests/BuildTestBase.cs | 8 +- .../Wasm.Build.Tests/NativeBuildTests.cs | 37 ++++++- .../Wasm.Build.Tests/NativeLibraryTests.cs | 96 ++++++++++++++++++ .../SharedBuildPerTestClassFixture.cs | 2 +- .../Wasm.Build.Tests/ToolCommand.cs | 2 + .../data/Workloads.Directory.Build.targets | 1 - .../testassets/AppUsingNativeLib/Program.cs | 21 ++++ .../AppUsingNativeLib/native-lib.cpp | 11 ++ .../testassets/AppUsingNativeLib/native-lib.h | 17 ++++ .../testassets/AppUsingSkiaSharp/Program.cs | 17 ++++ src/tests/BuildWasmApps/testassets/mono.png | Bin 0 -> 26462 bytes .../testassets/native-libs/native-lib.o | Bin 0 -> 542 bytes 28 files changed, 381 insertions(+), 143 deletions(-) create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs create mode 100644 src/tests/BuildWasmApps/testassets/AppUsingNativeLib/Program.cs create mode 100644 src/tests/BuildWasmApps/testassets/AppUsingNativeLib/native-lib.cpp create mode 100644 src/tests/BuildWasmApps/testassets/AppUsingNativeLib/native-lib.h create mode 100644 src/tests/BuildWasmApps/testassets/AppUsingSkiaSharp/Program.cs create mode 100644 src/tests/BuildWasmApps/testassets/mono.png create mode 100644 src/tests/BuildWasmApps/testassets/native-libs/native-lib.o diff --git a/eng/Versions.props b/eng/Versions.props index 63046ad9113c7..e0a4ab7d12662 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -162,7 +162,7 @@ 2.0.4 4.12.0 2.14.3 - 6.0.100-preview.7.21362.5 + 6.0.100-preview.7.21372.19 5.0.0-preview-20201009.2 diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 306cc52e98707..85d301885db2f 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 @@ -152,7 +155,11 @@ <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == ''">-Oz $(_EmccOptimizationFlagDefault) - -O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault) + $(EmccCompileOptimizationFlag) + + <_EmccCompileRsp>$(_WasmIntermediateOutputPath)emcc-compile.rsp + <_EmccCompileOutputMessageImportance Condition="'$(EmccVerbose)' == 'true'">Normal + <_EmccCompileOutputMessageImportance Condition="'$(EmccVerbose)' != 'true'">Low @@ -161,15 +168,42 @@ <_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="-emit-llvm" /> + + <_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 +228,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 @@ -234,7 +243,15 @@ - + + + + + @@ -259,8 +276,9 @@ + Arguments=""@$(_EmccDefaultFlagsRsp)" @(_EmccLDFlags, ' ')" + EnvironmentVariables="@(EmscriptenEnvVars)" + OutputMessageImportance="$(_EmccCompileOutputMessageImportance)" /> @@ -271,6 +289,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 +503,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 10b939d6da73c..bc45a6d54d73d 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 10bd9ac7973c5..534caf9025bf1 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/AndroidAppBuilder/AndroidApkFileReplacerTask.cs b/src/tasks/AndroidAppBuilder/AndroidApkFileReplacerTask.cs index a0af313aa1e6a..8a8149ef15484 100644 --- a/src/tasks/AndroidAppBuilder/AndroidApkFileReplacerTask.cs +++ b/src/tasks/AndroidAppBuilder/AndroidApkFileReplacerTask.cs @@ -26,8 +26,7 @@ public class AndroidApkFileReplacerTask : Task public override bool Execute() { - Utils.Logger = Log; - var apkBuilder = new ApkBuilder(); + var apkBuilder = new ApkBuilder(Log); apkBuilder.OutputDir = OutputDir; apkBuilder.AndroidSdk = AndroidSdk; apkBuilder.MinApiLevel = MinApiLevel; diff --git a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs index 345ad7219998d..fcf71c0e6f68e 100644 --- a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs +++ b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs @@ -87,11 +87,9 @@ public class AndroidAppBuilderTask : Task public override bool Execute() { - Utils.Logger = Log; - string abi = DetermineAbi(); - var apkBuilder = new ApkBuilder(); + var apkBuilder = new ApkBuilder(Log); apkBuilder.ProjectName = ProjectName; apkBuilder.AppDir = AppDir; apkBuilder.OutputDir = OutputDir; diff --git a/src/tasks/AndroidAppBuilder/ApkBuilder.cs b/src/tasks/AndroidAppBuilder/ApkBuilder.cs index 23475c9e1e6c2..c69288218d69f 100644 --- a/src/tasks/AndroidAppBuilder/ApkBuilder.cs +++ b/src/tasks/AndroidAppBuilder/ApkBuilder.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; public class ApkBuilder { @@ -32,6 +33,13 @@ public class ApkBuilder public string? DiagnosticPorts { get; set; } public ITaskItem[] Assemblies { get; set; } = Array.Empty(); + private TaskLoggingHelper logger; + + public ApkBuilder(TaskLoggingHelper logger) + { + this.logger = logger; + } + public (string apk, string packageId) BuildApk( string abi, string mainLibraryFileName, @@ -209,7 +217,7 @@ public class ApkBuilder string cmake = "cmake"; string zip = "zip"; - Utils.RunProcess(zip, workingDir: assetsToZipDirectory, args: "-q -r ../assets/assets.zip ."); + Utils.RunProcess(logger, zip, workingDir: assetsToZipDirectory, args: "-q -r ../assets/assets.zip ."); Directory.Delete(assetsToZipDirectory, true); if (!File.Exists(androidJar)) @@ -274,7 +282,7 @@ public class ApkBuilder // if lib doesn't exist (primarly due to runtime build without static lib support), fallback linking stub lib. if (!File.Exists(componentLibToLink)) { - Utils.LogInfo($"\nCouldn't find static component library: {componentLibToLink}, linking static component stub library: {staticComponentStubLib}.\n"); + logger.LogMessage(MessageImportance.High, $"\nCouldn't find static component library: {componentLibToLink}, linking static component stub library: {staticComponentStubLib}.\n"); componentLibToLink = staticComponentStubLib; } @@ -339,8 +347,8 @@ public class ApkBuilder cmakeBuildArgs += " --config Debug"; } - Utils.RunProcess(cmake, workingDir: OutputDir, args: cmakeGenArgs); - Utils.RunProcess(cmake, workingDir: OutputDir, args: cmakeBuildArgs); + Utils.RunProcess(logger, cmake, workingDir: OutputDir, args: cmakeGenArgs); + Utils.RunProcess(logger, cmake, workingDir: OutputDir, args: cmakeBuildArgs); // 2. Compile Java files @@ -369,15 +377,15 @@ public class ApkBuilder .Replace("%MinSdkLevel%", MinApiLevel)); string javaCompilerArgs = $"-d obj -classpath src -bootclasspath {androidJar} -source 1.8 -target 1.8 "; - Utils.RunProcess(javac, javaCompilerArgs + javaActivityPath, workingDir: OutputDir); - Utils.RunProcess(javac, javaCompilerArgs + monoRunnerPath, workingDir: OutputDir); - Utils.RunProcess(dx, "--dex --output=classes.dex obj", workingDir: OutputDir); + Utils.RunProcess(logger, javac, javaCompilerArgs + javaActivityPath, workingDir: OutputDir); + Utils.RunProcess(logger, javac, javaCompilerArgs + monoRunnerPath, workingDir: OutputDir); + Utils.RunProcess(logger, dx, "--dex --output=classes.dex obj", workingDir: OutputDir); // 3. Generate APK string debugModeArg = StripDebugSymbols ? string.Empty : "--debug-mode"; string apkFile = Path.Combine(OutputDir, "bin", $"{ProjectName}.unaligned.apk"); - Utils.RunProcess(aapt, $"package -f -m -F {apkFile} -A assets -M AndroidManifest.xml -I {androidJar} {debugModeArg}", workingDir: OutputDir); + Utils.RunProcess(logger, aapt, $"package -f -m -F {apkFile} -A assets -M AndroidManifest.xml -I {androidJar} {debugModeArg}", workingDir: OutputDir); var dynamicLibs = new List(); dynamicLibs.Add(Path.Combine(OutputDir, "monodroid", "libmonodroid.so")); @@ -433,21 +441,21 @@ public class ApkBuilder // NOTE: we can run android-strip tool from NDK to shrink native binaries here even more. File.Copy(dynamicLib, Path.Combine(OutputDir, destRelative), true); - Utils.RunProcess(aapt, $"add {apkFile} {destRelative}", workingDir: OutputDir); + Utils.RunProcess(logger, aapt, $"add {apkFile} {destRelative}", workingDir: OutputDir); } - Utils.RunProcess(aapt, $"add {apkFile} classes.dex", workingDir: OutputDir); + Utils.RunProcess(logger, aapt, $"add {apkFile} classes.dex", workingDir: OutputDir); // 4. Align APK string alignedApk = Path.Combine(OutputDir, "bin", $"{ProjectName}.apk"); - Utils.RunProcess(zipalign, $"-v 4 {apkFile} {alignedApk}", workingDir: OutputDir); + Utils.RunProcess(logger, zipalign, $"-v 4 {apkFile} {alignedApk}", workingDir: OutputDir); // we don't need the unaligned one any more File.Delete(apkFile); // 5. Generate key (if needed) & sign the apk SignApk(alignedApk, apksigner); - Utils.LogInfo($"\nAPK size: {(new FileInfo(alignedApk).Length / 1000_000.0):0.#} Mb.\n"); + logger.LogMessage(MessageImportance.High, $"\nAPK size: {(new FileInfo(alignedApk).Length / 1000_000.0):0.#} Mb.\n"); return (alignedApk, packageId); } @@ -460,7 +468,7 @@ private void SignApk(string apkPath, string apksigner) if (!File.Exists(signingKey)) { - Utils.RunProcess("keytool", "-genkey -v -keystore debug.keystore -storepass android -alias " + + Utils.RunProcess(logger, "keytool", "-genkey -v -keystore debug.keystore -storepass android -alias " + "androiddebugkey -keypass android -keyalg RSA -keysize 2048 -noprompt " + "-dname \"CN=Android Debug,O=Android,C=US\"", workingDir: OutputDir, silent: true); } @@ -468,7 +476,7 @@ private void SignApk(string apkPath, string apksigner) { File.Copy(signingKey, Path.Combine(OutputDir, "debug.keystore")); } - Utils.RunProcess(apksigner, $"sign --min-sdk-version {MinApiLevel} --ks debug.keystore " + + Utils.RunProcess(logger, apksigner, $"sign --min-sdk-version {MinApiLevel} --ks debug.keystore " + $"--ks-pass pass:android --key-pass pass:android {apkPath}", workingDir: OutputDir); } @@ -499,8 +507,8 @@ public void ReplaceFileInApk(string file) if (!File.Exists(apkPath)) throw new Exception($"{apkPath} was not found"); - Utils.RunProcess(aapt, $"remove -v bin/{Path.GetFileName(apkPath)} {file}", workingDir: OutputDir); - Utils.RunProcess(aapt, $"add -v bin/{Path.GetFileName(apkPath)} {file}", workingDir: OutputDir); + Utils.RunProcess(logger, aapt, $"remove -v bin/{Path.GetFileName(apkPath)} {file}", workingDir: OutputDir); + Utils.RunProcess(logger, aapt, $"add -v bin/{Path.GetFileName(apkPath)} {file}", workingDir: OutputDir); // we need to re-sign the apk SignApk(apkPath, apksigner); diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index 259622da70785..465c8845209d9 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -194,8 +194,6 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task public override bool Execute() { - Utils.Logger = Log; - if (string.IsNullOrEmpty(CompilerBinaryPath)) { throw new ArgumentException($"'{nameof(CompilerBinaryPath)}' is required.", nameof(CompilerBinaryPath)); @@ -545,15 +543,25 @@ private bool PrecompileLibrary(ITaskItem assemblyItem, string? monoPaths) Log.LogMessage(MessageImportance.Low, $"AOT compiler arguments: {responseFileContent}"); + string args = $"--response=\"{responseFilePath}\""; + + // Log the command in a compact format which can be copy pasted + StringBuilder envStr = new StringBuilder(string.Empty); + foreach (KeyValuePair kvp in envVariables) + envStr.Append($"{kvp.Key}={kvp.Value} "); + Log.LogMessage(MessageImportance.Low, $"Exec: {envStr}{CompilerBinaryPath} {args}"); + try { // run the AOT compiler - (int exitCode, string output) = Utils.TryRunProcess(CompilerBinaryPath, - $"--response=\"{responseFilePath}\"", + (int exitCode, string output) = Utils.TryRunProcess(Log, + CompilerBinaryPath, + args, envVariables, assemblyDir, silent: false, - debugMessageImportance: MessageImportance.Low); + debugMessageImportance: MessageImportance.Low, + label: assembly); if (exitCode != 0) { Log.LogError($"Precompiling failed for {assembly}: {output}"); diff --git a/src/tasks/AppleAppBuilder/AppleAppBuilder.cs b/src/tasks/AppleAppBuilder/AppleAppBuilder.cs index 0b20d27f38835..a0637d8d8453d 100644 --- a/src/tasks/AppleAppBuilder/AppleAppBuilder.cs +++ b/src/tasks/AppleAppBuilder/AppleAppBuilder.cs @@ -153,7 +153,6 @@ public string TargetOS public override bool Execute() { - Utils.Logger = Log; bool isDevice = (TargetOS == TargetNames.iOS || TargetOS == TargetNames.tvOS); if (!File.Exists(Path.Combine(AppDir, MainLibraryFileName))) @@ -227,7 +226,7 @@ public override bool Execute() if (GenerateXcodeProject) { - Xcode generator = new Xcode(TargetOS, Arch); + Xcode generator = new Xcode(Log, TargetOS, Arch); generator.EnableRuntimeLogging = EnableRuntimeLogging; generator.DiagnosticPorts = DiagnosticPorts; @@ -239,7 +238,7 @@ public override bool Execute() if (isDevice && string.IsNullOrEmpty(DevTeamProvisioning)) { // DevTeamProvisioning shouldn't be empty for arm64 builds - Utils.LogInfo("DevTeamProvisioning is not set, BuildAppBundle step is skipped."); + Log.LogMessage(MessageImportance.High, "DevTeamProvisioning is not set, BuildAppBundle step is skipped."); } else { diff --git a/src/tasks/AppleAppBuilder/Xcode.cs b/src/tasks/AppleAppBuilder/Xcode.cs index b79387da224f8..2e938d8ea0e36 100644 --- a/src/tasks/AppleAppBuilder/Xcode.cs +++ b/src/tasks/AppleAppBuilder/Xcode.cs @@ -6,6 +6,8 @@ using System.IO; using System.Linq; using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; internal class Xcode { @@ -13,27 +15,29 @@ internal class Xcode private string SysRoot { get; set; } private string Target { get; set; } private string XcodeArch { get; set; } + private TaskLoggingHelper Logger { get; set; } - public Xcode(string target, string arch) + public Xcode(TaskLoggingHelper logger, string target, string arch) { + Logger = logger; Target = target; XcodeArch = (arch == "x64") ? "x86_64" : arch; switch (Target) { case TargetNames.iOS: - SysRoot = Utils.RunProcess("xcrun", "--sdk iphoneos --show-sdk-path"); + SysRoot = Utils.RunProcess(Logger, "xcrun", "--sdk iphoneos --show-sdk-path"); break; case TargetNames.iOSsim: - SysRoot = Utils.RunProcess("xcrun", "--sdk iphonesimulator --show-sdk-path"); + SysRoot = Utils.RunProcess(Logger, "xcrun", "--sdk iphonesimulator --show-sdk-path"); break; case TargetNames.tvOS: - SysRoot = Utils.RunProcess("xcrun", "--sdk appletvos --show-sdk-path"); + SysRoot = Utils.RunProcess(Logger, "xcrun", "--sdk appletvos --show-sdk-path"); break; case TargetNames.tvOSsim: - SysRoot = Utils.RunProcess("xcrun", "--sdk appletvsimulator --show-sdk-path"); + SysRoot = Utils.RunProcess(Logger, "xcrun", "--sdk appletvsimulator --show-sdk-path"); break; default: - SysRoot = Utils.RunProcess("xcrun", "--sdk macosx --show-sdk-path"); + SysRoot = Utils.RunProcess(Logger, "xcrun", "--sdk macosx --show-sdk-path"); break; } @@ -145,7 +149,7 @@ public string GenerateXCode( // if lib doesn't exist (primarly due to runtime build without static lib support), fallback linking stub lib. if (!File.Exists(componentLibToLink)) { - Utils.LogInfo($"\nCouldn't find static component library: {componentLibToLink}, linking static component stub library: {staticComponentStubLib}.\n"); + Logger.LogMessage(MessageImportance.High, $"\nCouldn't find static component library: {componentLibToLink}, linking static component stub library: {staticComponentStubLib}.\n"); componentLibToLink = staticComponentStubLib; } @@ -298,7 +302,7 @@ public string GenerateXCode( .Replace("//%APPLE_RUNTIME_IDENTIFIER%", RuntimeIdentifier) .Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib))); - Utils.RunProcess("cmake", cmakeArgs.ToString(), workingDir: binDir); + Utils.RunProcess(Logger, "cmake", cmakeArgs.ToString(), workingDir: binDir); return Path.Combine(binDir, projectName, projectName + ".xcodeproj"); } @@ -391,7 +395,7 @@ public string BuildAppBundle( string config = optimized ? "Release" : "Debug"; args.Append(" -configuration ").Append(config); - Utils.RunProcess("xcodebuild", args.ToString(), workingDir: Path.GetDirectoryName(xcodePrjPath)); + Utils.RunProcess(Logger, "xcodebuild", args.ToString(), workingDir: Path.GetDirectoryName(xcodePrjPath)); string appPath = Path.Combine(Path.GetDirectoryName(xcodePrjPath)!, config + "-" + sdk, Path.GetFileNameWithoutExtension(xcodePrjPath) + ".app"); @@ -400,7 +404,7 @@ public string BuildAppBundle( .EnumerateFiles("*", SearchOption.AllDirectories) .Sum(file => file.Length); - Utils.LogInfo($"\nAPP size: {(appSize / 1000_000.0):0.#} Mb.\n"); + Logger.LogMessage(MessageImportance.High, $"\nAPP size: {(appSize / 1000_000.0):0.#} Mb.\n"); return appPath; } diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 8756a3156c220..3376b4d5e7bdc 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -22,10 +22,11 @@ public static string GetEmbeddedResource(string file) return reader.ReadToEnd(); } - public static (int exitCode, string output) RunShellCommand(string command, + public static (int exitCode, string output) RunShellCommand( + TaskLoggingHelper logger, + string command, IDictionary envVars, string workingDir, - TaskLoggingHelper logger, bool silent=false, bool logStdErrAsMessage=false, MessageImportance debugMessageImportance=MessageImportance.Low, @@ -37,16 +38,16 @@ public static (int exitCode, string output) RunShellCommand(string command, : ("/bin/sh", $"\"{scriptFileName}\""); string msgPrefix = label == null ? string.Empty : $"[{label}] "; - LogMessage(debugMessageImportance, $"Running {command} via script {scriptFileName}:", msgPrefix); - LogMessage(debugMessageImportance, File.ReadAllText(scriptFileName), msgPrefix); + logger.LogMessage(debugMessageImportance, $"Running {command} via script {scriptFileName}:", msgPrefix); + logger.LogMessage(debugMessageImportance, File.ReadAllText(scriptFileName), msgPrefix); - return TryRunProcess(shell, + return TryRunProcess(logger, + shell, args, envVars, workingDir, silent: silent, logStdErrAsMessage: logStdErrAsMessage, - logger: logger, label: label, debugMessageImportance: debugMessageImportance); @@ -74,6 +75,7 @@ static string CreateTemporaryBatchFile(string command) } public static string RunProcess( + TaskLoggingHelper logger, string path, string args = "", IDictionary? envVars = null, @@ -83,12 +85,12 @@ public static string RunProcess( MessageImportance debugMessageImportance=MessageImportance.High) { (int exitCode, string output) = TryRunProcess( + logger, path, args, envVars, workingDir, silent: silent, - logger: Logger, debugMessageImportance: debugMessageImportance); if (exitCode != 0 && !ignoreErrors) @@ -98,20 +100,18 @@ public static string RunProcess( } public static (int, string) TryRunProcess( + TaskLoggingHelper logger, string path, string args = "", IDictionary? envVars = null, string? workingDir = null, bool silent = true, bool logStdErrAsMessage = false, - TaskLoggingHelper? logger = null, MessageImportance debugMessageImportance=MessageImportance.High, string? label=null) { - Logger = logger; - string msgPrefix = label == null ? string.Empty : $"[{label}] "; - LogMessage(debugMessageImportance, $"Running: {path} {args}", msgPrefix); + logger.LogMessage(debugMessageImportance, $"Running: {path} {args}", msgPrefix); var outputBuilder = new StringBuilder(); var processStartInfo = new ProcessStartInfo { @@ -126,17 +126,17 @@ public static (int, string) TryRunProcess( if (workingDir != null) processStartInfo.WorkingDirectory = workingDir; - LogMessage(debugMessageImportance, $"Using working directory: {workingDir ?? Environment.CurrentDirectory}", msgPrefix); + logger.LogMessage(debugMessageImportance, $"Using working directory: {workingDir ?? Environment.CurrentDirectory}", msgPrefix); if (envVars != null) { if (envVars.Count > 0) - LogMessage(MessageImportance.Low, $"Setting environment variables for execution:", msgPrefix); + logger.LogMessage(MessageImportance.Low, $"Setting environment variables for execution:", msgPrefix); foreach (KeyValuePair envVar in envVars) { processStartInfo.EnvironmentVariables[envVar.Key] = envVar.Value; - Logger?.LogMessage(MessageImportance.Low, $"{msgPrefix}\t{envVar.Key} = {envVar.Value}"); + logger.LogMessage(MessageImportance.Low, $"{msgPrefix}\t{envVar.Key} = {envVar.Value}"); } } @@ -155,9 +155,9 @@ public static (int, string) TryRunProcess( if (!silent) { if (logStdErrAsMessage) - LogMessage(debugMessageImportance, e.Data, msgPrefix); + logger.LogMessage(debugMessageImportance, e.Data, msgPrefix); else - Logger?.LogWarning(msg); + logger.LogWarning(msg); } outputBuilder.AppendLine(e.Data); } @@ -170,7 +170,7 @@ public static (int, string) TryRunProcess( return; if (!silent) - LogMessage(debugMessageImportance, e.Data, msgPrefix); + logger.LogMessage(debugMessageImportance, e.Data, msgPrefix); outputBuilder.AppendLine(e.Data); } }; @@ -178,7 +178,7 @@ public static (int, string) TryRunProcess( process.BeginErrorReadLine(); process.WaitForExit(); - Logger?.LogMessage(debugMessageImportance, $"{msgPrefix}Exit code: {process.ExitCode}"); + logger.LogMessage(debugMessageImportance, $"{msgPrefix}Exit code: {process.ExitCode}"); return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n')); } @@ -223,24 +223,4 @@ public static void DirectoryCopy(string sourceDir, string destDir, Func envVarsDict = GetEnvironmentVariablesDict(); ConcurrentBag outputItems = new(); try @@ -112,7 +119,7 @@ bool ProcessSourceFile(ITaskItem srcItem) try { - string command = $"emcc {Arguments} -c -o {objFile} {srcFile}"; + string command = $"emcc {Arguments} -c -o \"{objFile}\" \"{srcFile}\""; // Log the command in a compact format which can be copy pasted StringBuilder envStr = new StringBuilder(string.Empty); @@ -120,12 +127,12 @@ bool ProcessSourceFile(ITaskItem srcItem) envStr.Append($"{key}={envVarsDict[key]} "); Log.LogMessage(MessageImportance.Low, $"Exec: {envStr}{command}"); (int exitCode, string output) = Utils.RunShellCommand( + Log, command, envVarsDict, workingDir: Environment.CurrentDirectory, - logger: Log, logStdErrAsMessage: true, - debugMessageImportance: MessageImportance.High, + debugMessageImportance: messageImportance, label: Path.GetFileName(srcFile)); if (exitCode != 0) diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs index bdd84c295ee7d..7ae4ca9920a2c 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/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs b/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs index c76659d5db3a0..4b4a283dc652f 100644 --- a/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs +++ b/src/tasks/WorkloadBuildTasks/InstallWorkloadFromArtifacts.cs @@ -34,8 +34,6 @@ public class InstallWorkloadFromArtifacts : Task public override bool Execute() { - Utils.Logger = Log; - if (!HasMetadata(WorkloadId, nameof(WorkloadId), "Version") || !HasMetadata(WorkloadId, nameof(WorkloadId), "ManifestName")) { @@ -59,6 +57,7 @@ public override bool Execute() Log.LogMessage(MessageImportance.High, $"{Environment.NewLine}** workload install **{Environment.NewLine}"); (int exitCode, string output) = Utils.TryRunProcess( + Log, Path.Combine(SdkDir, "dotnet"), $"workload install --skip-manifest-update --no-cache --configfile \"{nugetConfigPath}\" {WorkloadId.ItemSpec}", workingDir: Path.GetTempPath(), diff --git a/src/tasks/WorkloadBuildTasks/PackageInstaller.cs b/src/tasks/WorkloadBuildTasks/PackageInstaller.cs index f6d4e85cf76c8..227dcb4fbf9b7 100644 --- a/src/tasks/WorkloadBuildTasks/PackageInstaller.cs +++ b/src/tasks/WorkloadBuildTasks/PackageInstaller.cs @@ -60,7 +60,7 @@ private bool InstallActual(PackageReference[] references, bool stopOnMissing) _logger.LogMessage(MessageImportance.Low, $"Restoring packages: {string.Join(", ", references.Select(r => $"{r.Name}/{r.Version}"))}"); string args = $"restore \"{projectPath}\" /p:RestorePackagesPath=\"{_packagesDir}\""; - (int exitCode, string output) = Utils.TryRunProcess("dotnet", args, silent: false, debugMessageImportance: MessageImportance.Low); + (int exitCode, string output) = Utils.TryRunProcess(_logger, "dotnet", args, silent: false, debugMessageImportance: MessageImportance.Low); if (exitCode != 0) { LogErrorOrWarning($"Restoring packages failed with exit code: {exitCode}. Output:{Environment.NewLine}{output}", stopOnMissing); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs index dacf67ff4f395..002ca2480311a 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs @@ -16,10 +16,16 @@ public BlazorWasmTests(ITestOutputHelper output, SharedBuildPerTestClassFixture { } - [ConditionalFact(typeof(BuildTestBase), nameof(IsUsingWorkloads))] - public void PublishTemplateProject() + // TODO: invariant case? + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug", false)] + [InlineData("Debug", true)] // just aot + [InlineData("Release", false)] // should re-link + [InlineData("Release", true)] + public void PublishTemplateProject(string config, bool aot) { - string id = "blazorwasm"; + string id = $"blazorwasm_{config}_aot_{aot}"; InitPaths(id); if (Directory.Exists(_projectDir)) Directory.Delete(_projectDir, recursive: true); @@ -37,14 +43,16 @@ public void PublishTemplateProject() .ExecuteWithCapturedOutput("new blazorwasm") .EnsureSuccessful(); - string publishLogPath = Path.Combine(logPath, $"{id}.publish.binlog"); + string publishLogPath = Path.Combine(logPath, $"{id}.binlog"); new DotNetCommand(s_buildEnv) .WithWorkingDirectory(_projectDir) - .ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", "-p:RunAOTCompilation=true") + .ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", aot ? "-p:RunAOTCompilation=true" : "", $"-p:Configuration={config}") .EnsureSuccessful(); //TODO: validate the build somehow? // compare dotnet.wasm? + // relinking - dotnet.wasm should be smaller + // // playwright? } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BuildTestBase.cs index 7d211ae618912..4dd5144729cd1 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); @@ -330,10 +332,7 @@ protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProp } if (useCache) - { _buildContext.CacheBuild(buildArgs, new BuildProduct(_projectDir, logFilePath, true)); - Console.WriteLine($"caching build for {buildArgs}"); - } return (_projectDir, result.buildOutput); } @@ -539,7 +538,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 30e487f8171c0..80c7ba5fe85b8 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeBuildTests.cs @@ -1,10 +1,10 @@ // 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; +using Xunit.Sdk; #nullable enable @@ -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), @@ -44,5 +43,37 @@ private void NativeBuild(string projectNamePrefix, string projectContents, Build test: output => {}, host: host, id: id); } + + [Theory] + [BuildAndRun(host: RunHost.None, aot: true)] + public void IntermediateBitcodeToObjectFilesAreNotLLVMIR(BuildArgs buildArgs, string id) + { + string printFileTypeTarget = @" + + + + + + + + + "; + string projectName = $"bc_to_o_{buildArgs.Config}"; + + buildArgs = buildArgs with { ProjectName = projectName }; + buildArgs = ExpandBuildArgs(buildArgs, insertAtEnd: printFileTypeTarget); + + (_, string output) = BuildProject(buildArgs, + initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42), + dotnetWasmFromRuntimePack: false, + id: id); + + if (!output.Contains("wasm-dis exit code: 0")) + throw new XunitException($"Expected to successfully run wasm-dis on System.Private.CoreLib.dll.o ." + + " It might fail if it was incorrectly compiled to a bitcode file, instead of wasm."); + } } } 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 0000000000000..e06c099b19c4b --- /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/SharedBuildPerTestClassFixture.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/SharedBuildPerTestClassFixture.cs index 2c43614ea4169..e84a151bd5b02 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/SharedBuildPerTestClassFixture.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/SharedBuildPerTestClassFixture.cs @@ -44,7 +44,7 @@ private void RemoveDirectory(string path) { try { - Directory.Delete(path, recursive: true); + Directory.Delete(path, recursive: true); } catch (Exception ex) { diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/ToolCommand.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/ToolCommand.cs index b45fefac9acda..9d39dcce04a57 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/ToolCommand.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/ToolCommand.cs @@ -88,6 +88,7 @@ private async Task ExecuteAsyncInternal(string executable, string return; output.Add($"[{_label}] {e.Data}"); + Console.WriteLine($"[{_label}] {e.Data}"); ErrorDataReceived?.Invoke(s, e); }; @@ -97,6 +98,7 @@ private async Task ExecuteAsyncInternal(string executable, string return; output.Add($"[{_label}] {e.Data}"); + Console.WriteLine($"[{_label}] {e.Data}"); OutputDataReceived?.Invoke(s, e); }; 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 19f795a01f235..62e463bb19958 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 0000000000000..5134392c9d8ca --- /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 0000000000000..329a593279fe2 --- /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 0000000000000..826637b3a2d81 --- /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 0000000000000..0aeedffaf6d98 --- /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 0000000000000000000000000000000000000000..7469aec9d1fcfa933c0e6c9257d3ff0375744214 GIT binary patch literal 26462 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 JmFn<_{{hgsTz3Ef literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..10ccf42c5ff2397c2201e74ed6ffa7237ed4f24f GIT binary patch literal 542 zcmYLG&5qMB5T0?8rcEn0XSg^5qy;HSn=0MHt^C}Oc!aL&m^5t?S5CUio>Dk*;KYF& z2adc055Vj21~}eI$kzOP^Z6T(4XmjU0PySSbOLgX9d_5G)0X`15z7+w&Bc0M7PVk% zU5QGnI|6uyo0jqY`XGzC70T)iJUA$we$brHy#m7IlkwhP<0N|BV;!9!z?B~mij?dF zi&{?%NG%ErGNINbnFO}Z>;-|Q`YwR{slyeAA+&jXhZsJjb>T5P2Gqg~)p?<_XF z7>~dAy%1I~_RY=AEHCNP%met-*IYf^zNm9D{Mn~A*y#Od5+9HZqIXf#Dga+E=bKS{ z5Gk3lmPO|p!jB&Jlqe;SUcZ0y)^^+hx{t6Q0bW_|7U7qE3m6^r$s-r4!HEvJXd01) zn>%!P$?E(I&gRiHn$pWy9@@BxZ{tG6obj!Q^P-LUR>`V3R#7fxUW!H@6&DegRVY5{ zZ>U)rYTEX=F<4pNRG~4U?zI!VrRg$F(q)p)lg(V{&6XKUXKW^tt0Y@y)7jM~n=kl_ GXa4|FRg;1M literal 0 HcmV?d00001