Skip to content

Commit 340508f

Browse files
pavelsavararadical
andauthored
[wasi+browser] bundle timezones into .wasm (#82250)
Co-authored-by: Ankit Jain <radical@gmail.com>
1 parent 3d160bc commit 340508f

35 files changed

+720
-687
lines changed

eng/liveBuilds.targets

-2
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@
192192
$(LibrariesNativeArtifactsPath)package.json;
193193
$(LibrariesNativeArtifactsPath)dotnet.wasm;
194194
$(LibrariesNativeArtifactsPath)dotnet.js.symbols;
195-
$(LibrariesNativeArtifactsPath)dotnet.timezones.blat;
196195
$(LibrariesNativeArtifactsPath)*.dat;"
197196
IsNative="true" />
198197
<!-- for threaded wasm -->
@@ -225,7 +224,6 @@
225224
<LibrariesRuntimeFiles
226225
Include="
227226
$(LibrariesNativeArtifactsPath)dotnet.wasm;
228-
$(LibrariesNativeArtifactsPath)dotnet.timezones.blat;
229227
$(LibrariesNativeArtifactsPath)*.dat;"
230228
IsNative="true" />
231229

eng/native/configurecompiler.cmake

+3-2
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,9 @@ if(CLR_CMAKE_TARGET_UNIX)
586586
add_compile_definitions($<$<NOT:$<BOOL:$<TARGET_PROPERTY:IGNORE_DEFAULT_TARGET_OS>>>:TARGET_ANDROID>)
587587
elseif(CLR_CMAKE_TARGET_LINUX)
588588
add_compile_definitions($<$<NOT:$<BOOL:$<TARGET_PROPERTY:IGNORE_DEFAULT_TARGET_OS>>>:TARGET_LINUX>)
589+
if(CLR_CMAKE_TARGET_BROWSER)
590+
add_compile_definitions($<$<NOT:$<BOOL:$<TARGET_PROPERTY:IGNORE_DEFAULT_TARGET_OS>>>:TARGET_BROWSER>)
591+
endif()
589592
if(CLR_CMAKE_TARGET_LINUX_MUSL)
590593
add_compile_definitions($<$<NOT:$<BOOL:$<TARGET_PROPERTY:IGNORE_DEFAULT_TARGET_OS>>>:TARGET_LINUX_MUSL>)
591594
endif()
@@ -599,8 +602,6 @@ if(CLR_CMAKE_TARGET_UNIX)
599602
endif()
600603
elseif(CLR_CMAKE_TARGET_WASI)
601604
add_compile_definitions($<$<NOT:$<BOOL:$<TARGET_PROPERTY:IGNORE_DEFAULT_TARGET_OS>>>:TARGET_WASI>)
602-
elseif(CLR_CMAKE_TARGET_BROWSER)
603-
add_compile_definitions($<$<NOT:$<BOOL:$<TARGET_PROPERTY:IGNORE_DEFAULT_TARGET_OS>>>:TARGET_BROWSER>)
604605
else(CLR_CMAKE_TARGET_UNIX)
605606
add_compile_definitions($<$<NOT:$<BOOL:$<TARGET_PROPERTY:IGNORE_DEFAULT_TARGET_OS>>>:TARGET_WINDOWS>)
606607
endif(CLR_CMAKE_TARGET_UNIX)

src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,13 @@
221221
<PlatformManifestFileEntry Include="libmono-profiler-browser.a" IsNative="true" />
222222
<PlatformManifestFileEntry Include="libmono-wasm-eh-js.a" IsNative="true" />
223223
<PlatformManifestFileEntry Include="libmono-wasm-eh-wasm.a" IsNative="true" />
224+
<PlatformManifestFileEntry Include="wasm-bundled-timezones.a" IsNative="true" />
224225
<PlatformManifestFileEntry Include="dotnet.js" IsNative="true" />
225226
<PlatformManifestFileEntry Include="dotnet.worker.js" IsNative="true" />
226227
<PlatformManifestFileEntry Include="dotnet.js.symbols" IsNative="true" />
227228
<PlatformManifestFileEntry Include="dotnet.d.ts" IsNative="true" />
228229
<PlatformManifestFileEntry Include="dotnet-legacy.d.ts" IsNative="true" />
229230
<PlatformManifestFileEntry Include="dotnet.wasm" IsNative="true" />
230-
<PlatformManifestFileEntry Include="dotnet.timezones.blat" IsNative="true" />
231231
<PlatformManifestFileEntry Include="icudt.dat" IsNative="true" />
232232
<PlatformManifestFileEntry Include="icudt_no_CJK.dat" IsNative="true" />
233233
<PlatformManifestFileEntry Include="icudt_CJK.dat" IsNative="true" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Sys
10+
{
11+
[LibraryImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetTimeZoneData", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
12+
internal static partial IntPtr GetTimeZoneData(string fileName, out int length);
13+
}
14+
}

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

+3
Original file line numberDiff line numberDiff line change
@@ -2143,6 +2143,9 @@
21432143
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetDefaultTimeZone.AnyMobile.cs" Condition="'$(TargetsAndroid)' == 'true' or '$(TargetsLinuxBionic)' == 'true' or '$(IsiOSLike)' == 'true'">
21442144
<Link>Common\Interop\Unix\System.Native\Interop.GetDefaultTimeZone.AnyMobile.cs</Link>
21452145
</Compile>
2146+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetTimeZoneData.Wasm.cs" Condition="'$(TargetsWasi)' == 'true' or '$(TargetsBrowser)' == 'true'">
2147+
<Link>Common\src\Interop\Unix\System.Native\Interop.GetTimeZoneData.Wasm.cs</Link>
2148+
</Compile>
21462149
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetEnv.cs">
21472150
<Link>Common\Interop\Unix\System.Native\Interop.GetEnv.cs</Link>
21482151
</Compile>

src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.NonAndroid.cs

+119-33
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public sealed partial class TimeZoneInfo
1818
private const string TimeZoneDirectoryEnvironmentVariable = "TZDIR";
1919
private const string TimeZoneEnvironmentVariable = "TZ";
2020

21+
#if TARGET_WASI || TARGET_BROWSER
22+
// if TZDIR is set, then the embedded TZ data will be ignored and normal unix behavior will be used
23+
private static readonly bool UseEmbeddedTzDatabase = string.IsNullOrEmpty(Environment.GetEnvironmentVariable(TimeZoneDirectoryEnvironmentVariable));
24+
#endif
25+
2126
private static TimeZoneInfo GetLocalTimeZoneCore()
2227
{
2328
// Without Registry support, create the TimeZoneInfo from a TZ file
@@ -29,9 +34,31 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
2934
value = null;
3035
e = null;
3136

37+
byte[]? rawData=null;
3238
string timeZoneDirectory = GetTimeZoneDirectory();
3339
string timeZoneFilePath = Path.Combine(timeZoneDirectory, id);
34-
byte[] rawData;
40+
41+
#if TARGET_WASI || TARGET_BROWSER
42+
if (UseEmbeddedTzDatabase)
43+
{
44+
if(!TryLoadEmbeddedTzFile(timeZoneFilePath, out rawData))
45+
{
46+
e = new FileNotFoundException(id, "Embedded TZ data not found");
47+
return TimeZoneInfoResult.TimeZoneNotFoundException;
48+
}
49+
50+
value = GetTimeZoneFromTzData(rawData, id);
51+
52+
if (value == null)
53+
{
54+
e = new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidFileData, id, id));
55+
return TimeZoneInfoResult.InvalidTimeZoneException;
56+
}
57+
58+
return TimeZoneInfoResult.Success;
59+
}
60+
#endif
61+
3562
try
3663
{
3764
rawData = File.ReadAllBytes(timeZoneFilePath);
@@ -74,52 +101,68 @@ private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id,
74101
/// <remarks>
75102
/// Lines that start with # are comments and are skipped.
76103
/// </remarks>
77-
private static List<string> GetTimeZoneIds()
104+
private static IEnumerable<string> GetTimeZoneIds()
105+
{
106+
try
107+
{
108+
var fileName = Path.Combine(GetTimeZoneDirectory(), TimeZoneFileName);
109+
#if TARGET_WASI || TARGET_BROWSER
110+
if (UseEmbeddedTzDatabase)
111+
{
112+
if(!TryLoadEmbeddedTzFile(fileName, out var rawData))
113+
{
114+
return Array.Empty<string>();
115+
}
116+
using var blobReader = new StreamReader(new MemoryStream(rawData), Encoding.UTF8);
117+
return ParseTimeZoneIds(blobReader);
118+
}
119+
#endif
120+
using var reader = new StreamReader(fileName, Encoding.UTF8);
121+
return ParseTimeZoneIds(reader);
122+
}
123+
catch (IOException) { }
124+
catch (UnauthorizedAccessException) { }
125+
return Array.Empty<string>();
126+
}
127+
128+
private static List<string> ParseTimeZoneIds(StreamReader reader)
78129
{
79130
List<string> timeZoneIds = new List<string>();
80131

81-
try
132+
string? zoneTabFileLine;
133+
while ((zoneTabFileLine = reader.ReadLine()) != null)
82134
{
83-
using (StreamReader sr = new StreamReader(Path.Combine(GetTimeZoneDirectory(), TimeZoneFileName), Encoding.UTF8))
135+
if (!string.IsNullOrEmpty(zoneTabFileLine) && zoneTabFileLine[0] != '#')
84136
{
85-
string? zoneTabFileLine;
86-
while ((zoneTabFileLine = sr.ReadLine()) != null)
137+
// the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
138+
139+
int firstTabIndex = zoneTabFileLine.IndexOf('\t');
140+
if (firstTabIndex >= 0)
87141
{
88-
if (!string.IsNullOrEmpty(zoneTabFileLine) && zoneTabFileLine[0] != '#')
142+
int secondTabIndex = zoneTabFileLine.IndexOf('\t', firstTabIndex + 1);
143+
if (secondTabIndex >= 0)
89144
{
90-
// the format of the line is "country-code \t coordinates \t TimeZone Id \t comments"
91-
92-
int firstTabIndex = zoneTabFileLine.IndexOf('\t');
93-
if (firstTabIndex >= 0)
145+
string timeZoneId;
146+
int startIndex = secondTabIndex + 1;
147+
int thirdTabIndex = zoneTabFileLine.IndexOf('\t', startIndex);
148+
if (thirdTabIndex >= 0)
94149
{
95-
int secondTabIndex = zoneTabFileLine.IndexOf('\t', firstTabIndex + 1);
96-
if (secondTabIndex >= 0)
97-
{
98-
string timeZoneId;
99-
int startIndex = secondTabIndex + 1;
100-
int thirdTabIndex = zoneTabFileLine.IndexOf('\t', startIndex);
101-
if (thirdTabIndex >= 0)
102-
{
103-
int length = thirdTabIndex - startIndex;
104-
timeZoneId = zoneTabFileLine.Substring(startIndex, length);
105-
}
106-
else
107-
{
108-
timeZoneId = zoneTabFileLine.Substring(startIndex);
109-
}
150+
int length = thirdTabIndex - startIndex;
151+
timeZoneId = zoneTabFileLine.Substring(startIndex, length);
152+
}
153+
else
154+
{
155+
timeZoneId = zoneTabFileLine.Substring(startIndex);
156+
}
110157

111-
if (!string.IsNullOrEmpty(timeZoneId))
112-
{
113-
timeZoneIds.Add(timeZoneId);
114-
}
115-
}
158+
if (!string.IsNullOrEmpty(timeZoneId))
159+
{
160+
timeZoneIds.Add(timeZoneId);
116161
}
117162
}
118163
}
119164
}
120165
}
121-
catch (IOException) { }
122-
catch (UnauthorizedAccessException) { }
123166

124167
return timeZoneIds;
125168
}
@@ -379,6 +422,22 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
379422
return false;
380423
}
381424

425+
#if TARGET_WASI || TARGET_BROWSER
426+
private static bool TryLoadEmbeddedTzFile(string name, [NotNullWhen(true)] out byte[]? rawData)
427+
{
428+
IntPtr bytes = Interop.Sys.GetTimeZoneData(name, out int length);
429+
if(bytes == IntPtr.Zero)
430+
{
431+
rawData = null;
432+
return false;
433+
}
434+
435+
rawData = new byte[length];
436+
Marshal.Copy(bytes, rawData, 0, length);
437+
return true;
438+
}
439+
#endif
440+
382441
/// <summary>
383442
/// Gets the tzfile raw data for the current 'local' time zone using the following rules.
384443
///
@@ -387,6 +446,10 @@ private static bool TryLoadTzFile(string tzFilePath, [NotNullWhen(true)] ref byt
387446
/// 2. Get the default TZ from the device
388447
/// 3. Use UTC if all else fails.
389448
///
449+
/// On WASI / Browser
450+
/// 1. if TZDIR is not set, use TZ variable as id to embedded database.
451+
/// 2. fall back to unix behavior if TZDIR is set.
452+
///
390453
/// On all other platforms
391454
/// 1. Read the TZ environment variable. If it is set, use it.
392455
/// 2. Look for the data in /etc/localtime.
@@ -406,6 +469,11 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
406469
{
407470
#if TARGET_IOS || TARGET_TVOS
408471
tzVariable = Interop.Sys.GetDefaultTimeZone();
472+
#elif TARGET_WASI || TARGET_BROWSER
473+
if (UseEmbeddedTzDatabase)
474+
{
475+
return false; // use UTC
476+
}
409477
#else
410478
return
411479
TryLoadTzFile("/etc/localtime", ref rawData, ref id) ||
@@ -432,6 +500,24 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
432500
{
433501
tzFilePath = tzVariable;
434502
}
503+
504+
#if TARGET_WASI || TARGET_BROWSER
505+
if (UseEmbeddedTzDatabase)
506+
{
507+
// embedded database only supports relative paths
508+
if (tzVariable[0] == '/')
509+
{
510+
return false;
511+
}
512+
if(!TryLoadEmbeddedTzFile(tzFilePath, out rawData))
513+
{
514+
return false;
515+
}
516+
id = tzVariable;
517+
return true;
518+
}
519+
#endif
520+
435521
return TryLoadTzFile(tzFilePath, ref rawData, ref id);
436522
}
437523

src/mono/mono/utils/mono-dl-wasm.c

+43
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <config.h>
22
#include <mono/utils/mono-compiler.h>
3+
#include <mono/eglib/glib.h>
34

45
#if defined (HOST_WASM)
56

@@ -93,3 +94,45 @@ mono_dl_close_handle (MonoDl *module, MonoError *error)
9394
MONO_EMPTY_SOURCE_FILE (mono_dl_wasm);
9495

9596
#endif
97+
98+
#if defined (HOST_WASM)
99+
100+
static GHashTable *name_to_blob = NULL;
101+
102+
typedef struct {
103+
const unsigned char *data;
104+
unsigned int size;
105+
} FileBlob;
106+
107+
int
108+
mono_wasm_add_bundled_file (const char *name, const unsigned char *data, unsigned int size)
109+
{
110+
// printf("mono_wasm_add_bundled_file: %s %p %d\n", name, data, size);
111+
if(name_to_blob == NULL)
112+
{
113+
name_to_blob = g_hash_table_new (g_str_hash, g_str_equal);
114+
}
115+
FileBlob *blob = g_new0 (FileBlob, 1);
116+
blob->data = data;
117+
blob->size = size;
118+
g_hash_table_insert (name_to_blob, (gpointer) name, blob);
119+
return 0;
120+
}
121+
122+
const unsigned char*
123+
mono_wasm_get_bundled_file (const char *name, int* out_length)
124+
{
125+
FileBlob *blob = (FileBlob *)g_hash_table_lookup (name_to_blob, name);
126+
if (blob != NULL)
127+
{
128+
// printf("mono_wasm_get_bundled_file: %s %p %d \n", name, blob->data, blob->size);
129+
*out_length = blob->size;
130+
return blob->data;
131+
}
132+
133+
// printf("mono_wasm_get_bundled_file: %s not found \n", name);
134+
*out_length = 0;
135+
return NULL;
136+
}
137+
138+
#endif /* HOST_WASM */

src/mono/mono/utils/mono-dl.h

+5
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,10 @@ int mono_dl_convert_flags (int mono_flags, int native_flags);
5858
char* mono_dl_current_error_string (void);
5959
const char* mono_dl_get_system_dir (void);
6060

61+
#if defined (HOST_WASM)
62+
int mono_wasm_add_bundled_file (const char *name, const unsigned char *data, unsigned int size);
63+
const unsigned char* mono_wasm_get_bundled_file (const char *name, int* out_length);
64+
#endif /* HOST_WASM */
65+
6166
#endif /* __MONO_UTILS_DL_H__ */
6267

src/mono/wasi/Wasi.Build.Tests/BuildTestBase.cs

-1
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,6 @@ protected static void AssertBasicAppBundle(string bundleDir,
360360
{
361361
"index.html",
362362
mainJS,
363-
"dotnet.timezones.blat",
364363
"dotnet.wasm",
365364
"mono-config.json",
366365
"dotnet.js"

0 commit comments

Comments
 (0)