build(android): pin the macOS aapt2 artifact so dependency verification passes off-Linux#854
Conversation
…on passes off-Linux
com.android.tools.build:aapt2 is published as one jar per host OS, and Gradle only
resolves the variant matching the machine it runs on -- so a --write-verification-metadata
run can only ever record that host's variant. The metadata introduced in v9.1.1 was
generated on the Linux CI runner, so it pins aapt2-8.5.2-11315950-linux.jar and nothing
else, and every macOS build dies at :app:processFullDebugResources:
Dependency verification failed for configuration ':app:detachedConfiguration2'
One artifact failed verification: aapt2-8.5.2-11315950-osx.jar
That blocks the Android module outright on macOS, which in turn blocks locally verifying
the Kotlin half of the cross-platform parity contract.
Add the -osx variant additively, leaving the -linux pin untouched. aapt2 is the only
platform-classified artifact in the file, so this is the whole fix.
The checksum was verified from the publisher, not from Gradle's failure report: an
independent download hashes to fbcece1a...ff212f, matching Google's published .sha256,
.sha512, .sha1 and .md5 sidecars. Running the same procedure against -linux.jar
reproduces the d8cfecdc... pin already committed here -- the control showing the method
is sound. The jar carries a universal Mach-O (x86_64 + arm64), so it runs natively on
Apple silicon.
android/README.md documents the case, since it is the one situation where editing
verification-metadata.xml by hand is correct rather than a bypass.
Verified on macOS (arm64) with JDK 17:
./gradlew compileFullDebugKotlin -> BUILD SUCCESSFUL
./gradlew testFullDebugUnitTest -> 3051/3052 pass, 5 skipped
The single failure is pre-existing and unrelated to this change: SyncChipStateTest's
lastSyncedAt_takesPriorityOverHistorySyncExperimental resolves a sub-60s age, and that
"now" branch calls uiString(), which throws "NoopApplication is not attached" under plain
JVM unit tests. TodayScoring.kt and SyncChipStateTest.kt are both byte-identical to
upstream/main, so it is upstream's and is left for a separate change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ryanbr
left a comment
There was a problem hiding this comment.
Verified independently rather than taking the PR's word — which matters more than usual for a change to a trust file.
The checksum is correct. Fetched Google's published digest myself:
Google aapt2-8.5.2-11315950-osx.jar.sha256 : fbcece1a…ff212f
this PR : fbcece1a…ff212f MATCH
And the control holds, which is what makes it trustworthy. The same two commands against -linux.jar return d8cfecdc…021edb, byte-identical to the pin already committed in this repo. The method reproduces a value the project already trusts, so the new one is trustworthy by the same standard. Designing the change so a reviewer could re-run that in two commands is the right instinct for a verification file, and refusing to paste the "actual" value out of Gradle's error is exactly right — that value is what verification is challenging.
Also checked:
- Purely additive — 0 deletions in the whole PR; the
-linuxpin is untouched. aapt2really is the only platform-classified artifact — it is the sole-linux/-osx/-windowsname in the file, so this is the complete fix, not the first of several.- Placement is right —
-linux.jar,-osx.jar,.pom, so a regeneration will not churn the diff. - Leaving
-windowsout is the correct call. Pinning an untested checksum into a trust file would undercut the point of the file; better to wait for someone who can verify it.
The README subsection earns its place — without it this reads as a violation of the existing "never hand-edit" rule, when it is in fact the one case where hand-editing is correct.
Worth noting what this unblocks: combined with #828 (the missing DAO fake overrides, merged earlier today), a macOS contributor can run testFullDebugUnitTest again. Both halves of that blockage are now cleared — @jbergler hit exactly this on #831 and left the Android-test box unticked because of it, and it is worth re-running there.
One question, not blocking: your run reports 3051 / 3052 passing. Which one fails? Two other PRs today reported a pre-existing SyncChipStateTest failure (NoopApplication is not attached) reproducing on pristine main, so I suspect the same — but worth confirming it is that and not something this surfaces.
Approving.
…the rename is for I renamed workoutHrDeviceId -> workoutHrDeviceIds and updated the KOTLIN resolver test while missing its SWIFT twin, which still called the old name — a compile break in StrandTests. Nothing caught it: StrandTests runs only under xcodebuild on macOS, and app-build.yml is disabled. Anyone running tests locally on a Mac would have hit it, which #854 has just made possible again. The four existing cases keep their expectations, now as single-element lists. Added the two cases none of them covered, which are the whole point of the change: an IMPORTED row on a multi-namespace install resolves to the full #814 union (the case the single-id resolver silently under-read), while a DETECTED row on that same install still resolves to exactly one id — the union must not leak into the branch where it would pull in a strap that never recorded the bout.
The problem
com.android.tools.build:aapt2is published as one jar per host OS —-linux.jar,-osx.jar,-windows.jar— and Gradle resolves only the variant matching the machine it runs on. A--write-verification-metadatarun can therefore only ever record the host it ran on.android/gradle/verification-metadata.xml(added in #683) was generated on the Linux CI runner, so it pinsaapt2-8.5.2-11315950-linux.jarand nothing else. On macOS every build fails at:app:processFullDebugResources:This blocks the Android module outright on a Mac —
compileFullDebugKotlinandtestFullDebugUnitTestboth die before they run, so the Kotlin half of the cross-platform parity contract cannot be verified locally at all.android.ymlis disabled by default, so nothing catches it: a macOS contributor's first./gradlewinvocation fails, and the message points at a Gradle troubleshooting page rather than at the cause.The change
Add the
-osxvariant additively, leaving the-linuxpin untouched.aapt2is the only platform-classified artifact in the file, so this is the whole fix.It sits between
-linux.jarand.pom, which is where Gradle's own ordering puts it, so a later regeneration will not churn the diff.How the checksum was verified
Deliberately not by pasting the "actual" value out of Gradle's failure report — that value is precisely what verification is challenging, so trusting it defeats the check. Instead, from the publisher:
fbcece1a…ff212f..sha256,.sha512,.sha1and.md5sidecars — all four.-linux.jarreproducesd8cfecdc…, the pin already committed in this repo. The method reproduces a checksum this project already trusts, which is what makes the new one trustworthy by the same standard — and it is a check a reviewer can re-run in two commands.x86_64 + arm64), so it runs natively on Apple silicon.android/README.mdgets a short subsection on this, placed next to the existing "never bypass verification or hand-edit" rule — because this is the one case where editing the file by hand is correct rather than a bypass, and without the note the change reads as a violation of that rule.Only
-osxis added. Google publishes a-windowsvariant too, but it is left out: it could not be tested here, and pinning an untested checksum into a trust file is the wrong instinct. Happy to add it if a Windows contributor verifies it.Verification
macOS 26.2 (arm64), JDK 17.0.19, Android SDK 34:
./gradlew compileFullDebugKotlinprocessFullDebugResources./gradlew testFullDebugUnitTestprocessFullDebugResourcesBuild configuration only — no BLE, protocol, analytics, schema or UI surface is touched.
The one failing test is pre-existing and unrelated to this change
The test resolves a sub-60s age, and that
"now"branch ofshortSyncAgoreturnsuiString(R.string.l10n_today_screen_sync_chip_now_…), which routes throughNoopApplication.localizedString— not available under plain JVM unit tests. Its siblinglastSyncedAt_isSyncedWithAgeTextpasses becausenow - 65lands in the"1m"branch, which is pure string math.TodayScoring.ktandSyncChipStateTest.ktare both byte-identical tomainhere, so this is not introduced by this PR. It has simply never run anywhere, sinceandroid.ymlis disabled by default. The fix is to move the test off the resource-backed branch (now - 5→now - 65), which preserves what it actually asserts — the priority ordering, not the age text. Left out to keep this PR to one concern; glad to send it separately if useful.🤖 Generated with Claude Code