Symptom
Diagnosing link-time issues with fbuild is harder than it needs to be because two pieces of standard build-time observability are missing:
fbuild build -v does NOT print the link command line. -v exposes per-file compile progress and a few summary lines, but the actual ld/gcc -o firmware.elf … invocation is never shown. This makes it impossible to compare fbuild's link command against PlatformIO's (which pio run -v prints in full).
- No
firmware.map is written next to firmware.elf. PlatformIO writes one by default (-Wl,-Map=firmware.map), and the first line of that map is canonical:
Archive member included to satisfy reference by file (symbol)
It is the primary tool for answering "why is this symbol in my ELF?" When a user passes -Wl,-Map= in build_flags, fbuild appears to honor it for that single build, but if any subsequent rebuild redirects the output path, the file gets clobbered.
Why this matters
I just spent ~45 minutes diagnosing a regression where fbuild produces a 10.1% larger AVR binary than PlatformIO (filed separately as #304). Both observability gaps slowed that down significantly:
- Without the link command line, I couldn't see whether fbuild was passing
.o files or .a files to ld (the root cause was that it passes .o files; PIO passes .a).
- Without a map file, I had to manually reconstruct the archive-vs-object decision by diffing
nm output between two ELFs.
PlatformIO's combination of -v printing the link command + an always-emitted firmware.map cuts that investigation to ~5 minutes.
Proposed fixes
A. -v should print the final link command
When -v is set, the post-link step (or whatever crate emits the link invocation) should eprintln! the full argv before running it, identically to how compile commands are printed today. Format suggestion (match PlatformIO's style for diffability):
Linking .build/<env>/release/firmware.elf
<linker> <flag> <flag> -o firmware.elf <object> <object> <archive>
B. Always emit firmware.map
Add -Wl,-Map=firmware.map to the default link flags when building for non-WASM targets. Should land in the same output directory as firmware.elf. The cost is essentially zero (~50-500 KB of plain text), and even on resource-constrained users' machines, it pays for itself the first time the user asks "what's bloating my binary."
Acceptable knob: a --no-map-file opt-out for users who insist on minimal artifacts; default ON.
C. (Bonus) Add fbuild diag <env> or fbuild build --print-link
A standalone subcommand or flag that prints what the link command would be without actually running it. Useful for CI integration that diffs link commands across versions or against PIO.
Test plan
- Unit / integration: capture stdout of
fbuild build -v -e attiny85 and assert it contains the substring firmware.elf followed by at least one .o/.a file path on the same line.
- Snapshot test: build a fixture project and
cat firmware.map, assert it starts with Archive member included to satisfy reference by file.
- CI: spot-check on three platforms (avr, teensy41, esp32dev) that the map file lands next to the ELF.
Related
Both fixes are small and self-contained; happy to PR them if there's a preference on which crate hosts the link-emission code.
Symptom
Diagnosing link-time issues with fbuild is harder than it needs to be because two pieces of standard build-time observability are missing:
fbuild build -vdoes NOT print the link command line.-vexposes per-file compile progress and a few summary lines, but the actualld/gcc -o firmware.elf …invocation is never shown. This makes it impossible to compare fbuild's link command against PlatformIO's (whichpio run -vprints in full).firmware.mapis written next tofirmware.elf. PlatformIO writes one by default (-Wl,-Map=firmware.map), and the first line of that map is canonical:Why this matters
I just spent ~45 minutes diagnosing a regression where fbuild produces a 10.1% larger AVR binary than PlatformIO (filed separately as #304). Both observability gaps slowed that down significantly:
.ofiles or.afiles told(the root cause was that it passes.ofiles; PIO passes.a).nmoutput between two ELFs.PlatformIO's combination of
-vprinting the link command + an always-emittedfirmware.mapcuts that investigation to ~5 minutes.Proposed fixes
A.
-vshould print the final link commandWhen
-vis set, the post-link step (or whatever crate emits the link invocation) shouldeprintln!the full argv before running it, identically to how compile commands are printed today. Format suggestion (match PlatformIO's style for diffability):B. Always emit
firmware.mapAdd
-Wl,-Map=firmware.mapto the default link flags when building for non-WASM targets. Should land in the same output directory asfirmware.elf. The cost is essentially zero (~50-500 KB of plain text), and even on resource-constrained users' machines, it pays for itself the first time the user asks "what's bloating my binary."Acceptable knob: a
--no-map-fileopt-out for users who insist on minimal artifacts; default ON.C. (Bonus) Add
fbuild diag <env>orfbuild build --print-linkA standalone subcommand or flag that prints what the link command would be without actually running it. Useful for CI integration that diffs link commands across versions or against PIO.
Test plan
fbuild build -v -e attiny85and assert it contains the substringfirmware.elffollowed by at least one.o/.afile path on the same line.cat firmware.map, assert it starts withArchive member included to satisfy reference by file.Related
build_info.jsonemitter (different bug, same observability surface).Both fixes are small and self-contained; happy to PR them if there's a preference on which crate hosts the link-emission code.