Compiling Go Code for Different Platforms

We’ve all written code for personal use, but eventually, we have to write code that others can test and use. Code distribution is an issue that has plagued software engineers for a long time, and it has led to the development of innovative solutions such as containerization, and–more relatable to this post–the java virtual machine. The Java virtual machine (jvm) can be considered an innovation in its own right, as it allowed developers to not worry about whether their code would run on another platform. They could simply write code in Java (or any other language that compiled to Java bytecode), and as long as that platform had the Java virtual machine installed, it would execute as expected. I suppose it’s no surprise then, that a language like Go would consider the issue of where Go code could run, and the Go developers provided an easy approach to this problem.

One way to compile Go code is by running go build. By default, it builds a binary that is executable only on the current operating system and architecture. However, Go allows you to specify a platform to build for even if it isn’t the platform it is being compiled in. This is done through the GOOS environment variable. Assuming you have a Go source file, main.go inside module test, we can specify a Windows build like so:

1
GOOS=windows go build

Running this code would compile the Go source file in the current directory as a .exe file. On a UNIX system, you can use the file utility to view information about the created executable. Also, because Go uses static linking, you can simply copy this executable file to a Windows platform and execute it without fear of missing runtime dependencies.

Cross-compilation and GOOS can also be useful when testing if you are not certain the platform your code would be compiling on. For example, imagine you are writing integration tests for a program and want to build the tool before running sub tests. Given a binary name bin, we could have something like:

1
2
3
4
5
6
7
8
9
10
11
func TestMain(m *testing.M) {
if runtime.GOOS == "windows" {
bin += ".exe"
}

build := exec.Command("go", "build", "-o", bin)
if err := build.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Cannot build tool %s: %s", bin, err)
os.Exit(1)
}
}

Here, we are using the GOOS constant from the runtime package to check if our tests are running on the Windows operating system, in which case we append a .exe suffix to the binary name so that Go can find it.

A list of supported values for GOOS can be found here.

In production environments, cross-compilation would generally be automated by scripts or CI/CD pipelines, but this is just a peek into how it is done. I think it would be interesting to understand how some of these features were built into the language too. If you’re interested, I found a more detailed treatment of cross-compilation in Go in this Digital Ocean article.

I hope you learned something from this short piece!