Skip to content

fix(ldf): honor lib_deps on Teensy/STM32 and warn on inert lib_ldf_mode (#1214) - #1226

Merged
zackees merged 2 commits into
mainfrom
fix/1214-honor-lib-deps
Aug 1, 2026
Merged

fix(ldf): honor lib_deps on Teensy/STM32 and warn on inert lib_ldf_mode (#1214)#1226
zackees merged 2 commits into
mainfrom
fix/1214-honor-lib-deps

Conversation

@zackees

@zackees zackees commented Aug 1, 2026

Copy link
Copy Markdown
Member

Closes #1214scoped to asks #2/#3, per the reporter's own follow-up.

Scope: ask #1 is deliberately NOT implemented

The issue originally asked for a deeper LDF, then the reporter withdrew it:

Withdrawing ask #1. My framing was wrong — a maintainer pointed out that shallow, sketch-only LDF is the intended default (PlatformIO parity), and the code agrees with them, not with me.

They're right, and the code says so out loud: framework_libs.rs carries a regression test named inactive_local_library_header_cannot_select_framework_library (#1094). Deepening the scan would undo that, and would also re-introduce the problem FastLED spent real effort escaping — libraries pulled in by default drag their global constructors into the image, making them unstrippable.

That invariant is untouched here and still passes. The scan still seeds only from project translation units. This PR adds an opt-in.

The actual gap

On the Teensy/STM32 path a project had no lever at all. Per the issue's table, lib_deps, lib_ldf_mode, library.json dependencies, and header hints were all inert — including a plain unconditional #include <SPI.h> in a library header. So a dependency the finder cannot infer was simply unexpressible.

Changes

1. lib_deps force-selects framework libraries on Teensy and STM32

get_lib_deps() was already parsed (ini_parser/mod.rs:249) — only the ESP32 orchestrator ever consumed it. Now Teensy and STM32 do too.

Declared libraries are selected in a new pass 0, before the scan, so they flow through the existing reconciliation loop and pull in their own transitive dependencies. That's PlatformIO's semantics for an explicit declaration, and it's why this isn't a bolt-on append at the end — a test covers exactly that (declared_library_pulls_in_its_own_transitive_dependency).

2. Entry matching tolerates real lib_deps syntax

entry matches framework lib SPI
SPI, spi yes
SPI@^1.0, arduino/SPI@1.2.3 yes
https://github.com/example/SPI.git no
./vendor/SPI no

URLs and local paths name something to fetch, so they're left to the installer path rather than being mangled into a bare name that accidentally matches.

3. declared_deps is part of the cache key

This one matters more than it looks. A lib_deps edit changes the selection without touching any scanned file, so without it in the key a warm cache silently serves the old selection — the same latent-until-cold-resolution failure the issue describes ("a warm cache can hide it"). Entries are sorted before hashing, so reordering ini lines doesn't needlessly throw away a valid entry. Both properties are tested.

4. lib_ldf_mode no longer no-ops silently

It was never read anywhere in the workspace, so a project setting deep got nothing — which is how you lose an afternoon. It now warns that the setting is inert and points at lib_deps. chain/off pass without noise since they describe what fbuild actually does.

Tests

8 new, all passing:

  • unreached_library_is_not_selected_without_a_declaration — the fix(ldf): avoid self-selecting Teensy framework libraries and their global state #1094 baseline, pinned so this PR can't quietly widen the scan
  • lib_deps_declaration_selects_an_unreached_library — the issue's exact shape
  • lib_deps_matching_ignores_case_owner_and_version, lib_deps_urls_and_paths_never_match_a_framework_library, declared_dep_name_normalization
  • declared_library_pulls_in_its_own_transitive_dependency
  • c04c_declared_deps_change_invalidates_key, c04d_declared_deps_order_does_not_affect_key

Verified locally: -p fbuild-library-select -p fbuild-build-engine -p fbuild-config green, #1094's inactive_local_library_header_cannot_select_framework_library still passes, clippy clean with -D warnings across all four crates.

Not in scope

The reporter notes the real fix for the FastLED breakage belongs on the FastLED side (the Teensy 4.x driver shouldn't depend on the Arduino SPI library at all) and has filed it there. This PR gives consumers the lever that was missing regardless.

library.json dependencies remains ESP32-install-path-only; it affects installation, not framework-library selection, so wiring it here would be a different change than the one asked for.

🤖 Generated with Claude Code

…de (#1214)

Scoped to asks #2/#3 only. The reporter explicitly WITHDREW ask #1 (deepen
the LDF to scan library sources) after a maintainer pointed out that
shallow, sketch-only resolution is the intended default — #1094's
`inactive_local_library_header_cannot_select_framework_library` is a
deliberate, tested invariant, and deepening the scan would also drag
libraries' global constructors into every image. That invariant is untouched
here and still passes; this adds an opt-in, it does not widen the scan.

The actual gap was that a project had NO lever at all on the Teensy/STM32
path: `lib_deps`, `lib_ldf_mode`, `library.json` dependencies and header
hints were all inert, so a dependency the finder cannot infer was
unexpressible.

- `lib_deps` now force-selects framework libraries on Teensy and STM32.
  `get_lib_deps()` was already parsed but only the ESP32 orchestrator ever
  consumed it. Declared libraries are selected in a new pass 0, BEFORE the
  scan, so they participate in the reconciliation passes and pull in their
  own transitive dependencies — PlatformIO's semantics for an explicit
  declaration, not a bolt-on.
- Entry matching tolerates what `lib_deps` actually contains: case, owner
  prefixes, and version specs (`arduino/SPI@^1.0` matches `SPI`). URLs and
  local paths never match a framework library — they name something to
  fetch, and are left to the installer path.
- `declared_deps` joins the library-select cache key. A `lib_deps` edit
  changes the selection without touching any scanned file, so without this
  a warm cache would silently serve the old selection — the same
  latent-until-cold-resolution failure mode the issue describes. The key
  sorts entries, so reordering ini lines does not throw away a valid entry.
- `lib_ldf_mode` was never read anywhere in the workspace, so a project
  setting `deep` got a silent no-op. It now warns that the setting is inert
  and points at `lib_deps`. `chain`/`off` pass without noise since they
  match actual behavior.

8 new tests: the issue's exact shape (library-header-only reachability) stays
unselected without a declaration and selected with one, entry-normalization
cases, URL/path rejection, transitive pull-in of a declared library's own
deps, and both cache-key properties.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@zackees, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 99ce7b16-0b9f-44f3-8fc7-533eaa506fce

📥 Commits

Reviewing files that changed from the base of the PR and between db67b04 and c18d130.

📒 Files selected for processing (8)
  • bench/fastled-examples/src/main.rs
  • crates/fbuild-build-arm/src/stm32/orchestrator/mod.rs
  • crates/fbuild-build-arm/src/teensy/orchestrator.rs
  • crates/fbuild-build-engine/src/framework_libs.rs
  • crates/fbuild-config/src/ini_parser/mod.rs
  • crates/fbuild-library-select/benches/resolve_warm.rs
  • crates/fbuild-library-select/src/cache.rs
  • crates/fbuild-library-select/src/lib.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The bench lives outside crates/, which is where I searched for construction
sites — Windows CI caught it. Whole-workspace check is clean now.

Co-Authored-By: Claude <noreply@anthropic.com>
@zackees
zackees merged commit dd1ddd4 into main Aug 1, 2026
100 checks passed
@fastled-project-sync fastled-project-sync Bot moved this to Triage in FastLED Tracker Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

LDF resolves libraries only from sketch includes — library-source #include <SPI.h> never reaches the link line

1 participant