Send me an email!
Cross-compiling Go and hidden uses of cgo
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.
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.
A better hidden use of cgo can be found in the
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.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
cgo is enabled.)
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
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.