Uh oh!
There was an error while loading. Please reload this page.
[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML - #11449
[TrimmableTypeMap] Generate NativeAOT ProGuard rules from DGML#11449simonrozsival merged 14 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a trimmable NativeAOT + R8 workflow where ProGuard/R8 keep rules are generated from NativeAOT ILC DGML scan output intersected with acw-map.txt, allowing R8 to remove unused generated Java wrappers and significantly reduce classes.dex size.
Changes:
- Generate NativeAOT-specific ProGuard rules after
IlcCompilefrom*.scan.dgml.xml+acw-map.txt, and adjust R8 configuration generation to avoid broad keep rules in this mode. - Extend the trimmable typemap pipeline to classify framework peers, selectively emit array typemap entries only when referenced from non-framework assemblies, and persist the list of generated typemap assemblies to stabilize incremental builds.
- Add runtime feature plumbing for
IsNativeAotRuntimeand adjust NativeAOT JNI initialization ordering/inputs to provide required Java peer marker classes.
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/TestFixtures/TestTypes.cs | Adds framework-like SSL and network peer types to exercise framework JCW/peer scanning scenarios. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs | Adds test coverage for framework peer marking and array-entry emission behavior. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs | Tests framework ACW conditionality and array-entry emission rules; updates anchor visibility assertions. |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs | Verifies generator can emit expected framework JCW Java sources. |
| src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targets | Plumbs UseTrimmableNativeAotProguardConfiguration into the R8 task invocation. |
| src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets | Adds _AndroidTrimmableTypeMapMaxArrayRank to the property cache for incremental invalidation. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.MonoVM.apkdesc | Updates expected APK contents/sizes after build output changes. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.MonoVM.apkdesc | Updates expected APK contents/sizes after build output changes. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs | Adds incremental/build validation for array-rank changes and NativeAOT/CoreCLR typemap behaviors. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs | Adds task-level tests for generated typemap assembly list file and framework JCW emission. |
| src/Xamarin.Android.Build.Tasks/Tasks/R8.cs | Adds a trimmable NativeAOT mode that alters generated ProGuard config inputs and common rules. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs | Adds framework assembly classification and writes a generated-assemblies list file. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateProguardConfiguration.cs | Adds DGML+ACW-map based ProGuard rule generation for NativeAOT trimmable builds. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets | Persists typemap-generated assembly list and uses it for downstream item population/incremental correctness. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets | Hooks DGML-based ProGuard rule generation and adjusts ILC inputs for trimmable typemap NativeAOT. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets | Adds opt-out switch to skip linked-assembly ProGuard configuration generation when replaced by DGML flow. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets | Enables DGML generation for R8 builds, configures skip/alternate ProGuard generation, and sets runtime feature. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targets | Sets IsNativeAotRuntime=false runtime feature for MonoVM. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targets | Sets IsNativeAotRuntime=false runtime feature for CoreCLR. |
| src/native/nativeaot/host/host.cc | Ensures NativeAOT host provides global refs for required Java peer marker classes during init. |
| src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs | Adds RuntimeFeature.IsNativeAotRuntime AppContext switch. |
| src/Mono.Android/Android.Runtime/JNIEnvInit.cs | Refactors JNI initialization to share common state init and add NativeAOT-specific runtime initialization entrypoint. |
| src/Mono.Android/Android.Runtime/JNIEnv.cs | Routes unhandled exception propagation consistently for NativeAOT. |
| src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs | Treats NativeAOT unhandled exceptions like the CoreCLR path. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs | Passes framework assembly names into scanning and tightens JCW generation filtering logic. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs | Tracks framework peers and controls array-entry emission based on cross-assembly references. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs | Adds IsFrameworkAssembly and GenerateArrayEntries to peer model. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs | Indexes referenced types by referenced assembly to support framework peer reference detection. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs | Updates documentation around __ArrayMapRank{N} anchors. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs | Adds no-array-map initialization paths when max array rank is 0. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs | Adds additional unconditional types and prevents unconditional rooting for framework ACWs; gates array emission. |
| src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs | Uses the refactored JNIEnvInit initialization path and reuses common type/value manager creation helpers. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
7b41d33 to
158bce0CompareUse NativeAOT scan DGML to identify retained managed types, intersect them with acw-map.txt, and emit concrete R8 keep rules for trimmable typemap NativeAOT builds. This avoids keeping the broad generated Java wrapper set when R8 is enabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move DGML-based NativeAOT ProGuard generation into a separate MSBuild task so GenerateProguardConfiguration remains focused on linked assembly scanning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep NativeAOT.targets free of ProGuard coordination properties. Let LlvmIr targets hardcode linked-assembly ProGuard timing for ILLink and NativeAOT, and keep the trimmable NativeAOT R8 mode flag with the trimmable NativeAOT typemap targets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the trimmable NativeAOT common R8 rules to an embedded ProGuard resource, keep GenerateProguardConfiguration unchanged, add a shared trimmable typemap ProGuard target name with a CoreCLR stub, and restore the existing linked-assembly ProGuard target wiring. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Default NativeAOT trimmable typemap builds to AndroidLinkTool=r8 so the generated DGML-based keep rules are active without requiring users to opt in manually. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tolerate the no-input typemap generation case by creating an empty assembly list, avoid nested framework reference scans, and disable XML resolver use when reading ILC DGML. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
158bce0 to
3d4e1cdComparesimonrozsival
commented
May 23, 2026
@copilot there is a failure in CI, fix it: |
Agent-Logs-Url: https://github.com/dotnet/android/sessions/91b096b3-9ab7-4980-8234-097a0c3caca5 Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Collect NativeAOT DGML files from per-RID intermediate output paths and merge their retained type metadata into the single ProGuard configuration consumed by R8/D8. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
commented
May 25, 2026
/review |
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
⚠️ Needs Changes
Issue counts: 0 ❌ errors · 3
Summary
The approach is solid — intersecting ILC DGML metadata with acw-map.txt to generate focused ProGuard rules is well-designed, and the size reduction results are impressive (classes.dex from 259 kB → 15 kB).
Key items to address:
- O(n2) dedup in
LoadJavaTypesFromAcwMap—List.Contains()for dedup is quadratic; use aHashSetalongside the list. - Uncoded error messages —
Log.LogErrorcalls inGenerateNativeAotProguardConfigurationshould useLog.LogCodedErrorwithXA####codes andProperties.Resources. - Null-unsafe
GetManifestResourceStream— the new code path in R8.cs calls.CopyTo()on a potentially null stream.
Positive callouts:
- Clean separation: DGML-based logic stays in a dedicated task, leaving the existing
GenerateProguardConfigurationpath untouched. - Good use of
XmlReader(streaming) over LINQ-to-XML for parsing the large DGML files. - Proper
DtdProcessing.ProhibitandXmlResolver = nullfor XML security. - The MSBuild targets have correct
Inputs/Outputsfor incremental build support. - The
_ReadGeneratedTrimmableTypeMapAssembliesfallback withMakeDir+Touchis a nice resilience improvement.
Generated by Android PR Reviewer for issue #11449 · ● 18.8M
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
commented
May 27, 2026
/review |
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
🤖 Code Review Summary
Verdict:
Overview
Well-structured PR that introduces DGML-based ProGuard rule generation for NativeAOT trimmable typemap builds. The architecture is clean: a dedicated GenerateNativeAotProguardConfiguration task handles the DGML↔ACW intersection, keeping it separate from the existing GenerateProguardConfiguration path. The results (classes.dex from 259 kB → 15 kB) are impressive.
Issues
| Severity | Count | Category |
|---|---|---|
| 1 | Missing error code documentation | |
| 💡 suggestion | 3 | Performance, documentation |
What's good
- JavaPeerScanner fix: Iterating over
ReferencedTypeNamesByAssemblyand checkingframeworkAssemblyNames.Contains()is more efficient than the reverse — good algorithmic improvement. - Proper XML security:
DtdProcessing.ProhibitandXmlResolver = nullon the DGML reader. - Clean task structure: Extends
AndroidTask, properTaskPrefix,[Required]properties with defaults, coded errors fromProperties.Resources, returns!Log.HasLoggedErrors. - Incremental build support: The
_GenerateTrimmableTypeMapProguardConfigurationtarget has properInputs/OutputsandFileWrites. - Good separation: CoreCLR gets an empty stub target with a TODO linking to
#11052, NativeAOT gets the real implementation. GetEmbeddedResourceStreamhelper: Nice extraction that adds null-checking the original code lacked.
Generated by Android PR Reviewer for issue #11449 · ● 30.7M
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Document the new XA43xx diagnostics, clarify the NativeAOT DGML/acw-map matching logic, and avoid unnecessary allocations in the ProGuard configuration task. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
commented
May 27, 2026
/review |
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
✅ LGTM — Clean, well-structured PR
Solid work generating NativeAOT ProGuard rules from DGML metadata. The results are impressive — 95% reduction in classes.dex size and 94% fewer Java classes.
Highlights:
- Good use of streaming
XmlReaderfor the large DGML files (24MB+) - Proper
DtdProcessing.Prohibit+XmlResolver = nullfor XML security - Clean separation: new
GenerateNativeAotProguardConfigurationtask handles DGML-based logic without touching the existingGenerateProguardConfigurationpath - Error codes (XA4319–4321) properly documented with markdown files and index entries
- Incremental build support via
Inputs/OutputsandFileWrites - The
GetEmbeddedResourceStreamhelper inR8.csis a nice refactor to avoid null-returningGetManifestResourceStream
Summary of suggestions (all 💡):
| # | Severity | Category | File |
|---|---|---|---|
| 1 | Performance | GenerateNativeAotProguardConfiguration.cs — use CopyIfStringChanged to avoid unnecessary timestamp updates | |
| 2 | 💡 | Patterns | GenerateNativeAotProguardConfiguration.cs — prefer !Log.HasLoggedErrors over return false |
| 3 | 💡 | Formatting | GenerateNativeAotProguardConfiguration.cs — file-scoped namespace |
| 4 | 💡 | Performance | GenerateNativeAotProguardConfiguration.cs — ordinal IndexOf |
| 5 | 💡 | MSBuild targets | NativeAOT.targets — BeforeTargets vs DependsOn |
Not flagged (no tests found): There are no unit tests for the new GenerateNativeAotProguardConfiguration task. Given the parsing logic for DGML XML and acw-map files, isolated tests would be valuable — but the PR description shows successful end-to-end validation, so this is not blocking.
CI: Unable to check CI status (tooling not authenticated in this environment).
Generated by Android PR Reviewer for issue #11449 · ● 21.8M
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
simonrozsival
commented
May 27, 2026
@copilot address all review comments |
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
…Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Avoid rerunning IlcCompile from the outer multi-RID build when generating NativeAOT trimmable typemap ProGuard rules. The RID-specific inner builds already produce the DGML files consumed by the outer build. Also match DGML type metadata to assembly-qualified acw-map keys so same-named managed types from unrelated assemblies are not retained accidentally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lc-metadata Resolve conflict in Trimmable.CoreCLR.targets: main now implements the CoreCLR proguard generation that the PR previously stubbed out as a TODO, so take main's _GenerateProguardAfterTargets PropertyGroup and drop the placeholder _GenerateTrimmableTypeMapProguardConfiguration target. The NativeAOT path retains its own _GenerateTrimmableTypeMapProguardConfiguration target in Trimmable.NativeAOT.targets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.

Summary
IlcCompileby intersecting retained managed type metadata from ILC scan DGML withacw-map.txtGenerateNativeAotProguardConfigurationtask, leaving the existing linked-assemblyGenerateProguardConfigurationpath unchangedStacked on #11292
Related to dotnet/runtime#120204
Part of #10790
Part of #11052
Results
HelloWorld NativeAOT
android-arm64Release with trimmable typemap. Before is the existing default without R8 Java shrinking; after is the new default R8 behavior from this PR.classes.dexsizeclasses.dexin APKThe HelloWorld ILC scan DGML input is 24,044,488 bytes and
acw-map.txthas 27,234 lines. The generated ProGuard configuration has 49 rules.20-run measurement on Apple M1 for the isolated
GenerateNativeAotProguardConfigurationtask:Validation
MSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj -c Debug -nr:false --nologo -v minimalMSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -c Debug -nr:false --nologo -v minimalMSBUILDDISABLENODEREUSE=1 ./dotnet-local.sh build samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj -t:SignAndroidPackage -c Release -p:PublishAot=true -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -p:AndroidPackageFormat=apk -nr:false --nologo -v minimal