94 lines
3.0 KiB
Python
94 lines
3.0 KiB
Python
"""PlatformIO hooks for deterministic assets and partition size limits."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
Import("env")
|
|
|
|
|
|
PROJECT_DIRECTORY = Path(env.subst("$PROJECT_DIR"))
|
|
BUILD_DIRECTORY = Path(env.subst("$BUILD_DIR"))
|
|
PARTITION_FILE = PROJECT_DIRECTORY / "partitions.csv"
|
|
ASSET_SCRIPT = PROJECT_DIRECTORY / "scripts" / "generate_web_assets.py"
|
|
FILESYSTEM_WRAPPER = PROJECT_DIRECTORY / "scripts" / "reproducible_mklittlefs.py"
|
|
|
|
|
|
def parse_size(value: str) -> int:
|
|
value = value.strip()
|
|
suffixes = {"K": 1024, "M": 1024 * 1024}
|
|
if value[-1:].upper() in suffixes:
|
|
return int(value[:-1], 0) * suffixes[value[-1].upper()]
|
|
return int(value, 0)
|
|
|
|
|
|
def partition_sizes() -> dict[str, int]:
|
|
sizes: dict[str, int] = {}
|
|
with PARTITION_FILE.open(newline="", encoding="utf-8") as partition_stream:
|
|
for row in csv.reader(partition_stream):
|
|
if not row or row[0].lstrip().startswith("#"):
|
|
continue
|
|
if len(row) < 5:
|
|
raise RuntimeError(f"Invalid partition row: {row}")
|
|
sizes[row[0].strip()] = parse_size(row[4])
|
|
return sizes
|
|
|
|
|
|
def generate_assets(source, target, env) -> None:
|
|
command = [
|
|
env.subst("$PYTHONEXE"),
|
|
str(ASSET_SCRIPT),
|
|
"--source",
|
|
str(PROJECT_DIRECTORY / "web"),
|
|
"--output",
|
|
str(PROJECT_DIRECTORY / "data"),
|
|
]
|
|
subprocess.run(command, check=True)
|
|
|
|
|
|
def enforce_size(path: Path, partition_name: str) -> None:
|
|
limits = partition_sizes()
|
|
if partition_name not in limits:
|
|
raise RuntimeError(f"Missing required partition: {partition_name}")
|
|
actual_size = path.stat().st_size
|
|
maximum_size = limits[partition_name]
|
|
if actual_size > maximum_size:
|
|
raise RuntimeError(
|
|
f"{path.name} is {actual_size} bytes and exceeds the "
|
|
f"{partition_name} partition limit of {maximum_size} bytes"
|
|
)
|
|
print(
|
|
f"maze32 size check: {path.name} uses {actual_size}/{maximum_size} bytes "
|
|
f"in partition {partition_name}"
|
|
)
|
|
|
|
|
|
def check_application(source, target, env) -> None:
|
|
enforce_size(application_image, "factory")
|
|
|
|
|
|
def check_filesystem(source, target, env) -> None:
|
|
enforce_size(filesystem_image, "littlefs")
|
|
|
|
|
|
filesystem_image = BUILD_DIRECTORY / "littlefs.bin"
|
|
application_image = BUILD_DIRECTORY / f"{env.subst('$PROGNAME')}.bin"
|
|
real_filesystem_tool = env.subst("$MKFSTOOL")
|
|
|
|
env["ENV"]["MAZE32_REAL_MKFSTOOL"] = real_filesystem_tool
|
|
env["ENV"]["MAZE32_BUILD_DIR"] = str(BUILD_DIRECTORY)
|
|
env.Replace(MKFSTOOL=str(FILESYSTEM_WRAPPER))
|
|
|
|
env.AddPreAction("$BUILD_DIR/littlefs.bin", generate_assets)
|
|
env.AddPostAction("$BUILD_DIR/littlefs.bin", check_filesystem)
|
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", check_application)
|
|
env.AddCustomTarget(
|
|
"webassets",
|
|
None,
|
|
generate_assets,
|
|
title="Generate deterministic web assets",
|
|
description="Compress web sources into the LittleFS data directory",
|
|
)
|