Repository navigation
Description
Summary
fbuild has no way to build or flash a filesystem image. PlatformIO's buildfs / uploadfs targets have no fbuild equivalent, so there is no path from a sketch's data/ directory to an on-device filesystem.
Split out of #1354, whose original premise turned out to be partly stale: build-time .lnk resolution already exists. This is the actual remaining gap.
Verified current state
- No
buildfs,uploadfs,--filesystemor equivalent anywhere in the fbuild CLI surface. .lnkresolution and materialization already exist:crates/fbuild-toolchain/src/lnk/mod.rs:1-22documentsscanner -> resolver -> materializer -> downstream build steps, andmaterialize.rsprojects<src>/<rel>/foo.ext.lnkinto<build_resources_dir>/<rel>/foo.ext.- One consumer already exists:
crates/fbuild-build-esp/src/esp32/orchestrator/embed_stage.rs:38-46resolves.lnkentries named inboard_build.embed_filesand objcopy-embeds them into firmware. - So the pipeline terminates at "materialized into the build tree" with no filesystem-image consumer.
Proposed
fbuild buildfs — assemble a filesystem image from the build-tree projection of data/:
- inputs = committed files in
data/union materialized.lnkblobs - exclude
*.lnksidecars from the image (they must never ship to the device) - invoke
mklittlefs/mkspiffsper the environment'sboard_build.filesystem - pre-check total size plus filesystem overhead against the partition size from the partition CSV, and fail at build time rather than at runtime mount
fbuild uploadfs — flash that image to the filesystem partition via esptool (already a dependency for firmware flashing).
Partition configuration — ci/boards.py in FastLED already has board_build_filesystem_size (unused) and board_partitions (used by two boards, both huge_app.csv). A default partition CSV reserving a filesystem region is needed for boards that want one.
Platform capability
buildfs is only meaningful where a flash filesystem exists:
| Platform | Flash FS | PIO buildfs/uploadfs |
|---|---|---|
| ESP32 (Arduino-ESP32) | LittleFS, SPIFFS (legacy), FFat | Yes |
| ESP8266 | LittleFS (SPIFFS deprecated in core docs) | Yes (plus uploadfsota) |
| RP2040/RP2350 (arduino-pico) | LittleFS only | Yes |
| Teensy 4.x | LittleFS_Program — runtime only, re-upload wipes files |
No |
| AVR | none (1 KB EEPROM) | No |
| SD card, all platforms | runtime library, removable media | n/a — not deployable by a build tool |
Selecting a filesystem target on AVR or Teensy should be a clear error, not a silent no-op.
Blocker
FastLED has no LittleFS backend. FsImpl is implemented only for Arduino SD over SPI, the WASM browser VFS, and a host stub. A littlefs image would be unreachable from FastLED's FileSystem API. buildfs should not ship pointing at an API gap — see the paired FastLED issue linked below.
Related
- feat: resolve remote example assets at build time (sha256-verified, globally cached) #1354 — original build-time resolution issue (narrow it to this)
- investigate: storage-target descriptors and misconfiguration diagnostics for .lnk / assets.json #1356 — storage targets and misconfiguration diagnostics
- research: normalize the .lnk format — two incompatible variants exist today #1357 —
.lnkformat normalization
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
zackees commentedon Aug 22, 2026
Tracked in the meta issue: FastLED/FastLED#3986 — remote assets: hosting, build-time resolution, and on-device filesystem deployment.
zackees commentedon Aug 23, 2026
Unblocked: FastLED/FastLED#3984 is merged, so
fl::getEmbeddedFs()now exists and a LittleFS image would be readable fromfl::FileSystem.That was the stated blocker here — shipping
buildfswithout a backend would have produced images nothing could read. The remaining work in this issue (buildfs/uploadfs, partition config, mklittlefs/esptool wiring) is independent of it now.zackees commentedon Oct 29, 2026
Progress: the image-assembly half is up as #1788 —
fbuild buildfs.What it does today:
data/(ordata_dir) into a LittleFS/SPIFFS image usingmklittlefs/mkspiffs, sized from the board's partition CSV;.fetch, JSON.lnk) into the image undertheir projected name and never ships the pointer; a pointer that fails to
parse is now a hard error (it was silently copied into the image during
development, which would have handed the device a pointer instead of
content);
.lnk(FastLED runtime asset links) as ordinary data;spiffs/littlefssubtype(
0x82/0x83included), and fails at build time with the byte shortfallwhen
data/cannot fit, instead of failing to mount at runtime;--check-onlyprints the size plan without running the tool.Proven end-to-end on this host with the real
mklittlefs(ver. 05d49dc):addresses
0x310000/1 MiB partition → 1048576-byte image,mklittlefs -llists the packed tree, 18 new unit tests, full workspace suite 3974 passed.
Still open here (deliberately not in #1788 — it belongs to the daemon deploy
path, not the image builder):
uploadfs— flash the image to the filesystem partition via esptool;currently have none.
zackees commentedon Oct 29, 2026
Update: the image-assembly half of this issue is merged — #1788 (
fbuild buildfs) landed onmain(squash/merge commitf4bb0f18).Shipped:
fbuild buildfspacksdata/(or the env'sdata_dir) into a LittleFS/SPIFFS image using the realmklittlefs/mkspiffs, sized from the board's partition CSV (row picked by label orspiffs/littlefs/0x82/0x83subtype), with--check-only/--json, blob-pointer resolution (.fetch / JSON .lnk packed under the projected name, never shipped), and a capacity pre-check that fails with the byte shortfall before the tool runs. Docs indocs/reference/cli.md.Still open in this issue (intentionally not done here):
uploadfs, which is a daemon deploy operation needing region selection and progress plumbing, plus default filesystem partition CSVs for boards that do not declare one.zackees commentedon Oct 29, 2026
uploadfsis up as #1789 (the deploy half;buildfsshipped in #1788).What it does: resolves the target partition through the same
buildfs::plan_imagethe build half uses, so the image is written at exactly the offset the firmware mounts — no offset flag; builds a missing image first; writes the region throughesptoolwith the board's flash params and the build's resolved esptool path; preempts the daemon's serial lease first so a monitor session is not "port busy";--verifyreads the region back withverify-flash.Verified on an ESP32-S3 (
/dev/ttyACM1): 1,441,792-byte LittleFS image written at0x290000,verifymatched on device,mklittlefs -llists the packed tree.Still open against this issue after #1789 merges: RP2040 littlefs and ESP8266 image building (both need their own image pipeline — the current plan reader is an ESP32 partition CSV), and the FastLED LittleFS
FsImplbackend that the issue's "Blocker" section calls out.zackees commentedon Oct 30, 2026
fbuild uploadfs(ESP32 half of this issue) is merged: #1789 (70aec880).It flashes one filesystem image at the offset the project's partition CSV
declares — no user-supplied offset, one file/one region through the same
esptool argv +
--verifyreadbackfbuild deployuses.buildfsanduploadfsnow sharebuildfs::plan_image, so image size and flash offsetcan't drift apart.
Verified end-to-end on an ESP32-S3 (
/dev/ttyACM1): 1,441,792-byte LittleFSimage at
0x290000,uploadfs --verify-> matched.Still open here: RP2040 / ESP8266 filesystem support (ESP32 only today).
zackees commentedon Oct 30, 2026
RP2040 filesystem image building has landed: #1792 (
e1cf487f).What it does:
fbuild buildfsnow reads the filesystem region from arduino-pico's ownboards.txt(build.fs_start/build.fs_endon theflashmenu option selected byboard_build.filesystem_size, elseboard_build.flash) through the same loader the RP2040 build uses — so the image size and the firmware'sFS_START/FS_ENDdefines cannot drift, the same invariantplan_imagegives ESP32's partition CSV.Verified on a fixture
rpipicoproject withboard_build.filesystem_size = 0.5m: 524,288-byte littlefs image at0x1017f000(arduino-pico's ownfs_startfor that option),mklittlefs -llists all three packed files, and unpacking round-trips a 70,000-byte blob byte-identically. A board whose selected menu reserves no volume (the stock 2 MB Pico default,fs_start == fs_end) fails with both addresses plus the knob to set;--filesystem=spiffsis refused because arduino-pico has a single littlefs driver.Still open on this issue:
mkspiffs/mklittlefswith theeagle.flash.*.ldlinker-script layout, not a testable target on this host today).esptoolregion write, sofbuild uploadfsremains ESP32-only.zackees commentedon Oct 30, 2026
ESP8266 filesystem image building is up as #1800.
What it does: an ESP8266 module has no partition table, so
fbuild buildfsnowresolves the
eesz"Flash Size" menu option in arduino-esp8266'sboards.txt(
build.flash_ld=eagle.flash.4m1m.ld), reads that linker script, and takes boththe region and the filesystem geometry from its
_FS_start/_FS_end/_FS_block/_FS_pagesymbols — the same symbols the firmware links against,so image size and mounted volume cannot drift. The script is parsed directly
rather than re-derived from the duplicated
build.spiffs_start/spiffs_endcopies in
boards.txt.The block/page size moved into
FsGeometry(buildfs_geometry.rs) because animage formatted 4096/256 without being mounted 4096/256 does not mount: ESP32
stays 4096/256, an ESP8266 volume takes its script's own geometry (8192/256 for
a 4 MB layout), and the size estimate and the
mklittlefsargv come from thatone value.
Verified end-to-end with arduino-esp8266 3.30102.0 and PlatformIO's mklittlefs
on a
nodemcuv2/board_build.eesz = 4M2Mfixture: 2,072,576-byte littlefsimage at
0x40400000(the script's_FS_start),mklittlefs -l -b 8192 -p 256lists the packed tree, and unpacking round-trips a 70,000-byte blob
byte-identically. 14 new unit tests; workspace suite 4020 passed / 0 failed.
Still open on this issue:
esptool region write at
_FS_start - 0x40200000, and RP2040 goes overPICOBOOT/UF2;
fbuild uploadfsis ESP32-only until those transports exist.FsImplbackend the issue's "Blocker" section calls out.zackees commentedon Oct 30, 2026
ESP8266 image building landed: #1800 merged (merge commit f65fdf6, 2026-10-30T13:37:33Z).
fbuild buildfsnow covers three platforms:partitions.csv), 4096/256 geometry.build.fs_start/build.fs_endfrom arduino-pico'sflashmenu option.eeszmenu'seagle.flash.*.ldlinker script: region and block/page geometry from_FS_start/_FS_end/_FS_page/_FS_block(256/8192 there, not the ESP32 4096/256).fbuild uploadfsremains ESP32-only. Still open here: serial flashing of the ESP8266 image (_FS_start - 0x40200000write) and an RP2040 transport (PICOBOOT/UF2).zackees commentedon Oct 30, 2026
fbuild uploadfsnow flashes ESP8266 images too — PR #1802 (unmerged).The ESP8266 half needed one address conversion plus a deployer: the linker
script's
_FS_startis a CPU-mapped address (0x40400000for4M2M), whileesptool wants an offset from the start of flash, so
0x40200000comes back offbefore the write (
0x200000) and an address below the mapping is a hard errorrather than a wrapped huge offset. The write goes through the same esptool
region writer as ESP32 with
--chip esp8266and the board's own flashmode/frequency/size.
Verified on hardware:
nodemcuv2+board_build.eesz = 4M2Mon/dev/ttyUSB0(CH340) — the 2,072,576-byte littlefs image the buildfs PR produces flashed at
0x200000, then--verify(device-side MD5 comparison) matched.Remaining on this issue: an RP2040 transport (PICOBOOT/UF2). All three platforms
now build images; ESP32 and ESP8266 flash them.
zackees commentedon Oct 30, 2026
Merged #1802 (merge commit
3e901c21), which closes the ESP8266 half of the flashing story.Status matrix now on
mainfbuild buildfsfbuild uploadfs--verifyreadback)eagle.flash.*.ld)--chip esp8266, mapped→flash offset)build.fs_start/_endfrom theflashmenu)What #1802 added for ESP8266 uploads
uploadfsacceptsespressif8266; the linker script's_FS_startis a mappedaddress, so
0x40200000is subtracted before esptool sees the offset(underflow → hard error), while ESP32 partition offsets pass through unchanged.
--chip esp8266using the board's flashmode/freq/size (native espflash is off — no ESP8266 support).
FBUILD_ESPTOOL_PATH→ managed v5 binary →PATH(arduino-esp8266 pins the v4 source package, which the v5 CLI deployer can't drive).
Real hardware E2E on a nodemcuv2 / 4M2M (CH340,
/dev/ttyUSB0):2,072,576-byte littlefs flashed at0x200000,--verifymatched on-device.Remaining on this issue
RP2040 flashing (PICOBOOT/UF2 transport) —
buildfsfor RP2040 landed in #1792,but there is no deploy transport yet. Deploy transports for ESP32 and ESP8266 are
now complete.
zackees commentedon Oct 30, 2026
buildfs/uploadfsmatrix after #1803buildfsuploadfs#1792line)--verifyeeszlinker-script region and geometry--chip esp8266+--verifybuild.fs_start/fs_endper flash menuRP2040 is a different transport by nature: the region is only programmable by the
ROM's BOOTSEL mass-storage loader, at the absolute flash address the firmware
mounts (
build.fs_start, no mapping subtraction). #1803 encodes the image as UF2blocks at that address, writes them to the single mounted
RPI-RP2volume asNEW.UF2, and reports the volume + block count.--verifyis refused for RP2040because the region can only be read back over PICOBOOT — the volume ejecting is
the confirmation.
With that, every platform fbuild can build an image for also has an upload path, so
this issue's scope is complete pending #1803's merge and a hardware run on a Pico.
zackees commentedon Oct 30, 2026
#1803 merged (merge commit
503410b8) — the matrix is code-complete:buildfsuploadfs--verifyeeszlinker-script region + geometry--chip esp8266+--verifybuild.fs_start/fs_end#1803)Usage on a Pico: set
board_build.filesystem_size(the stock 2 MB Pico defaultreserves no volume),
fbuild buildfs -e rpipico, hold BOOTSEL while plugging in,then
fbuild uploadfs -e rpipico. The image is encoded as UF2 blocks at theabsolute
fs_startand written to the single mountedRPI-RP2volume asNEW.UF2; the volume ejecting is the confirmation.--verifyis refused (aregion is readable only over PICOBOOT) and
--port/--baudare ignored with awarning (BOOTSEL is mass storage).
One caveat to close this out honestly: the RP2040 upload path is not proven on
RP2040 hardware yet — no Pico was attached to the machine that wrote it. The
encoding, address arithmetic and write/eject handling are unit-tested (8 new
tests incl. a full 512 KiB / 2048-block image) and reuse
fbuild deploy'sexisting transport, but a first real
fbuild uploadfs -e rpipicoon a board isstill outstanding before I would call the cell green end-to-end.