Skip to content

aapt_rules.txt is added to FileWrites only inside _CreateBaseApkWithAapt2, so IncrementalClean deletes the manifest keeps when the base APK is up to date #12966

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

Up front, so it is weighed correctly: this is derived from reading the shipped targets, not from an end-to-end reproduction. I have quoted the whole mechanism below and would be glad to be told I have misread it. (I have filed the reproduced one as #12965.)

In tools/Xamarin.Android.Aapt2.targets, both the ProguardConfiguration entry and the FileWrites registration for aapt_rules.txt are inside _CreateBaseApkWithAapt2:

<Target Name="_CreateBaseApkWithAapt2">
  <PropertyGroup>
    <_Aapt2ProguardRules Condition=" '$(AndroidLinkTool)' != '' ">$(IntermediateOutputPath)aapt_rules.txt</_Aapt2ProguardRules>
    ...
  </PropertyGroup>
  <Aapt2Link ... ProguardRuleOutput="$(_Aapt2ProguardRules)" />
  <ItemGroup Condition=" '$(_Aapt2ProguardRules)' != '' And Exists('$(_Aapt2ProguardRules)') ">
    <ProguardConfiguration Include="$(_Aapt2ProguardRules)" />
    <FileWrites Include="$(_Aapt2ProguardRules)" />
    <FileWrites Include="$(IntermediateOutputPath)android\*\aapt_rules.txt" />
  </ItemGroup>
</Target>

When the base APK is up to date and this target is skipped, aapt_rules.txt is claimed by nobody for that build. IncrementalClean in Microsoft.Common.CurrentVersion.targets then computes orphans as the prior build's writes minus the current build's writes, and deletes those under the intermediate path:

<_CleanOrphanFileWrites Include="@(_CleanPriorFileWrites)" Exclude="@(_CleanCurrentFileWrites)"/>
...
<FindUnderPath Path="$(IntermediateOutputPath)" Files="@(_CleanOrphanFileWrites)">
  <Output TaskParameter="InPath" ItemName="_CleanOrphanFileWritesInIntermediate"/>
</FindUnderPath>
<Delete Files="@(_CleanOrphanFileWritesInIntermediate);@(_CleanOrphanFileWritesInOutput)" ... />

aapt_rules.txt lives under $(IntermediateOutputPath), so it matches and is deleted.

Why this matters: that file holds the keeps aapt2 generates for every type named in AndroidManifest.xml — activities, services, receivers and providers, i.e. precisely the classes the Android runtime instantiates by name. If they are lost, R8 may rename or remove an entry point, and the application fails at launch or when the affected component is first reached.

The same skip also removes the file from ProguardConfiguration, so even before any deletion R8 does not receive it on that build.

Steps to Reproduce

  1. A net10.0-android application, Configuration=Release, AndroidLinkTool=r8.
  2. Build once, so that obj/<configuration>/<tfm>/aapt_rules.txt exists and is recorded in the write log.
  3. Build again under conditions where _CreateBaseApkWithAapt2 is skipped as up to date, with any target that runs IncrementalClean.
  4. Observe aapt_rules.txt.

Expected: the file survives, or it is registered in FileWrites from a scope that runs on every build. A file the build depends on should not become deletable by the clean step purely because the target that produced it was skipped.

Did you find any workaround?

We rebuild R8's configuration list ourselves rather than relying on the items the SDK contributes, and we added a build check that fails when aapt_rules.txt is missing from R8's list — so the condition is caught at build time rather than shipping. That addresses the symptom in our build only.

A possible fix, if the reading above is right: move the two aapt_rules.txt registrations out of _CreateBaseApkWithAapt2 into a target that runs unconditionally when $(AndroidLinkTool) is set. The existing Exists() guard already makes it safe to register a file produced by an earlier build, so both the FileWrites claim and the ProguardConfiguration entry would survive a skipped build.

Relevant log output

# No log is attached, deliberately: this report is a reading of the shipped targets
# rather than a captured failure. The two files quoted in the description are:
#
#   <SDK>/tools/Xamarin.Android.Aapt2.targets          _CreateBaseApkWithAapt2
#   <dotnet>/sdk/10.0.401/Microsoft.Common.CurrentVersion.targets   IncrementalClean
#
# Microsoft.Android.Sdk.Windows 36.1.69, .NET SDK 10.0.401.

One thing I noticed and am deliberately not reporting as a defect, in case it looks like an omission: the sibling <FileWrites Include="$(IntermediateOutputPath)android\*\aapt_rules.txt" /> points at a different path from $(_Aapt2ProguardRules) and appears to cover per-ABI copies. It matches nothing in a non-per-ABI build, which is harmless.

Possibly related but distinct: #7447 also mentions a missing aapt_rules.txt, but it is Classic Xamarin.Android on VS 17.3.5 and the symptom there is a build failure during AOT, not a silent loss of manifest keeps.

Activity

  1. jonathanpeppers commented on Oct 1, 2026

    @jonathanpeppers
    Member

    @rojohn10 is this duplicate of this:

    It was only merged into main recently.

  2. added
    need-infoIssues that need more information from the author.
    and removed
    needs-triageIssues that need to be assigned.
    on Oct 1, 2026
  3. dotnet-policy-service commented on Oct 1, 2026

    @dotnet-policy-service

    Hi @rojohn10. We have added the "need-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

  4. dotnet-policy-service commented on Oct 9, 2026

    @dotnet-policy-service

    Hi @rojohn10. Due to inactivity, we will be closing this issue. Please feel free to re-open this issue if the issue persists. For enhanced visibility, if over 7 days have passed, please open a new issue and link this issue there. Thank you.

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

    need-infoIssues that need more information from the author.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions