Dominik Honnef

Cross-compiling Go and hidden uses of cgo

Published:
Last modified:
by

One of Go’s strengths is its built-in support for cross-compilation. Setting up and using cross-compilers in Go is completely painless and mainly involves setting some environment variables.

There is, however, one drawback to cross-compiling Go programs: It’s not possible to use cgo. Cgo is used for writing bindings to C libraries so that they can be used directly in Go.

Most of the time when people consider this drawback, they’re only thinking about their own packages. If they don’t explicitly use cgo, surely cross-compiling should be fine? Yes and no. Unfortunately, there are several packages in the Go standard library that make use of or even rely on cgo.

The net package

The net package is the prime example for its use of cgo to use the system resolver, used for making domain name lookups. At the same time, it’s a component that works really well without cgo in most cases, because it also implements its own resolver in pure Go, which will work fine for most people, unless they need the features that are only available with the system resolver, such as LDAP.

The os/user package

A better hidden use of cgo can be found in the os/user package. os/user needs cgo to determine the current user or to look up any other user by ID. When cgo isn’t available, functions like user.Current() and user.Lookup() will always return an error, saying that these functions aren’t implemented.

(Plan 9 is an exception to this rule. user.Current() works fine without cgo, while other functions in this package never work, even when cgo is enabled.)

The crypto/x509 package

The last example is crypto/x509, which parses X.509-encoded keys and certificates, i.e. the kind of certificates that SSL/TLS use. These certificates form a chain of trust, going all the way up to the root certificates. In order to verify a certificate, all certificates in that chain need to be verified, which means that Go needs access to them. Unfortunately, in OS X, the root certificates are stored in Apple’s Keychain software, and interfacing with that requires cgo. The saddening result of this is that a cross-compiled Go binary targeting OS X will not be able to use SSL/TLS correctly. Instead, it might return the following error:

x509: failed to load system roots and no roots provided

Conclusion

All three examples have in common that even though they might break your application when cross-compiled, the compilation process itself will succeed. Only at execution time will you realize that something is wrong. And if you’re bad at handling your errors (which, for obvious reasons, you shouldn’t be), you might even run into nil pointer dereferences.

This article isn’t supposed to mark cross-compilation as something unusable, but be aware of its limitations and, if possible, compile directly on the target platform.