[Ubuntu Touch Apps and Games] Hempe:
How to create a C++ Qml Ubuntu Touch Application from start to end (now with easy setup)

(Including packaging and creating your own ppa)

Important! Only use lowercase for your project name otherwise your gonna have a lot of problems

I created a branch for easy access but I recommend you still read the tutorial.

So to create the basic environment you can use the following (with the rename.sh you change all the important values)

bzr branch lp:~hansueli-burri/+junk/CreateYourProject
cd CreateYourProject/
./rename.sh

1. Things you need before we start

Create a Lauchpad Account (https://launchpad.net)

Create your OpenPGP key (Needed for packaging)

Sign the Ubuntu Code of Conduct (Needed to create a PPA)

Register an SSH Key (https://help.launchpad.net/YourAccount/CreatingAnSSHKeyPair) (Needed for uploading to lauchpad)

Install the Ubuntu SDK (http://developer.ubuntu.com/get-started/gomobile/)

2. How to start your Project?

Well the default templates didn't work for me so we create a custom project.

2.1. Create a new Folder (with the name of your project)

In my case apcalc-qml

2.2. Create a subfolder for your QML-files and a subfolder for your c++ files

In my case qml and cpp

2.3. Now we create a new Project-file

In my case apcalc-qml.pro

That looks something like this (When you create a new class it should be added automaticly if not you have to add it your self, like the applicationdata class)

apcalc-qml.pro

[COLOR="Purple"]QT[/COLOR] += qml quick
[COLOR="Green"]# If your application uses the Qt Mobility libraries, uncomment the following
# lines and add the respective components to the MOBILITY variable.
# CONFIG += mobility
# MOBILITY +=

#C++ source files[/COLOR]
[COLOR="Purple"]SOURCES[/COLOR] += cpp/main.cpp\
cpp/applicationdata.cpp\

[COLOR="Green"]#C++ header files
[/COLOR][COLOR="Purple"]HEADERS[/COLOR] += cpp/applicationdata.h

[COLOR="Green"]#Path to the libraries...
[/COLOR][COLOR="Purple"]INCLUDEPATH[/COLOR] += $$PWD\
$$PWD/../../../../usr/lib
[COLOR="Purple"]DEPENDPATH[/COLOR] += $$PWD/../../../../usr/lib

[COLOR="Green"]#Path to "other files" in this case the QML-Files
[/COLOR][COLOR="Purple"]OTHER_FILES[/COLOR] += \
qml/main.qml\
qml/basicCalc/*.qml

2.4. Now we should create the C++, Source, Header and QML files.

main.cpp

[COLOR="Blue"]
#include [COLOR="Green"]<QtGui/QGuiApplication>[/COLOR]
#include [COLOR="Green"]<QGuiApplication>[/COLOR]
#include [COLOR="Green"]<QQuickView>[/COLOR]
#include [COLOR="Green"]<QtQml/qqmlcontext.h>[/COLOR]
#include [COLOR="Green"]<stdio.h>[/COLOR]
#include [COLOR="Green"] "applicationdata.h"[/COLOR]
[/COLOR]
[COLOR="Olive"]int [/COLOR]main([COLOR="Olive"]int[/COLOR] argc, [COLOR="Olive"]char[/COLOR] *argv[])
{
[COLOR="Purple"]QGuiApplication[/COLOR] app(argc, argv);
[COLOR="Purple"]QQuickView[/COLOR] view;
[COLOR="Purple"]ApplicationData[/COLOR] data;

[COLOR="Green"] //Resize Mode so the content of the QML file will scale to the window size[/COLOR]
view.setResizeMode([COLOR="Purple"]QQuickView[/COLOR]::[COLOR="Purple"]SizeRootObjectToView[/COLOR]);

[COLOR="Green"] //With this we can add the c++ Object to the QML file[/COLOR]
view.rootContext()->setContextProperty([COLOR="Green"]"applicationData"[/COLOR], &data);

[COLOR="Green"] //Resolve the relativ path to the absolute path (at runtime)[/COLOR]
[COLOR="Olive"]const[/COLOR] [COLOR="Purple"]QString[/COLOR] qmlFilePath= [COLOR="Purple"]QString[/COLOR]::fromLatin1([COLOR="Green"]"%1/%2"[/COLOR]).arg([COLOR="Purple"]QCoreApplication[/COLOR]::applicationDirPath(), "qml/main.qml");
view.setSource([COLOR="Purple"]QUrl[/COLOR]::fromLocalFile(qmlFilePath));

[COLOR="Green"] //For debugging we print out the location of the qml file[/COLOR]
[COLOR="Purple"]QByteArray[/COLOR] ba = qmlFilePath.toLocal8Bit();
[COLOR="Olive"]const char[/COLOR] *str = ba.data();
printf([COLOR="Green"]"Qml File:%s\n"[/COLOR],str);

[COLOR="Green"] //Not sure if this is nessesary, but on mobile devices the app should start in fullscreen mode[/COLOR]
[COLOR="Blue"]#if[/COLOR] defined([COLOR="Purple"]Q_WS_SIMULATOR[/COLOR]) || defined([COLOR="Purple"]Q_OS_QNX[/COLOR])
view.showFullScreen();
[COLOR="Blue"] #else[/COLOR]
view.show();
[COLOR="Blue"] #endif[/COLOR]
[COLOR="Olive"]return[/COLOR] app.exec();
}

main.qml

import QtQuick 2.0
[COLOR="Olive"]import[/COLOR] Ubuntu.Components 0.1
[COLOR="Olive"]import[/COLOR] [COLOR="Green"]"basicCalc"[/COLOR]
[COLOR="Olive"]import[/COLOR] QtQuick.Window 2.0
[COLOR="Purple"]MainView[/COLOR] {
[COLOR="Green"] //objectName for functional testing purposes (autopilot-qt5)[/COLOR]
[COLOR="Sienna"]objectName[/COLOR]: [COLOR="Green"]"mainView"[/COLOR]
[COLOR="Sienna"]applicationName[/COLOR]: [COLOR="Green"]"apcalc-qml"[/COLOR]
[COLOR="Sienna"]automaticOrientation[/COLOR]:true;
[COLOR="Sienna"]width[/COLOR]: units.gu(60);
[COLOR="Sienna"]height[/COLOR]: units.gu(100);
[COLOR="Sienna"]id[/COLOR]:root
[COLOR="Purple"]Tabs[/COLOR] {
[COLOR="Sienna"]objectName[/COLOR]: [COLOR="Green"]"Tabs"[/COLOR]
[COLOR="Sienna"]ItemStyle.class[/COLOR]: [COLOR="Green"]"new-tabs"[/COLOR]
[COLOR="Sienna"]anchors.fill[/COLOR]: parent
[COLOR="Sienna"] id[/COLOR]:mainWindow;
[COLOR="Purple"]Tab[/COLOR] {
[COLOR="Sienna"]objectName[/COLOR]: [COLOR="Green"]"Calculator"[/COLOR]
[COLOR="Sienna"]title[/COLOR]:[COLOR="Green"] "Calculator"[/COLOR]
[COLOR="Sienna"]page[/COLOR]:[COLOR="Purple"]BasicCalc[/COLOR]{
[COLOR="Sienna"]width[/COLOR]: root.width;
[COLOR="Sienna"]height[/COLOR]: root.height-root.header.height;
[COLOR="Sienna"]anchors.top[/COLOR]: parent.top;
[COLOR="Sienna"]anchors.topMargin[/COLOR]: root.header.height;
[COLOR="Sienna"]onToCalculateChanged[/COLOR]: {
[COLOR="Green"] //access to the c++ Object [/COLOR]
result=[COLOR="Blue"]applicationData[/COLOR].calculate(toCalculate);
}
}
}
}
}

applicationdata.h

[COLOR="Blue"]#ifndef[/COLOR] APPLICATIONDATA_H
[COLOR="Blue"]#define APPLICATIONDATA_H
#include[/COLOR] [COLOR="Green"]<QObject>[/COLOR]

[COLOR="Olive"]class[/COLOR] [COLOR="Purple"]ApplicationData[/COLOR] : [COLOR="Olive"]public[/COLOR] [COLOR="Purple"]QObject[/COLOR]
{
[COLOR="Blue"] Q_OBJECT[/COLOR]
[COLOR="Olive"]public[/COLOR]:
[COLOR="Olive"]explicit[/COLOR] [COLOR="Purple"]ApplicationData[/COLOR]([COLOR="Purple"]QObject[/COLOR] *parent = [COLOR="Blue"]0[/COLOR]);
[COLOR="Purple"] Q_INVOKABLE QString [/COLOR]calculate([COLOR="Purple"]QString[/COLOR]) [COLOR="Olive"]const[/COLOR];
[COLOR="Olive"]signals[/COLOR]:

[COLOR="Olive"]public slots[/COLOR]:
};
[COLOR="Blue"]#endif[/COLOR] // APPLICATIONDATA_H

applicationdata.cpp

[COLOR="Blue"]#include[/COLOR] [COLOR="Green"]"applicationdata.h"[/COLOR]
[COLOR="Purple"]ApplicationData[/COLOR]::[COLOR="Purple"]ApplicationData[/COLOR]([COLOR="Purple"]QObject[/COLOR] *parent) :
[COLOR="Purple"]QObject[/COLOR](parent)
{
}
[COLOR="Purple"]QString ApplicationData[/COLOR]::calculate([COLOR="Purple"]QString[/COLOR] command) [COLOR="Olive"]const[/COLOR] {
[COLOR="Green"]	// Some Logic comes here[/COLOR]
[COLOR="Olive"]return[/COLOR] command;
}

Those were the important files.

Never forget to mark your functions as Q_INVOKABLE otherwise you can't access them from within the qml file.

You can have a look at the other qm files at http://bazaar.launchpad.net/~hansueli-burri/+junk/apcalc-qml/files

Before you can build your project we have to edit the build properties and add a custom build and clean up step. It should look something like this:

3. Packaging

Lets start with the easy things.

3.1. Create an App icon

Call it something like <YourProjectName>64.png

We will use this in the Desktop file.

3.2. Create a bin file <YourProjectName>.bin

It's a very simple textfile containing something like this, and nothing more.

/usr/share/<YourProjectName>/<YourBinaryName>

The Binary name will most probably be the same as your project name.

But It can be whatever you want because we create it in the rules file manually.

3.3. Create a desktop file

Here you got an example how to do it.

[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Terminal=false
Name=<YourProjectName>
Exec=/usr/share/<YourProjectName>/<YourBinaryName>
Icon=/usr/share/<YourProjectName>/<YourProjectName>64.png
StartupNotify=true
X-Ubuntu-Touch=true
X-Ubuntu-StageHint=SideStage

3.4.Debian packaging

First you create the debian folder in your project folder, containing the following files

changelog

control

copyright

install

compat

rules

and a Subfolder called source containing a file called format

The content of the format file should be:

3.0 (native)

The other files should contain the following. (for the original files go to http://bazaar.launchpad.net/~hansueli-burri/+junk/apcalc-qml/files/head:/debian/)

changelog

Here you can document your changes, changelog entries look something like:

<YourProjectName (0.1) raring; urgency=low

* initial release

-- <Full OpenPGP Identifier (Full name and email address)> Sat, 4 April 2013 00:00:00 +0200

for more information google it.

control

Here you put your build dependencies, dependencies for running your application, your homepage, discription and so on.

I think all the Build-Depends listed bellow are nessesary to successfully compile and build the qt5 qml project but correct me if I'm wrong.

Source: [COLOR="Gray"]<YourProjectName>[/COLOR]
Priority: extra
Maintainer: [COLOR="Gray"]<Full OpenPGP Identifier (Full name and email address)> [/COLOR]
Build-Depends: debhelper (>= 9),
qtbase5-dev,
qtdeclarative5-dev,
qt5-default,
[COLOR="Gray"]<YourBuildDependencies>[/COLOR]
Standards-Version: 3.9.4
Section: misc
Homepage: [COLOR="Gray"]<YourHomePage>[/COLOR]

Package: [COLOR="Gray"]<YourPackageName>[/COLOR]
Section: misc
Architecture: any
Multi-Arch: same
Pre-Depends: ${misc:Pre-Depends}
Depends: ${shlibs:Depends}, ${misc:Depends},
qtdeclarative5-ubuntu-ui-toolkit-plugin | qt-components-ubuntu,
qtdeclarative5-qtquick2-plugin,
[COLOR="Gray"]	<YourRunningDependencies>[/COLOR]
Description: [COLOR="Gray"]<YourShortDescription>
<YourLongDescription>[/COLOR]

copyright

Here you put licencing information mine looks like this

Format: http://dep.debian.net/deps/dep5
Upstream-Name: [COLOR="Gray"]<YourPackageName>[/COLOR]
Source:

Files: *
Copyright: 2013 [COLOR="Gray"]<YourName>[/COLOR]
License: GPL-3.0

Files: debian/*
Copyright: 2013 [COLOR="Gray"]<YourName>[/COLOR]
License: LGPL-3.0

License: GPL-3.0
This package is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 3 of the License.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License can be found in "/usr/share/common-licenses/GPL-3".

License: LGPL-3.0
This package is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU Lesser General
Public License can be found in "/usr/share/common-licenses/LGPL-3".

Install

If someone knows what this is needed for please tell me, cause I don't know, but with the following content everything will work;

usr/share/applications
usr/bin
usr/share/[COLOR="Gray"]<YourProjectName>[/COLOR]

compat

It just contains an 8, I also don't know why.

8

rules

This is a rather important file. Because here we define some of the logic to deploy the package.

Mine looks like this. You have to add all the files that need to be copied.

To understand what it does:

The content of $(CURDIR)/debian/tmp/usr/share/<YourProjectName> for example will be copied to /usr/share/<YourProjectName> when you install the package.

[COLOR="Blue"]#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.

# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1

# Work-around for some machines where INSTALL_ROOT is not set properly by
# dh_auto_install[/COLOR][COLOR="DimGray"]
[COLOR="MediumTurquoise"]override_dh_auto_install:[/COLOR]
dh_auto_install -- INSTALL_ROOT=$([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp

[COLOR="Blue"]# Workaround a bug in that debhelper package version[/COLOR]
[COLOR="MediumTurquoise"]override_dh_install:[/COLOR]
[COLOR="DarkRed"]mkdir[/COLOR] -p $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/share/applications/
[COLOR="DarkRed"]mkdir[/COLOR] -p $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/bin/
[COLOR="DarkRed"]mkdir[/COLOR] -p $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/share/apcalc-qml/
[COLOR="DarkRed"]mkdir[/COLOR] -p $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/share/apcalc-qml/
[COLOR="DarkRed"]cp[/COLOR] apcalc-qml.desktop $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/share/applications/
[COLOR="DarkRed"]cp[/COLOR] apcalc-qml $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/share/apcalc-qml/apcalc-qml
[COLOR="DarkRed"]cp[/COLOR] *.png $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/share/apcalc-qml/
[COLOR="DarkRed"]cp[/COLOR] apcalc-qml.bin $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/bin/apcalc-qml
[COLOR="DarkRed"]cp[/COLOR] -r qml/ $([COLOR="SeaGreen"]CURDIR[/COLOR])/debian/tmp/usr/share/apcalc-qml/

dh_install --sourcedir=debian/tmp --fail-missing

[COLOR="MediumTurquoise"]%:[/COLOR]
dh $@[/COLOR]

4. Upload everything to Lauchpad

You can push (upload) personal branches (those not related to a project) with the following command:

bzr push lp:~YOUR_LAUNCHPAD_NAME/+junk/BRANCHNAME

(... more information if requested)

5. Create a recipe and test it

5.1. Setup pbuilder

First of you need to install pbuilder and modify or create ~/.pbuilderrc

# Codenames for Debian suites according to their alias. Update these when
# needed.
UNSTABLE_CODENAME="saucy"
TESTING_CODENAME="saucy"
STABLE_CODENAME="raring"
STABLE_BACKPORTS_SUITE="$STABLE_CODENAME-backports"

# List of Debian suites.
DEBIAN_SUITES=($UNSTABLE_CODENAME $TESTING_CODENAME $STABLE_CODENAME
"unstable" "testing" "stable")

# List of Ubuntu suites. Update these when needed.
UBUNTU_SUITES=(“saucy",”raring" "quantal" "precise" "oneiric" "natty" "lucid" "hardy")

# Mirrors to use. Update these to your preferred mirror.
DEBIAN_MIRROR="ftp.us.debian.org"
UBUNTU_MIRROR="mirrors.kernel.org"

# Optionally use the changelog of a package to determine the suite to use if
# none set.
if [ -z "${DIST}" ] && [ -r "debian/changelog" ]; then
DIST=$(dpkg-parsechangelog | awk '/^Distribution: / {print $2}')
# Use the unstable suite for Debian experimental packages.
if [ "${DIST}" == "experimental" ]; then
DIST="unstable"
fi
fi

# Optionally set a default distribution if none is used. Note that you can set
# your own default (i.e. ${DIST:="unstable"}).
: ${DIST:="$(lsb_release --short --codename)"}

# Optionally change Debian codenames in $DIST to their aliases.
case "$DIST" in
$UNSTABLE_CODENAME)
DIST="unstable"
;;
$TESTING_CODENAME)
DIST="testing"
;;
$STABLE_CODENAME)
DIST="stable"
;;
esac

# Optionally set the architecture to the host architecture if none set. Note
# that you can set your own default (i.e. ${ARCH:="i386"}).
: ${ARCH:="$(dpkg --print-architecture)"}

NAME="$DIST"
if [ -n "${ARCH}" ]; then
NAME="$NAME-$ARCH"
DEBOOTSTRAPOPTS=("--arch" "$ARCH" "${DEBOOTSTRAPOPTS[@]}")
fi
BASETGZ="/var/cache/pbuilder/$NAME-base.tgz"
DISTRIBUTION="$DIST"
BUILDRESULT="/var/cache/pbuilder/$NAME/result/"
APTCACHE="/var/cache/pbuilder/$NAME/aptcache/"
BUILDPLACE="/var/cache/pbuilder/build/"

if $(echo ${DEBIAN_SUITES[@]} | grep -q $DIST); then
# Debian configuration
MIRRORSITE="http://$DEBIAN_MIRROR/debian/"
COMPONENTS="main contrib non-free"
if $(echo "$STABLE_CODENAME stable" | grep -q $DIST); then
EXTRAPACKAGES="$EXTRAPACKAGES debian-backports-keyring"
OTHERMIRROR="$OTHERMIRROR | deb http://www.backports.org/debian $STABLE_BACKPORTS_SUITE $COMPONENTS"
fi
elif $(echo ${UBUNTU_SUITES[@]} | grep -q $DIST); then
# Ubuntu configuration
MIRRORSITE="http://$UBUNTU_MIRROR/ubuntu/"
COMPONENTS="main restricted universe multiverse"
else
echo "Unknown distribution: $DIST"
exit 1
fi

5.2. How to use pbuilder?

Create a base environment for Ubuntu raring

sudo DIST=raring pbuilder create

Update a base environment for Ubuntu raring

sudo DIST=raring pbuilder update

Build package for Ubuntu raring

sudo DIST=raring pbuilder build <working-dir>/<application_name>.dsc

5.3. Content of the recipe

It can be as simple as this.

# bzr-builder format 0.3 deb-version 0.1~{revno}
lp:~YOUR_LAUNCHPAD_NAME/+junk/BRANCHNAME

(... more information if requested)

6. Create PPA

Now you just need to create your ppa as explaind on launchpad.

Important by default your ppa will not build for arm. you have to ask a question on launchpad to request that your ppa will be build for arm as well.

(... more information if requested)