Cross compiling Qt/X11

Overview

Cross compiling Qt Embedded is relatively trivial since it is so self
contained, but as the number of external dependencies increases (DirectFB,
D-Bus, GStreamer, OpenGL ES) adjusting your custom mkspec to individually
account for the location of dependencies on the host machine becomes
increasingly tedious/untidy. This complexity culminates in cross compiling
Qt/X11 which adds the additional dependency of fontconfig/FreeType/X11 to
the fray.

This blog is intended as a general overview of the cross
compilation of Qt/X11 for embedded Linux targets. The wide availability of
the BeagleBoard and the proliferation of embedded Linux platforms gives us
the rare opportunity (and impetus) to walk people through the cross
compilation of Qt for an X11 target and get it up and running on a
reference embedded device.

These steps can be summed up as:

  1. Build a reference Linux image and toolchain (Ångström)
  2. Create an appropriate Scratchbox 2 target
  3. Build Qt against this reference image
  4. Reap the whirlwind

This walk through will introduce you to 2 separate tools intended to
simplify cross compilation for embedded targets:

OpenEmbedded (used to build the reference Linux system (Ångström) and toolchain)
Scratchbox 2 (used to build Qt/X11 against the reference system)

They both have their strengths and their particular uses, and if I offend
your sensibilities by using them in conjunction be sure to express yourself
with vigour in the comments section below. Neither tool is required to
achieve this goal, both tools expedite achieving the desired goal.

Building Ångström

I have personally chosen to use Ångström as a reference platform as the
Open Embedded development environment is relatively straightforwards to
setup in a standard (K)Ubuntu Jaunty environment, supports both x86 and
x86_64 host machines, and because it provides a fairly minimalist Linux image
possessing everything required to build a full fledged version of Qt/X11
(X11, D-Bus, GStreamer, Glib, fontconfig, FreeType). Anyone from a Gentoo
background will also enjoy the reassuring quality of 9000 build steps
constructing your eventual Linux system from source.

I followed the instructions given here:

http://www.angstrom-distribution.org/building-ångström

which culminate in the following build step.

bitbake base-image ; bitbake console-image ; bitbake x11-image

I would personally recommend rather running:

bitbake beagleboard-demo-image

which builds an appealing E17 image and which meets all Qt/X11's needs.

The resulting Linux images are immediately deployable and you will have
acquired a very recent (by cross compiler standards) gcc toolchain to use in
the next step

Creating an appropriate Scratchbox2 target

Scratchbox enables you to abstract away the host machine when cross
compiling for a target. Rather than having to correct for host paths, you
can simply execute certain commands in a chrooted environment which
directly resembles the desired targets layout with the targets headers/libs
in their anticipated locations. With Scratchbox 2, you simply prefix
toolchain calls with sb2, and they are run within the specified
realm/sandbox.

Firstly, install Scratchbox 2 using your distributions package manager, or
via whatever mechanism you prefer. Next, you will need to create a suitable
Scratchbox 2 target. This involves executing "sb2-init" (from within the
staging/root directory containing the target's /usr/include and /usr/lib)
with the following arguments:

  1. an appropriate cpu transparency command (normally qemu-$arch)
  2. the desired name of the target
  3. the fully qualified path of the gcc compiler

My sb2-init line looked like this:

sb2-init -c /usr/bin/qemu-arm angstrom-beagle /OE/angstrom-dev/cross/armv7a/bin/arm-angstrom-linux-gnueabi-gcc

and was run within:

/OE/angstrom-dev/staging/armv7a-angstrom-linux-gnueabi;

(Tip: The targets you have created and associated parameters are helpfully
revealed in subsequent calls to sb2-init)

Prior to running sb2-init as shown above, I personally had to modify:

/usr/bin/sb2-build-libtool

by explicitly adding "--host=x86_64" to the configure line in order to get
libtool to compile successfully. The required diff would look something
like this (depending on your host):

- ./configure --prefix=$HOME/.scratchbox2/$TARGET --build=$(uname -m)-unknown-linux-gnu
+ ./configure --host=x86_64 --prefix=$HOME/.scratchbox2/$TARGET --build=$(uname -m)-unknown-linux-gnu

Cross compiling Qt/X11

Whenever you cross compile Qt you should create a custom mkspec based off
an appropriate reference mkspec, mkspecs/linux-g++ in this case. The
required modifications involve hijacking the qmake variables responsible
for defining all the target toolchain commands (gcc, g++, ar,
objcopy, strip) and prefixing each command with sb2.

My qmake.conf in my target mkspec looks like this:

=========================

#
# qmake configuration for linux-g++
#

MAKEFILE_GENERATOR = UNIX
TEMPLATE = app
CONFIG += qt warn_on release incremental link_prl
QT += core gui
QMAKE_INCREMENTAL_STYLE = sublib

include(../../common/g++.conf)
include(../../common/linux.conf)

# modifications to g++.conf
QMAKE_CC = sb2 gcc
QMAKE_CXX = sb2 g++
QMAKE_LINK = sb2 g++
QMAKE_LINK_SHLIB = sb2 g++

# modifications to linux.conf
QMAKE_AR = sb2 ar cqs
QMAKE_OBJCOPY = sb2 objcopy
QMAKE_STRIP = sb2 strip

load(qt_config)

=========================

Once you have modified this mkspec to address any additional considerations
you might have, you can export PKG_CONFIG_PATH to point to your targets
pkg-config (.pc) files, which are normally in a pkg-config directory under
the targets lib directory:

$targetPathOnHost/usr/lib/pkgconfig. Please note this path must be fully
qualified relative to the host)

Qt's configure script currently does not employ pkg-config when
establishing FreeType library metainformation when testing for fontconfig
support, instead opting for heuristics which are explicitly excluded when
cross compiling. This rough little (unsupported) patch will resolve this,
and will result in the correct detection and compilation of Qt with
fontconfig/FreeType support.

=========================

diff --git a/configure b/configure
index 3f3e55a..54de96b 100755
--- a/configure
+++ b/configure
@@ -5115,6 +5115,10 @@ if [ "$PLATFORM_X11" = "yes" ]; then
if [ -n "$PKG_CONFIG" ] && $PKG_CONFIG --exists fontconfig 2>/dev/null; then
QT_CFLAGS_FONTCONFIG=`$PKG_CONFIG --cflags fontconfig 2>/dev/null`
QT_LIBS_FONTCONFIG=`$PKG_CONFIG --libs fontconfig 2>/dev/null`
+ if $PKG_CONFIG --exists freetype2 2>/dev/null; then
+ QT_CFLAGS_FONTCONFIG="$QT_CFLAGS_FONTCONFIG `$PKG_CONFIG --cflags freetype2 2>/dev/null`"
+ QT_LIBS_FONTCONFIG="$QT_LIBS_FONTCONFIG `$PKG_CONFIG --libs freetype2 2>/dev/null`"
+ fi
else
QT_CFLAGS_FONTCONFIG=
QT_LIBS_FONTCONFIG="-lfreetype -lfontconfig"

=========================

then kick off the configure with:

./configure -arch arm -xplatform $customMkspecDirName -force-pkg-config

configure, make and pkg-config are being run directly on the host,
pkg-config is using the targets .pc files, the core tools (qmake, uic, moc)
are being built for the host, the config.tests are being correctly built
against the targets mkspecs and the cross compiler is used for all target
specific compilation. With any luck you with see a large number of the
configure tests pass, and see Qt configured to be built with full
fontconfig/GStreamer/D-Bus support. You can now build Qt by simple running
"make" (You are using the host machines Make, and please note that you
never actually need to prefix sb2 on the commands you run, the qmake
generated Makefile will contain sb2 calls where applicable)

Reap the whirlwind

Cross compiling Qt applications now simply involves running the freshly
built qmake against the desired project .pro file, and running make as per
usual. (You are using Qt tools built for the host, and the host system's
make and pkg-config applications amongst other things). Running make
therefore results in the host system's "make" proceeding through all
the build steps using the correctly prefixed build tools.

Assuming your build is dynamic, your libraries and plugins have to be
deployed to the prefix path you either implicitly or explicitly specified
at configure time)

(Disclaimer: I realize that there is no implicit dependence on Linux when
discussing X11, and there is nothing Linux centric in my instructions other
than that it is the reference (and host) platform. I am building a standard
Qt X11 build in a controlled environment advertising my headers/libs in
their canonical paths (via Scratchbox) and making use of qmake's cross
compiling infrastructure. If you have a suitable root filesystem,
associated toolchain (cross compiler), and if both Qt/X11 and Scratchbox 2
support your target platform, you should be set (although officially
thoroughly unsupported) to walk down the yellow brick road with these
instructions.)

****Note:  As of Qt 4.6 the above mentioned scratchbox2 approach is redundant on systems with a fully functioning pkg-config and is only recommended as a fall back. Build the Ångström sub-system, export PKG_CONFIG_PATH and PKG_CONFIG_SYSROOT appropriately and configure should correctly establish the capabilities of the sub-system.


Blog Topics:

Comments