Skip to content

build(android): pin the macOS aapt2 artifact so dependency verification passes off-Linux#854

Merged
ryanbr merged 1 commit into
ryanbr:mainfrom
vishk23:build/macos-aapt2-pin
Jul 27, 2026
Merged

build(android): pin the macOS aapt2 artifact so dependency verification passes off-Linux#854
ryanbr merged 1 commit into
ryanbr:mainfrom
vishk23:build/macos-aapt2-pin

Conversation

@vishk23

@vishk23 vishk23 commented Jul 27, 2026

Copy link
Copy Markdown

The problem

com.android.tools.build:aapt2 is 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-metadata run 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 pins aapt2-8.5.2-11315950-linux.jar and nothing else. On macOS every build fails at :app:processFullDebugResources:

Execution failed for task ':app:processFullDebugResources'.
> Could not isolate parameters ... of artifact transform AarResourcesCompilerTransform
   > Dependency verification failed for configuration ':app:detachedConfiguration2'
     One artifact failed verification: aapt2-8.5.2-11315950-osx.jar
       (com.android.tools.build:aapt2:8.5.2-11315950) from repository Google

This blocks the Android module outright on a Mac — compileFullDebugKotlin and testFullDebugUnitTest both die before they run, so the Kotlin half of the cross-platform parity contract cannot be verified locally at all. android.yml is disabled by default, so nothing catches it: a macOS contributor's first ./gradlew invocation fails, and the message points at a Gradle troubleshooting page rather than at the cause.

The change

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.

It sits between -linux.jar and .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:

V=8.5.2-11315950
B=https://dl.google.com/dl/android/maven2/com/android/tools/build/aapt2/$V
curl -sS "$B/aapt2-$V-osx.jar.sha256"
curl -sS -o /tmp/aapt2.jar "$B/aapt2-$V-osx.jar"
shasum -a 256 /tmp/aapt2.jar
  • An independent download hashes to fbcece1a…ff212f.
  • That matches Google's published .sha256, .sha512, .sha1 and .md5 sidecars — all four.
  • Control: the identical procedure run against -linux.jar reproduces d8cfecdc…, 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.
  • The jar contains a universal Mach-O (x86_64 + arm64), so it runs natively on Apple silicon.

android/README.md gets 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 -osx is added. Google publishes a -windows variant 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:

Command Before After
./gradlew compileFullDebugKotlin fails at processFullDebugResources BUILD SUCCESSFUL
./gradlew testFullDebugUnitTest fails at processFullDebugResources 3051 / 3052 pass, 5 skipped

Build configuration only — no BLE, protocol, analytics, schema or UI surface is touched.

The one failing test is pre-existing and unrelated to this change
com.noop.ui.SyncChipStateTest > lastSyncedAt_takesPriorityOverHistorySyncExperimental
java.lang.IllegalStateException: NoopApplication is not attached

The test resolves a sub-60s age, and that "now" branch of shortSyncAgo returns uiString(R.string.l10n_today_screen_sync_chip_now_…), which routes through NoopApplication.localizedString — not available under plain JVM unit tests. Its sibling lastSyncedAt_isSyncedWithAgeText passes because now - 65 lands in the "1m" branch, which is pure string math.

TodayScoring.kt and SyncChipStateTest.kt are both byte-identical to main here, so this is not introduced by this PR. It has simply never run anywhere, since android.yml is disabled by default. The fix is to move the test off the resource-backed branch (now - 5now - 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

…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 ryanbr left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 -linux pin is untouched.
  • aapt2 really is the only platform-classified artifact — it is the sole -linux/-osx/-windows name 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 -windows out 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.

@ryanbr
ryanbr merged commit cca23d6 into ryanbr:main Jul 27, 2026
2 checks passed
ryanbr added a commit that referenced this pull request Jul 27, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants