#!/usr/bin/env python3 """ Сборка ФИАС_поиск.exe — standalone, без установки Python. Запускать на Windows с 32-bit Python 3.8: python build.py Перед первым запуском установить зависимости: pip install -r requirements.txt """ from __future__ import annotations import os import subprocess import sys import shutil import struct import platform from pathlib import Path ROOT = Path(__file__).parent def check_env(): bits = struct.calcsize("P") * 8 ver = sys.version_info print(f"Python {ver.major}.{ver.minor}.{ver.micro} {bits}-bit {platform.system()}") if ver < (3, 8): sys.exit("Требуется Python 3.8+") if bits != 32: print(f"ВНИМАНИЕ: Python {bits}-bit. Для win32 .exe используйте 32-bit Python.") try: import PyInstaller print(f"PyInstaller {PyInstaller.__version__}") except ImportError: print("Установка PyInstaller...") subprocess.run( [sys.executable, "-m", "pip", "install", "pyinstaller>=5.13,<7"], check=True, ) def clean(): for name in ("build", "dist", "__pycache__"): p = ROOT / name if p.exists(): shutil.rmtree(p) for spec in ROOT.glob("*.spec"): spec.unlink() print("Очищено") def _data_args() -> list[str]: """--add-data for resources bundled into the .exe so the program is self-contained: КриптоПро XPI, and (if present) portable Firefox + geckodriver.""" args: list[str] = [] # Расширение КриптоПро — обязательно, всегда в репозитории. ext = ROOT / "extensions" if ext.is_dir(): args += ["--add-data", f"{ext}{os.pathsep}extensions"] else: print("ВНИМАНИЕ: папка extensions/ не найдена — расширение КриптоПро не попадёт в сборку!") # Портативный Firefox + geckodriver — наполняются скриптом fetch_vendor.py. vendor = ROOT / "vendor" if vendor.is_dir() and any(vendor.iterdir()): args += ["--add-data", f"{vendor}{os.pathsep}vendor"] print(f"vendor/ включён в сборку: {[p.name for p in vendor.iterdir()]}") else: print( "ВНИМАНИЕ: папка vendor/ пуста или отсутствует — Firefox и geckodriver НЕ будут\n" " встроены. Сначала запустите: python fetch_vendor.py" ) return args def build(): cmd = [ sys.executable, "-m", "PyInstaller", "--onefile", "--noconsole", "--name", "ФИАС_поиск", *_data_args(), # Собрать целиком вместе с data-файлами "--collect-all", "selenium", "--collect-all", "webdriver_manager", "--collect-all", "pdfplumber", "--collect-all", "pdfminer", "--collect-all", "openpyxl", "--collect-all", "certifi", # Явные скрытые импорты "--hidden-import", "selenium.webdriver.firefox.webdriver", "--hidden-import", "selenium.webdriver.firefox.options", "--hidden-import", "selenium.webdriver.firefox.service", "--hidden-import", "selenium.webdriver.support.expected_conditions", "--hidden-import", "selenium.webdriver.common.by", "--hidden-import", "webdriver_manager.firefox", "--hidden-import", "pdfminer.high_level", "--hidden-import", "pdfminer.converter", "--hidden-import", "pdfminer.layout", "--hidden-import", "pdfminer.pdfpage", "--hidden-import", "pdfminer.pdfinterp", "--hidden-import", "pdfminer.pdffont", "--hidden-import", "pdfminer.pdftypes", "--hidden-import", "pdfminer.pdfparser", "--hidden-import", "pdfminer.pdfdevice", "--hidden-import", "openpyxl.styles", "--hidden-import", "openpyxl.utils", "--hidden-import", "cryptography", "--hidden-import", "tkinter", "--hidden-import", "tkinter.filedialog", "--hidden-import", "tkinter.scrolledtext", "--hidden-import", "tkinter.messagebox", "--hidden-import", "queue", "--hidden-import", "logging", "--hidden-import", "configparser", "--hidden-import", "platform", str(ROOT / "main.py"), ] print("\nСборка...") result = subprocess.run(cmd, cwd=ROOT) if result.returncode != 0: sys.exit("PyInstaller завершился с ошибкой") exe = ROOT / "dist" / "ФИАС_поиск.exe" if exe.exists(): mb = exe.stat().st_size / 1024 / 1024 print(f"\n{'='*55}") print(f" Готово: {exe}") print(f" Размер: {mb:.1f} МБ") print(f"{'='*55}") print(" Firefox должен быть установлен на компьютере.") print(" geckodriver скачается автоматически при первом запуске.") else: sys.exit("dist/ФИАС_поиск.exe не найден после сборки") if __name__ == "__main__": import argparse p = argparse.ArgumentParser() p.add_argument("--clean", action="store_true", help="Только очистить build/dist") args = p.parse_args() if args.clean: clean() else: check_env() clean() build()