Cross-compile QT for ARM

When developing a QT application for an embedded ARM processor, it is often desirable that development work happens not on the ARM directly due to their low performance.  However, in order for this to work properly QT must be cross-compiled and ready for use.  This is not a very straightforward process, so this documentation attempts to lay this out in an easy way.  This guide shows you how to cross-compile for a generic ARM processor, however this same sequence should work for other devices such as a Raspberry Pi.

Our build system is Debian 8(jessie).

Part I: Root Filesystem and Cross-compiler

When cross-compiling, we need a root filesystem for our target device.  If you already have a root filesystem, mount and copy the entire filesystem to your local machine.  Otherwise, we will use debootstrap to create our root filesystem.  In order to properly use the ARM root filesystem however, we will use qemu-debootstrap.

Install the requirements:

apt-get install qemu-static debootstrap

Create a root filesystem directory and install some dependencies:

export ROOTFS=/path/to/rootfs/directory
mkdir $ROOTFS #directory which will become /
cd $ROOTFS #go to that directory
su #become root; alternatively 'sudo bash' should work

qemu-debootstrap --arch=armhf --variant=minbase jessie . http://http.debian.net/debian
#create a new armhf system of jessie.
#the minbase variant will install the minimum number of required packages in order to boot the system,
#everything else can be installed thru apt.
#this minimum install is 170M.

chroot . #change your root directory.
#qemu will handle the translation of ARM binaries to your local architecture

apt-get install libstdc++-4.9-dev libc6-dev libssl-dev
#install C/C++ headers, as well as openssl

In order to cross-compile QT, we also need the cross compiler.  The easiest way to do this is to download a pre-build cross compiler from Linaro.  The one that I have used is the 4.9 2015.05 version(gcc-linaro-4.9-2015.05-x86_64_arm-linux-gnueabihf.tar.xz), which you can download here.  Extract the compiler.

Part II: Configure and Compile

  • Now that we have our root filesystem and cross-compiler, it’s time to configure and compile QT.  First, we need the actual QT sources.
#do this in a directory that is not in your root filesystem, i.e. ~
git clone git://code.qt.io/qt/qt5.git
cd qt5
# If you need a specific version, checkout the tag here
# git checkout tags/....
./init-repository

Now, become the root user and set some variables that we will need in order to compile QT.

su #or 'sudo bash'
export PATH=$PATH:/path/to/extracted/compiler/(example: /home/user/Downloads/gcc-linaro-4.9-2015.05-x86_64_arm-linux-gnueabihf/bin)
ROOTFS=/path/to/root/fs
TRIPLET=$(arm-linux-gnueabihf-g++ -dumpmachine) #becomes arm-linux-gnueabihf
export PKG_CONFIG_LIBDIR=$ROOTFS/usr/lib/pkgconfig:$ROOTFS/usr/share/pkgconfig:$ROOTFS/usr/lib/arm-linux-gnueabihf/pkgconfig

We must also fix the symlinks in the root filesystem.  Clone the cross-compile-tools repository into a different directory than the one with all of your QT sources, and run the fixQualifiedLibraryPaths script:

cd ~
git clone https://github.com/rm5248/cross-compile-tools.git
./cross-compile-tools/fixQualifiedLibraryPaths $ROOTFS /path/to/extracted/compiler/gcc-linaro-4.9-2015.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++

Before we can configure Qt, we must create the mkspec for ARM hard float(ignore this step if you are using a soft float ARM processor).

cd ~/qt
cp -r qtbase/mkspecs/linux-arm-gnueabi-g++ qtbase/mkspecs/linux-arm-gnueabihf-g++
vi qtbase/mkspecs/linux-arm-gnueabihf-g++/qmake.conf #add 'hf' to all of the binaries, so that they now read 'arm-linux-gnueabihf-[gcc|g++|...]

Now we configure Qt. This is based off of the configure parameters in the Debian version of Qt.

./configure \
-confirm-license \
-prefix /usr \
-bindir /usr/lib/$TRIPLET/qt5/bin \
-libdir /usr/lib/$TRIPLET \
-headerdir /usr/include/$TRIPLET/qt5 \
-datadir /usr/share/qt5 \
-archdatadir /usr/lib/$TRIPLET/qt5 \
-plugindir /usr/lib/$TRIPLET/qt5/plugins \
-importdir /usr/lib/$TRIPLET/qt5/imports \
-translationdir /usr/share/qt5/translations \
-hostdatadir /usr/share/qt5 \
-opensource \
-nomake examples \
-nomake tests  \
-xplatform linux-arm-gnueabihf-g++ \
-platform linux-g++ \
-sysroot $ROOTFS \
-no-gui \
-no-widgets \
-hostprefix /usr/qt[version]-arm \
-reduce-exports

Some notes on this configure:

  • xplatform tells Qt that we are cross-compiling.  If you are using ARM soft float, change this to linux-arm-gnueabi-g++
  • hostprefix tells Qt where to install qmake on your local(build) machine, not on the ARM
  • We’re not building the GUI or widgets; this is appropriate for a headless install, but you may not wish to have a headless install
  • Check the output of ./configure –help to see all available options

Part III: Make

Because the Qt build does not work perfectly when cross-compiling, you will have to compile as root and install at the same time.

make install -j8 #-j8 tells make to use 8 processors; adjust this accordingly based on your system

Troubleshooting

For reasons that I can’t quite figure out, sometimes the build will fail when attempting to link programs with libz.so.1. If that happens, try cleaning the tree and start over:

git submodule foreach --recursive "git clean -dfx" && git clean -dfx

Part IV: Qt Creator

Now that we have Qt installed, we can setup Qt creator in order to easily cross compile.  Open up Qt creator, and go to Tools->Options.  Under the ‘Qt Versions’ tab, add a new Qt version.  Qt creator needs the qmake executable, which was installed in the ‘hostprefix’ folder that we defined in the configure script(in this case, /usr/qt5.4-arm/bin/qmake).

Next, define the compiler in the ‘Compilers’ tab.  Call it ‘GCC-arm’.

Finally, define a new kit.  Call it ‘qt-arm’, and set the compiler and Qt version that we just defined.

Once you open up your project in Qt creator, you can now add a new kit.  On the left side of the screen, go to the ‘Projects’ tab and click ‘Add Kit’.

If you are using the Linaro GCC compiler, be aware that it does not respect the Debian multiarch spec, and so if you have installed packages through apt they will not link cleanly.  When the linker runs, it should read /etc/ld.so.conf from your root filesystem, however it does not appear to support the include directives in this file. To fix that, edit ld.so.conf to include the multiarch directories(defined in Debian in /etc/ld.so.conf.d/arm-linux-gnueabihf.conf) to be the following:

include /etc/ld.so.conf.d/*.conf

/lib/arm-linux-gnueabihf
/usr/lib/arm-linux-gnueabihf

One other thing that you may have to do depending on where the compiler is from, is to adjust some symlinks in order to cross-compile properly. Otherwise you may get errors such as the following(using log4cxx as an example here):

/path/to/rootfs/usr/lib/arm-linux-gnueabihf/liblog4cxx.so: undefined reference to `vtable for std::basic_stringstream<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >@GLIBCXX_3.4'

To fix this problem, we must adjust what file the compiler is looking for when it attempts to link the program with libstdc++.

cd /path/to/gcc-root/arm-linux-gnueabihf/lib
rm libstdc++.so
ln -s $ROOTFS/usr/lib/arm-linux-gnueabihf/libstdc++.so.6 libstdc++.so

I believe that this has to do with differences in how Linaro has compiled the stdc++ library versus how Debian compiles their stdc++ library.