Cross-compilation notes

I'll keep updating this article as I go, just to put stuff in all the same place.


Cross-compiling for Linux

I'm pretty sure it's possible to cross-compile for Linux on other OSes, seeing as everything is open-source, but I have never done it - and why would I want to? Linux is the friendliest to build on, so it's better to use it as a build environment.

Cross-compiling for OSX

I've had success using xchain to compile for OSX from Linux, but it's a really long and painful experience.

The steps involve:

Basically, information that's out there gets outdated almost as soon as it's published, so even if I did put out a step-by-step guide, it would be outdated in no time.

I remember problems with libstdc++ for C++ compilation. I haven't tried building a cross LLVM-GCC yet.

Compiling for both 32-bit & 64-bit means two separate compilations, unlike natively on OSX where you can just use xcodebuild to get fat executables.

In summary, not sure whether it's worth the trouble, it's long to get running, fragile, and involves a lot of hand-holding.

Cross-compiling for Windows

mingw and msys on Windows are basically 'GCC on Windows', which works fine most of the time, and even includes tools like bash, some Unix utils (even vim!), but let me state that once and for all: mingw on Windows is slow as fuck.

A much better solution, in fact, is to use mingw on Linux (or others) to produce Windows executables. Apparently, mingw-w64 is a more up-to-date version (fork?) that also supports building 64-bit Windows executables.

Debian testing & upper contains a mingw-w64 package that'll get you started.

Cross-compilation means:

In the next sections, I'll just assume we're cross-compiling from Linux to Windows using mingw-w64.


Autotools philosophy

Autotools' principle is that:


About out-of-tree builds:

I haven't yet found a case where ./ or ./ are actually needed - if you have recent autotools installed, it's fine.

Cross-compiling for Windows with Autotools

When running configure, use the --host=HOST option. There's also --build and others, but I'm confused about those and never had to use them except when cross-compiling GCC for another platform. There's a reasonably good explanation in the automake docs.

Example HOST values:

It's a good idea to cross-compile into a prefix so you don't mix native and foreign libraries. So most of the times, the build process might look something like:

mkdir build
cd build
../yourpackage/configure --host=i686-w64-mingw32 --prefix=$HOME/i686-w64-mingw32-prefix
make install

Side-note about temporary programs

Some packages compile executables that are run during the build process - such as tests, or libjit's opcode generator.

Sometimes, they'll be configured such as these will be built for the build architecture (Linux) instead of the host architecture (Windows), which is perfect.

But if they're not, chances are you'll get an error about some missing executable and find an .exe in its place.

And if you're really unlucky, the package's files will invoke directly util instead of util$(EXEEXT) - you might need to fix that too.

To be able to run .exe directly from Linux, on Debian you can do the following:

sudo apt-get install wine binfmt-support
sudo /usr/sbin/update-binfmts --package wine --install wine /usr/bin/wine --magic MZ

If you're lucky, the temporary programs will run just fine and everything will work out. If it doesn't, time to read the autotools docs to know how to generate those tools for the build platform, not the host platform.

If running these with wine messes up your terminal (when pressing enter you see '^M'), running reset should fix it in no time.

Cross-compiling for OSX with Autotools

Same principle, use --host=i686-apple-darwin11 or whatever your cross toolchain is.


CMake philosophy

CMake is basically a build file generator - it can generate for:

CMake usage

The cmake command takes as argument the folder of a CMake project, ie. where the CMakeLists.txt file lives (casing may differ).

That means out-of-tree builds are possible as well. To specify variables like the prefix, use the -D option. Example run:

mkdir build
cmake ../yourpackage -DCMAKE_INSTALL_PREFIX=$HOME/prefix
make install

In fact, out-of-tree builds are the recommended way to go for CMake projects in general. Sometimes the build folders are created inside the source distribution, but if it's just a dependency and not your project, it's a bad habit in my opinion.

Cross-compiling for Windows with CMake

As far as I can tell, CMake has no built-in cross-compiling support like autotools does. However, it does support toolchain files. Here's how a toolchain file for mingw-w64 might look like:






Cross-compiling for OSX with CMake

Same principle, you can find toolchains on the internet.

Runtime dependencies

Find dynamically linked libraries on Linux

To know which dynamic libraries a Linux executable is linked against, one can simply run ldd EXEC

$ ldd ~/Dev/ooc/jit-tests/libjit1 (0x00007fff8c3fe000) => not found => /lib/x86_64-linux-gnu/ (0x00007f9d5468a000)
        /lib64/ (0x00007f9d54a5a000)

ldd is nice, because it respects LD_LIBRARY_PATH and works recursively - as long as it can find all the dependencies:

$ LD_LIBRARY_PATH=~/Dev/ooc/jit-tests/prefix/lib ldd ~/Dev/ooc/jit-tests/libjit1 (0x00007fff2fba2000) => ~/Dev/ooc/jit-tests/prefix/lib/ (0x00007f99eaa1b000) => /lib/x86_64-linux-gnu/ (0x00007f99ea64d000) => /lib/x86_64-linux-gnu/ (0x00007f99ea42f000) => /lib/x86_64-linux-gnu/ (0x00007f99ea22b000) => /lib/x86_64-linux-gnu/ (0x00007f99e9f2a000)
        /lib64/ (0x00007f99eacac000)

(Here, libjit was in a local prefix, and libm/libdl were dependencies of libjit, not the executable itself).

Find dynamically linked libraries on OSX

On OSX, use otool -L:

$ otool -L bin/pug
  ~/Dev/pug/pug/deps/prefix/lib/libcollections.0.dylib (compatibility version 1.0.0, current version 1.0.0)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

A few notes about otool:

Find dynamically linked libraries on Windows

On Windows, one can use the Dependency Walker to figure out which DLLs are loaded - it'll even go so far as monitor the executable while it's running to know which DLLs are not linked against, but still loaded dynamically (with LoadLibrary).

On Linux, you can run i686-w64-mingw32-objdump -p executable.exe - the output is a bit verbose, so you might want to pipe that into grep 'DLL Name'

$ i686-w64-mingw32-objdump -p bin/pug.exe | grep 'DLL Name'
        DLL Name: KERNEL32.dll
        DLL Name: msvcrt.dll
        DLL Name: USER32.dll

And that's it.

If you liked what you saw, please support my work!

Github logo Donate on GitHub Patreon logo Donate on Patreon

Latest video View all

video cover image
C++ vs Rust: which is faster?

I ported some Advent of Code solutions from C/C++ to Rust, and used the opportunity to compare performance. When I couldn't explain why they performed differently, I had no choice but to disassemble both and look at what the codegen was like!

Watch now
Looking for the homepage?
Another article: A terminal case of Linux