Skip to content

[Android] Hosting: Trim disabled MAUI Aspire support - #38616

Merged
kubaflo merged 6 commits into
net11.0from
simonrozsival-trim-maui-aspire-regex
Sep 22, 2026
Merged

kubaflo merged 6 commits into
net11.0from
simonrozsival-trim-maui-aspire-regex

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Dependency

This PR is stacked on #38630, which separately preserves handler constructors referenced through ElementHandlerAttribute<THandler> when trimming is enabled.

Root Cause

Microsoft.Maui.RuntimeFeature.EnableMauiAspire uses the runtime's [FeatureSwitchDefinition] pattern, but normal optimized Android builds previously left _EnableMauiAspire unset. As a result, the linker did not substitute the disabled feature and the guarded environment-variable/dev-tunnel implementation—including the source-generated LocalhostRegex—survived publishing.

Description of Change

  • Resolve _EnableMauiAspire and add its RuntimeHostConfigurationOption globally from the Core package during MSBuild evaluation, so both Controls and UseMauiCore consumers receive the switch without target-order dependencies.
  • Keep Aspire enabled for non-optimized builds and substitute it away from optimized builds.
  • Preserve validation of explicitly configured _EnableMauiAspire values.
  • Leave Aspire-enabled builds unchanged.
  • Add focused MSBuild coverage for default disabled/enabled behavior, explicit optimized-build warnings, and the runtime option's Value/Trim metadata.
  • Add URL rewrite coverage for dev-tunnel behavior.
  • Update the feature-switch documentation for regular optimized builds and the validation opt-out.

Release Targeting

This stack targets net11.0. If it cannot be accepted after the .NET 11 RC2 snap, it may need to move to .NET 12.

Android Release Measurements

A fresh RC2 full-package build and stock .NET 11 Android arm64 Release partial-trim comparison of baseline 2eed04f873 against the complete stack (00267ae424 + 3bde0d5ec3) plus the XAML/Shadow changes produced:

Output Baseline Patched stack Delta
Signed APK 21,428,303 bytes 21,354,575 bytes -73,728 bytes
linked Microsoft.Maui.dll 828,416 bytes 820,224 bytes -8,192 bytes
linked Microsoft.Maui.Controls.dll 1,533,952 bytes 1,513,472 bytes -20,480 bytes
linked Microsoft.Maui.Controls.Xaml.dll 136,704 bytes 27,648 bytes -109,056 bytes

In the non-Aspire output, ConfigureEnvironmentVariables is reduced to a return-only stub and LocalhostRegex is absent. An identical Aspire-enabled publish retains the implementation and regex.

System.Text.RegularExpressions.dll remains because the sample still reaches regex through Microsoft.Maui.Controls.ShadowTypeConverter; System.Private.Xml.dll also contains a generated LanguageRegex. This PR removes the MAUI Aspire regex root but does not eliminate the framework assembly entirely.

Validation

  • DOTNET_ROLL_FORWARD=Major dotnet test src/Core/tests/UnitTests/Core.UnitTests.csproj --no-restore --filter FullyQualifiedName~HostBuilderAppConfigurationTests --logger 'console;verbosity=minimal' -m:1 -p:PublicApiType=Validate — 7 passed on net11.0.
  • DOTNET_ROLL_FORWARD=Major dotnet test src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj --no-restore --filter 'FullyQualifiedName~MauiAspireFeatureSwitchMatchesOptimization|FullyQualifiedName~ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild' --logger 'console;verbosity=minimal' -m:1 -p:PublicApiType=Validate — 5 passed on net11.0.
  • Fresh RC2 package builds and arm64 partial publishes succeeded for baseline and the final stack.
  • The complete two-PR stack passed the combined preview7 XAML + Shadow + Aspire contract validation (8/8).
  • Linked CheckBoxHandler retains its public parameterless constructor, platform creation, connect, and disconnect methods.
  • A clean physical-device cold launch remained alive with PID 7613 and no TypeLoadException.

Reusable validation artifacts are at /Users/simon/.copilot/session-state/6e13df7f-1302-47fa-b0fa-6d104a1dc0a8/files/final-stack-validation.

Issues Fixed

N/A

@simonrozsival
simonrozsival deployed to copilot-pat-pool September 17, 2026 07:42 — with GitHub Actions Active
Copilot AI lite review requested due to automatic review settings September 17, 2026 07:42
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 38616

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 38616"

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

@simonrozsival
simonrozsival deployed to copilot-pat-pool September 17, 2026 07:42 — with GitHub Actions Active
@simonrozsival simonrozsival changed the title Trim disabled MAUI Aspire support from Android apps [Android] Hosting: Trim disabled MAUI Aspire support Sep 17, 2026
@simonrozsival
simonrozsival deployed to copilot-pat-pool September 17, 2026 07:43 — with GitHub Actions Active
@simonrozsival
simonrozsival deployed to copilot-pat-pool September 17, 2026 07:46 — with GitHub Actions Active
@simonrozsival
simonrozsival deployed to copilot-pat-pool September 17, 2026 07:47 — with GitHub Actions Active
@simonrozsival
simonrozsival deployed to copilot-pat-pool September 17, 2026 07:47 — with GitHub Actions Active

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.

Copilot review overview

🟡 Changes recommended

Test coverage gaps and outdated documentation remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Low severity

Open (1)
What changed in this PR

This PR updates MAUI Aspire feature-switch handling so disabled Aspire code can be trimmed from Android partial-trimmed Release builds.

Changes:

  • Configures Aspire switches before trim capture.
  • Applies conditional Microsoft.Maui trimmability.
  • Adds MSBuild and dev-tunnel URL rewrite coverage.
File Summary
src/​Core/​tests/​UnitTests/​Hosting/​HostBuilderAppConfigurationTests.cs Adds dev-tunnel URL rewrite tests.
src/​Core/​src/​Hosting/​Dispatching/​AppHostBuilderExtensions.cs Exposes URL rewriting for testing.
src/​Controls/​tests/​Xaml.UnitTests/​MSBuild/​MSBuildTests.cs Adds Aspire feature-switch and trimming configuration coverage.
src/​Controls/​src/​Build.Tasks/​nuget/​buildTransitive/​netstandard2.0/​Microsoft.Maui.Controls.targets Configures feature switches and conditional trimming behavior.

Copilot AI review requested due to automatic review settings September 17, 2026 07:56
@simonrozsival
simonrozsival changed the base branch from main to net11.0 September 17, 2026 08:02
@simonrozsival
simonrozsival force-pushed the simonrozsival-trim-maui-aspire-regex branch from f7b4c4f to 7ab68c3 Compare September 17, 2026 08:03
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Sep 17, 2026
Copilot stopped reviewing on behalf of simonrozsival due to an error September 17, 2026 08:17

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.

Comment thread src/Core/src/Hosting/Dispatching/AppHostBuilderExtensions.cs
Copilot AI review requested due to automatic review settings September 17, 2026 08:19

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.

Copilot review overview

🟡 Changes recommended

Unresolved moderate issues remain in Core-only switch wiring and test coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 2 Medium severity · 2 Low severity

Open (5)

Copilot AI review requested due to automatic review settings September 17, 2026 08:38
@kubaflo

This comment has been minimized.

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.

Copilot review overview

🔵 Needs a closer look

The Aspire switch wiring has two unresolved build-behavior issues, and its test does not verify required metadata.

Review effort: Lite
Findings: 1 High severity · 2 Medium severity · 2 Low severity

Open (5)
Previously missed (1)

In code that hasn't changed since last review

Low severity Assert the runtime option metadata in the MSBuild test

src/​Controls/​tests/​Xaml.UnitTests/​MSBuild/​MSBuildTests.cs:456

The test currently formats @(RuntimeHostConfigurationOption) as item identities only, so it verifies presence but not the Value or Trim="true" metadata that makes feature-switch substitution work. A regression that emits the wrong value or omits trimming would still pass. Include %(Value) and %(Trim) in the diagnostic message and assert the expected metadata.

@github-actions

Copy link
Copy Markdown
Contributor

Note

🔍 /review started

AzDO build 15376381 is running for android.

The s/agent-review-in-progress label stays on this PR while the run is active. The final recommendation and outcome labels are posted only after Gate, expert review, and Deep UI tests finish.

MauiBot

This comment was marked as outdated.

@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) and removed s/agent-review-in-progress AI review is currently running for this PR labels Sep 18, 2026
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Sep 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Note

🔍 /review started

AzDO build 15379547 is running for android.

The s/agent-review-in-progress label stays on this PR while the run is active. The final recommendation and outcome labels are posted only after Gate, expert review, and Deep UI tests finish.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@simonrozsival — new AI review results are available based on commit eaaf53c.

Gate Passed Confidence Low Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: net11.0 · Merge base: d73fb56e

✅ Verified (new API / feature) — this PR adds new API and a test that references it in the same project, so reverting the fix un-compiles the test: there is no valid "fails without the fix" baseline to establish (a compile-coupled baseline). The gate instead verified the fix by a clean build + pass with the fix, so this is a real PASS rather than a non-committal INCONCLUSIVE.

Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 HostBuilderAppConfigurationTests HostBuilderAppConfigurationTests 🛠️ BUILD ERROR ✅ PASS — 60s
📄 MSBuildTests MSBuildTests ✅ FAIL — 288s ✅ PASS — 386s
🔴 Without fix — 🧪 HostBuilderAppConfigurationTests: 🛠️ BUILD ERROR · 68s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/src/Core/tests/UnitTests/Hosting/HostBuilderAppConfigurationTests.cs(77,52): error CS0117: 'AppHostBuilderExtensions' does not contain a definition for 'ReplaceLocalhost' [/home/vsts/work/1/s/src/Core/tests/UnitTests/Core.UnitTests.csproj]
🟢 With fix — 🧪 HostBuilderAppConfigurationTests: PASS ✅ · 60s

(no coded error found; showing last 1200 chars)

v11.0)
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 11.0.0-rc.2.26467.104)
[xUnit.net 00:00:00.16]   Discovering: Microsoft.Maui.UnitTests
[xUnit.net 00:00:00.60]   Discovered:  Microsoft.Maui.UnitTests
[xUnit.net 00:00:00.61]   Starting:    Microsoft.Maui.UnitTests
[xUnit.net 00:00:00.78]   Finished:    Microsoft.Maui.UnitTests
  Passed AppConfigurationOverwritesValues [72 ms]
  Passed ConfigureServicesCanUseConfig [< 1 ms]
  Passed ReplacesLocalhostWithDevTunnel(uri: "https://example.com:5003/path", expected: "https://example.com:5003/path") [9 ms]
  Passed ReplacesLocalhostWithDevTunnel(uri: "HTTPS://LOCALHOST:5002/path?value=1", expected: "HTTPS://test-tunnel-5002.devtunnels.ms/path?value="···) [3 ms]
  Passed ReplacesLocalhostWithDevTunnel(uri: "http://localhost:5000", expected: "http://test-tunnel-5000.devtunnels.ms") [< 1 ms]
  Passed ReplacesLocalhostWithDevTunnel(uri: "https://localhost:5001/path", expected: "https://test-tunnel-5001.devtunnels.ms/path") [< 1 ms]
  Passed CanConfigureAppConfiguration [1 ms]
Test Run Successful.
Total tests: 7
     Passed: 7
 Total time: 1.8452 Seconds
🔴 Without fix — 📄 MSBuildTests: FAIL ✅ · 288s

Error-relevant lines (filtered from the build log):

     at Microsoft.Maui.Controls.MSBuild.UnitTests.MSBuildTests.MauiAspireFeatureSwitchMatchesOptimization(Boolean optimize, String explicitValue, String expected) in /_/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs:line 461
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
     at Microsoft.Maui.Controls.MSBuild.UnitTests.MSBuildTests.MauiAspireFeatureSwitchMatchesOptimization(Boolean optimize, String explicitValue, String expected) in /_/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs:line 462
   at InvokeStub_MSBuildTests.MauiAspireFeatureSwitchMatchesOptimization(Object, Span`1)
   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
[xUnit.net 00:00:30.09]              1>/home/vsts/work/1/s/artifacts/bin/Controls.Xaml.UnitTests/Debug/net11.0/temp/ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild/test.csproj : error MSB4057: The target "_MauiValidateAspireConfiguration" does not exist in the project.
[xUnit.net 00:00:30.09]         Build FAILED.
[xUnit.net 00:00:30.09]                  /home/vsts/work/1/s/artifacts/bin/Controls.Xaml.UnitTests/Debug/net11.0/temp/ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild/test.csproj : error MSB4057: The target "_MauiValidateAspireConfiguration" does not exist in the project.
     at Microsoft.Maui.Controls.MSBuild.UnitTests.MSBuildTests.Build(String projectFile, String target, String verbosity, String additionalArgs, Boolean shouldSucceed, String command) in /_/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs:line 265
   at Microsoft.Maui.Controls.MSBuild.UnitTests.MSBuildTests.ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild(String explicitValue) in /_/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs:line 483
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
      1>/home/vsts/work/1/s/artifacts/bin/Controls.Xaml.UnitTests/Debug/net11.0/temp/ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild/test.csproj : error MSB4057: The target "_MauiValidateAspireConfiguration" does not exist in the project.
 Build FAILED.
          /home/vsts/work/1/s/artifacts/bin/Controls.Xaml.UnitTests/Debug/net11.0/temp/ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild/test.csproj : error MSB4057: The target "_MauiValidateAspireConfiguration" does not exist in the project.
[xUnit.net 00:00:33.64]              1>/home/vsts/work/1/s/artifacts/bin/Controls.Xaml.UnitTests/Debug/net11.0/temp/ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild/test.csproj : error MSB4057: The target "_MauiValidateAspireConfiguration" does not exist in the project.
[xUnit.net 00:00:33.64]         Build FAILED.
[xUnit.net 00:00:33.64]                  /home/vsts/work/1/s/artifacts/bin/Controls.Xaml.UnitTests/Debug/net11.0/temp/ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild/test.csproj : error MSB4057: The target "_MauiValidateAspireConfiguration" does not exist in the project.
   at InvokeStub_MSBuildTests.ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild(Object, Span`1)
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
🟢 With fix — 📄 MSBuildTests: PASS ✅ · 386s

(no coded error found; showing last 1200 chars)

ntifier: "ios", shouldIncludeLegacyFile: True) [5 s]
  Passed SingleProject_CodesignEntitlementsRespected [3 s]
  Passed SingleProject_NeutralTfmCanActivateBuiltInBackendByIdentity [4 s]
  Passed SingleProject_DefaultEntitlementsUsedWhenNoCustomSet [3 s]
  Passed SingleProject_BuiltInPlatformFoldersCanBeExtended [4 s]
  Passed SingleProject_NeutralTfmActivatesBackendByIdentity [4 s]
  Passed DesignTimeBuild [8 s]
  Passed SingleProject_BareUnresolvedActivationValueUsesLegacySharedFolderBehavior(targetPlatformIdentifier: "ios") [4 s]
  Passed SingleProject_BareUnresolvedActivationValueUsesLegacySharedFolderBehavior(targetPlatformIdentifier: "") [4 s]
  Passed SingleProject_NonPlatformBuildExcludesPlatformSpecificFoldersButKeepsSharedFolder [4 s]
  Passed RandomEmbeddedResource [4 s]
  Passed SingleProject_RecognizedTfmIgnoresNeutralBackendSelector [3 s]
  Passed TargetsShouldSkip [9 s]
[xUnit.net 00:05:28.58]     TouchXamlFile [SKIP]
[xUnit.net 00:05:28.58]       source gen changes
[xUnit.net 00:05:28.60]   Finished:    Microsoft.Maui.Controls.Xaml.UnitTests
  Skipped TouchXamlFile [1 ms]
Test Run Successful.
Total tests: 76
     Passed: 74
    Skipped: 2
 Total time: 5.4871 Minutes

⚠️ Failure Details

  • 🛠️ HostBuilderAppConfigurationTests without fix: build failed before tests could run
    • /home/vsts/work/1/s/src/Core/tests/UnitTests/Hosting/HostBuilderAppConfigurationTests.cs(77,52): error CS0117: 'AppHostBuilderExtensions' does not contain a definition for 'ReplaceLocalhost' [/home/vs...
📁 Fix files reverted (3 files)
  • src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.targets
  • src/Core/src/Hosting/Dispatching/AppHostBuilderExtensions.cs
  • src/Core/src/nuget/buildTransitive/Microsoft.Maui.Core.Before.targets

📋 Pre-Flight — Context & Validation

Issue: N/A - no linked issue
PR: #38616 - [Android] Hosting: Trim disabled MAUI Aspire support
Platforms Affected: Android is the requested validation platform; the Core build-transitive switch configuration applies to MAUI Core and Controls consumers on all platforms
Files Changed: 3 implementation, 2 test, 1 documentation

Key Findings

  • Optimized builds previously left _EnableMauiAspire unset outside the Controls full-trim/AOT property group, so the FeatureSwitchDefinition could not substitute RuntimeFeature.EnableMauiAspire=false and trim the guarded environment-variable/dev-tunnel path.
  • The PR moves property resolution, runtime-host option emission, and explicit-override validation to Microsoft.Maui.Core.Before.targets, preserving non-optimized and explicitly enabled behavior while covering UseMauiCore consumers.
  • The PR is stacked on #38630, but its six-file diff is independently focused on Aspire feature-switch configuration, URL-rewrite coverage, and documentation.
  • Gate verification already passed the focused HostBuilderAppConfigurationTests and MSBuildTests; no gate verification was rerun.

Code Review Summary

Verdict: SKIPPED
Confidence: N/A
Errors: 0 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • Dedicated expert review is intentionally deferred to STEP 5b under the execution contract; no separate expert-review agent was launched in STEP 5a.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #38616 Configure and validate the Aspire feature switch globally from Core build-transitive evaluation ✅ PASSED (Gate) 3 implementation files Original PR; focused Core and MSBuild tests passed

🔬 Code Review — Deep Analysis

Code Review — PR #38616

Independent Assessment

What this changes: The PR moves _EnableMauiAspire defaulting and RuntimeHostConfigurationOption emission from the Controls-only linker-preparation target into Microsoft.Maui.Core.Before.targets. This gives both Controls and UseMauiCore consumers an explicit feature-switch value during evaluation: true when Optimize is not true, false when Optimize is true, while preserving an explicitly supplied value. It also relocates MA002 validation, exposes ReplaceLocalhost internally for focused tests, and updates documentation.

Inferred motivation: Optimized Android builds that do not use full trimming or AOT previously left the runtime feature switch unset, so the linker could not substitute RuntimeFeature.EnableMauiAspire=false and remove the guarded environment-variable and dev-tunnel code.

Approach Assessment

The approach is sound. Core build-transitive evaluation is the appropriate shared producer because it does not depend on Controls target execution and reaches Core-only consumers. Capturing whether _EnableMauiAspire was already populated before applying defaults preserves the distinction needed by MA002. No simpler change in the former Controls target would provide equivalent consumer coverage without retaining the ordering dependency.

Trim/AOT Evidence Chain

Link Source evidence Reachability implication
Build-time value Microsoft.Maui.Core.Before.targets resolves _EnableMauiAspire and emits Microsoft.Maui.RuntimeFeature.EnableMauiAspire with Trim="true". Optimized builds provide the linker an explicit false substitution; non-optimized or explicitly enabled builds retain true.
Runtime switch RuntimeFeature.EnableMauiAspire carries FeatureSwitchDefinition and reads the matching AppContext key. The runtime property is the recognized substitution point rather than merely relying on its ordinary default of true.
Guarded consumer ConfigureEnvironmentVariables returns before environment propagation and dev-tunnel rewriting when the switch is false. The Aspire-specific branch, including LocalhostRegex, becomes unreachable in the disabled trim configuration.
Tests MauiAspireFeatureSwitchMatchesOptimization checks the effective property plus runtime option Value and Trim; ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild checks explicit-value validation. The focused tests cover default disabled/enabled behavior, explicit override, metadata, and warning behavior.

MSBuild Evaluation-Order Assessment

The relocation is ordering-correct for the tested package import contract: configuration and item emission occur together in Core's .Before.targets, before downstream targets consume RuntimeHostConfigurationOption. The explicitness marker is captured before either default assignment mutates _EnableMauiAspire, and the target-time warning reads that immutable marker. No actionable ordering defect was identified.

Findings

No actionable inline findings. inline-findings.json contains an empty JSON array.

Failure-Mode Probing

  • Core-only consumer: Core's build-transitive import now emits the switch without requiring Controls targets.
  • Regular optimized build (TrimMode=partial): The value resolves to false, and the runtime option remains present with Trim=true.
  • Explicit true or false in an optimized build: The authored value is preserved and MA002 is emitted unless validation is intentionally disabled.
  • Non-optimized build: The default remains true, preserving Aspire environment propagation.
  • No Aspire usage at runtime: The change only supplies a configuration value; disabled builds return at the existing runtime guard and introduce no new startup state or subscriptions.

External Output Contract

Not applicable.

Verdict: NEEDS_DISCUSSION

Confidence: low
Summary: The expert review found the submitted implementation correct and found no code-level error, warning, or discussion item. Its formal verdict is capped only because current required-check state was unavailable/undetermined to the reviewer; the trusted workflow Gate supplied for this comparison independently passed.


🛠️ Try-Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 gpt-5.3-codex try-fix Compute consumer-local Aspire defaults at execution time instead of snapshotting explicitness during evaluation ⚠️ BLOCKED 0 files Baseline protocol rejected unrelated pre-existing tracked changes; no implementation or tests ran
2 gpt-5.6-sol try-fix Use an atomic <Choose> decision record containing the effective Aspire value and whether it was explicit ✅ PASS — 5/5 primary, 7/7 regression 3 files One pre-test MSBuild ordering flaw was corrected; final inline self-review was clean
PR PR #38616 Configure and validate the Aspire feature switch globally from Core build-transitive evaluation ✅ PASSED (Gate) 3 implementation files Original PR

Candidate Details

Candidate 1: Proposed consumer-local, execution-time defaulting and warning validation after optimization properties settle. It intentionally avoided the PR's Core evaluation-time explicitness snapshot, but EstablishBrokenBaseline.ps1 rejected the primary worktree's unrelated pre-existing tracked changes. No files were edited and neither focused test command ran. Full record: try-fix-1/content.md.

Candidate 2: Used an evaluation-time <Choose> to atomically select _MauiAspireConfigurationValue and _MauiAspireConfigurationSource, then projected the value into _EnableMauiAspire and RuntimeHostConfigurationOption. This avoids both the PR's mutable-property snapshot and Candidate 1's target-time consumer mutation. The focused Aspire MSBuild tests passed 5/5 and HostBuilderAppConfigurationTests passed 7/7 after one permitted correction for the clean detached worktree's missing build-task link. The final candidate had zero inline self-review findings and restoration reported Restored True. Full record and diff: try-fix-2/content.md.

Cross-Pollination

Model Round New Ideas? Details
N/A N/A N/A Skipped by STEP 5a execution contract

Exhausted: Yes — the two-candidate STEP 5a cap was reached; no cross-pollination or follow-up agents were permitted.
Selected Fix: PR's fix — Candidate 2 is a valid passing alternative, but the PR achieves the same tested defaults, explicit-override provenance, warning behavior, and Core/Controls coverage with a smaller and more conventional conditional-property implementation than the 38-line <Choose> decision record.


🏁 Report — Final Recommendation

✅ Final Recommendation: APPROVE

Winning candidate

Winner: pr

The submitted PR is the strongest candidate. It fixes the missing Aspire feature-switch substitution at the shared Core build-transitive layer, preserves explicit overrides and MA002 provenance, and does so with a smaller conventional conditional-property implementation than the passing alternative. The trusted Gate passed, the expert reviewer found no actionable code findings, and the regression cross-reference was clean.

Comparative ranking

Rank Candidate Implementation Validation Expert assessment Result
1 pr Resolve _EnableMauiAspire in Microsoft.Maui.Core.Before.targets, emit the runtime option globally, and retain explicit-value validation. ✅ Trusted Gate passed: the focused behavior failed without the fix and passed with it; submitted Core and MSBuild coverage passed. No errors, warnings, suggestions, or code-level discussion items. Winner
2 pr-plus-reviewer Byte-for-byte equivalent to pr; the expert supplied no actionable feedback, so reviewer.patch is empty. ✅ No separate run required because no source changed; inherits the same trusted Gate evidence as pr. No improvement over the submitted implementation. Valid tie, ranked below the already-submitted candidate
3 try-fix-2 Use a <Choose> decision record to select an effective value and Explicit/Default source, then project those into the compatibility property and runtime item. ✅ 5/5 primary Aspire MSBuild cases and 7/7 Core regression cases passed after the documented one-time harness correction. Correct final implementation with zero self-review findings. Valid alternative, but more verbose with no demonstrated correctness advantage
4 try-fix-1 Proposed consumer-local target-time defaulting after optimization properties settle. ⚠️ Blocked before implementation or testing by the baseline protocol; produced no code diff. No implementation available to assess empirically. Lowest-ranked because regression behavior was not validated

Why pr wins

  • Correct shared scope: Core's build-transitive .Before.targets reaches both Controls and UseMauiCore consumers and removes the former Controls-target ordering dependency.
  • Sound trim contract: RuntimeFeature.EnableMauiAspire is a FeatureSwitchDefinition; the emitted RuntimeHostConfigurationOption carries the resolved value with Trim="true", and the existing runtime guard isolates the removable environment/dev-tunnel path.
  • Preserved behavior: Non-optimized builds remain enabled, optimized builds default disabled, explicit values are retained, and explicitly configured optimized builds still receive MA002 unless validation is intentionally disabled.
  • Focused coverage: Tests assert effective values, runtime-option Value/Trim metadata, explicit warning behavior, and dev-tunnel URL rewriting.
  • Least complexity: try-fix-2 reaches the same tested contract through a 38-line <Choose> record and two extra intermediate properties. That separation is defensible, but no failure in the PR's capture-before-default pattern was found, so the additional mechanism is not justified.

Expert review reconciliation

The expert review's formal NEEDS_DISCUSSION/low-confidence label was caused solely by its inability to determine current required-check status. It identified no code defect or unresolved design question and wrote an empty inline-findings.json. For this comparison, the caller supplied a trusted Gate result of PASSED, satisfying the approval gate for the raw PR candidate.

Candidate artifacts

  • pr-plus-reviewer/reviewer.patch is empty because no reviewer change was warranted.
  • pr-plus-reviewer/candidate.patch contains the submitted six-file candidate diff against the pipeline PR base and excludes the trusted infrastructure overlay.
  • No candidate failed an executed regression test. The blocked, untested try-fix-1 is ranked below all passing candidates as required.

📱 UI Tests — Button,Label,Layout

Detected UI test categories: Button,Label,Layout

✅ Deep UI tests — 422 passed, 0 failed, 7 skipped across 3 categories on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Button 104/106 (2 skipped) ✓ —
Label 123/125 (2 skipped) ✓ —
Layout 195/198 (3 skipped) ✓ —
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@MauiBot MauiBot added s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) and removed s/agent-review-in-progress AI review is currently running for this PR labels Sep 18, 2026
kubaflo pushed a commit that referenced this pull request Sep 18, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

`ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a
shadow string. That reference contributes to retaining
`System.Text.RegularExpressions` and additional regex implementation
code in trimmed MAUI Android apps.

### Broader goal and related work

This PR is one step toward removing the roughly 300 KB linked
`System.Text.RegularExpressions` assembly from MAUI apps that do not use
regular expressions in their own application logic. Removing the
`ShadowTypeConverter` root is not sufficient by itself because other
framework and dependency roots still retain regex code.

Related work toward that goal includes:

- #38616 trims disabled MAUI Aspire support, including
`Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire
feature switch is off.
- dotnet/android#12813 avoids unnecessary XML marshalling references in
trimmable type-map dispatch assemblies. This helps remove XML assemblies
where they are not otherwise needed; `System.Private.Xml` contains a
generated `LanguageRegex` implementation that can independently retain
regex code.

Additional roots may still need to be removed or made trimmable before
`System.Text.RegularExpressions` disappears entirely from a given app.
For example, the measured sample-content app also reaches regex through
`CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`.

### Description of Change

Replaces the runtime `Regex` with an allocation-conscious inline
tokenizer/parser.

The parser intentionally preserves the existing unanchored
`Regex.Matches` behavior, including supported color forms,
invariant-culture decimal/scientific numbers, Unicode whitespace,
ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for
incomplete decimals or exponents.

Token boundaries are stored in a stack-allocated `Span<Part>`, and only
matched tokens are materialized as strings for the existing color and
numeric parsers.

The project now also bans the `System.Text.RegularExpressions` namespace
through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this
dependency from being reintroduced.

### Performance considerations

Vectorizing the character scans was considered but intentionally not
implemented. Shadow strings are normally short (roughly tens of
characters) and whitespace runs are generally zero to two characters, so
SIMD setup and branching are unlikely to beat the straightforward scalar
loops. A vectorized path would also require separate `netstandard`
fallbacks and careful tables to preserve the old regex's Unicode `\s`
and `\d` behavior, adding disproportionate complexity without
demonstrated benefit.

### Tests

- Added focused coverage for every supported color syntax,
offsets/radius/opacity, whitespace, malformed values, invariant culture,
and legacy regex edge cases.
- `ShadowTests`: 85 passed.
- `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and
`net10.0`.
- Verified an intentional `System.Text.RegularExpressions.Regex` usage
in `Controls.Core` fails with RS0030.

### Android size measurement

Published identical stock `.NET 11 preview 7` `dotnet new maui
--sample-content` apps in Release with `TrimMode=partial`, replacing
only the `Microsoft.Maui.Controls.dll` input in the patched build.

| Artifact | Baseline | Patched | Delta |
|---|---:|---:|---:|
| Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** |
| Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** |
| Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B |
1,495,040 B | **-512 B** |
| Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B |
330,752 B | **-3,584 B** |
| arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** |
| x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** |

`System.Text.RegularExpressions` remains retained by other roots in this
sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`),
`System.Data.Common`, and `System.Private.Xml`. This change removes the
`Microsoft.Maui.Controls` reference and reduces the linked regex
implementation, but does not eliminate the assembly from this app.

### Issues Fixed

None.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Sep 21, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

`ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a
shadow string. That reference contributes to retaining
`System.Text.RegularExpressions` and additional regex implementation
code in trimmed MAUI Android apps.

### Broader goal and related work

This PR is one step toward removing the roughly 300 KB linked
`System.Text.RegularExpressions` assembly from MAUI apps that do not use
regular expressions in their own application logic. Removing the
`ShadowTypeConverter` root is not sufficient by itself because other
framework and dependency roots still retain regex code.

Related work toward that goal includes:

- #38616 trims disabled MAUI Aspire support, including
`Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire
feature switch is off.
- dotnet/android#12813 avoids unnecessary XML marshalling references in
trimmable type-map dispatch assemblies. This helps remove XML assemblies
where they are not otherwise needed; `System.Private.Xml` contains a
generated `LanguageRegex` implementation that can independently retain
regex code.

Additional roots may still need to be removed or made trimmable before
`System.Text.RegularExpressions` disappears entirely from a given app.
For example, the measured sample-content app also reaches regex through
`CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`.

### Description of Change

Replaces the runtime `Regex` with an allocation-conscious inline
tokenizer/parser.

The parser intentionally preserves the existing unanchored
`Regex.Matches` behavior, including supported color forms,
invariant-culture decimal/scientific numbers, Unicode whitespace,
ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for
incomplete decimals or exponents.

Token boundaries are stored in a stack-allocated `Span<Part>`, and only
matched tokens are materialized as strings for the existing color and
numeric parsers.

The project now also bans the `System.Text.RegularExpressions` namespace
through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this
dependency from being reintroduced.

### Performance considerations

Vectorizing the character scans was considered but intentionally not
implemented. Shadow strings are normally short (roughly tens of
characters) and whitespace runs are generally zero to two characters, so
SIMD setup and branching are unlikely to beat the straightforward scalar
loops. A vectorized path would also require separate `netstandard`
fallbacks and careful tables to preserve the old regex's Unicode `\s`
and `\d` behavior, adding disproportionate complexity without
demonstrated benefit.

### Tests

- Added focused coverage for every supported color syntax,
offsets/radius/opacity, whitespace, malformed values, invariant culture,
and legacy regex edge cases.
- `ShadowTests`: 85 passed.
- `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and
`net10.0`.
- Verified an intentional `System.Text.RegularExpressions.Regex` usage
in `Controls.Core` fails with RS0030.

### Android size measurement

Published identical stock `.NET 11 preview 7` `dotnet new maui
--sample-content` apps in Release with `TrimMode=partial`, replacing
only the `Microsoft.Maui.Controls.dll` input in the patched build.

| Artifact | Baseline | Patched | Delta |
|---|---:|---:|---:|
| Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** |
| Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** |
| Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B |
1,495,040 B | **-512 B** |
| Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B |
330,752 B | **-3,584 B** |
| arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** |
| x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** |

`System.Text.RegularExpressions` remains retained by other roots in this
sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`),
`System.Data.Common`, and `System.Private.Xml`. This change removes the
`Microsoft.Maui.Controls` reference and reduces the linked regex
implementation, but does not eliminate the assembly from this app.

### Issues Fixed

None.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
simonrozsival added a commit to dotnet/android that referenced this pull request Sep 22, 2026
## Summary

- create XML marshalling metadata references lazily in generated `[Export]` dispatch assemblies
- avoid adding `System.Xml.ReaderWriter` to a generated type-map assembly when exported signatures do not use XML parser mappings
- preserve the existing XML marshalling references when `XmlPullParser` or `XmlResourceParser` mappings are used

## APK size and combined MAUI validation

The PR is draft because the stock MAUI `sample-content` application does not currently demonstrate an APK-size or packaged-assembly reduction from this change.

The first stock .NET 11 Android CoreCLR Release comparison produced an identical signed APK before and after this change:

| Build | Signed APK size |
|---|---:|
| Baseline | 23,300,175 bytes |
| Patched | 23,300,175 bytes |
| Difference | **0 bytes** |

Three independent MAUI trimming fixes were then applied together:

- dotnet/maui#38613 trims unused runtime XAML loader/parser code
- dotnet/maui#38615 removes `ShadowTypeConverter`'s regex dependency
- dotnet/maui#38616 trims disabled MAUI Aspire support and its generated localhost regex

The combined MAUI changes reduced the sample-content signed APK by 81,920 bytes, removed `XamlLoader`, `XamlParser`, and `LocalhostRegex`, and reduced the retained XML/regex implementations. However, `System.Xml.ReaderWriter` remains rooted directly by source-generated XAML line-info calls in the application, and `System.Private.Xml` remains transitively and through additional framework/control references.

To exercise this PR's actual code path, the identical combined-MAUI input was then published twice with the trimmable Android type map explicitly enabled:

```sh
dotnet publish CombinedTrimApp.csproj \
  -c Release -f net11.0-android -r android-arm64 \
  -p:AndroidKeyStore=false \
  -p:PublishReadyToRun=false \
  -p:TrimMode=partial \
  -p:_AndroidTypeMapImplementation=trimmable \
  -p:AndroidPackageFormats=apk
```

| Output | Baseline generator | Patched generator | Difference |
|---|---:|---:|---:|
| Signed APK | 21,248,079 B | 21,248,079 B | **0 B** |
| Unsigned APK | 21,118,994 B | 21,119,021 B | +27 B packaging noise |
| Generated type-map DLLs | 11,133,952 B | 11,133,952 B | **0 B** |
| `System.Xml.ReaderWriter.dll` | 5,120 B | 5,120 B | **0 B** |
| `System.Private.Xml.dll` | 381,440 B | 381,440 B | **0 B** |

All 86 generated type-map DLLs were byte-identical, and none had a `System.Xml.ReaderWriter` assembly reference. The sample-content app and its dependencies do not contain a non-XML `[Export]` dispatcher that triggers the bug fixed here. Its remaining `System.Xml.ReaderWriter` reference comes from generated XAML `InitializeComponent` line-info services (`System.Xml.IXmlLineInfo`), outside the Android type-map generator.

Therefore this change is valid generator cleanup and is covered by focused tests, but it does **not** currently reduce the stock MAUI sample-content APK or remove its XML assemblies. The benefit applies only to an app/library that generates a non-XML `[Export]` dispatcher and has no independent XML roots.

## Tests

- `dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj --no-restore --nologo -v:minimal`
- targeted `TypeMapAssemblyGeneratorTests.Generate_Export*` coverage
@kubaflo
kubaflo merged commit 9c0b29f into net11.0 Sep 22, 2026
32 of 37 checks passed
@kubaflo
kubaflo deleted the simonrozsival-trim-maui-aspire-regex branch September 22, 2026 22:02
@github-actions github-actions Bot added this to the .NET 11.0-rc2 milestone Sep 22, 2026
kubaflo added a commit that referenced this pull request Sep 25, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fix the missing Helix unit-test fixture and the independent device-test
failures uncovered while validating this PR on `net11.0`.

#### Helix correlation payload

The Aspire feature-switch tests added by #38616 import
`src/Core/src/nuget/buildTransitive/Microsoft.Maui.Core.Before.targets`,
but `PrepareMauiHelixPayload` did not stage that file. Both test methods
fail before their assertions run: three cases of
`MauiAspireFeatureSwitchMatchesOptimization` and two cases of
`ExplicitMauiAspireFeatureSwitchWarnsInOptimizedBuild`, in Windows/macOS
Debug and Release.

- Copy the required Core targets file into the existing source
correlation payload, preserving its source-relative path.
- Add Pester regression coverage that executes the actual staging target
in an isolated temporary directory, checks the correlation payload
destination, and compares staged file hashes with their sources.
- Add `eng/helix.proj` to the existing PowerShell test workflow's path
triggers.

#### Device-test trimming and synchronization

- Move the handler constructor API-shape check from trimmed device
applications to untrimmed Core unit tests. Unused constructor overloads
may legitimately disappear from a deployed application. The new theory
checks public concrete Core view handlers, excluding
`SwipeItemViewHandler`, whose mapper constructors are intentionally
protected.
- Preserve the `AbsoluteLayout` parameterless constructor used
indirectly by `HandlerDoesNotLeak`. The CI-linked Mono assembly
contained the type but no constructor, so the test failed before its
leak assertions.
- Preserve the protected `RequestManager.Glide` property used by
`GlideStaticEqualsGlideGet`, look it up on its declaring type, and
assert that reflection found it before invoking it. The CI APK had
trimmed the property. The singleton comparison is unchanged.
- Wait for the iOS flyout header to reach its minimum height using the
bounded `AssertEventually` helper. The previous assertion assumed UIKit
completed the header layout within the scroll helper's 10 ms delay. The
expected height (100 px) and tolerance (0.3 px) are unchanged; no
product layout code changes.
- Declare the existing Windows
`MapHandler.DisconnectHandler(FrameworkElement)` override in
`PublicAPI.Unshipped.txt`. While this PR was being validated, #38844
restored the Windows map implementation on `net11.0` but omitted that
declaration, causing RS0016 in both target-branch build 1611088 and this
PR's build 1611131. The updated base is incorporated by a normal merge,
and **both Windows map lifecycle tests remain enabled**. The earlier
temporary skips have been removed.
- Wait for the native Windows Map's embedded document to create its
layers and pin before the lifecycle tests close their temporary window.
The exact crash dump identifies `MapControl::OnLayerAdded` awaiting
`addSymbolLayer()` after `InitializeWebMap`; `NavigationCompleted` fires
before those continuations finish. The bounded waits observe local
script state, not authenticated map tiles, and preserve the existing
native-layer ownership assertions.

#### Android intent isolation and Windows execution reporting

- Scope Android explicit-component navigation fixtures to the test
package and a custom, unhandled action. After production correctly
removes the explicit component, the old broad implicit intent could open
the system resolver or Google Backup settings and leave the test
activity stopped. Subsequent layout and lifecycle-bound back-callback
tests then failed. Preserve the explicit-target launch monitor and
assert that the original `ComponentActivity` is `RESUMED` before and
after navigation, on the UI thread. Do not use `HasWindowFocus` as a
lifecycle check: system overlays can take focus without pausing the
activity.
- Select Windows MSIX dependencies by their actual `Dependencies/x64`
directory rather than matching `x64` anywhere in the full path. The
parent package directory already contains `x64`, so the old filter also
selected arm64, win32, and x86 dependencies. Install the app with the
selected dependency paths and emit deployment logs on failure.
- Fail Windows work items for abnormal packaged/unpackaged process exits
or empty/malformed xUnit files. The old Map category crashed with an
empty XML file, but the runner ignored it and reported a false green.
Valid category results are still merged and uploaded; ordinary WinUI
exit codes `0` and `-1` remain supported.
- Retain the packaged process handle before waiting and read its exit
code through the explicit getter, then dispose the handle. On Windows
PowerShell's .NET Framework, `WaitForExit` alone does not retain the
handle that `ExitCode` requires. The initial reporting change therefore
read an empty property and incorrectly failed otherwise passing packaged
suites. Unreadable exit codes still fail explicitly rather than being
treated as success.

The final diff introduces **no test skips or product behavior changes**.
Trimming remains enabled; only the exact indirectly used members are
rooted. The Windows API entry records an implementation already present
on the target branch rather than introducing a new API. No new pipeline
tests were added for these follow-ups; runner changes use syntax checks
and actual CI.

### Issues Fixed

Resolves the Helix payload failure observed on #38806 in [build
1608731](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1608731).
The same missing-file failures occur on the `net11.0` baseline in [build
1607883](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1607883).

Also addresses the independent failures in this PR's [device build
1609895](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1609895),
which also affected target-branch [build
1609516](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1609516).
The recurring Glide failure is tracked in #38240.

The subsequent Windows compile error is reproduced in target-branch
[build
1611088](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1611088)
and PR [build
1611131](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1611131).
Android isolation, Windows execution-reporting problems, and the native
Map initialization race were uncovered in [device build
1611162](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1611162).

### Validation

- Original staging regression failed before the payload fix; all **109**
Pester tests passed afterward. Debug and Release staging produced
byte-identical Core targets files.
- Main [build
1609883](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1609883)
succeeded. Actual XAML results confirmed all **20 affected executions**
passed across Windows/macOS and Debug/Release.
- Pinned RC2 SDK: **41 focused Core unit tests passed**, including the
migrated constructor contracts. Earlier main build 1611161 also confirms
all **32 constructor theory cases** in the inspected macOS unit leg.
- Android 13, CoreCLR Release with partial trimming: **49 image-source
tests passed**, including `GlideStaticEqualsGlideGet`. Linked metadata
confirms the protected property/getter is preserved. Device build
1611162 confirms the Glide case passes on **both Mono and CoreCLR**.
- iOS 18.5, Mono Release with partial trimming: the exact CI app and the
pre-change local app reproduced collapsed-header failures. With the
condition-based assertion, **442 Shell tests passed in each of three
consecutive local runs**. Device build 1611162 confirms **442/442 Shell
cases on each of Mono and CoreCLR iOS 18**, including all flyout-header
cases.
- The same trimmed local iOS app passed **190 memory tests**, with 3
existing skips. All inherited `AbsoluteLayout` leak cases passed; linked
metadata confirms the constructor is preserved. Device build 1611162
also confirms all three inherited cases in Mono iOS general tests.
- The earlier Android window-focus guard failed both old broad-intent
cases. With the isolated fixtures, the complete Release Mono API 33
Blazor suite passed **62 tests with 4 existing skips**, including all
seven cases that failed in CI. The subsequent lifecycle-check correction
and its additional positive/negative evidence are described below.
- Windows [device build
1611496](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1611496),
Helix job `073cf090-5d99-4ee4-acf8-afaf7066d8bb`, confirms **both Map
facts pass in both packaged and unpackaged execution**: 2/2 in each
mode, no skips. The disconnect test takes 1.89s/2.01s and the
remove/re-add test 2.31s/2.46s. All five unpackaged applications pass.
- The same Windows run successfully installs and executes all five
packaged applications. Complete result XML contains **3,249 passed, 160
existing skips, zero failed tests, and zero errors**. The packaged work
items were incorrectly red only because the launcher could not read
`ExitCode`; commit `8768da7c59` fixes the retained-handle requirement.
- Packaged runner and embedded PowerShell syntax checks pass. Changed C#
files are formatted; `git diff --check` passes. The Map follow-up also
compiles against the 245 managed assemblies extracted from the exact
failing CI payload.
- Before the latest push, [device build
1611589](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1611589),
head `8768da7c59`, produced **42 complete Apple XML files**, correlated
through its own submission logs and fully audited: **26,480 passed, 720
existing skips, zero failures/errors**, across Mono/CoreCLR iOS 18/26
and CoreCLR Mac 15/26.
- All **10 Android XML files** from that run were also correlated and
audited. Core, Controls, Essentials and Graphics passed on both
runtimes. Blazor Mono was **62 passed, 4 skipped**. Blazor CoreCLR was
**60 passed, 2 failed, 4 skipped**: only the old window-focus guards
failed; all seven original cascading cases passed.
- Commit `065fe7c97a` replaces those false-positive guards with
lifecycle assertions. Opening the notification shade locally reproduces
exactly the two old failures: the activity lacks window focus but
remains `RESUMED` before and after navigation. With the correction, the
complete Release API 33 suite passes with the shade open on **both Mono
and CoreCLR: 62 passed, 4 existing skips each**. A separate controlled
run opens Settings only after the navigation request; the corrected
guard rejects real backgrounding (`RESUMED` expected, `CREATED` actual),
and all seven original cascading failures return. A final normal CoreCLR
run again passes **62 tests with 4 existing skips**.
- Current-head [device build
1611714](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1611714),
own submission log 149, confirms Windows job
`f6501433-8b24-401b-ae86-311a339062ab`: **all ten real
packaged/unpackaged application work items passed**. All ten complete
XML files total **6,498 passed, 320 existing skips, zero
failures/errors** (3,249 passed/160 skipped per mode). Both Map facts
pass in each mode. All **52 packaged process launches** correctly report
native exit code `-1` and launcher exit code `0`, providing runtime
proof of the retained-handle correction.
- The same current-head build's full Apple matrix also passed: **42
complete XML files, 26,480 passed, 720 existing skips, zero
failures/errors**. All six jobs are correlated through this build's own
submission logs 125, 133 and 141.
- Current-head Android CI also passes on **both Mono and CoreCLR**. Each
full Blazor suite is **62 passed, 4 existing skips**, including both
formerly failing explicit-component theory rows and all seven original
cascading cases. All ten Android result files are complete and total
**9,238 passed, 176 existing skips, zero failures/errors**, correlated
through this build's own submission logs 157 and 165.

**Device verification complete:** [Device build
1611714](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1611714)
succeeded on published head `065fe7c97a` at 2026-09-25 03:25:54 UTC. All
**62 real work items** across nine Helix jobs passed. All **62 complete
XML files** were audited: **42,216 passed, 1,216 existing skips, zero
failures and zero assembly errors**. Counts are test executions across
lanes, not unique tests. The successful run was requested in [comment
5825614265](#38827 (comment)).

**Main verification complete:** [Main build
1611713](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1611713)
succeeded on the same published head at 2026-09-25 04:47:55 UTC. All
four platform/configuration build legs and the integration matrix
passed. Its four unit-test Helix jobs are correlated through own
submission logs 200 and 208; all **44 real test work items** and four
controllers passed. The final Windows Build integration leg reports
**108 passed, 2 existing skips, zero failures**. The PowerShell Script
Tests workflow, Build Analysis and Build Insights also succeeded. Both
pipelines are now verified green; no further rerun is needed on this
unchanged head.

The previous run's ten Windows work items remained unstarted for more
than 90 minutes and were canceled after the push; their artificial
`-1234` results are not application crashes or test execution. All
completed results were preserved before publishing the Android
correction. The replacement run above now provides actual Windows proof;
no queue outage is claimed for the earlier wait.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Sep 27, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

`ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a
shadow string. That reference contributes to retaining
`System.Text.RegularExpressions` and additional regex implementation
code in trimmed MAUI Android apps.

### Broader goal and related work

This PR is one step toward removing the roughly 300 KB linked
`System.Text.RegularExpressions` assembly from MAUI apps that do not use
regular expressions in their own application logic. Removing the
`ShadowTypeConverter` root is not sufficient by itself because other
framework and dependency roots still retain regex code.

Related work toward that goal includes:

- #38616 trims disabled MAUI Aspire support, including
`Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire
feature switch is off.
- dotnet/android#12813 avoids unnecessary XML marshalling references in
trimmable type-map dispatch assemblies. This helps remove XML assemblies
where they are not otherwise needed; `System.Private.Xml` contains a
generated `LanguageRegex` implementation that can independently retain
regex code.

Additional roots may still need to be removed or made trimmable before
`System.Text.RegularExpressions` disappears entirely from a given app.
For example, the measured sample-content app also reaches regex through
`CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`.

### Description of Change

Replaces the runtime `Regex` with an allocation-conscious inline
tokenizer/parser.

The parser intentionally preserves the existing unanchored
`Regex.Matches` behavior, including supported color forms,
invariant-culture decimal/scientific numbers, Unicode whitespace,
ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for
incomplete decimals or exponents.

Token boundaries are stored in a stack-allocated `Span<Part>`, and only
matched tokens are materialized as strings for the existing color and
numeric parsers.

The project now also bans the `System.Text.RegularExpressions` namespace
through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this
dependency from being reintroduced.

### Performance considerations

Vectorizing the character scans was considered but intentionally not
implemented. Shadow strings are normally short (roughly tens of
characters) and whitespace runs are generally zero to two characters, so
SIMD setup and branching are unlikely to beat the straightforward scalar
loops. A vectorized path would also require separate `netstandard`
fallbacks and careful tables to preserve the old regex's Unicode `\s`
and `\d` behavior, adding disproportionate complexity without
demonstrated benefit.

### Tests

- Added focused coverage for every supported color syntax,
offsets/radius/opacity, whitespace, malformed values, invariant culture,
and legacy regex edge cases.
- `ShadowTests`: 85 passed.
- `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and
`net10.0`.
- Verified an intentional `System.Text.RegularExpressions.Regex` usage
in `Controls.Core` fails with RS0030.

### Android size measurement

Published identical stock `.NET 11 preview 7` `dotnet new maui
--sample-content` apps in Release with `TrimMode=partial`, replacing
only the `Microsoft.Maui.Controls.dll` input in the patched build.

| Artifact | Baseline | Patched | Delta |
|---|---:|---:|---:|
| Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** |
| Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** |
| Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B |
1,495,040 B | **-512 B** |
| Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B |
330,752 B | **-3,584 B** |
| arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** |
| x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** |

`System.Text.RegularExpressions` remains retained by other roots in this
sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`),
`System.Data.Common`, and `System.Private.Xml`. This change removes the
`Microsoft.Maui.Controls` reference and reduces the linked regex
implementation, but does not eliminate the assembly from this app.

### Issues Fixed

None.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Sep 27, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

`ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a
shadow string. That reference contributes to retaining
`System.Text.RegularExpressions` and additional regex implementation
code in trimmed MAUI Android apps.

### Broader goal and related work

This PR is one step toward removing the roughly 300 KB linked
`System.Text.RegularExpressions` assembly from MAUI apps that do not use
regular expressions in their own application logic. Removing the
`ShadowTypeConverter` root is not sufficient by itself because other
framework and dependency roots still retain regex code.

Related work toward that goal includes:

- #38616 trims disabled MAUI Aspire support, including
`Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire
feature switch is off.
- dotnet/android#12813 avoids unnecessary XML marshalling references in
trimmable type-map dispatch assemblies. This helps remove XML assemblies
where they are not otherwise needed; `System.Private.Xml` contains a
generated `LanguageRegex` implementation that can independently retain
regex code.

Additional roots may still need to be removed or made trimmable before
`System.Text.RegularExpressions` disappears entirely from a given app.
For example, the measured sample-content app also reaches regex through
`CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`.

### Description of Change

Replaces the runtime `Regex` with an allocation-conscious inline
tokenizer/parser.

The parser intentionally preserves the existing unanchored
`Regex.Matches` behavior, including supported color forms,
invariant-culture decimal/scientific numbers, Unicode whitespace,
ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for
incomplete decimals or exponents.

Token boundaries are stored in a stack-allocated `Span<Part>`, and only
matched tokens are materialized as strings for the existing color and
numeric parsers.

The project now also bans the `System.Text.RegularExpressions` namespace
through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this
dependency from being reintroduced.

### Performance considerations

Vectorizing the character scans was considered but intentionally not
implemented. Shadow strings are normally short (roughly tens of
characters) and whitespace runs are generally zero to two characters, so
SIMD setup and branching are unlikely to beat the straightforward scalar
loops. A vectorized path would also require separate `netstandard`
fallbacks and careful tables to preserve the old regex's Unicode `\s`
and `\d` behavior, adding disproportionate complexity without
demonstrated benefit.

### Tests

- Added focused coverage for every supported color syntax,
offsets/radius/opacity, whitespace, malformed values, invariant culture,
and legacy regex edge cases.
- `ShadowTests`: 85 passed.
- `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and
`net10.0`.
- Verified an intentional `System.Text.RegularExpressions.Regex` usage
in `Controls.Core` fails with RS0030.

### Android size measurement

Published identical stock `.NET 11 preview 7` `dotnet new maui
--sample-content` apps in Release with `TrimMode=partial`, replacing
only the `Microsoft.Maui.Controls.dll` input in the patched build.

| Artifact | Baseline | Patched | Delta |
|---|---:|---:|---:|
| Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** |
| Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** |
| Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B |
1,495,040 B | **-512 B** |
| Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B |
330,752 B | **-3,584 B** |
| arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** |
| x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** |

`System.Text.RegularExpressions` remains retained by other roots in this
sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`),
`System.Data.Common`, and `System.Private.Xml`. This change removes the
`Microsoft.Maui.Controls` reference and reduces the linked regex
implementation, but does not eliminate the assembly from this app.

### Issues Fixed

None.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Oct 3, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

`ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a
shadow string. That reference contributes to retaining
`System.Text.RegularExpressions` and additional regex implementation
code in trimmed MAUI Android apps.

### Broader goal and related work

This PR is one step toward removing the roughly 300 KB linked
`System.Text.RegularExpressions` assembly from MAUI apps that do not use
regular expressions in their own application logic. Removing the
`ShadowTypeConverter` root is not sufficient by itself because other
framework and dependency roots still retain regex code.

Related work toward that goal includes:

- #38616 trims disabled MAUI Aspire support, including
`Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire
feature switch is off.
- dotnet/android#12813 avoids unnecessary XML marshalling references in
trimmable type-map dispatch assemblies. This helps remove XML assemblies
where they are not otherwise needed; `System.Private.Xml` contains a
generated `LanguageRegex` implementation that can independently retain
regex code.

Additional roots may still need to be removed or made trimmable before
`System.Text.RegularExpressions` disappears entirely from a given app.
For example, the measured sample-content app also reaches regex through
`CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`.

### Description of Change

Replaces the runtime `Regex` with an allocation-conscious inline
tokenizer/parser.

The parser intentionally preserves the existing unanchored
`Regex.Matches` behavior, including supported color forms,
invariant-culture decimal/scientific numbers, Unicode whitespace,
ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for
incomplete decimals or exponents.

Token boundaries are stored in a stack-allocated `Span<Part>`, and only
matched tokens are materialized as strings for the existing color and
numeric parsers.

The project now also bans the `System.Text.RegularExpressions` namespace
through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this
dependency from being reintroduced.

### Performance considerations

Vectorizing the character scans was considered but intentionally not
implemented. Shadow strings are normally short (roughly tens of
characters) and whitespace runs are generally zero to two characters, so
SIMD setup and branching are unlikely to beat the straightforward scalar
loops. A vectorized path would also require separate `netstandard`
fallbacks and careful tables to preserve the old regex's Unicode `\s`
and `\d` behavior, adding disproportionate complexity without
demonstrated benefit.

### Tests

- Added focused coverage for every supported color syntax,
offsets/radius/opacity, whitespace, malformed values, invariant culture,
and legacy regex edge cases.
- `ShadowTests`: 85 passed.
- `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and
`net10.0`.
- Verified an intentional `System.Text.RegularExpressions.Regex` usage
in `Controls.Core` fails with RS0030.

### Android size measurement

Published identical stock `.NET 11 preview 7` `dotnet new maui
--sample-content` apps in Release with `TrimMode=partial`, replacing
only the `Microsoft.Maui.Controls.dll` input in the patched build.

| Artifact | Baseline | Patched | Delta |
|---|---:|---:|---:|
| Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** |
| Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** |
| Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B |
1,495,040 B | **-512 B** |
| Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B |
330,752 B | **-3,584 B** |
| arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** |
| x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** |

`System.Text.RegularExpressions` remains retained by other roots in this
sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`),
`System.Data.Common`, and `System.Private.Xml`. This change removes the
`Microsoft.Maui.Controls` reference and reduces the linked regex
implementation, but does not eliminate the assembly from this app.

### Issues Fixed

None.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Oct 3, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

`ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a
shadow string. That reference contributes to retaining
`System.Text.RegularExpressions` and additional regex implementation
code in trimmed MAUI Android apps.

### Broader goal and related work

This PR is one step toward removing the roughly 300 KB linked
`System.Text.RegularExpressions` assembly from MAUI apps that do not use
regular expressions in their own application logic. Removing the
`ShadowTypeConverter` root is not sufficient by itself because other
framework and dependency roots still retain regex code.

Related work toward that goal includes:

- #38616 trims disabled MAUI Aspire support, including
`Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire
feature switch is off.
- dotnet/android#12813 avoids unnecessary XML marshalling references in
trimmable type-map dispatch assemblies. This helps remove XML assemblies
where they are not otherwise needed; `System.Private.Xml` contains a
generated `LanguageRegex` implementation that can independently retain
regex code.

Additional roots may still need to be removed or made trimmable before
`System.Text.RegularExpressions` disappears entirely from a given app.
For example, the measured sample-content app also reaches regex through
`CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`.

### Description of Change

Replaces the runtime `Regex` with an allocation-conscious inline
tokenizer/parser.

The parser intentionally preserves the existing unanchored
`Regex.Matches` behavior, including supported color forms,
invariant-culture decimal/scientific numbers, Unicode whitespace,
ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for
incomplete decimals or exponents.

Token boundaries are stored in a stack-allocated `Span<Part>`, and only
matched tokens are materialized as strings for the existing color and
numeric parsers.

The project now also bans the `System.Text.RegularExpressions` namespace
through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this
dependency from being reintroduced.

### Performance considerations

Vectorizing the character scans was considered but intentionally not
implemented. Shadow strings are normally short (roughly tens of
characters) and whitespace runs are generally zero to two characters, so
SIMD setup and branching are unlikely to beat the straightforward scalar
loops. A vectorized path would also require separate `netstandard`
fallbacks and careful tables to preserve the old regex's Unicode `\s`
and `\d` behavior, adding disproportionate complexity without
demonstrated benefit.

### Tests

- Added focused coverage for every supported color syntax,
offsets/radius/opacity, whitespace, malformed values, invariant culture,
and legacy regex edge cases.
- `ShadowTests`: 85 passed.
- `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and
`net10.0`.
- Verified an intentional `System.Text.RegularExpressions.Regex` usage
in `Controls.Core` fails with RS0030.

### Android size measurement

Published identical stock `.NET 11 preview 7` `dotnet new maui
--sample-content` apps in Release with `TrimMode=partial`, replacing
only the `Microsoft.Maui.Controls.dll` input in the patched build.

| Artifact | Baseline | Patched | Delta |
|---|---:|---:|---:|
| Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** |
| Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** |
| Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B |
1,495,040 B | **-512 B** |
| Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B |
330,752 B | **-3,584 B** |
| arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** |
| x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** |

`System.Text.RegularExpressions` remains retained by other roots in this
sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`),
`System.Data.Common`, and `System.Private.Xml`. This change removes the
`Microsoft.Maui.Controls` reference and reduces the linked regex
implementation, but does not eliminate the assembly from this app.

### Issues Fixed

None.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Oct 6, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

`ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a
shadow string. That reference contributes to retaining
`System.Text.RegularExpressions` and additional regex implementation
code in trimmed MAUI Android apps.

### Broader goal and related work

This PR is one step toward removing the roughly 300 KB linked
`System.Text.RegularExpressions` assembly from MAUI apps that do not use
regular expressions in their own application logic. Removing the
`ShadowTypeConverter` root is not sufficient by itself because other
framework and dependency roots still retain regex code.

Related work toward that goal includes:

- #38616 trims disabled MAUI Aspire support, including
`Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire
feature switch is off.
- dotnet/android#12813 avoids unnecessary XML marshalling references in
trimmable type-map dispatch assemblies. This helps remove XML assemblies
where they are not otherwise needed; `System.Private.Xml` contains a
generated `LanguageRegex` implementation that can independently retain
regex code.

Additional roots may still need to be removed or made trimmable before
`System.Text.RegularExpressions` disappears entirely from a given app.
For example, the measured sample-content app also reaches regex through
`CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`.

### Description of Change

Replaces the runtime `Regex` with an allocation-conscious inline
tokenizer/parser.

The parser intentionally preserves the existing unanchored
`Regex.Matches` behavior, including supported color forms,
invariant-culture decimal/scientific numbers, Unicode whitespace,
ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for
incomplete decimals or exponents.

Token boundaries are stored in a stack-allocated `Span<Part>`, and only
matched tokens are materialized as strings for the existing color and
numeric parsers.

The project now also bans the `System.Text.RegularExpressions` namespace
through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this
dependency from being reintroduced.

### Performance considerations

Vectorizing the character scans was considered but intentionally not
implemented. Shadow strings are normally short (roughly tens of
characters) and whitespace runs are generally zero to two characters, so
SIMD setup and branching are unlikely to beat the straightforward scalar
loops. A vectorized path would also require separate `netstandard`
fallbacks and careful tables to preserve the old regex's Unicode `\s`
and `\d` behavior, adding disproportionate complexity without
demonstrated benefit.

### Tests

- Added focused coverage for every supported color syntax,
offsets/radius/opacity, whitespace, malformed values, invariant culture,
and legacy regex edge cases.
- `ShadowTests`: 85 passed.
- `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and
`net10.0`.
- Verified an intentional `System.Text.RegularExpressions.Regex` usage
in `Controls.Core` fails with RS0030.

### Android size measurement

Published identical stock `.NET 11 preview 7` `dotnet new maui
--sample-content` apps in Release with `TrimMode=partial`, replacing
only the `Microsoft.Maui.Controls.dll` input in the patched build.

| Artifact | Baseline | Patched | Delta |
|---|---:|---:|---:|
| Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** |
| Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** |
| Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B |
1,495,040 B | **-512 B** |
| Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B |
330,752 B | **-3,584 B** |
| arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** |
| x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** |

`System.Text.RegularExpressions` remains retained by other roots in this
sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`),
`System.Data.Common`, and `System.Private.Xml`. This change removes the
`Microsoft.Maui.Controls` reference and reduces the linked regex
implementation, but does not eliminate the assembly from this app.

### Issues Fixed

None.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Oct 6, 2026
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

`ShadowTypeConverter` constructs a runtime `Regex` whenever it parses a
shadow string. That reference contributes to retaining
`System.Text.RegularExpressions` and additional regex implementation
code in trimmed MAUI Android apps.

### Broader goal and related work

This PR is one step toward removing the roughly 300 KB linked
`System.Text.RegularExpressions` assembly from MAUI apps that do not use
regular expressions in their own application logic. Removing the
`ShadowTypeConverter` root is not sufficient by itself because other
framework and dependency roots still retain regex code.

Related work toward that goal includes:

- #38616 trims disabled MAUI Aspire support, including
`Microsoft.Maui`'s source-generated `LocalhostRegex`, when the Aspire
feature switch is off.
- dotnet/android#12813 avoids unnecessary XML marshalling references in
trimmable type-map dispatch assemblies. This helps remove XML assemblies
where they are not otherwise needed; `System.Private.Xml` contains a
generated `LanguageRegex` implementation that can independently retain
regex code.

Additional roots may still need to be removed or made trimmable before
`System.Text.RegularExpressions` disappears entirely from a given app.
For example, the measured sample-content app also reaches regex through
`CommunityToolkit.Maui`, `System.Data.Common`, and `System.Private.Xml`.

### Description of Change

Replaces the runtime `Regex` with an allocation-conscious inline
tokenizer/parser.

The parser intentionally preserves the existing unanchored
`Regex.Matches` behavior, including supported color forms,
invariant-culture decimal/scientific numbers, Unicode whitespace,
ignored punctuation, greedy 3-to-8 digit hex matching, and rollback for
incomplete decimals or exponents.

Token boundaries are stored in a stack-allocated `Span<Part>`, and only
matched tokens are materialized as strings for the existing color and
numeric parsers.

The project now also bans the `System.Text.RegularExpressions` namespace
through a `Controls.Core`-specific `BannedSymbols.txt`, preventing this
dependency from being reintroduced.

### Performance considerations

Vectorizing the character scans was considered but intentionally not
implemented. Shadow strings are normally short (roughly tens of
characters) and whitespace runs are generally zero to two characters, so
SIMD setup and branching are unlikely to beat the straightforward scalar
loops. A vectorized path would also require separate `netstandard`
fallbacks and careful tables to preserve the old regex's Unicode `\s`
and `\d` behavior, adding disproportionate complexity without
demonstrated benefit.

### Tests

- Added focused coverage for every supported color syntax,
offsets/radius/opacity, whitespace, malformed values, invariant culture,
and legacy regex edge cases.
- `ShadowTests`: 85 passed.
- `Controls.Core` builds for `netstandard2.0`, `netstandard2.1`, and
`net10.0`.
- Verified an intentional `System.Text.RegularExpressions.Regex` usage
in `Controls.Core` fails with RS0030.

### Android size measurement

Published identical stock `.NET 11 preview 7` `dotnet new maui
--sample-content` apps in Release with `TrimMode=partial`, replacing
only the `Microsoft.Maui.Controls.dll` input in the patched build.

| Artifact | Baseline | Patched | Delta |
|---|---:|---:|---:|
| Signed APK | 36,903,788 B | 36,899,692 B | **-4,096 B** |
| Unsigned APK | 36,774,978 B | 36,771,221 B | **-3,757 B** |
| Linked `Microsoft.Maui.Controls.dll` (per ABI) | 1,495,552 B |
1,495,040 B | **-512 B** |
| Linked `System.Text.RegularExpressions.dll` (per ABI) | 334,336 B |
330,752 B | **-3,584 B** |
| arm64 assembly store | 7,689,840 B | 7,688,216 B | **-1,624 B** |
| x64 assembly store | 7,899,160 B | 7,896,448 B | **-2,712 B** |

`System.Text.RegularExpressions` remains retained by other roots in this
sample: `CommunityToolkit.Maui`, `Microsoft.Maui` (`LocalhostRegex`),
`System.Data.Common`, and `System.Private.Xml`. This change removes the
`Microsoft.Maui.Controls` reference and reduces the linked regex
implementation, but does not eliminate the assembly from this app.

### Issues Fixed

None.

---------

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

s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants