Skip to content

Fixes #5069. ConfigurationManager is trim-safe without TrimmerRootAssembly#5071

Merged
tig merged 6 commits intogui-cs:developfrom
harder:fix/5069-config-manager-aot-trim-safe
Apr 26, 2026
Merged

Fixes #5069. ConfigurationManager is trim-safe without TrimmerRootAssembly#5071
tig merged 6 commits intogui-cs:developfrom
harder:fix/5069-config-manager-aot-trim-safe

Conversation

@harder
Copy link
Copy Markdown
Contributor

@harder harder commented Apr 24, 2026

Summary

ConfigProperty.Initialize() previously relied on a reflection scan of AppDomain.CurrentDomain.GetAssemblies() to locate [ConfigurationProperty] host types. Under PublishTrimmed=true, types not otherwise referenced by the consumer were stripped and Scope<T>.GetUninitializedProperty(\"Theme\") would throw at startup — forcing AOT apps to carry <TrimmerRootAssembly Include=\"Terminal.Gui\" />, adding ~1.5 MB per binary.

Changes

  • New Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs — internal static registry listing the 29 types in Terminal.Gui that own [ConfigurationProperty] properties. Each entry is rooted with [DynamicDependency(PublicProperties, typeof(X))] so the trimmer preserves the properties the initializer depends on.
  • Terminal.Gui/Configuration/ConfigProperty.csInitialize() unconditionally registers the static list first, then falls back to the original AppDomain scan only when RuntimeFeature.IsDynamicCodeSupported is true (i.e., JIT, not AOT). External consumers and tests that define their own [ConfigurationProperty] properties continue to be auto-discovered in non-AOT builds.
  • Examples/NativeAot/NativeAot.csproj — drops the <TrimmerRootAssembly Include=\"Terminal.Gui\" /> workaround.
  • New Tests/UnitTestsParallelizable/Configuration/ConfigPropertyHostTypesTests.cs — asserts that the static registry matches the set of types reflected from the Terminal.Gui assembly at runtime, so any new [ConfigurationProperty] added to a previously-unlisted type fails the build.

Scope

Intentionally minimal. The [RequiresUnreferencedCode] / [RequiresDynamicCode] annotations on ConfigurationManager and Application.Init are left in place — removing those propagates through a large public API surface and is out of scope for this fix. This PR addresses the core bug: AOT apps no longer need TrimmerRootAssembly and won't crash at startup.

Referenced issues / prior art

Test plan

  • dotnet build Terminal.Gui/Terminal.Gui.csproj — 0 warnings, 0 errors
  • dotnet build Tests/UnitTestsParallelizable — 0 warnings, 0 errors
  • UnitTests.Parallelizable full run: 16395 passed / 0 failed / 17 skipped (all pre-existing skips)
  • New ConfigPropertyHostTypesTests passes and will catch drift if a future type adds [ConfigurationProperty] without being listed
  • Examples/NativeAot publish for linux-x64 — ILC trim analysis completes with no type-stripped errors. The NativeAot's gcc link step fails in the local WSL environment (gcc: error: unrecognized command-line option '--target=x86_64-linux-gnu'), but the managed/trim analysis step — the only part this PR changes — succeeds. Full native-link verification is appropriate for the repo's CI matrix.
  • Recommend CI run publishes for the 6-RID AOT matrix (linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64, win-arm64) to confirm binary-size reduction and no runtime regressions.

To pull down this PR locally:

git remote add copilot https://github.com/harder/Terminal.Gui.git
git fetch copilot fix/5069-config-manager-aot-trim-safe
git checkout copilot/fix/5069-config-manager-aot-trim-safe

🤖 Generated with Claude Code

…ootAssembly

ConfigProperty.Initialize() previously relied on a reflection scan of
AppDomain.CurrentDomain.GetAssemblies() to locate [ConfigurationProperty]
host types. Under PublishTrimmed=true, types not otherwise referenced by
the consumer were stripped and Scope<T>.GetUninitializedProperty("Theme")
would throw at startup - forcing AOT apps to carry
<TrimmerRootAssembly Include="Terminal.Gui" />, adding ~1.5 MB per binary.

Terminal.Gui's own host types are now declared statically in a new
internal ConfigPropertyHostTypes registry and rooted via
[DynamicDependency(PublicProperties, typeof(X))] so the trimmer preserves
the properties the initializer depends on. The AppDomain scan is retained
as a JIT-only supplement (gated on RuntimeFeature.IsDynamicCodeSupported)
so test suites and external consumers with their own [ConfigurationProperty]
properties continue to be discovered automatically in non-AOT builds.

Examples/NativeAot drops the TrimmerRootAssembly workaround.
A new parallelizable unit test asserts the static registry matches the set
of types reflected at runtime, so drift between the list and new
[ConfigurationProperty] additions is caught at build time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@harder harder requested a review from tig as a code owner April 24, 2026 19:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Makes ConfigurationManager initialization trim-/AOT-safe by avoiding a reflection scan of all loaded assemblies in AOT scenarios, eliminating the need for TrimmerRootAssembly in NativeAOT consumers.

Changes:

  • Added a static, trimmer-rooted registry of Terminal.Gui types that host [ConfigurationProperty] properties.
  • Updated ConfigProperty.Initialize() to register the static list first and only perform the AppDomain assembly scan when dynamic code is supported.
  • Removed the NativeAOT example’s TrimmerRootAssembly workaround and added a guard-rail unit test to prevent registry drift.

Reviewed changes

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

File Description
Tests/UnitTestsParallelizable/Configuration/ConfigPropertyHostTypesTests.cs Adds a drift-detection test to ensure the static host-type registry stays in sync with attribute usage.
Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs Introduces the static registry of [ConfigurationProperty] host types with DynamicDependency rooting for trimming.
Terminal.Gui/Configuration/ConfigProperty.cs Registers the static host list unconditionally; performs reflection scanning only when RuntimeFeature.IsDynamicCodeSupported is true.
Examples/NativeAot/NativeAot.csproj Drops TrimmerRootAssembly Include="Terminal.Gui" now that config init is intended to be trim-safe.

Comment thread Terminal.Gui/Configuration/ConfigProperty.cs Outdated
Comment thread Tests/UnitTestsParallelizable/Configuration/ConfigPropertyHostTypesTests.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comment thread Tests/UnitTestsParallelizable/Configuration/ConfigPropertyHostTypesTests.cs Outdated
Comment thread Terminal.Gui/Configuration/ConfigProperty.cs Outdated
Comment thread Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

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.

ConfigurationManager still requires TrimmerRootAssembly for AOT

3 participants