Недавно потребовалось мне сделать небольшую прогу под 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 x86 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


Успехов в сборке!
Расскажите про свой опыт!

Комментарии (16)


  1. Nikobraz
    05.08.2015 21:18
    +4

    Мне было влом есть кактус.
    Пишу на 2.7 и PyQt4.


    1. el777
      05.08.2015 22:41
      +2

      Тоже вариант. С ним действительно проще собирается и тулзы нормально работают.
      Но как-то больше понравилось как сама прога на Qt5 работает — быстрее запускается GUI.
      А когда я начал собирать под Винду, все уже было написано — переписывать назад не хотелось :)


      1. Nikobraz
        05.08.2015 23:03

        Я как-то пытался PyQt5 собрать, так и не получилось его ни к MinGW, ни к MS VS прикрутить(просил 2010, а под рукой были только 2012 и 2013).
        А Python 3 не использую, потому что пару раз нарывался на то, что нужных библиотек под него нет.
        Я ничего серьезного пока не пишу, но все равно неприятно.

        Если важна скорость попробуйте PyPy
        http://habrahabr.ru/post/87364/


        1. el777
          05.08.2015 23:41
          +1

          > Я как-то пытался PyQt5 собрать…
          Такая же картина, причем в pip есть целых два пакета (PyQt5 и python-qt5), которые либо не ставятся либо не собираются. Поэтому приходится качать готовый с сайта. До сих пор не понимаю, почему на том же самом сайте не захотели выложить SIP, который приходится руками собирать?

          И вот эта магия «возьми SIP собранный в полнолуние с помощью MingW и плясок, добавь к нему PyQt5 с речного берега, потом щепотку нативного pyreadline, тщательно перемешай в ступе автоконфигом и т.п.» немного удручает.

          PyPy надо посмотреть. Спасибо за наводку.
          Пробовали его под виндой и в таких приложениях?


          1. Nikobraz
            06.08.2015 07:19

            Я из-за этих причин, стараюсь вообще не касаться изучения С++ и использования Open Source на нем.
            Есть более интересные способы прожить жизнь, чем неделями собирать софт из исходников.
            Как исключение системы портов и портежей, сами понимаете в каких операционках, они на удивление стабильны.

            Не помню уже, что собирал. Там была ошибка в либах mysql. О баге уже знали, но когда выйдет исправление, черт его знает. Бывают хорошие люди, которые явно указывают какую версию либ использовать, а самые продуманные прикладывают к своему коду.

            ИМХО, исходный код должен быть в таком состоянии, чтобы человек мог его скачать, скачать необходимые библиотеки с дефолтных сайтов и собрать без головных болей и красных глаз. Да чтобы еще запустилось.

            Ситуация напоминает середину 2000-х, когда на коне была Delphi 7. Выпускалась масса компонентов с намеренными ошибками, не зная языка подключить их не представлялось возможным. Только тут ошибки ненамеренные и поиск их не имея боевого опыта программирования не имеет смысла.

            PyPy пока не знаю куда воткнуть. Думаю сайтик поднять в 2 экземплярах и покрутить его одновременно на оригинальном интерпретаторе и PyPy, посмотреть самому на скорость и стабильность. Но не знаю дойдут ли руки.


  1. iroln
    05.08.2015 22:30
    +1

    Мой опыт: приложение на Py3.4 и PyQt4 собирается cx_Freeze и запускается нормально. Правда там был какой-то баг в cx_Freeze, который исправили, но почему-то в официальный релиз на тот момент этот патч не попал. При этом исправленная версия была доступна на всем известном сайте. С PyQt5 собирать standalone-сборку честно не пробовал, надо будет проверить.


    1. el777
      05.08.2015 22:49
      +2

      У меня тоже cx_freeze шел в лидерах — раньше остальных смог собрать прогу, но не рабочую. Видимо, я на этот баг и напоролся.
      Я пробовал самостоятельно компилить патченую версию, но это превратилось в такую мороку.
      Самое смешное, что cx_freeze единственный из тройки не поддерживает сборку в single-executable, т.к. автору не нравятся те хаки, которые для этого применяют py2exe и pyinstaller. Из-за этого и появилась вторая часть моего рецепта — превращение в single exe.


      1. iroln
        05.08.2015 22:55
        +2

        Да, я когда этот вопрос изучал, выделил cx_freeze как самый адекватный проект, он на тот момент умел собирать сложные штуки, типа pyqt/pyside+numpy+scipy+matplotlib и т.п. Это мне и было нужно. Помню, пробовал pyinstaller, переплевался и выкинул в мусорку.

        cx_freeze не умеет собирать single exe, это да, но я в своё время проникся идеей не делать так, хотя бы потому что мой проект зависел от разных dll, и я их периодически обновлял, не пересобирая само приложение, что оказалось очень удобно.


      1. danfe
        06.08.2015 19:12

        Самое смешное, что cx_freeze единственный из тройки не поддерживает сборку в single-executable, т.к. автору не нравятся те хаки, которые для этого применяют py2exe и pyinstaller.
        В свое время нам почему-то не подошел cx_Freeze, очень возможно, что как раз поэтому: требовался единственный экзешник.

        Посмотрел в том проекте сейчас: для деплоймента в GNU/Linux я таки-писал спек-файл для PyInstaller, а для венды/вайна — setup.py для py2exe, хотя он и не понимал .egg-bundles («does not eat eggs for breakfast»), т.е. пришлось писать unpack_egg(), а потом еще самостоятельно сжимать бинарник UPX'ом.

        Не помню точно, почему не унифицировал: в комментарии написано, что Windows is currently not supported, and would require changing «excludes» list below (в обоих случаях я старался ужаться по минимуму, поэтому скрупулезно выкидывал ненужные либы). Возможно, py2exe, несмотря на недостатки, позволял получить файл меньшего размера.


  1. veveve
    06.08.2015 10:41
    +1

    Используйте Nuitka. Сейчас это лучшая из альтернатив (ИМХО, конечно). Я пробовал собирать небольшое приложение на PyQt5, всё прошло просто и без проблем.

    nuitka.net


    1. el777
      06.08.2015 11:04
      +1

      Была такая мысль, тоже его попробовал, но не задалось.
      Может есть хороший мануал или какие нюансы его использования?


      1. veveve
        06.08.2015 17:45

        Так нет никаких нюансов :) просто запускаете из консоли с нужными параметрами и всё. Я своё приложение собирал так:

        nuitka --standalone --recurse-all --recurse-stdlib --windows-disable-console --windows-icon=ICON_FILE --recurse-directory=PROJECT_DIR

        ICON_FILE и PROJECT_DIR надо заменить соответствующими путями.

        Если будете использовать какие-то очень сложные модули, есть вероятность, что их dll'ки придётся вручную в каталог с exe'шником положить, чтобы всё работало. В случае с PyQt5, повторюсь, работает без этого.


        1. el777
          07.08.2015 13:02

          Вы со вторым питоном использовали?
          У меня не взлетело (:

          Error, need to find Python2 executable under C:\Python26 or C:\Python27 to execute scons which is not Python3 compatible.
          


          1. SeoJiHun
            07.08.2015 18:00

            Ну так установите соответствующую версию Nuitka.


            1. kentaskis
              09.08.2015 15:12

              Лично мне помогла установка соотв. версии Python параллельно с тройкой, для которой и собиралось.


            1. el777
              12.08.2015 16:20

              Я ставил через pip:
              python -m pip install nuitka
              Питон в системе только 3, чтобы не мешать либы.