"""Подготавливает паспорт 166 тем же генератором firmware-info, что используется в 167.""" # Версия, время UTC и ревизии фиксируются до компиляции. import argparse # Разбирает одинаковые с проектом 167 пути prebuild. import importlib.util # Подключает общую реализацию без копирования генератора в прошивку. import json # Объединяет идентификаторы продукта с версией из C-заголовка. from pathlib import Path # Нормализует пути независимо от рабочего каталога CCS. import re # Проверяет единственное допустимое целое определение каждого компонента версии. import tempfile # Хранит объединённые настройки только на время вызова общего генератора. def read_version(header): # Сохраняет правила исходного адаптера 167 и ограничения firmware-info v1. text = re.sub(r"/\*.*?\*/|//[^\n]*", "", header.read_text(encoding="utf-8-sig"), flags=re.S) # Не принимает комментарии или примеры за действующую версию. result = [] # Накапливает major, minor и patch в порядке регистров контракта. for part, limit in (("MAJOR", 255), ("MINOR", 255), ("PATCH", 999)): # Два первых компонента ограничены октетом, исправление допускает 0..999. name = "FIRMWARE_VERSION_" + part # Использует тот же интерфейс заголовка версии, что 167. definitions = re.findall(r"^\s*#\s*define\s+" + name + r"\b([^\n]*)", text, re.M) # Требует единственного определения без условных альтернатив. if len(definitions) != 1 or not re.fullmatch(r"(?:0|[1-9][0-9]*)[uU]?", definitions[0].strip()): # Не допускает выражений, шестнадцатеричных литералов и неоднозначной версии. raise ValueError(f"{header}: define {name} exactly once as a decimal integer") # Останавливает сборку вместо выпуска ложного паспорта. value = int(definitions[0].strip().rstrip("uU")) # Суффикс C не является частью десятичного числа Python. if value > limit: # Проверяет границы до записи какого-либо заголовка сборки. raise ValueError(f"{name} must be in 0..{limit}") # Сохраняет ограничения отображения и Modbus-контракта 167. result.append(value) # Записывает проверенную компоненту в правильное поле паспорта. return result # Номер версии формируется только из firmware_version.h. def prepare(project, templates, output): # Генерирует заголовок и JSON для одной конкретной конфигурации CCS. settings = json.loads((project / "firmware_identity.json").read_text(encoding="utf-8-sig")) # Сохраняет собственный product 166 и тот же target TMS320F28335. settings["version"] = read_version(project / "firmware_version.h") # Не дублирует SemVer в двух редактируемых файлах. spec = importlib.util.spec_from_file_location("shared_firmware_identity", templates / "c/firmware-info/tools/identity.py") # Берёт проверенную общую реализацию из той же зависимости, что 167. identity = importlib.util.module_from_spec(spec) # Создаёт изолированный модуль генератора без изменения системного Python. spec.loader.exec_module(identity) # Загружает формат заголовка, UTC и правила формирования Build ID. with tempfile.TemporaryDirectory(prefix="balsam166-identity-") as directory: # Удаляет только временные объединённые настройки этого вызова. config = Path(directory) / "settings.json" # Настройки не записываются в исходники и не попадают в историю Git. config.write_text(json.dumps(settings), encoding="utf-8") # Передаёт общему генератору проверенную версию и product. return identity.prepare(project, templates, config, output) # Создаёт обязательные generated/firmware_identity.h и JSON до компиляции. if __name__ == "__main__": # Импорт из теста не запускает изменение файлов сборки. parser = argparse.ArgumentParser(description=__doc__) # Объясняет назначение prebuild при ручном вызове. for name in ("project", "templates", "output"): parser.add_argument("--" + name, type=Path, required=True) # Совместим с параметрами prebuild исходного проекта 167. args = parser.parse_args() # Проверяет обязательные каталоги перед генерацией. print(json.dumps(prepare(args.project.resolve(), args.templates.resolve(), args.output.resolve()))) # Показывает фактическую идентичность текущей сборки.