ariya.io About Talks Articles

Cross Compiling with Docker on WSL 2

4 min read

Now that WSL 2 packs a true Linux kernel and supports Linux containers (via Docker), it can be a perfect setup to perform application cross compilations.

While Docker for Windows will soon support WSL 2, it is just easier to use WSL 2 as is, install Docker, and use it. In case you are new to the wonderful world of WSL, check the documentation to have it installed. Note that for WSL 2, you need to be using Windows Insiders for now. Update: No need to use Windows Insiders anymore as WSL 2 is now included with Windows 10 June 2020 update (version 2004).

WSL Initial Screen

Once Ubuntu 18.04 is installed (the default for WSL), you can verify that it is indeed working:

$ uname -a
Linux XPS 4.19.43-microsoft-standard #1 SMP Mon May 20 19:35:22 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.2 LTS"

If uname -a returns something like this instead:

$ uname -a
Linux XPS 4.4.0-18362-Microsoft #1-Microsoft Mon Mar 18 12:02:00 PST 2019 x86_64 x86_64 x86_64 GNU/Linux

then you still got the original WSL, not WSL 2. Refer to the documentation again on how to enable WSL 2 instead.

Next step is to install Docker. There are tons of tutorial on this subject, see for instance this guide on Digital Ocean. Once it is properly installed, start the daemon by running:

$ sudo service docker start

At this point, it is likely wise to add yourself to the proper group, to avoid using sudo all the time:

$ sudo usermod -aG docker $USER

Before we do something crazy, let us ensure that Docker works:

$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

Since the topic is cross-compilation, let us assume there is a rather simplistic Hello world in ANSI C/C90/C99:

#include <stdio.h>

int main(int argc, char** argv) {
  printf("Hello, world!\n");
  return 0;
}

with the following Makefile:

.POSIX:
.SUFFIXES:

CFLAGS = -O -Wall -std=c90

all: hello
hello: hello.o
	$(CC) $(LDFLAGS) -o hello hello.o

.SUFFIXES: .c .o
.c.o:
	$(CC) $(CFLAGS) -c $<

Cross-compilation with Dockcross

Run a quick test on your host system (assuming gcc and friends are already available):

$ gcc -o hello hello.c
$ ./hello
"Hello, world!"

What about compiling this marvelous C program for ARM? Another tool we would need is this excellent project, Dockcross, a Docker-based setup for painless cross-compilation.

$ git clone https://github.com/dockcross/dockcross.git && cd dockcross
$ docker run --rm dockcross/linux-armv7 > ./dockcross-linux-armv7
$ chmod +x ./dockcross-linux-armv7

Cross-compile the program for ARM v7 (this is for 32-bit ARM architecture):

$ ./dockcross-linux-armv7 bash -c "$CC -o hello hello.c -static"

Or better, use the Makefile, and hence the simplified command:

$ ./dockcross-linux-armv7 make LDFLAGS=-static
/usr/xcc/armv7-unknown-linux-gnueabi/bin/armv7-unknown-linux-gnueabi-gcc -O -Wall -std=c90 -c hello.c
/usr/xcc/armv7-unknown-linux-gnueabi/bin/armv7-unknown-linux-gnueabi-gcc -static -o hello hello.o

As the evidence that this is not a native host binary anymore, verify the freshly baked executable:

$ file ./hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, for GNU/Linux 4.10.8, with debug_info, not stripped

Whoa! That was indeed quite painless.

How do we execute this file to ensure that it is working as expected? QEMU, another great open-source project, to the rescue! First make sure that it is there (we need the qemu-user package).

$ sudo apt -y install qemu-user

And now the fun time: running the ARMv7 binary we built earlier:

$ qemu-arm ./hello
"Hello, world!"

Of course, this is just one target architecture. Amazingly, Dockcross supports a wide range of cross-compilation targets, including for MIPS and PowerPC architectures, or even building for Windows and WebAssembly.

Here is how to build a Windows executable in 3 easy steps:

$ docker run --rm dockcross/windows-static-x86 > ./dockcross-windows-static-x86
$ chmod +x ./dockcross-windows-static-x86
$ ./dockcross-windows-static-x86 make
/usr/src/mxe/usr/bin/i686-w64-mingw32.static-gcc -O -Wall -std=c90 -c hello.c
/usr/src/mxe/usr/bin/i686-w64-mingw32.static-gcc  -o hello hello.o
$ file ./hello
./hello: PE32 executable (console) Intel 80386, for MS Windows

And thanks to the excellent Windows-Linux interoperability of WSL, you can also run the executable directly.

$ ./hello
"Hello, world!"

In summary, even from a shiny Windows laptop, the combination of WSL 2, Docker, dockcross, and QEMU allows us to cross-compile apps for a number of processor architecture and operating system combinations.

Now, what kind of great apps do you plan to build today?

Related posts:

♡ this article? Explore more articles and follow me Twitter.

Share this on Twitter Facebook