This is a HOW-TO on writing Windows software, including GUI
software, without using a Windows box at all except for final
testing. Among other benefits, this lets you develop in the simple,
testable, scriptable Linux environment; it means you don’t have
to forever check things in and out of source-control to test whether
you’ve broken the other platform’s builds; and it’s
also handy if you’ve got a powerful Linux box but only a feeble
Windows box.
Well, actually, it isn’t really a HOW-TO, in the grand
tradition; it’s more of a “WHETHER-TO”. That’s
because it doesn’t always go into enough detail to let you
reconstruct everything described, but it at least tells you that
it’s possible — so that if you’re wondering
whether-to try and develop Windows software in this fashion,
you can be reassured, before setting off in that direction, that the
destination is attainable.
There are a lot of bits and pieces to set up a complete
cross-development and cross-test environment. We should all be hugely
grateful for the vast amount of development effort put in by the GCC,
binutils, Mingw, Wine, Qt and other projects to enable the setup
described in this one small blog post.
1. A cross-compiler toolchain targetting Mingw32
apt-get install mingw32-binutils mingw32-runtime mingw32
Alternatively, configure and install GNU binutils for target
“i586-mingw32”; install the headers from the mingw32-runtime
package (which you can’t build yet); configure and install a
Stage 1 cross-GCC with
--target=i586-mingw32 --enable-languages="c"
--enable-threads=win32 --disable-libmudflap --disable-libssp
--enable-__cxa_atexit --enable-sjlj-exceptions
--disable-win32-registry --with-gnu-as --with-gnu-ld
as extra configure arguments; configure and install the
w32api
package; configure and install the mingw32-runtime package; and
finally configure and install a Stage 2 fully-working cross-GCC with
--target=i586-mingw32 --enable-languages="c,c++"
--enable-threads=win32 --disable-libmudflap --disable-libssp
--enable-__cxa_atexit --enable-sjlj-exceptions
--disable-win32-registry --with-gnu-as --with-gnu-ld
--disable-libstdcxx-pch
--enable-libstdcxx-allocator=new
as configure arguments. Note that, supposing you want everything to
live in
/usr/local/i586-mingw32, you need to give GCC and
binutils “
--prefix=/usr/local”, and everything
else “
--prefix=/usr/local/i586-mingw32”.
Except for using w32api and mingw-runtime instead of glibc, this
isn’t that different from how to build a cross-compiler for any
other target.
If you want to use the exact same versions of everything I did,
it’s binutils 2.19.51.0.2, mingw-runtime 3.14, w32api 3.11, and GCC 4.3.3.
A recently-invented third alternative, which I haven’t tried,
is the “official” cross-hosted Mingw build tool scripts,
which are available on the
Mingw Sourceforge page.
2. A native pkgconfig for your cross-compiled libraries
Cross-compiled libraries for Mingw32 will put their pkgconfig
“.pc” files in
/usr/local/i586-mingw32/lib/pkgconfig. In order for configure
scripts targetting Mingw32 to find them, you’ll need a
“cross-pkgconfig” — but one which, like a
cross-compiler, is built for the build platform, not the target
platform. If it’s named using the target prefix, as if it were
part of the cross-compiler — i.e., in our case,
i586-mingw32-pkgconfig — configure scripts will use it
to determine which cross-compiled libraries are present.
Configure pkgconfig 0.23 with:
--with-pc-path=/usr/local/i586-mingw32/lib/pkgconfig --program-prefix=i586-mingw32-
(yes, that ends with a hyphen).
3. Cross-compiled versions of all the libraries you use
How hard it is to arrange for these, depends a lot on each
individual library. In theory all you should need to do is configure
the library with “--prefix=/usr/local/i586-mingw32
--host=i586-mingw32”, but in practice very few libraries do
the Right Thing with that alone. (Honourable mentions here go to
libxml2 and taglib.)
Other things you might have to do to more recalcitrant libraries
include: setting CC=i586-mingw32-gcc (and sometimes
CXX, AR and/or RANLIB similarly); disabling
parts of the library (for libcdio use
--disable-joliet --disable-example-progs
--without-iso-info --without-iso-read --without-cd-info
--without-cd-drive --without-cd-read
to disable all the example programs) — or, if the worst comes to
the worst, actually patching out bits of the library. I had to do that
to make taglib compile as a static library.
Boost, as usual, presents the most
challenging fight you’ll have with a build system. Here, without
further commentary, is the Makefile snippet needed to cross-compile
Boost for Mingw32; $(BUILD) is the build directory and
$(PREFIX) is where to install the result —
/usr/local would match a toolchain built as described above:
cross-mingw32-boost:
mkdir -p $(BUILD)/cross-mingw32-boost
tar xjf boost-*bz2 -C $(BUILD)/cross-mingw32-boost
cd $(BUILD)/cross-mingw32-boost/* \
&& ./bootstrap.sh --prefix=$(PREFIX)/i586-mingw32 \
--libdir=$(PREFIX)/i586-mingw32/lib \
--includedir=$(PREFIX)/i586-mingw32/include \
&& echo \
"using gcc : : i586-mingw32-g++ : <compileflags>-mthreads <linkflags>-mthreads ;" \
> jtl-config.jam \
&& ./bjam -q --layout=system variant=release \
link=static threading=multi --without-iostreams \
-sGXX=i586-mingw32-g++ --without-python \
threadapi=win32 --user-config=jtl-config.jam \
&& sudo rm -rf $(PREFIX)/i586-mingw32/include/boost \
&& sudo ./bjam -q --layout=system variant=release \
link=static threading=multi --without-iostreams \
-sGXX=i586-mingw32-g++ --without-python \
threadapi=win32 --user-config=jtl-config.jam install
for i in $(PREFIX)/i586-mingw32/lib/libboost_*.a ; do \
sudo i586-mingw32-ranlib $$i ; \
done
rm -rf $(BUILD)/cross-mingw32-boost
Again, if you’d like a checklist of successes I’ve had
here, then with greater or lesser effort it’s proved possible to
make cross-compiled versions of zlib 1.2.3, Boost 1.39.0,
libcdio 0.80, taglib 1.5, and libxml2 2.6.30.
4. Wine Is Not an Emulator
Configure and install Wine
1.0.1. Admirably, this just works out of the box, though if your Linux
box is 64-bit, you’ll need the 32-bit versions of its dependent
libraries installed.
Having got this far, you should have all you need to compile, link,
and test Windows programs. Of course, they do have to be Windows
programs; Mingw is not Cygwin, and your program needs to be compatible
with real, proper Windows including <windows.h>,
WSAEventSelect, CreateWindowEx and all that jazz
— plus, of course, the Windows text-encoding
and file-naming
rules.
Indeed, depending on how your project’s unit-tests are set
up, you can probably run most of them, too, under Wine. Just arrange
for them to be invoked using Wine: instead of
“run-test various-args”, execute instead
“wine run-test various-args”. In some
situations, this alone would justify the effort of setting up a
cross-development environment: the ability to know, before checking in
code on Linux, that it passes all its tests both on Linux and
Windows.
5. Qt
Trolltech’s, now Nokia’s, Qt framework has for a while been a really
good way of writing open-source Linux GUI applications without getting
bogged down in X11 or other unhelpful toolkits. Originally Qt was only
available for free on the X11 platform, but subsequently the Windows
(and even MacOS) versions were also made available to the free
software community, and more recently still have been relicensed under
the GNU LGPL. This makes it not only a good way of writing open-source
Linux applications, but both open-source and proprietary applications
on Linux and Windows (and, again, MacOS too).
So it would be handy if a cross-compiled version of Qt could be
used to write and test Windows versions of Linux Qt applications using
Wine. The problem is, Qt’s sources are huge — twice
the size of KOffice, three times the size of Firefox, five times the
size of GLib+GTK put together — which is enough to put a fellow
off trying to cross-compile it.
But fortunately, Trolltech supply a binary installer for the
Windows development libraries — an installer which works
under Wine. So, download
qt-win-opensource-x.y.z.exe and run it under
Wine. Pick an installation directory (for instance,
/usr/local/i586-mingw32/qt — or, in Mingw-speak,
Z:\usr\local\i586-mingw32\qt), and let it install. When it
asks for a Mingw installation, give it your cross-compiler’s
prefix directory (e.g. /usr/local/i586-mingw32); it’ll
moan, but let you ignore the moaning and install anyway (do so).
You then need to arrange for Qt’s pkgconfig files to be
available to the cross-compiler. The Win32 installation of Qt
doesn’t have pkgconfig files, but you can modify the ones from a
native Linux installation of the same version of Qt. To do this, issue
the following commands (as root):
# cd /usr/lib/pkgconfig (Or wherever your existing QtCore.pc is)
# for i in Qt*.pc ; do sed \
-e 's,^prefix=.*,prefix=/usr/local/i586-mingw32/qt,' \
-e 's,-I.*/include,-I/usr/local/i586-mingw32/qt/include,' \
-e 's,-l[^ ]*,&4,' \
< $i > /usr/local/i586-mingw32/lib/pkgconfig/$i ; done
The three
sed replacements fix up the
prefix= lines
in the
.pc files, then fix up stray
-I directives in
the
CFLAGS lines that don’t use the defined
prefix, then finally take account of the extra versioning
present in the Windows filenames (instead of
QtCore.lib,
Windows has
QtCore4.lib, and similarly across the whole
framework).
(It was getting a development version of Chorale’s Qt GUI more-or-less
up under Wine, and thus bringing Win32 into its sights for the first
time, that prompted the writing of this blog post.)
6. The Promised Land
So there you (hopefully, by now) have it. A well-behaved program,
such as Chorale, should mostly configure, build, unit-test, and run
its Windows version straightforwardly on a Linux box.
Naturally, you still need to use a real Windows box for final
testing — not everything that can happen on a real Windows box
can be modelled inside Wine, and nor is everything necessarily as
compatible between different Windows versions as you’d hope. But
by marginalising Windows out of its own development process until as
late as possible, the rest of the development can be eased, indeed
accelerated, by only having to develop on a single platform.