You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Under the trimmable typemap (NativeAOT), R8 shrinking removes user-authored AndroidJavaSource Java classes that aren't referenced from the managed dependency graph. This breaks multidex behavior and, more importantly, can break real apps that call user Java via JNI/reflection (which ILC's managed graph can't see).
Reproduced via BuildAfterMultiDexIsNotRequired(NativeAOT):
Expected: file exists
But was: ".../obj/Release/android/bin/classes2.dex"
Diagnosis
CreateMultiDexRequiredApplication adds two AndroidJavaSource files (ManyMethods.java, ManyMethods2.java), each with 32768 methods (Bind=False, unreferenced) → 65536 methods > the 64K dex limit → must produce classes2.dex.
Local repro (NativeAOT, arm64):
The Java is compiled: obj/Release/.../android/bin/classes/ManyMethods.class is 1.7 MB each.
The classes zip that D8/R8 consumes (classes.zip, 3.9 MB) containsManyMethods.class / ManyMethods2.class.
But the produced classes.dex is only ~275 KB and contains none of those methods, and no classes2.dex is produced.
Because AndroidEnableMultiDex=True selects R8 (Xamarin.Android.D8.targets, _UseR8), and _R8EnableShrinking=True for AndroidLinkTool=r8, R8 shrinks the unreferenced ManyMethods classes away. The trimmable NativeAOT proguard config (GenerateNativeAotProguardConfiguration, built from the ILC DGML + acw-map) keeps ACW/JCW types and android.R/android.Manifest, but not user AndroidJavaSource classes — so R8 drops them.
The legacy/CoreCLR path keeps them (the CoreCLR variant of the test passes).
Why this matters beyond the test
AndroidJavaSource (Bind=False) classes are user-authored Java, often invoked from native/JNI or reflection. R8 shrinking them based on the managed reachability graph is unsound and can silently remove Java the app needs at runtime.
Fix direction (needs design)
Keep user AndroidJavaSource-derived classes in the trimmable NativeAOT proguard config (emit -keep for them), or
Don't apply R8 shrinking to the user Java side under NativeAOT (the managed side is already trimmed by ILC; the Java side's reachability isn't captured by the managed graph), or
Treat the app's own compiled Java classes as kept program classes.
Acceptance criteria
BuildAfterMultiDexIsNotRequired(NativeAOT) passes (classes2.dex produced when required, absent when not).
Unreferenced user AndroidJavaSource classes survive into the dex on NativeAOT.
Another repro of this same bug (R8 shrinking Java out on trimmable NativeAOT due to missing keeps), from a different angle — bound library Java types rather than unreferenced Bind=False user Java:
BindingWithAndroidJavaSource(NativeAOT) fails:
`.../App/obj/Release/android/bin/classes.dex` should include `Lcom/xamarin/android/test/msbuildtest/JavaSourceJarTest;`!
Expected: True / But was: False
Verified via binlog that R8 does receive the library jars (obj/Release/lp/1/jl/libs/*.jar) as JavaLibrariesToEmbed, but no keep rule preserves JavaSourceJarTest / JavaSourceTestExtension / JavaSourceTestInterface, so R8 shrinks them out.
One concrete fix lead this adds: the proguard config that does keep these types, proguard_project_references.cfg ($(_ProguardProjectConfiguration)), is explicitly excluded from R8 on trimmable NativeAOT in Xamarin.Android.Common.targets:
<_ProguardConfigurationInclude="$(_ProguardProjectConfiguration)"Condition=" '$(AndroidLinkTool)' != '' and ('$(_AndroidRuntime)' != 'NativeAOT' or '$(_AndroidTypeMapImplementation)' != 'trimmable') " />
That file already contains the correct, complete keeps:
-keep class com.xamarin.android.test.msbuildtest.JavaSourceTestInterface { *; }
-keep class com.xamarin.android.test.msbuildtest.JavaSourceJarTest { *; }
-keep class com.xamarin.android.test.msbuildtest.JavaSourceTestExtension { *; }
…while the trimmable replacement proguard_project_primary.cfg is empty apart from # ACW keep rules are generated from NativeAOT ILC metadata.
So in addition to the fix directions already listed here, one option is simply to stop excluding proguard_project_references.cfg on trimmable NativeAOT (it already carries the right keeps via the standard ACW-map path) — though the exclusion was deliberate, so it's worth confirming it doesn't over-keep and undermine trimming/size.
This surfaces once NativeAOT defaults to the trimmable typemap. Opened #11835 for the binding-specific repro before finding this issue; closing that as a duplicate of this one. Acceptance criteria there: BindingWithAndroidJavaSource(NativeAOT) should also pass.
Activity
simonrozsival commentedon Jul 1, 2026
Another repro of this same bug (R8 shrinking Java out on trimmable NativeAOT due to missing keeps), from a different angle — bound library Java types rather than unreferenced
Bind=Falseuser Java:BindingWithAndroidJavaSource(NativeAOT)fails:Verified via binlog that R8 does receive the library jars (
obj/Release/lp/1/jl/libs/*.jar) asJavaLibrariesToEmbed, but no keep rule preservesJavaSourceJarTest/JavaSourceTestExtension/JavaSourceTestInterface, so R8 shrinks them out.One concrete fix lead this adds: the proguard config that does keep these types,
proguard_project_references.cfg($(_ProguardProjectConfiguration)), is explicitly excluded from R8 on trimmable NativeAOT inXamarin.Android.Common.targets:That file already contains the correct, complete keeps:
…while the trimmable replacement
proguard_project_primary.cfgis empty apart from# ACW keep rules are generated from NativeAOT ILC metadata.So in addition to the fix directions already listed here, one option is simply to stop excluding
proguard_project_references.cfgon trimmable NativeAOT (it already carries the right keeps via the standard ACW-map path) — though the exclusion was deliberate, so it's worth confirming it doesn't over-keep and undermine trimming/size.This surfaces once NativeAOT defaults to the trimmable typemap. Opened #11835 for the binding-specific repro before finding this issue; closing that as a duplicate of this one. Acceptance criteria there:
BindingWithAndroidJavaSource(NativeAOT)should also pass.[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
[TrimmableTypeMap][NativeAOT] Build plumbing fixes (#11989)
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
[tests] Cover interface method desugaring parity (#12610)