Take the phone's aapt2 from the SDK the job installs rather than the workload's
ci / build and test (push) Successful in 1m49s
ci / android head (push) Failing after 7s
ci / api image (push) Successful in 30s

The android job got past restore and died in the .NET Android SDK's own tooling resolution:

  warning : An error occurred trying to start process
            '.../packs/Microsoft.Android.Sdk.Linux/36.1.69/tools/Linux/aapt2' … No such file or directory
  error XA0111: Unsupported version of AAPT2 found at path '.../tools/Linux'

The error names the wrong problem. Nothing was found, so nothing had a version, and XA0111 points at
an Aapt2ToolPath in the project file that has never been set. The warning above it is the real message
and it is only a warning.

The pack was incomplete, and on this runner it would have stayed that way: act's host executor keeps
/usr/share/dotnet between runs, and `dotnet workload install` reads the installed-workload records and
does nothing when android is listed, whatever is on disk. So two changes, each of which stands alone.

The build and the packaging step are now given -p:Aapt2ToolPath pointing at build-tools, which this job
installs itself and can therefore vouch for. That is already the aapt2 the packaging step shells out to
for `dump badging`, so this makes one tool of what were two, and the manifest the feed publishes is now
read by the binary that wrote it. Checked rather than assumed: build-tools 36.0.0 answers aapt2 2.20 and
.NET for Android 36.1.43 builds and packages this head against it with no complaint. The version is
named once, in the step's env, because three things now depend on it agreeing with itself.

And the workload step probes for the pack file that went missing and repairs the workload when it is
absent. The probe is a witness rather than the point — the build no longer touches that binary — but a
5 MB file near the end of a 130 MB package is what a truncated extraction loses first, and r8.jar and
manifestmerger.jar are what it loses next.
This commit is contained in:
2026-08-04 22:32:52 +02:00
parent 7e0a1b2af8
commit 65256fa337
2 changed files with 101 additions and 4 deletions
+31
View File
@@ -574,6 +574,37 @@ The lasting hazard is the first paragraph and not the fix. Any change to a share
to a lock file this repository cannot verify from a machine without the Android workload, and it will go
on being noticed later than every other one.
**`dotnet workload install` is not a repair, and on a persistent runner that is the difference between a
one-off failure and a permanent one.** It reads the installed-workload records and does nothing when the
workload is listed, whatever is actually on disk. This runner is act's host executor — the checkout path
is `/root/.cache/act/<hash>/hostexecutor`, so `/usr/share/dotnet` survives between runs — and it reached
a state where the android pack's targets were present and `tools/Linux/aapt2` was not. Every later run
reinstalled nothing and failed identically:
```
warning : An error occurred trying to start process '…/packs/Microsoft.Android.Sdk.Linux/36.1.69/tools/Linux/aapt2' … No such file or directory
error XA0111: Unsupported version of AAPT2 found at path '…/tools/Linux'
```
**The error names the wrong problem**, which is the part worth remembering: nothing was found, so nothing
had a version, and XA0111 sends you to `Aapt2ToolPath` in the project file — where there is nothing to
find. The warning above it is the real message and it is a warning.
Two changes, and each stands on its own. The build is now given
`-p:Aapt2ToolPath=$ANDROID_HOME/build-tools/<v>`, so it uses Google's aapt2 from the SDK the job installs
itself rather than the workload's copy — the same binary the packaging step already shells out to for
`dump badging`, which makes one tool of what were two and means the manifest the feed publishes is read
by the binary that wrote it. Verified compatible rather than assumed: build-tools 36.0.0 answers
`aapt2 2.20-13193326` and .NET for Android 36.1.43 builds and packages the head against it without
complaint. And the workload step now probes for that pack file and runs `dotnet workload repair` when it
is absent — the probe is a witness rather than the point, since a 5 MB file near the end of a 130 MB
package is what a truncated extraction loses first, and `r8.jar` and `manifestmerger.jar` are what it
loses next.
The pack layout is host-shaped, which the probe has to know: the Linux pack keeps its host binaries under
`tools/Linux/`, the Windows one puts `aapt2.exe` straight in `tools/`. A probe written on Windows and
matching `tools/aapt2*` finds nothing on Linux and repairs a pack that is fine.
**A Docker `ARG` named `VERSION` silently sets MSBuild's `Version`.** An `ARG` is an environment variable
for the rest of the stage, MSBuild reads environment variables as global properties, and MSBuild property
names are case-insensitive — so `ARG VERSION` in a build stage sets `Version` for every project built in