Skip to content

Touching the project file empties ProguardConfigFiles in libraryprojectimports.cache, so R8 receives no library consumer rules on an incremental Release build #12965

Description

@rojohn10

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:

  1. _ResolveLibraryProjectImports (tools/Xamarin.Android.EmbeddedResource.targets) lists $(MSBuildProjectFullPath) among its Inputs, so any timestamp change on the project file makes the target re-run.
  2. When ResolveLibraryProjectImports re-runs over libraries that are already extracted, it writes the cache with <ProguardConfigFiles /> empty.
  3. _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

  1. 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.
  2. dotnet restore <app>.csproj
  3. Populate the cache:
    dotnet msbuild <app>.csproj -t:_ExtractLibraryProjectImports -p:Configuration=Release -p:TargetFramework=net10.0-android
    
  4. Open obj/Release/net10.0-android/libraryprojectimports.cache and count the children of <ProguardConfigFiles>. (34 here.)
  5. Touch the .csproj. Change nothing else — not one byte of content.
  6. Re-run exactly the command from step 3. It succeeds.
  7. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssues that need to be assigned.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions