Android framework version
net10.0-android
Affected platform version
.NET SDK 10.0.400 · workload set 10.0.401 · Microsoft.Android.Sdk.Windows 36.1.69 · R8 8.11.18 · Windows 11
Description
Touching the application project file — a timestamp change, with no change to its contents — causes $(IntermediateOutputPath)libraryprojectimports.cache to be rewritten with an empty, self-closing <ProguardConfigFiles />. Every referenced library's consumer proguard.txt therefore disappears from R8's configuration list, on a build that exits 0 with no errors and no warnings.
The chain is:
_ResolveLibraryProjectImports (tools/Xamarin.Android.EmbeddedResource.targets) lists $(MSBuildProjectFullPath) among its Inputs, so any timestamp change on the project file makes the target re-run.
- When
ResolveLibraryProjectImports re-runs over libraries that are already extracted, it writes the cache with <ProguardConfigFiles /> empty.
_ExtractLibraryProjectImports reads that cache and maps the element directly onto the item R8 consumes:
<ReadLibraryProjectImportsCache CacheFile="$(_AndroidLibraryProjectImportsCache)">
...
<Output TaskParameter="ProguardConfigFiles" ItemName="ProguardConfiguration" />
</ReadLibraryProjectImportsCache>
So R8 shrinks and obfuscates without any library's -keep rules. Anything a library resolves by name at runtime — reflection, Class.forName, a Glide module, a Gson type adapter — can be renamed or removed. The build is green; the failure appears on the device, in Release only.
This is easy to miss for three reasons: a clean build is always correct, so it does not reproduce under the first thing anyone tries; there is no warning naming the dropped rules; and the trigger is ordinary editor and version-control activity, because any timestamp change on the project file is sufficient.
Steps to Reproduce
- A
net10.0-android application referencing AAR/jar libraries that ship consumer ProGuard rules (in our case AndroidX, Glide, Gson, protobuf and kotlinx — 34 such files), built Configuration=Release with AndroidLinkTool=r8.
dotnet restore <app>.csproj
- Populate the cache:
dotnet msbuild <app>.csproj -t:_ExtractLibraryProjectImports -p:Configuration=Release -p:TargetFramework=net10.0-android
- Open
obj/Release/net10.0-android/libraryprojectimports.cache and count the children of <ProguardConfigFiles>. (34 here.)
- Touch the
.csproj. Change nothing else — not one byte of content.
- Re-run exactly the command from step 3. It succeeds.
- Open the cache again.
<ProguardConfigFiles /> is now self-closing: 0 entries.
Expected: re-running _ResolveLibraryProjectImports over already-extracted libraries leaves ProguardConfigFiles populated — either by re-discovering the rules from the extracted lp\<n>\jl\proguard.txt files, or by preserving the existing cache entries when extraction is skipped. A build that reports success should not hand R8 a smaller rule set than the project supplies.
Actual: 34 entries become 0, and R8 runs without them.
Did you find any workaround?
Yes, and it is a workaround rather than a fix. We rebuild R8's configuration list ourselves from the extracted lp\<n>\jl\ tree instead of trusting the cache, and we added a build check that fails the build when a library's consumer rules are missing from R8's list. That turns a silent loss into a build error, but it does not stop the cache being emptied.
A simpler mitigation for anyone hitting this: a clean build is always correct, so deleting obj before a Release build avoids it — at the cost of the incremental build.
Relevant log output
# step 4 - after a build that populated the cache
<ProguardConfigFiles> 34 entries
...obj\Release\net10.0-android\lp\100\jl\proguard.txt
...obj\Release\net10.0-android\lp\103\jl\proguard.txt
...obj\Release\net10.0-android\lp\105\jl\proguard.txt
...
...obj\Release\net10.0-android\lp\172\jl\proguard.txt
# step 5 - touch the .csproj, contents unchanged
# step 6 - re-run the same target
msbuild exit code 0, no errors, no warnings
# step 7 - the same cache file
<ProguardConfigFiles /> self-closing, 0 entries
Possibly related but, I believe, distinct: #7447 also mentions a missing aapt_rules.txt, but that is Classic Xamarin.Android on VS 17.3.5 and the symptom there is a build failure during AOT. Here the build succeeds and the loss is silent.
I am filing a second, separate issue about aapt_rules.txt being registered in FileWrites only inside _CreateBaseApkWithAapt2, which has the same consequence (R8 receiving fewer rules than the project supplies) through a different mechanism.
Android framework version
net10.0-android
Affected platform version
.NET SDK 10.0.400 · workload set 10.0.401 ·
Microsoft.Android.Sdk.Windows36.1.69 · R8 8.11.18 · Windows 11Description
Touching the application project file — a timestamp change, with no change to its contents — causes
$(IntermediateOutputPath)libraryprojectimports.cacheto be rewritten with an empty, self-closing<ProguardConfigFiles />. Every referenced library's consumerproguard.txttherefore disappears from R8's configuration list, on a build that exits 0 with no errors and no warnings.The chain is:
_ResolveLibraryProjectImports(tools/Xamarin.Android.EmbeddedResource.targets) lists$(MSBuildProjectFullPath)among itsInputs, so any timestamp change on the project file makes the target re-run.ResolveLibraryProjectImportsre-runs over libraries that are already extracted, it writes the cache with<ProguardConfigFiles />empty._ExtractLibraryProjectImportsreads that cache and maps the element directly onto the item R8 consumes:So R8 shrinks and obfuscates without any library's
-keeprules. Anything a library resolves by name at runtime — reflection,Class.forName, a Glide module, a Gson type adapter — can be renamed or removed. The build is green; the failure appears on the device, in Release only.This is easy to miss for three reasons: a clean build is always correct, so it does not reproduce under the first thing anyone tries; there is no warning naming the dropped rules; and the trigger is ordinary editor and version-control activity, because any timestamp change on the project file is sufficient.
Steps to Reproduce
net10.0-androidapplication referencing AAR/jar libraries that ship consumer ProGuard rules (in our case AndroidX, Glide, Gson, protobuf and kotlinx — 34 such files), builtConfiguration=ReleasewithAndroidLinkTool=r8.dotnet restore <app>.csprojobj/Release/net10.0-android/libraryprojectimports.cacheand count the children of<ProguardConfigFiles>. (34 here.).csproj. Change nothing else — not one byte of content.<ProguardConfigFiles />is now self-closing: 0 entries.Expected: re-running
_ResolveLibraryProjectImportsover already-extracted libraries leavesProguardConfigFilespopulated — either by re-discovering the rules from the extractedlp\<n>\jl\proguard.txtfiles, or by preserving the existing cache entries when extraction is skipped. A build that reports success should not hand R8 a smaller rule set than the project supplies.Actual: 34 entries become 0, and R8 runs without them.
Did you find any workaround?
Yes, and it is a workaround rather than a fix. We rebuild R8's configuration list ourselves from the extracted
lp\<n>\jl\tree instead of trusting the cache, and we added a build check that fails the build when a library's consumer rules are missing from R8's list. That turns a silent loss into a build error, but it does not stop the cache being emptied.A simpler mitigation for anyone hitting this: a clean build is always correct, so deleting
objbefore a Release build avoids it — at the cost of the incremental build.Relevant log output
Possibly related but, I believe, distinct: #7447 also mentions a missing
aapt_rules.txt, but that is Classic Xamarin.Android on VS 17.3.5 and the symptom there is a build failure during AOT. Here the build succeeds and the loss is silent.I am filing a second, separate issue about
aapt_rules.txtbeing registered inFileWritesonly inside_CreateBaseApkWithAapt2, which has the same consequence (R8 receiving fewer rules than the project supplies) through a different mechanism.