[Blazor] Custom ICU loading from runtime path (#90236)
authorIlona Tomkowicz <32700855+ilonatommy@users.noreply.github.com>
Mon, 14 Aug 2023 21:42:07 +0000 (23:42 +0200)
committerGitHub <noreply@github.com>
Mon, 14 Aug 2023 21:42:07 +0000 (17:42 -0400)
* Loading a user-created custom file from absolute path.
* Issue 2 solved: files from runtime pack work when abs path is used.
* Cover not found file scenario.

* Appied @maraf's suggestion.
Co-authored-by: Ankit Jain <radical@gmail.com>
eng/testing/scenarios/BuildWasmAppsJobsList.txt
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs [new file with mode: 0644]
src/mono/wasm/Wasm.Build.Tests/IcuShardingTests.cs
src/mono/wasm/Wasm.Build.Tests/IcuTests.cs
src/mono/wasm/Wasm.Build.Tests/IcuTestsBase.cs
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs
src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs

index 00f348126643859bdd2976f154fadafdca3c57cc..51bb6bcbb3f29369ebedd7fed3262f8d74fd8ef9 100644 (file)
@@ -15,6 +15,7 @@ Wasm.Build.Tests.Blazor.NativeTests
 Wasm.Build.Tests.Blazor.NoopNativeRebuildTest
 Wasm.Build.Tests.Blazor.WorkloadRequiredTests
 Wasm.Build.Tests.Blazor.IcuTests
+Wasm.Build.Tests.Blazor.IcuShardingTests
 Wasm.Build.Tests.BuildPublishTests
 Wasm.Build.Tests.ConfigSrcTests
 Wasm.Build.Tests.HybridGlobalizationTests
index 981c48a65a7f30c94375619ca8065219fb2bf352..64539a49fdaa8abfc6ea2116c50cd1f218e661fc 100644 (file)
@@ -169,15 +169,23 @@ Copyright (c) .NET Foundation. All rights reserved.
     <Warning Condition="'$(InvariantGlobalization)' == 'true' AND '$(HybridGlobalization)' == 'true'" Text="%24(HybridGlobalization) has no effect when %24(InvariantGlobalization) is set to true." />
     <Warning Condition="'$(BlazorIcuDataFileName)' != '' AND '$(HybridGlobalization)' == 'true'" Text="%24(HybridGlobalization) has no effect when %24(BlazorIcuDataFileName) is set." />
     <PropertyGroup>
-      <HybridGlobalization Condition="'$(BlazorIcuDataFileName)' != ''">false</HybridGlobalization>
+      <_RuntimePackDir>$(MicrosoftNetCoreAppRuntimePackDir)</_RuntimePackDir>
+      <_RuntimePackDir Condition="'$(_RuntimePackDir)' == ''">%(ResolvedRuntimePack.PackageDirectory)</_RuntimePackDir>
+      <_RuntimePackNativeDir>$([MSBuild]::NormalizeDirectory($(_RuntimePackDir), 'runtimes', 'browser-wasm', 'native'))</_RuntimePackNativeDir>
+      <_LoadCustomIcuData Condition="'$(InvariantGlobalization)' != 'true' AND '$(HybridGlobalization)' != 'true' AND '$(BlazorWebAssemblyLoadAllGlobalizationData)' != 'true' AND '$(BlazorIcuDataFileName)' != ''">true</_LoadCustomIcuData>
+      <_LoadCustomIcuData Condition="'$(_LoadCustomIcuData)' == ''">false</_LoadCustomIcuData>
+      <_BlazorIcuDataFileName Condition="'$(_LoadCustomIcuData)' == 'true' AND Exists('$(BlazorIcuDataFileName)')">$(BlazorIcuDataFileName)</_BlazorIcuDataFileName>
+      <_BlazorIcuDataFileName Condition="'$(_LoadCustomIcuData)' == 'true' AND !Exists('$(BlazorIcuDataFileName)') AND !$([System.IO.Path]::IsPathRooted($(BlazorIcuDataFileName)))">$(_RuntimePackNativeDir)$(BlazorIcuDataFileName)</_BlazorIcuDataFileName>
+      <_IsHybridGlobalization Condition="'$(InvariantGlobalization)' != 'true' AND '$(_LoadCustomIcuData)' != 'true'">$(HybridGlobalization)</_IsHybridGlobalization>
+      <_IsHybridGlobalization Condition="'$(_IsHybridGlobalization)' == ''">false</_IsHybridGlobalization>
       <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(InvariantGlobalization)' != 'true' AND '$(HybridGlobalization)' != 'true'">$(BlazorWebAssemblyLoadAllGlobalizationData)</_BlazorWebAssemblyLoadAllGlobalizationData>
       <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(_BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false</_BlazorWebAssemblyLoadAllGlobalizationData>
-      <_IsHybridGlobalization>$(HybridGlobalization)</_IsHybridGlobalization>
-      <_IsHybridGlobalization Condition="'$(InvariantGlobalization)' == 'true' OR '$(HybridGlobalization)' == ''">false</_IsHybridGlobalization>
-      <_BlazorIcuDataFileName Condition="'$(InvariantGlobalization)' != 'true' AND '$(BlazorWebAssemblyLoadAllGlobalizationData)' != 'true' AND '$(HybridGlobalization)' != 'true'">$(BlazorIcuDataFileName)</_BlazorIcuDataFileName>
-      <_LoadCustomIcuData>false</_LoadCustomIcuData>
-      <_LoadCustomIcuData Condition="'$(_BlazorIcuDataFileName)' != ''">true</_LoadCustomIcuData>
     </PropertyGroup>
+    <ItemGroup Condition="'$(_LoadCustomIcuData)' == 'true' AND Exists('$(BlazorIcuDataFileName)')">
+      <!-- if ICU comes from runtime pack then it is already included in ReferenceCopyLocalPaths -->
+      <ReferenceCopyLocalPaths Include="$(_BlazorIcuDataFileName)" />
+    </ItemGroup>
+    <Error Condition="'$(_LoadCustomIcuData)' == 'true' AND !Exists('$(_BlazorIcuDataFileName)')" Text="Could not find %24(BlazorIcuDataFileName)=$(BlazorIcuDataFileName), or when used as a path relative to the runtime pack (='$(_RuntimePackNativeDir)')."/>
   </Target>
 
   <Target Name="_ResolveWasmConfiguration" DependsOnTargets="_ResolveGlobalizationConfiguration">
diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/IcuShardingTests.cs
new file mode 100644 (file)
index 0000000..09132ef
--- /dev/null
@@ -0,0 +1,112 @@
+// 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;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+#nullable enable
+
+namespace Wasm.Build.Tests.Blazor;
+
+// these tests only check if correct ICU files got copied
+public class IcuShardingTests : BlazorWasmTestBase
+{
+    public IcuShardingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
+        : base(output, buildContext) {}
+
+    [Theory]
+    [InlineData("Debug", "icudt.dat")]
+    [InlineData("Release", "icudt.dat")]    
+    [InlineData("Debug", "icudt_CJK.dat")]
+    [InlineData("Release", "icudt_CJK.dat")]
+    public async Task CustomIcuFileFromRuntimePack(string config, string fileName)
+    {
+        string id = $"blz_customFromRuntimePack_{config}_{GetRandomId()}";
+        string projectFile = CreateBlazorWasmTemplateProject(id);
+        var buildOptions = new BlazorBuildOptions(
+                id,
+                config,
+                WarnAsError: true,
+                GlobalizationMode: GlobalizationMode.PredefinedIcu,
+                PredefinedIcudt: fileName
+            );
+        AddItemsPropertiesToProject(
+            projectFile,
+            extraProperties: 
+                $"<BlazorIcuDataFileName>{fileName}</BlazorIcuDataFileName>");
+
+        (CommandResult res, string logPath) = BlazorBuild(buildOptions);
+        await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config });
+    }
+
+    [Theory]
+    [InlineData("Debug", "incorrectName.dat", false)]
+    [InlineData("Release", "incorrectName.dat", false)]
+    [InlineData("Debug", "icudtNonExisting.dat", true)]
+    [InlineData("Release", "icudtNonExisting.dat", true)]
+    public void NonExistingCustomFileAssertError(string config, string fileName, bool isFilenameCorrect)
+    {
+        string id = $"blz_invalidCustomIcu_{config}_{GetRandomId()}";
+        string projectFile = CreateBlazorWasmTemplateProject(id);
+        AddItemsPropertiesToProject(
+            projectFile,
+            extraProperties: 
+                $"<BlazorIcuDataFileName>{fileName}</BlazorIcuDataFileName>");
+
+        try
+        {
+            (CommandResult res, string logPath) = BlazorBuild(
+                new BlazorBuildOptions(
+                    id,
+                    config,
+                    WarnAsError: false,
+                    GlobalizationMode: GlobalizationMode.PredefinedIcu,
+                    PredefinedIcudt: fileName
+                ));
+        }
+        catch (XunitException ex)
+        {
+            if (isFilenameCorrect)
+            {
+                Assert.Contains($"Could not find $(BlazorIcuDataFileName)={fileName}, or when used as a path relative to the runtime pack", ex.Message);
+            }
+            else
+            {
+                Assert.Contains("File name in $(BlazorIcuDataFileName) has to start with 'icudt'", ex.Message);
+            }
+        }
+        catch (Exception)
+        {
+            throw new Exception("Unexpected exception in test scenario.");
+        }
+        // we expect build error, so there is not point in running the app
+    }
+
+    [Theory]
+    [InlineData("Debug")]
+    [InlineData("Release")]
+    public async Task CustomFileNotFromRuntimePackAbsolutePath(string config)
+    {
+        string id = $"blz_invalidCustomIcu_{config}_{GetRandomId()}";
+        string projectFile = CreateBlazorWasmTemplateProject(id);
+        AddItemsPropertiesToProject(
+            projectFile,
+            extraProperties: 
+                $"<BlazorIcuDataFileName>{IcuTestsBase.CustomIcuPath}</BlazorIcuDataFileName>");
+
+        (CommandResult res, string logPath) = BlazorBuild(
+            new BlazorBuildOptions(
+                id,
+                config,
+                WarnAsError: false,
+                GlobalizationMode: GlobalizationMode.PredefinedIcu,
+                PredefinedIcudt: IcuTestsBase.CustomIcuPath
+            ));
+        await BlazorRunForBuildWithDotnetRun(new BlazorRunOptions() { Config = config });
+    }
+}
\ No newline at end of file
index a14e6440eaf1d5312ba30108b0b3ba0864aa35b1..dbaeba4bb1165bb5eb2fb4ab22b1d7bc7fb6f1fd 100644 (file)
@@ -20,8 +20,8 @@ public class IcuShardingTests : IcuTestsBase
     public static IEnumerable<object?[]> IcuExpectedAndMissingCustomShardTestData(bool aot, RunHost host)
         => ConfigWithAOTData(aot)
             .Multiply(
-                new object[] { s_customIcuPath, s_customIcuTestedLocales, false },
-                new object[] { s_customIcuPath, s_customIcuTestedLocales, true })
+                new object[] { CustomIcuPath, s_customIcuTestedLocales, false },
+                new object[] { CustomIcuPath, s_customIcuTestedLocales, true })
             .WithRunHosts(host)
             .UnwrapItemsAsArrays();
 
index c7a18a4dce742e15a64c938a45d38a3197ca29c7..0f58d24ac9f3d0ce46857dc4e8bbf14b77f6676a 100644 (file)
@@ -68,7 +68,7 @@ public class IcuTests : IcuTestsBase
         bool dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release");
 
         buildArgs = buildArgs with { ProjectName = projectName };
-        buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"<WasmIcuDataFileName>{s_customIcuPath}</WasmIcuDataFileName><WasmIncludeFullIcuData>{fullIcu}</WasmIncludeFullIcuData>");
+        buildArgs = ExpandBuildArgs(buildArgs, extraProperties: $"<WasmIcuDataFileName>{CustomIcuPath}</WasmIcuDataFileName><WasmIncludeFullIcuData>{fullIcu}</WasmIncludeFullIcuData>");
 
         string testedLocales = fullIcu ? s_fullIcuTestedLocales : s_customIcuTestedLocales;
         string programText = GetProgramText(testedLocales);
@@ -79,7 +79,7 @@ public class IcuTests : IcuTestsBase
                             InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
                             DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
                             GlobalizationMode: fullIcu ? GlobalizationMode.FullIcu : GlobalizationMode.PredefinedIcu,
-                            PredefinedIcudt: fullIcu ? "" : s_customIcuPath));
+                            PredefinedIcudt: fullIcu ? "" : CustomIcuPath));
         if (fullIcu)
             Assert.Contains("$(WasmIcuDataFileName) has no effect when $(WasmIncludeFullIcuData) is set to true.", output);
 
index 1e0b5f1bb701d409866c6897e97b6da87c85bedf..9d4911d5ca96402c685c0ebe2f17282fd2e82cd4 100644 (file)
@@ -28,7 +28,7 @@ public abstract class IcuTestsBase : TestMainJsTestBase
     }
 
     // custom file contains only locales "cy-GB", "is-IS", "bs-BA", "lb-LU" and fallback locale: "en-US":
-    protected static string s_customIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat");
+    public static string CustomIcuPath = Path.Combine(BuildEnvironment.TestAssetsPath, "icudt_custom.dat");
 
     protected static readonly string s_customIcuTestedLocales = $@"new Locale[] {{
         new Locale(""cy-GB"",  ""Dydd Sul""), new Locale(""is-IS"",  ""sunnudagur""), new Locale(""bs-BA"",  ""nedjelja""), new Locale(""lb-LU"",  ""Sonndeg""),
index 44f192d9a32d6a6ac86ec8f4dd7cc32bf3b6a4d5..53895087d73d1490f70d577570e49f8b0ae23ae0 100644 (file)
@@ -64,7 +64,7 @@ public class AssetsComputingHelper
             ".dat" when invariantGlobalization && fileName.StartsWith("icudt") => "invariant globalization is enabled",
             ".dat" when loadFullICUData && fileName != "icudt" => "full ICU data is enabled",
             ".dat" when hybridGlobalization && fileName != "icudt_hybrid" => "hybrid globalization is enabled",
-            ".dat" when !string.IsNullOrEmpty(customIcuCandidateFilename) && fileName != customIcuCandidateFilename => "custom icu file will be used instead of icu from the runtime pack",
+            ".dat" when !string.IsNullOrEmpty(customIcuCandidateFilename) && fileName != customIcuCandidateFilename => "custom icu file either from absolute path or from runtime pack path will be used",
             ".dat" when IsDefaultIcuMode() && !(icuShardsFromRuntimePack.Any(f => f == fileName)) => "automatic icu shard selection, based on application culture, is enabled",
             ".json" when fromMonoPackage && (fileName == "emcc-props" || fileName == "package") => $"{fileName}{extension} is not used by Blazor",
             ".ts" when fromMonoPackage && fileName == "dotnet.d" => "dotnet type definition is not used by Blazor",
index 36cccd457040964859c6c2c78a3630b24c9f4a14..f2e4336da3630b884fe13b5810aaf5a90e950150 100644 (file)
@@ -83,10 +83,10 @@ public class ComputeWasmBuildAssets : Task
                 return true;
             }
 
-            if (AssetsComputingHelper.TryGetAssetFilename(CustomIcuCandidate, out string customIcuCandidateFilename))
+            if (!AssetsComputingHelper.TryGetAssetFilename(CustomIcuCandidate, out string customIcuCandidateFilename))
             {
-                var customIcuCandidate = AssetsComputingHelper.GetCustomIcuAsset(CustomIcuCandidate);
-                assetCandidates.Add(customIcuCandidate);
+                // if it's not empty then it's already in Candidates and will get filtered by ShouldFilterCandidate if needed
+                Log.LogMessage(MessageImportance.Low, "Custom icu asset was passed as empty.");
             }
 
             for (int i = 0; i < Candidates.Length; i++)