website logo

Last Updated:

Build Static Binary and Cross Compile CGO Project using Zig Compiler

The Go compiler is known for building static binary and have robust cross compiling capabilities. These static binaries contain all the bell and whistles to run the application on all target operating system and architecture.

But, when you include any CGO library in your project, cross compiling and building a static binary become harder and error-prone.

Why this is the case? Why Go compiler make dynamically linked binary in a CGO project? And how to solve this issue with the Zig compiler? We will discuss this topic in this blog.

Static binary and its benefits

Statically linked binary contain all its dependency in the executable itself. Therefore, it doesn’t expect any library of specific version is installed in the user computer. On the contrary, dynamically linked binary don’t contain its dependency and relies on operating system to provide those libraries in the runtime.

If you are building the executable for yourself or targeting a niche customer base which has all the dependency installed in their operating system, then dynamically linked binary gives a smaller executable and lower RAM usage. Which is good for many cases.

But when you try to cross compile and distribute to a larger and more general set of audience, the problem starts to appear. In high security environment like financial corporation, it is advised to build static binaries to reduce attack surface.

Why Golang can’t build static binaries in CGO projects

In a CGO project, during compilation, The Go compiler uses the default C compiler provided by the operating system to compile the C code. On Linux, it uses the GNU Compiler Collection (GCC) by default. The GCC compiler doesn’t statically link your binary by default. In addition, due to the limitation of GPL license, sometime it is not legal to statically link some C libraries.

Cross compilation is also hard in CGO project. You need to install all required dependency and C compilers for your target operating system. This is not very easy to do and most of the time throwing some error. Cross compiling to a different architecture is also hard because of the same reason.

How Zig compiler helps to cross compile CGO project.

Zig is a system programming language like C or Rust. Zig comes with its own libC. Zig compiler can cross compile to any target listed on their support table. This includes all the major operating system and architecture.

To use Zig compiler to build CGO project, first you have to download and install the Zig programming language in your computer. Then run zig targets to list all the libC target zig comes with.

$ zig targets

"libc": [
  "aarch64_be-linux-gnu",
  "aarch64_be-linux-musl",
  "aarch64_be-windows-gnu",
  "aarch64-linux-gnu",
  "aarch64-linux-musl",
  "aarch64-windows-gnu",
  "armeb-linux-gnueabi",
  "armeb-linux-gnueabihf",
  "armeb-linux-musleabi",
  "armeb-linux-musleabihf",
  "armeb-windows-gnu",
  "arm-linux-gnueabi",
  "arm-linux-gnueabihf",
  "arm-linux-musleabi",
  "arm-linux-musleabihf",
  "arm-windows-gnu",
  "i386-linux-gnu",
  "i386-linux-musl",
  "i386-windows-gnu",
  "mips64el-linux-gnuabi64",
  "mips64el-linux-gnuabin32",
  "mips64el-linux-musl",
  "mips64-linux-gnuabi64",
  "mips64-linux-gnuabin32",
  "mips64-linux-musl",
  "mipsel-linux-gnu",
  "mipsel-linux-musl",
  "mips-linux-gnu",
  "mips-linux-musl",
  "powerpc64le-linux-gnu",
  "powerpc64le-linux-musl",
  "powerpc64-linux-gnu",
  "powerpc64-linux-musl",
  "powerpc-linux-gnu",
  "powerpc-linux-musl",
  "riscv64-linux-gnu",
  "riscv64-linux-musl",
  "s390x-linux-gnu",
  "s390x-linux-musl",
  "sparc-linux-gnu",
  "sparcv9-linux-gnu",
  "wasm32-freestanding-musl",
  "x86_64-linux-gnu",
  "x86_64-linux-gnux32",
  "x86_64-linux-musl",
  "x86_64-windows-gnu"
 ],

Command to build statically linked binary

I am running a Linux machine. My Golang version is 1.22 and Zig version is 0.11.0. To build a static binary for my x86_64 architecture machine, run the following command in the root of your project directory.

CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zig cc -target x86_64-linux-musl" CXX="zig c++ -target x86_64-linux-musl" go build -o amd64-build

Here, CC and CXX variable represent the C and C++ compiler we want to use in the Go build. Here I am using the musl libc library to build a statically linked binary.

Musl is a C standard library based on the Linux kernel and released under MIT license.

You can use ldd command to check if your binary is statically linked or not.

$ ldd amd64-build

	statically linked

Cross compiling CGO project to arm64 machine

As arm64 machines are cheap and performant, cross compiling your app to arm64 target is also an important requirement. Zig compiler also helps you to cross compile your CGO project to arm64 machine.

CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC="zig cc -target aarch64-linux-musl" CXX="zig c++ -target aarch64-linux-musl" go build -o arm64-build

The generated arm64-build binary is statically linked and runs on arm64 processor.

$ ldd arm64-build

	statically linked

If you want, you can also make a dynamically linked binary for arm64 target using the aarch64-linux-gnu Zig compiler target.

CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC="zig cc -target aarch64-linux-gnu" CXX="zig c++ -target aarch64-linux-gnu" go build -o arm64-build

Conclusion

I hope you learn something new by reading this blog. Now you can make statically linked cross compiled binaries for your CGO project. This is really necessary when you work with the SQLite package. Make sure to subscribe to my newsletter by add your email address below. Have a good day.

See Also