Sunday, 20 March 2011

Cross-compilation Adventures

At work we were recently asked to produce a Windows 64-bit build of our library, however our existing build system is not able to build 64-bit DLLs. The server is a Debian x64 machine running trac, subversion and hudson.

The automated build process is based on a combination of nant and premake scripts (we use premake to generate code::blocks project files for our development machines and makefiles for the build machine). The scripts use gcc/g++ to build the Linux version of the library (.so) for our kiosks and they use MinGW to cross-compile the library for Windows x86.

MinGW is just the standard version installed from the repositories and is not currently capable of producing 64-bit binaries, so we needed to switch that section of our toolchain.

The rest of this post documents how we configured a new toolchain capable of 64-bit builds and makes use of the MinGW-w64 project.

Install MinGW-w64

The MinGW-w64 project provides two toolchains, one targetting Win32 builds and one targetting Win64 builds.

Download the Win32 toolchain and extract to /opt/mw32
Download the Win64 toolchain and extract to /opt/mw64

In /usr/local/bin create the following helper scripts (this is optional but we found them useful):

mw32-ar :
#!/bin/bash

PATH="/opt/mw32/bin/:$PATH"
i686-w64-mingw32-ar $@

mw32-gcc :
#!/bin/bash

PATH="/opt/mw32/bin/:$PATH"
i686-w64-mingw32-gcc -m32 $@

mw32-g++ :
#!/bin/bash

PATH="/opt/mw32/bin/:$PATH"
i686-w64-mingw32-g++ -m32 $@

mw64-ar :
#!/bin/bash

PATH="/opt/mw64/bin/:$PATH"
x86_64-w64-mingw32-ar $@

mw64-gcc :
#!/bin/bash

PATH="/opt/mw64/bin/:$PATH"
x86_64-w64-mingw32-gcc -m64 $@

mw64-g++ :
#!/bin/bash

PATH="/opt/mw64/bin/:$PATH"
x86_64-w64-mingw32-g++ -m64 $@


Building wxWidgets

Our library is built using wxWidgets since it is fast, easy to use and provides cross-platform access to string functions, file handling, XML parsing, image loading/saving and so on. We statically link wxWidgets and so the following describes how to build 32-bit and 64-bit static versions of wxWidgets.

Download wxWidgets >= 2.9 since 2.8.x does not seem to build to 64-bit successfully. As of writing there is also a problem with legacy references that break the build and the simplest way to fix this is to hack the "configure" and "configure.in" scripts removing "-lwctl3d32".

Open a shell in the wxWidgets root folder (having extracted it somewhere) and run the following. I recommend going away and having a nice cup of tea while the make is running since it will take a while.

$ PATH=/opt/mw32/bin/:$PATH ./configure prefix=/opt/mw32/mingw --host=i686-w64-mingw32 --enable-unicode --disable-monolithic --disable-shared --build=`./config.guess`
$ PATH=/opt/mw32/bin/:$PATH make
$ sudo make install

$ make clean

$ PATH=/opt/mw64/bin/:$PATH ./configure prefix=/opt/mw64/mingw --host=x86_64-w64-mingw32 --enable-unicode --disable-monolithic --disable-shared --build=`./config.guess`
$ PATH=/opt/mw64/bin/:$PATH make
$ sudo make install

As of writing the make install does not copy the headers to a nice "wx" folder and so the simplest solution is to symlink them:
$ cd /opt/mw32/mingw/include
$ sudo ln -s wx-2.9/wx ./
$ cd /opt/mw64/mingw/include
$ sudo ln -s wx-2.9/wx ./

Since our server is also used to build Linux versions of the library, it has the wxWidgets developmental packages installed from the repositories and therefore also has the "wx-config" tool available. To make this tool aware of our cross-compilation configurations we simply need to symlink the scripts to the appropriate location:
$ cd /usr/lib/wx/config
$ sudo ln -s /opt/mw32/mingw/lib/wx/config/i686-w64-mingw32-msw-unicode-static-2.9 ./
$ sudo ln -s /opt/mw64/mingw/lib/wx/config/x86_64-w64-mingw32-msw-unicode-static-2.9 ./


Usage

Building something using the appropriate wxWidgets libraries is now a case of passing the appropriate wx-config command to the compiler:

32-bit build options:
`wx-config --host=i686-w64-mingw32 --version=2.9 --cxxflags`
32-bit link options:
-static-libstdc++ -static-libgcc `wx-config --host=i686-w64-mingw32 --version=2.9 --libs`
64-bit build options:
`wx-config --host=x86_64-w64-mingw32 --version=2.9 --cxxflags`
64-bit link options:
-static-libstdc++ -static-libgcc `wx-config --host=x86_64-w64-mingw32 --version=2.9 --libs`

If this is configured in a makefile then the helper scripts can be used to tell make which toolchain to use, e.g.:
$ make config=cross CC=mw32-gcc CXX=mw32-g++ AR=mw32-ar

Congratulations, you should now have a Linux system capable of cross-compiling to both 32-bit and 64-bit Windows binaries.


Update

I have since found that when setting up a build (using tools such as premake) it is simpler if the helper scripts act in the same way as the native build tools and handle the -m32 and -m64 flags appropriately. The following is an example of the mw-g++ script I am now using to replace the previously defined mw32-g++ and mw64-g++ :

#!/bin/bash

target="x32"
for arg in $*; do
        if [ "$arg" = "-m64" ]; then
                target="x64"
                break
        fi
done

if [ "$target" = "x64" ]; then
        PATH="/opt/mw64/bin/:$PATH"
        x86_64-w64-mingw32-g++ $@
else
        PATH="/opt/mw32/bin/:$PATH"
        i686-w64-mingw32-g++ $@
fi

No comments:

Post a Comment