diff --git a/eng/Versions.props b/eng/Versions.props index fb8f027e0f2205..d70bf1b85b3f59 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -165,7 +165,7 @@ 2.0.4 4.12.0 2.14.3 - 6.0.100-rc.2.21463.12 + 6.0.100-rc.2.21473.29 6.0.0-preview-20210916.1 diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index c4c5a1875c3bfc..8afeea6cdeeb13 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -11,6 +11,7 @@ Wasm.Build.Tests.LocalEMSDKTests Wasm.Build.Tests.MainWithArgsTests Wasm.Build.Tests.NativeBuildTests Wasm.Build.Tests.NativeLibraryTests +Wasm.Build.Tests.PInvokeTableGeneratorTests Wasm.Build.Tests.RebuildTests Wasm.Build.Tests.SatelliteAssembliesTests Wasm.Build.Tests.WasmBuildAppTest diff --git a/src/libraries/workloads-testing.targets b/src/libraries/workloads-testing.targets index d3dc57267a608a..6a8fa5055bbbb3 100644 --- a/src/libraries/workloads-testing.targets +++ b/src/libraries/workloads-testing.targets @@ -16,7 +16,6 @@ - @@ -41,7 +40,6 @@ - diff --git a/src/mono/wasm/BlazorOverwrite.targets b/src/mono/wasm/BlazorOverwrite.targets deleted file mode 100644 index a276d385723725..00000000000000 --- a/src/mono/wasm/BlazorOverwrite.targets +++ /dev/null @@ -1,741 +0,0 @@ - - - - - true - - - true - - - - - $(MSBuildThisFileDirectory)..\ - <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">net6.0 - <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' != 'Core'">net472 - <_BlazorWebAssemblySdkTasksAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\$(_BlazorWebAssemblySdkTasksTFM)\Microsoft.NET.Sdk.BlazorWebAssembly.Tasks.dll - <_BlazorWebAssemblySdkToolAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\net6.0\Microsoft.NET.Sdk.BlazorWebAssembly.Tool.dll - - - - - - - - - - - - - - true - true - - - false - false - true - false - false - false - <_AggressiveAttributeTrimming Condition="'$(_AggressiveAttributeTrimming)' == ''">true - false - true - - - false - false - false - false - true - - - false - - <_TargetingNET60OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '6.0'))">true - - - - false - - true - true - false - ComputeFilesToPublish;_GatherWasmFilesToPublish;$(WasmNestedPublishAppDependsOn) - <_ScrambleDotnetJsFileNameAfterThisTarget Condition="'$(UsingBrowserRuntimeWorkload)' != 'true'">ResolveRuntimePackAssets - <_ScrambleDotnetJsFileNameAfterThisTarget Condition="'$(UsingBrowserRuntimeWorkload)' == 'true'">WasmBuildApp - - - Publish - - - - - - - - - - - - $(ResolveStaticWebAssetsInputsDependsOn); - _AddBlazorWasmStaticWebAssets; - - - - _GenerateBuildBlazorBootJson; - $(StaticWebAssetsPrepareForRunDependsOn) - - - - $(ResolvePublishStaticWebAssetsDependsOn); - ProcessPublishFilesForBlazor; - ComputeBlazorExtensions; - _AddPublishBlazorBootJsonToStaticWebAssets; - - - - $(GenerateStaticWebAssetsPublishManifestDependsOn); - GeneratePublishBlazorBootJson; - - - - - - - - - - <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion) - <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion) - <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js - - - - <_DotNetJsItem Remove="@(_DotNetJsItem)" /> - <_DotNetJsItem Include="@(WasmNativeAsset)" Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'"> - _framework/$(_BlazorDotnetJsFileName) - native - true - - - - - <_DotnetJsStaticWebAssetCandidate Remove="@(_DotnetJsStaticWebAssetCandidate)" /> - <_DotnetJsCopyCandidates Remove="@(_DotnetJsCopyCandidates)" /> - - - - - - - - - - - - <_DotnetJsStaticWebAsset Include="@(_DotnetJsStaticWebAssetCandidate->'%(ContentRoot)_framework\dotnet.js')" /> - <_BlazorStaticWebAsset Include="@(_DotnetJsStaticWebAsset)" /> - - - - - - - <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion) - <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion) - <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js - - - - <_DotNetJsItem Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.DestinationSubPath)' == 'dotnet.js' AND '%(ReferenceCopyLocalPaths.AssetType)' == 'native'"> - _framework/$(_BlazorDotnetJsFileName) - - - - - <_DotNetJsItem Remove="@(_DotNetJsItem)" /> - <_DotNetJsItem Include="@(WasmNativeAsset)" Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'"> - _framework/$(_BlazorDotnetJsFileName) - native - true - - - - - - - - - - - - - <_DotnetJsStaticWebAsset Include="@(_DotnetJsStaticWebAssetCandidate->'%(ContentRoot)_framework\dotnet.js')" /> - <_BlazorStaticWebAsset Include="@(_DotnetJsStaticWebAsset)" /> - - - - - - - - - - - - - - - - <_BlazorEnableTimeZoneSupport>$(BlazorEnableTimeZoneSupport) - <_BlazorEnableTimeZoneSupport Condition="'$(_BlazorEnableTimeZoneSupport)' == ''">true - <_BlazorInvariantGlobalization>$(InvariantGlobalization) - <_BlazorInvariantGlobalization Condition="'$(_BlazorInvariantGlobalization)' == ''">true - <_BlazorCopyOutputSymbolsToOutputDirectory>$(CopyOutputSymbolsToOutputDirectory) - <_BlazorCopyOutputSymbolsToOutputDirectory Condition="'$(_BlazorCopyOutputSymbolsToOutputDirectory)'==''">true - <_BlazorWebAssemblyLoadAllGlobalizationData>$(BlazorWebAssemblyLoadAllGlobalizationData) - <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(_BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false - - - $(OutputPath)$(PublishDirName)\ - - - - - - <_BlazorJSFile Include="$(BlazorWebAssemblyJSPath)" /> - <_BlazorJSFile Include="$(BlazorWebAssemblyJSMapPath)" Condition="Exists('$(BlazorWebAssemblyJSMapPath)')" /> - <_BlazorJsFile> - _framework/%(Filename)%(Extension) - - - - - - <_BlazorConfigFileCandidates Include="@(StaticWebAsset)" Condition="'%(SourceType)' == 'Discovered'" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_BlazorBuildGZipCompressDirectory>$(IntermediateOutputPath)build-gz\ - - - - - - - - - - - - - - - - <_BlazorBuildGZipCompressedFile> - %(RelatedAsset) - - - <_BlazorGzipStaticWebAsset Include="@(_BlazorBuildGZipCompressedFile->'%(FullPath)')" /> - - - - <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json - - - - <_BuildBlazorBootJson - Include="$(_BlazorBuildBootJsonPath)" - RelativePath="_framework/blazor.boot.json" /> - - - - - - - - - - - - - - - - - - - - <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json - <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false - - - - <_BlazorJsModuleCandidatesForBuild - Include="@(StaticWebAsset)" - Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Publish'" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_BlazorTypeGranularTrimmerDescriptorFile>$(IntermediateOutputPath)typegranularity.trimmerdescriptor.xml - - - - <_BlazorTypeGranularAssembly - Include="@(ManagedAssemblyToLink)" - Condition="'%(Extension)' == '.dll' AND $([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))"> - false - all - - - - - - - - - - - - - - - - - <_BlazorAotEnabled>$(UsingBrowserRuntimeWorkload) - <_BlazorAotEnabled Condition="'$(_BlazorAotEnabled)' == ''">false - <_BlazorLinkerEnabled>$(PublishTrimmed) - <_BlazorLinkerEnabled Condition="'$(_BlazorLinkerEnabled)' == ''">true - - - - - - <_BlazorPublishPrefilteredAssets - Include="@(StaticWebAsset)" - Condition="'%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture' or '%(AssetRole)' == 'Alternative'" /> - - - - - - - - - - - - - - - - - <_BlazorExtensionsCandidate Include="@(BlazorPublishExtension->'%(FullPath)')"> - $(PackageId) - Computed - $(PublishDir)wwwroot - $(StaticWebAssetBasePath) - %(BlazorPublishExtension.RelativePath) - Publish - All - Primary - BlazorWebAssemblyResource - extension:%(BlazorPublishExtension.ExtensionName) - Never - PreserveNewest - %(BlazorPublishExtension.Identity) - - - - - - - - - - - - - - - - - - - <_PublishBlazorBootJson - Include="$(IntermediateOutputPath)blazor.publish.boot.json" - RelativePath="_framework/blazor.boot.json" /> - - - - - - - - - - - <_BlazorPublishAsset - Include="@(StaticWebAsset)" - Condition="'%(AssetKind)' != 'Build' and '%(StaticWebAsset.AssetTraitValue)' != 'manifest' and ('%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture') and '%(StaticWebAsset.AssetTraitValue)' != 'boot'" /> - - <_BlazorPublishConfigFile - Include="@(StaticWebAsset)" - Condition="'%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' and '%(StaticWebAsset.AssetTraitValue)' == 'settings'"/> - - <_BlazorJsModuleCandidatesForPublish - Include="@(StaticWebAsset)" - Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Build'" /> - - - <_BlazorPublishAsset Remove="@(_BlazorExtensionsCandidatesForPublish)" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_CompressedFileOutputPath>$(IntermediateOutputPath)compress\ - <_BlazorWebAssemblyBrotliIncremental>true - - - - <_DotNetHostDirectory>$(NetCoreRoot) - <_DotNetHostFileName>dotnet - <_DotNetHostFileName Condition="'$(OS)' == 'Windows_NT'">dotnet.exe - - - - - - - - <_GzipFileToCompressForPublish Include="@(StaticWebAsset)" - Condition="'%(AssetKind)' != 'Build' and ('%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture')" > - %(Identity) - Alternative - Content-Encoding - gzip - - - <_BrotliFileToCompressForPublish Include="@(_GzipFileToCompressForPublish)" Condition="'%(AssetKind)' != 'Build'"> - br - - - - <_AlreadyGzipCompressedAssets - Include="@(StaticWebAsset)" - Condition="'%(AssetKind)' != 'Build' and ('%(StaticWebAsset.AssetTraitName)' == 'Content-Encoding' and '%(StaticWebAsset.AssetTraitValue)' == 'gzip')" /> - <_GzipFileToCompressForPublish Remove="@(_AlreadyGzipCompressedAssets->'%(RelatedAsset)')" /> - - - - - - - - - - - - - - - - <_BlazorPublishGZipCompressedFile> - %(RelatedAsset) - - <_BlazorPublishBrotliCompressedFile> - %(RelatedAsset) - - - - - - - - diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 1c282834433a6d..e564d43520b592 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -143,6 +143,7 @@ <_MonoAotCrossCompilerPath>@(MonoAotCrossCompiler->WithMetadataValue('RuntimeIdentifier','browser-wasm')) <_EmccDefaultFlagsRsp>$([MSBuild]::NormalizePath($(_WasmRuntimePackSrcDir), 'emcc-default.rsp')) + false true true $(WasmBuildNative) @@ -156,8 +157,7 @@ <_EmccAssertionLevelDefault>0 <_EmccOptimizationFlagDefault Condition="'$(_WasmDevel)' == 'true'">-O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault) - <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == '' and '$(OS)' != 'Windows_NT' and '$(Configuration)' == 'Debug'">-Os - <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == '' and '$(Configuration)' != 'Debug'">-Oz + <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == '' and '$(Configuration)' == 'Debug' and '$(WasmBuildingForNestedPublish)' != 'true'">-O1 <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == ''">-Oz $(_EmccOptimizationFlagDefault) @@ -206,6 +206,9 @@ <_EmccLDFlags Include="@(_EmccCommonFlags)" /> <_EmccLDFlags Include="-s TOTAL_MEMORY=$(EmccTotalMemory)" /> + + <_EmccLDFlags Include="-s ERROR_ON_UNDEFINED_SYMBOLS=0" Condition="'$(WasmBuildingForNestedPublish)' != 'true'" /> + <_DriverCDependencies Include="$(_WasmPInvokeHPath);$(_WasmICallTablePath)" /> <_DriverCDependencies Include="$(_DriverGenCPath)" Condition="'$(_DriverGenCNeeded)' == 'true'" /> @@ -296,7 +299,8 @@ Inputs="@(_BitcodeFile);$(_EmccDefaultFlagsRsp);$(_EmccCompileBitcodeRsp)" Outputs="@(_BitcodeFile->'%(ObjectFile)')" Condition="'$(_WasmShouldAOT)' == 'true' and @(_BitcodeFile->Count()) > 0" - DependsOnTargets="_WasmWriteRspForCompilingBitcode"> + DependsOnTargets="_WasmWriteRspForCompilingBitcode" + Returns="@(FileWrites)"> <_BitCodeFile Dependencies="%(_BitCodeFile.Dependencies);$(_EmccDefaultFlagsRsp);$(_EmccCompileBitcodeRsp)" /> @@ -362,7 +366,8 @@ + DependsOnTargets="_WasmSelectRuntimeComponentsForLinking;_WasmCompileAssemblyBitCodeFilesForAOT;_WasmWriteRspFilesForLinking" + Returns="@(FileWrites)" > diff --git a/src/tasks/WasmAppBuilder/EmccCompile.cs b/src/tasks/WasmAppBuilder/EmccCompile.cs index a5d6af6c3b473a..b0bc0ff80050b3 100644 --- a/src/tasks/WasmAppBuilder/EmccCompile.cs +++ b/src/tasks/WasmAppBuilder/EmccCompile.cs @@ -95,6 +95,7 @@ private bool ExecuteActual() if (!ShouldCompile(srcFile, objFile, depFiles, out string reason)) { Log.LogMessage(MessageImportance.Low, $"Skipping {srcFile} because {reason}."); + outputItems.Add(CreateOutputItemFor(srcFile, objFile)); } else { @@ -107,7 +108,8 @@ private bool ExecuteActual() if (_numCompiled == _totalFiles) { // nothing to do! - return true; + OutputFiles = outputItems.ToArray(); + return !Log.HasLoggedErrors; } if (_numCompiled > 0) @@ -200,9 +202,7 @@ bool ProcessSourceFile(string srcFile, string objFile) else Log.LogMessage(MessageImportance.Low, $"Copied {tmpObjFile} to {objFile}"); - ITaskItem newItem = new TaskItem(objFile); - newItem.SetMetadata("SourceFile", srcFile); - outputItems.Add(newItem); + outputItems.Add(CreateOutputItemFor(srcFile, objFile)); int count = Interlocked.Increment(ref _numCompiled); Log.LogMessage(MessageImportance.High, $"[{count}/{_totalFiles}] {Path.GetFileName(srcFile)} -> {Path.GetFileName(objFile)} [took {elapsedSecs:F}s]"); @@ -219,6 +219,13 @@ bool ProcessSourceFile(string srcFile, string objFile) File.Delete(tmpObjFile); } } + + ITaskItem CreateOutputItemFor(string srcFile, string objFile) + { + ITaskItem newItem = new TaskItem(objFile); + newItem.SetMetadata("SourceFile", srcFile); + return newItem; + } } private bool ShouldCompile(string srcFile, string objFile, string[] depFiles, out string reason) diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs index 825de67d6e2704..7264944fa5b571 100644 --- a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs @@ -3,13 +3,10 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; -using System.Text.Json; using System.Reflection; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -121,25 +118,42 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules w.WriteLine("// GENERATED FILE, DO NOT MODIFY"); w.WriteLine(); - var decls = new HashSet(); - foreach (var pinvoke in pinvokes.OrderBy(l => l.EntryPoint)) + var pinvokesGroupedByEntryPoint = pinvokes.Where(l => modules.ContainsKey(l.Module)) + .OrderBy(l => l.EntryPoint) + .GroupBy(l => l.EntryPoint); + + var comparer = new PInvokeComparer(); + foreach (IGrouping group in pinvokesGroupedByEntryPoint) { - if (modules.ContainsKey(pinvoke.Module)) { - try + var candidates = group.Distinct(comparer).ToArray(); + PInvoke first = candidates[0]; + if (ShouldTreatAsVariadic(candidates)) + { + Log.LogWarning($"Found a native function ({first.EntryPoint}) with varargs, which is not supported. Calling it will fail at runtime. Module: {first.Module}." + + $" Managed DllImports: {Environment.NewLine}{CandidatesToString(candidates)}"); + + string? decl = GenPInvokeDecl(first, treatAsVariadic: true); + if (decl != null) { - var decl = GenPInvokeDecl(pinvoke); - if (decls.Contains(decl)) - continue; + w.WriteLine($"// Variadic signature created for"); + foreach (PInvoke pinvoke in candidates) + w.WriteLine($"// {pinvoke.Method}"); w.WriteLine(decl); - decls.Add(decl); - } - catch (NotSupportedException) - { - // See the FIXME in GenPInvokeDecl - Log.LogWarning($"Cannot handle function pointer arguments/return value in pinvoke method '{pinvoke.Method}' in type '{pinvoke.Method.DeclaringType}'."); - pinvoke.Skip = true; } + + continue; + } + + var decls = new HashSet(); + foreach (var candidate in candidates) + { + var decl = GenPInvokeDecl(candidate, treatAsVariadic: false); + if (decl == null || decls.Contains(decl)) + continue; + + w.WriteLine(decl); + decls.Add(decl); } } @@ -186,6 +200,25 @@ static string ModuleNameToId(string name) return fixedName; } + + static bool ShouldTreatAsVariadic(PInvoke[] candidates) + { + if (candidates.Length < 2) + return false; + + PInvoke first = candidates[0]; + if (TryIsMethodGetParametersUnsupported(first.Method, out _)) + return false; + + int firstNumArgs = first.Method.GetParameters().Length; + return candidates + .Skip(1) + .Any(c => !TryIsMethodGetParametersUnsupported(c.Method, out _) && + c.Method.GetParameters().Length != firstNumArgs); + } + + static string CandidatesToString(IEnumerable group) + => string.Join(Environment.NewLine, group); } private string MapType (Type t) @@ -205,7 +238,29 @@ private string MapType (Type t) return "int"; } - private string GenPInvokeDecl(PInvoke pinvoke) + // FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types + // https://github.com/dotnet/runtime/issues/43791 + private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotNullWhen(true)] out string? reason) + { + try + { + method.GetParameters(); + } + catch (NotSupportedException nse) + { + reason = nse.Message; + return true; + } + catch + { + // not concerned with other exceptions + } + + reason = null; + return false; + } + + private string? GenPInvokeDecl(PInvoke pinvoke, bool treatAsVariadic) { var sb = new StringBuilder(); var method = pinvoke.Method; @@ -215,15 +270,33 @@ private string GenPInvokeDecl(PInvoke pinvoke) sb.Append($"int {pinvoke.EntryPoint} (int, int, int, int, int);"); return sb.ToString(); } + + if (TryIsMethodGetParametersUnsupported(pinvoke.Method, out string? reason)) + { + Log.LogWarning($"Skipping the following DllImport because '{reason}'. {Environment.NewLine} {pinvoke.Method}"); + pinvoke.Skip = true; + return null; + } + sb.Append(MapType(method.ReturnType)); sb.Append($" {pinvoke.EntryPoint} ("); - int pindex = 0; - var pars = method.GetParameters(); - foreach (var p in pars) { - if (pindex > 0) - sb.Append(','); - sb.Append(MapType(pars[pindex].ParameterType)); - pindex++; + if (!treatAsVariadic) + { + int pindex = 0; + var pars = method.GetParameters(); + foreach (var p in pars) { + if (pindex > 0) + sb.Append(','); + sb.Append(MapType(pars[pindex].ParameterType)); + pindex++; + } + } + else + { + // FIXME: handle sigs with different first args + ParameterInfo firstParam = method.GetParameters()[0]; + sb.Append(MapType(firstParam.ParameterType)); + sb.Append(", ..."); } sb.Append(");"); return sb.ToString(); @@ -366,7 +439,7 @@ private static bool IsBlittable (Type type) private static void Error (string msg) => throw new LogAsErrorException(msg); } -internal class PInvoke +internal class PInvoke : IEquatable { public PInvoke(string entryPoint, string module, MethodInfo method) { @@ -379,6 +452,30 @@ public PInvoke(string entryPoint, string module, MethodInfo method) public string Module; public MethodInfo Method; public bool Skip; + + public bool Equals(PInvoke? other) + => other != null && + string.Equals(EntryPoint, other.EntryPoint, StringComparison.Ordinal) && + string.Equals(Module, other.Module, StringComparison.Ordinal) && + string.Equals(Method.ToString(), other.Method.ToString(), StringComparison.Ordinal); + + public override string ToString() => $"{{ EntryPoint: {EntryPoint}, Module: {Module}, Method: {Method}, Skip: {Skip} }}"; +} + +internal class PInvokeComparer : IEqualityComparer +{ + public bool Equals(PInvoke? x, PInvoke? y) + { + if (x == null && y == null) + return true; + if (x == null || y == null) + return false; + + return x.Equals(y); + } + + public int GetHashCode(PInvoke pinvoke) + => $"{pinvoke.EntryPoint}{pinvoke.Module}{pinvoke.Method}".GetHashCode(); } internal class PInvokeCallback diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs index 687429a3fca482..66b5c5b2782766 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs @@ -1,7 +1,6 @@ // 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 System.Linq; using Xunit; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs index 6c7e3ed788be56..727e6589c4c6d2 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs @@ -79,6 +79,7 @@ private CommandResult PublishForRequiresWorkloadTest(string config, string extra [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/59538")] public void Net50Projects_NativeReference(string config) => BuildNet50Project(config, aot: false, expectError: true, @""); @@ -92,6 +93,7 @@ public void Net50Projects_NativeReference(string config) [Theory] [MemberData(nameof(Net50TestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/59538")] public void Net50Projects_AOT(string config, bool aot, bool expectError) => BuildNet50Project(config, aot: aot, expectError: expectError); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs index 3ccf400ee92113..eb306286ba5e0d 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -1,6 +1,7 @@ // 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; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index 9047fd062a82e4..cd0d3ac4396349 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -77,6 +77,7 @@ protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, buildArgs = newBuildArgs; _testOutput.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}"); + Console.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}"); (_, string output) = BuildProject(buildArgs, id: id, dotnetWasmFromRuntimePack: false, @@ -137,6 +138,18 @@ internal void CompareStat(IDictionary oldStat, IDictionary GetFilesTable(bool unchanged, params string[] baseDirs) + { + var dict = new Dictionary(); + foreach (var baseDir in baseDirs) + { + foreach (var file in Directory.EnumerateFiles(baseDir, "*", new EnumerationOptions { RecurseSubdirectories = true })) + dict[Path.GetFileName(file)] = (file, unchanged); + } + + return dict; + } + internal IDictionary GetFilesTable(BuildArgs buildArgs, BuildPaths paths, bool unchanged) { List files = new() diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs index 96f6d41fa10025..3131dec390ce05 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using System.Linq; using Wasm.Build.Tests; using Xunit; @@ -33,5 +34,60 @@ public void NoOpRebuildForNativeBuilds(BuildArgs buildArgs, bool nativeRelink, b CompareStat(originalStat, newStat, pathsDict.Values); RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug")] + [InlineData("Release")] + public void BlazorNoopRebuild(string config) + { + string id = $"blz_rebuild_{config}"; + string projectFile = CreateBlazorWasmTemplateProject(id); + AddItemsPropertiesToProject(projectFile, extraProperties: "true"); + + string objDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm"); + + BlazorBuild(id, config, NativeFilesType.Relinked); + File.Move(Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build.binlog"), + Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build-first.binlog")); + + var pathsDict = GetFilesTable(true, objDir); + pathsDict.Remove("runtime-icall-table.h"); + var originalStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + + // build again + BlazorBuild(id, config, NativeFilesType.Relinked); + var newStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + + CompareStat(originalStat, newStat, pathsDict.Values); + } + + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug")] + [InlineData("Release")] + public void BlazorOnlyLinkRebuild(string config) + { + string id = $"blz_relink_{config}"; + string projectFile = CreateBlazorWasmTemplateProject(id); + AddItemsPropertiesToProject(projectFile, extraProperties: "true"); + + string objDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm"); + + BlazorBuild(id, config, NativeFilesType.Relinked); + File.Move(Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build.binlog"), + Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build-first.binlog")); + + var pathsDict = GetFilesTable(true, objDir); + pathsDict.Remove("runtime-icall-table.h"); + pathsDict.UpdateTo(unchanged: false, "dotnet.wasm", "dotnet.js", "emcc-link.rsp"); + + var originalStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + + // build again + BlazorBuild(id, config, NativeFilesType.Relinked, "-p:EmccLinkOptimizationFlag=-O1"); + var newStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + + CompareStat(originalStat, newStat, pathsDict.Values); + } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs new file mode 100644 index 00000000000000..27783ae85c95ad --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs @@ -0,0 +1,139 @@ +// 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 PInvokeTableGeneratorTests : BuildTestBase + { + public PInvokeTableGeneratorTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [BuildAndRun(host: RunHost.V8)] + public void NativeLibraryWithVariadicFunctions(BuildArgs buildArgs, RunHost host, string id) + { + string code = @" + using System; + using System.Runtime.InteropServices; + public class Test + { + public static int Main(string[] args) + { + Console.WriteLine($""Main running""); + if (args.Length > 0) + { + // We don't want to run this, because we can't call variadic functions + Console.WriteLine($""sum_three: {sum_three(7, 14, 21)}""); + Console.WriteLine($""sum_two: {sum_two(3, 6)}""); + Console.WriteLine($""sum_one: {sum_one(5)}""); + } + return 42; + } + + [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_one(int a); + [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_two(int a, int b); + [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_three(int a, int b, int c); + }"; + + (buildArgs, string output) = BuildForVariadicFunctionTests(code, + buildArgs with { ProjectName = $"variadic_{buildArgs.Config}_{id}" }, + id); + Assert.Matches("warning.*native function.*sum.*varargs", output); + Assert.Matches("warning.*sum_(one|two|three)", output); + + output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + Assert.Contains("Main running", output); + } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [BuildAndRun(host: RunHost.V8)] + public void DllImportWithFunctionPointersCompilesWithWarning(BuildArgs buildArgs, RunHost host, string id) + { + string code = @" + using System; + using System.Runtime.InteropServices; + public class Test + { + public static int Main() + { + Console.WriteLine($""Main running""); + return 42; + } + + [DllImport(""variadic"", EntryPoint=""sum"")] + public unsafe static extern int using_sum_one(delegate* unmanaged callback); + + [DllImport(""variadic"", EntryPoint=""sum"")] + public static extern int sum_one(int a, int b); + }"; + + (buildArgs, string output) = BuildForVariadicFunctionTests(code, + buildArgs with { ProjectName = $"fnptr_{buildArgs.Config}_{id}" }, + id); + Assert.Matches("warning.*Skipping.*because.*function pointer", output); + Assert.Matches("warning.*using_sum_one", output); + + output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + Assert.Contains("Main running", output); + } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [BuildAndRun(host: RunHost.V8)] + public void DllImportWithFunctionPointers_ForVariadicFunction_CompilesWithWarning(BuildArgs buildArgs, RunHost host, string id) + { + string code = @" + using System; + using System.Runtime.InteropServices; + public class Test + { + public static int Main() + { + Console.WriteLine($""Main running""); + return 42; + } + + [DllImport(""variadic"", EntryPoint=""sum"")] + public unsafe static extern int using_sum_one(delegate* unmanaged callback); + }"; + + (buildArgs, string output) = BuildForVariadicFunctionTests(code, + buildArgs with { ProjectName = $"fnptr_variadic_{buildArgs.Config}_{id}" }, + id); + Assert.Matches("warning.*Skipping.*because.*function pointer", output); + Assert.Matches("warning.*using_sum_one", output); + + output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + Assert.Contains("Main running", output); + } + + private (BuildArgs, string) BuildForVariadicFunctionTests(string programText, BuildArgs buildArgs, string id) + { + string filename = "variadic.o"; + buildArgs = ExpandBuildArgs(buildArgs, + extraItems: $"", + extraProperties: "true<_WasmDevel>true"); + + (_, string output) = BuildProject(buildArgs, + initProject: () => + { + File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", filename), + Path.Combine(_projectDir!, filename)); + }, + publish: buildArgs.AOT, + id: id, + dotnetWasmFromRuntimePack: false); + + return (buildArgs, output); + } + } +} diff --git a/src/tests/BuildWasmApps/testassets/native-libs/variadic.c b/src/tests/BuildWasmApps/testassets/native-libs/variadic.c new file mode 100644 index 00000000000000..cd4009439d19da --- /dev/null +++ b/src/tests/BuildWasmApps/testassets/native-libs/variadic.c @@ -0,0 +1,14 @@ +#include + +int sum(int n, ...) +{ + int result = 0; + va_list ptr; + va_start(ptr, n); + + for (int i = 0; i < n; i++) + result += va_arg(ptr, int); + + va_end(ptr); + return result; +} diff --git a/src/tests/BuildWasmApps/testassets/native-libs/variadic.o b/src/tests/BuildWasmApps/testassets/native-libs/variadic.o new file mode 100644 index 00000000000000..b4558ce3519793 Binary files /dev/null and b/src/tests/BuildWasmApps/testassets/native-libs/variadic.o differ