One of Go's quietly brilliant features is that cross-compiling is not a project, it is two environment variables. I needed a binary for an ARM single-board computer, and rather than compile on the thing, which is slow and short on memory, I built it on the laptop:
GOOS=linux GOARCH=arm GOARM=7 go build -o myapp .
That is the whole trick. No toolchain to install, no cross-gcc, no sysroot, assuming you are not using cgo. Pure Go, static binary, copy it over with scp and run it.
The detail that caught me out was GOARM. I left it off the first time, Go defaulted to ARMv6, and the binary ran fine on the older board I tested with and then refused to start on the newer one with an illegal instruction. Backwards, you would think, but the mismatch is the point: build for the wrong ARM version and the CPU meets an instruction it does not implement. Set GOARM=7 for anything modern, GOARM=6 for the original Pi and its kin, and check /proc/cpuinfo on the target if you are unsure.
The other half-hour I will not get back: cgo. The moment you import anything that needs C, CGO_ENABLED=0 stops working and you suddenly do need a cross-toolchain after all. For pure Go, though, this is as painless as building gets, and I still find it slightly magical that the laptop can produce a working ARM binary without ever having met an ARM chip.