Summary
Firmware builds are not reproducible: building the same source tree on two machines produces different firmware.bin and resources-image.lfs, because resource packing depends on filesystem iteration order.
Why it matters
Reproducible builds let a developer rebuild a release tag and confirm the result is bit-for-bit identical to the official build : proving the build environment is correct and that the official binary corresponds exactly to its published source (no tampering, no hidden divergence). Today that is impossible.
Root cause
Two build scripts iterate input directories in filesystem-dependent order:
utils/qoi_packer.py : os.listdir() over the GUI icons. The packed qoi.data atlas (stored in the LittleFS resource image) and the generated per-icon Resource offsets therefore vary by host.
utils/translations_and_fonts/lang.py : Path.iterdir() over the language directories, later emitted in dict-insertion order, so translation/font data varies by host.
os.listdir() and Path.iterdir() return entries in arbitrary, filesystem-dependent order (APFS, ext4, etc. all differ), so identical source builds to different binaries on different machines.
Evidence
- Packing the 256-icon set under different input orderings produces a different atlas each time; sorting first yields one identical result regardless of order.
- The same 256 icons enumerate in a different
os.listdir() order on APFS vs an ext4/overlay filesystem (e90a158a vs b450fd54); since the packer preserves that order, the packed qoi.data (and the resource image) necessarily differ. Same-machine builds are byte-identical, so directory order is the only cross-host variable.
- The build already runs in a pinned Docker image (which fixes the toolchain), but the source is mounted, not baked into the image, so
os.listdir() reads the host filesystem's order : the container cannot canonicalize it.
Proposed fix
Sort both directory walks so output depends only on source content. Pull request: #5320.
Summary
Firmware builds are not reproducible: building the same source tree on two machines produces different
firmware.binandresources-image.lfs, because resource packing depends on filesystem iteration order.Why it matters
Reproducible builds let a developer rebuild a release tag and confirm the result is bit-for-bit identical to the official build : proving the build environment is correct and that the official binary corresponds exactly to its published source (no tampering, no hidden divergence). Today that is impossible.
Root cause
Two build scripts iterate input directories in filesystem-dependent order:
utils/qoi_packer.py:os.listdir()over the GUI icons. The packedqoi.dataatlas (stored in the LittleFS resource image) and the generated per-iconResourceoffsets therefore vary by host.utils/translations_and_fonts/lang.py:Path.iterdir()over the language directories, later emitted in dict-insertion order, so translation/font data varies by host.os.listdir()andPath.iterdir()return entries in arbitrary, filesystem-dependent order (APFS, ext4, etc. all differ), so identical source builds to different binaries on different machines.Evidence
os.listdir()order on APFS vs an ext4/overlay filesystem (e90a158avsb450fd54); since the packer preserves that order, the packedqoi.data(and the resource image) necessarily differ. Same-machine builds are byte-identical, so directory order is the only cross-host variable.os.listdir()reads the host filesystem's order : the container cannot canonicalize it.Proposed fix
Sort both directory walks so output depends only on source content. Pull request: #5320.