Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<Target Name="_WriteBuildWasmBootJsonFile"
DependsOnTargets="_WriteWasmBootJsonBuildPropertyStamp"
Condition="'$(WasmBuildingForNestedPublish)' != 'true'"
Inputs="@(IntermediateAssembly);@(WasmStaticWebAsset);@(_WasmJsModuleCandidatesForBuild);@(_WasmFilesToIncludeInFileSystemStaticWebAsset);@(_WasmJsConfigStaticWebAsset);@(_WasmDotnetJsForBuild);@(WasmBootConfigExtension);$(ProjectRuntimeConfigFilePath);$(MSBuildProjectFullPath);$(MSBuildThisFileFullPath);$(_WebAssemblySdkTasksAssembly);$(IntermediateOutputPath)wasm-bootjson-build.stamp"
Inputs="@(IntermediateAssembly);@(WasmStaticWebAsset);@(_WasmJsModuleCandidatesForBuild);@(_WasmFilesToIncludeInFileSystemStaticWebAsset);@(_WasmJsConfigStaticWebAsset);@(_WasmDotnetJsForBuild);@(WasmBootConfigExtension);$(ProjectRuntimeConfigFilePath);$(ProjectRuntimeConfigDevFilePath);$(MSBuildProjectFullPath);$(MSBuildThisFileFullPath);$(_WebAssemblySdkTasksAssembly);$(IntermediateOutputPath)wasm-bootjson-build.stamp"
Outputs="$(IntermediateOutputPath)wasm-bootjson-build.complete.stamp">

<GenerateWasmBootJson
Expand All @@ -727,6 +727,7 @@ Copyright (c) .NET Foundation. All rights reserved.
EnvVariables="@(WasmEnvironmentVariable)"
Profilers="$(WasmProfilers)"
RuntimeConfigJsonPath="$(ProjectRuntimeConfigFilePath)"
RuntimeConfigDevJsonPath="$(ProjectRuntimeConfigDevFilePath)"
TargetFrameworkVersion="$(TargetFrameworkVersion)"
ModuleAfterConfigLoaded="@(WasmModuleAfterConfigLoaded)"
ModuleAfterRuntimeReady="@(WasmModuleAfterRuntimeReady)"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Xunit;

Comment thread
tmat marked this conversation as resolved.
namespace Microsoft.NET.Sdk.WebAssembly.Tests;

public class GenerateWasmBootJsonTests
{
[Fact]
public void ReadRuntimeConfigFiles_NullMainConfigPath_ReturnsNull()
{
var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(null, null);

Assert.Null(result);
}

[Fact]
public void ReadRuntimeConfigFiles_MainConfigNotExists_ReturnsNull()
{
using var dir = new TempDirectory();
var nonExistentPath = Path.Combine(dir.Path, "does-not-exist.runtimeconfig.json");
var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(nonExistentPath, null);

Assert.Null(result);
}

[Fact]
public void ReadRuntimeConfigFiles_DevConfigPreservesBooleanAndNumericTypes()
{
using var dir = new TempDirectory();
var mainConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.json",
configProperties: new() { ["key1"] = "value1" });
// Write dev config with native JSON boolean and number (not string) values.
var devConfigPath = Path.Combine(dir.Path, "App.runtimeconfig.dev.json");
File.WriteAllText(devConfigPath, """
{
"runtimeOptions": {
"configProperties": {
"System.HotReload.Enable": true,
"System.HotReload.MaxRetries": 10
}
}
}
""");

var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(mainConfig, devConfigPath);

Assert.NotNull(result?.runtimeOptions?.configProperties);
var props = result!.runtimeOptions!.configProperties!;
Assert.Equal(JsonValueKind.True, ((JsonElement)props["System.HotReload.Enable"]).ValueKind);
Assert.Equal(JsonValueKind.Number, ((JsonElement)props["System.HotReload.MaxRetries"]).ValueKind);
Assert.Equal(10, ((JsonElement)props["System.HotReload.MaxRetries"]).GetInt32());
}

[Fact]
public void ReadRuntimeConfigFiles_MainConfigOnly_ReturnsMainProperties()
{
using var dir = new TempDirectory();
var mainConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.json",
configProperties: new() { ["key1"] = "value1", ["key2"] = "42" });

var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(mainConfig, null);

Assert.NotNull(result);
Assert.NotNull(result.runtimeOptions?.configProperties);
Assert.Equal("value1", result.runtimeOptions!.configProperties!["key1"].ToString());
Assert.Equal("42", result.runtimeOptions.configProperties["key2"].ToString());
}

[Fact]
public void ReadRuntimeConfigFiles_DevConfigNotExists_ReturnsMainPropertiesUnchanged()
{
using var dir = new TempDirectory();
var mainConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.json",
configProperties: new() { ["key1"] = "value1" });
var devConfigPath = Path.Combine(dir.Path, "App.runtimeconfig.dev.json");

var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(mainConfig, devConfigPath);

Assert.NotNull(result);
Assert.Equal("value1", result.runtimeOptions?.configProperties?["key1"].ToString());
}

[Fact]
public void ReadRuntimeConfigFiles_DevConfigAddsNewProperty()
{
using var dir = new TempDirectory();
var mainConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.json",
configProperties: new() { ["key1"] = "value1" });
var devConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.dev.json",
configProperties: new() { ["key2"] = "value2" });

var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(mainConfig, devConfig);

Assert.NotNull(result?.runtimeOptions?.configProperties);
Assert.Equal("value1", result!.runtimeOptions!.configProperties!["key1"].ToString());
Assert.Equal("value2", result.runtimeOptions.configProperties["key2"].ToString());
}

[Fact]
public void ReadRuntimeConfigFiles_DevConfigOverridesMainProperty()
{
using var dir = new TempDirectory();
var mainConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.json",
configProperties: new() { ["System.Runtime.Feature"] = "false" });
var devConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.dev.json",
configProperties: new() { ["System.Runtime.Feature"] = "true" });

var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(mainConfig, devConfig);

Assert.NotNull(result?.runtimeOptions?.configProperties);
Assert.Equal("true", result!.runtimeOptions!.configProperties!["System.Runtime.Feature"].ToString());
}

[Fact]
public void ReadRuntimeConfigFiles_DevConfigMergesWhenMainHasNoConfigProperties()
{
using var dir = new TempDirectory();
var mainConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.json",
configProperties: null);
var devConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.dev.json",
configProperties: new() { ["System.HotReload.Enable"] = "true" });

var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(mainConfig, devConfig);

Assert.NotNull(result?.runtimeOptions?.configProperties);
Assert.Equal("true", result!.runtimeOptions!.configProperties!["System.HotReload.Enable"].ToString());
}

[Fact]
public void ReadRuntimeConfigFiles_DevConfigEmptyProperties_DoesNotAlterResult()
{
using var dir = new TempDirectory();
var mainConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.json",
configProperties: new() { ["key1"] = "value1" });
var devConfig = WriteRuntimeConfig(dir.Path, "App.runtimeconfig.dev.json",
configProperties: new());

var result = GenerateWasmBootJson.ReadRuntimeConfigFiles(mainConfig, devConfig);

Assert.NotNull(result?.runtimeOptions?.configProperties);
Assert.Single(result!.runtimeOptions!.configProperties!);
Assert.Equal("value1", result.runtimeOptions.configProperties["key1"].ToString());
}

private static string WriteRuntimeConfig(string dir, string fileName, Dictionary<string, string>? configProperties)
{
var path = Path.Combine(dir, fileName);
using var stream = File.OpenWrite(path);
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
writer.WriteStartObject();
writer.WritePropertyName("runtimeOptions");
writer.WriteStartObject();
if (configProperties is not null)
{
writer.WritePropertyName("configProperties");
writer.WriteStartObject();
foreach (var (key, value) in configProperties)
writer.WriteString(key, value);
writer.WriteEndObject();
}
writer.WriteEndObject();
writer.WriteEndObject();
return path;
}

private sealed class TempDirectory : System.IDisposable
{
public string Path { get; } = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName());

public TempDirectory() => Directory.CreateDirectory(Path);

public void Dispose()
{
// Silently ignore cleanup failures to avoid masking actual test failures.
try { Directory.Delete(Path, recursive: true); } catch { }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkVersion)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildUtilitiesCoreVersion)" />
<ProjectReference Include="..\..\tasks\Microsoft.NET.Sdk.WebAssembly.Pack.Tasks\Microsoft.NET.Sdk.WebAssembly.Pack.Tasks.csproj" />
</ItemGroup>
Comment thread
tmat marked this conversation as resolved.

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public class GenerateWasmBootJson : Task

public string? RuntimeConfigJsonPath { get; set; }

public string? RuntimeConfigDevJsonPath { get; set; }

public string Jiterpreter { get; set; }

public string RuntimeOptions { get; set; }
Expand Down Expand Up @@ -463,12 +465,7 @@ private void WriteBootConfig(string entryAssemblyName)
}
}

if (RuntimeConfigJsonPath != null && File.Exists(RuntimeConfigJsonPath))
{
using var fs = File.OpenRead(RuntimeConfigJsonPath);
var runtimeConfig = JsonSerializer.Deserialize<RuntimeConfigData>(fs, BootJsonBuilderHelper.JsonOptions);
result.runtimeConfig = runtimeConfig;
}
result.runtimeConfig = ReadRuntimeConfigFiles(RuntimeConfigJsonPath, IsPublish ? null : RuntimeConfigDevJsonPath);
Comment thread
tmat marked this conversation as resolved.

Profilers ??= Array.Empty<string>();
var browserProfiler = Profilers.FirstOrDefault(p => p.StartsWith("browser:"));
Expand Down Expand Up @@ -569,4 +566,36 @@ private Version ParsedTargetFrameworkVersion
private bool IsTargeting90OrLater() => ParsedTargetFrameworkVersion >= version90;
private bool IsTargeting100OrLater() => ParsedTargetFrameworkVersion >= version100;
private bool IsTargeting110OrLater() => ParsedTargetFrameworkVersion >= version110;

/// <summary>
/// Reads the main runtimeconfig.json and merges <c>configProperties</c> from the companion
/// runtimeconfig.dev.json (when it exists) into the result. Dev config values take precedence.
/// </summary>
internal static RuntimeConfigData? ReadRuntimeConfigFiles(string? mainConfigPath, string? devConfigPath)
{
if (!File.Exists(mainConfigPath))
return null;
Comment thread
tmat marked this conversation as resolved.

using var fs = File.OpenRead(mainConfigPath);
var runtimeConfig = JsonSerializer.Deserialize<RuntimeConfigData>(fs, BootJsonBuilderHelper.JsonOptions);

if (File.Exists(devConfigPath))
{
Comment thread
tmat marked this conversation as resolved.
// Merge overrides from runtimeconfig.dev.json (e.g. Hot Reload switches set by the SDK in debug builds).
using var devFs = File.OpenRead(devConfigPath);
var devRuntimeConfig = JsonSerializer.Deserialize<RuntimeConfigData>(devFs, BootJsonBuilderHelper.JsonOptions);
if (devRuntimeConfig?.runtimeOptions?.configProperties is { } devProps && devProps.Count > 0)
{
runtimeConfig ??= new RuntimeConfigData();
runtimeConfig.runtimeOptions ??= new RuntimeOptionsData();
runtimeConfig.runtimeOptions.configProperties ??= new Dictionary<string, object>();
foreach (var kvp in devProps)
{
runtimeConfig.runtimeOptions.configProperties[kvp.Key] = kvp.Value;
}
}
}

return runtimeConfig;
}
Comment thread
tmat marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="Microsoft.NET.Sdk.WebAssembly.Pack.Tasks.Tests" />
<Compile Include="..\Common\Utils.cs" />
<Compile Include="..\WasmAppBuilder\WebcilConverter.cs" />
<Compile Include="..\WasmAppBuilder\LogAdapter.cs" />
Expand Down
Loading