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:
For an example, I've make a small application that:
- Has some C++ code
- Has some QML code
- Uses gettext for translations
- 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
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/
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 $@ $<
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 .
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 $@
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`
$(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
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!