Files
templates/tools/firmware-build/python-runtime.ps1
T
Andrey 4d101302be Добавить SCI и J-Link и обновить общие порты устройств
Подключить профили прошивки из JSON и подготовку Python для CCS/Keil.
Добавить SCI-программатор F28335 и поддержку STM32 через J-Link.
Обновить Linux-консоль, CAN485 и Modbus и проверки общих портов.
2026-11-13 17:07:20 +03:00

88 lines
16 KiB
PowerShell
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.
[CmdletBinding(PositionalBinding = $false)] # Именованные служебные параметры не должны поглощать позиционный путь запускаемого Python-скрипта.
param( # Единая точка входа CCS/Keil работает без зависимости от python.exe в PATH.
[string]$ResolveFile, # При настройке IDE записывает точный путь проверенного интерпретатора в UTF-8.
[switch]$CheckOnly, # Проверка окружения не скачивает runtime и не запускает проектный скрипт.
[Parameter(Position = 0, ValueFromRemainingArguments = $true)][string[]]$PythonArguments # Путь скрипта и все оставшиеся аргументы передаются Python без позиционной привязки к ResolveFile.
) # Завершает описание публичных параметров обвязки.
$ErrorActionPreference = 'Stop' # Любая ошибка подготовки возвращается IDE как отказ шага сборки.
$env:PYTHONUTF8 = '1' # Python 3.8+ передаёт пути и диагностику в UTF-8 независимо от локальной кодовой страницы Windows.
[Console]::OutputEncoding = New-Object Text.UTF8Encoding($false) # Windows PowerShell 5 должен читать вывод Python в той же кодировке.
$OutputEncoding = [Console]::OutputEncoding # Согласует кодировку дочерних native-процессов с JSON и сообщениями SETGUI.
function Test-FirmwarePython([string]$Candidate) { # Проверяет настоящий Python вместо одноимённого псевдонима Microsoft Store.
if (-not $Candidate -or -not (Test-Path -LiteralPath $Candidate -PathType Leaf) -or $Candidate -like '*WindowsApps*') { return $null } # Пропускает отсутствующие файлы и псевдонимы магазина без открытия окна установки.
try { # Неподходящий кандидат не мешает проверить остальные установленные интерпретаторы.
$result = & $Candidate -c 'import sys,json,hashlib,struct,tempfile,ctypes,xml.etree.ElementTree;sys.exit(1) if sys.version_info<(3,8) else print(sys.executable)' 2>$null # Общим инструментам нужны Python 3.8+ и только стандартная библиотека.
if ($LASTEXITCODE -eq 0 -and $result) { return [string]($result | Select-Object -Last 1) } # Возвращает фактический exe, а не имя команды или путь launcher.
} catch {} # Ошибка запуска одного кандидата оставляет поиск остальных доступным.
return $null # Отсутствие пригодного runtime обрабатывается общей установкой ниже.
} # Завершает проверку одного интерпретатора.
function Find-FirmwarePython { # Ищет готовый runtime до любых сетевых действий.
if ($env:FIRMWARE_PYTHON) { # Явный выбор администратора проекта имеет приоритет над автоматическим поиском.
$chosen = Test-FirmwarePython $env:FIRMWARE_PYTHON # Проверяет указанный абсолютный путь тем же набором импортов.
if (-not $chosen) { throw 'FIRMWARE_PYTHON должен указывать на работающий Python 3.8+.' } # Неверное явное значение не подменяется другой версией незаметно.
return $chosen # Оба шага IDE используют выбранный интерпретатор.
} # После явной настройки остальные кандидаты не нужны.
$cache = Join-Path $env:LOCALAPPDATA 'SET/FirmwareBuild' # Локальный runtime принадлежит пользователю и не требует администратора.
foreach ($relative in @('python-3.13.12-amd64/python.exe', 'python-3.8.10-win32/python.exe')) { # Порядок предпочитает современный runtime, сохраняя совместимость с Windows 7.
$chosen = Test-FirmwarePython (Join-Path $cache $relative) # Проверяет ранее подготовленную локальную копию.
if ($chosen) { return $chosen } # Повторные сборки обходятся без скачивания и установки.
} # Завершает поиск локального управляемого runtime.
foreach ($command in @('python', 'python3')) { # Установленный Python можно использовать без изменения пользовательского PATH.
$candidate = Get-Command $command -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 # Получает полный путь исполняемого файла команды.
if ($candidate) { $chosen = Test-FirmwarePython $candidate.Source; if ($chosen) { return $chosen } } # Не принимает неподходящую версию за готовое окружение.
} # После PATH проверяет зарегистрированные установки без требования перезапуска IDE.
foreach ($registry in @('HKCU:/Software/Python/PythonCore', 'HKLM:/Software/Python/PythonCore', 'HKLM:/Software/WOW6432Node/Python/PythonCore')) { # PEP 514 учитывает установки текущего пользователя, x64 и x86.
foreach ($version in @(Get-ChildItem -LiteralPath $registry -ErrorAction SilentlyContinue | Sort-Object PSChildName -Descending)) { # Перебирает только зарегистрированные версии Python.
$key = Get-Item -LiteralPath ($version.PSPath + '/InstallPath') -ErrorAction SilentlyContinue # Реестр хранит корневой каталог независимой установки.
if ($key) { $chosen = Test-FirmwarePython (Join-Path ([string]$key.GetValue('')) 'python.exe'); if ($chosen) { return $chosen } } # Возвращает первую реально работающую версию 3.8+.
} # Завершает перебор версий одного раздела реестра.
} # Ни один существующий runtime не оказался пригодным.
return $null # Вызывающий подготовит переносимый runtime, если проверка допускает установку.
} # Завершает поиск установленного Python.
try { # Все ошибки возвращаются одним ненулевым кодом, понятным CCS/Keil.
$pythonExe = Find-FirmwarePython # Использует готовую установку без изменения окружения машины.
if (-not $pythonExe -and $CheckOnly) { throw 'Python 3.8+ не найден; примените автоматическую настройку проекта для подготовки runtime.' } # Режим диагностики не устанавливает ПО.
if (-not $pythonExe) { # Установка нужна только компьютеру без подходящего Python.
$legacy = [Environment]::OSVersion.Version -lt [Version]'6.2' -or -not [Environment]::Is64BitOperatingSystem # Windows 7 и x86 используют последнюю совместимую бинарную ветку 3.8.
$version = if ($legacy) { '3.8.10' } else { '3.13.12' } # Зафиксированные версии делают подготовку воспроизводимой.
$arch = if ($legacy) { 'win32' } else { 'amd64' } # Разрядность runtime не зависит от разрядности установленной IDE.
$expected = if ($legacy) { '760dc79bcb434ee80b1001a30bb6f798287881851bac6d8137867894d40ef1fc' } else { '76f238f606250c87c6beac75dccd35ee99070a13490555936abb6cb64ecce3d0' } # SHA-256 официальных embeddable ZIP с python.org проверен при подготовке обвязки.
$cache = Join-Path $env:LOCALAPPDATA 'SET/FirmwareBuild' # Runtime хранится отдельно от исходников и сохраняется после Clean.
$destination = Join-Path $cache ('python-' + $version + '-' + $arch) # Каждый вариант получает независимый каталог пользователя.
New-Item -ItemType Directory -Force -Path $cache | Out-Null # Создаёт только собственный каталог инструментов сборки.
$temporary = Join-Path $cache ('download-' + [Guid]::NewGuid().ToString('N')) # Уникальный staging исключает частично установленный runtime.
New-Item -ItemType Directory -Path $temporary | Out-Null # Распаковка выполняется вне постоянного каталога.
try { # Ошибка сети или распаковки не должна повредить ранее установленный Python.
$archive = Join-Path $temporary 'python.zip' # Собственный архив удаляется вместе со staging.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Загрузка использует TLS 1.2 также на Windows 7 с обновлённой .NET.
$client = New-Object Net.WebClient # WebClient доступен штатной Windows PowerShell без внешних модулей.
try { $client.DownloadFile(('https://www.python.org/ftp/python/' + $version + '/python-' + $version + '-embed-' + $arch + '.zip'), $archive) } finally { $client.Dispose() } # Скачивает строго официальный зафиксированный пакет Python.
$sha = [Security.Cryptography.SHA256]::Create() # Проверяет полные байты архива до запуска любого файла из него.
try { $actual = ([BitConverter]::ToString($sha.ComputeHash([IO.File]::ReadAllBytes($archive)))).Replace('-', '').ToLowerInvariant() } finally { $sha.Dispose() } # Нормализует SHA-256 для точного сравнения с закреплённым значением.
if ($actual -ne $expected) { throw 'SHA-256 архива Python не совпал с ожидаемым значением.' } # Неподтверждённый архив никогда не распаковывается.
Add-Type -AssemblyName System.IO.Compression.FileSystem # Использует штатную .NET 4.5+ для ZIP без зависимости от Python.
$unpacked = Join-Path $temporary 'runtime' # Отделяет исполняемые файлы от скачанного ZIP.
[IO.Compression.ZipFile]::ExtractToDirectory($archive, $unpacked) # Распаковывает проверенный официальный ZIP во временный каталог.
$checked = Test-FirmwarePython (Join-Path $unpacked 'python.exe') # До публикации проверяет версию и необходимые модули runtime.
if (-not $checked) { throw 'Подготовленный Python не запускается; проверьте Universal C Runtime Windows.' } # Отсутствующая системная зависимость не выдаётся за успех установки.
if (Test-Path -LiteralPath $destination) { throw ('Каталог runtime занят непригодной установкой: ' + $destination) } # Не удаляет существующие пользовательские файлы автоматически.
Move-Item -LiteralPath $unpacked -Destination $destination # Атомарно публикует только полностью проверенную локальную установку.
$pythonExe = Join-Path $destination 'python.exe' # Все последующие вызовы используют точный путь установленного runtime.
} finally { # Очищает только собственный staging после проверки его абсолютного расположения.
$resolved = [IO.Path]::GetFullPath($temporary) # Нормализует путь перед рекурсивным удалением на Windows.
if ($resolved.StartsWith([IO.Path]::GetFullPath($cache) + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { Remove-Item -LiteralPath $resolved -Recurse -Force -ErrorAction SilentlyContinue } # Удаляет исключительно созданную текущим вызовом папку внутри собственного кэша.
} # Завершает подготовку одного переносимого runtime.
} # Готовый Python найден или успешно подготовлен.
if ($ResolveFile) { [IO.File]::WriteAllText([IO.Path]::GetFullPath($ResolveFile), $pythonExe, (New-Object Text.UTF8Encoding($false))) } # GUI получает точный путь без разбора локализованного вывода консоли.
if ($PythonArguments.Count) { # Непустые аргументы означают запуск шага сборки после подготовки зависимостей.
$bootstrap = 'import os,runpy,sys;p=sys.argv[1];sys.argv=sys.argv[1:];sys.path.insert(0,os.path.dirname(os.path.abspath(p)));runpy.run_path(p,run_name=''__main__'')' # Одинарные Python-кавычки сохраняются при передаче через Windows PowerShell 5 и изолированный runtime.
& $pythonExe -c $bootstrap @PythonArguments # Передаёт исходные аргументы отдельными элементами без повторного разбора командной оболочкой.
exit $LASTEXITCODE # Ошибка Python останавливает prebuild/postbuild с исходным кодом завершения.
} # В режиме настройки достаточно успешной подготовки runtime.
Write-Output $pythonExe # Терминал показывает фактически выбранный интерпретатор.
exit 0 # Успех означает работающий Python с проверенной стандартной библиотекой.
} catch { # Единая диагностика доступна как в IDE, так и в окне настройки SETGUI.
[Console]::Error.WriteLine('[firmware-build] ' + $_.Exception.Message) # Показывает конкретную причину отказа без скрытого перехода к другой цепочке.
exit 2 # Ненулевой код запрещает дальнейшие стадии неподготовленной сборки.
} # Завершает общий запуск runtime и инструментов прошивки.