Blog | Blue Matador

Golang Pros & Cons for DevOps (Part 5 of 6): Cross-Platform Compiling

Written by Keilan Jackson | Jan 15, 2018 4:45:00 PM

In part 5 of Goalng Pros & Cons for DevOps, we discuss cross-platform compilation for Go projects.

Be sure to read up on the last post about “The Time Package and Method Overloading” if you missed it, or subscribe to our blog updates to be notified when the rest of the series is published.

Golang Pro: Compiling for Windows on Linux

As someone who primarily uses linux, it can be a real pain to deal with Windows on the rare occasions that I have to. This is especially true when working on our Smart Agent™, which runs on both Linux and Windows and dives deep into OS-specific issues for both our log management and monitoring products. 

Since our agent is written in Golang, though, actually compiling the code to run on Windows is fairly painless, and can easily be done in a Linux environment. The bulk of the work is handled by passing in two variables when running the go build command: GOARCH and GOOS.

A full list of the possible combinations is available by running go tool dist list, and on Go 1.8 there are 38 combinations. The below example shows how to build Linux and Windows for both  AMD64 and Intel i386 architectures, and you can easily see how a Makefile could be created to easily build for many different targets.

GOOS=linux GOARCH=amd64 go build -o bin/myapp_linux_amd64 myapp
GOOS=windows GOARCH=amd64 go build -o bin/myapp_windows_amd64 myapp
GOOS=linux GOARCH=386 go build -o bin/myapp_linux_386 myapp

Cgo

If your project uses cgo, you will have a little more trouble. In order to get cgo code to compile cross-platform, you have to have the correct toolchain set up on the build machine. It had been a while since I had to deal directly with gcc, but it was relatively easy to find the correct commands to set up an Ubuntu 16.04 box. Below are some one-liners you can use to set up your build machine when figuring things out with cgo:

# Install cgo dependencies
apt-get install -y gcc libsystemd-dev gcc-multilib
# cgo for linux/386
apt-get install -y libc6-dev-i386
# cgo for windows
apt-get install -y gcc-mingw-w64

And the modified build commands would be:

CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=gcc go build -o bin/myapp_linux_amd64 myapp
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CXX=x86_64-w64-mingw32-g++ CC=x86_64-w64-mingw32-gcc go build -o bin/myapp_windows_amd64 myapp
CGO_ENABLED=1 GOOS=linux GOARCH=386 CC=gcc go build -o bin/myapp_linux_386 myapp

Golang Con: Docs for Windows

The golang documentation site is really well done. It has useful examples, links to source code, and of course there is the playground for testing out snippets of code. One shortcoming of the golang docs, however, is when you're using code that behaves differently on Windows. 

Some pages, like the os page do a pretty good job about explaining the difference between Unix and WIndows systems for many functions. I wonder, though, why they even include some functions like Getegid in the standard libraries for windows with comments like: 

Getegid returns the numeric effective group id of the caller.
On Windows, it returns -1.

I would much rather the compiler fail when using functions like this in code built for Windows, instead of finding out only at runtime that the value is useless, or the function call does nothing.

Other pages in the documentation cop out completely with a blanket statement about Windows, such as the exec page:

Note that the examples in this package assume a Unix system. They may not run on Windows, and they do not run in the Go Playground used by golang.org and godoc.org.

Very helpful.

Golang Con: Windows Support in Community Packages

This con is only really apparent because of how easy it is to "try out" compiling for windows after developing a feature on Unix. There has been quite a few times where I will use an open source library to write a feature for linux, and accidentally break the Windows build because the library uses Unix-only calls. There are two relatively simple solutions:

1. Contribute

If you have the time and feel like contributing back to the project, make a PR that adds in Windows support! Beware that this can be very time-consuming depending on your dev setup, because even though building for Windows is easy, testing is not.

2. Use Build Constraints

Golang makes it super easy to exclude or include files for a build using build constraints. For example, to only include a dependency (that only works in Windows) in a file that is built only for NT you could do:

// +build windows

package mypackage

import "github.com/bluematador/windows-only"

You can also negate which GOOS to build on:

// +build !windows

package mypackage

import "github.com/bluematador/linux-only"

The nice thing here is it forces you to organize things a little bit more, and you can still have the same function callable in both OSes but with different behavior by using build constraints on the file it is defined in.

Every other week or so, we are posting a new guide like this in our six-part series on “Golang Pros & Cons for DevOps.” Next up: #6: Defer Statements and Package Dependency Versioning.