Files
templates/cpp/signal-eigen/build.py
T
Andrey 41e24629ce Расширить DEIF, загрузку CAN и общую обработку сигналов
Добавить общее ядро DEIF, эмуляцию приборов и переносимые панели Python/Qt.
Расширить загрузчики F2812 и F28335, обновление Flash и профиль STM Invertor.
Добавить альтернативный МНК Eigen, модели неисправностей и диагностику сигналов.
Обновить справочники устройств, проекты CCS и регрессионные проверки.
2026-10-24 15:40:51 +03:00

64 lines
11 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.
import argparse # Разбираем путь выходной библиотеки и целевую архитектуру настольной сборки.
import hashlib # Проверяем закреплённый архив Eigen перед использованием заголовков.
import os # Находим стандартный каталог установщика Visual Studio в Windows.
from pathlib import Path # Разрешаем пути независимо от текущего рабочего каталога сборки.
import platform # Выбираем расширение библиотеки и инструментарий целевой ОС.
import shutil # Находим доступный системный компилятор для Unix-подобных ОС.
import subprocess # Запускаем компиляторы с проверкой кода завершения.
import tarfile # Читаем заголовки и лицензии из проверенного архива без сетевого доступа.
ROOT = Path(__file__).resolve().parent # Все исходники и закреплённая зависимость расположены рядом со скриптом.
CORE = ROOT.parents[1] / "c" / "set-protocol" # Подключаем исходный C99-алгоритм из того же репозитория, не создавая его копию.
SHA256 = "8586084f71f9bde545ee7fa6d00288b264a2b7ac3607b974e54d13e7162c1c72" # Контрольная сумма официального архива Eigen 3.4.0 из GitLab.
def headers(directory): # Подготавливаем только необходимые заголовки и лицензии в каталоге конкретной сборки.
archive = ROOT / "vendor" / "eigen-3.4.0.tar.gz" # Закреплённый архив позволяет собирать EXE без скачивания зависимостей.
if hashlib.sha256(archive.read_bytes()).hexdigest() != SHA256: # Повреждённая или подменённая зависимость не должна попадать в бинарный модуль.
raise RuntimeError("Неверная SHA-256 архива Eigen 3.4.0") # Останавливаем сборку с явным указанием нарушенной проверки.
destination = directory / "eigen-3.4.0" # Раздельный каталог исключает пересечение разных целевых сборок.
with tarfile.open(archive, "r:gz") as source: # Читаем gzip-архив штатной библиотекой Python, совместимой с Python 3.8.
for member in source.getmembers(): # Рассматриваем каждый элемент, но не используем небезопасную полную распаковку архива.
parts = Path(member.name).parts[1:] # Удаляем единственный верхний каталог официального дистрибутива.
if not member.isfile() or not parts or any(part in ("..", "/", "\\") for part in parts): # Разрешаем только обычные файлы с безопасным относительным путём.
continue # Пропускаем ссылки, каталоги и элементы, способные выйти за корень распаковки.
if parts[0] != "Eigen" and not parts[0].startswith("COPYING"): # Для QR достаточно основного дерева Eigen и его лицензионных файлов.
continue # Примеры, тесты и неподдерживаемые модули не нужны при сборке приложения.
target = destination.joinpath(*parts) # Сохраняем исходное расположение внутренних include относительно корня Eigen.
target.resolve().relative_to(destination.resolve()) # Дополнительно проверяем итоговый абсолютный путь перед записью.
target.parent.mkdir(parents=True, exist_ok=True) # Создаём только каталог текущего безопасного файла.
with source.extractfile(member) as content: # Открываем содержимое обычного файла внутри проверенного архива.
target.write_bytes(content.read()) # Сохраняем оригинальные заголовки без изменения стороннего кода.
return destination # Возвращаем корень, который компилятор должен добавить в include-пути.
def build(output, arch): # Собираем отдельный модуль, сохраняя основное C99-ядро без зависимости от C++.
output = output.resolve() # Абсолютный путь надёжен при смене рабочего каталога компилятора.
directory = output.parent / (".signal-eigen-" + arch) # Объектные файлы x86 и x64 не смешиваются.
directory.mkdir(parents=True, exist_ok=True) # Создаём каталог выходной библиотеки и промежуточных файлов.
include = headers(directory) # Проверяем архив и получаем локальные заголовки Eigen до запуска компилятора.
if platform.system() == "Windows": # На Windows используем тот же установленный MSVC, что основная DLL.
vswhere = Path(os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")) / "Microsoft Visual Studio/Installer/vswhere.exe" # Стандартный путь установщика соответствует основному сборщику SETGUI.
installation = subprocess.check_output([str(vswhere), "-latest", "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath"], text=True).strip() # Находим установку с компиляторами C/C++ для обеих архитектур.
if not installation: # Пустой ответ означает отсутствие подходящего инструментария.
raise RuntimeError("Не найдены Visual Studio C++ Build Tools") # Сообщаем причину вместо создания приложения без выбранного варианта Eigen.
vcvars = Path(installation) / "VC/Auxiliary/Build" / ("vcvars32.bat" if arch == "x86" else "vcvars64.bat") # Настраиваем среду именно под разрядность Python и EXE.
common = ["cl", "/nologo", "/O2", "/MT", "/DPCAN_ABI_BUILD_DLL", "/I" + str(CORE / "include")] # Оба варианта C и Eigen используют оптимизацию и статический CRT без новой зависимости от установленного runtime.
commands = [common + ["/std:c11", "/c", str(CORE / "src/set_signal.c"), "/Fo" + str(directory / "signal.obj")], common + ["/std:c++14", "/EHsc", "/I" + str(include), "/c", str(ROOT / "signal_eigen.cpp"), "/Fo" + str(directory / "eigen.obj")], ["link", "/nologo", "/DLL", "/MACHINE:" + arch.upper(), "/SUBSYSTEM:WINDOWS,6.01", "/OUT:" + str(output), str(directory / "signal.obj"), str(directory / "eigen.obj")]] # Отдельно компилируем C99 и C++, а PE-подсистему закрепляем на Windows 7 (6.01).
for command in commands: # Последовательные этапы используют только готовые объектные файлы предыдущих команд.
subprocess.run('call "%s" && %s' % (vcvars, subprocess.list2cmdline(command)), cwd=directory, shell=True, check=True) # Batch-настройка MSVC и компилятор выполняются в одном процессе cmd.
else: # Настольные Linux/macOS используют системные C/C++-компиляторы и тот же исходный алгоритм.
cc, cxx = shutil.which("cc"), shutil.which("c++") # C-исходник компилируем как C99, а Eigen исключительно как C++14.
if not cc or not cxx: # Обе стадии необходимы для сохранения языковой границы общего ядра.
raise RuntimeError("Нужны компиляторы C и C++") # Отсутствие инструментария не приводит к скрытому отключению Eigen.
subprocess.run([cc, "-O2", "-std=c99", "-fPIC", "-I" + str(CORE / "include"), "-c", str(CORE / "src/set_signal.c"), "-o", str(directory / "signal.o")], check=True) # Получаем позиционно-независимый объект оригинального C99-ядра.
subprocess.run([cxx, "-O2", "-std=c++14", "-fPIC", "-shared", "-I" + str(CORE / "include"), "-I" + str(include), str(ROOT / "signal_eigen.cpp"), str(directory / "signal.o"), "-o", str(output)], check=True) # Линкер C++ добавляет свою стандартную библиотеку к общему настольному модулю.
for name in ("COPYING.MPL2",): # Распространяем основной текст лицензии вместе с бинарным компонентом.
shutil.copyfile(include / name, output.parent / ("Eigen-" + name)) # Имя явно указывает, к какой встроенной библиотеке относится лицензия.
print(output) # Показываем точный путь успешно собранного компонента для журналов сборки.
if __name__ == "__main__": # CLI используется как вручную, так и сборщиком EXE.
parser = argparse.ArgumentParser(description="Сборка альтернативного МНК Eigen QR") # Объясняем назначение отдельного настольного модуля.
parser.add_argument("--output", type=Path, required=True) # Вызывающий определяет выходной путь и расширение DLL/SO.
parser.add_argument("--arch", choices=("x86", "x64"), default="x64") # Windows 7 x86 явно выбирает 32-битный PE вместо значения по умолчанию.
arguments = parser.parse_args() # Проверяем обязательные параметры до работы с файловой системой.
build(arguments.output, arguments.arch) # Полностью выполняем сборку либо завершаем процесс исключением и ненулевым кодом.