Skip to content

build: de-waf the post-link pipeline (phase 0 + 1) - #1886

Open
jplexer wants to merge 11 commits into
coredevices:mainfrom
jplexer:build/unwaf-phase0
Open

build: de-waf the post-link pipeline (phase 0 + 1)#1886
jplexer wants to merge 11 commits into
coredevices:mainfrom
jplexer:build/unwaf-phase0

Conversation

@jplexer

@jplexer jplexer commented Aug 18, 2026

Copy link
Copy Markdown
Member

First steps toward making waf a thin orchestrator over standalone tooling to later switch it out with another build system. No behavior changes intended; artifacts were verified byte-identical where deterministic.

Cleanup

  • Drop three dead waf tools (binary_header, c_inject_include_files, compress). They were unused, and two had latent import bugs.
  • Fix two latent pebble_test.py defects: mutable default args accumulating libs across all 285 clar() call sites, and a loop-leaked test_source that would drop files if a test glob ever matched more than one.
  • Deduplicate the FirmwareDescription helpers: canonical copy now lives in pebble.commander.util.fw_binary_info (the live flash-time path for non-pblboot boards, i.e. asterix); the tools/ scripts are thin shims keeping their .elf support and CLIs.

build-info.json

configure now writes build/build-info.json: board, platform, variant, slot, runners, artifact paths, and all CONFIG_* symbols. This is the neutral interface the extracted tools read: any future build system that writes the same file drives the same tools unchanged.

Extracted tools (waf calls them; nothing about them is waf-specific)

  • tools/fw_image.py: post-link pipeline: objcopy hex/bin, pblboot header, loghash extraction/check. Replaces five waf rules + a post-build merge.
  • tools/make_fw_bundle.py: .pbz bundling
  • tools/make_qemu_images.py: QEMU micro/SPI flash images
  • tools/gitinfo.py: standalone git version info with --json.
  • Firmware/resource size caps move from hardcoded wscript tables to Kconfig (FW_MAX_SIZE, SYSTEM_RESOURCES_MAX_SIZE); verified identical for every board × variant combination.

./pbl no longer parses waf's c4che or spawns waf for bundle/qemu images: it reads build-info.json.

Verification

  • objcopy outputs, loghash dicts, bundle contents and QEMU image layouts byte-identical to the replaced waf rules for the same inputs.
  • Full configure/build/bundle/test cycle green; unit tests: 328 suites, 0 failures.

jplexer and others added 3 commits August 18, 2026 23:41
Remove three custom waf tools that nothing uses:

- binary_header.py: loaded at configure time but no task generator in
  the tree uses the binary_header feature. It also references the re
  and os modules without importing them, so parts of it would raise
  NameError if ever exercised.
- c_inject_include_files.py: never loaded by any wscript, and it
  references waflib.Node.Nod3 (typo), so loading it would fail.
- compress.py: no importers anywhere in the tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
Two latent defects in the clar test harness:

- clar() declared mutable default arguments (test_libs=[],
  override_includes=[]) and add_clar_test() appended pthread/m to
  test_libs and libutil/libbtutil/duma to the caller's use list in
  place. The shared defaults accumulated duplicates across all call
  sites, and multi-platform tests grew their use list once per
  platform. Copy the lists before modifying them.

- clar() derived test_name in a loop over test_sources but built the
  test binaries in a separate loop afterwards, using the loop-leaked
  last test_source with the first source's name. A glob matching more
  than one file would silently drop all but the last. Nest the
  platform loop so every matched source gets its own test.

No behavior change for existing call sites: every current test glob
matches exactly one file, and the duplicate libs were harmless.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
PebbleFirmwareBinaryInfo and the FirmwareDescription struct insertion
existed in two diverged copies: tools/fw_binary_info.py +
tools/insert_firmware_descr.py (manual CLIs) and a vendored copy in
pebble-commander's imaging command, which is the live path for boards
without pblboot (asterix gets the struct prepended at PULSE flash
time).

Make pebble.commander.util.fw_binary_info the canonical
implementation, next to the vendored stm32_crc it depends on, so the
package stays self-contained. The tools/ scripts become thin shims:
fw_binary_info.py subclasses to keep its .elf input support (which
needs the repo-local binutils helper), and insert_firmware_descr.py
keeps its CLI. Output is byte-identical to the previous
implementations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
@jplexer
jplexer requested a review from gmarull as a code owner August 18, 2026 21:42
jplexer and others added 8 commits August 18, 2026 23:49
Move the git revision logic from tools/waf/gitinfo.py into
tools/gitinfo.py with a plain subprocess implementation and a small
CLI (--json), so post-link and bundle tooling can use it without waf.
The waf module becomes a shim keeping the ctx-based signature for the
remaining wscript callers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
Replace the hardcoded per-SoC size tables in wscript and pbl with two
derived Kconfig symbols:

- FW_MAX_SIZE: defaults to FW_FLASH_SIZE, which already encodes the
  per-board/per-variant bank sizes, with an nRF52 exception keeping
  32 KiB reserved for the bootloader in the non-recovery bank.
- SYSTEM_RESOURCES_MAX_SIZE: per-SoC pbpack cap.

Verified against the old tables for every board across
normal/prf/prf+mfg variants; all values are identical. One edge case
improves: PRF built with RECOVERY_FW_AS_FW now gets the normal-area
cap on SF32LB52 instead of the 576 KiB recovery-bank cap, since it is
linked into the normal firmware area.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
Write a neutral JSON description of the configured build (board,
platform, variant, slot, runners, artifact paths, and every CONFIG_*
symbol) to build/build-info.json. This is the interface the
standalone build tooling reads instead of parsing waf's c4che, and it
is producer-agnostic: any build system that writes the same file can
drive the same tools.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
Replace the five per-step waf rules in _link_firmware (objcopy hex,
objcopy bin, two pblboot header inserts, loghash extraction) plus the
merge_loghash_dicts post-build function with a single standalone CLI
driven by build-info.json. waf now invokes it as one rule with the
ELF as input and the hex/bin/loghash dictionaries as outputs.

Along the way:

- tools/waf/pblboot.py moves to tools/pblboot.py, dropping its two
  waf task-rule wrappers; the argparse CLI and the header/priority
  functions are unchanged.
- tools/waf/generate_log_strings_json.py is removed; the extraction
  and format-specifier check now live in fw_image.py, writing both
  pebbleos_loghash_dict.json and src/fw/loghash_dict.json (the merge
  step had exactly one input dict, so both files were always
  identical).
- bld.LOGHASH_DICTS and env.PBLBOOT_PRIORITY/FIRMWARE_OFFSET plumbing
  goes away.

Verified: objcopy outputs and the loghash dictionary are byte-
identical to what the replaced waf rules produce for the same ELF,
and the pblboot header CRC/offset/content check out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
Move the .pbz bundling glue (_make_bundle/_get_version_info) from the
root wscript into a standalone CLI driven by build-info.json: version
info via tools/gitinfo, the CONFIG_FW_MAX_SIZE gate, resource pack /
loghash / layouts attachment and the output naming convention.

The waf bundle command is gone: ./pbl bundle invokes the tool without
spawning a waf process, and CI uses it directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
Move the inline qemu_image_micro/qemu_image_spi logic from the root
wscript into a standalone CLI driven by build-info.json. The waf
qemu_image_* commands are gone: ./pbl qemu builds the images directly,
a new ./pbl qemu_images command covers the CI usage, and pbl's last
waf-forwarding helper (_run_waf) goes away.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
Load build state from the neutral build/build-info.json written at
configure time rather than importing waf's c4che/_cache.py. The Env
shim keeps waf's ConfigSet semantics (missing keys read as []) so the
runner helpers are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
All remaining ./waf invocations in the workflows become ./pbl
(configure/build/test forward through pbl's passthrough; bundle and
qemu_images are pbl commands now). The workflow path filters gain
'pbl' so CLI changes retrigger the builds, and the docs drop stale
./waf and binary_header references.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Joshua Jun <lets@throw.rocks>
@jplexer
jplexer force-pushed the build/unwaf-phase0 branch from 993815f to ed76351 Compare August 18, 2026 21:49
Comment thread tools/fw_image.py
Comment on lines +42 to +45
def _objcopy(elf, out, fmt):
subprocess.check_call(
["arm-none-eabi-objcopy"] + OBJCOPY_STRIP_ARGS + ["-O", fmt, elf, out]
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: on a build system based e.g. on CMake, having this outside of the build system will be non-idiomatic, and probably cause problems like dependencies not being tracked properly (to determine when something has to be re-run). Also, we may not always want to invoke such tool, what if we support one day native builds, llvm, etc.?

Comment thread Kconfig
Comment on lines +37 to +55
config FW_MAX_SIZE
hex
default 0xf8000 if SOC_NRF52 && (!RECOVERY_FW || MFG)
default FW_FLASH_SIZE
help
Maximum size of the firmware image binary, enforced when
bundling and flashing. Defaults to the firmware flash bank
size; the nRF52 normal-firmware bank keeps 32 KiB reserved for
the bootloader.

config SYSTEM_RESOURCES_MAX_SIZE
hex
default 0x100000 if SOC_NRF52
default 0x200000 if SOC_SF32LB52
default 0x200000 if SOC_QEMU
default 0x40000
help
Maximum size of the system resources pbpack, enforced after the
resource build.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should define some sort of partition map at board level for that I think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants