Thursday, January 2, 2014

Qt 5 + Android (January 2014 Edition)

Introduction (2014.01.02)


Sorry this is so late- as usual life gets busy. The last entry I made about Qt5 and Android ended with a stuttering failure. This entry focuses on Linux and building Qt 5 modules for Linux. There are instructions for this at http://qt-project.org/wiki/Qt5ForAndroidBuilding but the instructions assume you know a lot about Android/Qt before starting. This guide is aimed at the semi-knowledgeable that are partially in the know about Qt5 and Android technologies.

The architecture I'm using is Linux x86_64 so please take that into account when following the instructions. Let's get started:

Step 1: Download Android SDK/NDK


For steps that involve downloading from http, I fallback to a tool called wget. You can also scour the internet and find wget for Windows- it's a handy tool to have around when scripting.

https://developer.android.com/sdk/index.html
wget http://dl.google.com/android/adt/adt-bundle-linux-x86_64-20131030.zip

# http://code.google.com/p/mingw-and-ndk/downloads/list
wget https://mingw-and-ndk.googlecode.com/files/android-ndk-r8e-ma-linux-x86_64.tar.xz

Obviously if you are working with a 32-bit machine use the x86 binaries. Once these files are downloaded and placed somewhere (let's say locally into /home/john/sdk and /home/john/ndk) we can move on. FYI, to extract a tar.xz file in Linux use:

# Saving you a google.
tar -xJf android-ndk-r8e-ma-linux-x86_64.tar.xz

There's a step here that isn't really elaborated upon in the main instructions: installing an Android API. They casually have mentioned you will probably want API's 10, 11, and 16. There's a problem, we are command-line cool so we don't want to start a user interface just to pull these down. No problem! Do the following:

cd /home/john/sdk/tools
./android update sdk -u --filter android-10,android-11,android-16

It will prompt you to continue with a "y". If you want to be really cool, add a file in the same location called "y.txt" that contains the letter y and a newline. Save it. Then when you would do the android update, do this:

./android update sdk -u --filter android-10,android-11,android-16 < y.txt

Redirect that input into the updater. Perfect! License accepted. Say you don't want to do all of this by hand, just check out my script for installing the adt-bundle.pl (for linux-x86_64 only) at https://github.com/signatal/system/blob/master/tools/adt-bundle.pl . (PS - If you're looking to help out on the automation side let me know).

You must also set the following environment variables:

ANDROID_SDK_ROOT : Obviously set this to your sdk root that was downloaded.

ANDROID_API_VERSION : android-16 is what I have now started setting this too (see notes in Step 6 as to why this might be an issue). These environment variables MUST be present before configuring Qt to be built! Why these aren't configure command line options? Who knows, maybe they are?


Step 2: Familiarize Yourself with the NDK


The NDK you've downloaded for Android contains some prebuilt cross-compilers that are helpful when building Qt5. If you want the binaries to work on an Android system they need to be built with one of these compilers. To locate one of the compilers, navigate into the ndk toolchains directory:

cd /home/john/ndk
cd toolchains

# The following is the name of one of our selected cross compiler
# selections.
cd arm-linux-androideabi-4.7

cd prebuilt/linux-x86_64

# Just take a look at what's here.
ls

Notice that this feels surprisingly like a linux /usr directory. It's complete with include, bin, lib, share, etc. directories. It's almost as if someone set this up so that a single environment variable could point at it to build Android binaries. In the bin directory you won't see the normal gcc/g++: instead you'll see programs prefixed with "arm-linux-androideabi-".

There's another interesting directory here called arm-linux-androideabi which is at the same directory and include/share/bin/lib. If you traverse this one and go into another bin directory you'll find the C++ compilers and tools.

At this point I had to ask the question: I know what arm, linux, and android are. But what does ABI stand for? It stands for Application Binary Interface- and it's the means by which the Operating System knows how to execute an application. This compiler will produce code that will run on an Android operating system.

Step 3: Obtain Qt5


Most tutorials will actually set the environment up as you go. Going to take a reverse approach and download Qt and try to build it without the environment variables. The reason is because I want to be sure that what I am writing is correct and I want to document what happens when the environment variables aren't right.

# Clone Qt and place it into a directory called qt5. You might
# think, "Oh, this is surprisingly fast!"
git clone git://gitorious.org/qt/qt5.git qt5

cd qt5

# You might think, "Oh, this is suprisingly slow!" Clones each
# module individually so takes awhile.
perl init-repository

Step 4: Strike Gold


You want to know why I split up Obtaining Qt5 and Building Qt5? The reason is I want to explain what the configure scripts are going to look for when building using the Android compiler. Navigate into qt5/qtbase. Then open the script "configure". Just do it- it's not that bad.

cd qt5
cd qtbase
vim configure

Look for the line in the file that looks kind of like:

QMAKE_CONF_COMPILER=$CFG_DEFAULT_ANDROID_NDK_ROOT/toolchains/$ANDROID_NDK_TOOLS_PREFIX-$CFG_DEFAULT_ANDROID_NDK_TOOLCHAIN_VERSION/prebuilt/$CFG_DEFAULT_ANDROID_NDK_HOST/bin/$ANDROID_NDK_TOOLS_PREFIX-g++

Whew. That's a big line! But there's a method to my madness. This line is pure gold for us trying to build this damn thing because in one line it's giving us a hint as to what the input variables should look like. It's having an equation and knowing the answer, we just have to solve for the unknowns!

$CFG_DEFAULT_ANDROID_NDK_ROOT : We know this to be /home/john/ndk/android-ndk-r8e. Now we go into /home/john/ndk/android-ndk-r8e/toolchains to see what is there (the answer is all sorts of stuff).

$ANDROID_NDK_TOOLS_PREFIX : This could be arm, llvm, mipsel, x86. Most likely our default we are building for will be arm (most Android architectures are arm-based). This can be respecified if you want to by using the -android-arch flag, just stick with armeabi-v7a for now though! When that's the case, the prefix used will be arm-linux-androideabi. The platform architecture will be arch-arm.

$CFG_DEFAULT_ANDROID_NDK_TOOLCHAIN_VERSION : From above, the only values this could be are 4.4.3, 4.6, 4.7.

$CFG_DEFAULT_ANDROID_NDK_HOST : This must be linux-x86_64.

After putting it all together, we can determine that the compiler that will be used is:

/home/john/ndk/android-ndk-r8e/toolchains/arm-linux-androideabi-4.7/prebuilt/linux-x86_64/bin/arm-linux-androideabi-g++

Step 5: Build Qt5


I put a great deal of time in pointing out various parts of the NDK for a reason. In the above steps we've already downloaded the Android SDK/NDK and pulled down and initialized the Qt5 project. We've polked around at the contents of the NDK to kind of get the feel about what might be necessary to build Qt 5 for Android. Like most big libraries we need to run a configure step before we can build. Qt is no exception- especially when it comes to building with a cross-compiler.

The configure step I suggest using is:

./configure -developer-build -opensource -confirm-license -xplatform android-g++ -nomake tests -nomake examples -android-ndk /home/john/ndk -android-sdk /home/john/sdk -android-ndk-host linux-x86_64 -android-toolchain-version 4.7 -skip qttranslations -skip  qtwebkit -skip qtserialport -skip qtwebkit-examples -no-warnings-are-errors

Let me break this down:

./configure : Obviously this is the configure script invocation. Everything following it is just command-line options.

-developer-build : Use this flag if you don't plan on performing the installation step.

-opensource : This flag will automatically choose the "Open Source" version of Qt. You can leave this out if you want, but it will prompt you for Open Source/Commercial. Just figured I'd help you speed things along.

-confirm-license : Same as Open Source, this will confirm the license automatically. It can also be not added, but it will also stop configuration to ask.

-xplatform android-g++ : If you look this up in the configure script, it states that this will set "The target platform when cross-compiling" (the x is for cross- just in case you didn't get it).

-nomake tests -nomake examples : Don't make these things, we don't need em.

-android-ndk /home/john/ndk : Specify where the ndk is located. This is a CRUCIAL step! The reason it's crucial is that we get our compiler tools and environment from this location.

-android-sdk /home/john/sdk : Specify where the sdk is located. This is also a crucial step.

-android-ndk-host linux-x86_64 : Specify your host system. If you are in doubt about the exact value of this string, just look at my section above to try and determine this for yourself. Never hurts to double check.

-android-toolchain-version 4.7 : Aren't you glad I made you go through the NDK directories? Choose a version string inside of your NDK/toolchains directory that makes sense. In my case, I had versions for 4.4.3, 4.6, and 4.7 given my host system. The Qt configure script uses this toolchain version to help locate the compiler tools/environment, so choose wisely.

-skip qttranslations -skip  qtwebkit -skip qtserialport -skip qtwebkit-examples -no-warnings-are-errors : Same skippy stuff as usual.

Whew. Breaking that down was a chore and a half- so I hope it saves someone a few minutes of time later. There is more to this story! Even if you do somehow configure the project correctly it can still bail out on you early because of the absence of some magical environment variables.

Step 6: Problems

After compiling I had a really strange error occur with some Java code:

javac -source 6 -target 6 -Xlint:unchecked -bootclasspath /home/jr/system/tools/android-sdk/adt-bundle/sdk/platforms/android-10/android.jar -cp /home/jr/system/tools/qt5-android-build/qt5/qtbase/src/android/accessibility/jar/src/:/home/jr/system/tools/android-sdk/adt-bundle/sdk/platforms/android-10/android.jar -d .classes.bundled src/org/qtproject/qt5/android/accessibility/QtAccessibilityDelegate.java src/org/qtproject/qt5/android/accessibility/QtNativeAccessibility.java
src/org/qtproject/qt5/android/accessibility/QtAccessibilityDelegate.java:62: error: cannot find symbol
public class QtAccessibilityDelegate extends View.AccessibilityDelegate
                                                 ^
  symbol:   class AccessibilityDelegate
  location: class View
src/org/qtproject/qt5/android/accessibility/QtAccessibilityDelegate.java:99: error: cannot find symbol
    public AccessibilityNodeProvider getAccessibilityNodeProvider(View host)
           ^
  symbol:   class AccessibilityNodeProvider
  location: class QtAccessibilityDelegate
src/org/qtproject/qt5/android/accessibility/QtAccessibilityDelegate.java:193: error: cannot find symbol
    private AccessibilityNodeInfo getNodeForView()

...

I know this doesn't normally fit this blog (due to it being a C++ technologies blog), but I want to try and fix this. Without this working it appears as though both Declarative and Quick modules will not build- leaving us with half-baked Qt Libraries. There was something in the Qt documentation about android-10 being the earliest API that could be used. I believe this to be incorrect now- since the error above is complaining about not finding View.AccessibilityDelegate, which was just introduced in API-14 of the Android SDK:

http://developer.android.com/reference/android/view/View.AccessibilityDelegate.html

This leads me to my final confusion: all blog posts suck because they fall quickly out of date. I'm sure if five minutes have passed since I hit Post that this entry will also be chalked full of bad information. I encourage everyone who reads this to PLEASE comment whether or not these instructions worked for you- it will help everyone in the future who happens to stumble in.