This post is about how to cross-compile Go applications for other architectures. Cross-compiling Go applications could get quiet challenging when we have dependencies to modules which depend on cgo
and on C
-libraries.
Update: I’ve added a section at the end.
Introduction
I’m currently writing a small Go application which has a gui implemented by using github.com/gotk3/gotk3 as library for gtk bindings. Compiling on a Raspberry Pi is quiet slow compared to my laptop, which is already running for some years.
Why don’t I just cross-compile the application on my laptop
Yes cross-compiling could finally speed up my development workflow and is quiet easy for Go when there are no dependencies to cgo or even libraries used by cgo.
I’d like to share the different ways how to cross-compile Go for the Raspberry Pi.
Compiling without cgo
Cross-compiling Go-applications for other architectures like aarch64
is quiet easy. Consider the following simple hello-world
Go application:
You can easily cross-compile this for a Raspberry Pi and run it there:
$ env GOARCH=arm64 GOOS=linux go build -o hello-world main.go
$ scp hello-world rpi:~/hello-world
$ ssh rpi ./hello-world
hello world
As long as we are writing applications which do not need cgo this works very well.
Compiling with cgo
Let’s try a small example including cgo::
Let’s try to build it for the Raspberry Pi the same way we did without cgo:
$ env GOARCH=arm64 GOOS=linux CGO_ENABLED=1 go build -o cgo main.go
# runtime/cgo
gcc: error: unrecognized command line option ‘-marm’; did you mean ‘-mabm’?
This fails because go tries to use the default gcc
compiler of our operating system. But in this case we need to use a compiler which builds for arm. On archlinux we can install aarch64-linux-gnu-gcc
and also pass the name of the compiler to the go build
-command:
$ export CC=aarch64-linux-gnu-gcc
$ GOARCH=arm64 GOOS=linux CGO_ENABLED=1 go build -o cgo main.go
$ scp hello-world rpi:~/hello-world
$ ssh rpi ./cgo
hello world cgo
Adding libraries for cgo
As I noted at the beginning, I want to use github.com/gotk3/gotk3.
So let’s implement a small user interface and cross-compile it for the Raspberry Pi:
$ export CC=aarch64-linux-gnu-gcc
$ env GOARCH=arm64 GOOS=linux CGO_ENABLED=1 go build -o cgo main.go
# github.com/gotk3/gotk3/glib
/usr/lib/gcc/aarch64-linux-gnu/8.2.0/../../../../aarch64-linux-gnu/bin/ld: cannot find -lgio-2.0
/usr/lib/gcc/aarch64-linux-gnu/8.2.0/../../../../aarch64-linux-gnu/bin/ld: cannot find -lgobject-2.0
/usr/lib/gcc/aarch64-linux-gnu/8.2.0/../../../../aarch64-linux-gnu/bin/ld: cannot find -lglib-2.0
collect2: error: ld returned 1 exit status
So what went wrong here? Let’s assume the necessary libraries are installed on the x86 system we’re working on. The problem is that these are not for the cross-compile toolchain.
We also have a Raspberry Pi running the aarch64
architecture and having the libraries installed. Let’s mount the file system of the sdcard which is normally used by the Raspberry Pi and pass the path to the go compiler:
$ mount /dev/sdc2 /mnt
$ export CC=aarch64-linux-gnu-gcc
$ export CGO_CFLAGS="--sysroot=/mnt"
$ export CGO_LDFLAGS="--sysroot=/mnt"
$ env GOARCH=arm64 GOOS=linux CGO_ENABLED=1 go build -o cgo main.go
That’s it.
Of course we could copy the content of the Raspberry Pi’s file system somewhere on our disk so we don’t have to mount the sdcard all the time and keep the Raspberry Pi running while we are cross-compiling.
cgo & pkg-build
Note: this section was added later on
After writing this blog post and compiling for aarch64
I recognized that the GPIO support on the Raspberry Pi at aarch64
does not work well. Afterwards I tried to cross-compile my application for armv7
.
Long story short: I had to learn that Go is using its own pkg-config
(see docs) to gather build dependency information.
To cross-compile my application for armv7
I have used the precompiled toolchain from archlinuxARM and added the environment variables PKG_CONFIG_DIR
, PKG_CONFIG_LIBDIR
and PKG_CONFIG_SYSROOT_DIR
.
Now I’m compiling successfully for armv7
using the following command:
$ mount /dev/sdc2 /mnt
$ export CC=aarch64-linux-gnu-gcc
$ export CGO_CFLAGS="--sysroot=/mnt"
$ export CGO_LDFLAGS="--sysroot=/mnt"
$ export CC="path/to/toolchain/arm-unknown-linux-gnueabihf/bin/arm-unknown-linux-gnueabihf-gcc
$ env \
PKG_CONFIG_DIR="" \
PKG_CONFIG_LIBDIR="/mnt/usr/lib/pkgconfig:/mnt/usr/share/pkgconfig"\
PKG_CONFIG_SYSROOT_DIR="/mnt" \
GOARCH=arm \
GOARM=7 \
GOOS=linux \
CGO_ENABLED=1 \
go build -o app ./
Addition
Archlinux directly offers the aarch64-linux-gnu-gcc
as a package but this is not the case for the armv7
or other architectures and I don’t know whether or not the compilers are easily available on other distributions. crosstool-ng
is able to create cross-compiling toolchains for different architectures. There are prebuilt cross-compilers available at the Cross-Compiling documentation of the Archlinux ARM website and they also explain how to build your own cross-compiler using crosstool-ng
.