Портируем Qt Quick Components на S60 5th edition

в 8:37, , рубрики: Без рубрики

Портируем Qt Quick Components на S60 5th edition

Приступая к реализации клиента Футубры для Symbian, я не сомневался в выборе Qt Quick в качестве фреймворка для реализации пользовательского интерфейса приложения. Благодаря его реактивному биндингу данных становится возможным реализовывать слой представления, используя передовые архитектурные паттерны проектирования, в частности — Model-View-ViewModel. Однако Qt Quick в «голом» виде располагает очень скудным набором базовых элементов, из которых предлагается собирать более сложные: кнопки, поля ввода и прочее. Памятуя, какая уйма времени ушла на их реализацию при создании пользовательского интерфейса на прошлом проекте, я сразу отказался от идеи делать их собственноручно и обратил свой взор на библиотеку Qt Quick Components. Под катом находится описание того, как мне удалось расширить границы её применимости и «завести» для платформы S60 5th edition.

Библиотека Qt Quick Components представляет собой совокупность плагинов Qt Quick, реализующих широкий набор виджетов поверх QML. Одной из главных особенностей библиотеки является её кроссплатформенность. Существует десктопная версия для Windows, Mac OS, KDE и GNOME, а также вариации для братьев меньших в лице Symbian, Maemo и MeeGo. Конечно, ввиду особенностей платформ, разрешений экранов и Design Guidelines, версии имеют некоторые отличия, но зареюзать порядка 80% QML-кода для, скажем, Windows и Symbian вполне реально. Не менее важной чертой библиотеки является нативный вид входящих в её состав виджетов для каждой из платформ.

Начав исследовать документацию версии библиотеки для Symbian, я сразу столкнулся с неприятной новостью. В соответствии с документацией Qt Quick Components поддерживаются только для современных Symbian^3, Symbian Anna и Nokia Belle. Мне же, в виду популярности в нашей стране сенсорных смартфонов Nokia первого поколения, как то 5800 XpressMusic, 5228 и т.п., нужно было поддержать ещё и платформу S60 5th edition. Оставалось одно – на свой страх и риск портировать библиотеку и на эту операционную систему тоже. Опыт оказался удачным, и здесь я опубликовал полные исходные коды демонстрационного приложения, использующего собственную версию Qt Quick Components. Далее иллюстрируются только наиболее сложные моменты портирования, связанные в основном с qmake-программированием.

Выгружаем

Здесь важно отметить, что выкачивать нужно именно ветку 1.0 – она работает практически из коробки. Более поздние версии библиотеки 1.1.х имеют «тяжелые» зависимости от Symbian SDK в виде заголовочных файлов и библиотек, отсутствующих в S60 5th edition SDK. Заполучить исходники можно либо в виде архива по ссылке, либо воспользовавшись соответствующей командой Git:

git clone git://gitorious.org/qt-components/qt-components.git -b 1.0-symbian

Шаманим

Несмотря на то, что в документации Qt приведен подробный пример создания плагинов, за скобками остается весьма непростая часть, связанная с настройкой конфигурационных *.pro и *.pri-файлов проекта (далее – просто скрипты). Так, создаваемые по умолчанию средой Qt Creator скрипты не содержат механизмов для создания установщика, содержащего плагины Qt Quick. Одной из особенностей установки приложений на Symbian является то, что все исполняемые файлы и динамические библиотеки должны быть помещены в каталог C:sysbin, в то время как Windows расположение оных никак не регламентирует. В результате очень легко прийти к ситуации, когда файловые структуры, явившиеся результатом установки приложения для эмулятора и устройства, будут существенным образом отличаться, что приведет к поиску трудноуловимых багов инсталлятора, специфичных для каждой из платформ. Моя основная идея при разработке скриптов как раз заключалась в том, чтобы они одинаково устанавливали приложение как на Windows, так и на Symbian. Приступим к их рассмотрению.

/config.pri


isEmpty(APP_SOURCE_TREE): APP_SOURCE_TREE = $$PWD
isEmpty(APP_INSTALL_LIBS): APP_INSTALL_LIBS = $$APP_SOURCE_TREE/lib
isEmpty(APP_INSTALL_ROOT): APP_INSTALL_ROOT = $$APP_SOURCE_TREE/install
isEmpty(APP_INSTALL_BINS): APP_INSTALL_BINS = $$APP_INSTALL_ROOT/sys/bin
isEmpty(APP_INSTALL_IMPORTS): APP_INSTALL_IMPORTS = $$APP_INSTALL_ROOT/resource/demo/imports
isEmpty(APP_INSTALL_RESOURCES): APP_INSTALL_RESOURCES = $$APP_INSTALL_ROOT/resource/apps/demo
symbian {
    # Symbian-specific paths from
    # [QtSDK]SymbianSDKs[SymbianSDK]mkspecsfeaturessymbiandata_caging_paths.prf
    load(data_caging_paths)
    # project-specific
    isEmpty(APP_IMPORTS_BASE_DIR): APP_IMPORTS_BASE_DIR   = $$RESOURCE_FILES_DIR/demo/imports
    isEmpty(APP_RESOURCES_BASE_DIR): APP_RESOURCES_BASE_DIR = $$APP_RESOURCE_DIR/demo
    isEmpty(APP_CAPABILITY): APP_CAPABILITY = NetworkServices ReadUserData UserEnvironment
}

В файле config.pri определяются все переменные окружения, используемые в других скриптах. С помощью $$PWD определяется путь к корню проекта, остальные же пути определяются относительно него. При сборке проекта в его корне будет создана папочка install, являющаяся прообразом системного диска C: на устройстве. Точно так же, как и в ней, проект будет разложен и на устройстве.

Скрипты плагинов для краткости рассмотрим на примере самого простого из них — components. Каждый плагин имеет pri-файл и одноименный pro-файл. В первом описываются свойства плагина, используемые для создания установщика, во втором – свойства, используемые для сборки собственно библиотеки.

/src/3rdparty/qt-components/components/components.pri


QT_COMPONENTS_PLUGIN.source_path = 
    src/3rdparty/qt-components/components
QT_COMPONENTS_PLUGIN.imports_path = 
    Qt/labs/components
QT_COMPONENTS_PLUGIN.target = 
    $$qtLibraryTarget(embedded_qt_components_plugin)
QT_COMPONENTS_PLUGIN.qml_files += 
   qmldir 
   Checkable.qml 
   CheckableGroup.qml 
   CheckableGroup.js
APP_PLUGINS += QT_COMPONENTS_PLUGIN

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

  • source_path — путь к плагину относительно корня проекта;
  • imports_path — путь, относительно корня директории импорта приложения; если заменить слеши на точки, то мы получим именования импортов в qml-файлах приложения, например в данном случае —
    import Qt.labs.components;
  • target — название собираемой библиотеки;
  • qml_files — список виджетов плагина, доступных при его подключении в qml-файле.

Относительные пути импорта собираемых Qt Quick Components один-в-один совпадают с оными для имеющихся на устройстве оригинальных плагинов. Абстрагирование qml-файлов от знания природы плагинов, с которыми они работают, позволяет безболезненно переключаться между ними в сборках для S60 5th edition и современного Symbian. В последней строчке pri-файла мы добавляем плагин components к APP_PLUGINS – массиву устанавливаемых на устройство плагинов.

/src/3rdparty/qt-components/components/components.pro


include(../../../../config.pri)
include(components.pri)
TARGETPATH = $$QT_COMPONENTS_PLUGIN.imports_path
TEMPLATE = lib
TARGET = $$QT_COMPONENTS_PLUGIN.target
INCLUDEPATH += $$PWD $$PWD/models
CONFIG += qt plugin
QT += declarative network script
DEFINES += QT_BUILD_COMPONENTS_LIB
QML_FILES += $$QT_COMPONENTS_PLUGIN.qml_files
symbian {
    TARGET.EPOCALLOWDLLDATA = 1
    TARGET.CAPABILITY += $$APP_CAPABILITY
    TARGET.UID3 = 0xE1E604E2
}
HEADERS += qglobalenums.h
SOURCES += plugin.cpp
include(kernel/kernel.pri)
include(models/models.pri)
include(../../../../install.pri)

Парный pro-файл в самом начале включает в себя config.pri, чтобы воспользоваться всеми переменными окружения, например APP_CAPABILITY. Использование последней обусловлено системой безопасности Symbian, которая настаивает на том, чтобы уровень доступа динамических библиотек, используемых исполняемым файлом, был не ниже уровня доступа самого файла. Чтобы не забивать себе этим голову, проще всего давать задавать всем плагинам и exe-шнику одинаковый CAPABILITY.

Заводим

Ниже представлен конфигурационный файл, собирающий все плагины воедино. В него мы включаем pri-файлы всех плагинов Qt Quick Components и формируем директивы по генерации файлов с инструкциями для создания установщика для Symbian.

src/app/gui/gui.pro


include(../../../config.pri)
include(../../3rdparty/qt-components/components/components.pri)
include(../../3rdparty/qt-components/symbian/symbian.pri)
include(../../3rdparty/qt-components/symbian/extras/extras.pri)
CONFIG += qt mobility
QT += core declarative network
TARGET = EmbeddedComponents
DESTDIR = $$APP_INSTALL_BINS
# skip...
symbian {
    # skip...
    for(plugin, APP_PLUGINS) {
        pluginstub = pluginstub$${plugin}
        pluginstubsources = $${pluginstub}.sources
        $$pluginstubsources = $$symbianRemoveSpecialCharacters($$eval($${plugin}.target)).dll
        pluginstubpath = $${pluginstub}.path
        $$pluginstubpath = $$APP_IMPORTS_BASE_DIR/$$eval($${plugin}.imports_path)
        resources = resources$${plugin}
        resourcessources = $${resources}.sources
        for(qmlfile, $$list($$eval($${plugin}.qml_files))) {
            $$resourcessources += $$APP_SOURCE_TREE/$$eval($${plugin}.source_path)/$$qmlfile
        }
        resourcespath = $${resources}.path
        $$resourcespath = $$APP_IMPORTS_BASE_DIR/$$eval($${plugin}.imports_path)
        DEPLOYMENT += $$pluginstub $$resources
    }
}
# skip...

Ну и наконец, как подключаются импорты в самом приложении.

src/app/gui/application.cpp

QDeclarativeView *Application::buildRootView() {
    QScopedPointer<QDeclarativeView> view(new QDeclarativeView());
    QObject::connect(view->engine(), SIGNAL(quit()), view.data(), SLOT(close()));
    view->setResizeMode(QDeclarativeView::SizeRootObjectToView);
    view->engine()->addImportPath(QLatin1String("../../resource/demo/imports"));
    view->setSource(QUrl("qrc:/layout/main.qml"));
    return view.take();
}

В соответствии с определенными нами переменными окружения, само приложение установилось в /sys/bin, а плагины – в /resource/demo/imports того же диска. Отсюда получаем путь, который необходимо указать в функции addImportPath у QDeclarativeEngine.

Вместо заключения

Приложение, созданное с помощью собственноручно собранных Qt Quick Components, практически неотличимо от аналогичного, собранного с их оригинальной версией. Это породило забавное недопонимание со стороны QA Team в Nokia Store. Они долго не хотели публиковать приложение, упрекая меня в том, что я якобы использую настоящие Qt Quick Components, но при этом почему-то не декларирую зависимость от них в установщике. Вот их комментарий:

Jerry (VC-Signing) (Thu, 03 May 2012 18:41:30 +0000)
Dear Publisher,
Your app uses Qt Quick Components for Symbian, but your app package is missing the matching component dependency declaration. Please add the dependency and rebuild your app.

С этой проблемой мне удалось разобраться лишь собрав приложение для современного Symbian с использованием оригинальных плагинов. В виду того, что для S60 5th edition сделать то же самое невозможно физически, сотрудники Nokia Store не стали настаивать на её присутствии в установщике приложения для этой платформы. Ну и напоследок ещё раз напомню, что исходные коды демонстрационного приложения с собственной версией Qt Quick Components доступны здесь.

Павел Осипов,
Разработчик официального клиента Футубры для Symbian

Автор: PavelOsipov

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