Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nguyenthenguyen/d584ef151f65ac22988a to your computer and use it in GitHub Desktop.
Save nguyenthenguyen/d584ef151f65ac22988a to your computer and use it in GitHub Desktop.
Cross compiling a simple go server for windows

How to build Golang windows/arm static binaries from linux

Alternate title: Cross compiling Windows/Darwin/Linux amd64/386/arm all from linux

After fumbling around trying to figure out the go toolchain and cross compilation configuration, I ran across the wiki page on Go's homepage. It's super helpful, and worked out of the box. I'm including the necessary scripts here in case they get changed or lost, and we can help Google find it (since it's the first real source I've found that "Just Worked"). http://code.google.com/p/go-wiki/wiki/WindowsCrossCompiling

I also went a step further, and built for windows/amd64 and linux/arm

The Setup

Install go

hg clone -u release https://code.google.com/p/go
cd go/src
./all.bash

Grab the two helper scripts from: http://code.google.com/p/go-wiki/wiki/WindowsCrossCompiling

buildcmd

#!/bin/sh
set -e
for arch in 8 6; do
        for cmd in a c g l; do
                go tool dist install -v cmd/$arch$cmd
        done
done
exit 0

buildpkg

#!/bin/sh
if [ -z "$1" ]; then
        echo 'GOOS is not specified' 1>&2
        exit 2
else
        export GOOS=$1
        if [ "$GOOS" = "windows" ]; then
                export CGO_ENABLED=0
        fi
fi
shift
if [ -n "$1" ]; then
        export GOARCH=$1
fi
cd $GOROOT/src
go tool dist install -v pkg/runtime
go install -v -a std

Set yourself up for windows cross compile!

$ chmod +x ./buildcmd
$ chmod +x ./buildpkg
$ ./buildcmd
$ ./buildpkg windows 386
$ ./buildpkg windows amd64
$ ./buildpkg darwin amd64

Now, go build yourself a static windows go binary!

$ cat hello.go
package main

import "fmt"

func main() {
        fmt.Printf("Hello\n")
}

$ GOOS=windows GOARCH=386 go build -o hello_i386.exe hello.go
$ GOOS=windows GOARCH=amd64 go build -o hello_amd64.exe hello.go
$ file hello_amd64.exe
geocode_amd64.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
$ file hello_i386.exe
geocode_amd64.exe: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows
$ wine ./hello_i386.exe 2>/dev/null
Hello

And just for kicks -- let's cross compile for darwin (OSX Native)

$ GOOS=darwin GOARCH=amd64 go build -o hello_osx_amd64 hello.go
$ GOOS=darwin GOARCH=386 go build -o hello_osx_386 hello.go

$ file hello_osx_386
server_osx_386: Mach-O executable i386
$ file hello_osx_amd64
server_osx_amd64: Mach-O 64-bit executable

Just be aware that there are some side effects in this method because CGO_ENABLED=0. With the latest versions of Go, however, I was able to run a small http server without issues, and another server that connects to postgres through a third party go library (although I didn't actually set up postgres on windows -- but the program ran and returned a standard database connection error message). Pretty slick!

Building for ARM

Building for ARM is just as straight forward, except you have to re-run make.bash (because you have to switch your toolchain from x86 to ARM). If not, you'll do:

$ GOOS=linux GOARCH=arm go build -o geocode_arm geocode.go 
go build runtime: linux/arm must be bootstrapped using make.bash

Back to the go build root -- get all set up for arm

$ cd $GOROOT/src
$ GOARCH=arm ./make.bash
Installed Go for linux/arm in /home/blaine/src/go
Installed commands in /home/blaine/src/go/bin
...

Now, we can try to compile again
``` bash
$ GOOS=linux GOARCH=arm go build -o hello_arm hello.go
$ file hello_arm
geocode_arm: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, not stripped

NICE!

But, one caveat

Now that we've retooled the chain for ARM, we can't build for windows until we re-run the make.bash -- although interestingly, amd64 still works. Probably because both "arm" and "386" are technically "foreign" to my host x64 system, so perhaps the toolchain can only support a single foreign architecture at a time. This probably also explains why "go run file.go" still works fine -- amd64 is enabled by default, regardless of GOOS configuration.

$ GOOS=windows GOARCH=386 go build -o hello_i386.exe hello.go 
go tool: no such tool "8g"
$ GOOS=windows GOARCH=amd64 go build -o hello_amd64.exe hello.go
$ file hello_amd64.exe
hello_amd64.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

Re-tooling for 386

$ CD $GOROOT
$ GOARCH=386 ./make.bash
...
Installed Go for linux/386 in /home/blaine/src/go
Installed commands in /home/blaine/src/go/bin

And, just to test:

$ GOOS=windows GOARCH=386 go build -o hello_i386.exe geocode.go
$ file hello_i386.exe
hello_i386.exe: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment