fix(ci): improve release build diagnostics and encoding
- 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:
@@ -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'
|
||||||
|
|||||||
66
build.py
66
build.py
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user