Summary
On the trimmable typemap + NativeAOT path, R8 (release shrinking) removes bound library Java types from classes.dex because the proguard config that keeps them is excluded and the trimmable path's replacement keep-config is empty. This causes BindingWithAndroidJavaSource to fail (a library's Java class is missing from the app's dex), and more generally means user/library Java types packaged via a binding project can be shrunk out of a trimmable NativeAOT app.
This is currently latent on main because NativeAOT still defaults to the managed typemap. It surfaces once NativeAOT defaults to trimmable (see the stacked PR that makes trimmable the NativeAOT default).
Repro
Build tests, Xamarin.Android.Build.Tests:
BindingWithAndroidJavaSource(NativeAOT)
Failure:
`.../App/obj/Release/android/bin/classes.dex` should include `Lcom/xamarin/android/test/msbuildtest/JavaSourceJarTest;`!
Expected: True
But was: False
Minimal shape: a binding/class library that contributes a Java class (via @(AndroidJavaSource) / a bound jar) referenced by an app, built Release for NativeAOT with the trimmable typemap.
Root cause (verified via binlog)
- Release build ⇒ the dex is produced by R8 (shrinking on).
- R8 does receive the library jars (
obj/Release/lp/1/jl/libs/*.jar) as JavaLibrariesToEmbed — the inputs are correct.
- But no keep rule preserves the library Java types, so R8 shrinks
JavaSourceJarTest, JavaSourceTestExtension, and JavaSourceTestInterface out of the dex.
- The only proguard config that keeps them is
proguard_project_references.cfg ($(_ProguardProjectConfiguration)), which contains:
-keep class com.xamarin.android.test.msbuildtest.JavaSourceTestInterface { *; }
-keep class com.xamarin.android.test.msbuildtest.JavaSourceJarTest { *; }
-keep class com.xamarin.android.test.msbuildtest.JavaSourceTestExtension { *; }
…and it is excluded from R8 on trimmable NativeAOT in Xamarin.Android.Common.targets:
<_ProguardConfiguration Include="$(_ProguardProjectConfiguration)"
Condition=" '$(AndroidLinkTool)' != '' and ('$(_AndroidRuntime)' != 'NativeAOT' or '$(_AndroidTypeMapImplementation)' != 'trimmable') " />
- The trimmable path is meant to generate keeps into
proguard_project_primary.cfg instead, but on this build that file is empty — only:
# ACW keep rules are generated from NativeAOT ILC metadata.
The ACW-from-ILC generation does not cover raw java-source-jar classes or bound library Java types, so nothing keeps them.
Net: R8 gets the jars but no keep ⇒ library Java types are dropped from the dex.
Why it wasn't caught earlier
The original monolithic PR (#11617) had the same _ProguardProjectConfiguration exclusion and already defaulted NativeAOT to trimmable, and it did not skip BindingWithAndroidJavaSource. So the proguard-keep generation for trimmable NativeAOT appears to have regressed between #11617 and the current typemap-manager base branch (the extracted/refined stack), which now emits an empty proguard_project_primary.cfg for this scenario.
Suggested fixes (need a decision)
- (A) Stop excluding
proguard_project_references.cfg on trimmable NativeAOT. That file already contains the correct, complete keep rules produced by the standard ACW-map path. Simple one-line change. Risk: the exclusion was deliberate — confirm it doesn't over-keep and undermine trimming/size goals.
- (B) Make the trimmable-NativeAOT proguard generator (
GenerateNativeAotProguardConfiguration / _GenerateTrimmableTypeMapProguardConfiguration in Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets) emit keeps for library/bound Java types into the primary config. More work; preserves the intended design.
Recommend diffing the NativeAOT proguard generation between #11617 and the current base to see exactly what regressed, which should decide A vs B.
Environment
- Local
make prepare && make all build of the typemap-default-nativeaot branch.
- Verified by decompiling/binlog: R8 command included the
lp/1/jl/libs/*.jar inputs and --pg-conf proguard_xamarin.cfg / proguard_project_primary.cfg / aapt_rules.txt but not proguard_project_references.cfg; proguard_project_primary.cfg was empty apart from the comment.
Summary
On the trimmable typemap + NativeAOT path, R8 (release shrinking) removes bound library Java types from
classes.dexbecause the proguard config that keeps them is excluded and the trimmable path's replacement keep-config is empty. This causesBindingWithAndroidJavaSourceto fail (a library's Java class is missing from the app's dex), and more generally means user/library Java types packaged via a binding project can be shrunk out of a trimmable NativeAOT app.This is currently latent on
mainbecause NativeAOT still defaults to themanagedtypemap. It surfaces once NativeAOT defaults totrimmable(see the stacked PR that makes trimmable the NativeAOT default).Repro
Build tests,
Xamarin.Android.Build.Tests:Failure:
Minimal shape: a binding/class library that contributes a Java class (via
@(AndroidJavaSource)/ a bound jar) referenced by an app, built Release for NativeAOT with the trimmable typemap.Root cause (verified via binlog)
obj/Release/lp/1/jl/libs/*.jar) asJavaLibrariesToEmbed— the inputs are correct.JavaSourceJarTest,JavaSourceTestExtension, andJavaSourceTestInterfaceout of the dex.proguard_project_references.cfg($(_ProguardProjectConfiguration)), which contains:Xamarin.Android.Common.targets:proguard_project_primary.cfginstead, but on this build that file is empty — only:Net: R8 gets the jars but no keep ⇒ library Java types are dropped from the dex.
Why it wasn't caught earlier
The original monolithic PR (#11617) had the same
_ProguardProjectConfigurationexclusion and already defaulted NativeAOT to trimmable, and it did not skipBindingWithAndroidJavaSource. So the proguard-keep generation for trimmable NativeAOT appears to have regressed between #11617 and the current typemap-manager base branch (the extracted/refined stack), which now emits an emptyproguard_project_primary.cfgfor this scenario.Suggested fixes (need a decision)
proguard_project_references.cfgon trimmable NativeAOT. That file already contains the correct, complete keep rules produced by the standard ACW-map path. Simple one-line change. Risk: the exclusion was deliberate — confirm it doesn't over-keep and undermine trimming/size goals.GenerateNativeAotProguardConfiguration/_GenerateTrimmableTypeMapProguardConfigurationinMicrosoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets) emit keeps for library/bound Java types into the primary config. More work; preserves the intended design.Recommend diffing the NativeAOT proguard generation between #11617 and the current base to see exactly what regressed, which should decide A vs B.
Environment
make prepare && make allbuild of the typemap-default-nativeaot branch.lp/1/jl/libs/*.jarinputs and--pg-conf proguard_xamarin.cfg / proguard_project_primary.cfg / aapt_rules.txtbut notproguard_project_references.cfg;proguard_project_primary.cfgwas empty apart from the comment.