Skip to content

Build libdartjni.so without a GNU build ID - #49

Merged
aquasolterra merged 2 commits into
mainfrom
deterministic-dartjni
Sep 5, 2026
Merged

Build libdartjni.so without a GNU build ID#49
aquasolterra merged 2 commits into
mainfrom
deterministic-dartjni

Conversation

@aquasolterra

Copy link
Copy Markdown
Member

Two builds of the same tag came out different, and the difference was 20 bytes: the APK published from CI carries 1cf532bd… in libdartjni.so's build ID and one built locally carries 109e9b91…. cmp -l puts every differing byte at offsets 737-756, which is exactly the hash inside .note.gnu.build-id. Nothing else in the library differs, and nothing here reads that ID.

package:jni compiles the library through CMake, so the flag goes in via the root Gradle file, scoped to that one subproject -- it is the only module here building native code of its own, and a linker flag reaching libraries nobody has looked at is a change nobody has looked at either. Ciyue arrived at the same fix after F-Droid reported the same failure (mumu-lhl/Ciyue c07f8c8).

Why it is worth a commit of its own: with binary: in an F-Droid recipe, F-Droid rebuilds a release from source and compares it against the APK published here, and ships this project's signed APK when they match -- so somebody moving from a GitHub download to F-Droid keeps their install and their database instead of exporting, uninstalling and importing. The comparison copies the signature and checks it still verifies, which v2 signatures make equivalent to byte equality, so 20 bytes is as fatal as a megabyte.

Verified after the change: no .note.gnu.build-id section remains, the file is 120 bytes smaller, and every other section keeps its exact size. .rodata and .data.rel.ro are byte-identical to the previous build; .text is not, because removing the note shifts the sections (-40 and -32 bytes respectively) and the PC-relative distances between them move with it. No code was added or removed.

Not yet verified, and the reason not to merge this before someone does: path_provider_android reaches the database path through this library, so a broken one would fail at startup rather than at build time, and there was no phone attached.

Two builds of the same tag came out different, and the difference was 20 bytes:
the APK published from CI carries `1cf532bd…` in `libdartjni.so`'s build ID and
one built locally carries `109e9b91…`. `cmp -l` puts every differing byte at
offsets 737-756, which is exactly the hash inside `.note.gnu.build-id`. Nothing
else in the library differs, and nothing here reads that ID.

`package:jni` compiles the library through CMake, so the flag goes in via the
root Gradle file, scoped to that one subproject -- it is the only module here
building native code of its own, and a linker flag reaching libraries nobody has
looked at is a change nobody has looked at either. Ciyue arrived at the same fix
after F-Droid reported the same failure (mumu-lhl/Ciyue c07f8c8).

Why it is worth a commit of its own: with `binary:` in an F-Droid recipe, F-Droid
rebuilds a release from source and compares it against the APK published here,
and ships *this project's* signed APK when they match -- so somebody moving from
a GitHub download to F-Droid keeps their install and their database instead of
exporting, uninstalling and importing. The comparison copies the signature and
checks it still verifies, which v2 signatures make equivalent to byte equality,
so 20 bytes is as fatal as a megabyte.

Verified after the change: no `.note.gnu.build-id` section remains, the file is
120 bytes smaller, and every other section keeps its exact size. `.rodata` and
`.data.rel.ro` are byte-identical to the previous build; `.text` is not, because
removing the note shifts the sections (-40 and -32 bytes respectively) and the
PC-relative distances between them move with it. No code was added or removed.

Not yet verified, and the reason not to merge this before someone does:
`path_provider_android` reaches the database path through this library, so a
broken one would fail at startup rather than at build time, and there was no
phone attached.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The build-ID change alters nothing a user can see, and the changelog says so in
those words rather than inventing an improvement. What it is for belongs there
too, briefly: it is the release from which two builds of one tag come out
identical, so an F-Droid recipe can point `binary:` at it. The releases up to
1.11.0 cannot be verified that way -- their `libdartjni.so` carries a build ID
that differs per machine.

The build number goes to 13 because Android refuses a lower versionCode than
the one installed, and the release workflow refuses a tag that disagrees with
this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@aquasolterra
aquasolterra merged commit a22a35c into main Sep 5, 2026
3 checks passed
@aquasolterra
aquasolterra deleted the deterministic-dartjni branch September 5, 2026 08:47
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.

1 participant