diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8df873a5765ea..0d7aad96f5897 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 ae139287cf0b3..d578146bafbea 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 763b369167db5..e7cd12b6a4eaa 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 ebefed0e7e960..284ae301d8888 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 0000000000000..41a1956a2743c --- /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 264a881802f31..f8a558defe35b 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 0f96a592bf508..cb901d27f56a9 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 23c2032c51cd4..fdb6e4b189fce 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 b97a645b8b2a5..58429b132138c 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 a0dc8afc4adc8..2d0bd120bd307 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 1689c9e312d2d..52cda26a787ae 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 aa283d47e7c08..261edc3fb0d26 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 e33d39ddea3ea..76653b6d68af4 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 045d3e0b585be..62ae02d3aa4a4 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 dd99a40e94ca8..2cb86dc56da24 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 6b1a2219ca738..099f9c24fb422 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 3037ae44d799d..63314c0e85b91 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 aa3f12a4c29c6..e5425f8d0a31b 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 0000000000000..14b302d86b801 --- /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 0000000000000..b557c15384bb4 --- /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 7dbcf577a657d..0be8b50a064f6 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 3fa95271ee2c5..89ec53ebd419b 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 14dc0a0f1d0bc..39059fa7eba11 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 3979d76aeadb7..290cb3ca06b3d 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 b82c314a815f8..658d1f42a1287 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 6992c70d9f7ad..b420ac2995d86 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 e89b59c5f7100..8f013c5f0c677 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 9e3b75b2ba26c..d324c29baf91f 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 d3290b58a7f9f..502e2176adf21 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 8e6f520077dfe..06abdd51ab2b4 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 c3dc47d69d134..62c8e534591a4 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 0000000000000..dbdc8fdead841 --- /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 0000000000000..82b51b609cd8c --- /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 23f96d6498368..2562217a9354a 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 56d21202b8963..a2a66b6f85f62 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 4643790337dc0..e4456ee6b5958 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 672eeffed1d8c..cdaca1e2c9f0d 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 010ffb4db20d1..e37f83ec12f19 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 0b05c5502c0a5..668d4efeffc5c 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")))