From 3cab6dd440a5f3763bfbd2d582b36fe51095686a Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 22 Jul 2020 14:07:30 -0500 Subject: [PATCH] [release/5.0-preview8] Wasm ICU (#39739) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ICU integration and asset loading overhaul (#37971) This PR overhauls runtime startup/asset loading and adds support for ICU integration. The mono-config.js format is reworked and simplified, with new functionality added: Individual assets can be loaded from one or more remote sources with configurable fallback behavior In addition to the existing support for loading assemblies, you can now pre-load arbitrary files into the native heap or into emscripten's virtual file system. VFS support previously only existed in runtime-test.js but now is available to any consumer of dotnet.js. Assets can have a virtual path set so that their application-facing path does not necessarily have to match their path on the server. One or more ICU data archives can be added to the assets list and will be automatically loaded and used to enable ICU-based globalization support. Many configuration knobs that previously required API calls can now be set declaratively in the configuration file (environment variables, etc.) WasmAppBuilder is updated to add ICUDataFiles and RemoteSources parameters that can be used to add the associated information to the config file declaratively from a msbuild project. Various adjustments are made to existing tests and test cases so that they will pass with the addition of ICU integration. Co-authored-by: EgorBo Co-authored-by: Alexander Köplinger Co-authored-by: Larry Ewing * [wasm] Include data archives and timezone data by default (#39586) * Add data archive loading to the generic loading logic * [mono] Update ICU version, disable some tests for Browser (#39596) Co-authored-by: Katelyn Gadd Co-authored-by: EgorBo Co-authored-by: Alexander Köplinger --- eng/Version.Details.xml | 4 +- eng/Versions.props | 2 +- eng/liveBuilds.targets | 3 +- eng/testing/tests.mobile.targets | 14 +- .../Common/src/Interop/Interop.Calendar.cs | 11 +- .../Interop.TimeZoneDisplayNameType.cs | 16 + .../src/Interop/Interop.TimeZoneInfo.cs | 8 - .../pal_icushim_internal.h | 5 + .../pal_icushim_static.c | 83 +++ src/libraries/Native/native-binplace.proj | 1 + .../TaiwanCalendarDaysAndMonths.cs | 1 + .../CultureInfo/CultureInfoEnglishName.cs | 14 +- .../CultureInfo/CultureInfoNativeName.cs | 14 +- .../DateTimeFormatInfoCalendarWeekRule.cs | 11 +- .../DateTimeFormatInfoTests.cs | 7 +- .../NumberFormatInfoCurrencyGroupSizes.cs | 2 +- ...NumberFormatInfoCurrencyNegativePattern.cs | 24 +- .../NumberFormatInfoNumberGroupSizes.cs | 4 +- .../System/Globalization/RegionInfoTests.cs | 45 +- .../System/Globalization/TextInfoTests.cs | 17 +- .../System.Private.CoreLib.Shared.projitems | 7 +- .../System/Globalization/CalendarData.Icu.cs | 20 +- .../TimeZoneInfo.GetDisplayName.Invariant.cs | 13 + .../src/System/TimeZoneInfo.GetDisplayName.cs | 67 +++ .../src/System/TimeZoneInfo.Unix.cs | 47 -- .../tests/System/AppDomainTests.cs | 1 + .../Reflection/AssemblyNameProxyTests.cs | 1 + .../tests/AssemblyLoadContextTest.cs | 1 + .../tests/SatelliteAssemblies.cs | 1 + src/libraries/tests.proj | 7 +- src/mono/cmake/config.h.in | 3 + src/mono/configure.ac | 12 +- src/mono/mono.proj | 1 + src/mono/mono/metadata/Makefile.am | 14 +- .../System.Private.CoreLib.csproj | 1 + .../src/Mono/MonoPInvokeCallbackAttribute.cs | 10 + src/mono/wasm/.editorconfig | 13 + src/mono/wasm/Makefile | 8 +- src/mono/wasm/runtime-test.js | 120 ++-- src/mono/wasm/runtime/driver.c | 5 +- src/mono/wasm/runtime/library_mono.js | 538 ++++++++++++++---- .../WasmAppBuilder/PInvokeTableGenerator.cs | 7 +- .../WasmAppBuilder/WasmAppBuilder.cs | 103 ++-- 43 files changed, 931 insertions(+), 355 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Interop.TimeZoneDisplayNameType.cs create mode 100644 src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_static.c create mode 100644 src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.GetDisplayName.Invariant.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.GetDisplayName.cs create mode 100644 src/mono/netcore/System.Private.CoreLib/src/Mono/MonoPInvokeCallbackAttribute.cs create mode 100755 src/mono/wasm/.editorconfig diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8df873a5765eab..0d7aad96f5897b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -4,9 +4,9 @@ https://github.com/dotnet/standard cfe95a23647c7de1fe1a349343115bd7720d6949 - + https://github.com/dotnet/icu - bf5a3a643815a8a46693d618d08dbc96f353ca9e + 797c523dd8d75096319f3591958f703b8d74d04b diff --git a/eng/Versions.props b/eng/Versions.props index ae139287cf0b36..d578146bafbeaa 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -118,7 +118,7 @@ 5.0.0-preview.3.20363.5 - 5.0.0-preview.8.20364.1 + 5.0.0-preview.8.20370.1 9.0.1-alpha.1.20356.1 9.0.1-alpha.1.20356.1 diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 763b369167db5c..e7cd12b6a4eaa8 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -191,7 +191,8 @@ Include=" $(LibrariesNativeArtifactsPath)dotnet.js; $(LibrariesNativeArtifactsPath)dotnet.wasm; - $(LibrariesNativeArtifactsPath)dotnet.timezones.blat;" + $(LibrariesNativeArtifactsPath)dotnet.timezones.blat; + $(LibrariesNativeArtifactsPath)icudt.dat;" IsNative="true" /> diff --git a/eng/testing/tests.mobile.targets b/eng/testing/tests.mobile.targets index ebefed0e7e9600..284ae301d88880 100644 --- a/eng/testing/tests.mobile.targets +++ b/eng/testing/tests.mobile.targets @@ -14,7 +14,7 @@ - $HARNESS_RUNNER wasm test --engine=$(JSEngine) $(JSEngineArgs) --js-file=runtime.js -v --output-directory=$XHARNESS_OUT -- --enable-zoneinfo --run WasmTestRunner.dll $(AssemblyName).dll + $HARNESS_RUNNER wasm test --engine=$(JSEngine) $(JSEngineArgs) --js-file=runtime.js -v --output-directory=$XHARNESS_OUT -- --run WasmTestRunner.dll $(AssemblyName).dll @@ -121,10 +121,17 @@ AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" /> + + + + $([System.IO.Directory]::GetParent('%(Identity)').Name) + + - + + @@ -140,7 +147,6 @@ - + AssemblySearchPaths="@(AssemblySearchPaths)"/> #include #include #include #include #include +#include #include #include #include @@ -55,6 +57,7 @@ #include "pal_compiler.h" +#if !defined(STATIC_ICU) // List of all functions from the ICU libraries that are used in the System.Globalization.Native.so #define FOR_ALL_UNCONDITIONAL_ICU_FUNCTIONS \ PER_FUNCTION_BLOCK(u_charsToUChars, libicuuc) \ @@ -279,3 +282,5 @@ FOR_ALL_ICU_FUNCTIONS #define usearch_getMatchedLength(...) usearch_getMatchedLength_ptr(__VA_ARGS__) #define usearch_last(...) usearch_last_ptr(__VA_ARGS__) #define usearch_openFromCollator(...) usearch_openFromCollator_ptr(__VA_ARGS__) + +#endif // !defined(STATIC_ICU) diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_static.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_static.c new file mode 100644 index 00000000000000..41a1956a2743c8 --- /dev/null +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_static.c @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#include +#include +#include "pal_icushim_internal.h" +#include "pal_icushim.h" +#include +#include +#include +#include + +static void log_icu_error(const char* name, UErrorCode status) +{ + const char * statusText = u_errorName(status); + fprintf(stderr, "ICU call %s failed with error #%d '%s'.\n", name, status, statusText); +} + +static void U_CALLCONV icu_trace_data(const void* context, int32_t fnNumber, int32_t level, const char* fmt, va_list args) +{ + char buf[1000]; + utrace_vformat(buf, sizeof(buf), 0, fmt, args); + printf("[ICUDT] %s: %s\n", utrace_functionName(fnNumber), buf); +} + +#ifdef __EMSCRIPTEN__ +#include + +EMSCRIPTEN_KEEPALIVE int32_t mono_wasm_load_icu_data(void * pData); + +EMSCRIPTEN_KEEPALIVE int32_t mono_wasm_load_icu_data(void * pData) +{ + UErrorCode status = 0; + udata_setCommonData(pData, &status); + + if (U_FAILURE(status)) { + log_icu_error("udata_setCommonData", status); + return 0; + } else { + //// Uncomment to enable ICU tracing, + //// see https://github.com/unicode-org/icu/blob/master/docs/userguide/icu_data/tracing.md + // utrace_setFunctions(0, 0, 0, icu_trace_data); + // utrace_setLevel(UTRACE_VERBOSE); + return 1; + } +} +#endif + +int32_t GlobalizationNative_LoadICU(void) +{ + const char* icudir = getenv("DOTNET_ICU_DIR"); + if (icudir) + u_setDataDirectory(icudir); + else + ; // default ICU search path behavior will be used, see http://userguide.icu-project.org/icudata + + UErrorCode status = 0; + UVersionInfo version; + // Request the CLDR version to perform basic ICU initialization and find out + // whether it worked. + ulocdata_getCLDRVersion(version, &status); + + if (U_FAILURE(status)) { + log_icu_error("ulocdata_getCLDRVersion", status); + return 0; + } + + return 1; +} + +void GlobalizationNative_InitICUFunctions(void* icuuc, void* icuin, const char* version, const char* suffix) +{ + // no-op for static +} + +int32_t GlobalizationNative_GetICUVersion(void) +{ + UVersionInfo versionInfo; + u_getVersion(versionInfo); + + return (versionInfo[0] << 24) + (versionInfo[1] << 16) + (versionInfo[2] << 8) + versionInfo[3]; +} diff --git a/src/libraries/Native/native-binplace.proj b/src/libraries/Native/native-binplace.proj index 264a881802f315..f8a558defe35b0 100644 --- a/src/libraries/Native/native-binplace.proj +++ b/src/libraries/Native/native-binplace.proj @@ -25,6 +25,7 @@ + diff --git a/src/libraries/System.Globalization.Calendars/tests/TaiwanCalendar/TaiwanCalendarDaysAndMonths.cs b/src/libraries/System.Globalization.Calendars/tests/TaiwanCalendar/TaiwanCalendarDaysAndMonths.cs index 0f96a592bf508e..cb901d27f56a9f 100644 --- a/src/libraries/System.Globalization.Calendars/tests/TaiwanCalendar/TaiwanCalendarDaysAndMonths.cs +++ b/src/libraries/System.Globalization.Calendars/tests/TaiwanCalendar/TaiwanCalendarDaysAndMonths.cs @@ -9,6 +9,7 @@ namespace System.Globalization.Tests public class TaiwanCalendarDaysAndMonths { [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/39285", TestPlatforms.Browser)] public void DayNames_MonthNames() { string[] expectedDayNames = diff --git a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoEnglishName.cs b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoEnglishName.cs index 23c2032c51cd41..fdb6e4b189fcee 100644 --- a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoEnglishName.cs +++ b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoEnglishName.cs @@ -11,8 +11,18 @@ public class CultureInfoEnglishName public static IEnumerable EnglishName_TestData() { yield return new object[] { CultureInfo.CurrentCulture.Name, CultureInfo.CurrentCulture.EnglishName }; - yield return new object[] { "en-US", "English (United States)" }; - yield return new object[] { "fr-FR", "French (France)" }; + + if (PlatformDetection.IsNotBrowser) + { + yield return new object[] { "en-US", "English (United States)" }; + yield return new object[] { "fr-FR", "French (France)" }; + } + else + { + // Browser's ICU doesn't contain CultureInfo.EnglishName + yield return new object[] { "en-US", "en (US)" }; + yield return new object[] { "fr-FR", "fr (FR)" }; + } } [Theory] diff --git a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoNativeName.cs b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoNativeName.cs index b97a645b8b2a57..58429b132138cc 100644 --- a/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoNativeName.cs +++ b/src/libraries/System.Globalization/tests/CultureInfo/CultureInfoNativeName.cs @@ -11,8 +11,18 @@ public class CultureInfoNativeName public static IEnumerable NativeName_TestData() { yield return new object[] { CultureInfo.CurrentCulture.Name, CultureInfo.CurrentCulture.NativeName }; - yield return new object[] { "en-US", "English (United States)" }; - yield return new object[] { "en-CA", "English (Canada)" }; + + if (PlatformDetection.IsNotBrowser) + { + yield return new object[] { "en-US", "English (United States)" }; + yield return new object[] { "en-CA", "English (Canada)" }; + } + else + { + // Browser's ICU doesn't contain CultureInfo.NativeName + yield return new object[] { "en-US", "en (US)" }; + yield return new object[] { "en-CA", "en (CA)" }; + } } [Theory] diff --git a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoCalendarWeekRule.cs b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoCalendarWeekRule.cs index a0dc8afc4adc82..2d0bd120bd307d 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoCalendarWeekRule.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoCalendarWeekRule.cs @@ -12,7 +12,16 @@ public static IEnumerable CalendarWeekRule_Get_TestData() { yield return new object[] { DateTimeFormatInfo.InvariantInfo, CalendarWeekRule.FirstDay }; yield return new object[] { new CultureInfo("en-US").DateTimeFormat, CalendarWeekRule.FirstDay }; - yield return new object[] { new CultureInfo("br-FR").DateTimeFormat, DateTimeFormatInfoData.BrFRCalendarWeekRule() }; + + if (PlatformDetection.IsNotBrowser) + { + yield return new object[] { new CultureInfo("br-FR").DateTimeFormat, DateTimeFormatInfoData.BrFRCalendarWeekRule() }; + } + else + { + // "br-FR" is not presented in Browser's ICU. Let's test ru-RU instead. + yield return new object[] { new CultureInfo("ru-RU").DateTimeFormat, CalendarWeekRule.FirstFourDayWeek }; + } } [Theory] diff --git a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs index 1689c9e312d2db..52cda26a787aef 100644 --- a/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs +++ b/src/libraries/System.Globalization/tests/DateTimeFormatInfo/DateTimeFormatInfoTests.cs @@ -66,7 +66,12 @@ public void NativeCalendarName_Get_ReturnsExpected(DateTimeFormatInfo dtfi, Cale try { dtfi.Calendar = calendar; - Assert.Equal(nativeCalendarName, dtfi.NativeCalendarName); + + if (PlatformDetection.IsNotBrowser) + { + // Browser's ICU doesn't contain NativeCalendarName, + Assert.Equal(nativeCalendarName, dtfi.NativeCalendarName); + } } catch { diff --git a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyGroupSizes.cs b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyGroupSizes.cs index aa283d47e7c083..261edc3fb0d26e 100644 --- a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyGroupSizes.cs +++ b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyGroupSizes.cs @@ -13,7 +13,7 @@ public static IEnumerable CurrencyGroupSizes_TestData() yield return new object[] { NumberFormatInfo.InvariantInfo, new int[] { 3 } }; yield return new object[] { CultureInfo.GetCultureInfo("en-US").NumberFormat, new int[] { 3 } }; - if (!PlatformDetection.IsUbuntu && !PlatformDetection.IsWindows7 && !PlatformDetection.IsWindows8x && !PlatformDetection.IsFedora) + if (PlatformDetection.IsNotBrowser && !PlatformDetection.IsUbuntu && !PlatformDetection.IsWindows7 && !PlatformDetection.IsWindows8x && !PlatformDetection.IsFedora) { yield return new object[] { CultureInfo.GetCultureInfo("ur-IN").NumberFormat, new int[] { 3, 2 } }; } diff --git a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyNegativePattern.cs b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyNegativePattern.cs index e33d39ddea3ea6..76653b6d68af4c 100644 --- a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyNegativePattern.cs +++ b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoCurrencyNegativePattern.cs @@ -22,14 +22,24 @@ public void CurrencyNegativePattern_Get_ReturnsExpected(NumberFormatInfo format, Assert.Contains(format.CurrencyNegativePattern, acceptablePatterns); } + public static IEnumerable CurrencyNegativePatternTestLocales() + { + yield return new object[] { "en-US" }; + yield return new object[] { "en-CA" }; + yield return new object[] { "fa-IR" }; + yield return new object[] { "fr-CD" }; + yield return new object[] { "fr-CA" }; + + if (PlatformDetection.IsNotBrowser) + { + // Browser's ICU doesn't contain these locales + yield return new object[] { "as" }; + yield return new object[] { "es-BO" }; + } + } + [Theory] - [InlineData("en-US")] - [InlineData("en-CA")] - [InlineData("fa-IR")] - [InlineData("fr-CD")] - [InlineData("as")] - [InlineData("es-BO")] - [InlineData("fr-CA")] + [MemberData(nameof(CurrencyNegativePatternTestLocales))] public void CurrencyNegativePattern_Get_ReturnsExpected_ByLocale(string locale) { CultureInfo culture; diff --git a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoNumberGroupSizes.cs b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoNumberGroupSizes.cs index 045d3e0b585be7..62ae02d3aa4a4c 100644 --- a/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoNumberGroupSizes.cs +++ b/src/libraries/System.Globalization/tests/NumberFormatInfo/NumberFormatInfoNumberGroupSizes.cs @@ -13,8 +13,8 @@ public static IEnumerable NumberGroupSizes_TestData() yield return new object[] { NumberFormatInfo.InvariantInfo, new int[] { 3 } }; yield return new object[] { CultureInfo.GetCultureInfo("en-US").NumberFormat, new int[] { 3 } }; - // Culture does not exist on Windows 7 - if (!PlatformDetection.IsWindows7) + // Culture does not exist on Windows 7 and in Browser's ICU + if (!PlatformDetection.IsWindows7 && PlatformDetection.IsNotBrowser) { yield return new object[] { CultureInfo.GetCultureInfo("ur-IN").NumberFormat, NumberFormatInfoData.UrINNumberGroupSizes() }; } diff --git a/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs b/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs index dd99a40e94ca83..2cb86dc56da24c 100644 --- a/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs +++ b/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs @@ -90,20 +90,51 @@ public void DisplayName(string name, string expected) } } + public static IEnumerable NativeName_TestData() + { + if (PlatformDetection.IsNotBrowser) + { + yield return new object[] { "GB", "United Kingdom" }; + yield return new object[] { "SE", "Sverige" }; + yield return new object[] { "FR", "France" }; + } + else + { + // Browser's ICU doesn't contain RegionInfo.NativeName + yield return new object[] { "GB", "GB" }; + yield return new object[] { "SE", "SE" }; + yield return new object[] { "FR", "FR" }; + } + } + [Theory] - [InlineData("GB", "United Kingdom")] - [InlineData("SE", "Sverige")] - [InlineData("FR", "France")] + [MemberData(nameof(NativeName_TestData))] public void NativeName(string name, string expected) { Assert.Equal(expected, new RegionInfo(name).NativeName); } + public static IEnumerable EnglishName_TestData() + { + if (PlatformDetection.IsNotBrowser) + { + yield return new object[] { "en-US", new string[] { "United States" } }; + yield return new object[] { "US", new string[] { "United States" } }; + yield return new object[] { "zh-CN", new string[] { "China", "People's Republic of China", "China mainland" }}; + yield return new object[] { "CN", new string[] { "China", "People's Republic of China", "China mainland" } }; + } + else + { + // Browser's ICU doesn't contain RegionInfo.EnglishName + yield return new object[] { "en-US", new string[] { "US" } }; + yield return new object[] { "US", new string[] { "US" } }; + yield return new object[] { "zh-CN", new string[] { "CN" }}; + yield return new object[] { "CN", new string[] { "CN" } }; + } + } + [Theory] - [InlineData("en-US", new string[] { "United States" })] - [InlineData("US", new string[] { "United States" })] - [InlineData("zh-CN", new string[] { "China", "People's Republic of China", "China mainland" })] - [InlineData("CN", new string[] { "China", "People's Republic of China", "China mainland" })] + [MemberData(nameof(EnglishName_TestData))] public void EnglishName(string name, string[] expected) { string result = new RegionInfo(name).EnglishName; diff --git a/src/libraries/System.Globalization/tests/System/Globalization/TextInfoTests.cs b/src/libraries/System.Globalization/tests/System/Globalization/TextInfoTests.cs index 6b1a2219ca738f..099f9c24fb4226 100644 --- a/src/libraries/System.Globalization/tests/System/Globalization/TextInfoTests.cs +++ b/src/libraries/System.Globalization/tests/System/Globalization/TextInfoTests.cs @@ -183,6 +183,19 @@ public static IEnumerable ToLower_TestData_netcore() } } + public static IEnumerable GetTestLocales() + { + yield return "tr"; + yield return "tr-TR"; + + if (PlatformDetection.IsNotBrowser) + { + // Browser's ICU doesn't contain these locales + yield return "az"; + yield return "az-Latn-AZ"; + } + } + public static IEnumerable ToLower_TestData() { foreach (string cultureName in s_cultureNames) @@ -226,7 +239,7 @@ public static IEnumerable ToLower_TestData() yield return new object[] { cultureName, "\u03A3", "\u03C3" }; } - foreach (string cultureName in new string[] { "tr", "tr-TR", "az", "az-Latn-AZ" }) + foreach (string cultureName in GetTestLocales()) { yield return new object[] { cultureName, "\u0130", "i" }; yield return new object[] { cultureName, "i", "i" }; @@ -349,7 +362,7 @@ public static IEnumerable ToUpper_TestData() } // Turkish i - foreach (string cultureName in new string[] { "tr", "tr-TR", "az", "az-Latn-AZ" }) + foreach (string cultureName in GetTestLocales()) { yield return new object[] { cultureName, "i", "\u0130" }; yield return new object[] { cultureName, "\u0130", "\u0130" }; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 3037ae44d799d7..63314c0e85b914 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1068,7 +1068,10 @@ Common\Interop\Interop.ResultCode.cs - + + Common\Interop\Interop.TimeZoneDisplayNameType.cs + + Common\Interop\Interop.TimeZoneInfo.cs @@ -1821,9 +1824,11 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs index aa3f12a4c29c6d..e5425f8d0a31b3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs @@ -422,22 +422,36 @@ internal static bool EnumCalendarInfo(string localeName, CalendarId calendarId, return result; } +#if !TARGET_BROWSER private static unsafe bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, ref IcuEnumCalendarsData callbackContext) { return Interop.Globalization.EnumCalendarInfo(EnumCalendarInfoCallback, localeName, calendarId, dataType, (IntPtr)Unsafe.AsPointer(ref callbackContext)); } +#else + private static unsafe bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, ref IcuEnumCalendarsData callbackContext) + { + // Temp workaround for pinvoke callbacks for Mono-Wasm-Interpreter + // https://github.com/dotnet/runtime/issues/39100 + var calendarInfoCallback = new Interop.Globalization.EnumCalendarInfoCallback(EnumCalendarInfoCallback); + return Interop.Globalization.EnumCalendarInfo( + System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(calendarInfoCallback), + localeName, calendarId, dataType, (IntPtr)Unsafe.AsPointer(ref callbackContext)); + } - private static unsafe void EnumCalendarInfoCallback(string calendarString, IntPtr context) + [Mono.MonoPInvokeCallback(typeof(Interop.Globalization.EnumCalendarInfoCallback))] +#endif + private static unsafe void EnumCalendarInfoCallback(char* calendarStringPtr, IntPtr context) { try { + var calendarStringSpan = new ReadOnlySpan(calendarStringPtr, string.wcslen(calendarStringPtr)); ref IcuEnumCalendarsData callbackContext = ref Unsafe.As(ref *(byte*)context); if (callbackContext.DisallowDuplicates) { foreach (string existingResult in callbackContext.Results) { - if (string.Equals(calendarString, existingResult, StringComparison.Ordinal)) + if (string.CompareOrdinal(calendarStringSpan, existingResult) == 0) { // the value is already in the results, so don't add it again return; @@ -445,7 +459,7 @@ private static unsafe void EnumCalendarInfoCallback(string calendarString, IntPt } } - callbackContext.Results.Add(calendarString); + callbackContext.Results.Add(calendarStringSpan.ToString()); } catch (Exception e) { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.GetDisplayName.Invariant.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.GetDisplayName.Invariant.cs new file mode 100644 index 00000000000000..14b302d86b8015 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.GetDisplayName.Invariant.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System +{ + public sealed partial class TimeZoneInfo + { + private unsafe void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, string uiCulture, ref string? displayName) + { + displayName = _standardDisplayName; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.GetDisplayName.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.GetDisplayName.cs new file mode 100644 index 00000000000000..b557c15384bb46 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.GetDisplayName.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using System.Security; + +using Internal.IO; + +namespace System +{ + public sealed partial class TimeZoneInfo + { + private unsafe void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, string uiCulture, ref string? displayName) + { + if (GlobalizationMode.Invariant) + { + displayName = _standardDisplayName; + return; + } + + string? timeZoneDisplayName; + bool result = Interop.CallStringMethod( + (buffer, locale, id, type) => + { + fixed (char* bufferPtr = buffer) + { + return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length); + } + }, + uiCulture, + _id, + nameType, + out timeZoneDisplayName); + + if (!result && uiCulture != FallbackCultureName) + { + // Try to fallback using FallbackCultureName just in case we can make it work. + result = Interop.CallStringMethod( + (buffer, locale, id, type) => + { + fixed (char* bufferPtr = buffer) + { + return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length); + } + }, + FallbackCultureName, + _id, + nameType, + out timeZoneDisplayName); + } + + // If there is an unknown error, don't set the displayName field. + // It will be set to the abbreviation that was read out of the tzfile. + if (result) + { + displayName = timeZoneDisplayName; + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index 7dbcf577a657de..0be8b50a064f6d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -107,53 +107,6 @@ private TimeZoneInfo(byte[] data, string id, bool dstDisabled) ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime); } - private unsafe void GetDisplayName(Interop.Globalization.TimeZoneDisplayNameType nameType, string uiCulture, ref string? displayName) - { - if (GlobalizationMode.Invariant) - { - displayName = _standardDisplayName; - return; - } - - string? timeZoneDisplayName; - bool result = Interop.CallStringMethod( - (buffer, locale, id, type) => - { - fixed (char* bufferPtr = buffer) - { - return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length); - } - }, - uiCulture, - _id, - nameType, - out timeZoneDisplayName); - - if (!result && uiCulture != FallbackCultureName) - { - // Try to fallback using FallbackCultureName just in case we can make it work. - result = Interop.CallStringMethod( - (buffer, locale, id, type) => - { - fixed (char* bufferPtr = buffer) - { - return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length); - } - }, - FallbackCultureName, - _id, - nameType, - out timeZoneDisplayName); - } - - // If there is an unknown error, don't set the displayName field. - // It will be set to the abbreviation that was read out of the tzfile. - if (result) - { - displayName = timeZoneDisplayName; - } - } - /// /// Returns a cloned array of AdjustmentRule objects /// diff --git a/src/libraries/System.Runtime.Extensions/tests/System/AppDomainTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/AppDomainTests.cs index 3fa95271ee2c50..89ec53ebd419bd 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/AppDomainTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/AppDomainTests.cs @@ -358,6 +358,7 @@ public void Load() } [Fact] + [PlatformSpecific(~TestPlatforms.Browser)] public void LoadBytes() { Assembly assembly = typeof(AppDomainTests).Assembly; diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Reflection/AssemblyNameProxyTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/Reflection/AssemblyNameProxyTests.cs index 14dc0a0f1d0bce..39059fa7eba11f 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Reflection/AssemblyNameProxyTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Reflection/AssemblyNameProxyTests.cs @@ -13,6 +13,7 @@ namespace System.Reflection.Tests public static class AssemblyNameProxyTests { [Fact] + [PlatformSpecific(~TestPlatforms.Browser)] public static void GetAssemblyName_AssemblyNameProxy() { AssemblyNameProxy anp = new AssemblyNameProxy(); diff --git a/src/libraries/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs b/src/libraries/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs index 3979d76aeadb74..290cb3ca06b3da 100644 --- a/src/libraries/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs @@ -18,6 +18,7 @@ public partial class AssemblyLoadContextTest private const string TestAssemblyNotSupported = "System.Runtime.Loader.Test.AssemblyNotSupported"; [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/39379", TestPlatforms.Browser)] public static void GetAssemblyNameTest_ValidAssembly() { var expectedName = typeof(AssemblyLoadContextTest).Assembly.GetName(); diff --git a/src/libraries/System.Runtime.Loader/tests/SatelliteAssemblies.cs b/src/libraries/System.Runtime.Loader/tests/SatelliteAssemblies.cs index b82c314a815f8a..658d1f42a12870 100644 --- a/src/libraries/System.Runtime.Loader/tests/SatelliteAssemblies.cs +++ b/src/libraries/System.Runtime.Loader/tests/SatelliteAssemblies.cs @@ -189,6 +189,7 @@ public static IEnumerable SatelliteLoadsCorrectly_TestData() [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] [MemberData(nameof(SatelliteLoadsCorrectly_TestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/39379", TestPlatforms.Browser)] public void SatelliteLoadsCorrectly(string alc, string assemblyName, string culture) { AssemblyName satelliteAssemblyName = new AssemblyName(assemblyName + ".resources"); diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 6992c70d9f7ade..b420ac2995d863 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -27,10 +27,11 @@ + + + - - - + diff --git a/src/mono/cmake/config.h.in b/src/mono/cmake/config.h.in index e89b59c5f71009..8f013c5f0c6774 100644 --- a/src/mono/cmake/config.h.in +++ b/src/mono/cmake/config.h.in @@ -210,6 +210,9 @@ /* Use static zlib */ #cmakedefine HAVE_STATIC_ZLIB 1 +/* Use static ICU */ +#cmakedefine STATIC_ICU 1 + /* Use OS-provided zlib */ #cmakedefine HAVE_SYS_ZLIB 1 diff --git a/src/mono/configure.ac b/src/mono/configure.ac index 9e3b75b2ba26cf..d324c29baf91f2 100644 --- a/src/mono/configure.ac +++ b/src/mono/configure.ac @@ -1312,6 +1312,12 @@ AC_ARG_WITH(spectre-mitigation, [ --with-spectre-mitigation=yes,no AC_ARG_WITH(spectre-indirect-branch-choice, [ --with-spectre-indirect-branch-choice=keep,thunk,inline,extern Convert indirect branches to the specified kind of thunk (defaults to inline)], [], [with_spectre_indirect_branch_choice=inline]) AC_ARG_WITH(spectre-function-return-choice, [ --with-spectre-function-return-choice=keep,thunk,inline,extern Convert function return instructions to the specified kind of thunk (defaults to inline)], [], [with_spectre_function_return_choice=inline]) +AC_ARG_WITH(static_icu, [ --with-static-icu=yes|no Integrate ICU statically into the runtime (defaults to no)],[ + if test x$with_static_icu = xyes ; then + AC_DEFINE(STATIC_ICU,1,[Integrate ICU statically into the runtime.]) + fi +], [with_static_icu=no]) + dnl dnl Spectre compiler mitigation flag checks dnl @@ -1700,6 +1706,7 @@ AM_CONDITIONAL(INSTALL_MONOTOUCH, [test "x$with_monotouch" != "xno"]) AM_CONDITIONAL(INSTALL_MONOTOUCH_WATCH, [test "x$with_monotouch_watch" != "xno"]) AM_CONDITIONAL(INSTALL_MONOTOUCH_TV, [test "x$with_monotouch_tv" != "xno"]) AM_CONDITIONAL(BITCODE, test "x$with_bitcode" = "xyes") +AM_CONDITIONAL(STATIC_ICU, test "x$with_static_icu" = "xyes") AM_CONDITIONAL(INSTALL_XAMMAC, [test "x$with_xammac" != "xno"]) AM_CONDITIONAL(INSTALL_TESTING_AOT_FULL_INTERP, [test "x$with_testing_aot_full_interp" != "xno"]) AM_CONDITIONAL(INSTALL_TESTING_AOT_HYBRID, [test "x$with_testing_aot_hybrid" != "xno"]) @@ -6842,7 +6849,10 @@ if test x$with_core = xonly; then fi if test x$have_shim_globalization = xyes || test x$cross_compiling = xyes; then ICU_SHIM_PATH=../../../libraries/Native/Unix/System.Globalization.Native - if test x$target_osx = xyes; then + if test x$target_wasm = xyes && test x$with_static_icu = xyes; then + ICU_CFLAGS="-DTARGET_UNIX -DU_DISABLE_RENAMING" + have_sys_icu=yes + elif test x$target_osx = xyes; then ORIG_CPPFLAGS=$CPPFLAGS # adding icu path to pkg_config_path PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/local/opt/icu4c/lib/pkgconfig diff --git a/src/mono/mono.proj b/src/mono/mono.proj index d3290b58a7f9f6..502e2176adf21a 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -643,6 +643,7 @@ <_MonoConfigureParams Include="--disable-icall-tables"/> <_MonoConfigureParams Include="--disable-crash-reporting"/> <_MonoConfigureParams Include="--with-bitcode=yes"/> + <_MonoConfigureParams Include="--with-static-icu=yes"/> <_MonoConfigureParams Include="--enable-minimal=ssa,com,jit,reflection_emit_save,portability,assembly_remapping,attach,verifier,full_messages,appdomains,shadowcopy,security,sgen_marksweep_conc,sgen_split_nursery,sgen_gc_bridge,logging,remoting,shared_perfcounters,sgen_debug_helpers,soft_debug,interpreter,assert_messages,cleanup,mdb,gac,threads,$(_MonoEnableMinimal)"/> <_MonoCFLAGS Include="-fexceptions" /> <_MonoCFLAGS Include="-I$(PkgMicrosoft_NETCore_Runtime_ICU_Transport)/runtimes/browser-wasm/native/include" /> diff --git a/src/mono/mono/metadata/Makefile.am b/src/mono/mono/metadata/Makefile.am index 8e6f520077dfe8..06abdd51ab2b41 100644 --- a/src/mono/mono/metadata/Makefile.am +++ b/src/mono/mono/metadata/Makefile.am @@ -154,12 +154,18 @@ nodist_libmonoruntime_shimglobalization_la_SOURCES = \ @ICU_SHIM_PATH@/pal_localeStringData.c \ @ICU_SHIM_PATH@/pal_normalization.c \ @ICU_SHIM_PATH@/pal_timeZoneInfo.c \ - @ICU_SHIM_PATH@/pal_icushim.c \ @ICU_SHIM_PATH@/entrypoints.c libmonoruntime_shimglobalization_la_CFLAGS = @ICU_CFLAGS@ -I$(top_srcdir)/../libraries/Native/Unix/System.Globalization.Native/ -I$(top_srcdir)/../libraries/Native/Unix/Common/ -endif -endif + +if STATIC_ICU +nodist_libmonoruntime_shimglobalization_la_SOURCES += @ICU_SHIM_PATH@/pal_icushim_static.c +else +nodist_libmonoruntime_shimglobalization_la_SOURCES += @ICU_SHIM_PATH@/pal_icushim.c +endif # STATIC_ICU + +endif # HAVE_SYS_ICU +endif # ENABLE_NETCORE if !ENABLE_NETCORE culture_libraries = ../culture/libmono-culture.la @@ -498,7 +504,7 @@ libmonoruntimeinclude_HEADERS = \ sgen-bridge.h \ threads.h \ tokentype.h \ - verify.h + verify.h if !ENABLE_MSVC_ONLY diff --git a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj index c3dc47d69d1344..62c8e534591a43 100644 --- a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -163,6 +163,7 @@ + diff --git a/src/mono/netcore/System.Private.CoreLib/src/Mono/MonoPInvokeCallbackAttribute.cs b/src/mono/netcore/System.Private.CoreLib/src/Mono/MonoPInvokeCallbackAttribute.cs new file mode 100644 index 00000000000000..dbdc8fdead8415 --- /dev/null +++ b/src/mono/netcore/System.Private.CoreLib/src/Mono/MonoPInvokeCallbackAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Mono +{ + [AttributeUsage(AttributeTargets.Method)] + internal sealed class MonoPInvokeCallbackAttribute : Attribute + { + public MonoPInvokeCallbackAttribute(Type type) {} + } +} diff --git a/src/mono/wasm/.editorconfig b/src/mono/wasm/.editorconfig new file mode 100755 index 00000000000000..82b51b609cd8c5 --- /dev/null +++ b/src/mono/wasm/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org + +# top-most EditorConfig file +root = false + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = tabs +indent_size = 4 +trim_trailing_whitespace = true \ No newline at end of file diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index 23f96d6498368c..2562217a9354af 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -17,7 +17,7 @@ NATIVE_DIR?=$(OBJDIR)/native/net5.0-Browser-$(CONFIG)-wasm BUILDS_BIN_DIR?=$(BINDIR)/native/net5.0-Browser-$(CONFIG)-wasm ICU_LIBDIR?= -all: build-native timezone-data +all: build-native timezone-data icu-data # # EMSCRIPTEN SETUP @@ -54,7 +54,8 @@ MONO_LIBS = \ ${NATIVE_DIR}/System.Native/libSystem.Native.a \ ${NATIVE_DIR}/System.IO.Compression.Native/libSystem.IO.Compression.Native.a \ $(ICU_LIBDIR)/libicuuc.a \ - $(ICU_LIBDIR)/libicui18n.a + $(ICU_LIBDIR)/libicui18n.a \ + $(ICU_LIBDIR)/libicudata.a EMCC_FLAGS=--profiling-funcs -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s BINARYEN=1 -s ALIASING_FUNCTION_POINTERS=0 -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString', 'UTF8ArrayToString', 'addFunction']" -s "EXPORTED_FUNCTIONS=['_putchar']" --source-map-base http://example.com -emit-llvm -s FORCE_FILESYSTEM=1 -s USE_ZLIB=1 EMCC_DEBUG_FLAGS =-g -Os -s ASSERTIONS=1 -DENABLE_NETCORE=1 -DDEBUG=1 @@ -114,6 +115,9 @@ clean: timezone-data: cp runtime/dotnet.timezones.blat $(BUILDS_BIN_DIR) +icu-data: + cp $(ICU_LIBDIR)/icudt.dat $(BUILDS_BIN_DIR) + runtime: EMSDK_PATH=$(TOP)/src/mono/wasm/emsdk $(TOP)/build.sh --subset mono --arch wasm --os Browser -c $(CONFIG) /p:ContinueOnError=false /p:StopOnFirstFailure=true diff --git a/src/mono/wasm/runtime-test.js b/src/mono/wasm/runtime-test.js index 56d21202b89631..a2a66b6f85f626 100644 --- a/src/mono/wasm/runtime-test.js +++ b/src/mono/wasm/runtime-test.js @@ -130,9 +130,6 @@ while (true) { } else if (args [0] == "--disable-on-demand-gc") { enable_gc = false; args = args.slice (1); - } else if (args [0] == "--enable-zoneinfo") { - enable_zoneinfo = true; - args = args.slice (1); } else { break; } @@ -170,97 +167,54 @@ var Module = { MONO.mono_wasm_setenv (variable, setenv [variable]); } - // Read and write files to virtual file system listed in mono-config - if (typeof config.files_to_map != 'undefined') { - Module.print("Mapping test support files listed in config.files_to_map to VFS"); - const files_to_map = config.files_to_map; - try { - for (var i = 0; i < files_to_map.length; i++) - { - if (typeof files_to_map[i].directory != 'undefined') - { - var directory = files_to_map[i].directory == '' ? '/' : files_to_map[i].directory; - if (directory != '/') { - Module['FS_createPath']('/', directory, true, true); - } - - const files = files_to_map[i].files; - for (var j = 0; j < files.length; j++) - { - var fullPath = directory != '/' ? directory + '/' + files[j] : files[j]; - var content = new Uint8Array (read ("supportFiles/" + fullPath, 'binary')); - writeContentToFile(content, fullPath); - } - } - } - } - catch (err) { - Module.printErr(err); - Module.printErr(err.stack); - test_exit(1); - } - } - if (!enable_gc) { Module.ccall ('mono_wasm_enable_on_demand_gc', 'void', ['number'], [0]); } - if (enable_zoneinfo) { - // The timezone file is generated by https://github.com/dotnet/blazor/tree/master/src/TimeZoneData. - // The file format of the TZ file look like so - // - // [4-byte magic number] - // [4 - byte length of manifest] - // [json manifest] - // [data bytes] - // - // The json manifest is an array that looks like so: - // - // [...["America/Fort_Nelson",2249],["America/Glace_Bay",2206]..] - // - // where the first token in each array is the relative path of the file on disk, and the second is the - // length of the file. The starting offset of a file can be calculated using the lengths of all files - // that appear prior to it. - var zonedata = new Uint8Array(read ("dotnet.timezones.blat", "binary")); - MONO.mono_wasm_load_data (zonedata, "/usr/share/zoneinfo"); - } - MONO.mono_load_runtime_and_bcl ( - config.vfs_prefix, - config.deploy_prefix, - config.enable_debugging, - config.assembly_list, - function () { - App.init (); - }, - function (asset) - { - // for testing purposes add BCL assets to VFS until we special case File.Open - // to identify when an assembly from the BCL is being open and resolve it correctly. - var content = new Uint8Array (read (asset, 'binary')); - var path = asset.substr(config.deploy_prefix.length); - writeContentToFile(content, path); - - if (typeof window != 'undefined') { + config.loaded_cb = function () { + App.init (); + }; + config.fetch_file_cb = function (asset) { + // console.log("fetch_file_cb('" + asset + "')"); + // for testing purposes add BCL assets to VFS until we special case File.Open + // to identify when an assembly from the BCL is being open and resolve it correctly. + /* + var content = new Uint8Array (read (asset, 'binary')); + var path = asset.substr(config.deploy_prefix.length); + writeContentToFile(content, path); + */ + + if (typeof window != 'undefined') { return fetch (asset, { credentials: 'same-origin' }); - } else { + } else { // The default mono_load_runtime_and_bcl defaults to using - // fetch to load the assets. It also provides a way to set a + // fetch to load the assets. It also provides a way to set a // fetch promise callback. // Here we wrap the file read in a promise and fake a fetch response // structure. - return new Promise((resolve, reject) => { - var response = { ok: true, url: asset, - arrayBuffer: function() { - return new Promise((resolve2, reject2) => { - resolve2(content); - } - )} + return new Promise ((resolve, reject) => { + var bytes = null, error = null; + try { + bytes = read (asset, 'binary'); + } catch (exc) { + error = exc; } - resolve(response) - }) - } + var response = { ok: (bytes && !error), url: asset, + arrayBuffer: function () { + return new Promise ((resolve2, reject2) => { + if (error) + reject2 (error); + else + resolve2 (new Uint8Array (bytes)); + } + )} + } + resolve (response); + }) } - ); + }; + + MONO.mono_load_runtime_and_bcl_args (config); }, }; diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 4643790337dc00..e4456ee6b59583 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -343,7 +343,7 @@ void mono_initialize_internals () } EMSCRIPTEN_KEEPALIVE void -mono_wasm_load_runtime (const char *managed_path, int enable_debugging) +mono_wasm_load_runtime (const char *unused, int enable_debugging) { const char *interp_opts = ""; @@ -355,9 +355,6 @@ mono_wasm_load_runtime (const char *managed_path, int enable_debugging) // corlib assemblies. monoeg_g_setenv ("COMPlus_DebugWriteToStdErr", "1", 0); #endif -#ifdef ENABLE_NETCORE - monoeg_g_setenv ("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1", 0); -#endif mini_parse_debug_option ("top-runtime-invoke-unhandled"); diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js index 672eeffed1d8cb..cdaca1e2c9f0d1 100644 --- a/src/mono/wasm/runtime/library_mono.js +++ b/src/mono/wasm/runtime/library_mono.js @@ -25,6 +25,11 @@ var MonoSupportLib = { export_functions: function (module) { module ["pump_message"] = MONO.pump_message; module ["mono_load_runtime_and_bcl"] = MONO.mono_load_runtime_and_bcl; + module ["mono_load_runtime_and_bcl_args"] = MONO.mono_load_runtime_and_bcl_args; + module ["mono_wasm_load_bytes_into_heap"] = MONO.mono_wasm_load_bytes_into_heap; + module ["mono_wasm_load_icu_data"] = MONO.mono_wasm_load_icu_data; + module ["mono_wasm_globalization_init"] = MONO.mono_wasm_globalization_init; + module ["mono_wasm_get_loaded_files"] = MONO.mono_wasm_get_loaded_files; }, mono_text_decoder: undefined, @@ -44,11 +49,11 @@ var MonoSupportLib = { decode: function (start, end, save) { if (!MONO.mono_text_decoder) { MONO.mono_text_decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-16le') : undefined; - } + } var str = ""; if (MONO.mono_text_decoder) { - // When threading is enabled, TextDecoder does not accept a view of a + // When threading is enabled, TextDecoder does not accept a view of a // SharedArrayBuffer, we must make a copy of the array first. var subArray = typeof SharedArrayBuffer !== 'undefined' && Module.HEAPU8.buffer instanceof SharedArrayBuffer ? Module.HEAPU8.slice(start, end) @@ -148,9 +153,8 @@ var MonoSupportLib = { var numBytes = var_list.length * Int32Array.BYTES_PER_ELEMENT; var ptr = Module._malloc(numBytes); var heapBytes = new Int32Array(Module.HEAP32.buffer, ptr, numBytes); - for (let i=0; i 0) + ? virtualName.substr(0, lastSlash) + : null; + var fileName = (lastSlash > 0) + ? virtualName.substr(lastSlash + 1) + : virtualName; + if (fileName.startsWith("/")) + fileName = fileName.substr(1); + if (parentDirectory) { + if (ctx.tracing) + console.log ("MONO_WASM: Creating directory '" + parentDirectory + "'"); + + var pathRet = ctx.createPath( + "/", parentDirectory, true, true // fixme: should canWrite be false? + ); + } else { + parentDirectory = "/"; + } + + if (ctx.tracing) + console.log ("MONO_WASM: Creating file '" + fileName + "' in directory '" + parentDirectory + "'"); + + if (!this.mono_wasm_load_data_archive (bytes, parentDirectory)) { + var fileRet = ctx.createDataFile ( + parentDirectory, fileName, + bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ + ); } + break; + + default: + throw new Error ("Unrecognized asset behavior:", asset.behavior, "for asset", asset.name); + } + + if (asset.behavior === "assembly") + ctx.mono_wasm_add_assembly (virtualName, offset, bytes.length); + else if (asset.behavior === "icu") { + if (this.mono_wasm_load_icu_data (offset)) + ctx.num_icu_assets_loaded_successfully += 1; + else + console.error ("Error loading ICU asset", asset.name); + } + }, + + // deprecated + mono_load_runtime_and_bcl: function ( + unused_vfs_prefix, deploy_prefix, enable_debugging, file_list, loaded_cb, fetch_file_cb + ) { + var args = { + fetch_file_cb: fetch_file_cb, + loaded_cb: loaded_cb, + enable_debugging: enable_debugging, + assembly_root: deploy_prefix, + assets: [] + }; + + for (var i = 0; i < file_list.length; i++) { + var file_name = file_list[i]; + var behavior; + if (file_name === "icudt.dat") + behavior = "icu"; + else // if (file_name.endsWith (".pdb") || file_name.endsWith (".dll")) + behavior = "assembly"; + + args.assets.push ({ + name: file_name, + behavior: behavior + }); + } + + return this.mono_load_runtime_and_bcl_args (args); + }, + + // Initializes the runtime and loads assemblies, debug information, and other files. + // @args is a dictionary-style Object with the following properties: + // assembly_root: (required) the subfolder containing managed assemblies and pdbs + // enable_debugging: (required) + // assets: (required) a list of assets to load along with the runtime. each asset + // is a dictionary-style Object with the following properties: + // name: (required) the name of the asset, including extension. + // behavior: (required) determines how the asset will be handled once loaded: + // "heap": store asset into the native heap + // "assembly": load asset as a managed assembly (or debugging information) + // "icu": load asset as an ICU data archive + // "vfs": load asset into the virtual filesystem (for fopen, File.Open, etc) + // load_remote: (optional) if true, an attempt will be made to load the asset + // from each location in @args.remote_sources. + // virtual_path: (optional) if specified, overrides the path of the asset in + // the virtual filesystem and similar data structures once loaded. + // is_optional: (optional) if true, any failure to load this asset will be ignored. + // loaded_cb: (required) a function () invoked when loading has completed. + // fetch_file_cb: (optional) a function (string) invoked to fetch a given file. + // If no callback is provided a default implementation appropriate for the current + // environment will be selected (readFileSync in node, fetch elsewhere). + // If no default implementation is available this call will fail. + // remote_sources: (optional) additional search locations for assets. + // sources will be checked in sequential order until the asset is found. + // the string "./" indicates to load from the application directory (as with the + // files in assembly_list), and a fully-qualified URL like "https://example.com/" indicates + // that asset loads can be attempted from a remote server. Sources must end with a "/". + // environment_variables: (optional) dictionary-style Object containing environment variables + // runtime_options: (optional) array of runtime options as strings + // aot_profiler_options: (optional) dictionary-style Object. see the comments for + // mono_wasm_init_aot_profiler. If omitted, aot profiler will not be initialized. + // coverage_profiler_options: (optional) dictionary-style Object. see the comments for + // mono_wasm_init_coverage_profiler. If omitted, coverage profiler will not be initialized. + // globalization_mode: (optional) configures the runtime's globalization mode: + // "icu": load ICU globalization data from any runtime assets with behavior "icu". + // "invariant": operate in invariant globalization mode. + // "auto" (default): if "icu" behavior assets are present, use ICU, otherwise invariant. + // diagnostic_tracing: (optional) enables diagnostic log messages during startup + mono_load_runtime_and_bcl_args: function (args) { + try { + return this._load_assets_and_runtime (args); + } catch (exc) { + console.error ("error in mono_load_runtime_and_bcl_args:", exc); + throw exc; + } + }, + + // @bytes must be a typed array. space is allocated for it in the native heap + // and it is copied to that location. returns the address of the allocation. + mono_wasm_load_bytes_into_heap: function (bytes) { + var memoryOffset = Module._malloc (bytes.length); + var heapBytes = new Uint8Array (Module.HEAPU8.buffer, memoryOffset, bytes.length); + heapBytes.set (bytes); + return memoryOffset; + }, + + num_icu_assets_loaded_successfully: 0, + + // @offset must be the address of an ICU data archive in the native heap. + // returns true on success. + mono_wasm_load_icu_data: function (offset) { + var fn = Module.cwrap ('mono_wasm_load_icu_data', 'number', ['number']); + var ok = (fn (offset)) === 1; + if (ok) + this.num_icu_assets_loaded_successfully++; + return ok; + }, + + _finalize_startup: function (args, ctx) { + MONO.loaded_assets = ctx.loaded_assets; + MONO.loaded_files = ctx.loaded_files; + if (ctx.tracing) { + console.log ("MONO_WASM: loaded_assets: " + JSON.stringify(ctx.loaded_assets)); + console.log ("MONO_WASM: loaded_files: " + JSON.stringify(ctx.loaded_files)); + } + + var load_runtime = Module.cwrap ('mono_wasm_load_runtime', null, ['string', 'number']); + + console.log ("MONO_WASM: Initializing mono runtime"); + + this.mono_wasm_globalization_init (args.globalization_mode); + + if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { + try { + load_runtime ("unused", args.enable_debugging); + } catch (ex) { + print ("MONO_WASM: load_runtime () failed: " + ex); + var err = new Error(); + print ("MONO_WASM: Stacktrace: \n"); + print (err.stack); + + var wasm_exit = Module.cwrap ('mono_wasm_exit', null, ['number']); + wasm_exit (1); } + } else { + load_runtime ("unused", args.enable_debugging); } - file_list.forEach (function(file_name) { - - var fetch_promise = fetch_file_cb (locateFile(deploy_prefix + "/" + file_name)); + MONO.mono_wasm_runtime_ready (); + args.loaded_cb (); + }, + + _load_assets_and_runtime: function (args) { + if (args.assembly_list) + throw new Error ("Invalid args (assembly_list was replaced by assets)"); + if (args.runtime_assets) + throw new Error ("Invalid args (runtime_assets was replaced by assets)"); + if (args.runtime_asset_sources) + throw new Error ("Invalid args (runtime_asset_sources was replaced by remote_sources)"); + if (!args.loaded_cb) + throw new Error ("loaded_cb not provided"); + + var ctx = { + tracing: args.diagnostic_tracing || false, + pending_count: args.assets.length, + mono_wasm_add_assembly: Module.cwrap ('mono_wasm_add_assembly', null, ['string', 'number', 'number']), + loaded_assets: Object.create (null), + // dlls and pdbs, used by blazor and the debugger + loaded_files: [], + createPath: Module['FS_createPath'], + createDataFile: Module['FS_createDataFile'] + }; - fetch_promise.then (function (response) { + if (ctx.tracing) + console.log ("mono_wasm_load_runtime_with_args", JSON.stringify(args)); + + this._apply_configuration_from_args (args); + + var fetch_file_cb = this._get_fetch_file_cb_from_args (args); + + var onPendingRequestComplete = function () { + --ctx.pending_count; + + if (ctx.pending_count === 0) { + try { + MONO._finalize_startup (args, ctx); + } catch (exc) { + console.error ("Unhandled exception in _finalize_startup", exc); + throw exc; + } + } + }; + + var processFetchResponseBuffer = function (asset, url, blob) { + try { + MONO._handle_loaded_asset (ctx, asset, url, blob); + } catch (exc) { + console.error ("Unhandled exception in processFetchResponseBuffer", exc); + throw exc; + } finally { + onPendingRequestComplete (); + } + }; + + args.assets.forEach (function (asset) { + var attemptNextSource; + var sourceIndex = 0; + var sourcesList = asset.load_remote ? args.remote_sources : [""]; + + var handleFetchResponse = function (response) { if (!response.ok) { - // If it's a 404 on a .pdb, we don't want to block the app from starting up. - // We'll just skip that file and continue (though the 404 is logged in the console). - if (response.status === 404 && file_name.match(/\.pdb$/) && MONO.mono_wasm_ignore_pdb_load_errors) { - --pending; - throw "MONO-WASM: Skipping failed load for .pdb file: '" + file_name + "'"; - } - else { - throw "MONO_WASM: Failed to load file: '" + file_name + "'"; + try { + attemptNextSource (); + return; + } catch (exc) { + console.error ("MONO_WASM: Unhandled exception in handleFetchResponse attemptNextSource for asset", asset.name, exc); + throw exc; } } - else { - loaded_files.push (response.url); - return response ['arrayBuffer'] (); + + try { + var bufferPromise = response ['arrayBuffer'] (); + bufferPromise.then (processFetchResponseBuffer.bind (this, asset, response.url)); + } catch (exc) { + console.error ("MONO_WASM: Unhandled exception in handleFetchResponse for asset", asset.name, exc); + attemptNextSource (); } - }).then (function (blob) { - var asm = new Uint8Array (blob); - var memory = Module._malloc(asm.length); - var heapBytes = new Uint8Array(Module.HEAPU8.buffer, memory, asm.length); - heapBytes.set (asm); - mono_wasm_add_assembly (file_name, memory, asm.length); - - //console.log ("MONO_WASM: Loaded: " + file_name); - --pending; - if (pending == 0) { - MONO.loaded_files = loaded_files; - var load_runtime = Module.cwrap ('mono_wasm_load_runtime', null, ['string', 'number']); - - console.log ("MONO_WASM: Initializing mono runtime"); - if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { - try { - load_runtime (vfs_prefix, enable_debugging); - } catch (ex) { - print ("MONO_WASM: load_runtime () failed: " + ex); - var err = new Error(); - print ("MONO_WASM: Stacktrace: \n"); - print (err.stack); - - var wasm_exit = Module.cwrap ('mono_wasm_exit', null, ['number']); - wasm_exit (1); + }; + + attemptNextSource = function () { + if (sourceIndex >= sourcesList.length) { + var msg = "MONO_WASM: Failed to load " + asset.name; + try { + var isOk = asset.is_optional || + (asset.name.match (/\.pdb$/) && MONO.mono_wasm_ignore_pdb_load_errors); + + if (isOk) + console.log (msg); + else { + console.error (msg); + throw new Error (msg); } + } finally { + onPendingRequestComplete (); + } + } + + var sourcePrefix = sourcesList[sourceIndex]; + sourceIndex++; + + // HACK: Special-case because MSBuild doesn't allow "" as an attribute + if (sourcePrefix === "./") + sourcePrefix = ""; + + var attemptUrl; + if (sourcePrefix.trim() === "") { + if (asset.behavior === "assembly") + attemptUrl = locateFile (args.assembly_root + "/" + asset.name); + else + attemptUrl = asset.name; + } else { + attemptUrl = sourcePrefix + asset.name; + } + + try { + if (asset.name === attemptUrl) { + if (ctx.tracing) + console.log ("Attempting to fetch '" + attemptUrl + "'"); } else { - load_runtime (vfs_prefix, enable_debugging); + if (ctx.tracing) + console.log ("Attempting to fetch '" + attemptUrl + "' for", asset.name); } - MONO.mono_wasm_runtime_ready (); - loaded_cb (); + var fetch_promise = fetch_file_cb (attemptUrl); + fetch_promise.then (handleFetchResponse); + } catch (exc) { + console.error ("MONO_WASM: Error fetching " + attemptUrl, exc); + attemptNextSource (); } - }); + }; + + attemptNextSource (); }); }, + // Performs setup for globalization. + // @globalization_mode is one of "icu", "invariant", or "auto". + // "auto" will use "icu" if any ICU data archives have been loaded, + // otherwise "invariant". + mono_wasm_globalization_init: function (globalization_mode) { + var invariantMode = false; + + if (globalization_mode === "invariant") + invariantMode = true; + + if (!invariantMode) { + if (this.num_icu_assets_loaded_successfully > 0) { + console.log ("MONO_WASM: ICU data archive(s) loaded, disabling invariant mode"); + } else if (globalization_mode !== "icu") { + console.log ("MONO_WASM: ICU data archive(s) not loaded, using invariant globalization mode"); + invariantMode = true; + } else { + var msg = "invariant globalization mode is inactive and no ICU data archives were loaded"; + console.error ("MONO_WASM: ERROR: " + msg); + throw new Error (msg); + } + } + + if (invariantMode) + this.mono_wasm_setenv ("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1"); + }, + + // Used by the debugger to enumerate loaded dlls and pdbs mono_wasm_get_loaded_files: function() { - console.log(">>>mono_wasm_get_loaded_files"); - return this.loaded_files; + return MONO.loaded_files; }, - + + mono_wasm_get_loaded_asset_table: function() { + return MONO.loaded_assets; + }, + mono_wasm_clear_all_breakpoints: function() { if (!this.mono_clear_bps) this.mono_clear_bps = Module.cwrap ('mono_wasm_clear_all_breakpoints', null); this.mono_clear_bps (); }, - + mono_wasm_add_null_var: function(className) { fixed_class_name = MONO._mono_csharp_fixup_class_name(Module.UTF8ToString (className)); @@ -779,16 +1101,30 @@ var MonoSupportLib = { return className.replace(/\//g, '.').replace(/`\d+/g, ''); }, - mono_wasm_load_data: function (data, prefix) { + mono_wasm_load_data_archive: function (data, prefix) { + if (data.length < 8) + return false; + var dataview = new DataView(data.buffer); var magic = dataview.getUint32(0, true); // get magic number if (magic != 0x626c6174) { - throw new Error ("File is of wrong type"); + return false; } var manifestSize = dataview.getUint32(4, true); - var manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); - var manifest = JSON.parse(manifestContent); + if (manifestSize == 0 || data.length < manifestSize + 8) + return false; + + var manifest; + try { + manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize); + manifest = JSON.parse(manifestContent); + if (!(manifest instanceof Array)) + return false; + } catch (exc) { + return false; + } + data = data.slice(manifestSize+8); // Create the folder structure @@ -796,18 +1132,13 @@ var MonoSupportLib = { // /usr/share/zoneinfo/Africa // /usr/share/zoneinfo/Asia // .. - var p = prefix.slice(1).split('/'); - p.forEach((v, i) => { - FS.mkdir(v); - Module['FS_createPath']("/" + p.slice(0, i).join('/'), v, true, true); - }) + var folders = new Set() manifest.filter(m => { - m = m[0].split('/') - if (m!= null) { - if (m.length > 2) folders.add(m.slice(0,m.length-1).join('/')); - folders.add(m[0]); - } + var file = m[0]; + var last = file.lastIndexOf ("/"); + var directory = file.slice (0, last); + folders.add(directory); }); folders.forEach(folder => { Module['FS_createPath'](prefix, folder, true, true); @@ -817,9 +1148,10 @@ var MonoSupportLib = { var name = row[0]; var length = row[1]; var bytes = data.slice(0, length); - Module['FS_createDataFile'](`${prefix}/${name}`, null, bytes, true, true); + Module['FS_createDataFile'](prefix, name, bytes, true, true); data = data.slice(length); } + return true; } }, diff --git a/tools-local/tasks/mobile.tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/tools-local/tasks/mobile.tasks/WasmAppBuilder/PInvokeTableGenerator.cs index 010ffb4db20d1a..e37f83ec12f192 100644 --- a/tools-local/tasks/mobile.tasks/WasmAppBuilder/PInvokeTableGenerator.cs +++ b/tools-local/tasks/mobile.tasks/WasmAppBuilder/PInvokeTableGenerator.cs @@ -185,10 +185,11 @@ void EmitNativeToInterp(StreamWriter w, List callbacks) w.WriteLine("InterpFtnDesc wasm_native_to_interp_ftndescs[" + callbacks.Count + "];"); foreach (var cb in callbacks) { - var method = cb.Method; + MethodInfo method = cb.Method; + bool isVoid = method.ReturnType.FullName == "System.Void"; - if (method.ReturnType != typeof(void) && !IsBlittable(method.ReturnType)) - Error("The return type of pinvoke callback method '" + method + "' needs to be blittable."); + if (!isVoid && !IsBlittable(method.ReturnType)) + Error($"The return type '{method.ReturnType.FullName}' of pinvoke callback method '{method}' needs to be blittable."); foreach (var p in method.GetParameters()) { if (!IsBlittable(p.ParameterType)) Error("Parameter types of pinvoke callback method '" + method + "' needs to be blittable."); diff --git a/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs b/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs index 0b05c5502c0a57..668d4efeffc5c0 100644 --- a/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -29,6 +29,8 @@ public class WasmAppBuilder : Task public ITaskItem[]? AssemblySearchPaths { get; set; } public ITaskItem[]? ExtraAssemblies { get; set; } public ITaskItem[]? FilesToIncludeInFileSystem { get; set; } + public ITaskItem[]? RemoteSources { get; set; } + Dictionary? _assemblies; Resolver? _resolver; @@ -70,81 +72,66 @@ public override bool Execute () Directory.CreateDirectory(Path.Join(AppDir, "managed")); foreach (var assembly in _assemblies!.Values) File.Copy(assembly.Location, Path.Join(AppDir, "managed", Path.GetFileName(assembly.Location)), true); - foreach (var f in new string[] { "dotnet.wasm", "dotnet.js", "dotnet.timezones.blat" }) + foreach (var f in new string[] { "dotnet.wasm", "dotnet.js", "dotnet.timezones.blat", "icudt.dat" }) File.Copy(Path.Join (MicrosoftNetCoreAppRuntimePackDir, "native", f), Path.Join(AppDir, f), true); File.Copy(MainJS!, Path.Join(AppDir, "runtime.js"), true); - var filesToMap = new Dictionary>(); - if (FilesToIncludeInFileSystem != null) + using (var sw = File.CreateText(Path.Join(AppDir, "mono-config.js"))) { - string supportFilesDir = Path.Join(AppDir, "supportFiles"); - Directory.CreateDirectory(supportFilesDir); + sw.WriteLine("config = {"); + sw.WriteLine("\tassembly_root: \"managed\","); + sw.WriteLine("\tenable_debugging: 0,"); + sw.WriteLine("\tassets: ["); - foreach (var item in FilesToIncludeInFileSystem) + foreach (var assembly in _assemblies.Values) + sw.WriteLine($"\t\t{{ behavior: \"assembly\", name: \"{Path.GetFileName(assembly.Location)}\" }},"); + + if (FilesToIncludeInFileSystem != null) { - string? targetPath = item.GetMetadata("TargetPath"); - if (string.IsNullOrEmpty(targetPath)) + string supportFilesDir = Path.Join(AppDir, "supportFiles"); + Directory.CreateDirectory(supportFilesDir); + + var i = 0; + foreach (var item in FilesToIncludeInFileSystem) { - targetPath = Path.GetFileName(item.ItemSpec); - } + string? targetPath = item.GetMetadata("TargetPath"); + if (string.IsNullOrEmpty(targetPath)) + { + targetPath = Path.GetFileName(item.ItemSpec); + } - // We normalize paths from `\` to `/` as MSBuild items could use `\`. - targetPath = targetPath.Replace('\\', '/'); + // We normalize paths from `\` to `/` as MSBuild items could use `\`. + targetPath = targetPath.Replace('\\', '/'); - string? directory = Path.GetDirectoryName(targetPath); + var generatedFileName = $"{i++}_{Path.GetFileName(item.ItemSpec)}"; - if (!string.IsNullOrEmpty(directory)) - { - Directory.CreateDirectory(Path.Join(supportFilesDir, directory)); - } - else - { - directory = "/"; - } + File.Copy(item.ItemSpec, Path.Join(supportFilesDir, generatedFileName), true); - File.Copy(item.ItemSpec, Path.Join(supportFilesDir, targetPath), true); + var actualItemName = "supportFiles/" + generatedFileName; - if (filesToMap.TryGetValue(directory, out List? files)) - { - files.Add(Path.GetFileName(targetPath)); - } - else - { - files = new List(); - files.Add(Path.GetFileName(targetPath)); - filesToMap[directory] = files; + sw.WriteLine("\t\t{"); + sw.WriteLine("\t\t\tbehavior: \"vfs\","); + sw.WriteLine($"\t\t\tname: \"{actualItemName}\","); + sw.WriteLine($"\t\t\tvirtual_path: \"{targetPath}\","); + sw.WriteLine("\t\t},"); } } - } - using (var sw = File.CreateText(Path.Join(AppDir, "mono-config.js"))) - { - sw.WriteLine("config = {"); - sw.WriteLine("\tvfs_prefix: \"managed\","); - sw.WriteLine("\tdeploy_prefix: \"managed\","); - sw.WriteLine("\tenable_debugging: 0,"); - sw.WriteLine("\tassembly_list: ["); - foreach (var assembly in _assemblies.Values) - { - sw.Write("\t\t\"" + Path.GetFileName(assembly.Location) + "\""); - sw.WriteLine(","); - } + var enableRemote = (RemoteSources != null) && (RemoteSources!.Length > 0); + var sEnableRemote = enableRemote ? "true" : "false"; + + sw.WriteLine($"\t\t{{ behavior: \"icu\", name: \"icudt.dat\", load_remote: {sEnableRemote} }},"); + sw.WriteLine($"\t\t{{ behavior: \"vfs\", name: \"dotnet.timezones.blat\", virtual_path: \"/usr/share/zoneinfo/\" }}"); sw.WriteLine ("\t],"); - sw.WriteLine("\tfiles_to_map: ["); - foreach (KeyValuePair> keyValuePair in filesToMap) - { - sw.WriteLine("\t{"); - sw.WriteLine($"\t\tdirectory: \"{keyValuePair.Key}\","); - sw.WriteLine("\t\tfiles: ["); - foreach (string file in keyValuePair.Value) - { - sw.WriteLine($"\t\t\t\"{file}\","); - } - sw.WriteLine("\t\t],"); - sw.WriteLine("\t},"); + + if (enableRemote) { + sw.WriteLine("\tremote_sources: ["); + foreach (var source in RemoteSources!) + sw.WriteLine("\t\t\"" + source.ItemSpec + "\", "); + sw.WriteLine ("\t],"); } - sw.WriteLine ("\t],"); - sw.WriteLine ("}"); + + sw.WriteLine ("};"); } using (var sw = File.CreateText(Path.Join(AppDir, "run-v8.sh")))