[wasm] Add zoneinfo data for System.Runtime.TimeZoneInfoTests
authorTammy Qiu <tammy.qiu@yahoo.com>
Tue, 7 Jul 2020 18:09:27 +0000 (14:09 -0400)
committerGitHub <noreply@github.com>
Tue, 7 Jul 2020 18:09:27 +0000 (14:09 -0400)
Add zoneinfo data for System.Runtime.TimeZoneInfoTests
* Include dotnet.timezones.blat in runtime pack
* Adding enable-zoneinfo to runscriptcommand in tests.mobile.targets
* add mono_wasm_load_data to library-mono.js

Co-authored-by: Larry Ewing <lewing@microsoft.com>
eng/liveBuilds.targets
eng/testing/tests.mobile.targets
src/libraries/Native/native-binplace.proj
src/mono/wasm/Makefile
src/mono/wasm/runtime-test.js
src/mono/wasm/runtime/dotnet.timezones.blat [new file with mode: 0644]
src/mono/wasm/runtime/library_mono.js
tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs

index 52fe4a6..763b369 100644 (file)
       <LibrariesRuntimeFiles Condition="'$(TargetOS)' == 'Browser'"
                              Include="
         $(LibrariesNativeArtifactsPath)dotnet.js;
-        $(LibrariesNativeArtifactsPath)dotnet.wasm;"
+        $(LibrariesNativeArtifactsPath)dotnet.wasm;
+        $(LibrariesNativeArtifactsPath)dotnet.timezones.blat;"
         IsNative="true" />
     </ItemGroup>
 
index c5e8d06..89f3a4b 100644 (file)
@@ -14,7 +14,7 @@
 
   <PropertyGroup Condition="'$(TargetOS)' == 'Browser'">
     <!-- We need to set this in order to get extensibility on xunit category traits and other arguments we pass down to xunit via MSBuild properties -->
-    <RunScriptCommand>$HARNESS_RUNNER wasm test --engine=$(JSEngine) $(JSEngineArgs) --js-file=runtime.js -v --output-directory=$XHARNESS_OUT -- --run WasmTestRunner.dll $(AssemblyName).dll</RunScriptCommand>
+    <RunScriptCommand>$HARNESS_RUNNER wasm test --engine=$(JSEngine) $(JSEngineArgs) --js-file=runtime.js -v --output-directory=$XHARNESS_OUT -- --enable-zoneinfo --run WasmTestRunner.dll $(AssemblyName).dll</RunScriptCommand>
   </PropertyGroup>
 
   <!-- Generate a self-contained app bundle for Android with tests. -->
index 20b0efb..264a881 100644 (file)
@@ -24,6 +24,7 @@
       <BinPlaceItem Include="$(NativeBinDir)*.dwarf" />
       <BinPlaceItem Condition="'$(TargetOS)' == 'Browser'" Include="$(NativeBinDir)dotnet.js" />
       <BinPlaceItem Condition="'$(TargetOS)' == 'Browser'" Include="$(NativeBinDir)dotnet.wasm" />
+      <BinPlaceItem Condition="'$(TargetOS)' == 'Browser'" Include="$(NativeBinDir)dotnet.timezones.blat" />
       <FileWrites Include="@(BinPlaceItem)" />
     </ItemGroup>
   </Target>
index 3194d62..0ceeddd 100644 (file)
@@ -16,7 +16,7 @@ MONO_BIN_DIR?=$(BINDIR)/mono/Browser.wasm.$(CONFIG)
 SYS_NATIVE_DIR?=$(OBJDIR)/native/net5.0-Browser-$(CONFIG)-wasm/System.Native
 BUILDS_BIN_DIR?=$(BINDIR)/native/net5.0-Browser-$(CONFIG)-wasm
 
-all: build-native
+all: build-native timezone-data
 
 #
 # EMSCRIPTEN SETUP
@@ -56,7 +56,7 @@ MONO_LIBS = \
        $(MONO_BIN_DIR)/libmono-icall-table.a \
        ${SYS_NATIVE_DIR}/libSystem.Native.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', 'addFunction']" -s "EXPORTED_FUNCTIONS=['_putchar']" --source-map-base http://example.com  -emit-llvm -s FORCE_FILESYSTEM=1 -s USE_ZLIB=1
+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
 EMCC_RELEASE_FLAGS=-Oz --llvm-opts 2 -DENABLE_NETCORE=1
 
@@ -112,6 +112,9 @@ clean:
 # Helper targets
 .PHONY: runtime
 
+timezone-data:
+       cp runtime/dotnet.timezones.blat $(BUILDS_BIN_DIR)
+
 runtime:
        EMSDK_PATH=$(TOP)/src/mono/wasm/emsdk $(TOP)/build.sh --subset mono --arch wasm --os Browser -c $(CONFIG)
 
index d41ed2a..dfbd2bc 100644 (file)
@@ -93,12 +93,12 @@ function fail_exec (reason) {
 }
 
 function inspect_object (o) {
-    var r = "";
-    for(var p in o) {
-        var t = typeof o[p];
-        r += "'" + p + "' => '" + t + "', ";
-    }
-    return r;
+       var r = "";
+       for(var p in o) {
+               var t = typeof o[p];
+               r += "'" + p + "' => '" + t + "', ";
+       }
+       return r;
 }
 
 // Preprocess arguments
@@ -132,7 +132,7 @@ while (true) {
                args = args.slice (1);
        } else if (args [0] == "--enable-zoneinfo") {
                enable_zoneinfo = true;
-               args = args.slice (1);                  
+               args = args.slice (1);
        } else {
                break;
        }
@@ -149,7 +149,7 @@ function writeContentToFile(content, path)
 if (typeof window == "undefined")
   load ("mono-config.js");
 
-var Module = { 
+var Module = {
        mainScriptUrlOrBlob: "dotnet.js",
 
        print: print,
@@ -205,37 +205,25 @@ var Module = {
                        Module.ccall ('mono_wasm_enable_on_demand_gc', 'void', ['number'], [0]);
                }
                if (enable_zoneinfo) {
-                       // Load the zoneinfo data into the VFS rooted at /zoneinfo
-                       FS.mkdir("zoneinfo");
-                       Module['FS_createPath']('/', 'zoneinfo', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Indian', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Atlantic', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'US', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Brazil', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Pacific', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Arctic', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'America', true, true);
-                       Module['FS_createPath']('/zoneinfo/America', 'Indiana', true, true);
-                       Module['FS_createPath']('/zoneinfo/America', 'Argentina', true, true);
-                       Module['FS_createPath']('/zoneinfo/America', 'Kentucky', true, true);
-                       Module['FS_createPath']('/zoneinfo/America', 'North_Dakota', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Australia', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Etc', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Asia', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Antarctica', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Europe', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Mexico', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Africa', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Chile', true, true);
-                       Module['FS_createPath']('/zoneinfo', 'Canada', true, true);                     
-                       var zoneInfoData = read ('zoneinfo.data', 'binary');
-                       var metadata = JSON.parse(read ("mono-webassembly-zoneinfo-fs-smd.js.metadata", 'utf-8'));
-                       var files = metadata.files;
-                       for (var i = 0; i < files.length; ++i) {
-                               var byteArray = zoneInfoData.subarray(files[i].start, files[i].end);
-                               writeContentToFile(byteArray, files[i].filename);
-                       }
+                       // 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,
@@ -282,7 +270,7 @@ if (typeof window == "undefined")
 const IGNORE_PARAM_COUNT = -1;
 
 var App = {
-    init: function () {
+       init: function () {
 
                var assembly_load = Module.cwrap ('mono_wasm_assembly_load', 'number', ['string'])
                var find_class = Module.cwrap ('mono_wasm_assembly_find_class', 'number', ['number', 'string', 'string'])
@@ -399,5 +387,5 @@ var App = {
                } else {
                        fail_exec ("Unhanded argument: " + args [0]);
                }
-    },
+       },
 };
diff --git a/src/mono/wasm/runtime/dotnet.timezones.blat b/src/mono/wasm/runtime/dotnet.timezones.blat
new file mode 100644 (file)
index 0000000..4302a00
Binary files /dev/null and b/src/mono/wasm/runtime/dotnet.timezones.blat differ
index 61e6234..b288060 100644 (file)
@@ -779,6 +779,49 @@ var MonoSupportLib = {
                        // and nested class names like Foo/Bar to Foo.Bar
                        return className.replace(/\//g, '.').replace(/`\d+/g, '');
                },
+
+               mono_wasm_load_data: function (data, prefix) {
+                       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");
+                       }
+                       var manifestSize = dataview.getUint32(4, true);
+                       var manifestContent = Module.UTF8ArrayToString(data, 8, manifestSize);
+                       var manifest = JSON.parse(manifestContent);
+                       data = data.slice(manifestSize+8);
+
+                       // Create the folder structure
+                       // /usr/share/zoneinfo
+                       // /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]);
+                               }       
+                       });
+                       folders.forEach(folder => {
+                               Module['FS_createPath'](prefix, folder, true, true);
+                       });
+
+                       for (row of manifest) {
+                               var name = row[0];
+                               var length = row[1];
+                               var bytes = data.slice(0, length);
+                               Module['FS_createDataFile'](`${prefix}/${name}`, null, bytes, true, true);
+                               data = data.slice(length);
+                       }
+               }
        },
 
        mono_wasm_add_typed_value: function (type, str_value, value) {
@@ -993,7 +1036,7 @@ var MonoSupportLib = {
        mono_wasm_fire_bp: function () {
                console.log ("mono_wasm_fire_bp");
                debugger;
-       }
+       },
 };
 
 autoAddDeps(MonoSupportLib, '$MONO')
index 31082da..0e38c7a 100644 (file)
@@ -71,7 +71,7 @@ public class WasmAppBuilder : Task
         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" })
+        foreach (var f in new string[] { "dotnet.wasm", "dotnet.js", "dotnet.timezones.blat" })
             File.Copy(Path.Join (MicrosoftNetCoreAppRuntimePackDir, "native", f), Path.Join(AppDir, f), true);
         File.Copy(MainJS!, Path.Join(AppDir, "runtime.js"),  true);