Add runtimeconfig.json support for WebAssembly (#56486)
authorFan Yang <52458914+fanyang-mono@users.noreply.github.com>
Fri, 6 Aug 2021 13:04:16 +0000 (09:04 -0400)
committerGitHub <noreply@github.com>
Fri, 6 Aug 2021 13:04:16 +0000 (09:04 -0400)
* Initial change for adding runtimeconfig to wasm

* Add runtimeconfig support to wasm

* Move test folder and update runtimeconifg task name

* Fix test

* Fix test csproj file

* Use the right var for main dll name

* Resolve review feedback part 1

* Resolve review feedbacks part 2

* Add doc for updating dependencies when adding new tasks

* Remove redundant condition and redundant copy

* There is no runtimeconfig.json file to parse, when OutputType is library. So skip it.

* Skip for Android tests as well.

* Remove the wrong locatioin of WasmFilesToIncludeInFileSystem

* Update runtimeconfig file path

* Add RuntimeConfigParserTasksAssemblyPath to linker test project template

* Fix test failures on WASM EAT and AOT lanes

* [wasm] Add a test for reading runtimeconfig.json

* improve error reporting slightly

* Enable tests

* One more thing to enable tests

* Run target _WasmGenerateRuntimeConfig when runtimeconfig.json file exists

* Add a test for configure runtime using msbuild properties

* Fix runtime test failures

* Update tests base on feedback

* Add missing ")"

* Add one more condition

* More format changes

* Better error handling

* [wasm] Fix build for a test support project

This fixes the build for ApplyUpdateReferencedAssembly.csproj, which is
a support project for HotReload wasm functional test.

```
D:\workspace\_work\1\s\src\mono\wasm\build\WasmApp.targets(131,5): error : Could not find
    D:\workspace\_work\1\s\artifacts\bin\ApplyUpdateReferencedAssembly\net6.0-Release\browser-wasm\publish\ApplyUpdateReferencedAssembly.runtimeconfig.json
    for D:\workspace\_work\1\s\artifacts\bin\ApplyUpdateReferencedAssembly\net6.0-Release\browser-wasm\publish\ApplyUpdateReferencedAssembly.dll.

    [D:\workspace\_work\1\s\src\tests\FunctionalTests\WebAssembly\Browser\HotReload\ApplyUpdateReferencedAssembly\ApplyUpdateReferencedAssembly.csproj]
```

The wasm targets should be run at all for this project. But they are run
because they get imported by tests.wasm.targets, which gets imported because
`$(IsTestProject)=true`.

The csproj has `$(IsTestProject)=false`, and `$(IsTestSupportProject)=true`,
which should mean that the test.props/targets don't get imported. But the
import is conditioned on `$(EnableTestSupport)`, which gets set in
`src/libraries/Directory.Build.props`. And that means setting
`$(IsTestProject)=true` in the csproj is too late.

So, instead, set that in a `Directory.Build.props`. And also, ensure that the
`Directory.Build.props` for functional tests doesn't override the value!

Co-authored-by: Ankit Jain <radical@gmail.com>
29 files changed:
eng/testing/linker/project.csproj.template
eng/testing/linker/trimmingTests.targets
eng/testing/tests.mobile.targets
eng/testing/tests.wasm.targets
src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj
src/libraries/System.Text.Encoding/tests/Encoding/Encoding.cs
src/libraries/System.Text.Encoding/tests/Encoding/EncodingGetEncodingTests.cs
src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingEncode.cs
src/libraries/System.Text.Encoding/tests/UTF7Encoding/UTF7EncodingTests.cs
src/libraries/sendtohelixhelp.proj
src/libraries/tests.proj
src/mono/wasm/build/README.md
src/mono/wasm/build/WasmApp.InTree.targets
src/mono/wasm/build/WasmApp.LocalBuild.props
src/mono/wasm/build/WasmApp.LocalBuild.targets
src/mono/wasm/build/WasmApp.targets
src/mono/wasm/runtime/driver.c
src/tasks/AndroidAppBuilder/Templates/monodroid.c
src/tests/BuildWasmApps/Wasm.Build.Tests/WasmBuildAppTest.cs
src/tests/Common/wasm-test-runner/WasmTestRunner.proj
src/tests/Directory.Build.targets
src/tests/FunctionalTests/Directory.Build.props
src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/ApplyUpdateReferencedAssembly.csproj
src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/Directory.Build.props [new file with mode: 0644]
src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/Program.cs [new file with mode: 0644]
src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/WebAssembly.Browser.RuntimeConfig.Test.csproj [new file with mode: 0644]
src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/index.html [new file with mode: 0644]
src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/runtime.js [new file with mode: 0644]
src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/runtimeconfig.template.json [new file with mode: 0644]

index c9eeb18..e3a89ac 100644 (file)
@@ -7,6 +7,7 @@
     <MonoAOTCompilerTasksAssemblyPath>{MonoAOTCompilerTasksAssemblyPath}</MonoAOTCompilerTasksAssemblyPath>
     <WasmAppBuilderTasksAssemblyPath>{WasmAppBuilderTasksAssemblyPath}</WasmAppBuilderTasksAssemblyPath>
     <JsonToItemsTaskFactoryTasksAssemblyPath>{JsonToItemsTaskFactoryTasksAssemblyPath}</JsonToItemsTaskFactoryTasksAssemblyPath>
+    <RuntimeConfigParserTasksAssemblyPath>{RuntimeConfigParserTasksAssemblyPath}</RuntimeConfigParserTasksAssemblyPath>
     <MicrosoftNetCoreAppRuntimePackRidDir>{MicrosoftNetCoreAppRuntimePackRidDir}</MicrosoftNetCoreAppRuntimePackRidDir>
     <RepositoryEngineeringDir>{RepositoryEngineeringDir}</RepositoryEngineeringDir>
     <TargetFramework>{TestTargetFramework}</TargetFramework>
index 539053c..f175961 100644 (file)
@@ -97,6 +97,7 @@
                                                  .Replace('{MonoProjectRoot}', '$(MonoProjectRoot)')
                                                  .Replace('{MonoAOTCompilerTasksAssemblyPath}', '$(MonoAOTCompilerTasksAssemblyPath)')
                                                  .Replace('{JsonToItemsTaskFactoryTasksAssemblyPath}', '$(JsonToItemsTaskFactoryTasksAssemblyPath)')
+                                                 .Replace('{RuntimeConfigParserTasksAssemblyPath}', '$(RuntimeConfigParserTasksAssemblyPath)')
                                                  .Replace('{WasmAppBuilderTasksAssemblyPath}', '$(WasmAppBuilderTasksAssemblyPath)')
                                                  .Replace('{MicrosoftNetCoreAppRuntimePackRidDir}', '$(MicrosoftNetCoreAppRuntimePackRidDir)'))"
                       Overwrite="true" />
index 3beee87..572213e 100644 (file)
              AssemblyFile="$(RuntimeConfigParserTasksAssemblyPath)"
              Condition="'$(RuntimeConfigParserTasksAssemblyPath)' != ''" />
 
-  <Target Name="GenerateRuntimeConfig">
+  <Target Name="GenerateRuntimeConfig" Condition="'$(OutputType)' != 'library' and '$(TargetOS)' != 'Browser'">
     <PropertyGroup>
       <RuntimeConfigFilePath>$(PublishDir)$(AssemblyName).runtimeconfig.json</RuntimeConfigFilePath>
       <ParsedRuntimeConfigFilePath>$(PublishDir)runtimeconfig.bin</ParsedRuntimeConfigFilePath>
     </PropertyGroup>
 
-    <ItemGroup Condition="'$(RunAOTCompilation)' == 'true'">
+    <ItemGroup>
       <RuntimeConfigReservedProperties Include="RUNTIME_IDENTIFIER"/>
       <RuntimeConfigReservedProperties Include="APP_CONTEXT_BASE_DIRECTORY"/>
     </ItemGroup>
index 8618fdb..8d6829e 100644 (file)
              AssemblyFile="$(WasmBuildTasksAssemblyPath)" />
 
   <Target Name="_BundleAOTTestWasmAppForHelix" DependsOnTargets="PrepareForWasmBuildApp">
+    <PropertyGroup>
+      <_MainAssemblyPath Condition="'%(WasmAssembliesToBundle.FileName)' == $(AssemblyName) and '%(WasmAssembliesToBundle.Extension)' == '.dll'">%(WasmAssembliesToBundle.Identity)</_MainAssemblyPath>
+      <RuntimeConfigFilePath>$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</RuntimeConfigFilePath>
+    </PropertyGroup>
+
     <ItemGroup>
       <BundleFiles Include="$(WasmMainJSPath)"                  TargetDir="publish" />
       <BundleFiles Include="@(WasmAssembliesToBundle)"          TargetDir="publish\%(WasmAssembliesToBundle.RecursiveDir)" />
+      <BundleFiles Include="$(RuntimeConfigFilePath)"           TargetDir="publish" />
 
       <BundleFiles Include="$(MonoProjectRoot)\wasm\data\aot-tests\*" TargetDir="publish" />
     </ItemGroup>
       <_WasmPropertyNames Include="WasmLinkIcalls" />
       <_WasmPropertyNames Include="WasmDedup" />
       <_WasmPropertyNames Include="IncludeSatelliteAssembliesInVFS" />
+      <_WasmPropertyNames Include="AssemblyName" />
 
       <_WasmPropertiesToPass
         Include="$(%(_WasmPropertyNames.Identity))"
index ea4a7ff..0662b27 100644 (file)
@@ -2,7 +2,6 @@
   <PropertyGroup>
     <TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
     <TestRuntime>true</TestRuntime>
-    <IgnoreForCI Condition="'$(TargetOS)' == 'Browser'">true</IgnoreForCI> <!-- remove once https://github.com/dotnet/runtime/issues/38433 is fixed -->
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="ActivityTests.cs" />
index e7640cb..9ae2917 100644 (file)
@@ -64,7 +64,6 @@ namespace System.Text.Encodings.Tests
             }
         }
 
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json
         [Theory]
         [MemberData(nameof(Encoding_TestData))]
         public static void VerifyCodePageAttributes(int codepage, string name, string bodyName, string headerName, bool isBrowserDisplay,
@@ -81,7 +80,6 @@ namespace System.Text.Encodings.Tests
             Assert.Equal(windowsCodePage, encoding.WindowsCodePage);
         }
 
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json
         [Theory]
         [MemberData(nameof(Normalization_TestData))]
         public static void NormalizationTest(int codepage, bool normalized, bool normalizedC, bool normalizedD, bool normalizedKC, bool normalizedKD)
index 6a5fc49..2589dd1 100644 (file)
@@ -88,7 +88,6 @@ namespace System.Text.Tests
             new CodePageMapping("x-unicode-2-0-utf-8", 65001)
         };
 
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json
         [Fact]
         public void TestEncodingNameAndCopdepageNumber()
         {
@@ -99,7 +98,6 @@ namespace System.Text.Tests
             }
         }
 
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json
         [Fact]
         public void GetEncoding_EncodingName()
         {
@@ -119,7 +117,6 @@ namespace System.Text.Tests
             }
         }
 
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json
         [Fact]
         public void GetEncoding_WebName()
         {
index 781c844..a2e9654 100644 (file)
@@ -6,7 +6,6 @@ using Xunit;
 
 namespace System.Text.Tests
 {
-    [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json
     public class UTF7EncodingEncode
     {
         public static IEnumerable<object[]> Encode_Basic_TestData()
index c8d628c..841dc5e 100644 (file)
@@ -6,7 +6,6 @@ using Xunit;
 
 namespace System.Text.Tests
 {
-    [ActiveIssue("https://github.com/dotnet/runtime/issues/38433", TestPlatforms.Browser)] // wasm doesn't honor runtimeconfig.json
     public class UTF7EncodingTests
     {
         [Fact]
index 08bdf4f..e9ddef9 100644 (file)
       <HelixCorrelationPayload Include="$(WasmBuildTargetsDir)"                  Destination="build/wasm" />
       <HelixCorrelationPayload Include="$(MonoAotCrossDir)"                      Destination="build/cross" />
       <HelixCorrelationPayload Include="$(JsonToItemsTaskFactoryDir)"            Destination="build/JsonToItemsTaskFactory" />
+      <HelixCorrelationPayload Include="$(RuntimeConfigParserDir)"               Destination="build/RuntimeConfigParser" />
     </ItemGroup>
 
     <ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(Scenario)' != 'BuildWasmApps'">
index ebb11e1..4e60c62 100644 (file)
     <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Security.Cryptography.Xml\tests\System.Security.Cryptography.Xml.Tests.csproj" />
     <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Security.Cryptography.X509Certificates\tests\System.Security.Cryptography.X509Certificates.Tests.csproj" />
 
-    <!-- https://github.com/dotnet/runtime/issues/38433 -->
-    <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj" />
-
     <!-- Issue: https://github.com/dotnet/runtime/issues/55767 -->
     <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Runtime.Experimental\tests\System.Runtime.Experimental.Tests.csproj" />
   </ItemGroup>
index ea7b73e..854ea04 100644 (file)
@@ -49,3 +49,29 @@ The various task inputs correspond to properties as:
 This should be a step towards eventually having this build as a sdk.
 
 Refer to `WasmApp.targets` for more information about the properties/items used as inputs to the process.
+
+## Updating dependencies needed for building wasm apps
+
+For example, if the wasm targets are using a new task, then references to that
+need to be updated in a few places. Essentially, look for existing references
+to `MonoAOTCompiler`, or `WasmAppBuilder` in the relevant files, and duplicate
+them for the new task assembly.
+
+1. The task assembly dir, and its path need to be in two properties:
+    ```xml
+    <JsonToItemsTaskFactoryDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'JsonToItemsTaskFactory', 'Debug', '$(NetCoreAppToolCurrent)'))</JsonToItemsTaskFactoryDir>
+    <JsonToItemsTaskFactoryTasksAssemblyPath>$([MSBuild]::NormalizePath('$(JsonToItemsTaskFactoryDir)', 'JsonToItemsTaskFactory.dll'))</JsonToItemsTaskFactoryTasksAssemblyPath>
+    ```
+
+    And this needs to be set in:
+    - `Directory.Build.props`
+    - `src/mono/wasm/build/WasmApp.LocalBuild.props`
+    - `src/mono/wasm/build/WasmApp.LocalBuild.targets`
+    - `src/tests/Common/wasm-test-runner/WasmTestRunner.proj`
+
+2. The new dependency (eg. task assembly) needs to be sent to helix as a payload, see `src/libraries/sendtohelixhelp.proj`. Use `MonoAOTCompiler` as an example.
+
+3. Make changes similar to the one for existing dependent tasks in
+   - `eng/testing/linker/trimmingTests.targets`,
+   - `src/tests/Common/wasm-test-runner/WasmTestRunner.proj`
+   - `src/tests/Directory.Build.targets`
\ No newline at end of file
index 1537277..b345da9 100644 (file)
@@ -1,6 +1,7 @@
 <Project>
   <!-- This depends on the root Directory.Build.targets imported this file -->
   <UsingTask TaskName="MonoAOTCompiler" AssemblyFile="$(MonoAOTCompilerTasksAssemblyPath)" />
+  <UsingTask TaskName="RuntimeConfigParserTask" AssemblyFile="$(RuntimeConfigParserTasksAssemblyPath)" />
 
   <!-- TODO: this breaks runtime tests on Helix due to the file not being there for some reason. Once this is fixed we can remove the UpdateRuntimePack target here -->
   <!--<Import Project="$(RepositoryEngineeringDir)targetingpacks.targets" Condition="'$(TargetingpacksTargetsImported)' != 'true'"/>-->
index ea87a8b..d92024f 100644 (file)
@@ -35,6 +35,7 @@
     <WasmAppBuilderDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppBuilder', 'Debug', '$(_NetCoreAppToolCurrent)', 'publish'))</WasmAppBuilderDir>
     <WasmBuildTasksDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmBuildTasks', 'Debug', '$(_NetCoreAppToolCurrent)', 'publish'))</WasmBuildTasksDir>
     <MonoAOTCompilerDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'MonoAOTCompiler', 'Debug', '$(_NetCoreAppToolCurrent)'))</MonoAOTCompilerDir>
+    <RuntimeConfigParserDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'RuntimeConfigParser', 'Debug', '$(_NetCoreAppToolCurrent)'))</RuntimeConfigParserDir>
     <JsonToItemsTaskFactoryDir>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'JsonToItemsTaskFactory', 'Debug', '$(_NetCoreAppToolCurrent)'))</JsonToItemsTaskFactoryDir>
 
     <MonoArtifactsPath>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'mono', '$(TargetOS).$(TargetArchitecture).$(RuntimeConfig)'))</MonoArtifactsPath>
@@ -47,6 +48,7 @@
 
     <MicrosoftNetCoreAppRuntimePackLocationToUse>$([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'microsoft.netcore.app.runtime.browser-wasm'))</MicrosoftNetCoreAppRuntimePackLocationToUse>
     <MonoAOTCompilerDir>$([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'MonoAOTCompiler'))</MonoAOTCompilerDir>
+    <RuntimeConfigParserDir>$([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'RuntimeConfigParser'))</RuntimeConfigParserDir>
     <JsonToItemsTaskFactoryDir>$([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'JsonToItemsTaskFactory'))</JsonToItemsTaskFactoryDir>
     <WasmAppBuilderDir>$([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'WasmAppBuilder'))</WasmAppBuilderDir>
     <WasmBuildTasksDir>$([MSBuild]::NormalizeDirectory($(BuildBaseDir), 'WasmBuildTasks'))</WasmBuildTasksDir>
@@ -65,6 +67,7 @@
     <WasmAppBuilderTasksAssemblyPath>$([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll'))</WasmAppBuilderTasksAssemblyPath>
     <WasmBuildTasksAssemblyPath>$([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll'))</WasmBuildTasksAssemblyPath>
     <MonoAOTCompilerTasksAssemblyPath>$([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll'))</MonoAOTCompilerTasksAssemblyPath>
+    <RuntimeConfigParserTasksAssemblyPath>$([MSBuild]::NormalizePath('$(RuntimeConfigParserDir)', 'RuntimeConfigParser.dll'))</RuntimeConfigParserTasksAssemblyPath>
     <JsonToItemsTaskFactoryTasksAssemblyPath>$([MSBuild]::NormalizePath('$(JsonToItemsTaskFactoryDir)', 'JsonToItemsTaskFactory.dll'))</JsonToItemsTaskFactoryTasksAssemblyPath>
   </PropertyGroup>
 
index c5f431d..23f5249 100644 (file)
@@ -22,6 +22,7 @@
 <Project>
   <Import Project="$(MSBuildThisFileDirectory)WasmApp.targets" />
   <UsingTask TaskName="MonoAOTCompiler" AssemblyFile="$(MonoAOTCompilerTasksAssemblyPath)" />
+  <UsingTask TaskName="RuntimeConfigParserTask" AssemblyFile="$(RuntimeConfigParserTasksAssemblyPath)" />
 
   <PropertyGroup>
     <PublishTrimmed>true</PublishTrimmed>
@@ -64,6 +65,7 @@
       <WasmAppBuilderTasksAssemblyPath>$([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll'))</WasmAppBuilderTasksAssemblyPath>
       <WasmBuildTasksAssemblyPath>$([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll'))</WasmBuildTasksAssemblyPath>
       <MonoAOTCompilerTasksAssemblyPath>$([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll'))</MonoAOTCompilerTasksAssemblyPath>
+      <RuntimeConfigParserTasksAssemblyPath>$([MSBuild]::NormalizePath('$(RuntimeConfigParserDir)', 'RuntimeConfigParser.dll'))</RuntimeConfigParserTasksAssemblyPath>
       <JsonToItemsTaskFactoryTasksAssemblyPath>$([MSBuild]::NormalizePath('$(JsonToItemsTaskFactoryDir)', 'JsonToItemsTaskFactory.dll'))</JsonToItemsTaskFactoryTasksAssemblyPath>
     </PropertyGroup>
 
index 7de0e16..32c55ee 100644 (file)
       <WasmMainAssemblyFileName Condition="'$(WasmMainAssemblyFileName)' == ''">$(TargetFileName)</WasmMainAssemblyFileName>
 
       <WasmAppDir>$([MSBuild]::NormalizeDirectory($(WasmAppDir)))</WasmAppDir>
+
+      <_MainAssemblyPath Condition="'%(WasmAssembliesToBundle.FileName)' == $(AssemblyName) and '%(WasmAssembliesToBundle.Extension)' == '.dll' and $(WasmGenerateAppBundle) == 'true'">%(WasmAssembliesToBundle.Identity)</_MainAssemblyPath>
+      <_WasmRuntimeConfigFilePath Condition="$(_MainAssemblyPath) != ''">$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</_WasmRuntimeConfigFilePath>
     </PropertyGroup>
 
+    <Warning Condition="'$(WasmGenerateAppBundle)' == 'true' and $(_MainAssemblyPath) == ''" Text="Could not find %24(AssemblyName)=$(AssemblyName) in the assemblies to be bundled.." />
+    <Warning Condition="'$(WasmGenerateAppBundle)' == 'true' and $(_WasmRuntimeConfigFilePath) != '' and !Exists($(_WasmRuntimeConfigFilePath))" 
+             Text="Could not find $(_WasmRuntimeConfigFilePath) for $(_MainAssemblyPath)." />
+
     <ItemGroup>
       <_WasmAssembliesInternal Include="@(WasmAssembliesToBundle->Distinct())" />
 
     </ItemGroup>
   </Target>
 
-  <Target Name="_WasmGenerateAppBundle" Condition="'$(WasmGenerateAppBundle)' == 'true'">
+  <Target Name="_WasmGenerateRuntimeConfig" Condition="Exists('$(_WasmRuntimeConfigFilePath)')">
+    <PropertyGroup>
+      <_ParsedRuntimeConfigFilePath>$([System.IO.Path]::GetDirectoryName($(_MainAssemblyPath)))\runtimeconfig.bin</_ParsedRuntimeConfigFilePath>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <_RuntimeConfigReservedProperties Include="RUNTIME_IDENTIFIER"/>
+      <_RuntimeConfigReservedProperties Include="APP_CONTEXT_BASE_DIRECTORY"/>
+    </ItemGroup>
+
+    <!-- Parse *.runtimeconfig.json file -->
+    <RuntimeConfigParserTask
+        RuntimeConfigFile="$(_WasmRuntimeConfigFilePath)"
+        OutputFile="$(_ParsedRuntimeConfigFilePath)"
+        RuntimeConfigReservedProperties="@(_RuntimeConfigReservedProperties)">
+    </RuntimeConfigParserTask>
+
+    <ItemGroup>
+      <WasmFilesToIncludeInFileSystem Include="$(_ParsedRuntimeConfigFilePath)" />
+    </ItemGroup>
+  </Target>
+
+  <Target Name="_WasmGenerateAppBundle" Condition="'$(WasmGenerateAppBundle)' == 'true'" DependsOnTargets="_WasmGenerateRuntimeConfig">
     <Error Condition="'$(WasmMainJSPath)' == ''" Text="%24(WasmMainJSPath) property needs to be set" />
 
     <PropertyGroup>
index ca98e34..c880196 100644 (file)
@@ -7,6 +7,7 @@
 #include <stdint.h>
 #include <assert.h>
 #include <dlfcn.h>
+#include <sys/stat.h>
 
 #include <mono/metadata/assembly.h>
 #include <mono/metadata/tokentype.h>
@@ -92,6 +93,8 @@ void mono_trace_init (void);
 
 static MonoDomain *root_domain;
 
+#define RUNTIMECONFIG_BIN_FILE "runtimeconfig.bin"
+
 static MonoString*
 mono_wasm_invoke_js (MonoString *str, int *is_exception)
 {
@@ -476,6 +479,13 @@ mono_wasm_register_bundled_satellite_assemblies ()
 
 void mono_wasm_link_icu_shim (void);
 
+void
+cleanup_runtime_config (MonovmRuntimeConfigArguments *args, void *user_data)
+{
+       free (args);
+       free (user_data);
+}
+
 EMSCRIPTEN_KEEPALIVE void
 mono_wasm_load_runtime (const char *unused, int debug_level)
 {
@@ -493,7 +503,8 @@ mono_wasm_load_runtime (const char *unused, int debug_level)
     // corlib assemblies.
        monoeg_g_setenv ("COMPlus_DebugWriteToStdErr", "1", 0);
 #endif
-
+       // When the list of app context properties changes, please update RuntimeConfigReservedProperties for
+       // target _WasmGenerateRuntimeConfig in WasmApp.targets file
        const char *appctx_keys[2];
        appctx_keys [0] = "APP_CONTEXT_BASE_DIRECTORY";
        appctx_keys [1] = "RUNTIME_IDENTIFIER";
@@ -502,6 +513,23 @@ mono_wasm_load_runtime (const char *unused, int debug_level)
        appctx_values [0] = "/";
        appctx_values [1] = "browser-wasm";
 
+       char *file_name = RUNTIMECONFIG_BIN_FILE;
+       int str_len = strlen (file_name) + 1; // +1 is for the "/"
+       char *file_path = (char *)malloc (sizeof (char) * (str_len +1)); // +1 is for the terminating null character
+       int num_char = snprintf (file_path, (str_len + 1), "/%s", file_name);
+       struct stat buffer;
+
+       assert (num_char > 0 && num_char == str_len);
+
+       if (stat (file_path, &buffer) == 0) {
+               MonovmRuntimeConfigArguments *arg = (MonovmRuntimeConfigArguments *)malloc (sizeof (MonovmRuntimeConfigArguments));
+               arg->kind = 0;
+               arg->runtimeconfig.name.path = file_path;
+               monovm_runtimeconfig_initialize (arg, cleanup_runtime_config, file_path);
+       } else {
+               free (file_path);
+       }
+
        monovm_initialize (2, appctx_keys, appctx_values);
 
        mini_parse_debug_option ("top-runtime-invoke-unhandled");
index 4ed02eb..2e6f93f 100644 (file)
@@ -234,13 +234,13 @@ mono_droid_runtime_init (const char* executable, int managed_argc, char* managed
     appctx_values[1] = bundle_path;
 
     char *file_name = RUNTIMECONFIG_BIN_FILE;
-    int str_len = strlen (bundle_path) + strlen (file_name) + 2;
-    char *file_path = (char *)malloc (sizeof (char) * str_len);
-    int num_char = snprintf (file_path, str_len, "%s/%s", bundle_path, file_name);
+    int str_len = strlen (bundle_path) + strlen (file_name) + 1; // +1 is for the "/"
+    char *file_path = (char *)malloc (sizeof (char) * (str_len +1)); // +1 is for the terminating null character
+    int num_char = snprintf (file_path, (str_len + 1), "%s/%s", bundle_path, file_name);
     struct stat buffer;
 
     LOG_INFO ("file_path: %s\n", file_path);
-    assert (num_char > 0 && num_char < str_len);
+    assert (num_char > 0 && num_char == str_len);
 
     if (stat (file_path, &buffer) == 0) {
         MonovmRuntimeConfigArguments *arg = (MonovmRuntimeConfigArguments *)malloc (sizeof (MonovmRuntimeConfigArguments));
index 59b8231..412bf4a 100644 (file)
@@ -85,6 +85,71 @@ namespace Wasm.Build.Tests
                         extraProperties: "<WasmBuildNative>true</WasmBuildNative>",
                         dotnetWasmFromRuntimePack: false);
 
+        [Theory]
+        [BuildAndRun]
+        public void PropertiesFromRuntimeConfigJson(BuildArgs buildArgs, RunHost host, string id)
+        {
+            buildArgs = buildArgs with { ProjectName = $"runtime_config_{buildArgs.Config}_{buildArgs.AOT}" };
+            buildArgs = ExpandBuildArgs(buildArgs);
+
+            string programText = @"
+                using System;
+                using System.Runtime.CompilerServices;
+
+                var config = AppContext.GetData(""test_runtimeconfig_json"");
+                Console.WriteLine ($""test_runtimeconfig_json: {(string)config}"");
+                return 42;
+            ";
+
+            string runtimeConfigTemplateJson = @"
+            {
+                ""configProperties"": {
+                  ""abc"": ""4"",
+                  ""test_runtimeconfig_json"": ""25""
+                }
+            }";
+
+            BuildProject(buildArgs,
+                        initProject: () =>
+                        {
+                            File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText);
+                            File.WriteAllText(Path.Combine(_projectDir!, "runtimeconfig.template.json"), runtimeConfigTemplateJson);
+                        },
+                        id: id,
+                        dotnetWasmFromRuntimePack: !(buildArgs.AOT || buildArgs.Config == "Release"));
+
+            RunAndTestWasmApp(buildArgs, expectedExitCode: 42,
+                                test: output => Assert.Contains("test_runtimeconfig_json: 25", output), host: host, id: id);
+        }
+
+        [Theory]
+        [BuildAndRun]
+        public void PropertiesFromCsproj(BuildArgs buildArgs, RunHost host, string id)
+        {
+            buildArgs = buildArgs with { ProjectName = $"runtime_config_csproj_{buildArgs.Config}_{buildArgs.AOT}" };
+            buildArgs = ExpandBuildArgs(buildArgs, extraProperties: "<ThreadPoolMaxThreads>20</ThreadPoolMaxThreads>");
+
+            string programText = @"
+                using System;
+                using System.Runtime.CompilerServices;
+
+                var config = AppContext.GetData(""System.Threading.ThreadPool.MaxThreads"");
+                Console.WriteLine ($""System.Threading.ThreadPool.MaxThreads: {(string)config}"");
+                return 42;
+            ";
+
+            BuildProject(buildArgs,
+                        initProject: () =>
+                        {
+                            File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText);
+                        },
+                        id: id,
+                        dotnetWasmFromRuntimePack: !(buildArgs.AOT || buildArgs.Config == "Release"));
+
+            RunAndTestWasmApp(buildArgs, expectedExitCode: 42,
+                                test: output => Assert.Contains("System.Threading.ThreadPool.MaxThreads: 20", output), host: host, id: id);
+        }
+
         void TestMain(string projectName,
                       string programText,
                       BuildArgs buildArgs,
index f9479f9..5f4d4f0 100644 (file)
@@ -14,6 +14,7 @@
     <WasmAppBuilderTasksAssemblyPath>$(CORE_ROOT)\WasmAppBuilder\WasmAppBuilder.dll</WasmAppBuilderTasksAssemblyPath>
     <MonoAOTCompilerTasksAssemblyPath>$(CORE_ROOT)\MonoAOTCompiler\MonoAOTCompiler.dll</MonoAOTCompilerTasksAssemblyPath>
     <JsonToItemsTaskFactoryTasksAssemblyPath>$(CORE_ROOT)\JsonToItemsTaskFactory\JsonToItemsTaskFactory.dll</JsonToItemsTaskFactoryTasksAssemblyPath>
+    <RuntimeConfigParserTasksAssemblyPath>$(CORE_ROOT)\RuntimeConfigParser\RuntimeConfigParser.dll</RuntimeConfigParserTasksAssemblyPath>
   </PropertyGroup>
 
   <Target Name="BuildApp" BeforeTargets="WasmBuildApp">
index 4bf4c58..8ab2ab3 100644 (file)
           Include="$(ArtifactsBinDir)\JsonToItemsTaskFactory\Debug\$(NetCoreAppToolCurrent)\**"
           TargetDir="JsonToItemsTaskFactory/"/>
 
+        <!-- RuntimeConfigParser always builds in Debug -->
+        <RunTimeDependencyCopyLocal
+          Include="$(ArtifactsBinDir)\RuntimeConfigParser\Debug\$(NetCoreAppToolCurrent)\**"
+          TargetDir="RuntimeConfigParser/"/>
+
         <RunTimeDependencyCopyLocal
           Include="$(RepoRoot)\src\tests\Common\wasm-test-runner\WasmTestRunner.proj"
           TargetDir="wasm-test-runner/"/>
index a85fa69..ca9552d 100644 (file)
@@ -3,7 +3,7 @@
     <RunAnalyzers>false</RunAnalyzers>
     <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <IsTestProject>true</IsTestProject>
+    <IsTestProject Condition="'$(IsTestProject)' == ''">true</IsTestProject>
     <IsFunctionalTest>true</IsFunctionalTest>
   </PropertyGroup>
 
index a7a2111..b32d3ba 100644 (file)
@@ -3,8 +3,6 @@
     <TestRuntime>true</TestRuntime>
     <DeltaScript>deltascript.json</DeltaScript>
     <OutputType>library</OutputType>
-    <IsTestProject>false</IsTestProject>
-    <IsTestSupportProject>true</IsTestSupportProject>
     <!-- to call AsssemblyExtensions.ApplyUpdate we need Optimize=false, EmitDebugInformation=true in all configurations -->
     <Optimize>false</Optimize>
     <EmitDebugInformation>true</EmitDebugInformation>
diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/Directory.Build.props b/src/tests/FunctionalTests/WebAssembly/Browser/HotReload/ApplyUpdateReferencedAssembly/Directory.Build.props
new file mode 100644 (file)
index 0000000..1a524b2
--- /dev/null
@@ -0,0 +1,7 @@
+<Project>
+  <PropertyGroup>
+      <IsTestProject>false</IsTestProject>
+      <IsTestSupportProject>true</IsTestSupportProject>
+  </PropertyGroup>
+  <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props, $(MSBuildThisFileDirectory)..))" />
+</Project>
diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/Program.cs b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/Program.cs
new file mode 100644 (file)
index 0000000..6d1ae32
--- /dev/null
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Sample
+{
+    public class Test
+    {
+        public static void Main(string[] args)
+        {
+            Console.WriteLine ("Hello, World!");
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static int TestMeaning()
+        {
+            var config = AppContext.GetData("test_runtimeconfig_json");
+            int result = ((string)config).Equals("25") ? 42 : 1;
+            return result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/WebAssembly.Browser.RuntimeConfig.Test.csproj b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/WebAssembly.Browser.RuntimeConfig.Test.csproj
new file mode 100644 (file)
index 0000000..4efa17a
--- /dev/null
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TestRuntime>true</TestRuntime>
+    <Scenario>WasmTestOnBrowser</Scenario>
+    <ExpectedExitCode>42</ExpectedExitCode>
+    <WasmMainJSPath>runtime.js</WasmMainJSPath>
+  </PropertyGroup>
+  
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+  </ItemGroup>
+
+  <Target Name="AfterWasmBuildApp" AfterTargets="WasmBuildApp">
+    <Copy SourceFiles="$(MSBuildThisFileDirectory)\index.html" DestinationFolder="$(WasmAppDir)" />
+  </Target>
+  
+</Project>
diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/index.html b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/index.html
new file mode 100644 (file)
index 0000000..9da181f
--- /dev/null
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<!--  Licensed to the .NET Foundation under one or more agreements. -->
+<!-- The .NET Foundation licenses this file to you under the MIT license. -->
+<html>
+  <head>
+    <title>TESTS</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  </head>
+  <body onload="onLoad()">
+    <h3 id="header">Wasm Browser Sample</h3>
+    Result from Sample.Test.TestMeaning: <span id="out"></span>
+    <script type='text/javascript'>
+      var is_testing = false;
+      var onLoad = function() {
+        var url = new URL(decodeURI(window.location));
+        let args = url.searchParams.getAll('arg');
+        is_testing = args !== undefined && (args.find(arg => arg == '--testing') !== undefined);
+      };
+
+      var test_exit = function(exit_code)
+      {
+        if (!is_testing) {
+          console.log(`test_exit: ${exit_code}`);
+          return;
+        }
+
+        /* Set result in a tests_done element, to be read by xharness */
+        var tests_done_elem = document.createElement("label");
+        tests_done_elem.id = "tests_done";
+        tests_done_elem.innerHTML = exit_code.toString();
+        document.body.appendChild(tests_done_elem);
+
+        console.log(`WASM EXIT ${exit_code}`);
+      };
+
+      var App = {
+        init: function () {
+          var exit_code = BINDING.call_static_method("[WebAssembly.Browser.RuntimeConfig.Test] Sample.Test:TestMeaning", []);
+          document.getElementById("out").innerHTML = exit_code;
+
+          if (is_testing)
+          {
+            console.debug(`exit_code: ${exit_code}`);
+            test_exit(exit_code);
+          }
+        },
+      };
+    </script>
+    <script type="text/javascript" src="runtime.js"></script>
+
+    <script defer src="dotnet.js"></script>
+
+  </body>
+</html>
diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/runtime.js b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/runtime.js
new file mode 100644 (file)
index 0000000..b522747
--- /dev/null
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+var Module = { 
+
+    config: null,
+
+    preInit: async function() {
+        await MONO.mono_wasm_load_config("./mono-config.json"); // sets Module.config implicitly
+    },
+
+    onRuntimeInitialized: function () {
+        if (!Module.config || Module.config.error) {
+            console.log("No config found");
+            test_exit(1);
+            throw(Module.config.error);
+        }
+        
+        Module.config.loaded_cb = function () {
+            try {
+                App.init ();
+            } catch (error) {
+                test_exit(1);
+                throw (error);
+            }
+        };
+        Module.config.fetch_file_cb = function (asset) {
+            return fetch (asset, { credentials: 'same-origin' });
+        }
+
+        try
+        {
+            MONO.mono_load_runtime_and_bcl_args (Module.config);
+        } catch (error) {
+            test_exit(1);
+            throw(error);
+        }
+    },
+};
diff --git a/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/runtimeconfig.template.json b/src/tests/FunctionalTests/WebAssembly/Browser/RuntimeConfig/runtimeconfig.template.json
new file mode 100644 (file)
index 0000000..e7cd22b
--- /dev/null
@@ -0,0 +1,6 @@
+{
+    "configProperties": {
+      "abc": "4",
+      "test_runtimeconfig_json": "25"
+    }
+}
\ No newline at end of file