fix(ci): improve release build diagnostics and encoding
Some checks failed
Desktop CI / tests (push) Successful in 17s
Desktop Release / release (push) Failing after 1m27s

- run build.py with UTF-8 env in release workflow

- capture full build output to dist/build.log and print it on failure

- extend ISCC output decoding in build.py with UTF-16 fallbacks
This commit is contained in:
2026-02-15 22:34:03 +03:00
parent 0f07fe250c
commit 5be8ab9af7
2 changed files with 46 additions and 34 deletions

View File

@@ -114,8 +114,20 @@ jobs:
- name: Build release zip - name: Build release zip
if: env.CONTINUE == 'true' if: env.CONTINUE == 'true'
shell: powershell shell: powershell
env:
PYTHONUTF8: "1"
PYTHONIOENCODING: "utf-8"
run: | run: |
python build.py $ErrorActionPreference = "Continue"
$buildLog = "dist/build.log"
New-Item -ItemType Directory -Force -Path "dist" | Out-Null
python build.py *>&1 | Tee-Object -FilePath $buildLog
$code = $LASTEXITCODE
if ($code -ne 0) {
Write-Host "Build failed with exit code $code. Dumping build log:"
Get-Content -Path $buildLog -Raw
exit $code
}
- name: Ensure artifacts exist - name: Ensure artifacts exist
if: env.CONTINUE == 'true' if: env.CONTINUE == 'true'

View File

@@ -4,7 +4,7 @@ import subprocess
import sys import sys
from app_version import APP_VERSION from app_version import APP_VERSION
# --- Конфигурация --- # --- Configuration ---
APP_NAME = "AnabasisManager" APP_NAME = "AnabasisManager"
UPDATER_NAME = "AnabasisUpdater" UPDATER_NAME = "AnabasisUpdater"
VERSION = APP_VERSION # Единая версия приложения VERSION = APP_VERSION # Единая версия приложения
@@ -32,38 +32,38 @@ def write_version_marker():
os.makedirs(DIST_DIR, exist_ok=True) os.makedirs(DIST_DIR, exist_ok=True)
with open(marker_path, "w", encoding="utf-8") as f: with open(marker_path, "w", encoding="utf-8") as f:
f.write(str(VERSION).strip() + "\n") f.write(str(VERSION).strip() + "\n")
print(f"[OK] Обновлен маркер версии: {marker_path}") print(f"[OK] Version marker written: {marker_path}")
except Exception as e: except Exception as e:
print(f"[ERROR] Не удалось записать version.txt: {e}") print(f"[ERROR] Failed to write version.txt: {e}")
sys.exit(1) sys.exit(1)
def copy_icon_to_dist(): def copy_icon_to_dist():
icon_abs_path = os.path.abspath(ICON_PATH) icon_abs_path = os.path.abspath(ICON_PATH)
if not os.path.exists(icon_abs_path): if not os.path.exists(icon_abs_path):
print("[WARN] icon.ico не найден, пропуск копирования иконки в dist.") print("[WARN] icon.ico not found, skipping icon copy into dist.")
return return
try: try:
os.makedirs("dist", exist_ok=True) os.makedirs("dist", exist_ok=True)
os.makedirs(DIST_DIR, exist_ok=True) os.makedirs(DIST_DIR, exist_ok=True)
shutil.copy2(icon_abs_path, os.path.join("dist", "icon.ico")) shutil.copy2(icon_abs_path, os.path.join("dist", "icon.ico"))
shutil.copy2(icon_abs_path, os.path.join(DIST_DIR, "icon.ico")) shutil.copy2(icon_abs_path, os.path.join(DIST_DIR, "icon.ico"))
print("[OK] Иконка скопирована в dist/icon.ico и dist/AnabasisManager/icon.ico") print("[OK] Icon copied to dist/icon.ico and dist/AnabasisManager/icon.ico")
except Exception as e: except Exception as e:
print(f"[ERROR] Не удалось скопировать icon.ico в dist: {e}") print(f"[ERROR] Failed to copy icon.ico into dist: {e}")
sys.exit(1) sys.exit(1)
def ensure_project_root(): def ensure_project_root():
missing = [name for name in SAFE_CLEAN_ROOT_FILES if not os.path.exists(name)] missing = [name for name in SAFE_CLEAN_ROOT_FILES if not os.path.exists(name)]
if missing: if missing:
print("[ERROR] Скрипт нужно запускать из корня проекта.") print("[ERROR] Run this script from the project root.")
print(f"[ERROR] Не найдены: {', '.join(missing)}") print(f"[ERROR] Missing files: {', '.join(missing)}")
sys.exit(1) sys.exit(1)
def run_build(): def run_build():
print(f"--- 1. Запуск PyInstaller для {APP_NAME} v{VERSION} ---") print(f"--- 1. Running PyInstaller for {APP_NAME} v{VERSION} ---")
icon_abs_path = os.path.abspath(ICON_PATH) icon_abs_path = os.path.abspath(ICON_PATH)
has_icon = os.path.exists(icon_abs_path) has_icon = os.path.exists(icon_abs_path)
@@ -86,14 +86,14 @@ def run_build():
try: try:
subprocess.check_call(command) subprocess.check_call(command)
print("\n[OK] Сборка PyInstaller завершена.") print("\n[OK] PyInstaller build completed.")
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"\n[ERROR] Ошибка при сборке: {e}") print(f"\n[ERROR] Build failed: {e}")
sys.exit(1) sys.exit(1)
def run_updater_build(): def run_updater_build():
print(f"\n--- 1.2 Сборка {UPDATER_NAME} ---") print(f"\n--- 1.2 Building {UPDATER_NAME} ---")
icon_abs_path = os.path.abspath(ICON_PATH) icon_abs_path = os.path.abspath(ICON_PATH)
has_icon = os.path.exists(icon_abs_path) has_icon = os.path.exists(icon_abs_path)
updater_spec_dir = os.path.join("build", "updater_spec") updater_spec_dir = os.path.join("build", "updater_spec")
@@ -116,14 +116,14 @@ def run_updater_build():
command = [arg for arg in command if arg] command = [arg for arg in command if arg]
try: try:
subprocess.check_call(command) subprocess.check_call(command)
print(f"[OK] {UPDATER_NAME} собран.") print(f"[OK] {UPDATER_NAME} built.")
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"[ERROR] Ошибка при сборке {UPDATER_NAME}: {e}") print(f"[ERROR] Failed to build {UPDATER_NAME}: {e}")
sys.exit(1) sys.exit(1)
def run_cleanup(): def run_cleanup():
print(f"\n--- 2. Оптимизация папки {APP_NAME} ---") print(f"\n--- 2. Optimizing {APP_NAME} folder ---")
# Пытаемся найти папку PySide6 внутри сборки # Пытаемся найти папку PySide6 внутри сборки
pyside_path = os.path.join(DIST_DIR, "PySide6") pyside_path = os.path.join(DIST_DIR, "PySide6")
@@ -138,21 +138,21 @@ def run_cleanup():
shutil.rmtree(path) shutil.rmtree(path)
else: else:
os.remove(path) os.remove(path)
print(f"Удалено: {item}") print(f"Removed: {item}")
except Exception as e: except Exception as e:
print(f"Пропуск {item}: {e}") print(f"Skipped {item}: {e}")
def create_archive(): def create_archive():
print(f"\n--- 3. Создание архива {ARCHIVE_NAME}.zip ---") print(f"\n--- 3. Creating archive {ARCHIVE_NAME}.zip ---")
try: try:
# Создаем zip-архив из папки DIST_DIR # Создаем zip-архив из папки DIST_DIR
# base_name - имя файла без расширения, format - 'zip', root_dir - что упаковываем # base_name - имя файла без расширения, format - 'zip', root_dir - что упаковываем
shutil.make_archive(os.path.join("dist", ARCHIVE_NAME), 'zip', DIST_DIR) shutil.make_archive(os.path.join("dist", ARCHIVE_NAME), 'zip', DIST_DIR)
print(f"[OK] Архив создан: dist/{ARCHIVE_NAME}.zip") print(f"[OK] Archive created: dist/{ARCHIVE_NAME}.zip")
except Exception as e: except Exception as e:
print(f"[ERROR] Не удалось создать архив: {e}") print(f"[ERROR] Failed to create archive: {e}")
def _find_iscc(): def _find_iscc():
@@ -175,7 +175,7 @@ def _decode_process_output(raw_bytes):
return "" return ""
if isinstance(raw_bytes, str): if isinstance(raw_bytes, str):
return raw_bytes return raw_bytes
for enc in ("utf-8", "cp1251", "cp866"): for enc in ("utf-8-sig", "utf-16", "utf-16-le", "cp1251", "cp866", "latin-1"):
try: try:
return raw_bytes.decode(enc) return raw_bytes.decode(enc)
except Exception: except Exception:
@@ -184,20 +184,20 @@ def _decode_process_output(raw_bytes):
def build_installer(): def build_installer():
print(f"\n--- 4. Создание установщика {INSTALLER_NAME} ---") print(f"\n--- 4. Building installer {INSTALLER_NAME} ---")
if os.name != "nt": if os.name != "nt":
print("[INFO] Установщик Inno Setup создается только на Windows. Шаг пропущен.") print("[INFO] Inno Setup installer is built only on Windows. Step skipped.")
return return
if not os.path.exists(INSTALLER_SCRIPT): if not os.path.exists(INSTALLER_SCRIPT):
print(f"[ERROR] Не найден скрипт установщика: {INSTALLER_SCRIPT}") print(f"[ERROR] Installer script not found: {INSTALLER_SCRIPT}")
sys.exit(1) sys.exit(1)
if not os.path.exists(DIST_DIR): if not os.path.exists(DIST_DIR):
print(f"[ERROR] Не найдена папка сборки приложения: {DIST_DIR}") print(f"[ERROR] Build output folder not found: {DIST_DIR}")
sys.exit(1) sys.exit(1)
iscc_path = _find_iscc() iscc_path = _find_iscc()
if not iscc_path: if not iscc_path:
print("[ERROR] Не найден Inno Setup Compiler (ISCC.exe).") print("[ERROR] Inno Setup Compiler (ISCC.exe) not found.")
print("[ERROR] Установите Inno Setup 6 или задайте переменную окружения ISCC_PATH.") print("[ERROR] Install Inno Setup 6 or set ISCC_PATH environment variable.")
sys.exit(1) sys.exit(1)
icon_abs_path = os.path.abspath(ICON_PATH) icon_abs_path = os.path.abspath(ICON_PATH)
@@ -224,11 +224,11 @@ def build_installer():
raise RuntimeError(f"ISCC exited with code {completed.returncode}") raise RuntimeError(f"ISCC exited with code {completed.returncode}")
installer_path = os.path.join("dist", INSTALLER_NAME) installer_path = os.path.join("dist", INSTALLER_NAME)
if not os.path.exists(installer_path): if not os.path.exists(installer_path):
print(f"[ERROR] Установщик не создан: {installer_path}") print(f"[ERROR] Installer was not created: {installer_path}")
sys.exit(1) sys.exit(1)
print(f"[OK] Установщик создан: {installer_path}") print(f"[OK] Installer created: {installer_path}")
except Exception as e: except Exception as e:
print(f"[ERROR] Ошибка при создании установщика: {e}") print(f"[ERROR] Failed to build installer: {e}")
sys.exit(1) sys.exit(1)
@@ -248,7 +248,7 @@ if __name__ == "__main__":
build_installer() build_installer()
print("\n" + "=" * 30) print("\n" + "=" * 30)
print("ПРОЦЕСС ЗАВЕРШЕН") print("BUILD COMPLETED")
print(f"Файл для отправки: dist/{ARCHIVE_NAME}.zip") print(f"Release archive: dist/{ARCHIVE_NAME}.zip")
print(f"Установщик: dist/{INSTALLER_NAME}") print(f"Installer: dist/{INSTALLER_NAME}")
print("=" * 30) print("=" * 30)