Sunday 12 May 2013

Migrating from Harmattan to Sailfish Silica using an Abstraction Library

Introduction

Note: This post will be updated for the Qt5 Alpha shortly!

Sailfish Silica and Harmattan are related: both build on Qt  / QML: so it should be possible to port an app from Harmattan to Sailfish, or even to maintain an app that runs on both platforms from a common code base. Indeed doing so, and some of the challenges I have encountered was the trigger for this blog.

In future articles I will examine this theme from an architectural point of view, and question if a multi-platform from one code basis is actually desirable or truly possible; but in the context of this post we will assume that such an endeavor is both possible and desirable; and show how we can achieve this using an abstraction library that will remove direct dependencies on com.nokia.meego or sailfish.silica from the core code of our apps.

The Problem

Unless your app is very basic, the chances are you will have import commands such as import com.nokia.meego 1.0 in your QML code, giving you access to Harmattan components such as PageStackWindow, ToolBarLayout, and even Button, which in turn give your app the Harmattan look and feel.

Equally a native Sailfish Silica app will have imports like import Sailfish.Silica 1.0 which gives access to equivalent Silica components and look and feel.

Now if we are doing a one-way port, abandoning Harmattan in favour of Sailfish Silica, then we can just replace the import call, replace or rename some of the components, and voila we have a Sailfish app.

But if we want to maintain an update an app on both platforms then things get a little more tricky. Clearly we don't want to maintain 2 parallel projects, one importing Harmattan, and the other Sailfish. Every time we add new functionality or fix a bug we will have to remember to change both projects .... 

Equally we don't want the kludge of platform dependent IFs or IFDEFs littering our code. Our app code should concentrate on doing what it is supposed to do, and not need to worry about the platform it is running on.

To achieve this we are going to use an abstraction library.The basic concepts are well documented by Thomas Perl on his blog which you can find here

In this article I will build on Thomas's work, and address some additional problems.



Nomenclature

The first problem is one of naming. We are going to create a set of QML wrapper components, and a c++ "plugin"; but what should the whole best be called? A package? A component set? An import library? In this article I will simply use the term "library".

If I refer to "core app" or "core project", then I mean your app or project that until now imports com.nokia.meego, and that will soon import an abstraction library instead. 

The Basic Concept

We will abstract all calls to import com.nokia.meego 1.0
and import Sailfish.Silica 1.0 out of our core project. Our project will instead import our abstraction library - in my case import org.flyingsheep.abstractui 1.0

The abstraction library wil have 2 versions (or flavours), the one will wrap Harmattan components and import com.nokia.meego, and the other will wrap Sailfish Silica and import sailfish.silica.

We then deploy the appropriate flavour of our abstraction library to the SDKs / Emulators, so that our core app will find the correct flavour. In the one case it will "get" Harmattan components, and in the other Sailfish - both using the same import.

This allows us to remove the complication of which component set is actually called out of our core code: This is the power of abstraction.

If you have not yet done so, your next step should be to read Thomas's post, then return here.

Your first Abstraction Library

By now you should have an abstraction project (in 2 flavours), containing a set of QML files that look like this:
//Start of AUIButton.qml --Harmattan Flavour
importQtQuick1.1
importcom.nokia.meego1.0
Button{
}
//End of AUIButton.qml


and for Sailfish:

//Start of AUIButton.qml --Sailfish Flavour
importQtQuick1.1
importsailfish.silica1.0
Button{
}
//End of AUIButton.qml
You should also have a qmldir file that references your qmls:
AUIButton 1.0 AUIButton.qml AUIButtonColumn 1.0 AUIButtonColumn.qml AUIButtonRow 1.0 AUIButtonRow.qml
..More references here


You will have modified you core app to import your reference library and use your component wrappers (i.e. AUIButton instead of Button).

It's now time to build and run your core project. We will first do this against Harmattan, as a Harmattan project with abstracted Harmattan components should give us the fewest problems.

Further Complications: Qt Types

But when I was at this stage, while my app would start, it suffered from errors: namely:

ReferenceError: Can't find variable: PageOrientation
ReferenceError: Can't find variable: PageStatus
ReferenceError: Can't find variable: DialogStatus

It took it a little head scratching before it clicked: Not only does the Harmattan component set include QML components, it also contains a c++ library / plugin which exposes c++ constant enums to QML:  examples being PageOrientation, PageStatus and DialogStatus.

This library is referenced on the first line of the QMLDIR file

plugin meegoplugin

On the Nokia Simulator hosted on OSX the library is: 
/Users/<your_home_dir>/QtSDK/Simulator/Qt/gcc/imports/com/nokia/meego/libmeegoplugin.dylib

On the QEMU Emulator the library is: 
/Users/<your_home_dir>/QtSDK/Madde/sysroots/harmattan_sysroot_10.2011.34-1_slim/usr/lib/qt4/imports/com/nokia/meego/libmeegoplugin.so

As an experiment I copied the libmeegoplugin file across to my abstractui library, and modified the QMLDIR file to point to it. While this cured the three errors above, it caused others, but at least showed I was on the right track.

So if we are to continue using such constants in our core project, then we have to extend our abstraction library with a c++ plugin so it exposes equivalents.

It is possible that other types are used and may need to be abstracted: I limit my demo to these 3 examples.

Extending the Abstraction Library with a Plugin to Expose Qt Types


Below I describe how to extend our abstraction library to expose equivalent types. 

We will need to do this in both the Harmattan and Sailfish flavours of the library. I use the Harmattan flavour as an example below; but the Sailfish version will be near identical (the differences being in the .pro file).

Get the Header Files

We will need the original header files describing the PageOrientationPageStatus and DialogStatus types. To do that we need to download the sources of the Qt Components from here. This also give us a reference as to how plugins are built.

Once you have extracted this, you will need to find the 3 header files:

  • PageOrientation.h
  • PageStatus.h
  • DialogStatus.h

These will be renamed, and added to our project.

Create the Project, Header and Source Files

Until now my abstraction project abstractui consisted only of a .pro file and the .qml wrapper files. It is now time to get our hands dirty with a little C++.

A little bit of cutting and pasting later, it now looks like this:


Pro File:

The .pro file is still work in progress, I need to improve the file structure / handling, but at them moment it looks like this:

# Start of abstractui.pro file

TEMPLATE=lib
TARGET=abstractui
QT+=declarative
CONFIG+=qtplugin
 
TARGET=$$qtLibraryTarget($$TARGET)
uri=org.flyingsheep.abstractui
 
#Input
SOURCES+=\
plugin.cpp
 
HEADERS+=\
auimdialogstatus.h\
auimpagestatus.h\
auimpageorientation.h\
plugin.h
 
#DISPLAYQMLFILESINPROJECT:WorkinProgress
#auiqmlsisasymlinkfromthecurrentdirectory($$PWD)tothedirectorycontainingtheqmls
#Thesymlinkwascreatedfromthecurrentdirectorywith:
#ln-s../org/flyingsheep/abstractui/auiqmls
 
OTHER_FILES=$$PWD/auiqmls/*.qml\
qmldir
# End of abstractui.pro file

Header Files

The 3 aui*.h headers are just renamed clones of the 3 headers listed above, with no changes whatsoever to content.

The plugin.h file looks like this:

//Start of plugin.h
#ifndefABSTRACTUI_PLUGIN_H
#defineABSTRACTUI_PLUGIN_H
 
#include<QDeclarativeExtensionPlugin>
 
classAbstractuiPlugin:publicQDeclarativeExtensionPlugin
{
Q_OBJECT
#ifQT_VERSION>=0x050000
Q_PLUGIN_METADATA(IID"org.qt-project.Qt.QQmlExtensionInterface")
#endif
public:
voidregisterTypes(constchar*uri);
};
 
#endif// ABSTRACTUI_PLUGIN_H
//End of plugin.h

Source Files

Our plugin.cpp is actually very simple. It has just one function registerTypes, which in turn registers an uncreatable (constant) type for AUIPageStatus, AUIDialogStatus, and AUIPageOrientation.
 
//Start of plugin.cpp
#include"plugin.h"
#include"auimpagestatus.h"
#include"auimdialogstatus.h"
#include"auimpageorientation.h"
 
#include<qdeclarative.h>
 
voidAbstractuiPlugin::registerTypes(constchar*uri)
{
Q_ASSERT(uri==QLatin1String("org.flyingsheep.abstractui"));
qmlRegisterUncreatableType<AUIMPageStatus>(uri,1,0,"AUIPageStatus","");
qmlRegisterUncreatableType<AUIMDialogStatus>(uri,1,0,"AUIDialogStatus","");
qmlRegisterUncreatableType<AUIMPageOrientation>(uri,1,0,"AUIPageOrientation","");
}
 
#ifQT_VERSION<0x050000
Q_EXPORT_PLUGIN2(abstractuiplugin,AbstractuiPlugin)
#endif
//End of plugin.cpp

qmldir File

plugin abstractui
AUIButton 1.0 AUIButton.qml
AUIButtonColumn 1.0 AUIButtonColumn.qml
AUIButtonRow 1.0 AUIButtonRow.qml
..More references here

Using the Extended Abstraction Library

You now have an abstraction library, extended with a c++ plugin that exposes 3 types. To use this you must:
  • distribute the library file libabstractui.so and the qmldir file to your import folder
  • modify the code of your core project to use AUIPageStatus instead of PageStatus etc.
As always, I hope this helps. Have fun.

No comments:

Post a Comment