R8: unconditional -dontobfuscate makes Google Play's Feb 2027 25% obfuscation requirement unachievable
- Dominant language
- C#
- Stars
- 2.1k
- Forks
- 579
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 252
Description
### Summary
`proguard_xamarin.cfg` — the config the SDK generates and passes to R8 on every `$(AndroidLinkTool)=r8` build — contains an unconditional `-dontobfuscate`. There is no supported property to remove or override it, and no R8 CLI flag that can undo it. The result is that **a .NET for Android app cannot produce an obfuscated `classes*.dex` at all.**
That was a cosmetic difference until this month. Google Play has now announced a code-optimization requirement that makes it a compliance problem:
> From February 2027, apps and games on Google Play will need to meet minimum optimization requirements. You will need to achieve a minimum of **25% optimization, obfuscation and shrinking** for any app uploads to Play Console.
—
The requirement is scoped to non-negligible DEX: **>10 MB for apps**, >50 MB for games. Announcement: . Apps that miss the thresholds "may see store visibility impact" and may get a warning shown on their store listing.
Because obfuscation is one of the three metrics and .NET for Android pins it at zero, **every .NET MAUI / .NET for Android app above 10 MB of DEX fails that metric by construction**, regardless of how the developer configures their project.
### Measured on a shipping app
.NET 10, `Microsoft.Android.Sdk` **36.1.69**, `$(AndroidLinkTool)=r8`, Release, built on `ubuntu-latest`. Measured on the signed APK actually shipped to users:
```
classes.dex 8,570,180
classes2.dex 8,354,084
classes3.dex 7,644,760
classes4.dex 1,745,504
----------
26,314,528 bytes = 25.1 MB DEX
```
R8 markers are present in all four, and full mode is in use:
```
~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,
"min-api":26,"pg-map-id":"3bb8354","r8-mode":"full","version":"8.11.18"}
```
So shrinking and optimization do run. Obfuscation does not — **zero** renamed class descriptors in any of the four files:
```
Landroidx/activity/R$id;
Landroidx/activity/result/contract/ActivityResultContracts$PickVisualMedia$MediaCapabilities$$ExternalSyntheticApiModelOutline0;
...
```
Count of descriptors matching a minified shape (`L(a-z0-9{1,2}/)+a-z0-9{1,2};`): **0**.
### Root cause
`proguard_xamarin.cfg` is an embedded resource in `Xamarin.Android.Build.Tasks.dll` and begins:
```
# This is Xamarin-specific (and enhanced) configuration.
-dontobfuscate
-keep class android.support.multidex.MultiDexApplication { (); }
-keep class net.dot.jni.** { *; (); }
-keep class mono.MonoRuntimeProvider* { *; (...); }
...
```
`R8.cs` writes it out inside `CreateResponseFile()` immediately before invoking `r8`, so no MSBuild target can get between generation and use. `_CalculateProguardConfigurationFiles` (`Xamarin.Android.Common.targets`) then includes it in `@(_ProguardConfiguration)`, which `Xamarin.Android.D8.targets` passes as `ProguardConfigurationFiles`, and `R8.cs` emits one `--pg-conf` per entry.
The resource carries no comment explaining the flag, and I could not find an issue or PR discussing the decision.
### Why the obvious workarounds do not work
**1. `$(AndroidR8ExtraArguments)` cannot force minification back on.** R8 8.11.18 (the version shipped in `tools/r8.jar`) has `--no-tree-shaking`, `--no-minification`, `--no-data-resources` and `--no-desugaring`, and **no positive counterpart to any of them** — there is no `--minification` / `--obfuscate`.
**2. Appending another `--pg-conf` cannot cancel it.** `-dontobfuscate` is order-independent and sticky, which I verified directly against the shipped `r8.jar` on a two-class fixture:
| configs passed | result |
| --- | --- |
| `b.cfg` (no `-dontobfuscate`) | `Ldemo/Helper;` and `secretMethodName` both renamed |
| `a.cfg` (`-dontobfuscate`) then `b.cfg` | both names survive |
| `b.cfg` then `a.cfg` (`-dontobfuscate`) **last** | both names survive |
**3. `$(ProguardConfigFiles)` "works" but is not a safe answer.** It replaces the entire default list, so `proguard_xamarin.cfg` is never passed and `-dontobfuscate` never reaches R8. But the same list also carries `proguard_project_primary.cfg` (the generated `-keep` for every Android Callable Wrapper) and `proguard_project_references.cfg`. Re-listing those by hand is required for the app to launch at all, means maintaining a copy of an SDK-internal keep file that drifts silently against the SDK, and fails as a **launch crash rather than a build error**. The property is also undocumented — it appears on no page under (which has zero hits for "obfuscat" or "minif" anywhere), and its only description in the tree is the `Common.targets` comment "to keep backwards compatibility".
### Note on #11709
(merged 2026-06-23) already deals with disallowed global options in *library* `proguard.txt` files, and states that "the SDK-generated configs (`proguard_xamarin.cfg`, `proguard_project_primary.cfg`, etc.) are left untouched". So the neighbouring code was revisited recently and the global `-dontobfuscate` was deliberately retained — which is why I am raising this as its own issue rather than assuming it is an oversight.
### Ask
Some supported way to opt in to R8 minification, with the SDK continuing to own the keeps that make it safe (ACWs, `mono.*`, `net.dot.jni.*`, bound types via `GenerateProguardConfiguration`). For example an `$(AndroidEnableObfuscation)` / `$(AndroidR8Minification)` property that omits `-dontobfuscate` from the generated config while leaving every generated `-keep` in place.
Two things that would help even without a full opt-in:
1. **Guidance.** If obfuscation is unsafe for .NET for Android for a structural reason, documenting that reason — and Microsoft's position on the February 2027 requirement — would let teams plan. Right now the build properties page does not mention obfuscation at all.
2. **A supported seam**, if the answer is "bring your own config": documenting `$(ProguardConfigFiles)` and giving a way to append to `@(_ProguardConfiguration)` minus the one file, so nobody has to hand-copy generated keeps.
Happy to test a fix against a real 25 MB-DEX app and report the resulting Play Console numbers.
### Environment
- .NET 10, `Microsoft.Android.Sdk.Windows` **36.1.69** (`tools/Version.commit`: `d549e1d`, `release/10.0.1xx`)
- R8 **8.11.18**, `r8-mode: full`, `min-api: 26`
- Release build on `ubuntu-latest`; `$(AndroidLinkTool)=r8`, `$(JavaMaximumHeapSize)=6G`
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.