Files
templates/c/firmware-info/tools/c2000_postbuild.py

120 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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 sys # Запускает проектный сборщик тем же Python, которым выполняется общий postbuild.
import tempfile
from identity import archive_build, seal, verify
def build_flash_variant(compiler: Path, build_dir: Path, source: Path, stage: Path) -> tuple: # Возвращает файлы дополнительного Flash-образа только для проектов с явным сборщиком.
builder = build_dir.resolve().parent / "build_flash.py" # Соглашение CCS: Debug/Release расположены рядом с проектным сценарием сборки Flash.
if not builder.is_file(): # Отсутствие сценария оставляет прежний выпуск EEPROM без дополнительных требований к проекту.
return () # Пустой список не добавляет Flash-файлы в архив обычного проекта.
image = stage / (source.stem + "_flash.out") # Отдельное имя и временный каталог исключают подмену RAM-образа и использование устаревшего Flash-файла.
subprocess.run([sys.executable, str(builder), "--postbuild", "--compiler", str(compiler.resolve()), "--build-dir", str(build_dir.resolve()), "--output", str(image)], cwd=build_dir, check=True) # Режим postbuild использует уже собранные объекты и запрещает повторный запуск Make.
files = (image, image.with_suffix(".map")) # Карта линковки сопровождает COFF и позволяет проверить адрес аппаратной точки входа.
for file in files: # Оба результата должны быть выпущены текущим вызовом сборщика.
if not file.is_file() or not file.stat().st_size: # Успешный код процесса без непустого результата не считается успешной сборкой.
raise ValueError(f"Flash builder produced no file: {file}") # Ошибка останавливает postbuild до архивирования или экспорта неполного выпуска.
return files # Передаёт проверенные файлы вызывающему шагу для подписи и сохранения.
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")
flash_files = build_flash_variant(compiler, build_dir, source, stage) # Сначала собирает дополнительный Flash-вариант; его ошибка не допускает выпуск нового 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"))
if flash_files: # Проекты со сборщиком получают независимую проверку точных байтов Flash-образа.
seal(flash_files[0], metadata) # Использует тот же паспорт prebuild для второго варианта этой сборки.
verify(flash_files[0], flash_files[0].with_suffix(".out.manifest.json")) # Проверяет размер и SHA-256 перед сохранением результатов.
serial_image = stage / (artifact + "_flash_rs.bin") # Отдельный SCI8 BIN содержит Flash-адреса и предназначен только для новой команды записи по RS.
serial_map = serial_image.with_suffix('.bin.map') # Карта конверсии позволяет отличить Flash-адреса от EEPROM/RAM BIN.
subprocess.run([str(tool), str(flash_files[0]), '-boot', '-sci8', '-map', str(serial_map), '-o', str(serial_image), '-b'], check=True) # Конвертирует именно Flash COFF с загрузочными адресами секций.
if not serial_image.is_file() or not serial_image.stat().st_size: # Пустая конверсия не должна попасть в выпуск либо каталог GUI.
raise ValueError(f'hex2000 produced no Flash RS image: {serial_image}') # Передаёт ошибку конверсии общему postcompile.
seal(serial_image, metadata) # Привязывает RS BIN к той же версии, ревизии и времени текущего prebuild.
verify(serial_image, serial_image.with_suffix('.bin.manifest.json')) # Проверяет точные байты перед копированием в Bin и архив.
flash_files += (serial_image, serial_map, serial_image.with_suffix('.bin.manifest.json'), serial_image.with_suffix('.bin.sha256')) # Включает транспортный BIN, карту и контрольные файлы в общий выпуск Flash.
for file in flash_files: # Сохраняет COFF и карту также рядом с обычным OUT для выбора в CCS.
destination = source.parent / file.name # Каталог исходного OUT задаёт место назначения без путей конкретного изделия в templates.
destination.write_bytes(file.read_bytes()) # Копирует только результаты успешного текущего вызова, сохраняя исходный RAM-образ.
for item in stage.iterdir():
item.replace(output / item.name)
result = output / bin_image.name # BIN остаётся основным образом EEPROM и возвращаемым результатом общего postbuild.
flash_archive = tuple(output / file.name for file in flash_files) # После переноса из временного каталога берёт актуальные Flash-файлы из firmware.
if flash_files: # Добавляет сопроводительные файлы только при выполненной сборке Flash.
flash_archive += (output / (flash_files[0].name + ".manifest.json"), output / (flash_files[0].name + ".sha256")) # Архив и GUI получают отдельный паспорт и контрольную сумму Flash COFF.
print(f"[firmware-build] Flash OUT: {source.parent / flash_files[0].name}") # Показывает пользователю CCS конкретный файл для прошивки внутренней Flash через JTAG.
archive_build(result, metadata, output, ( # Сохраняет файлы текущей сборки в архиве и передаёт весь архив в каталог GUI.
source, # Копирует исходный COFF-файл OUT с символами отладки под его именем для загрузки через JTAG из GUI.
output / hex_image.name,
output / (hex_image.name + ".map"),
output / (bin_image.name + ".map"), *flash_archive), export=export) # Экспортирует EEPROM и Flash в одной папке текущего выпуска.
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())