Сложности сборки Python3 + Qt5 приложений под Windows
Недавно потребовалось мне сделать небольшую прогу под Windows. Раньше мне не доводилось разрабатывать под нее.
Сама программа несложная, написалась относительно быстро. Намного больше времени отъела сборка ее под винду. Понятно, что выбранные инструменты (Python3 + Qt5) не родные, а универстальные, но что потребуется столько времени затратить на сборку, я не предполагал.
Соответственно, хочется поделиться практикой, может кому еще придется стучаться лбом в эту стену.
Под катом выстраданная инструкция как легко собирать PyQt5 приложения в single-file.exe не требующий инсталлятора.
Наиболее известные решения для моей задачи — сборки python приложений в .exe это: py2exe, cx_freeze и pyinstaller. Про каждый написано много всего. Но очень часто авторы грешат халтурой — легко и быстро собирается программа и запускается… на том же самом компьютере. Надо ли говорить, что это две большие разницы — запуск на дев-среде и у пользователя? У меня-то и так заработает безо всяких сборок и танцев с бубном. Кроме того — кто из пользователей будет ставить себе Qt?
Для каждого инструмента есть туториал. Но у каждого были свои проблемы: один не запускался (pyinstaller до сих пор поддерживает 3-й питон в экспериментальном режиме), другой собрал то, что не смог запустить, третий вообще долгое время ничего не собирал, ругая все вокруг.
После долгой борьбы был выбран py2exe.
Секрет успеха в правильной установке компонент из правильных источников (я не даю прямые ссылки, т.к. они все равно устареют, пишу что где искать):
- Win XP 32-bit (или другая, которую вы возьмете за базу — я специально взял самую старую систему)
- Python 3.4 — устанавливаем с www.python.ord/download Windows x86 msi installer. Можно сразу добавить C:\Python34\ в %PATH%
- PyWin32 — ставим с sf.net файл pywin32–219.win32.py3.4.exe
- Qt5.5 — ставим с официального сайта qt.io. Одновременно с ним ставится компилятор mingw4.9.2
- После установки добавляем C:\Qt\Tools\mingw492_32\bin в %PATH%.
- Git for Windows — он нам нужен в любом случае, ставим с git-scm.com
- PyQt5 — качаем и ставим riverbankcomputing.co.uk win32×86 installer
- SIP — я так и не понял почему, но тот же самый riverbankcomputing.co.uk не удосужился собрать служебный модулек, поэтому качаем win сорцы, распаковываем, дальше удобно продолжить работу в консоли Git Bash:
- python configure.py -p win32-g++
- mingw32-make.exe
- mingw32-make.exe install.
- Само собой, инсталляция не сработает (. К счастью, нам нужно поставить всего 3 файла. Идем руками в подпапку sipgen/ folder и копируем sip.exe в C:\Python34.
- Затем иде в …/siplib и
- copy sip.pyd c:\python34\Lib\site-packages
- strip /c/Python34/Lib/site-packages/sip.pyd
- наконец, копируем .h-файл:
- cp sip.h /c/Python34/include/
- py2exe — ставим родным способом: python -m pip install py2exe
- pyreadline — так же: python -m pip install pyinstaller
- 7-Zip — c сайта 7-zip.org качаем x86 exe
- 7-zip extra — полезные дополнения качаем оттуда же, распаковываем в папку с установленным 7-zip folder. На конфликты txt-файлов можно забить.
- Resource Hacker — качаем angusj.com — впрочем, если вы не собираетесь заменять дефолтную пиктограмму программы на свою, то она вам не потребуется. Либо ее можно заменить на windres, которая идет в комплекте с mingw —, но у меня уже было сделано с RH.
Делюсь своим setup.py:
from distutils.core import setup
import os, sys
import py2exe
from glob import glob
import PyQt5
NAME="Proga"
qt_platform_plugins = [("platforms", glob(PyQt5.__path__[0] + r'\plugins\platforms\*.*'))]
data_files.extend(qt_platform_plugins)
msvc_dlls = [('.', glob(r'C:\Windows\System32\msvc?100.dll'))]
data_files.extend(msvc_dlls)
# print(data_files)
sys.argv.append('py2exe')
setup(
data_files=data_files,
windows=[
{
"script": "pyftp1.py",
"icon_resources": [(0, "resources/favicon.ico")]
}
],
# zipfile=None,
options={
"py2exe": {
"includes":["sip", "atexit",], # даже если мы нигде не используем atexit его надо добавить, sip указываем явно - сборщики часто про него забывают
# "packages": ['PyQt5'],
"compressed": True,
"dist_dir": "dist/" + NAME,
# "bundle_files": 0, # не сработало ( с этой опцией на выходе прога не может найти msvc*.dll
# "zipfile": None, # тоже лишнее
"optimize": 2,
}
}
)
Это программа-минимум. Теперь можно собирать само приложение:
python setup.py py2exe
На выходе получаем папку с готовой прогой в dist.
Это хорошо, но не всегда удобно для пользователей. На эту папку можно натравить создаватель инсталляторов, типа Inno Setup и получить msi. В моем случае, надо было обеспечить работоспособность программы с минимальными правами пользователей — точно без права установки либ.
Поэтому я пошел дальше и запаковал в 1 файл с помощью 7-zip. Схема такая: создаем 7z-архив и к нему пристыковывается специальная SFX-голова и конфиг. Голова распаковывает архив во временную папку, смотрит в конфиг и запускает нужный exe-файл. Собирается примерно так:
cat 7zS.sfx resources/config.txt Proga.7z > Proga.exe
В файле конфига можно указать разные опции, я задал минимум:
;!@Install@!UTF-8!
Title="Proga"
RunProgram="Proga\\pyftp1.exe"
;!@InstallEnd@!
Единственный недостаток — некрасивая картинка у exe файла — стандартный распаковщик. Тут-то и подменяем ему картинку на нужную нам:
RESHACKER.exe -addoverwrite Proga.exe, Proga.exe, resources/favicon.ico, ICONGROUP, MAINICON, 0
Чтобы не мучаться с этим всем в консоли, я сделал небольшой Makefile:
# start settings
DIST=dist
# change NAME also in setup.py and resource/config.txt
NAME=Proga
EXT=exe
# final name and location of the built program
FINAL=$(DIST)/$(NAME).$(EXT)
# external programs
7ZIPDIR="C:\Program Files\7-Zip"
RESHACKER="/c/Program\ Files/Resource\ Hacker/ResourceHacker.exe"
# intermediate steps
# no icon version of program
NOICON=$(DIST)/$(NAME)_no_icon.$(EXT)
# name of .7z archive
7Z_BASENAME=$(NAME).7z
7Z=$(DIST)/$(7Z_BASENAME)
# folder with ready .exe
PROGDIR=$(DIST)/$(NAME)
all: $(FINAL)
$(FINAL): $(NOICON)
# change icon
"$(RESHACKER)" -addoverwrite $(NOICON), $(FINAL), resources/favicon.ico, ICONGROUP, MAINICON, 0
$(NOICON): $(7Z)
#build autorunning sfx with default icon
cat $(7ZIPDIR)/7zS.sfx resources/config.txt $(7Z) > $(NOICON)
$(7Z): exe
# compress program folder to .7z
cd $(DIST); $(7ZIPDIR)\\\7z.exe a $(7Z_BASENAME) $(NAME)
exe: $(DIST)
# build program itself
python setup.py py2exe
$(DIST):
# create dist directory
# echo $(DIST)/
clean:
rm -rf $(DIST)/*
Теперь можно очень легко пересобирать прогу:
mingw492-make.exe clean all
Успехов в сборке!
Расскажите про свой опыт!