Skip to main content

Go: Cross-Compilation Including Cgo

·670 words·4 mins
Sebastian Scheibe
Author
Sebastian Scheibe
Table of Contents

Updated on 2023-05-24: Added information on cross-compiling using the MUSL and GNU toolchain on macOS for static linking.

Introduction
#

With the release of Go v1.5, it became possible to cross-compile applications for various targets such as Windows, macOS, Linux, Solaris, and Android. Cross-compilation allows you to build binaries for different operating systems from a single development machine. This article explains how to cross-compile Go applications, including those that use Cgo for C dependencies.

Supported Platforms
#

Go supports cross-compilation to many platforms. Below is a table of the supported GOOS and GOARCH combinations as of Go version 1.22.2:

Each entry represents a GOOS and GOARCH environment variable combination. The first one for example, aix/ppc64, would be defined as GOOS=aix GOARCH=ppc64 before the actual compile command.

> go tool dist list | column -c 35 | column -t
aix/ppc64        linux/mips64le
android/386      linux/mipsle
android/amd64    linux/ppc64
android/arm      linux/ppc64le
android/arm64    linux/riscv64
darwin/amd64     linux/s390x
darwin/arm64     netbsd/386
dragonfly/amd64  netbsd/amd64
freebsd/386      netbsd/arm
freebsd/amd64    netbsd/arm64
freebsd/arm      openbsd/386
freebsd/arm64    openbsd/amd64
freebsd/riscv64  openbsd/arm
illumos/amd64    openbsd/arm64
ios/amd64        openbsd/ppc64
ios/arm64        plan9/386
js/wasm          plan9/amd64
linux/386        plan9/arm
linux/amd64      solaris/amd64
linux/arm        wasip1/wasm
linux/arm64      windows/386
linux/loong64    windows/amd64
linux/mips       windows/arm
linux/mips64     windows/arm64

Cross-compile examples
#

Windows
#

# 32 bit
GOOS=windows GOARCH=386 go build

# 64 bit
GOOS=windows GOARCH=amd64 go build

# arm
GOOS=windows GOARCH=arm go build

# arm 64 bit
GOOS=windows GOARCH=arm64 go build

macOS
#

# 64 bit
GOOS=darwin GOARCH=amd64 go build

# arm 64 bit
GOOS=darwin GOARCH=arm64 go build

Linux
#

# 32 bit
GOOS=linux GOARCH=386 go build

# 64 bit
GOOS=linux GOARCH=amd64 go build

# arm
GOOS=linux GOARCH=arm go build

# arm 64 bit
GOOS=linux GOARCH=arm64 go build

Cross-compile with Cgo
#

Some dependencies, such as SQLite, require Cgo instead of just pure Go to compile.

The following examples cover both static and dynamic linking.

Static Linking Static linking ensures that the resulting binary includes all necessary dependencies and does not require any libraries on the target system. This is useful for creating self-contained binaries, especially for small images like Alpine.

Dynamic Linking Dynamic linking means that the resulting binary relies on shared libraries that must be present on the target system.

macOS to macOS
#

# 64 bit static
GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -ldflags="-extldflags=-static"

# 64 bit dynamic
GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build 

# arm64 bit static
GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -ldflags="-extldflags=-static"

# arm64 bit dynamic
GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build

macOS to Linux
#

MUSL: Static or Dynamic Linking
#

MUSL-based systems are, for example, Alpine.

Installing musl gcc enables cross-compilation.

In combination with static binaries, the binary will run on any Linux distribution. Non-static binaries only run on musl-based systems like Alpine.

brew install filosottile/musl-cross/musl-cross

The following command will build a static binary, which allows it to run on any linux distribution, see here for more information

# 64 bit static
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-musl-gcc  CXX=x86_64-linux-musl-g++ go build -ldflags="-extldflags=-static"

# 64 bit dynamic
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-musl-gcc  CXX=x86_64-linux-musl-g++ go build

# arm64 static
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-musl-gcc  CXX=aarch64-linux-musl-g++ go build -ldflags="-extldflags=-static"

# arm64 dynamic
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-musl-gcc  CXX=aarch64-linux-musl-g++ go build

GNU: Dynamic Linking preferred
#

Install the GNU toolchain:

brew tap messense/macos-cross-toolchains

# x64 / x86
brew install  messense/macos-cross-toolchains/x86_64-unknown-linux-gnu

# arm64
brew install  messense/macos-cross-toolchains/aarch64-unknown-linux-gnu 

Building the application using the GNU toolchain:

# 64 bit static
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-gnu-gcc  CXX=x86_64-linux-gnu-g++ go build -ldflags="-linkmode external -extldflags -static"

# 64 bit dynamic
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-gnu-gcc  CXX=x86_64-linux-gnu-g++ go build

# arm64 static
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc  CXX=aarch64-linux-gnu-g++ go build -ldflags="-linkmode external -extldflags -static"

# arm64 dynamic
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc  CXX=aarch64-linux-gnu-g++ go build

macOS to Windows
#

At first, make sure to have Windows compiler setup, such as MinGW.

brew install mingw-w64

Then compile:

# 32 bit
GOOS=windows GOARCH=386 CGO_ENABLED=1 CC="i686-w64-mingw32-gcc" go build

# 64 bit
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC="x86_64-w64-mingw32-gcc" go build

For more information regarding Cgo, please follow up here .

References
#