Wednesday, May 11, 2016

Developing for Ubuntu Phone without the SDK

I'm not a fan of IDEs. So when developing apps for Ubuntu Phone, I went looking for a way of doing everything from the command line. I'll describe the method I'm currently using to do this. It's pretty rough and there are probably better ways of doing this, but it does work!

For an example, I've make a small application that:
  • Has some C++ code
  • Has some QML code
  • Uses gettext for translations
I need to do the following things:
  • Extract translatable strings from source files and merge in translations
  • Cross-compile for the target device (ARM CPU) from my laptop (AMD64 CPU)
  • Create a click package
  • Deploy the click package to my phone for testing
I'm using make for the build system. It's pretty basic, but it's fairly easy to understand what it does.

The project

My example project has the following files:
 
Makefile
hello.apparmor
hello.cpp
hello.desktop.in
hello.h
hello.png
main.qml
manifest.json
po/fr.po
po/de.po
po/hello.robert-ancell.pot
share/locale/de/LC_MESSAGES/
share/locale/fr/LC_MESSAGES/

If you've done some Ubuntu phone apps hopefully these should be familiar.

Translations 

To make my app translatable to other languages (French and German in this example) I've used gettext:
  • The .desktop file needs translations added to it. This is done by using hello.dekstop.in and prefixing the fields that need translating with '_' (e.g. _Name). These are then combined with the translations to make hello.desktop.
  • In the .qml files translatable strings are marked with i18n.tr ("text to translate").
  • The compiled message catalogues (.mo files) need to be in the click package as share/locale/(language)/(appid).mo.
Gettext / intltool are somewhat scary to use, but here's the magic rules that work for me:

hello.desktop: hello.desktop.in po/*.po
    intltool-merge --desktop-style po $< $@

po/hello.robert-ancell.pot: main.qml hello.desktop.in
    xgettext --from-code=UTF-8 --language=JavaScript --keyword=tr --keyword=tr:1,2 --add-comments=TRANSLATORS main.qml -o po/hello.robert-ancell.pot
    intltool-extract --type=gettext/keys hello.desktop.in
    xgettext --keyword=N_ hello.desktop.in.h -j -o po/hello.robert-ancell.pot
    rm -f hello.desktop.in.h

share/locale/%/LC_MESSAGES/hello.robert-ancell.mo: po/%.po
    msgfmt -o $@ $<

Cross-compiling for the target device

To compile our package I need to make a chroot:

$ sudo click chroot -a armhf -f ubuntu-sdk-15.04 create

The following Makefile rule runs make inside this chroot, then packages the results into a click file:

click:
        click chroot -a armhf -f ubuntu-sdk-15.04 run ARCH_PREFIX=arm-linux-gnueabihf- make
        click build --ignore=Makefile --ignore=*.cpp --ignore=*.h --ignore=*.pot --ignore=*.po --ignore=*.in --ignore=po .

Note the ARCH_PREFIX variable in the above. I've used this to run the correct cross-compiler when inside the chroot. When compiling from my laptop this variable is not set so it uses the local compiler.

hello_moc.cpp: hello.h
    moc $< -o $@

hello: hello.cpp hello_moc.cpp
    $(ARCH_PREFIX)g++ -g -Wall -std=c++11 -fPIC $^ -o $@ `$(ARCH_PREFIX)pkg-config --cflags --libs Qt5Widgets Qt5Quick`

Running 'make click' in this project will spit out hello.robert-ancell_1_armhf.click.

Testing

I connect my phone with a USB cable and copy the click package over and install it locally:

$ adb push hello.robert-ancell_1_armhf.click /tmp/hello.robert-ancell_1_armhf.click
$ phablet-shell
$ pkcon install-local --allow-untrusted /tmp/hello.robert-ancell_1_armhf.click

Then on the phone I quit any instances of this app running, refresh the app scope (pull down at the top) and run my test version.

Summary

Just one more rule to add to the Makefile, then it all works:

all: hello \
     hello.desktop \
     po/hello.robert-ancell.pot \
     share/locale/de/LC_MESSAGES/hello.robert-ancell.mo \
     share/locale/fr/LC_MESSAGES/hello.robert-ancell.mo

The whole example is available in Launchpad:

$ bzr branch lp:~robert-ancell/+junk/hello-example

Happy hacking!