cx_Freeze + virtualenv = баги и зигзаги

в 7:52, , рубрики: cx_freeze, python, python3, virtualenv

На днях решил попробовать собрать своего свежеиспечённого бота для Телеграм в исполняемый файл. Существуют различные решения: py2exe, pyInstaller, cx_Freeze. Я использую Ubuntu 14.04 и на моём компьютере, и на сервере, поэтому выбор пал на cx_Freeze, ввиду его кросс-платформенности и поддержки Python 3. (На момент написания статьи я ещё не успел распробовать pyInstaller, который обладает этими же свойствами. Если будет что-то интересное, расскажу об этом в другой статье).

Установка cx_Freeze

Изначально казалось, что всё банально:

sudo apt install cx-freeze

Не подходит, потому что устанавливает cx_Freeze для Python2.

Попробуем через pip3:

sudo pip3 install cx_Freeze

Однако, как бы не так. На Убунте (другие ОС пока не пробовал) это вызывает следующую ошибку (прошу прощения за картинки. На момент написания статьи сделать текстовый копипаст оказалось затруднительно по причинам из серии «это долгая история»):

image

Порывшись по StackExchange, я понял, что без установки из исходников дело не обойдётся.

Скачал. Распаковал. Запустил:

sudo python3 setup.py install

История аналогичная:

image

Снова порылся по StackOverflow. Пришлось залезть в setup.py и заменить там строчку:

if not vars.get("Py_ENABLE_SHARED", 0):

на

if True:

Попробовал запустить снова — и установилось! Ура!

На заметку

Во время написания статьи я повторял процесс установки на «свежей» виртуальной машине с Ubuntu 14.04 32bit. Натолкнулся на ошибку:

/usr/bin/ld: cannot find -lz

Согласно этому, она решается (на 32-битных системах) с помощью:

sudo apt-get install lib32z1-dev

И ещё, на всякий случай (поскольку в этом вопросе это было упомянуто), я установил python-dev:

sudo apt-get install python-dev

Собираем скрипт!

Рассмотрим простейший способ сборки скрипта с помощью cx_Freeze. Переходим в папку со скриптами и запускаем:

cxfreeze main.py --target-dir build

где main.py — имя основного (запускаемого) скрипта, а build — папка, куда пойдут исполняемый файл и библиотеки.

Следует учитывать, что в отличие от, скажем, компиляторов C++, cx_Freeze не отлавливает ошибки. Поэтому он может прекрасно всё собрать, но при запуске исполняемого файла посыпятся исключения, причём в довольно неразборчивом формате. Советую потестить код, пока он ещё питонический.

Думаю, что оптимизация кода для сборки в исполняемый файл — тема для отдельного обсуждения, ибо при сборке могут вылезти проблемы, которых при запуске скрипта через интерпретатор Python просто не было. Здесь расскажу об одной (слава богу, одной! больше просто не возникло) такой проблеме.

Мой скрипт обращается к внешним файлам с настройками. Но при запуске из исполняемого файла он почему-то стал воспринимать имя скрипта, как одну из папок на пути к файлу настроек. То есть, если собранный исполняемый файл под названием main находится в /tmp/001, а файл настроек — в /tmp/001/sett, то скрипт думал, что файл настроек на самом деле располагается в /tmp/001/main/sett. Что, естественно, бред. А если же запустить питоновый скрипт — этой проблемы нет.

Дело в том что я в начале своих программ зачастую впихиваю следующую строчку:

SCRIPT_FOLDER = path.dirname(path.realpath(__file__))

Эту переменную я потом использую при задании путей к файлам, которые всегда находятся в одном и том же месте относительно скрипта: файлы настроек, сохранения, токены, коды приложений, и т. д. Таким образом я гарантирую, что программа считает файл из правильного места вне зависимости от того, откуда она была запущена (например, из другой папки или через демона).

Собранные через cx_Freeze скрипты обычно либо неправильно интерпретируют глобальную переменную __file__, либо вообще ее не знают. Последнее как раз произошло, когда я попробовал собрать и запустить исполняемый файл из простенького тестового скрипта:

from os import path

SCRIPT_FOLDER = path.dirname(path.realpath(__file__))

print(SCRIPT_FOLDER)

Проблема оказалась известной. Достаточно было слегка пропатчить мою программу:

if getattr(sys, 'frozen', False):
	# frozen
	SCRIPT_FOLDER = path.dirname(sys.executable)
else:
	SCRIPT_FOLDER = path.dirname(path.realpath(__file__))

В результате, всё завелось с полпинка! Отлично. Простая часть завершена! Переходим к повторению всего этого в виртуальной среде!

Ох уж эти виртуальные среды!

Если вы не пользуетесь virtualenv, полагаю, вас не очень заинтересует дальнейшее чтение этой статьи. Особенность заключается не только в установке cx_Freeze в среду, но и в запуске его таким образом, чтобы он пользовался именно зависимостями из среды, а не системными.

На заметку

Пользоваться средой в шелле (запускается через «source env/bin/activate») нам не придётся. Я пробовал, эффекта это не дало.

Установка

У меня виртуальная среда находится в папке env в основном каталоге моего проекта. Для установки cx_Freeze в среду переходим в папку с исходниками cx_Freeze (с обработанным по указанной выше методике файлом setup.py) и запускаем:

/path/to/my/project/env/bin/python3 setup.py install

Не очень удобно. Пришлось прописывать путь к интерпретатору Питона в нашей среде. Но всё установилось.

Сборка

Однострочный метод, описанный выше, здесь не пройдёт. Нужно использовать другой — через установочный файл.

Файл этот, в краткой форме, которая мне была вполне достаточна, выглядит следующим образом:

from cx_Freeze import setup, Executable

PROGRAM_NAME = "Train delays bot"
VERSION = "0.0.5"
MAIN_SCRIPT_NAME = "train_delays_bot_main.py"

setup(  name = PROGRAM_NAME,
        version = VERSION,               
        executables = [Executable(MAIN_SCRIPT_NAME)])

Самое главное здесь, это задать MAIN_SCRIPT_NAME, которая соответствует имени главного файла нашей программы.

Помещаем этот скрипт в папку проекта и переходим туда. Запускаем:

env/bin/python3 setup.py install

Автоматически создалась папка build, а в ней другая — exe.linux-x86_64-3.4 (в вашем случае она может называться по-другому, в зависимости от ОС и версии интерпретатора). В ней располагаются исполняемый файл и библиотеки. Я перенёс туда папки с файлами токенов и настроек. Всё запустилось. И версии зависимостей оказались как раз из виртуальной среды. Что не может не радовать, особенно после двух часов траходрома упорного труда.

В заключение

И вместо дисклеймера. Ни в коем случае не претендую на абсолютную правильность и максимальное удобство вышеизложенных методов. Если у вас есть более эффективные методы, расскажите о них в комментариях. Думаю, всем, кто осилил мою писанину, будет это интересно.

Автор: Highstaker

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js