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
This commit is contained in:
co-authored by
GitHub
parent
8d20606203
commit
ba473bf529
+19
-8
@@ -5,10 +5,13 @@
|
|||||||
Output schema (consumed by bin/size_report.py):
|
Output schema (consumed by bin/size_report.py):
|
||||||
{"<env>": {"flash_bytes": <int>, "ram_bytes": <int>}}
|
{"<env>": {"flash_bytes": <int>, "ram_bytes": <int>}}
|
||||||
|
|
||||||
flash_bytes is the size of the main firmware image (.bin). ram_bytes is the
|
flash_bytes is the size of the main firmware image (.bin); for targets whose
|
||||||
static RAM footprint (.data + .bss) emitted into the manifest by
|
packaged artifacts include no raw .bin (e.g. nRF52) it falls back to the
|
||||||
bin/platformio-custom.py; it is omitted for manifests that predate it, and
|
flash_bytes value (ELF text + data) emitted into the manifest by
|
||||||
size_report.py renders those as "n/a".
|
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 json
|
||||||
@@ -42,11 +45,19 @@ def collect_sizes(manifest_dir):
|
|||||||
):
|
):
|
||||||
bin_size = entry["bytes"]
|
bin_size = entry["bytes"]
|
||||||
break
|
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:
|
if bin_size is not None:
|
||||||
entry = {"flash_bytes": bin_size}
|
entry["flash_bytes"] = bin_size
|
||||||
ram_bytes = data.get("ram_bytes")
|
ram_bytes = data.get("ram_bytes")
|
||||||
if isinstance(ram_bytes, int) and not isinstance(ram_bytes, bool):
|
if isinstance(ram_bytes, int) and not isinstance(ram_bytes, bool):
|
||||||
entry["ram_bytes"] = ram_bytes
|
entry["ram_bytes"] = ram_bytes
|
||||||
|
if entry:
|
||||||
sizes[board] = entry
|
sizes[board] = entry
|
||||||
return sizes
|
return sizes
|
||||||
|
|
||||||
|
|||||||
+47
-14
@@ -45,6 +45,27 @@ def infer_architecture(board_cfg):
|
|||||||
return "stm32"
|
return "stm32"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def run_size_tool(env, flag, purpose):
|
||||||
|
"""Run the toolchain size tool against the built ELF and return its output.
|
||||||
|
|
||||||
|
Shared plumbing for compute_ram_bytes / compute_flash_bytes. Returns None
|
||||||
|
when the ELF is missing or the tool fails; the manifest then simply omits
|
||||||
|
the metric and downstream size reports show "n/a".
|
||||||
|
"""
|
||||||
|
elf = env.File(env.subst("$BUILD_DIR/${PROGNAME}.elf"))
|
||||||
|
if not elf.exists():
|
||||||
|
return None
|
||||||
|
size_tool = env.subst("$SIZETOOL") or "size"
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(
|
||||||
|
[size_tool, flag, elf.get_abspath()],
|
||||||
|
env=env["ENV"],
|
||||||
|
universal_newlines=True,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"mtjson: skipping {purpose} ({size_tool} failed: {exc})")
|
||||||
|
return None
|
||||||
|
|
||||||
def compute_ram_bytes(env):
|
def compute_ram_bytes(env):
|
||||||
"""Static RAM usage (.data + .bss) of the ELF, via the toolchain size tool.
|
"""Static RAM usage (.data + .bss) of the ELF, via the toolchain size tool.
|
||||||
|
|
||||||
@@ -54,18 +75,8 @@ def compute_ram_bytes(env):
|
|||||||
Returns None when the value cannot be determined; the manifest then simply
|
Returns None when the value cannot be determined; the manifest then simply
|
||||||
omits ram_bytes and downstream size reports show "n/a".
|
omits ram_bytes and downstream size reports show "n/a".
|
||||||
"""
|
"""
|
||||||
elf = env.File(env.subst("$BUILD_DIR/${PROGNAME}.elf"))
|
output = run_size_tool(env, "-A", "ram_bytes")
|
||||||
if not elf.exists():
|
if output is None:
|
||||||
return None
|
|
||||||
size_tool = env.subst("$SIZETOOL") or "size"
|
|
||||||
try:
|
|
||||||
output = subprocess.check_output(
|
|
||||||
[size_tool, "-A", elf.get_abspath()],
|
|
||||||
env=env["ENV"],
|
|
||||||
universal_newlines=True,
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
print(f"mtjson: skipping ram_bytes ({size_tool} failed: {exc})")
|
|
||||||
return None
|
return None
|
||||||
ram = 0
|
ram = 0
|
||||||
found = False
|
found = False
|
||||||
@@ -88,6 +99,24 @@ def compute_ram_bytes(env):
|
|||||||
continue
|
continue
|
||||||
return ram if found else None
|
return ram if found else None
|
||||||
|
|
||||||
|
def compute_flash_bytes(env):
|
||||||
|
"""Flash footprint (text + data) of the ELF via the toolchain size tool.
|
||||||
|
|
||||||
|
Approximates the flashed app image for targets whose packaged artifacts do
|
||||||
|
not include a raw .bin (e.g. nRF52: hex/uf2/DFU zip only); consumed by
|
||||||
|
bin/collect_sizes.py as a fallback flash measurement for the size report
|
||||||
|
and the bin/ram_budgets.json budget gate. Returns None when the value
|
||||||
|
cannot be determined; the manifest then simply omits flash_bytes.
|
||||||
|
"""
|
||||||
|
output = run_size_tool(env, "-B", "flash_bytes")
|
||||||
|
if output is None:
|
||||||
|
return None
|
||||||
|
for line in output.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 3 and parts[0].isdigit() and parts[1].isdigit():
|
||||||
|
return int(parts[0]) + int(parts[1])
|
||||||
|
return None
|
||||||
|
|
||||||
def manifest_gather(source, target, env):
|
def manifest_gather(source, target, env):
|
||||||
global manifest_ran
|
global manifest_ran
|
||||||
if manifest_ran:
|
if manifest_ran:
|
||||||
@@ -141,9 +170,9 @@ def manifest_gather(source, target, env):
|
|||||||
d["part_name"] = partition_map[p]
|
d["part_name"] = partition_map[p]
|
||||||
out.append(d)
|
out.append(d)
|
||||||
print(d)
|
print(d)
|
||||||
manifest_write(out, env, compute_ram_bytes(env))
|
manifest_write(out, env, compute_ram_bytes(env), compute_flash_bytes(env))
|
||||||
|
|
||||||
def manifest_write(files, env, ram_bytes=None):
|
def manifest_write(files, env, ram_bytes=None, flash_bytes=None):
|
||||||
# Defensive: also skip manifest writing if we cannot determine architecture
|
# Defensive: also skip manifest writing if we cannot determine architecture
|
||||||
def get_project_option(name):
|
def get_project_option(name):
|
||||||
try:
|
try:
|
||||||
@@ -184,6 +213,10 @@ def manifest_write(files, env, ram_bytes=None):
|
|||||||
# the CI size report and the bin/ram_budgets.json budget gate.
|
# the CI size report and the bin/ram_budgets.json budget gate.
|
||||||
if ram_bytes is not None:
|
if ram_bytes is not None:
|
||||||
manifest["ram_bytes"] = ram_bytes
|
manifest["ram_bytes"] = ram_bytes
|
||||||
|
# Flash footprint (text + data); fallback flash measurement for targets
|
||||||
|
# whose packaged artifacts include no raw .bin (e.g. nRF52).
|
||||||
|
if flash_bytes is not None:
|
||||||
|
manifest["flash_bytes"] = flash_bytes
|
||||||
# Get partition table (generated in esp32_pre.py) if it exists
|
# Get partition table (generated in esp32_pre.py) if it exists
|
||||||
if env.get("custom_mtjson_part"):
|
if env.get("custom_mtjson_part"):
|
||||||
# custom_mtjson_part is a JSON string, convert it back to a dict
|
# custom_mtjson_part is a JSON string, convert it back to a dict
|
||||||
|
|||||||
Reference in New Issue
Block a user