- Packaging PyQt application using pyqtdeploy for both Linux and Android
- Prerequisite
- Linux
- Android
- Android Studio
- Installing Android SDK
- Installing our Android NDK
- Downloading Qt 5.12.2
- Building the pyqt-demo .apk
- pyqtdeploy, или упаковываем Python-программу в exe’шник… the hard way
- Установка pyqtdeploy
- Структура программы
- Обзор плагинов sysroot (документация)
- Собираем sysroot
- Создаем «проектный» файл (документация)
- Собираем exe’шник (документация)
- Лирическое отступление #1 — меняем поведение программы в зависимости от того, «заморожено» оно или нет
- Лирическое отступление #2 — использование ресурсов (изображения, иконки и пр.)
- Итоги
Packaging PyQt application using pyqtdeploy for both Linux and Android
Aug 21, 2019 · 5 min read
This article is a guide on building the pyqt-demo application for both Linux and Android using pyqtdeploy tool.
It should also be possible to build Android applications on Windows and Linux but this isn’t tested. — Pyqt-demo README
Prerequisite
It is assume that you have already installed python 3 and pip for this part.
Pyqtdeploy is on pip so we can easily install it :
Now we need the pyqt-demo code source. You can find it on the pip website. Download the source code and extract the demo folder : https://pypi.org/project/pyqtdeploy/#files
You should have the following structures :
Now if we open sysroot. j son we will find that we need to download some libraries for our app and it might differ from platforms to platform. Like you might need openssl-1.0.2r.tar.gz for android.
In order to speed up the process and not have to manually download everything. We can use this small script to download everything at once.
Notes: Remember this is for Linux and Android. If you are using another platform you might want to verify the sysroot.json file for openssl version.
Linux
We have basically everything needed to start building our demo app for Linux. It doesn’t get more complicated than that.
Using the —verbose option will show the details of the build. It is not required but can help with understanding what is happening. The build will take time because it will build Qt 5.12.2 from source for us.
We notice also in the sysroot.json that we can specify which modules to build and which to skip. It can really help to speed up the build time. You also obviously don’t need to rebuild it every time. Looking a bit more closely in the build-demo.py file we realize that it execute several steps :
- pyqtdeploy-sysroot
- pyqtdeploy-build
- qmake
- make or nmake for windows
Another file we can take a look at is the pyqt-demo.pdy which is used in step 2. This file is actually auto generated by pyqtdeploy .
This tool will generate a .pdy for you and help you include all the files needed for your application. You can find more details in the official documentation. For now, we don’t need to modify this as we already have our project file.
We have reviewed the basic components of a pyqtdeploy project. I recommend you keep exploring the 3 files we mentioned to understand a little bit more the structure.
Android
Building for Android might be a little bit more tricky and requires extra tools and libraries. The official documentation lacks a bit of information on this. This section will try to complete the official tutorial in particular for people who might have never done Android development like me.
Android Studio
First step would be to install Android Studio. We don’t need the full IDE for android development but it will install all the tools we want and offer an easy to use interface to download the libraries needed. Also you will have an emulator installed for testing your .apk package in case you don’t have an android device on which to test your app.
Installing Android SDK
I have a device (tablet Nexus 9) running Android Nougat (7.1.1) so I am going to build for it.
Open Android Studio and click on Configure -> Settings
Now choose Android 7.0 (Nougat).
Click on Apply to install the SDK.
Note : Why didn’t I choose Android 7.1.1 (Nougat)? Unfortunately, I attempt to use it but always got an error message saying that my NDK wasn’t compatible with this version and that for several NDK version…
Installing our Android NDK
Because we are using Qt 5.12.2 we need Android NDK 19c version. For some unknown reason the latest is not compatible.
You can extract it in your Android folder which should have been created in your user space and already has your Sdk folder with the Android SDK with downloaded previously. You can create an
Downloading Qt 5.12.2
When building for Android, we are not going to build Qt 5.12 from source. Instead you will need to download it : https://www.qt.io/download
You can install it in your user folder under (e.g
/Qt/ ). Be sure to remember the path we will need it.
Now be sure you have a folder named android_armv7 . It is the folder name specify in the sysroot.json for Android target. In our case, it should be here
Building the pyqt-demo .apk
We have all the libraries needed to build the Android app.
This little guide summarize the all process to create the PyQt demo application. One of the advantage of using PyQt is to be able to build for multiple platform app and unlike the more famous Electron it doesn’t ship an entire browser with it.
Источник
pyqtdeploy, или упаковываем Python-программу в exe’шник… the hard way
Наверняка, каждый, кто хоть раз писал что-то на Python, задумывался о том, как распространять свою программу (или, пусть даже, простой скрипт) без лишней головной боли: без необходимости устанавливать сам интерпретатор, различные зависимости, кроссплатформенно, чтобы одним файлом-exe’шником (на крайний случай, архивом) и минимально возможного размера.
Для этой цели существует немало инструментов: PyInstaller, cx_Freeze, py2exe, py2app, Nuitka и многие другие… Но что, если вы используете в своей программе PyQt? Несмотря на то, что многие (если не все) из выше перечисленных инструментов умеют упаковывать программы, использующие PyQt, существует другой инструмент от разработчиков самого PyQt под названием pyqtdeploy. К моему несчастью, я не смог найти ни одного вменяемого гайда по симу чуду, ни на русском, ни на английском. На хабре и вовсе, если верить поиску, есть всего одно упоминание, и то — в комментариях (из него я и узнал про эту утилиту). К сожалению, официальная документация написана довольно поверхностно: не указан ряд опций, которые можно использовать во время сборки, для выяснения которых мне пришлось лезть в исходники, не описан ряд тонкостей, с которыми мне пришлось столкнуться.
Данная статья не претендует на всеобъемлющее описание pyqtdeploy и работы с ним, но, в конце концов, всегда приятно иметь все в одном месте, не так ли?
Замечание. В статье исполняемый файл собирается под linux. Несмотря на это, в качестве синонима используется слово «exe’шник» для экономии букв и уменьшения числа повторений.
Все началось с того, что мне захотелось один мой проект запихнуть в исполняемый файл со всеми зависимостями (вы и сами уже догадались). Сначала я решил попробовать провернуть эту операцию с помощью PyInstaller — шикарный инструмент, простой, хорошо документированный. Но на выходе я получил папку размером 170 МБ (для сравнения, весь PyQt5 весил около 180 МБ). Поковырявшись в собранных либах, я понял, что используемые мной модули — QtCore, QtGui, QtWidgets — тащат с собой почти весь пакет. Попытки поиграться с опцией —exclude-module не увенчались успехом. Справедливости ради, если использовать опцию —onefile и включить сжатие, то получится файл размером 60 МБ, что все равно много. К тому же, во время запуска происходит разархивирование программы во временную папку, что увеличивает время старта и все равно (пусть и где-то там) отжирает все те же 170 МБ.
Тут мне подвернулся pyqtdeploy. «Утилита от самих разработчиков PyQt… Ну уж они-то должны знать, как по-максимуму отвязаться от лишних зависимостей внутри PyQt и Qt?» — подумал я и взялся плотненько за сей агрегат.
Так что же такое pyqtdeploy? В первом приближении, то же самое, что и выше перечисленные программы. Все ваши модули (стандартная библиотека, PyQt, все прочие модули) упаковываются средствами Qt (используется утилита rcc) в так называемый файл ресурсов, генерируется обертка вокруг питоновского интерпретатора на C++, позволяющая получать доступ ко все вашим модулям, и потом все это пакуется/компилируется/… в исполняемый файл. Для работы самого pyqtdeploy нужны Python 3.5+ и PyQt5. Перечислим несколько особенностей (за подробностями сюда и сюда):
- может собирать exe’шники на основе PyQt4 и PyQt5, Python 2.7 и Python 3.3+ (максимальная поддерживаемая версия на данный момент Python 3.7.2);
- позволяет статически (все пихаем в exe’шник) и динамически привязывать зависимости (использовать уже установленные в системе библиотеки, пакеты — с рядом ограничений);
- поддерживаемые платформы:
- android-32;
- android-64;
- ios-64;
- linux-64;
- macos-64;
- win-32;
- win-64;
- также позволяет собирать несвязанные с PyQt и Qt программы, но из-за тесной интеграции с QtCore, будет тянуть оттуда кое-что в качестве зависимостей.
Установка pyqtdeploy
Как уже было сказано выше, у нас должен быть установлен Python 3.5+ и PyQt5:
Сборка нашего exe’шника состоит из нескольких этапов:
- Разработка нашей Python-программы, как обычно (сюрприз!);
- Сборка так называемого sysroot для нашей платформы, где будут лежать собранные из исходников нужные зависимости;
- Создание «проектного» файла с расширением .pdy, где будет вся необходимая информация для сборки нашего exe’шника (пути к собранным Qt, PyQt, Python, прочим библиотекам и модулям и другие опции);
- Собственно сборка exe’шника с помощью qmake.
Структура программы
Возьмем в качестве примера проект со следующей структурой: main.py — «точка входа» для нашей программы, она вызывает mainwindow.py — допустим, отрисовывает окошечко с виджетами и берет из resources иконку icon.png и mainwindow.ui, сгенерированный нами с помощью Qt Designer. Имеющиеся зависимости, версии библиотек и прочие необходимые вещи будут всплывать по ходу повествования:
Обзор плагинов sysroot (документация)
Как уже было сказано ранее, на этом этапе мы собираем все необходимые части, которые затем будут использоваться при генерации исполняемого файла. Данный процесс осуществляется с использованием конфигурационного файла sysroot.json (в принципе, вы можете назвать его как хотите и указать затем путь к нему). Он состоит из блоков, каждый из которых описывает сборку отдельного компонента (Python, Qt и т.д.). В pyqtdeploy реализован API, позволяющий вам написать свой плагин, управляющий сборкой необходимой вам библиотеки/модуля/whatever, если он еще не реализован разработчиками pyqtdeploy. Давайте пробежимся по стандартным плагинам и их параметрам (примеры из документации):
openssl (не обязательный) — позволяет собирать из исходников или использовать установленную в системе библиотеку (подробности). Компонент, описывающий данный плагин в sysroot.json, выглядит следующим образом:
Первое, на что следует обратить внимание, это синтаксис: arch1|arch2|. #plugin-name . То есть мы можем выбрать, на какой платформе использовать этот плагин (ios, android, macos, win, linux), а на какой — нет. Более того, этот синтаксис применим и к параметрам внутри блока.
- source (обязательный) — имя архива с исходниками;
- no_asm (не обязательный) — выключаем ассемблерные оптимизации. Если включен, в PATH должен быть установлен nasm;
- python_source (не обязательный) — имя архива, содержащего патчи, необходимые для сборки OpenSSL под macOS для Python v3.6.4 и более ранних версий;
zlib (не обязательный) — используется при сборке других компонентов (если не указан, по идее, будет использоваться тот, что установлен в системе) (подробности):
- source (обязательный) — очевидно, имя архива с исходниками;
- static_msvc_runtime (не обязательный) — статически привязать MSVC библиотеки (Windows);
qt5 (обязательный) — тут понятно (подробности):
- qt_dir (не обязательный, если указан source) — путь к папке с установленным Qt;
- source (не обязательный, если указан qt_dir) — имя архива с исходниками Qt;
- edition (обязательный, если указан source) — один из 2 вариантов:
- commercial;
- opensource;
- ssl — 3 возможных варианта:
- openssl-linked — будет собран из исходников (подробности должны быть указаны в описании компонента openssl);
- securetransport — используется SSL, реализованный в Qt (который, в свою очередь, будет использовать Apple’s Secure Transport);
- openssl-runtime — используется версия OpenSSL, установленная в системе;
- configure_options — дополнительные опции, используемые при сборке Qt. Существует их целая прорва, смотрим тут;
- skip — позволяет исключить из сборки ненужные модули (точнее говоря, top-level директории, содержащие модули). Открываем архив с исходниками Qt и видим папки, начинающиеся с qt — это и есть top-level директории. Имейте в виду, что эти папки могут содержать и те модули, что вам нужны. К сожалению, можно скипнуть только top-level директорию целиком (подробности);
- disabled_features — позволяет исключить выбранный функционал. Для просмотра всех возможных фич можно воспользоваться командой configure -list-features (подробности)
- static_msvc_runtime (не обязательный) — статически привязать MSVC библиотеки (Windows);
python (обязательный) — тут тоже понятно (подробности):
- build_host_from_source (обязательный) — true — собираем Python для хоста из исходников, false — используем установленный Python (не поддерживается для win32);
- build_target_from_source (обязательный) — true — собираем Python для целевой платформы из исходников, false — используем установленный Python (использование установленного Python поддерживается только на win32);
- source (обязательный, если Python собирается из исходников) — имя архива с исходниками Python;
- version (обязательный, если используется установленный Python) — версия установленного Python;
- dynamic_loading (не обязательный) — true — включить поддержку динамической загрузки модулей расширения (тех, что на C);
- host_installation_bin_dir (не обязательный) — путь к установленному Python, если не собирается из исходников (если не указан, на win ищется в реестре автоматически, на других платформах — в PATH);
sip (обязательный) — компонент, отвечающий за автоматическое генерирование Python-bindings для C/C++ библиотек (подробности тут и тут):
- module_name (обязательный) — имя sip-модуля;
- source (обязательный) — имя архива с исходниками sip;
pyqt5 (обязательный) — тут тоже понятно (подробности):
- disabled_features (не обязательный) — позволяет выключить конкретный функционал. Если не указан, выключаемые фичи определяются автоматически на основе фич, выключенных в собранном нами Qt (подробности);
- modules (обязательный) — перечисляем модули, которые мы хотим собрать (подробности);
- source (обязательный) — имя архива с исходниками PyQt;
pyqt3D, pyqtchart, pyqtdatavisualization, pyqtpurchasing, qscintilla (не обязательные) — дополнительные модули, не входящие в состав PyQt. Имеют единственный параметр source — имя архива с исходниками.
Стоит заметить, что некоторые значения параметров могут не работать друг с другом. В таких случаях вы получите ошибку при сборке sysroot с информацией, что не так. Я постарался здесь описать такие случаи, по крайней мере, для обязательных компонентов.
Собираем sysroot
Давайте взглянем на итоговый sysroot.json для нашей программы:
Что интересного мы тут видим? Во-первых, не используется ряд компонентов(например, ssl, pyqt3D и прочие). Во-вторых, собирать наш exe’шник мы будет под linux (а точнее, linux-64; в нашем случае, можно не указывать перед каждым компонентом платформу).
Далее, в qt5 по-максимуму выключены модули и функции, которые не будут использоваться (те, о назначении которых у меня было хотя бы минимальное представление). Среди top-level директорий собирается только QtBase. Особо упомяну опции -optimize-size и -ccache . Первая позволяет уменьшить размер собранного Qt и, соответственно, итогового файла (у меня получилось минус 5 МБ), но увеличится время компиляции, вторая — использовать ccache (по крайней мере, на linux), что при повторных компиляциях СУЩЕСТВЕННО уменьшает время (у меня уменьшилось раз в 5). Никакой настройки не требует, просто ставим командой apt install ccache .
В pyqt5 собираем только модули QtCore, QtGui, QtWidgets.
В python включен dynamic_loading , так как мы хотим позднее динамически прилинковать C-extension.
Прежде чем приступить к сборке sysroot, не забываем скачать все необходимые исходники: zlib, Qt5, Python, sip, PyQt5 и кладем их в папочку с sysroot.json (можно и любую другую, указав потом путь к ней). Запускаем сборку:
Данная команда имеет еще несколько опций, которые можно посмотреть здесь.
Крайне рекомендую также использовать опцию —verbose . Будьте готовы к тому, что вы получите целую кучу ошибок, прежде чем все удачно соберется. Многие из них будут связаны с тем, что у вас не установлены dev-пакеты. Я их здесь не перечисляю, ибо они зависят от вашей конфигурации и платформы. Наверняка, вам нужен будет python3-dev, также смотрим тут (особенно, разделы Requirements). Правда, вам никто не запрещает использовать для тех же Qt и Python уже установленные версии (я не пробовал, возможны свои подводные камни).
Ну и запаситесь попкорном, ибо, в зависимости от мощности вашего калькулятора компьютера, это может занять немалое время.
Создаем «проектный» файл (документация)
Как только у нас все удачно собралось, приступаем к выбору модулей, которые мы хотим запаковать в exe’шник. Для этого в pyqtdeploy есть удобная утилита с GUI. Запускаем (имя .pdy файла может быть любым):
Application Source. В первой вкладке мы видим следующие настройки:
- Name — имя вашего будущего exe’шника;
- Main script file (не указывается, если используется Entry Point) — скрипт, используемый для запуска программы (в нашем случае, main.py);
- Entry Point (не указывается, если используется Main script file) — точка входа для программы, основанной на setuptools;
- sys.path — используется для указания дополнительных директорий, zip-файлов и яиц (тех, что Python egg), которые будут добавлены в sys.path (я не использовал, смотрим доки, там подробно описана эта опция);
- Target Python version — версия Python;
- Target PyQt version — PyQt4 или PyQt5 (игнорируется, если вы мазохист и решили собрать программу, не использующую PyQt, этим монстром);
- Use console — выбрать, если приложение должно использовать консоль (только Windows). Может быть полезно для дебага;
- Application bundle — выбрать, если приложение должно быть собрано как bundle (только MacOS);
- Application Package Directory — содержит все файлы, составляющие вашу программу. Для добавления жмем кнопку Scan… У нас папка со всеми «кишками» (src) отделена от main.py, так что выбираем эту папку и галочками выделяем все файлы, которые мы хотим включить в итоговый файл. Если же у вас нет такого разделения (т.е. main.py находится внутри src), то напротив main.py галочку нужно снять (или напротив вашего аналога, указанного в Main script file).
Еще один момент: любой файл с расширением .py будет «заморожен» (будет сгенерирован байт-код) — в ряде случаев это может быть нежелательным.
- Scan… — добавляем файлы в Application Package Directory;
- Remove all — очищаем Application Package Directory;
- Include all — выделяем все файлы в Application Package Directory;
- Exclude all — снимаем выделение со всех файлов в Application Package Directory;
- Exclusions — паттерны, позволяющие исключить файлы из Application Package Directory. Дважды кликаем на пустой строке для добавления;
qmake. Так как в сборке участвует qmake, здесь можно добавить дополнительные параметры для него (я не использовал);
PyQt Modules. На этой вкладке выделяем все PyQt-модули, которые мы явно импортируем в нашей программе. Если они зависят от других модулей, те выделятся автоматически. В нашем случае использовались QtCore, QtGui, QtWidgets, uic; sip подхватился автоматом.
Если планируется использовать уже установленный PyQt, а не привязывать статически его к нашему исполняемому файлу, ничего не выделяем (такой сценарий не тестировался).
Standard Library. Здесь тот же подход, что и в предыдущем пункте, только для стандартной библиотеки. Если у вас в программе явно импортируется какой-то модуль, ставим галку. Если выделенным нами модулям (или самому интерпретатору) нужны другие модули, они выделятся автоматом (квадратики).
Правда это не всегда работает. Если поставили какой-то пакет со стороны (через тот же pip), и он импортирует что-то из стандартной библиотеки (еще не выделенное), вы получите при запуске ImportError . Так что вам придется вернуться сюда и поставить галочку. Например, я использую библиотеку PIL, и одному из модулей нужны была библиотека fractions.
Python использует ряд модулей/пакетов (например, ssl), которым для работы нужны внешние библиотеки. Если мы хотим их статически привязать, то мы настраиваем это дело справа. В INCLUDEPATH указываем путь к заголовочным файлам (headers), в LIBS — путь к этой либе (мной не использовались, так что подробности смотрим в доках).
Other Packages. На этой вкладке выбираем необходимые нам сторонние пакеты (например, установленные из pypi). Подход тот же, что и в Application source: кликаем дважды на пустой строке, выбираем папку (в нашем случае, site-packages используемого при разработке virtual environment), жмем Scan и выбираем нужные пакеты/модули (у нас это PIL).
Other Extension Modules. Тут мы настраиваем модули расширения на C, которые хотим СТАТИЧЕСКИ привязать к exe’шнику (сторонние; те, что в стандартной библиотеке, привязываются сами).
Мы может настроить как компиляцию с нуля этих самых расширений, так и привязку уже скомпилированных. Второе делается довольно просто. Допустим у нас есть пакет Package со статической либой Lib.a, то в поле Name указываем полное имя расширения, используемое во время импорта — Package.Lib (без расширения .a); затем в поле LIBS указываем путь к этому расширению, например, -L/home/user1/venv/programme1/lib/python3.7/site-packages/Package -lLib (это специальный формат, также можно указать путь «по старинке», /home/user1/venv/programme1/lib/python3.7/site-packages/Package/Lib.a).
С компиляцией я не разбирался, но советую почитать, во-первых, про эту вкладку в доках, во-вторых, про qmake (там гораздо подробнее описаны опции, чем в pyqt’шных доках).
А что, если у нас динамическая либа, например, Lib.so? Еще проще — переименовываем ее в Package.Lib.so (т.е. все то же полное имя расширения, используемое во время импорта + расширение) и кладем его рядом с нашим exe’шником. Все должно подхватится, если это простое расширение без всяких зависимостей. В противном случае, ждите опять кучу ImportError . Мне, например, так и не удалось прикрутить _imaging.so, используемый PIL’ом.
Locations. Тут тоже подробно не останавливаемся, за описанием отдельных путей сюда. Если вы действовали в соответствии с этой статьей (собранный sysroot лежит тут же, рядом с main.pdy), тут менять ничего не надо.
Собираем exe’шник (документация)
Наконец-таки собираем наш исполняемый файл:
Гипотетически, все должно собраться, на деле — доки и гугл вам в помощь.
Лирическое отступление #1 — меняем поведение программы в зависимости от того, «заморожено» оно или нет
Если вам нужно определить, запущена ваша программа как есть или из собранного exe’шника, используется тот же подход, что и в PyInstaller:
Лирическое отступление #2 — использование ресурсов (изображения, иконки и пр.)
У Qt имеется специальная «система ресурсов», которая позволяет с помощью утилиты rcc упаковать любые бинарные файлы в exe’шник. Далее с помощью пути специального формата вы можете получить доступ к необходимому ресурсу. В нашем проекте файл с иконкой icon.png расположен в src/resources/images, тогда путь в «системе ресурсов» будет выглядеть так — :/src/resources/images/icon.png. Как видите, ничего хитрого. Однако с таким путем есть одна засада — его понимают только Qt’шные функции. Т.е. если вы напишите у себя в программе что-нибудь в духе:
Все будет в порядке. Но если, например, так:
Ничего не выйдет, ибо open будет пытаться найти такой путь в вашей файловой системе и, естественно, ничего не найдет.
Если вам нужно читать запакованные ресурсы не только средствами Qt (например, вы, как и я, создавали GUI с помощью Qt Designer и получили файл .ui, который потом надо прочитать с помощью loadUi ), нужно будет сделать как-то так:
Итоги
Стоит ли так сильно заморачиваться, если вам нужен exe’шник, и старые добрые дедовские способы распространения программы вам по каким-то причинам не подходят? Если вы не используете PyQt, то, на мой взгляд, точно не стоит. Используйте что-нибудь более дружелюбное (тот же PyInstaller). Если хотите выжать максимум соков из вашего файла — дерзайте. В конечном счете мне таки удалось уменьшить размер файла до
40 МБ (c -optimize-size
35 МБ), что все-равно больше, чем хотелось бы.
Когда у нас собрана минимально необходимая Qt и PyQt, было бы неплохо попробовать сделать на их основе exe’шник с помощью PyInstaller или cx_Freeze и посмотреть на размер, но это, как говорится, уже другая история.
Источник