86 lines
3.8 KiB
Python
86 lines
3.8 KiB
Python
"""Convert a CCS C2000 OUT to SCI8 HEX/BIN and seal its pre-build identity.
|
|
|
|
Uses only the standard library, TI hex2000 and the shared identity module.
|
|
No network, SETGUI, or project-specific paths are required.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import tempfile
|
|
|
|
from identity import archive_build, seal, verify
|
|
|
|
|
|
def postbuild(compiler: Path, build_dir: Path, artifact: str,
|
|
metadata: Path, output: Path, export: Path = None) -> Path:
|
|
# CCS may include the configured output directory in this macro.
|
|
# Resolve the input separately; output filenames must use only its basename.
|
|
artifact_path = Path(artifact)
|
|
if not artifact or artifact_path.name in ("", ".", ".."):
|
|
raise ValueError("artifact must name an OUT file without its extension")
|
|
artifact = artifact_path.name
|
|
if any(c in artifact for c in '\\/:*?"<>|'):
|
|
raise ValueError("invalid artifact file base name")
|
|
source_base = (artifact_path if artifact_path.is_absolute()
|
|
else build_dir / artifact_path)
|
|
source = Path(str(source_base) + ".out").resolve()
|
|
tool = compiler.resolve() / "bin" / "hex2000.exe"
|
|
if not tool.is_file():
|
|
raise FileNotFoundError(f"C2000 Hex Utility not found: {tool}")
|
|
if not source.is_file():
|
|
raise FileNotFoundError(f"compiled OUT not found: {source}")
|
|
metadata = metadata.resolve()
|
|
settings = json.loads(metadata.read_text(encoding="utf-8"))
|
|
build_id = settings["build_id"]
|
|
if not isinstance(build_id, str) or not re.fullmatch(r"[0-9a-fA-F]{8}|[0-9a-fA-F]{7}\+|NOGIT000", build_id):
|
|
raise ValueError("invalid pre-build identity; run precompile before building")
|
|
if build_id == "NOGIT000":
|
|
print("[firmware-build] Git identity unavailable: using NOGIT000")
|
|
output = output.resolve()
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
# Keep failed conversions and incomplete manifests out of the final folder.
|
|
with tempfile.TemporaryDirectory(prefix=".firmware-", dir=output) as temporary:
|
|
stage = Path(temporary)
|
|
hex_image = stage / (artifact + ".hex")
|
|
bin_image = stage / (artifact + "_" + build_id + ".bin")
|
|
for image, mode in ((hex_image, "-i"), (bin_image, "-b")):
|
|
subprocess.run([str(tool), str(source), "-boot", "-sci8",
|
|
"-map", str(image.with_suffix(image.suffix + ".map")),
|
|
"-o", str(image), mode], check=True)
|
|
if not image.is_file() or not image.stat().st_size:
|
|
raise ValueError(f"hex2000 produced no image: {image}")
|
|
seal(bin_image, metadata)
|
|
verify(bin_image, bin_image.with_suffix(bin_image.suffix + ".manifest.json"))
|
|
for item in stage.iterdir():
|
|
item.replace(output / item.name)
|
|
result = output / bin_image.name
|
|
archive_build(result, metadata, output, (
|
|
output / hex_image.name,
|
|
output / (hex_image.name + ".map"),
|
|
output / (bin_image.name + ".map")), export=export)
|
|
print(f"[firmware-build] BIN: {result}")
|
|
print(f"[firmware-build] Manifest: {result}.manifest.json")
|
|
print(f"[firmware-build] SHA-256: {result}.sha256")
|
|
return result
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
for name in ("compiler", "build-dir", "metadata", "output"):
|
|
parser.add_argument("--" + name, type=Path, required=True)
|
|
parser.add_argument("--artifact", required=True)
|
|
parser.add_argument("--export", type=Path)
|
|
try:
|
|
postbuild(**vars(parser.parse_args()))
|
|
except (OSError, ValueError, KeyError, TypeError, subprocess.CalledProcessError) as error:
|
|
parser.exit(1, f"[firmware-build] ERROR: {error}\n")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|