testR1
This commit is contained in:
269
build_32bit.py
Normal file
269
build_32bit.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
Скрипт сборки Программы 17 (Kadastr-17) в 32-битный исполняемый файл.
|
||||
|
||||
Требования перед запуском:
|
||||
• 32-битный Python (3.8 — 3.12) установлен и доступен в PATH или PYTHON32
|
||||
• PyInstaller, openpyxl, Pillow установлены для этого интерпретатора
|
||||
|
||||
Быстрый старт на Windows:
|
||||
py -3.8-32 build_32bit.py
|
||||
|
||||
Быстрый старт на Linux (32-bit Python в /usr/bin/python3.8):
|
||||
PYTHON32=/path/to/python3.8-32bit python3 build_32bit.py
|
||||
|
||||
Переменная окружения PYTHON32 позволяет указать нужный интерпретатор явно.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ─────────────────────────────── Конфигурация ───────────────────────────── #
|
||||
|
||||
APP_NAME = "KadastrParser17"
|
||||
MAIN_SCRIPT = "main.py"
|
||||
DIST_DIR = "dist_32bit"
|
||||
BUILD_DIR = "build_32bit"
|
||||
|
||||
# Имя результирующего файла (на Windows добавится .exe автоматически)
|
||||
EXE_NAME = f"{APP_NAME}_32bit"
|
||||
|
||||
# Разделитель путей в --add-data: ':' на Linux/macOS, ';' на Windows
|
||||
_SEP = ";" if sys.platform == "win32" else ":"
|
||||
|
||||
# Файлы/папки данных, которые нужно включить в сборку.
|
||||
# Формат: (источник, папка-назначение-внутри-exe)
|
||||
ADD_DATA = [
|
||||
# Шаблон формы — программа ищет его по пути data/ШАБЛОН ВЫЯВЛЕННЫЕ-1.xlsx
|
||||
(f"data{_SEP}data"),
|
||||
# Пакет services (на случай если PyInstaller его не соберёт автоматически)
|
||||
(f"services{_SEP}services"),
|
||||
# Вспомогательный MIF-парсер, лежащий в корне проекта
|
||||
(f"read_mif_file.py{_SEP}."),
|
||||
]
|
||||
|
||||
# Hidden imports — модули, которые PyInstaller не обнаруживает автоматически
|
||||
HIDDEN_IMPORTS = [
|
||||
"tkinter",
|
||||
"tkinter.ttk",
|
||||
"tkinter.filedialog",
|
||||
"tkinter.messagebox",
|
||||
"openpyxl",
|
||||
"openpyxl.styles",
|
||||
"openpyxl.drawing.image",
|
||||
"openpyxl.drawing.spreadsheet_drawing",
|
||||
"PIL",
|
||||
"PIL.Image",
|
||||
"xml.etree.ElementTree",
|
||||
"zipfile",
|
||||
"pathlib",
|
||||
"re",
|
||||
"io",
|
||||
"copy",
|
||||
"datetime",
|
||||
"threading",
|
||||
]
|
||||
|
||||
|
||||
# ─────────────────────────────── Утилиты ────────────────────────────────── #
|
||||
|
||||
def find_python32() -> str:
|
||||
"""
|
||||
Найти 32-битный интерпретатор Python.
|
||||
Порядок поиска:
|
||||
1. Переменная окружения PYTHON32
|
||||
2. Лаунчер Windows 'py -3.X-32' (проверяем версии 3.12..3.8)
|
||||
3. Попытка запустить текущий интерпретатор (sys.executable) как fallback
|
||||
"""
|
||||
# 1. Явно указано пользователем
|
||||
env_py = os.environ.get("PYTHON32", "")
|
||||
if env_py:
|
||||
p = Path(env_py)
|
||||
if p.is_file():
|
||||
print(f" Используем PYTHON32={env_py}")
|
||||
return str(p)
|
||||
else:
|
||||
print(f" PYTHON32={env_py} не найден, ищем автоматически…")
|
||||
|
||||
# 2. Windows py-лаунчер
|
||||
if sys.platform == "win32":
|
||||
for minor in range(12, 7, -1):
|
||||
tag = f"-3.{minor}-32"
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["py", tag, "--version"],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print(f" Найден 32-bit Python через лаунчер: py {tag}")
|
||||
return f"py {tag}" # специальный маркер — см. _build_args()
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
continue
|
||||
|
||||
# 3. Текущий интерпретатор (может быть 64-bit — предупредим)
|
||||
print(f" Используем текущий интерпретатор: {sys.executable}")
|
||||
_warn_if_64bit(sys.executable)
|
||||
return sys.executable
|
||||
|
||||
|
||||
def _warn_if_64bit(python_exe: str):
|
||||
bits = subprocess.run(
|
||||
[python_exe, "-c",
|
||||
"import struct; print(struct.calcsize('P')*8)"],
|
||||
capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
if bits == "64":
|
||||
print(" ⚠ Внимание: выбранный Python — 64-bit!")
|
||||
print(" Установите 32-bit Python и укажите путь через PYTHON32=…")
|
||||
elif bits == "32":
|
||||
print(" ✓ Python 32-bit подтверждён")
|
||||
|
||||
|
||||
def _build_args(python_interp: str) -> list:
|
||||
"""Собрать аргументы командной строки для вызова PyInstaller."""
|
||||
# Если интерпретатор — это 'py X.Y-32' (Windows лаунчер)
|
||||
if python_interp.startswith("py "):
|
||||
parts = python_interp.split() # ['py', '-3.X-32']
|
||||
prefix = parts # ['py', '-3.X-32', '-m', 'PyInstaller']
|
||||
prefix += ["-m", "PyInstaller"]
|
||||
else:
|
||||
prefix = [python_interp, "-m", "PyInstaller"]
|
||||
|
||||
args = prefix + [
|
||||
MAIN_SCRIPT,
|
||||
"--onefile",
|
||||
"--windowed",
|
||||
f"--name={EXE_NAME}",
|
||||
"--clean",
|
||||
"--noconfirm",
|
||||
f"--distpath={DIST_DIR}",
|
||||
f"--workpath={BUILD_DIR}",
|
||||
"--specpath=.",
|
||||
]
|
||||
|
||||
for data_item in ADD_DATA:
|
||||
args += ["--add-data", data_item]
|
||||
|
||||
for imp in HIDDEN_IMPORTS:
|
||||
args += ["--hidden-import", imp]
|
||||
|
||||
return args
|
||||
|
||||
|
||||
# ─────────────────────────── Основные функции ───────────────────────────── #
|
||||
|
||||
def build():
|
||||
print("=" * 55)
|
||||
print(" Сборка Программы 17 — KadastrParser (32-bit)")
|
||||
print("=" * 55)
|
||||
|
||||
# Проверяем рабочую директорию
|
||||
if not Path(MAIN_SCRIPT).is_file():
|
||||
print(f"Файл {MAIN_SCRIPT} не найден.")
|
||||
print("Запустите скрипт из корневой папки проекта.")
|
||||
return False
|
||||
|
||||
# Ищем 32-bit Python
|
||||
print("\n[1/3] Поиск 32-битного интерпретатора Python…")
|
||||
python = find_python32()
|
||||
|
||||
# Формируем команду
|
||||
print("\n[2/3] Запуск PyInstaller…")
|
||||
cmd = _build_args(python)
|
||||
print(" Команда:", " ".join(cmd))
|
||||
print()
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"\nОшибка PyInstaller (код {e.returncode})")
|
||||
return False
|
||||
except FileNotFoundError as e:
|
||||
print(f"\nФайл не найден: {e}")
|
||||
print("Убедитесь, что PyInstaller установлен для выбранного интерпретатора:")
|
||||
print(f" {python} -m pip install pyinstaller")
|
||||
return False
|
||||
|
||||
# Проверяем результат
|
||||
print("\n[3/3] Проверка результата…")
|
||||
exe_candidates = [
|
||||
Path(DIST_DIR) / f"{EXE_NAME}.exe", # Windows
|
||||
Path(DIST_DIR) / EXE_NAME, # Linux/macOS
|
||||
]
|
||||
exe_path = next((p for p in exe_candidates if p.exists()), None)
|
||||
|
||||
if exe_path is None:
|
||||
print("Исполняемый файл не найден в папке dist_32bit/")
|
||||
return False
|
||||
|
||||
size_mb = exe_path.stat().st_size / (1024 * 1024)
|
||||
print(f" Файл: {exe_path}")
|
||||
print(f" Размер: {size_mb:.1f} MB")
|
||||
_check_architecture(exe_path)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _check_architecture(exe_path: Path):
|
||||
"""Определить разрядность .exe по первым байтам PE-заголовка."""
|
||||
try:
|
||||
with open(exe_path, "rb") as f:
|
||||
dos_header = f.read(64)
|
||||
if dos_header[:2] != b"MZ":
|
||||
return
|
||||
pe_offset = int.from_bytes(dos_header[60:64], "little")
|
||||
f.seek(pe_offset)
|
||||
pe_sig = f.read(4)
|
||||
if pe_sig != b"PE\x00\x00":
|
||||
return
|
||||
machine = int.from_bytes(f.read(2), "little")
|
||||
|
||||
MACHINE = {0x014C: "32-bit (x86)", 0x8664: "64-bit (x86-64)"}
|
||||
arch = MACHINE.get(machine, f"0x{machine:04X}")
|
||||
mark = "✓" if "32" in arch else "⚠"
|
||||
print(f" Архитектура: {arch} {mark}")
|
||||
except Exception:
|
||||
pass # Не критично — просто пропускаем
|
||||
|
||||
|
||||
def create_readme():
|
||||
dist = Path(DIST_DIR)
|
||||
dist.mkdir(exist_ok=True)
|
||||
readme = dist / "README.txt"
|
||||
readme.write_text(
|
||||
"Программа 17 — Заполнение формы по выявленным-1\n"
|
||||
"================================================\n\n"
|
||||
"Системные требования\n"
|
||||
"--------------------\n"
|
||||
" Windows 7 / 8 / 10 / 11 (32-bit или 64-bit)\n"
|
||||
" Минимум 256 MB оперативной памяти\n\n"
|
||||
"Запуск\n"
|
||||
"------\n"
|
||||
" Дважды щёлкните KadastrParser17_32bit.exe\n\n"
|
||||
"Исходные данные\n"
|
||||
"---------------\n"
|
||||
" Таблица 1 — сведения о кадастровом инженере (.xlsx)\n"
|
||||
" Таблица 2 — перечень выявленных (.xlsx)\n"
|
||||
" Таблица 3 — схемы (.xlsx с PNG, опционально)\n"
|
||||
" Папка MIF — MIF/MID файлы с координатами\n"
|
||||
" Шаблон — ШАБЛОН ВЫЯВЛЕННЫЕ-1.xlsx\n\n"
|
||||
"Результаты\n"
|
||||
"----------\n"
|
||||
" output/ — заполненные формы + ВСЕ_ФОРМЫ.xlsx + Таблица4…xlsx\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(f" README создан: {readme}")
|
||||
|
||||
|
||||
# ─────────────────────────────── Точка входа ────────────────────────────── #
|
||||
|
||||
if __name__ == "__main__":
|
||||
ok = build()
|
||||
if ok:
|
||||
create_readme()
|
||||
print("\nСборка завершена успешно!")
|
||||
print(f"Файлы находятся в папке '{DIST_DIR}/'")
|
||||
else:
|
||||
print("\nСборка завершилась с ошибкой.")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user