QML wrapper service explained
First, the background. We’ve been doing QtQuick work for couple of clients. We have few of our own internal projects. And then there is the Kelikamerat demo app which was ported from Android to QML in mere four hours.
Some would say we like working with Qt and QML. And they would be correct, Qt and especially Qt Quick is great tool for rapidly create applications. It provides a way to create Qt applications without writing single line of C++ or need to compile the app.
But there has to be easier way to build and deploy QML apps to real devices than to build a binary for every single supported platform by hand, by the developer.
We started thinking. One night at IRC we were talking about this situation and suddenly there was an idea.
Couple of days later we started building the web service which would package QML apps to .deb packages for the Nokia N900 device.
As we want to see this type of use case to be really easily possible in the future we want to share with you how we created this service.
How QML app works
Read more about Qt Quick and QML from the official site
An application with QML written UI is in its simplest form just one QML file.
As an example here is the Kelikamerat.qml file. The whole application is in that one file. It fetches the list of weather cameras, presents it as a list in the UI. User can then select one camera and the app fetches all photos for that camera and presents that as a photo carusel to the user.
Pretty standard stuff. App fetches data from the Internet and shows it to the user. Qt Quick environment allows all this without a single line of C++ code which we would need to compile.
The app contains logic but instead of C++ it is written in Javascript and embedded inside UI declaration. It is of course possible to separate the code, but for this app it is easier to keep everything in one file.
The QML file needs an interpreter which runs the Javascript and processes the UI declarations. The Qt SDK includes such little tool: qmlviewer. This tool runs QML files by creating runtime environment with QDeclarativeView and importing the file given as command line parameter.
How the package is generated
What this service does is basically the same as what qmlviewer does, if simplified to the bones.
More elaborate explanation covers four different areas. First, the C++ wrapper, then the resource file generation and the qmake project file. Last is the .deb package template.
C++ code for the wrapper
The service compiles small C++ program which creates QDeclarativeView instance and loads the QML file.
Tries to be as simple as possible
This code could be more complicated based on the features which we would like to expose to the QML side. This is the place to load all external libraries, open databases and generally do whatever a C++ program normally does. But in this service it is as simple as it can be.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <QApplication>
#include <QDeclarativeView>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDeclarativeView view;
view.setSource(QUrl("qrc:/wrapped-main-app.qml"));
view.show();
return app.exec();
}
|
In line 9 is the name of the QML file. This file is loaded and run. It is a resource named wrapped-main-app.qml which is put in place to the resource file by the service.
Line 11 is used to show the UI and line 12 enters the event loop and starts the program.
This is all there is to the wrapper, and this is all C++ code in the whole service. Everything else is needed just to get this code compiled and packaged to something that can be installed and run in the device.
At the moment only Nokia N900 is supported
At the moment only Nokia N900 is supported, but in the future we might add support for other platforms. The most interesting one would be S60 but at the moment Linux toolchain is in “will work eventually” state.
Also, this code does not use any special tricks to get better performance. For example it could change to OpenGL based renderer etc. There are, however, plans to support these in the future.
Resource template
Second piece to the puzzle is the resource file.
1 2 3 4 5 6 | <RCC>
<qresource prefix="/">
<file alias="wrapped-main-app.qml">%%qmlfilename%%</file>
%%additionalresources%%
</qresource>
</RCC>
|
This is the template that is expanded by the system. As explained in the service, the project repository should have only one .qml file in the top level. The name of that file will be the project name, and that file is used in this template as %%qmlfilename%% in line 3. There is also an alias for that file. The alias is used by the C++ code.
Second template variable in line 4 is %%additionalresources%% which is expanded to contain all files in the repository with the same directory structure and names as in the repository. This means that in the QML file you can query the files in your repository by qrc://path/in/the/repo.qml.
Project template
1 2 3 4 5 6 7 8 9 10 11 | QT += core gui declarative
TARGET = app
TEMPLATE = app
SOURCES += main.cpp
CONFIG += mobility
MOBILITY =
RESOURCES += resources.qrc
|
The project has one source file and one resource file. Qt components used are core, gui and declarative. Also mobility is enabled.
With these in place the service can now create a binary.
Package template
Hopefully in the future the whole package skeleton is configurable
Nokia N900 device uses .deb packages for distributing apps.
The .deb format is quite extensive and this service uses only the bare minimum what can be done with it. Only the package name is changed each run.
The Nokia Qt SDK contains tools to generate the skeleton package template. To this template the system changes the package name, and copies the executable in place.
There are plans to allow more customization of this step in the future.
Components of the system
The service itself consists of three components. The actual package generation was explained above. Here are the other two components: the website and the cluster which runs the packager. These two components are here to run the build command for your repo.
The idea behind this was to create it without any kind of state or user handling. That’s why the frontpage only has one text field and a button.
Uploading project files and configuration and all that would need more complex system. But there really is no need for all that. BitBucket provides far superior way to deliver the project and the result. No need to store anything in the service.
Web site
The website itself is written in Python using Werkzeug WSGI utility library. It is served with NGINX and gunicorn. Werkzeug was chosen for the task because it is the lightweight cousin of Django which we usually use for all web stuff.
The purpose for this is to send commands to the cluster. At the moment there is obviously only one command: start the build. And only parameter is the repository URL.
Cluster
The last part of the stack is the build cluster. This ties everything together and makes the build in these steps:
- Checks the URL agains regexp pattern. It has to point to BitBucket
- Send tweet “Starting to process”
- Creates temporary build directory
- Clones the repository
- Checks if the repo contains build result already
- Reads the repository content
- Populates template variables based on the repository file list
- Creates qmake project file
- Creates package files
- Runs qmake and make
- Runs mad dpkg-buildpackage
- Copy the results to work area
- Add, commit, push
- Clean the build directory leaving no trace of the build
- Send tweet “Done”
The cluster is running Celery and RabbitMQ is taking care of the messaging.
Celery is really cool little piece of software. Altought Celery might have been created for different types of applications in mind it works beatifully as a build cluster. The nature of the service and Celery makes this really scalable. There is no limit in the number of build nodes.
The communication to the user is handled by Twitter.
Templating and substitutions are done by custom Python tool created for this service. The templating system could be used to power pretty much any type of build system as the templating is quite flexible. It is repository based and on each build the temporary build directory is created by cloning the template repository.
Foreword
There is huge potential in QML and Qt Quick applications. We just need to forget about compiling apps and ease the deployment. It is really frustrating to have cool app ready in few hours. Then you realize it would need several days to compile and create packages for all supported platforms. It can’t be right.
Thanks for reading.