1"""Make the built image independent of where it was built.
3PlatformIO extra script. Wire it in with, in a project's platformio.ini:
5 extra_scripts = pre:../../tools/reproducible_build.py
9Every board reports a SHA-256 of its own firmware in its heartbeat, and CI keeps
10a registry mapping that digest back to the commit that produced it (see
11firmware/tools/which_firmware.py). That only works if the same source produces
12the same bytes on every machine.
14It did not. `ESP_ERROR_CHECK`, `assert` and the ESP log macros all expand
15`__FILE__`, and the compiler sees absolute paths, so the image carried strings
18 /home/aidan/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-spi.c
19 /home/aidan/STAR-daq/firmware/Hotfire_Code/common/SensorHotfireCore.h
21baked into the hashed bytes -- the username, the home directory and the checkout
22path. The same commit built by two people, or by CI, produced three different
23digests, so the digest identified the machine rather than the firmware.
25`-ffile-prefix-map=OLD=NEW` rewrites those paths at compile time (it covers
26`__FILE__` and the debug info both). GCC compares OLD as a literal prefix of the
27path it has already normalised, so the mapping has to be an absolute, canonical
28path -- which is why this is a script and not a line of `build_flags`: the header
29that leaks sits at `-I../common`, outside `$PROJECT_DIR`, and no interpolation
30available in platformio.ini can name its directory without a `..` that then
33Keep the replacement names stable. Changing `/star` or `/pio` changes every
34digest in the registry.
37from pathlib
import Path
44_MARKER = Path(
"firmware/tools/reproducible_build.py")
48 for candidate
in [start, *start.parents]:
49 if (candidate / _MARKER).is_file():
61 (env.subst(
"$PROJECT_CORE_DIR"),
"/piocore"),
62 (env.subst(
"$PROJECT_PACKAGES_DIR"),
"/pio"),
63 (str(REPO_ROOT)
if REPO_ROOT
else "",
"/star"),
73 old = str(Path(old).resolve())
77 flags.append(f
"-ffile-prefix-map={old}={new}")
80for _old, _new
in _MAPPINGS:
96_packages = env.subst(
"$PROJECT_PACKAGES_DIR")
98 _pkgdir = Path(_packages)
100 for _child
in sorted(_pkgdir.iterdir()):
101 if _child.is_dir()
and "@" in _child.name:
102 _add(_child,
"/pio/" + _child.name.split(
"@", 1)[0])
108env.Append(CCFLAGS=flags)
114 "[reproducible_build] WARNING: could not locate the repo root from "
115 f
"{env.subst('$PROJECT_DIR')}; this image will carry absolute source "
116 "paths and its digest will not match CI's"
118print(
"[reproducible_build] " +
" ".join(flags))