Skip to content

Update GenerateRuntimeConfigurationFiles task to generate Hot Reload runtime options - #53715

Merged
tmat merged 2 commits into
dotnet:mainfrom
tmat:GenerateHotReloadPropertiesToRuntimeConfigDev
Jul 15, 2026
Merged

Update GenerateRuntimeConfigurationFiles task to generate Hot Reload runtime options #53715
tmat merged 2 commits into
dotnet:mainfrom
tmat:GenerateHotReloadPropertiesToRuntimeConfigDev

Conversation

@tmat

@tmat tmat commented Apr 6, 2026

Copy link
Copy Markdown
Member

Update GenerateRuntimeConfigurationFiles task to generate Hot Reload runtime options to runtimeconfig.dev.json if EnableHotReloadInRuntimeConfigDevFile property is set. The property is set to true by default in Debug configuration.

The goal is to enable Hot Reload runtime switches in all Debug builds even if the project publishes AOT binaries.
We do so by generating runtimeconfig.dev.json, which overrides settings specified in runtimeconfig.json. The latter gets published while the former is only used for local development, thus the value of runtime switches of the published app can be different (Hot Reload disabled) than for dev builds (Hot Reload enabled).

This approach used to be blocked by dotnet/runtime#126606, which was recently fixed via
dotnet/runtime#128383. If the project targets .NET 10 or older the runtimeconfig.dev.json settings won't override runtimeconfig.json. We generate the dev.json file regardless as there is no harm to do so and it keeps the logic simpler.

See also discussion on dotnet/runtime#126546

Additionally, enable Hot Reload in dontet-watch when runtimeconfig.dev.json is generated with Hot Reload settings.

UPDATE: PR dotnet/runtime#130825 updates WASM build targets to apply runtimeconfig.dev.json.

@tmat tmat changed the title Update GenerateRuntimeConfigurationFiles task to generate Hot Reload … [Experimental] Update GenerateRuntimeConfigurationFiles task to generate Hot Reload … Apr 6, 2026
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Due to lack of recent activity, this PR has been labeled as 'Stale'. It will be closed if no further activity occurs within 7 more days. Any new comment will remove the label.

@github-actions github-actions Bot added the stale label Jun 6, 2026
@tmat
tmat force-pushed the GenerateHotReloadPropertiesToRuntimeConfigDev branch 2 times, most recently from 0036777 to 1a70afa Compare June 9, 2026 23:14
@tmat tmat changed the title [Experimental] Update GenerateRuntimeConfigurationFiles task to generate Hot Reload … Update GenerateRuntimeConfigurationFiles task to generate Hot Reload runtime options Jun 9, 2026
@tmat
tmat marked this pull request as ready for review June 9, 2026 23:15
Copilot AI review requested due to automatic review settings June 9, 2026 23:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the SDK’s runtimeconfig generation pipeline so runtimeconfig.dev.json can be generated for Hot Reload runtime switches (in addition to the existing probing-paths behavior), aligning with the Hot Reload runtime options requirements described in dotnet/runtime#126606.

Changes:

  • Split GenerateRuntimeConfigurationFiles dev-config generation into two independent switches: probing paths vs Hot Reload runtime options.
  • Update Microsoft.NET.Sdk.targets to compute GenerateRuntimeConfigDevFile based on the new switches and pass the new parameters to the task.
  • Add unit tests validating runtimeconfig.dev.json contents for Hot Reload-only, probing-paths-only, both enabled, and both disabled scenarios.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
test/Microsoft.NET.Build.Tasks.Tests/GivenAGenerateRuntimeConfigMultiThreading.cs Adds coverage for Hot Reload runtime options emission into runtimeconfig.dev.json under different switch combinations.
src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets Introduces new MSBuild properties for dev-config generation and wires them into the GenerateRuntimeConfigurationFiles task invocation.
src/Tasks/Microsoft.NET.Build.Tasks/GenerateRuntimeConfigurationFiles.cs Implements Hot Reload runtime switch emission into runtimeconfig.dev.json and gates probing-path generation behind a dedicated switch.

Comment thread src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets Outdated
@tmat
tmat marked this pull request as draft June 10, 2026 00:52
@github-actions github-actions Bot removed the stale label Jun 10, 2026
@tmat
tmat force-pushed the GenerateHotReloadPropertiesToRuntimeConfigDev branch 4 times, most recently from c56c492 to 3f6c162 Compare June 23, 2026 15:59
@tmat
tmat force-pushed the GenerateHotReloadPropertiesToRuntimeConfigDev branch 3 times, most recently from 87a437a to 28f6636 Compare June 30, 2026 18:47
@tmat
tmat force-pushed the GenerateHotReloadPropertiesToRuntimeConfigDev branch from 0d022a1 to 8c817a5 Compare July 8, 2026 01:48
…runtime options to runtimeconfig.dev.json

Update dotnet-watch to recognize the new setting.
@tmat
tmat marked this pull request as ready for review July 12, 2026 01:48
@tmat
tmat requested review from a team as code owners July 12, 2026 01:48
@tmat

tmat commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

@DustinCampbell @baronfel ptal

@javiercn

Copy link
Copy Markdown
Member

@tmat does this mean that hot reload now uses a "special" build and you might need to rebuild to run hot reload? (or that the build is always "hot reload" enabled).

@tmat

tmat commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

@tmat does this mean that hot reload now uses a "special" build and you might need to rebuild to run hot reload? (or that the build is always "hot reload" enabled).

No, it's not a special build. The regular Debug build produces another config file runtimeconfig.dev.json that the runtime uses to set runtime switches. The file is not published. It used to be generated in past to set additional probing paths and has not been used since .NET 6. With this change it's used again.

@pavelsavara

pavelsavara commented Jul 15, 2026

Copy link
Copy Markdown
Member

@tmat

tmat commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

Is this backward compatible ?

@pavelsavara Do you mean that if the WASM targets aren't loading it in .NET 11, will something break?

@pavelsavara

Copy link
Copy Markdown
Member

@pavelsavara Do you mean that if the WASM targets aren't loading it in .NET 11, will something break?

  • I think that browser target never loaded runtimeconfig.dev.json
  • I think that SDK changes typically impact all runtime versions, not just Net11
  • I think that if default runtimeconfig.json doesn't have hot reload enabled and runtimes don't read the other file, hot reload would not work.

Historically, we were converting runtimeconfig.json into runtimeconfig.bin at compile time.
I changed in in Net10 to be json transform into boot config manifest.
Even before it was one file, as far as I can remember.
I think Android still uses runtimeconfig.bin and RuntimeConfigParserTask transform, but I'm not sure.

I have not make detailed research, I could be wrong.
I suggest that you research and test those combinations.

@tmat

tmat commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

I think that if default runtimeconfig.json doesn't have hot reload enabled and runtimes don't read the other file, hot reload would not work.

There are two switches: System.Reflection.Metadata.MetadataUpdater.IsSupported and System.StartupHookProvider.IsSupported. The latter is not needed for WASM Hot Reload since startup hooks are not used for WASM. Re the former - if runtimeconfig.json specifies false for this switch then Hot Reload won't work correctly. That's the current state when PublishAot is set to true though, so it's already broken. The workaround for the customer is to explicitly set MetadataUpdateSupport msbuild property to true, which will override the default set by PublishAot.

After this change is merged dotnet-watch will see EnableHotReloadInRuntimeConfigDevFile set to true in Debug builds and hence assume that the necessary runtime switches are enabled (i.e. for WASM System.Reflection.Metadata.MetadataUpdater.IsSupported is true). It will attempt Hot Reload and that will fail. The only difference is that it will not fail with a friendly error message. The workaround of setting MetadataUpdateSupport msbuild property will resolve this issue.

So, ideally WASM targets will get updated to consider runtimeconfig.dev.json in .NET 11 so that the customer who sets PublishAot does not need to set MetadataUpdateSupport. If not, the workaround still works.

@tmat

tmat commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

@pavelsavara Filed: dotnet/runtime#130823

If a fix does not make it to .NET 11 I'll add a workaround to dotnet-watch to ignore EnableHotReloadInRuntimeConfigDevFile for WASM apps.

@tmat
tmat merged commit 2cc2abb into dotnet:main Jul 15, 2026
37 checks passed
@pavelsavara

Copy link
Copy Markdown
Member

The workaround of setting MetadataUpdateSupport msbuild property will resolve this issue.

Is this going to be necessary for Net10 browser after the SDK was upgraded ?
Could you apply the workaround for runtimes < Net11 in the SDK?

@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jul 16, 2026
@tmat

tmat commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

It is not; projects that target SDKs < v11 are already handled differently.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 28, 2026
Context: dotnet/sdk#53715

Starting with `11.0.100-preview.7.26376.106`, the .NET SDK emits a
`<App>.runtimeconfig.dev.json` file for `Debug` builds containing the
Hot Reload feature switches:

    {
      "runtimeOptions": {
        "configProperties": {
          "System.Reflection.Metadata.MetadataUpdater.IsSupported": true,
          "System.StartupHookProvider.IsSupported": true
        }
      }
    }

`hostfxr` layers this file on top of `*.runtimeconfig.json` at startup,
but .NET for Android does not use `hostfxr`: it bakes the runtime
properties into the application at build time. So the file was simply
ignored, and the switches never reached the app.

Do the same layering at build time for CoreCLR: read the dev file after
`*.runtimeconfig.json` in `RuntimePropertiesParser`, so its
`configProperties` win. This matches what Blazor WebAssembly does in
dotnet/runtime#130825.

This does not conflict with the switches we set ourselves.
`$(StartupHookSupport)` is only set to `false` when
`'$(Optimize)' == 'true'`, and the SDK only generates the dev file for
`Debug` builds, so the two are disjoint in practice. We never set
`$(MetadataUpdaterSupport)`.

Mono is unchanged: it reads runtime properties from the `rc.bin` blob
produced by `RuntimeConfigParserTask`, which only accepts a single input
file. Teaching that task about multiple files requires a change in
dotnet/runtime.

While here, add both `runtimeconfig` files to
`_GetGeneratePackageManagerJavaInputs`. Neither was an input before, so
editing them would not trigger a rebuild.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b158ad70-1e5e-44cd-a1f2-60353f3bb560
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 30, 2026
Changes: dotnet/dotnet@cb8306a...283ef97

- **Dependency Updates**:
  - From [11.0.0-beta.26365.101 to 11.0.0-beta.26378.119][3]
     - Microsoft.DotNet.Arcade.Sdk
     - Microsoft.DotNet.Build.Tasks.Feed
  - From [0.11.5-preview.26365.101 to 0.11.5-preview.26378.119][3]
     - Microsoft.DotNet.Cecil
  - From [11.0.0-preview.7.26365.101 to 11.0.0-preview.7.26378.119][3]
     - Microsoft.NET.ILLink
     - Microsoft.NETCore.App.Ref
  - From [11.0.100-preview.7.26365.101 to 11.0.100-preview.7.26378.119][3]
     - Microsoft.NET.Sdk
     - Microsoft.TemplateEngine.Authoring.Tasks

[3]: dotnet/dotnet@cb8306a...283ef97

### Apply `*.runtimeconfig.dev.json` to CoreCLR apps

Context: dotnet/sdk#53715

Starting with `11.0.100-preview.7.26376.106`, the .NET SDK emits a
`<App>.runtimeconfig.dev.json` file for `Debug` builds containing the
Hot Reload feature switches:

    {
      "runtimeOptions": {
        "configProperties": {
          "System.Reflection.Metadata.MetadataUpdater.IsSupported": true,
          "System.StartupHookProvider.IsSupported": true
        }
      }
    }

`hostfxr` layers this file on top of `*.runtimeconfig.json` at startup,
but .NET for Android does not use `hostfxr`: it bakes the runtime
properties into the application at build time. So the file was simply
ignored, and the switches never reached the app.

Do the same layering at build time for CoreCLR: read the dev file after
`*.runtimeconfig.json` in `RuntimePropertiesParser`, so its
`configProperties` win. This matches what Blazor WebAssembly does in
dotnet/runtime#130825.

This does not conflict with the switches we set ourselves.
`$(StartupHookSupport)` is only set to `false` when
`'$(Optimize)' == 'true'`, and the SDK only generates the dev file for
`Debug` builds, so the two are disjoint in practice. We never set
`$(MetadataUpdaterSupport)`.

Mono is unchanged: it reads runtime properties from the `rc.bin` blob
produced by `RuntimeConfigParserTask`, which only accepts a single input
file. Teaching that task about multiple files requires a change in
dotnet/runtime.

While here, add both `runtimeconfig` files to
`_GetGeneratePackageManagerJavaInputs`. Neither was an input before, so
editing them would not trigger a rebuild.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b158ad70-1e5e-44cd-a1f2-60353f3bb560

### Ignore `DotNetNewAndroidTest` for `dotnet test`

Context: #12254

Starting with .NET SDK `11.0.100-preview.7.26376.106`, `dotnet test` is
device-aware: it selects a device with `ComputeAvailableDevices`, then
deploys with `DeployToDevice` and computes the launch command with
`ComputeRunArguments`. Those are separate `ProjectInstance.Build ()`
calls against the same `ProjectInstance`, so `ProcessFrameworkReferences`
runs twice. MSBuild task outputs append, so `@(ResolvedRuntimePack)` ends
up with two `Microsoft.NETCore.App` entries and `ResolveFrameworkReferences`
throws:

    error MSB4018: The "ResolveFrameworkReferences" task failed unexpectedly.
    System.ArgumentException: An item with the same key has already been added. Key: Microsoft.NETCore.App

This reproduces with a stock SDK and the released
`Microsoft.Android.Sdk.Windows` `37.0.0-preview.6.59`, so it is not
caused by anything in this repo. Pinning a single
`$(RuntimeIdentifier)` does not help, and `dotnet run` works on the same
project, so there is nothing we can do from our targets. Ignore the
`dotnet test` cases until the fix flows back from dotnet/sdk.

The `dotnet run` case is unaffected and stays enabled.

### [ci] Work around broken `dotnet test` by using `dotnet run`

`dotnet test` currently fails to deploy to a device, so every APK
instrumentation lane fails with "Deployment to device failed. Fix any
deployment errors and run again." See #12254.

Drive the instrumentation with `dotnet run` instead. We do not lose the
test report: the on-device runner writes its own TRX and reports the
location back through the instrumentation bundle as
`INSTRUMENTATION_RESULT: resultsPath=<device path>`. `dotnet run` invokes
`adb shell am instrument -w`, which prints that bundle, so the path can be
scraped and the TRX pulled to where `PublishTestResults` expects it.

No product code changes; revert to `dotnet test` once the SDK fix lands.

Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 31, 2026
Context: dotnet/sdk#53715

Port of #12249 (which targets `release/11.0.1xx-preview7`) to `main`.

Starting with `11.0.100-preview.7.26376.106`, the .NET SDK emits a `<App>.runtimeconfig.dev.json` file for `Debug` builds containing the Hot Reload feature switches:

```json
{
  "runtimeOptions": {
    "configProperties": {
      "System.Reflection.Metadata.MetadataUpdater.IsSupported": true,
      "System.StartupHookProvider.IsSupported": true
    }
  }
}
```

`hostfxr` layers this file on top of `*.runtimeconfig.json` at startup, but .NET for Android does not use `hostfxr`: it bakes the runtime properties into the application at build time. So the file was simply ignored, and the switches never reached the app.

Do the same layering at build time for CoreCLR: read the dev file after `*.runtimeconfig.json` in `RuntimePropertiesParser`, so its `configProperties` win. This matches what Blazor WebAssembly does in dotnet/runtime#130825.

This does not conflict with the switches we set ourselves. `$(StartupHookSupport)` is only set to `false` when `'$(Optimize)' == 'true'`, and the SDK only generates the dev file for `Debug` builds, so the two are disjoint in practice. We never set `$(MetadataUpdaterSupport)`.

Mono is unchanged: it reads runtime properties from the `rc.bin` blob produced by `RuntimeConfigParserTask`, which only accepts a single input file. Teaching that task about multiple files requires a change in dotnet/runtime.

While here, add both `runtimeconfig` files to `_GetGeneratePackageManagerJavaInputs`. Neither was an input before, so editing them would not trigger a rebuild.

## Differences from #12249

The SDK `main` currently builds against does not yet emit the dev file. `Microsoft.NET.Sdk.targets` turns it off for every modern TFM:

```xml
<GenerateRuntimeConfigDevFile
    Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0'))">false</GenerateRuntimeConfigDevFile>
```

That condition is "6.0 or newer", so it applies to our `net11.0-android` projects. With `$(GenerateRuntimeConfigDevFile)` false the SDK neither writes the file nor gives `$(ProjectRuntimeConfigDevFilePath)` a default. dotnet/sdk#53715 is what flips this back on. Two adjustments follow:

* The new `RuntimeConfigDevJsonIsApplied` device test writes the dev file and sets `$(ProjectRuntimeConfigDevFilePath)` itself. Both can be dropped once `main` picks up the newer SDK.
* The `expectedFiles.Add ($"{proj.ProjectName}.runtimeconfig.dev.json")` assertion that #12249 adds to `BuildTest.DotNetBuild` is **not** ported, since it would fail on the current SDK. It should be added when `main` gets the newer SDK.

## Additional fix beyond #12249

`AddConfigProperties` originally used `GetProperty ("configProperties")` for the base file, which throws `KeyNotFoundException` when the key is absent. Both files now use `TryGetProperty`, matching `hostfxr`. Covered by the new `ConfigWithoutConfigPropertiesStillGetsDevProperties` test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants