Monday 16 December 2013

Cross-Compiling QT5 Windows Applications on Ubuntu 13.10

Introduction

I've built a QT application which runs on both Linux and Windows using QT4.8, but found that the accessibility features on QT4.8 are lacking on Windows (for example, a simple text-entry dialogue box will read the contents, but not show them up on a braille display, using Jaws 14).
I opted to make the move to QT5, and I wanted to ensure that:
  • the accessibility features were enabled
  • the windows applications could be statically linked as far as possible
  • the qt-creator application would enable me to produce both Linux and Windows versions of my program

Setting up the Environment

PATH=/opt/bin:$PATH

MINGW32 and Ubuntu Tools

In order to be able to build QT5 and applications, and a Windows release of QT5 and the cross-compilation applications, a number of Ubuntu packages are required.  These can be installed with the apt-get command:

sudo apt-get install autoconf automake bash bison bzip2 cmake flex gettext git g++ intltool libffi-dev libtool libltdl-dev libssl-dev libxml-parser-perl make openssl patch perl pkg-config scons sed unzip wget xz-utils

QT5 for Linux (Dynamic Linking)

I've downloaded QT5 from the git repository, and built in a separate directory as advised, but have had significant trouble, so I've ended up untarring multiple instances of the source, and building in-situ.

See at the end of this blog entry the sorts of problems I had, which were resolved by downloading a tarball, and building in-situ.

Download tarball

cd /opt/src 

wget http://download.qt-project.org/development_releases/qt/5.2/5.2.0-beta1/single/qt-everywhere-opensource-src-5.2.0-beta1.tar.gz

Unpack

tar xf qt-everywhere-opensource-src-5.2.0-beta1.tar.gz

mv qt-everywhere-opensoure-src-5.2.0-beta1 qt5.2.0-beta1-linux

Patch

Specific to the beta release5.2.0-beta1 that I chose:

localtime_r

There is a problem with localtime_r, so some alternative code needs to be included. The bugfix adds additional code for cross-compiling with mingw32, which is what we are doing.  It requires the variable Q_CC_MINGW to be defined.

cd /opt/src/qt5.2.0-beta1-win32/qtdeclarative/src/qml/jsruntime

wget https://codereview.qt-project.org/cat/67781%2C3%2Csrc/qml/jsruntime/qv4dateobject.cpp

Build in situ

cd /opt/src/qt5.2.0-beta1-linux
./configure --prefix=/opt -opensource -confirm-license \
  -shared -developer-build -debug \
  -nomake examples -no-compile-examples \
  -accessibility | tee config.log 2>&1
 

make -j 4 | tee compile.log 2>&1

make install | tee install.log 2>&1


QT Creator 3 for Linux


Download tarball

wget http://sourceforge.net/projects/qtcreator.mirror/files/Qt%20Creator%203.0.0/qt-creator-opensource-src-3.0.0.zip/download

Unpack

Build in situ



cd qt-creator-3.0.0


make install into /opt

make install INSTALL_ROOT=/opt | tee install.log 2>&1

QT5 for Windows (Static Linking)


Use same tarball as QT5 for Linux
Unpack

Build in situ
Defining Q_CC_MINGW

./configure -static -release -opensource -confirm-license \
      -make libs -nomake tests -nomake examples \
      -no-compile-examples \
      -accessibility \
      -no-sse -no-sse2 -no-sse3 -no-ssse3 -no-sse4.1 -no-sse4.2 \
      -no-avx -no-avx2 -no-neon -no-mips_dsp -no-mips_dspr2 \
      -no-gif \
      -optimized-qmake -reduce-relocations -no-pch \
      --xplatform=win32-g++ \
      -device-option CROSS_COMPILE=/usr/bin/i686-w64-mingw32- \
      --prefix=/opt/i686-w64-mingw32 > qtconfig.log 





make install into /opt/i686-w64-mingw32

Cross Compiling 101 

In order to cross-compile in general, all you need to do is set an environment variable: ${CROSS_COMPILE} to i686-pc-mingw32- and any time you need to call a compiler (e.g. gcc), use ${CROSS_COMPILE}gcc - this way, if the environment variable is set, the cross-compiler is used, and if it is not set, the native compiler is used.

A note about qmake

Qmake is a builder of makefiles (which in turn are used to build your qt application). Qmake takes an argument, which tells it what the target application is (e.g. a windows 32 program).. Qmake also converts the GUIs you've drawn into cpp source code for compilation.

There are some paths / libraries / definitions hard-coded into qmake, so if you use the default system-installed qmake, which is usually installed in /usr/bin, the files it creates will not build windows applications, even if you point it at the correct configuration file, as they will try to link your windows object files with linux libraries.

This is why we make a short-cut in /opt/bin called i686-pc-mingw32-qmake. So you can use ${CROSS_COMPILE}qmake in your scripts / programs.

QT Cross-Compiling 101

OK, so lets assume that you've been writing a QT application for Linux using QtCreator, and you've got all of your source code files, and your project xxx.pro file.

All you need to do is invoke the correct qmake, tell it what your source is, and what your target is, and sit back!

PATH=/opt/bin:$PATH
cd output-build-directory
${CROSS_COMPILE}qmake -spec win32-g++ path-to-project/xxx.pro

Qmake Gotchas

  • Make sure you use the correct qmake program.
  • The Mingw32 windows libraries are built as static, but as long as you use the correct qmake, this should be transparent to you.

Bugs / Problems / Gotchas

General

  1. The configure script needs a double hyphen for the prefix and xplatform options, not a single hyphen as indicated in all of the documentation. 

Cross-Compiling qtactiveqt/src/tools/idc

When cross-compiling the IDC on the Linux platform for the Win32 target, the Makefile is built to call up the Linux g++ compiler, and then asked to build a file which includes the windows.h header, which is clearly wrong.

My workaround was to replace the idc Makefile with a file containing the lines:

all:

install:

Downloading from git: 

My first attempt at building was from the git repository, but I changed to using the tarball because I was hampered by a number of errors.
  1. Git stable releases.  I found that the -developer-build option did not copy the private headers across on installation, and I could not get the QT_PRIVATE_HEADERS parameter for qmake to work, so when I tried to build qt-creator, it always failed.
  2. The private headers seems to be going through a change at the moment, and qt-creator expects them to be in a module called XXXX, but that module does not exist in the QT5 file set.