feat: create firmware skeleton and reproducible build
This commit is contained in:
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate deterministic gzip-compressed LittleFS web assets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import gzip
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SUPPORTED_SUFFIXES = {".css", ".html", ".ico", ".js", ".json", ".svg"}
|
||||
|
||||
|
||||
def generate_assets(source_directory: Path, output_directory: Path) -> list[Path]:
|
||||
source_directory = source_directory.resolve()
|
||||
output_directory = output_directory.resolve()
|
||||
if not source_directory.is_dir():
|
||||
raise ValueError(f"Web source directory does not exist: {source_directory}")
|
||||
if source_directory == output_directory:
|
||||
raise ValueError("Web source and output directories must differ")
|
||||
|
||||
source_files = sorted(
|
||||
path for path in source_directory.rglob("*")
|
||||
if path.is_file() and path.suffix.lower() in SUPPORTED_SUFFIXES
|
||||
)
|
||||
if not source_files:
|
||||
raise ValueError(f"No supported web assets found in {source_directory}")
|
||||
|
||||
if output_directory.exists():
|
||||
shutil.rmtree(output_directory)
|
||||
output_directory.mkdir(parents=True)
|
||||
|
||||
generated_files: list[Path] = []
|
||||
for source_path in source_files:
|
||||
relative_path = source_path.relative_to(source_directory)
|
||||
output_path = output_directory / f"{relative_path}.gz"
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with source_path.open("rb") as source, output_path.open("wb") as output:
|
||||
with gzip.GzipFile(
|
||||
filename="",
|
||||
mode="wb",
|
||||
compresslevel=9,
|
||||
fileobj=output,
|
||||
mtime=0,
|
||||
) as compressed:
|
||||
shutil.copyfileobj(source, compressed)
|
||||
os.utime(output_path, ns=(0, 0))
|
||||
generated_files.append(output_path)
|
||||
|
||||
for directory in sorted(
|
||||
(path for path in output_directory.rglob("*") if path.is_dir()),
|
||||
reverse=True,
|
||||
):
|
||||
os.utime(directory, ns=(0, 0))
|
||||
os.utime(output_directory, ns=(0, 0))
|
||||
|
||||
return generated_files
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--source", type=Path, default=Path("web"))
|
||||
parser.add_argument("--output", type=Path, default=Path("data"))
|
||||
arguments = parser.parse_args()
|
||||
|
||||
generated_files = generate_assets(arguments.source, arguments.output)
|
||||
for path in generated_files:
|
||||
print(path)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user