When working on command-line utilities which can be useful for various platforms, from Windows on x86 to Linux on MIPS, the existence of a cross-compilation is highly attractive. A number of different binaries can be constructed conveniently from a single, typically powerful host system.
Alpine Linux popularizes the use of musl a no-frills C standard library for Linux. According to its website:
musl is lightweight, fast, simple, free, and strives to be correct in the sense of standards-conformance and safety.
In addition, thanks to Zach van Rijn, we have a collection of static toolchains based on musl at musl.cc at our disposal. The number of supported systems is rather mind blowing, you got everything from the usual i686 to MIPS to Microblaze and many others. https://github.com/ariya/fastlz/actions
As I search for a viable alternative to the cross-compilation method based on Dockcross (see my previous blog post: Cross Compiling with Docker on WSL 2), musl.cc fits the requirements nicely. I am in the process of migrating the continuous integration of FastLZ, my implementation of byte-aligned LZ77 compression algorithm, to be completely based on musl.cc.
Here is a quick walkthrough. As long as you are on Linux x86-64, you can follow along easily (and yes, this also works great on WSL, Windows Subsystems for Linux). As a reference, we will use the simplest ANSI C/C90 program available at github.com/ariya/hello-c90.
First and foremost, we need QEMU so we can test our binaries not native to x86-64. For convenience, GNU Make is also necessary.
$ sudo apt install -y qemu-users make
After that, let us grab the Hello C90 program:
$ git clone https://github.com/ariya/hello-c90.git $ cd hello-c90
For a start, let us try to produce MIPS64 binary of our little Hello C90 program. Thus, we ought to grab the toolchains first, weighing at about 90 MB.
$ curl -O https://musl.cc/mips64-linux-musl-cross.tgz $ tar xzf mips64-linux-musl-cross.tgz
To ensure that this fresh cross-compiler works, do a quick sanity check:
$ ./mips64-linux-musl-cross/bin/mips64-linux-musl-gcc --version mips64-linux-musl-gcc (GCC) 9.3.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This looks good! Now we can compile our Hello C90 program statically:
$ make CC=./mips64-linux-musl-cross/bin/mips64-linux-musl-gcc LDFLAGS=-static ./mips64-linux-musl-cross/bin/mips64-linux-musl-gcc -O -Wall -std=c90 -c hello.c ./mips64-linux-musl-cross/bin/mips64-linux-musl-gcc -static -o hello hello.o
Checking the resulting binary should give the following:
$ file ./hello ./hello: ELF 64-bit MSB executable, MIPS, MIPS-III version 1 (SYSV), statically linked, not stripped
It is exactly what we want! To run the executable:
$ qemu-mips64 ./hello Hello, world! From C90 with love...
Now, if you are doing this on WSL, or generally have a Windows machine available elsewhere, there is this fun activity of cross-compiling the above app for Windows, without the need for any Windows compiler and SDK. Same steps as before:
$ curl -O https://musl.cc/x86_64-w64-mingw32-cross.tgz $ tar xzf x86_64-w64-mingw32-cross.tgz $ make CC=./x86_64-w64-mingw32-cross/bin/x86_64-w64-mingw32-gcc LDFLAGS=-static $ file ./hello.exe ./hello.exe: PE32+ executable (console) x86-64, for MS Windows
To really test it, just bring
hello.exe to Windows and it is going to run as expected.
For more details and elaborated examples, check the collection of workflows YAML files of this Hello C program.
Combined with the continuous integration system of your choice, whether it is DIY via Jenkins or using one of the many services out there (GitHub Actions, Azure Pipelines, Travis CI), creating binaries for various operating systems becomes easier than ever!