Skip to content

Apply the link-cwd fix to the remaining ARM linkers, and cut a release with #1268 + #1263 #1269

Description

@zackees

Context

#1268 (0dd68b24) fixed the teensy linker running in the fbuild daemon's inherited cwd — normally the user's project root — which made arm-none-eabi-gcc/collect2/lto-wrapper drop scratch files there. The user-visible symptom was a stray 0-byte file literally named -r appearing in the repo root after every clean build (downstream report: FastLED/FastLED#3867).

Two things remain: the same bug is still latent in every other ARM linker, and the fix is not in any release.


Part 1 — apply the same fix to the remaining ARM linkers

All of these pass None as the cwd to run_command in exactly the shape teensy did, so any linker-spawned helper writes scratch files into whatever cwd the daemon inherited:

File Line(s)
crates/fbuild-build-arm/src/generic_arm/arm_linker.rs 184–186 (response-file branch), 193 (direct-args branch)
crates/fbuild-build-arm/src/nrf52/nrf52_linker.rs 129–131
crates/fbuild-build-arm/src/renesas/renesas_linker.rs 134–136
crates/fbuild-build-arm/src/sam/sam_linker.rs 150–152
crates/fbuild-build-arm/src/silabs/silabs_linker.rs 137–139
crates/fbuild-build-arm/src/stm32/orchestrator/arduino_mbed.rs 289–291

I deliberately scoped #1268 to teensy because that is the only one I could reproduce and verify on the hardware I had. These are very likely to have the identical problem, but each needs checking rather than a blind sed.

Pattern to follow

link_cwd_for in crates/fbuild-build-arm/src/teensy/teensy_linker.rs is reusable as-is — consider lifting it to a shared spot (e.g. crates/fbuild-build-arm/src/linker.rs) rather than copying it six times:

fn link_cwd_for(output_dir: &Path) -> Option<&Path> {
    if output_dir.is_absolute() { Some(output_dir) } else { None }
}

The one real hazard

Do not set the cwd unless every argument is absolute. The is_absolute() guard exists because moving cwd also relocates a relative -o. For teensy this was safe: -o is output_dir.join(...), objects/archives arrive absolute, and -T<script> resolves through absolute -L search paths. Verify that per linker — some may pass a relative linker script or output path, in which case they need the arguments absolutized first, or left alone.

arduino_mbed.rs:289 also passes None for env (not just cwd), so it may be a different kind of call — check what it actually spawns before changing it.


Part 2 — cut a release

main is 2 commits ahead of the newest tag, and both are fixes people are currently blocked on:

$ git log --oneline v2.5.14..main
0dd68b24 fix(teensy): run the link in the build dir, not the inherited cwd (#1268)
2976d863 fix(rp): honor Arduino Bluetooth menu (#1263)

Newest tag: v2.5.14. Newest PyPI release: 2.5.14 (published 2026-08-06 05:08:05Z, i.e. before both commits).

Consumers pin fbuild by exact version, so an unreleased fix is an unusable fix:

  • 2976d863 blocks chore(rp2350w): hand off final #3832 hardware validation FastLED#3864 entirely. Without it board_build.ipbtstack = ipv4btcble is ignored, libipv4.a is linked instead of libipv4-bt.a, and the RP2350W build dies at _needsbt.h's static_assert(ENABLE_CLASSIC, ...). All five RP2350W HIL flows are blocked on this, and the FastLED issue currently reads "validated with the local fbuild development executable" — a path that is not reproducible from a clean checkout.
  • 0dd68b24 is what stops bash compile dirtying a clean tree.

Please tag and publish v2.5.15 (or later) containing both. FastLED then bumps its fbuild== pin.


Verification — read this before testing

This bug is intermittent and produces false negatives very easily. Two traps I fell into:

  1. A repeat build is a silent no-op and looks like "fixed". Force real work (-c, or switch examples) and confirm the elapsed build time in the output. A "clean" result from a 2-second rebuild proves nothing.
  2. I concluded zccache-fp on PATH was the trigger from one positive and two negatives. It was pure coincidence. Don't infer causation from a handful of runs here.

Reproduction that does work:

cd /some/empty/dir
rm -f -- ./-r
fbuild.exe "<project-dir>" build -e <env> -c      # must show real elapsed time
ls -la -- ./-r                                    # before the fix: 0-byte stray

After the fix the invocation dir stays clean and the stray appears under the gitignored build tree instead. Compare artifact sizes before/after to confirm nothing functional changed — teensy41 was byte-identical (flash 72704, RAM 81984).

Note the file name's leading dash: clean up with rm -- ./-r, never plain rm -r.

Acceptance criteria

  • Each ARM linker above either runs the link in its (absolute) output dir, or has a comment recording why it must keep the inherited cwd.
  • link_cwd_for lives in one place rather than being duplicated per linker.
  • A clean build for at least one non-teensy ARM target leaves the invocation directory unchanged, verified with elapsed build time shown.
  • soldr cargo test -p fbuild-build-arm --lib passes.
  • A release ≥ v2.5.15 is tagged and on PyPI containing 0dd68b24 and 2976d863.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions