Skip to content

Apply *.runtimeconfig.dev.json to CoreCLR apps - #12268

Open
jonathanpeppers wants to merge 2 commits into
mainfrom
jonathanpeppers-port-runtimeconfig-dev-json
Open

Apply *.runtimeconfig.dev.json to CoreCLR apps#12268
jonathanpeppers wants to merge 2 commits into
mainfrom
jonathanpeppers-port-runtimeconfig-dev-json

Conversation

@jonathanpeppers

@jonathanpeppers jonathanpeppers commented Jul 30, 2026

Copy link
Copy Markdown
Member

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:

{
  "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:

<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 [release/11.0.1xx-preview7] Update dependencies from dotnet/dotnet #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.

Context: dotnet/sdk#53715

Port of #12249 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:

    {
      "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.

`main` is still on an SDK that sets `$(GenerateRuntimeConfigDevFile)` to
`false` for `net6.0`+, so the new `RuntimeConfigDevJsonIsApplied` device
test writes the dev file and sets
`$(ProjectRuntimeConfigDevFilePath)` itself. Both can be dropped once we
pick up the newer SDK, at which point the `BuildTest.DotNetBuild`
assertion for `*.runtimeconfig.dev.json` from #12249 can be added too.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b158ad70-1e5e-44cd-a1f2-60353f3bb560
Copilot AI review requested due to automatic review settings July 30, 2026 13:39

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 CoreCLR build pipeline to apply *.runtimeconfig.dev.json at build time (since .NET for Android doesn’t use hostfxr), ensuring Debug-only Hot Reload feature switches flow into the baked runtime properties.

Changes:

  • Layer *.runtimeconfig.dev.json configProperties over *.runtimeconfig.json in RuntimePropertiesParser (dev file wins).
  • Plumb $(ProjectRuntimeConfigDevFilePath) into GenerateNativeApplicationConfigSources and include both runtimeconfig files as incremental inputs for _GeneratePackageManagerJava.
  • Add unit coverage for the parser merge behavior and a device integration test proving the switches reach the running app.

Reviewed changes

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

Show a summary per file
File Description
tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs Adds a device test validating the dev runtimeconfig switches are observed at runtime for CoreCLR Debug builds.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets Adds both runtimeconfig files as _GeneratePackageManagerJava inputs and passes the dev path into the native app config generation task.
src/Xamarin.Android.Build.Tasks/Utilities/RuntimePropertiesParser.cs Implements merge logic to layer *.runtimeconfig.dev.json configProperties over the base runtimeconfig at build time.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/RuntimePropertiesParserTests.cs Introduces unit tests for parsing and precedence rules between base and dev runtimeconfig files.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs Plumbs the dev runtimeconfig path into RuntimePropertiesParser.ParseConfig(...) for CoreCLR builds.

Comment thread src/Xamarin.Android.Build.Tasks/Utilities/RuntimePropertiesParser.cs Outdated
`AddConfigProperties (..., required: true)` used `GetProperty
("configProperties")`, which throws `KeyNotFoundException` when the file
omits the key. A project that sets no feature switches produces exactly
that shape, and it would have prevented `*.runtimeconfig.dev.json` from
being applied even though it carries the Hot Reload switches.

Drop the `required` flag and always use `TryGetProperty()`, treating a
missing `runtimeOptions` or `configProperties` in either file as "no
properties". This is also what `hostfxr` does.

Not reachable today: `Microsoft.Android.Sdk.CoreCLR.targets` adds four
`@(RuntimeHostConfigurationOption)` items unconditionally, so a CoreCLR
app always has a non-empty `configProperties`. Fix it anyway so the
merge does not depend on that.

Add `ConfigWithoutConfigPropertiesStillGetsDevProperties` to cover it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b158ad70-1e5e-44cd-a1f2-60353f3bb560
@jonathanpeppers jonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jul 30, 2026
@jonathanpeppers
jonathanpeppers enabled auto-merge (squash) July 30, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants