Tactility/Buildscripts/generate-assets.py
Ken Van Hoeylandt 0ebd471cc0
Reduce internal RAM use: embedded assets, fonts in flash, PSRAM stacks, file_ops (#701)
- The `/system` FAT volume is removed. Its files are embedded in the firmware as generated C arrays.
- Default-size fonts are generated at build time and used from flash.
- The LVGL task and the Launcher run on PSRAM-preferred stacks. A new kernel `file_ops` API takes the file work off those threads.
- `MainDispatcher` is reimplemented as an on-demand task.
- More long-lived state is allocated with the PSRAM-preferring helpers.
- Other fixes and improvements
2026-10-18 17:30:43 +02:00

92 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""
Embeds every file of an asset folder as a C byte array.
Usage: generate-assets.py <asset-folder> <output-folder>
Writes generated_assets.h and generated_assets.c. A file "fonts/AdwaitaSans.ttf" becomes the symbols
asset_fonts_AdwaitaSans_ttf (the bytes) and asset_fonts_AdwaitaSans_ttf_size (the byte count).
The size is also available as the constant asset_fonts_AdwaitaSans_ttf_SIZE for use in constant initializers.
Text files get an extra terminating 0 byte that is not part of the size, so they can be used as C strings.
Output files are only rewritten when their content changes, to avoid needless recompiles.
"""
import os
import re
import sys
TEXT_EXTENSIONS = {".pem", ".csv", ".html", ".json", ".codepoints", ".i18n", ".txt"}
def symbol_name(relative_path: str) -> str:
return "asset_" + re.sub(r"[^A-Za-z0-9]", "_", relative_path)
def write_if_changed(path: str, content: str):
if os.path.exists(path):
with open(path, "r", encoding="utf-8", newline="") as file:
if file.read() == content:
return
with open(path, "w", encoding="utf-8", newline="") as file:
file.write(content)
def main():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <asset-folder> <output-folder>")
sys.exit(1)
asset_folder = os.path.abspath(sys.argv[1])
output_folder = os.path.abspath(sys.argv[2])
os.makedirs(output_folder, exist_ok=True)
relative_paths = []
for directory, _, file_names in os.walk(asset_folder):
for file_name in file_names:
relative_paths.append(os.path.relpath(os.path.join(directory, file_name), asset_folder).replace(os.sep, "/"))
relative_paths.sort()
header = [
"// Generated by Buildscripts/generate-assets.py. Do not edit.",
"#pragma once",
"",
"#include <stddef.h>",
"#include <stdint.h>",
"",
"#ifdef __cplusplus",
'extern "C" {',
"#endif",
"",
]
source = [
"// Generated by Buildscripts/generate-assets.py. Do not edit.",
'#include "generated_assets.h"',
"",
]
for relative_path in relative_paths:
with open(os.path.join(asset_folder, relative_path), "rb") as file:
data = file.read()
symbol = symbol_name(relative_path)
size = len(data)
if os.path.splitext(relative_path)[1].lower() in TEXT_EXTENSIONS:
data += b"\0"
header.append(f"// {relative_path}")
header.append(f"extern const uint8_t {symbol}[];")
header.append(f"extern const size_t {symbol}_size;")
header.append(f"#define {symbol}_SIZE {size}")
source.append(f"// {relative_path}")
source.append(f"const uint8_t {symbol}[] = {{")
for offset in range(0, len(data), 24):
source.append(" " + ", ".join(f"0x{byte:02x}" for byte in data[offset:offset + 24]) + ",")
if len(data) == 0:
source.append(" 0")
source.append("};")
source.append(f"const size_t {symbol}_size = {size};")
source.append("")
header += ["", "#ifdef __cplusplus", "}", "#endif", ""]
write_if_changed(os.path.join(output_folder, "generated_assets.h"), "\n".join(header))
write_if_changed(os.path.join(output_folder, "generated_assets.c"), "\n".join(source))
if __name__ == "__main__":
main()