import os import shutil import subprocess import sys from app_version import APP_VERSION # --- Конфигурация --- APP_NAME = "AnabasisManager" UPDATER_NAME = "AnabasisUpdater" VERSION = APP_VERSION # Единая версия приложения MAIN_SCRIPT = "main.py" UPDATER_SCRIPT = "updater_gui.py" ICON_PATH = "icon.ico" INSTALLER_SCRIPT = os.path.join("installer", "AnabasisManager.iss") DIST_DIR = os.path.join("dist", APP_NAME) ARCHIVE_NAME = f"{APP_NAME}-{VERSION}" # Формат Название-Версия INSTALLER_NAME = f"{APP_NAME}-setup-{VERSION}.exe" SAFE_CLEAN_ROOT_FILES = {"main.py", "updater_gui.py", "requirements.txt", "build.py"} REMOVE_LIST = [ "Qt6Pdf.dll", "Qt6PdfQuick.dll", "Qt6PdfWidgets.dll", "Qt6VirtualKeyboard.dll", "Qt6Positioning.dll", "Qt6PrintSupport.dll", "Qt6Svg.dll", "Qt6Sql.dll", "Qt6Charts.dll", "Qt6Multimedia.dll", "Qt63DCore.dll", "translations", "Qt6QuickTemplates2.dll" ] def write_version_marker(): marker_path = os.path.join(DIST_DIR, "version.txt") try: os.makedirs(DIST_DIR, exist_ok=True) with open(marker_path, "w", encoding="utf-8") as f: f.write(str(VERSION).strip() + "\n") print(f"[OK] Обновлен маркер версии: {marker_path}") except Exception as e: print(f"[ERROR] Не удалось записать version.txt: {e}") sys.exit(1) def ensure_project_root(): missing = [name for name in SAFE_CLEAN_ROOT_FILES if not os.path.exists(name)] if missing: print("[ERROR] Скрипт нужно запускать из корня проекта.") print(f"[ERROR] Не найдены: {', '.join(missing)}") sys.exit(1) def run_build(): print(f"--- 1. Запуск PyInstaller для {APP_NAME} v{VERSION} ---") icon_abs_path = os.path.abspath(ICON_PATH) has_icon = os.path.exists(icon_abs_path) command = [ "pyinstaller", "--noconfirm", "--onedir", "--windowed", "--exclude-module", "PySide6.QtWebEngineCore", "--exclude-module", "PySide6.QtWebEngineWidgets", "--exclude-module", "PySide6.QtWebEngineQuick", f"--name={APP_NAME}", f"--icon={icon_abs_path}" if has_icon else "", f"--add-data={icon_abs_path}{os.pathsep}." if has_icon else "", f"--add-data=auth_webview.py{os.pathsep}.", MAIN_SCRIPT ] command = [arg for arg in command if arg] try: subprocess.check_call(command) print("\n[OK] Сборка PyInstaller завершена.") except subprocess.CalledProcessError as e: print(f"\n[ERROR] Ошибка при сборке: {e}") sys.exit(1) def run_updater_build(): print(f"\n--- 1.2 Сборка {UPDATER_NAME} ---") icon_abs_path = os.path.abspath(ICON_PATH) has_icon = os.path.exists(icon_abs_path) updater_spec_dir = os.path.join("build", "updater_spec") updater_spec_path = os.path.join(updater_spec_dir, f"{UPDATER_NAME}.spec") if os.path.exists(updater_spec_path): os.remove(updater_spec_path) command = [ "pyinstaller", "--noconfirm", "--clean", "--onefile", "--windowed", f"--name={UPDATER_NAME}", "--distpath", DIST_DIR, "--workpath", os.path.join("build", "updater"), "--specpath", updater_spec_dir, f"--icon={icon_abs_path}" if has_icon else "", UPDATER_SCRIPT, ] command = [arg for arg in command if arg] try: subprocess.check_call(command) print(f"[OK] {UPDATER_NAME} собран.") except subprocess.CalledProcessError as e: print(f"[ERROR] Ошибка при сборке {UPDATER_NAME}: {e}") sys.exit(1) def run_cleanup(): print(f"\n--- 2. Оптимизация папки {APP_NAME} ---") # Пытаемся найти папку PySide6 внутри сборки pyside_path = os.path.join(DIST_DIR, "PySide6") if not os.path.exists(pyside_path): pyside_path = DIST_DIR for item in REMOVE_LIST: path = os.path.join(pyside_path, item) if os.path.exists(path): try: if os.path.isdir(path): shutil.rmtree(path) else: os.remove(path) print(f"Удалено: {item}") except Exception as e: print(f"Пропуск {item}: {e}") def create_archive(): print(f"\n--- 3. Создание архива {ARCHIVE_NAME}.zip ---") try: # Создаем zip-архив из папки DIST_DIR # base_name - имя файла без расширения, format - 'zip', root_dir - что упаковываем shutil.make_archive(os.path.join("dist", ARCHIVE_NAME), 'zip', DIST_DIR) print(f"[OK] Архив создан: dist/{ARCHIVE_NAME}.zip") except Exception as e: print(f"[ERROR] Не удалось создать архив: {e}") def _find_iscc(): candidates = [] iscc_env = os.getenv("ISCC_PATH", "").strip() if iscc_env: candidates.append(iscc_env) candidates.append(shutil.which("iscc")) candidates.append(shutil.which("ISCC.exe")) candidates.append(r"C:\Program Files (x86)\Inno Setup 6\ISCC.exe") candidates.append(r"C:\Program Files\Inno Setup 6\ISCC.exe") for candidate in candidates: if candidate and os.path.exists(candidate): return candidate return "" def build_installer(): print(f"\n--- 4. Создание установщика {INSTALLER_NAME} ---") if os.name != "nt": print("[INFO] Установщик Inno Setup создается только на Windows. Шаг пропущен.") return if not os.path.exists(INSTALLER_SCRIPT): print(f"[ERROR] Не найден скрипт установщика: {INSTALLER_SCRIPT}") sys.exit(1) if not os.path.exists(DIST_DIR): print(f"[ERROR] Не найдена папка сборки приложения: {DIST_DIR}") sys.exit(1) iscc_path = _find_iscc() if not iscc_path: print("[ERROR] Не найден Inno Setup Compiler (ISCC.exe).") print("[ERROR] Установите Inno Setup 6 или задайте переменную окружения ISCC_PATH.") sys.exit(1) icon_abs_path = os.path.abspath(ICON_PATH) command = [ iscc_path, f"/DMyAppVersion={VERSION}", f"/DMySourceDir={os.path.abspath(DIST_DIR)}", f"/DMyOutputDir={os.path.abspath('dist')}", f"/DMyIconFile={icon_abs_path}", os.path.abspath(INSTALLER_SCRIPT), ] try: subprocess.check_call(command) installer_path = os.path.join("dist", INSTALLER_NAME) if not os.path.exists(installer_path): print(f"[ERROR] Установщик не создан: {installer_path}") sys.exit(1) print(f"[OK] Установщик создан: {installer_path}") except subprocess.CalledProcessError as e: print(f"[ERROR] Ошибка при создании установщика: {e}") sys.exit(1) if __name__ == "__main__": ensure_project_root() # Предварительная очистка for folder in ["build", "dist"]: if os.path.exists(folder): shutil.rmtree(folder) run_build() run_updater_build() run_cleanup() write_version_marker() create_archive() build_installer() print("\n" + "=" * 30) print("ПРОЦЕСС ЗАВЕРШЕН") print(f"Файл для отправки: dist/{ARCHIVE_NAME}.zip") print(f"Установщик: dist/{INSTALLER_NAME}") print("=" * 30)