Приложение на Qt6#

Пример показывает два пути для одной и той же программы на Qt6: сборку из исходников и упаковку готовой бинарной поставки из архива. Программа пишется здесь же, поэтому оба пути повторяются целиком, без загрузок со стороны.

Qt6-приложения работают на среде org.astra.Qt6. Она несёт Qt6 целиком: libqt6core6t64, libqt6gui6, libqt6widgets6, libqt6network6, QML и мультимедиа. Qt6 существует только для линии 1.8.6, поэтому версия среды и SDK здесь одна.

Подготовка#

sudo flatpak install astra-main org.astra.Qt6//1.8.6
sudo flatpak install astra-extended org.astra.Qt6Sdk//1.8.6

Дерево примера#

qt6-hello/
|-- qt6-hello.yaml
`-- src/
    |-- CMakeLists.txt
    |-- main.cpp
    |-- ru.example.Qt6Hello.desktop
    |-- ru.example.Qt6Hello.metainfo.xml
    `-- icons/
        `-- 128x128/
            `-- app.png

В каталог icons/128x128 кладётся любой PNG размером 128 на 128. Имя файла роли не играет: при сборке иконки переименовываются в идентификатор приложения.

Исходный код#

src/main.cpp:

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QLabel label("Qt6 внутри Flatpak");
    label.setAlignment(Qt::AlignCenter);
    label.resize(360, 120);
    label.setWindowTitle("Qt6 Hello");
    label.show();
    return app.exec();
}

src/CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(qt6-hello LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)

add_executable(qt6-hello main.cpp)
target_link_libraries(qt6-hello PRIVATE Qt6::Widgets)

install(TARGETS qt6-hello RUNTIME DESTINATION bin)

src/ru.example.Qt6Hello.desktop:

[Desktop Entry]
Type=Application
Name=Qt6 Hello
Name[ru]=Пример на Qt6
Comment=Минимальное приложение на Qt6
Exec=qt6-hello
Icon=qt6-hello
Terminal=false
Categories=Utility;

src/ru.example.Qt6Hello.metainfo.xml:

<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
  <id>ru.example.Qt6Hello</id>
  <metadata_license>CC0-1.0</metadata_license>
  <project_license>MIT</project_license>
  <name>Qt6 Hello</name>
  <summary>Минимальное приложение на Qt6</summary>
  <description>
    <p>Пример приложения на Qt6 для проверки сборки во Flatpak.</p>
  </description>
  <launchable type="desktop-id">ru.example.Qt6Hello.desktop</launchable>
</component>

Путь первый: сборка из исходников#

qt6-hello.yaml:

version: "1.0"

metadata:
  id: ru.example.Qt6Hello
  name: "Qt6 Hello"
  version: "1.0"
  arch: amd64
  branch: stable

runtime:
  id: org.astra.Qt6
  version: "1.8.6"
  sdk: org.astra.Qt6Sdk

source:
  type: sourcedir
  sourcedir:
    path: ./src
    buildDeps:
      packages:
        - qt6-base-dev
        - qt6-tools-dev
        - cmake
    buildCommands:
      - cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/app -DCMAKE_BUILD_TYPE=Release
      - cmake --build build -j
      - cmake --install build

files:
  command: qt6-hello
  desktop: ./src/ru.example.Qt6Hello.desktop
  metainfo: ./src/ru.example.Qt6Hello.metainfo.xml
  icons-dir: ./src/icons

permissions:
  sockets: [wayland, fallback-x11]
  devices: [dri]
  shares: [ipc]
  filesystems: [xdg-documents]
  persist: [.config]

export:
  repository: ./repo

Сборка и установка:

cd qt6-hello
aft-app validate qt6-hello.yaml
sudo aft-app build qt6-hello.yaml
sudo flatpak remote-add --if-not-exists --no-gpg-verify local-repo ./repo
sudo flatpak install -y local-repo ru.example.Qt6Hello
flatpak run ru.example.Qt6Hello

Путь второй: упаковка из архива#

Источник tar рассчитан на случай, когда программа приходит собранной. Чтобы повторить этот путь на том же примере, архив сначала нужно сделать. Сборка идёт обычным образом, вне Flatpak, на машине с установленными qt6-base-dev и cmake:

cmake -S src -B build-native -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release
cmake --build build-native -j
DESTDIR="$PWD/stage" cmake --install build-native

В stage получится дерево usr/bin/qt6-hello. Дальше содержимое перекладывается в плоский каталог и упаковывается:

mkdir -p pack/data
cp stage/usr/bin/qt6-hello pack/
cp src/ru.example.Qt6Hello.desktop pack/data/
tar -C pack -cJf qt6-hello-1.0-linux-x64.tar.xz qt6-hello data

Плоский каталог здесь не случайность. Правила раскладки сопоставляются с именами записей верхнего уровня архива, а не с полными путями внутри него. Правило с шаблоном lib/* не сработает никогда: на верхнем уровне нет записи с таким именем. Каталог, попавший под правило, копируется целиком в <dest>/<имя каталога>, поэтому архив собирается без обёрточного каталога вида qt6-hello-1.0.

Манифест для архива отличается только секцией источника:

version: "1.0"

metadata:
  id: ru.example.Qt6Hello
  name: "Qt6 Hello"
  version: "1.0"
  arch: amd64
  branch: stable

runtime:
  id: org.astra.Qt6
  version: "1.8.6"
  sdk: org.astra.Qt6Sdk

source:
  type: tar
  tar:
    path: ./qt6-hello-1.0-linux-x64.tar.xz
    layout:
      - { pattern: "qt6-hello", dest: bin }
      - { pattern: "data",      dest: share }

files:
  command: qt6-hello
  desktop: ./src/ru.example.Qt6Hello.desktop
  metainfo: ./src/ru.example.Qt6Hello.metainfo.xml
  icons-dir: ./src/icons

permissions:
  sockets: [wayland, fallback-x11]
  devices: [dri]
  shares: [ipc]
  filesystems: [xdg-documents]
  persist: [.config]

export:
  repository: ./repo

Если в архиве лежат ещё и библиотеки, они разбираются отдельным правилом, а недостающие системные библиотеки докладываются пакетами из репозитория:

source:
  type: tar
  tar:
    path: ./app-1.0-linux-x64.tar.xz
    packages: [libwebp7]
    layout:
      - { pattern: "app",     dest: bin }
      - { pattern: "lib*",    dest: lib }
      - { pattern: "plugins", dest: lib }

Шаблон lib* заберёт все записи верхнего уровня, чьё имя начинается с lib, то есть и libapp.so.1, и libcodec.so. Поддерживаются только * и ?, и сопоставление идёт с именем записи целиком.

Для источников tar и system deb-зависимости не разбираются: состав задаёт сам источник. Что программа требует на самом деле, покажет analyze по разделяемым библиотекам:

aft-app analyze --tar ./qt6-hello-1.0-linux-x64.tar.xz \
    --runtime org.astra.Qt6 --runtime-version 1.8.6

Тонкости Qt6#

Если поставщик собрал приложение со своим Qt внутри, эти библиотеки в сборке лишние: они дублируют среду и мешают ей. Их убирают шаблонами очистки:

files:
  cleanPatterns:
    - "lib/libQt6*"

Приложениям на QML нужны модули QML. Они есть в среде, но Qt ищет их относительно расположения своих библиотек, поэтому чаще всего проблема оказывается ровно в предыдущем пункте: приложение принесло свою копию Qt.

Qt6 не тянет за собой Qt5. Программа, собранная против Qt6, но подгружающая плагин на Qt5, потребует обеих сред сразу, а так не бывает: плагин придётся исключить или заменить.

Диалоги открытия и печати Qt6 использует через xdg-desktop-portal. Разрешение на весь домашний каталог для этого не нужно: диалог возвращает приложению уже открытый файл.