160 lines
5.5 KiB
Python
160 lines
5.5 KiB
Python
#!/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()
|