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

[wasm] Collect, and process satellite assemblies automatically (#53656)

上级 2a61c25b
......@@ -69,8 +69,7 @@
<Target Name="_BundleAOTTestWasmAppForHelix" DependsOnTargets="PrepareForWasmBuildApp">
<ItemGroup>
<BundleFiles Include="$(WasmMainJSPath)" TargetDir="publish" />
<BundleFiles Include="@(WasmSatelliteAssemblies)" TargetDir="publish/%(WasmSatelliteAssemblies.CultureName)" />
<BundleFiles Include="@(WasmAssembliesToBundle)" TargetDir="publish" />
<BundleFiles Include="@(WasmAssembliesToBundle)" TargetDir="publish\%(WasmAssembliesToBundle.RecursiveDir)" />
<BundleFiles Include="$(MonoProjectRoot)\wasm\data\aot-tests\*" TargetDir="publish" />
</ItemGroup>
......@@ -131,17 +130,17 @@
<WasmDebugLevel Condition="'$(DebuggerSupport)' == 'true' and '$(WasmDebugLevel)' == ''">-1</WasmDebugLevel>
</PropertyGroup>
<ItemGroup>
<WasmSatelliteAssemblies Include="$(PublishDir)*\*.resources.dll" />
<WasmSatelliteAssemblies CultureName="$([System.IO.Directory]::GetParent('%(Identity)').Name)" />
<ItemGroup Condition="'$(IncludeSatelliteAssembliesInVFS)' == 'true'">
<_SatelliteAssemblies Include="$(PublishDir)*\*.resources.dll" />
<_SatelliteAssemblies CultureName="$([System.IO.Directory]::GetParent('%(Identity)').Name)" />
<_SatelliteAssemblies TargetPath="%(CultureName)\%(FileName)%(Extension)" />
<WasmAssembliesToBundle Include="$(PublishDir)\*.dll"/>
<WasmFilesToIncludeInFileSystem Include="@(_SatelliteAssemblies)" />
</ItemGroup>
<ItemGroup>
<WasmAssembliesToBundle Include="$(PublishDir)\**\*.dll"/>
<WasmFilesToIncludeInFileSystem Include="@(ContentWithTargetPath)" />
<WasmFilesToIncludeInFileSystem
Include="@(WasmSatelliteAssemblies)"
TargetPath="%(WasmSatelliteAssemblies.CultureName)\%(WasmSatelliteAssemblies.FileName)%(WasmSatelliteAssemblies.Extension)"
Condition="'$(IncludeSatelliteAssembliesInVFS)' == 'true'" />
<_CopyLocalPaths
Include="@(PublishItemsOutputGroupOutputs)"
......
......@@ -353,6 +353,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
<AotArguments>@(MonoAOTCompilerDefaultAotArguments, ';')</AotArguments>
<ProcessArguments>@(MonoAOTCompilerDefaultProcessArguments, ';')</ProcessArguments>
</_AotInputAssemblies>
<_AOT_InternalForceInterpretAssemblies Include="@(_WasmAssembliesInternal->WithMetadataValue('_InternalForceInterpret', 'true'))" />
<_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)" />
......@@ -422,9 +423,6 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
<_WasmNativeFileForLinking Include="@(_BitcodeFile)" />
</ItemGroup>
<Error Condition="@(_BitcodeFile->Count()) != @(_AOTAssemblies->Count())"
Text="Bug: Number of aot assemblies doesn't match the number of generated bitcode files. BitcodeFiles: @(_BitcodeFile->Count()) vs Assemblies: @(_AOTAssemblies->Count())" />
</Target>
<!-- '$(ArchiveTests)' != 'true' is to skip on CI for now -->
......
......@@ -61,7 +61,6 @@
Public items:
- @(WasmExtraFilesToDeploy) - Files to copy to $(WasmAppDir).
(relative path can be set via %(TargetPath) metadata)
- @(WasmSatelliteAssemblies)
- @(WasmFilesToIncludeInFileSystem) - Files to include in the vfs
- @(WasmNativeAsset) - Native files to be added to `NativeAssets` in the bundle.
......@@ -115,6 +114,13 @@
<MakeDir Directories="$(_WasmIntermediateOutputPath)" />
<ItemGroup>
<_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" />
<_WasmSatelliteAssemblies Include="@(_WasmAssembliesInternal)" />
<_WasmSatelliteAssemblies Remove="@(_WasmSatelliteAssemblies)" Condition="!$([System.String]::Copy('%(Identity)').EndsWith('.resources.dll'))" />
<!-- FIXME: Only include the ones with valid culture name -->
<_WasmSatelliteAssemblies CultureName="$([System.IO.Directory]::GetParent('%(Identity)').Name)" />
<_WasmAssembliesInternal Remove="@(_WasmSatelliteAssemblies)" />
</ItemGroup>
</Target>
......@@ -142,7 +148,7 @@
MainJS="$(WasmMainJSPath)"
Assemblies="@(_WasmAssembliesInternal)"
InvariantGlobalization="$(InvariantGlobalization)"
SatelliteAssemblies="@(WasmSatelliteAssemblies)"
SatelliteAssemblies="@(_WasmSatelliteAssemblies)"
FilesToIncludeInFileSystem="@(WasmFilesToIncludeInFileSystem)"
IcuDataFileName="$(WasmIcuDataFileName)"
RemoteSources="@(WasmRemoteSources)"
......@@ -193,11 +199,5 @@
<ItemGroup>
<WasmAssembliesFinal Include="@(_WasmAssembliesInternal)" LlvmBitCodeFile="" />
</ItemGroup>
<!-- $(WasmResolveAssembliesBeforeBuild) can add more assemblies, so no point checking the count in that case -->
<Error Condition="'$(WasmResolveAssembliesBeforeBuild)' != 'true' and @(WasmAssembliesFinal->Distinct()->Count()) != @(WasmAssembliesToBundle->Distinct()->Count()) and '$(WasmDedup)' != 'true'"
Text="Bug: The wasm build started with @(WasmAssembliesToBundle->Count()) assemblies, but completed with @(WasmAssembliesFinal->Count()).
WasmAssembliesToBundle: @(WasmAssembliesToBundle)
WasmAssembliesFinal: @(WasmAssembliesFinal)" />
</Target>
</Project>
......@@ -33,17 +33,15 @@
</PropertyGroup>
<ItemGroup>
<WasmSatelliteAssemblies Include="$(OriginalPublishDir)**\*.resources.dll" />
<WasmSatelliteAssemblies CultureName="$([System.IO.Directory]::GetParent('%(Identity)').Name)" />
<WasmAssembliesToBundle Include="$(OriginalPublishDir)*.dll" />
<WasmAssembliesToBundle Include="$(OriginalPublishDir)**\*.dll" />
<_ExtraFiles Include="$(ExtraFilesPath)**\*" />
<WasmFilesToIncludeInFileSystem Include="@(_ExtraFiles)" TargetPath="%(RecursiveDir)%(FileName)%(Extension)" />
</ItemGroup>
<ItemGroup Condition="'$(IncludeSatelliteAssembliesInVFS)' == 'true'">
<_SatelliteAssembliesForVFS Include="@(WasmSatelliteAssemblies)" />
<_SatelliteAssembliesForVFS Include="$(OriginalPublishDir)**\*.resources.dll" />
<_SatelliteAssembliesForVFS CultureName="$([System.IO.Directory]::GetParent('%(Identity)').Name)" />
<_SatelliteAssembliesForVFS TargetPath="%(CultureName)\%(FileName)%(Extension)" />
<WasmFilesToIncludeInFileSystem Include="@(_SatelliteAssembliesForVFS)" />
......
......@@ -189,6 +189,13 @@ public override bool Execute ()
{
string culture = assembly.GetMetadata("CultureName") ?? string.Empty;
string fullPath = assembly.GetMetadata("Identity");
if (string.IsNullOrEmpty(culture))
{
Log.LogWarning($"Missing CultureName metadata for satellite assembly {fullPath}");
continue;
}
// FIXME: validate the culture?
string name = Path.GetFileName(fullPath);
string directory = Path.Combine(AppDir, config.AssemblyRoot, culture);
Directory.CreateDirectory(directory);
......
......@@ -8,9 +8,11 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
#nullable enable
......@@ -29,7 +31,6 @@ public abstract class BuildTestBase : IClassFixture<SharedBuildPerTestClassFixtu
protected static readonly string s_emsdkPath;
protected static readonly bool s_skipProjectCleanup;
protected static readonly string s_xharnessRunnerCommand;
protected string? _projectDir;
protected readonly ITestOutputHelper _testOutput;
protected string _logPath;
......@@ -139,10 +140,17 @@ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture bu
.WithRunHosts(host)
.UnwrapItemsAsArrays();
protected void RunAndTestWasmApp(BuildArgs buildArgs, RunHost host, string id, Action<string> test, string? buildDir=null, int expectedExitCode=0, string? args=null)
protected string RunAndTestWasmApp(BuildArgs buildArgs,
RunHost host,
string id,
Action<string>? test=null,
string? buildDir = null,
int expectedExitCode = 0,
string? args = null,
Dictionary<string, string>? envVars = null)
{
buildDir ??= _projectDir;
Dictionary<string, string>? envVars = new();
envVars ??= new();
envVars["XHARNESS_DISABLE_COLORED_OUTPUT"] = "true";
if (buildArgs.AOT)
{
......@@ -180,6 +188,11 @@ protected void RunAndTestWasmApp(BuildArgs buildArgs, RunHost host, string id, A
Assert.DoesNotContain("AOT: image 'System.Private.CoreLib' found.", output);
Assert.DoesNotContain($"AOT: image '{buildArgs.ProjectName}' found.", output);
}
if (test != null)
test(output);
return output;
}
protected static string RunWithXHarness(string testCommand, string testLogPath, string projectName, string bundleDir,
......@@ -209,6 +222,8 @@ protected void RunAndTestWasmApp(BuildArgs buildArgs, RunHost host, string id, A
args.Append($" --run {projectName}.dll");
args.Append($" {appArgs ?? string.Empty}");
_testOutput.WriteLine(string.Empty);
_testOutput.WriteLine($"---------- Running with {testCommand} ---------");
var (exitCode, output) = RunProcess("dotnet", _testOutput,
args: args.ToString(),
workingDir: bundleDir,
......@@ -251,14 +266,21 @@ protected static void InitProjectDir(string dir)
<WasmMainJSPath>runtime-test.js</WasmMainJSPath>
##EXTRA_PROPERTIES##
</PropertyGroup>
<ItemGroup>
##EXTRA_ITEMS##
</ItemGroup>
##INSERT_AT_END##
</Project>";
protected static BuildArgs GetBuildArgsWith(BuildArgs buildArgs, string? extraProperties=null, string projectTemplate=SimpleProjectTemplate)
protected static BuildArgs ExpandBuildArgs(BuildArgs buildArgs, string extraProperties="", string extraItems="", string insertAtEnd="", string projectTemplate=SimpleProjectTemplate)
{
if (buildArgs.AOT)
extraProperties = $"{extraProperties}\n<RunAOTCompilation>true</RunAOTCompilation>\n";
extraProperties = $"{extraProperties}\n<RunAOTCompilation>true</RunAOTCompilation>\n<EmccVerbose>false</EmccVerbose>\n";
string projectContents = projectTemplate.Replace("##EXTRA_PROPERTIES##", extraProperties ?? string.Empty);
string projectContents = projectTemplate
.Replace("##EXTRA_PROPERTIES##", extraProperties)
.Replace("##EXTRA_ITEMS##", extraItems)
.Replace("##INSERT_AT_END##", insertAtEnd);
return buildArgs with { ProjectFileContents = projectContents };
}
......@@ -273,10 +295,10 @@ protected static BuildArgs GetBuildArgsWith(BuildArgs buildArgs, string? extraPr
{
if (useCache && _buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product))
{
Console.WriteLine ($"Using existing build found at {product.BuildPath}, with build log at {product.LogFile}");
Console.WriteLine ($"Using existing build found at {product.ProjectDir}, with build log at {product.LogFile}");
Assert.True(product.Result, $"Found existing build at {product.BuildPath}, but it had failed. Check build log at {product.LogFile}");
_projectDir = product.BuildPath;
Assert.True(product.Result, $"Found existing build at {product.ProjectDir}, but it had failed. Check build log at {product.LogFile}");
_projectDir = product.ProjectDir;
// use this test's id for the run logs
_logPath = Path.Combine(s_logRoot, id);
......@@ -297,7 +319,6 @@ protected static BuildArgs GetBuildArgsWith(BuildArgs buildArgs, string? extraPr
throw new Exception("_projectDir should be set, to use createProject=false");
}
StringBuilder sb = new();
sb.Append("publish");
sb.Append(s_defaultBuildArgs);
......@@ -305,6 +326,7 @@ protected static BuildArgs GetBuildArgsWith(BuildArgs buildArgs, string? extraPr
sb.Append($" /p:Configuration={buildArgs.Config}");
string logFilePath = Path.Combine(_logPath, $"{buildArgs.ProjectName}.binlog");
_testOutput.WriteLine($"-------- Building ---------");
_testOutput.WriteLine($"Binlog path: {logFilePath}");
sb.Append($" /bl:\"{logFilePath}\" /v:minimal /nologo");
if (buildArgs.ExtraBuildArgs != null)
......@@ -378,8 +400,14 @@ protected static void AssertDotNetWasmJs(string bundleDir, bool fromRuntimePack)
{
string nativeDir = GetRuntimeNativeDir();
AssertFile(Path.Combine(nativeDir, "dotnet.wasm"), Path.Combine(bundleDir, "dotnet.wasm"), "Expected dotnet.wasm to be same as the runtime pack", same: fromRuntimePack);
AssertFile(Path.Combine(nativeDir, "dotnet.js"), Path.Combine(bundleDir, "dotnet.js"), "Expected dotnet.js to be same as the runtime pack", same: fromRuntimePack);
AssertNativeFile("dotnet.wasm");
AssertNativeFile("dotnet.js");
void AssertNativeFile(string file)
=> AssertFile(Path.Combine(nativeDir, file),
Path.Combine(bundleDir, file),
$"Expected {file} to be {(fromRuntimePack ? "the same as" : "different from")} the runtime pack",
same: fromRuntimePack);
}
protected static void AssertFilesDontExist(string dir, string[] filenames, string? label = null)
......@@ -464,6 +492,7 @@ protected static string GetRuntimeNativeDir()
_testOutput.WriteLine($"Running {path} {args}");
Console.WriteLine($"Running: {path}: {args}");
Console.WriteLine($"WorkingDirectory: {workingDir}");
_testOutput.WriteLine($"WorkingDirectory: {workingDir}");
StringBuilder outputBuilder = new ();
var processStartInfo = new ProcessStartInfo
{
......@@ -584,7 +613,7 @@ public static int Main()
<Target Name=""PrepareForWasmBuild"">
<ItemGroup>
<WasmAssembliesToBundle Include=""$(TargetDir)publish\*.dll"" />
<WasmAssembliesToBundle Include=""$(TargetDir)publish\**\*.dll"" />
</ItemGroup>
</Target>
......@@ -594,5 +623,5 @@ public static int Main()
}
public record BuildArgs(string ProjectName, string Config, bool AOT, string ProjectFileContents, string? ExtraBuildArgs);
public record BuildProduct(string BuildPath, string LogFile, bool Result);
public record BuildProduct(string ProjectDir, string LogFile, bool Result);
}
......@@ -49,7 +49,7 @@ public void RelinkingWithoutAOT(BuildArgs buildArgs, bool? invariantGlobalizatio
extraProperties = $"{extraProperties}<InvariantGlobalization>{invariantGlobalization}</InvariantGlobalization>";
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = GetBuildArgsWith(buildArgs, extraProperties);
buildArgs = ExpandBuildArgs(buildArgs, extraProperties);
if (dotnetWasmFromRuntimePack == null)
dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release");
......@@ -76,8 +76,8 @@ public static int Main()
hasIcudt: invariantGlobalization == null || invariantGlobalization.Value == false);
string expectedOutputString = invariantGlobalization == true
? "False - en (ES)"
: "True - Invariant Language (Invariant Country)";
? "True - Invariant Language (Invariant Country)"
: "False - es (ES)";
RunAndTestWasmApp(buildArgs, expectedExitCode: 42,
test: output => Assert.Contains(expectedOutputString, output), host: host, id: id);
}
......
......@@ -77,7 +77,7 @@ public static int Main(string[] args)
string programText = projectContents.Replace("##CODE##", code);
buildArgs = buildArgs with { ProjectName = projectName, ProjectFileContents = programText };
buildArgs = GetBuildArgsWith(buildArgs);
buildArgs = ExpandBuildArgs(buildArgs);
if (dotnetWasmFromRuntimePack == null)
dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release");
......
......@@ -39,7 +39,7 @@ public void Relinking_ErrorWhenMissingEMSDK(BuildArgs buildArgs, string emsdkPat
ProjectName = projectName,
ExtraBuildArgs = $"/p:EMSDK_PATH={emsdkPath}"
};
buildArgs = GetBuildArgsWith(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
(_, string buildOutput) = BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
......@@ -54,7 +54,7 @@ private void NativeBuild(string projectNamePrefix, string projectContents, Build
string projectName = $"{projectNamePrefix}_{buildArgs.Config}_{buildArgs.AOT}";
buildArgs = buildArgs with { ProjectName = projectName, ProjectFileContents = projectContents };
buildArgs = GetBuildArgsWith(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<WasmBuildNative>true</WasmBuildNative>");
Console.WriteLine ($"-- args: {buildArgs}, name: {projectName}");
BuildProject(buildArgs,
......
......@@ -27,7 +27,7 @@ public void NoOpRebuild(BuildArgs buildArgs, bool nativeRelink, RunHost host, st
bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT;
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = GetBuildArgsWith(buildArgs, $"<WasmBuildNative>{(nativeRelink ? "true" : "false")}</WasmBuildNative>");
buildArgs = ExpandBuildArgs(buildArgs, $"<WasmBuildNative>{(nativeRelink ? "true" : "false")}</WasmBuildNative>");
BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xunit;
using Xunit.Abstractions;
#nullable enable
namespace Wasm.Build.Tests
{
public class SatelliteAssembliesTests : BuildTestBase
{
public SatelliteAssembliesTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}
public static IEnumerable<object?[]> SatelliteAssemblyTestData(bool aot, bool relinking, RunHost host)
=> ConfigWithAOTData(aot)
.Multiply(
new object?[] { relinking, "es-ES", "got: hola" },
new object?[] { relinking, null, "got: hello" },
new object?[] { relinking, "ja-JP", "got: \u3053\u3093\u306B\u3061\u306F" })
.WithRunHosts(host)
.UnwrapItemsAsArrays();
[Theory]
[MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false, RunHost.All })]
[MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true, RunHost.All })]
[MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false, RunHost.All })]
public void ResourcesFromMainAssembly(BuildArgs buildArgs,
bool nativeRelink,
string? argCulture,
string expectedOutput,
RunHost host,
string id)
{
string projectName = $"sat_asm_from_main_asm";
bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT;
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs,
projectTemplate: s_resourcesProjectTemplate,
extraProperties: $"<WasmBuildNative>{(nativeRelink ? "true" : "false")}</WasmBuildNative>",
extraItems: $"<EmbeddedResource Include=\"..\\resx\\*\" />");
BuildProject(buildArgs,
initProject: () => CreateProgramForCultureTest($"{projectName}.words", "TestClass"),
dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
id: id);
string output = RunAndTestWasmApp(
buildArgs, expectedExitCode: 42,
args: argCulture,
host: host, id: id);
Assert.Contains(expectedOutput, output);
}
[Theory]
[MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ false, RunHost.All })]
[MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ false, /*relinking*/ true, RunHost.All })]
[MemberData(nameof(SatelliteAssemblyTestData), parameters: new object[] { /*aot*/ true, /*relinking*/ false, RunHost.All })]
public void ResourcesFromProjectReference(BuildArgs buildArgs,
bool nativeRelink,
string? argCulture,
string expectedOutput,
RunHost host,
string id)
{
string projectName = $"sat_asm_proj_ref";
bool dotnetWasmFromRuntimePack = !nativeRelink && !buildArgs.AOT;
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs,
projectTemplate: s_resourcesProjectTemplate,
extraProperties: $"<WasmBuildNative>{(nativeRelink ? "true" : "false")}</WasmBuildNative>",
extraItems: $"<ProjectReference Include=\"..\\LibraryWithResources\\LibraryWithResources.csproj\" />");
BuildProject(buildArgs,
initProject: () => CreateProgramForCultureTest("LibraryWithResources.words", "LibraryWithResources.Class1"),
dotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
id: id);
string output = RunAndTestWasmApp(buildArgs,
expectedExitCode: 42,
args: argCulture,
host: host, id: id);
Assert.Contains(expectedOutput, output);
}
#pragma warning disable xUnit1026
[Theory]
[BuildAndRun(host: RunHost.None, aot: true)]
public void CheckThatSatelliteAssembliesAreNotAOTed(BuildArgs buildArgs, string id)
{
string projectName = $"check_sat_asm_not_aot";
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs,
projectTemplate: s_resourcesProjectTemplate,
extraProperties: $@"
<EmccCompileOptimizationFlag>-O0</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O0</EmccLinkOptimizationFlag>",
extraItems: $"<EmbeddedResource Include=\"..\\resx\\*\" />");
System.Console.WriteLine ($"--- aot: {buildArgs.AOT}");
BuildProject(buildArgs,
initProject: () => CreateProgramForCultureTest($"{projectName}.words", "TestClass"),
dotnetWasmFromRuntimePack: false,
id: id);
var bitCodeFileNames = Directory.GetFileSystemEntries(Path.Combine(_projectDir!, "obj"), "*.dll.bc", SearchOption.AllDirectories)
.Select(path => Path.GetFileName(path))
.ToArray();
// sanity check, in case we change file extensions
Assert.Contains($"{projectName}.dll.bc", bitCodeFileNames);
Assert.Empty(bitCodeFileNames.Where(file => file.EndsWith(".resources.dll.bc")));
}
#pragma warning restore xUnit1026
private void CreateProgramForCultureTest(string resourceName, string typeName)
=> File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"),
s_cultureResourceTestProgram
.Replace("##RESOURCE_NAME##", resourceName)
.Replace("##TYPE_NAME##", typeName));
private const string s_resourcesProjectTemplate =
@$"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<TargetFramework>{s_targetFramework}</TargetFramework>
<OutputType>Exe</OutputType>
<WasmGenerateRunV8Script>true</WasmGenerateRunV8Script>
<WasmMainJSPath>runtime-test.js</WasmMainJSPath>
##EXTRA_PROPERTIES##
</PropertyGroup>
<ItemGroup>
##EXTRA_ITEMS##
</ItemGroup>
##INSERT_AT_END##
</Project>";
private static string s_cultureResourceTestProgram = @"
using System;
using System.Runtime.CompilerServices;
using System.Globalization;
using System.Resources;
using System.Threading;
namespace ResourcesTest
{
public class TestClass
{
public static int Main(string[] args)
{
if (args.Length == 1)
{
string cultureToTest = args[0];
var newCulture = new CultureInfo(cultureToTest);
Thread.CurrentThread.CurrentCulture = newCulture;
Thread.CurrentThread.CurrentUICulture = newCulture;
}
var currentCultureName = Thread.CurrentThread.CurrentCulture.Name;
var rm = new ResourceManager(""##RESOURCE_NAME##"", typeof(##TYPE_NAME##).Assembly);
Console.WriteLine($""For '{currentCultureName}' got: {rm.GetString(""hello"")}"");
return 42;
}
}
}";
}
}
......@@ -20,7 +20,7 @@ public void CacheBuild(BuildArgs buildArgs, BuildProduct product)
public void RemoveFromCache(string buildPath)
{
KeyValuePair<BuildArgs, BuildProduct>? foundKvp = _buildPaths.Where(kvp => kvp.Value.BuildPath == buildPath).SingleOrDefault();
KeyValuePair<BuildArgs, BuildProduct>? foundKvp = _buildPaths.Where(kvp => kvp.Value.ProjectDir == buildPath).SingleOrDefault();
if (foundKvp == null)
throw new Exception($"Could not find build path {buildPath} in cache to remove.");
......@@ -36,7 +36,7 @@ public void Dispose()
Console.WriteLine ($"============== DELETING THE BUILDS =============");
foreach (var kvp in _buildPaths.Values)
{
RemoveDirectory(kvp.BuildPath);
RemoveDirectory(kvp.ProjectDir);
}
}
......
......@@ -9,6 +9,7 @@
<TestFramework>xunit</TestFramework>
<WasmGenerateAppBundle>false</WasmGenerateAppBundle>
<EnableDefaultItems>true</EnableDefaultItems>
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
<DefineConstants Condition="'$(ContinuousIntegrationBuild)' != 'true'">TEST_DEBUG_CONFIG_ALSO</DefineConstants>
<!-- This project should not build against the live built .NETCoreApp targeting pack as it contributes to the build itself. -->
<UseLocalTargetingRuntimePack>false</UseLocalTargetingRuntimePack>
......@@ -20,8 +21,6 @@
<PropertyGroup Condition="'$(ContinuousIntegrationBuild)' == 'true'">
<_PreCommand Condition="'$(OS)' != 'Windows_NT'">WasmBuildSupportDir=%24{HELIX_CORRELATION_PAYLOAD}/build</_PreCommand>
<_PreCommand Condition="'$(OS)' == 'Windows_NT'">set WasmBuildSupportDir=%HELIX_CORRELATION_PAYLOAD%/build &amp;</_PreCommand>
<_PreCommand Condition="'$(OS)' != 'Windows_NT'">$(_PreCommand) DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1</_PreCommand>
<_PreCommand Condition="'$(OS)' == 'Windows_NT'">$(_PreCommand) set DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 &amp;</_PreCommand>
</PropertyGroup>
<PropertyGroup>
......@@ -38,5 +37,7 @@
<ItemGroup>
<None Include="$(RepoRoot)\src\mono\wasm\runtime-test.js" CopyToOutputDirectory="PreserveNewest" />
<None Include="..\testassets\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
......@@ -72,7 +72,7 @@ public void AOT_ErrorWhenMissingEMSDK(BuildArgs buildArgs, string emsdkPath, str
ProjectName = projectName,
ExtraBuildArgs = $"/p:EMSDK_PATH={emsdkPath}"
};
buildArgs = GetBuildArgsWith(buildArgs);
buildArgs = ExpandBuildArgs(buildArgs);
(_, string buildOutput) = BuildProject(buildArgs,
initProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), s_mainReturns42),
......@@ -88,6 +88,9 @@ public class TestClass {
public static int Main()
{
Console.WriteLine($""tc: {Environment.TickCount}, tc64: {Environment.TickCount64}"");
// if this gets printed, then we didn't crash!
Console.WriteLine(""Hello, World!"");
return 42;
}
}";
......@@ -109,11 +112,11 @@ public void Bug49588_RegressionTest_NativeRelinking(BuildArgs buildArgs, RunHost
BuildArgs buildArgs,
RunHost host,
string id,
string? extraProperties = null,
string extraProperties = "",
bool? dotnetWasmFromRuntimePack = null)
{
buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = GetBuildArgsWith(buildArgs, extraProperties);
buildArgs = ExpandBuildArgs(buildArgs, extraProperties);
if (dotnetWasmFromRuntimePack == null)
dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release");
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace LibraryWithResources
{
public class Class1
{
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="..\resx\*" />
</ItemGroup>
</Project>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="bye" xml:space="preserve">
<value>ciao</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="hello" xml:space="preserve">
<value>hola</value>
</data>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="bye" xml:space="preserve">
<value>さようなら</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="hello" xml:space="preserve">
<value>こんにちは</value>
</data>
</root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="bye" xml:space="preserve">
<value>bye</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="hello" xml:space="preserve">
<value>hello</value>
</data>
</root>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册