chore: Add verbose mode to build-sd-fonts.py (#1923)
## Summary One of the recent font conversion script changes obliterated performance. Add a verbose mode to build-sd-fonts.py to help diagnose problems. Changed the CI process to use verbose mode. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**YES**_
This commit is contained in:
@@ -40,7 +40,7 @@ jobs:
|
||||
echo "metadata=$(python3 -c 'from cpfont_version import FONTS_MANIFEST_VERSION; print(FONTS_MANIFEST_VERSION)')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build SD card fonts
|
||||
run: python3 lib/EpdFont/scripts/build-sd-fonts.py --clean
|
||||
run: python3 lib/EpdFont/scripts/build-sd-fonts.py --clean --verbose -j 1
|
||||
|
||||
- name: Flatten output for release assets
|
||||
run: |
|
||||
|
||||
@@ -17,6 +17,12 @@ Usage:
|
||||
|
||||
# Generate only specific families
|
||||
python3 build-sd-fonts.py --only Literata,IBMPlexMono
|
||||
|
||||
# Stream child process output for debugging
|
||||
python3 build-sd-fonts.py --verbose
|
||||
|
||||
# Override the per-family timeout (default: 600s)
|
||||
python3 build-sd-fonts.py --timeout 1200
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -25,6 +31,8 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import urllib.request
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
@@ -141,7 +149,16 @@ def resolve_font_path(style_spec: dict, family_name: str, style_name: str) -> Pa
|
||||
return resolved
|
||||
|
||||
|
||||
def build_family(family: dict, output_base: Path) -> tuple[str, bool, str]:
|
||||
def _stream_pipe(pipe, prefix: str, dest: list[str]):
|
||||
"""Read lines from a pipe, print with prefix, and accumulate into dest."""
|
||||
for line in pipe:
|
||||
dest.append(line)
|
||||
print(f" [{prefix}] {line}", end="", flush=True)
|
||||
|
||||
|
||||
def build_family(
|
||||
family: dict, output_base: Path, verbose: bool = False, timeout: int = 600
|
||||
) -> tuple[str, bool, str]:
|
||||
"""Build a single font family. Returns (name, success, message)."""
|
||||
name = family["name"]
|
||||
output_dir = output_base / name
|
||||
@@ -185,18 +202,52 @@ def build_family(family: dict, output_base: Path) -> tuple[str, bool, str]:
|
||||
cmd.append("--force-autohint")
|
||||
|
||||
# Run fontconvert_sdcard.py
|
||||
start = time.monotonic()
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=600,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return name, False, result.stderr.strip() or f"Exit code {result.returncode}"
|
||||
return name, True, ""
|
||||
except subprocess.TimeoutExpired:
|
||||
return name, False, "Timed out after 600s"
|
||||
if verbose:
|
||||
proc = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
stdout_lines: list[str] = []
|
||||
stderr_lines: list[str] = []
|
||||
t_out = threading.Thread(
|
||||
target=_stream_pipe, args=(proc.stdout, name, stdout_lines)
|
||||
)
|
||||
t_err = threading.Thread(
|
||||
target=_stream_pipe, args=(proc.stderr, f"{name}/err", stderr_lines)
|
||||
)
|
||||
t_out.start()
|
||||
t_err.start()
|
||||
try:
|
||||
proc.wait(timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
elapsed = time.monotonic() - start
|
||||
return name, False, f"Timed out after {elapsed:.0f}s"
|
||||
finally:
|
||||
t_out.join()
|
||||
t_err.join()
|
||||
|
||||
if proc.returncode != 0:
|
||||
err = "".join(stderr_lines).strip()
|
||||
return name, False, err or f"Exit code {proc.returncode}"
|
||||
return name, True, ""
|
||||
else:
|
||||
result = subprocess.run(
|
||||
cmd, capture_output=True, text=True, timeout=timeout,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return name, False, result.stderr.strip() or f"Exit code {result.returncode}"
|
||||
return name, True, ""
|
||||
except subprocess.TimeoutExpired as e:
|
||||
elapsed = time.monotonic() - start
|
||||
tail = ""
|
||||
captured = getattr(e, "stderr", None) or getattr(e, "stdout", None)
|
||||
if captured:
|
||||
lines = captured.strip().splitlines()
|
||||
tail = "\n Last output:\n" + "\n".join(f" | {l}" for l in lines[-20:])
|
||||
return name, False, f"Timed out after {elapsed:.0f}s{tail}"
|
||||
except Exception as e:
|
||||
return name, False, str(e)
|
||||
|
||||
@@ -252,6 +303,14 @@ def main():
|
||||
help="Max parallel jobs (default: number of families)"
|
||||
)
|
||||
parser.add_argument("--clean", action="store_true", help="Clean output directory before building")
|
||||
parser.add_argument(
|
||||
"--verbose", "-v", action="store_true",
|
||||
help="Stream child process output in real time (useful for debugging timeouts)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeout", type=int, default=600,
|
||||
help="Per-family timeout in seconds (default: 600)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.manifest and not args.base_url:
|
||||
@@ -303,12 +362,14 @@ def main():
|
||||
|
||||
# Build phase (parallel)
|
||||
max_workers = args.jobs or len(families)
|
||||
print(f"\n=== Building {len(families)} families ({max_workers} parallel jobs) ===\n")
|
||||
verbose = args.verbose
|
||||
timeout = args.timeout
|
||||
print(f"\n=== Building {len(families)} families ({max_workers} parallel jobs, timeout {timeout}s) ===\n")
|
||||
|
||||
failed = []
|
||||
with ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = {
|
||||
executor.submit(build_family, family, output_base): family["name"]
|
||||
executor.submit(build_family, family, output_base, verbose, timeout): family["name"]
|
||||
for family in families
|
||||
}
|
||||
for future in as_completed(futures):
|
||||
|
||||
Reference in New Issue
Block a user