Files
meshtastic_firmware/bin/collect_sizes.py
T
Thomas GöttgensandGitHub ba473bf529 size gate: emit flash_bytes from ELF for targets without a packaged .bin (#10940)
* size gate: emit flash_bytes from ELF for targets without a packaged .bin

nRF52 builds package hex/uf2/DFU zip but no raw .bin, so collect_sizes.py
dropped budgeted envs like rak4631 entirely and the size-budget-gate failed
closed. Emit ELF text+data as flash_bytes in the manifest and use it as the
fallback flash measurement.

* Factor shared size-tool invocation into run_size_tool helper
2026-07-08 07:22:26 -05:00

78 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Collect firmware binary sizes from manifest (.mt.json) files into a single report.
Output schema (consumed by bin/size_report.py):
{"<env>": {"flash_bytes": <int>, "ram_bytes": <int>}}
flash_bytes is the size of the main firmware image (.bin); for targets whose
packaged artifacts include no raw .bin (e.g. nRF52) it falls back to the
flash_bytes value (ELF text + data) emitted into the manifest by
bin/platformio-custom.py. ram_bytes is the static RAM footprint (.data + .bss)
emitted into the manifest by bin/platformio-custom.py; either metric is
omitted for manifests that predate it, and size_report.py renders those as
"n/a".
"""
import json
import os
import sys
def collect_sizes(manifest_dir):
"""Scan manifest_dir for .mt.json files and return {board: sizes_dict}."""
sizes = {}
for fname in sorted(os.listdir(manifest_dir)):
if not fname.endswith(".mt.json"):
continue
path = os.path.join(manifest_dir, fname)
with open(path) as f:
data = json.load(f)
board = data.get("platformioTarget", fname.replace(".mt.json", ""))
# Find the main firmware .bin size (largest .bin, excluding OTA/littlefs/bleota)
bin_size = None
for entry in data.get("files", []):
name = entry.get("name", "")
if name.startswith("firmware-") and name.endswith(".bin"):
bin_size = entry["bytes"]
break
# Fallback: any .bin that isn't ota/littlefs/bleota
if bin_size is None:
for entry in data.get("files", []):
name = entry.get("name", "")
if name.endswith(".bin") and not any(
x in name for x in ["littlefs", "bleota", "ota"]
):
bin_size = entry["bytes"]
break
# Fallback: flash footprint emitted into the manifest (ELF text + data),
# for targets that package no raw .bin (e.g. nRF52 hex/uf2/DFU zip)
if bin_size is None:
flash_bytes = data.get("flash_bytes")
if isinstance(flash_bytes, int) and not isinstance(flash_bytes, bool):
bin_size = flash_bytes
entry = {}
if bin_size is not None:
entry["flash_bytes"] = bin_size
ram_bytes = data.get("ram_bytes")
if isinstance(ram_bytes, int) and not isinstance(ram_bytes, bool):
entry["ram_bytes"] = ram_bytes
if entry:
sizes[board] = entry
return sizes
if __name__ == "__main__":
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <manifest_dir> <output.json>", file=sys.stderr)
sys.exit(1)
manifest_dir = sys.argv[1]
output_path = sys.argv[2]
sizes = collect_sizes(manifest_dir)
with open(output_path, "w") as f:
json.dump(sizes, f, indent=2, sort_keys=True)
print(f"Collected sizes for {len(sizes)} targets -> {output_path}")