Согласитесь что заманчиво звучит представить ваши программы, использующие тулкит Qt, в Ubuntu Store для миллионов людей? Мне часто задают вопрос про упаковку Qt программ в snap пакет и решил показать вам на примере своего Hello World все этапы.
Вначале давайте рассмотрим простой вариант, когда программа уже скомпилирована и вам нужно "всего лишь" упаковать её в snap пакет. Поискал среди своих попыток поучиться азам программирования с помощью QtCreator и нашёл графический аналог Hello World в папке qtprogram1/. Дерево внутри было такое
. ├── qt1 │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindow.ui │ ├── qt1.pro │ └── qt1.pro.user └── qt1-build-desktop ├── Makefile ├── moc_mainwindow.cpp ├── qt1 └── ui_mainwindow.h 2 directories, 10 files
Готовый бинарник доступен по qtprogram1/qt1-build-desktop/qt1. Что нам нужно для создания snap пакета данной программы? По большому счёту лишь установленный snapcraft (sudo apt install snapcraft
) и пустая папка под ваш проект. Но, как не раз сообщал вам в предыдущих статьях, настоятельно рекомендую не загаживать свою основную систему и настроить "сборочный цех" в виртуальных средах или в контейнерах. Сборочный цех настоятельно рекомендую натравить на Apt-cacher-ng, который сэкономит вам трафик, время и нервы.
Итак, у нас есть snapcraft и пустая папка, в которую копирую своё поделие в лице qtprogram1/, и создаю файл snapcraft.yaml.
name: qt1 version: "1" summary: My first Qt app description: Qt Hello World confinement: strict architectures: [amd64] apps: qt1: command: desktop-launch $SNAP/opt/myapp/qt1 plugs: [home, unity7, x11, wayland, desktop, desktop-legacy, gsettings] parts: project-files: plugin: dump source: qtprogram1/ organize: qt1-build-desktop/qt1 : opt/myapp/qt1 after: [integration] integration: plugin: nil stage-packages: - libstdc++6 - libc-bin after: [desktop-qt5]
Подробнее о полях. Кратенько, имя = qt1, версия = 1, режим ограничения = жёсткий (strict). Бинарник скомпилирован под 64 битные системы и работать будет только в них, поэтому architectures: [amd64]. Теперь буду объяснять, читая файл snapcraft.yaml снизу вверх.
Официальное руководство о синтаксисе The snapcraft syntax.
В разделе кусочков (parts:) добавляю 2 кусочка под придуманными мной именами - project-files и integration. Кусочек integration будет помогать мне с добавлением в будущий пакет snap программы всего того что нужно ей для работы. Для этих целей для кусочка integration указываем плагин nil и через stage-packages добавляю пару имён пакетов из официального репозитория, чьё содержимое (пакетов deb) будет запаковано вместе с программой. Так как формировать stage-packages утомительно, то в помощь нам, юным снапкрафтерам, разработчики представили эталонные кусочки для популярных фреймворков/тулкитов, которые называются облачными (cloud parts). Более подробно о cloud parts и доступных помощниках.
Традиционно, в stage-packages добавляют всё то, что в мире deb называется зависимостями программы и указывается в Depends. Магической фразой after: [desktop-qt5], прошу snapcraft помочь мне с формированием нужного программе и динамически пополнить stage-packages такими именами пакетов, чьё содержимое сделают qt5-программу работоспособной внутри её snap пакета. В дополнение мне будет доступен эталонный shell-скрипт desktop-launch от разработчиков, в котором определены все нужные переменные окружения. Изучить содержимое desktop-launch мы сможем чуть позже, когда запустим первый цикл итерации, нам скачают свежею версию скрипта и разместят по адресу prime/bin/desktop-launch.
Кусочек project-files использует плагин dump для размещения программы там где мне нужно в будущем snap пакете. Плагину dump устанавливаю исходной папкой для его работы каталог source: qtprogram1/. Прошу разместить qt1-build-desktop/qt1 по адресу opt/myapp/qt1. Обратите внимание, что лидирующего слеша у opt нет, так как opt/ будет внутри снап пакета и в системах пользователей фактически будет находиться по абсолютному адресу /snap/qt1/1/opt/. Где размещать программу решать вам! Данный hello world, состоящий из одного бинарника, лучше бы разместить в usr/bin/, так как данный путь desktop-launch пропишет в различных переменных окружениях типа PATH. Но в вашем случае с бинарником могут идти сопровождающие файлы и не очень красиво будет какать в usr/bin/ всем чем попало, хоть и находящийся внутри snap пакета. Стыдно признаться, но я так поступил с LanguageTool, нарушив стандарты иерархии файловой системы (Filesystem Hierarchy Standard - FHS) =(. Для красоты прошу на всех стадиях работы snapcraft (Pull -> Build -> Stage -> Prime -> Snap) действия над кусочком project-files выполнять после кусочка integration (after: [integration]).
Официальное руководство о плагинах Snapcraft plugins reference.
Официальное руководство о переменных окружения, доступных дополнительно внутри snap пакета, Environment variables.
В разделе apps: указываю имя моего бинарника как qt1 и что фактически прошу вызывать мою программу через вызов shell-скрипта-обёртки desktop-launch $SNAP/opt/myapp/qt1. Для простой программы прошу подключить при установке в систему к следующим её интерфейсам - plugs: [home, unity7, x11]. Программа в snap темнице будет иметь только те окошки для связи со внешним миром, которые вы укажите в plugs. Если вам нужен доступ к звуку, сети и так далее, то читайте официальное руководство об интерфейсах Interfaces reference и указывайте требуемое через запятую.
Всё готово для создания пакета snap для вашей программы. Командуйте в вашем каталоге, где лежит snapcraft.yaml, snapcraft
и он, проходя этапы Pull -> Build -> Stage -> Prime -> Snap, создаст вам пакет. В моём случае родился qt1_1_amd64.snap
Как посмотреть что вам в итоге замуровали с программой? Обратите внимание на подкаталог prime/, который остался от работы предпоследнего этапа Prime. Просмотрите и проанализируйте скрипт prime/bin/desktop-launch, который будет формировать нужное окружение и запускать вашу программу. Проконтролируйте что ваша программа расположена там где вы просили. В моём случае это prime/opt/myapp/qt1.
Так же можно посмотреть содержимое пакета через команду unsquashfs -l *.snap > report.txt
.
Пробуем установить пакет!? sudo snap install --dangerous qt1_1_amd64.snap
Здесь --dangerous, так как пакет никем не подписан, авторство ваше не известно. Если пакет залить в Ubuntu Store и оно пройдёт автоматическое одобрение, то оно будет подписано вашей учётной записью Ubuntu SSO.
Ваша программа стартовала? Моя да!
Что если программа не стартует? Универсального решения проблемы нет. Узнайте через ldd (ldd qt1
) обо всех используемых библиотеках вашим бинарником и ищите (not found). Все имена библиотек, которые мелькнули в выводе ldd с not found, скормите команде dpkg -S имя
и выясните в каких пакетах они прячутся. Данные имена пакетов нужно занести в stage-packages и повторить итерацию упаковки - snapcraft
и снова установить пакет snap заново. Этим автоматически занимается snapcraft, но в исключительных случаях он может не справиться.
Если в программе вы используете сторонние файлы (текст, медиа), то "подключать-подгружать" их нужно, НЕ используя абсолютные пути. Другими словами, ДО упаковки программы в snap, пробуйте копировать каталог с программой и всем её скарбом в различные папки своей системы и добейтесь работоспособности.
Ваша программа не должна что-либо записывать в тот каталог, из которого она стартует. Snap пакет вашей программы, не распаковывая содержимое, будет примонтирован в каталог /snap/имя-программы/ с доступом только на чтение и это место не будет доступно для записи. Для проверки этого факта ДО каких-либо итераций упаковки легче и проще разместить программу в /opt/ вашей системы и сделать владельцем папок и файлов root'а.
sudo chown -R root:root /opt/myapp/
sudo find /opt/myapp/ -type d -exec chmod 0775 {}\;
sudo find /opt/myapp/ -type f -exec chmod 0664 {}\;
Мы сымитировали через /opt/myapp/ с владельцем root будущий смонтированный snap пакет и теперь программа может запускаться от вашей учётной записи, но записывать в каталоги и файлы, принадлежащие root она не сможет. Работает программа? Если нет, то прервитесь с упаковкой и сконцентрируйтесь на работоспособности программы в таких условиях.
Для записи чего-либо для программы существуют пути, доступные через переменные SNAP_USER_DATA и SNAP_USER_COMMON.
Давайте попробуем изменить snapcraft.yaml и заставим snapcraft собрать проект и упаковать его. Беглый осмотр вывел на файл qtprogram1/qt1-build-desktop/Makefile, в котором мелькает qmake. Плагин с таким же именем есть и у snapcraft'а.
Пробуем!?
name: qt1 version: "1" summary: My first Qt app description: Qt Hello World confinement: strict architectures: [amd64] apps: qt1: command: desktop-launch $SNAP/opt/myapp/qt1 plugs: [home, unity7, x11] parts: project-files: plugin: qmake source: . qt-version: qt5 project-files: [qtprogram1/qt1/qt1.pro] after: [integration] integration: plugin: nil stage-packages: - libstdc++6 - libc-bin after: [desktop-qt5]
Кусочек project-files теперь использует плагин qmake и специфичные опции, которые объясняют где взять файла проекта и что нужно выбрать qt5. Но из тёплых стран ко мне прилетела птица Обломинго. Подтвердилось, что во мне умер программист, так и не родившись . Свой HelloWorld писал во времена Qt4, а сейчас прошу собрать в Qt5. Посыпались ошибки, которые привели с помощью гуглежа на сайты, где профи посоветовали в файле qtprogram1/qt1/qt1.pro добавить widgets и привести строку к виду
QT += core gui widgets
. QtGui теперь в QtWidgets и поэтому в qtprogram1/qt1/main.cpp подключение должно быть таким #include <QtWidgets/QApplication>.
Проект стал собираться с помощью snapcraft и плагина к qmake, но итогового бинарного qt1 в нужном мне месте не наблюдалось. Команда find . -name "qt1" -type f
вывела лишь ./parts/project-files/build/qt1
Так же во время сборки пролетали строки, которые намекали, что итоговой сборки бинарника не будет.
Building project-files .... make install INSTALL_ROOT=/sp/qt1/parts/project-files/install make: Nothing to be done for 'install'.
Обратился за помощью в почтовой рассылке к разработчикам snapcraft и те объяснили, что в идеале мне нужно указать сделать требуемое в qt1.pro, но если бы я знал как это сделать . И тут же дали совет, что snapcraft умеет изменять поведение плагинов через Scriptlets и дали готовое решение для меня. Кусочек project-files нужно модифицировать так:
parts: project-files: plugin: qmake source: . qt-version: qt5 project-files: [qtprogram1/qt1/qt1.pro] install: | install -d $SNAPCRAFT_PART_INSTALL/opt/myapp install qt1 $SNAPCRAFT_PART_INSTALL/opt/myapp/qt1 after: [integration]
Чудеса просто! Snapcraft если умеет такое, то слов просто нет. В вашем случае может использоваться другая схема сборки программы, но основную суть вы должны уловить. Snapcraft через различные свои плагины умеет работать со всеми известными системами сборки и поможет с компиляцией вашего проекта из любых источников - от локальной папки до удалённого git.
На этом всё! Буду вам безмерно благодарен, если поделитесь статьёй со своими друзьями программистами, которые теперь легко и просто могут представить свои программы для миллионов пользователей.
Дата последней правки: 2019-11-16 08:42:57