160

I am trying to cross-compile a go app on OSX to build binaries for windows and linux. I have read everything what I could find on the net. Closest example that I have found has been published on (apart from many unfinished discussions on go-nuts mailing list):

http://solovyov.net/en/2012/03/09/cross-compiling-go/

yet it does not work on my installation. I have go 1.0.2. As 1.0.2 is quite recent it looks to me that all above examples do not apply to this version.

Tried to do ./make.bash --no-clean with ENV vars set to 386/windows, it does build go, however it builds go for my installation which is darwin/amd64 and completely ignores what is set in ENV that suppose to build different compiler.

Any advises how it can be done (if it can be done at all)?

Wolf
  • 9,679
  • 7
  • 62
  • 108
ljgww
  • 3,418
  • 6
  • 19
  • 21
  • parallel to this i have asked same question on golang-nuts mailing list, and with the kind help and patience of people there final recipe got cooked... this is the discussion thread: https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/woSc9l69nVo there were several steps and conclusions, I got wrong on the way, but now recipe seems pretty simple - 3 steps and some iterations. – ljgww Aug 29 '12 at 02:53
  • now that I am going over recap, I wander as to why ENV vars did not trigger correct compilation - maybe because I did `sudo` (likely I would get different unix ENV when sudo-ing so GOOS & GOARCH would not be available if they are not done inline) – ljgww Aug 29 '12 at 03:10
  • re: jdi - I was just trying to compile my "mockup" go app to win/lin binaries on mac, but, to do it I have had to build go itself for each combination of platform/processor. (cannot answer my own question as yet - having not enough reputation here) – ljgww Aug 29 '12 at 03:14
  • 1
    Did you type exactly what it said in the example? `CGO_ENABLED=0 GOOS=windows GOARCH=amd64 ./make.bash` - if you attempted to split it across more than one line then the environment variable won't be exported which fits the symptoms – Nick Craig-Wood Aug 29 '12 at 18:14
  • Make sure you are not confusing host and target architecture. You should see this output: "# Building compilers and Go bootstrap tool for host, darwin/amd64." "# Building packages and commands for host, darwin/amd64." "# Building packages and commands for windows/386." – Sam Aug 30 '12 at 00:43
  • This helped me a lot: http://code.google.com/p/go-wiki/wiki/WindowsCrossCompiling – topskip Aug 30 '12 at 14:19
  • as for: code.google.com/p/go-wiki/wiki/WindowsCrossCompiling helped me a lot but on one last line - how to compile target. The rest is confusing to me, it is for someone who knows shell scripts in details and it seems it is built for linux installation, possibly it would work on mac so i did not follow that I wanted to learn exact steps. I am not good with shell scripts. – ljgww Aug 30 '12 at 18:12
  • Re Nick: Yes initially I have done `export GOOS=windows` and all others before compiling compilers. I did not know what make does i thought make does cross compiling my app but then I learned it actually compiles a compiler. SUDO may not inherit ENV so I suspect that some of my early attempts failed because of this UNIX caveat. – ljgww Aug 30 '12 at 18:16
  • @ljgww the wiki page: yes, it works fine on mac, but you're right, you need a bit of shell script knowledge. After running the scripts, I can do this now: set the environment variables `GOARCH` and `GOOS` and the compilation builds a working binary for the platform/os in these variables. – topskip Aug 30 '12 at 18:43
  • @Patrick I assume in many cases x-compilation happens if you are working on code on some workstation which is later deployed to some server that runs other os. For example I develop on Mac and deploy on Linux and my cycle would be writing code, compiling for linux, transferring to server. In such case I assume exporting GOOS/GOARCH would be better solution. Defining inline is better option for me because my attempt is to write an app that can be deployed on different OSes. – ljgww Aug 30 '12 at 19:07
  • sudo cleans the environment, so that could have been contributing to your problem. See the -E argument to sudo, if you really need to use it. – Jeff Allen Sep 11 '12 at 13:25
  • I found [this article](http://dave.cheney.net/2012/09/08/an-introduction-to-cross-compilation-with-go) about cross-compiling go. The guy who wrote it also has a very handy util for it. – levtatarov Jul 09 '13 at 09:02
  • I am amazed how on the course of nearly a decade, what has been a simple question for one particular case of OS and compiler and one specific version used, this question became some sort of jump station for all kinds of cross compiling issues and answers. Perhaps, this shall be somehow locked so that people can ask new questions and get modern answers. – ljgww Oct 01 '21 at 20:24

8 Answers8

211

With Go 1.5 they seem to have improved the cross compilation process, meaning it is built in now. No ./make.bash-ing or brew-ing required. The process is described here but for the TLDR-ers (like me) out there: you just set the GOOS and the GOARCH environment variables and run the go build.

For the even lazier copy-pasters (like me) out there, do something like this if you're on a *nix system:

env GOOS=linux GOARCH=arm go build -v github.com/path/to/your/app

You even learned the env trick, which let you set environment variables for that command only, completely free of charge.

Leon S.
  • 3,337
  • 1
  • 19
  • 16
  • Is there an advantage to `env FOO=bar cmd` over `FOO=bar cmd`? – davidchambers May 31 '16 at 17:23
  • 5
    The `env` command runs only that call in a custom environment and 'resets' it after it is done. For example run `export GOOS=windows`, then the command with or without the `env` and `echo $GOOS` afterwards. With the `env` the GOOS was not changed. – Leon S. Jun 13 '16 at 09:17
  • 4
    the same is true (at least in Bash) without `env`. I ran `export GOOS=windows` then `GOOS=linux bash -c 'echo "GOOS: $GOOS"'` then `echo "GOOS: $GOOS"`. Does `env` provide greater compatibility with other shell dialects or with other platforms? If not it seems superfluous here. – davidchambers Jun 13 '16 at 14:43
  • After trying it out a little more, you seem to be correct in this case. However using `GOOS=windows` without the following command (accidentally adding an `&&` for example) will do the same as `export GOOS=windows` and set the environment variable, so you'll have to be careful there. The `env` guarantees leaving the environment in tact, and has some more usefull features for example adding `-i` will give you an empty environment, and running it without any command will print the current environment variables. `man env` will give you all the details. – Leon S. Jun 21 '16 at 13:33
  • 3
    @davidchambers In BASH they are equivalent. While in some other Shell, e.g. FISH shell, it does not support `FOO=bar cmd`, so you have to use `env FOO=bar cmd`. Thus I think the biggest advantage to use `env FOO=bar cmd` is compatibility. – PickBoy Dec 01 '16 at 12:12
  • 1
    Incredible answer right here. You solved my problem, taught me a new trick, and made me chuckle to myself. – T Blank Jan 16 '17 at 06:07
  • 1
    Great answer, thank you! In order to compile for use on heroku (intel x86) I slightly modified the line to `env GOOS=linux GOARCH=386 go build -v github.com/path/to/your/app` and it works like a champ – Ira Herman Aug 18 '17 at 19:19
  • run command `$ go tool dist list` to see possible combinations of GOOS and GOARCH – prembhaskal Mar 24 '23 at 17:36
143

Thanks to kind and patient help from golang-nuts, recipe is the following:

1) One needs to compile Go compiler for different target platforms and architectures. This is done from src folder in go installation. In my case Go installation is located in /usr/local/go thus to compile a compiler you need to issue make utility. Before doing this you need to know some caveats.

There is an issue about CGO library when cross compiling so it is needed to disable CGO library.

Compiling is done by changing location to source dir, since compiling has to be done in that folder

cd /usr/local/go/src

then compile the Go compiler:

sudo GOOS=windows GOARCH=386 CGO_ENABLED=0 ./make.bash --no-clean

You need to repeat this step for each OS and Architecture you wish to cross compile by changing the GOOS and GOARCH parameters.

If you are working in user mode as I do, sudo is needed because Go compiler is in the system dir. Otherwise you need to be logged in as super user. On Mac you may need to enable/configure SU access (it is not available by default), but if you have managed to install Go you possibly already have root access.

2) Once you have all cross compilers built, you can happily cross compile your application by using the following settings for example:

GOOS=windows GOARCH=386 go build -o appname.exe appname.go

GOOS=linux GOARCH=386 CGO_ENABLED=0 go build -o appname.linux appname.go

Change the GOOS and GOARCH to targets you wish to build.

If you encounter problems with CGO include CGO_ENABLED=0 in the command line. Also note that binaries for linux and mac have no extension so you may add extension for the sake of having different files. -o switch instructs Go to make output file similar to old compilers for c/c++ thus above used appname.linux can be any other extension.

Taavi
  • 135
  • 2
  • 16
ljgww
  • 3,418
  • 6
  • 19
  • 21
  • What initially confused me is that in first part of compilation make says: `# Building compilers and Go bootstrap tool for host, darwin/amd64` but later on it actually ends up as: `--- Installed Go for windows/386 in /usr/local/go Installed commands in /usr/local/go/bin` so one shall observe ending rather than beginning of compiling the compiler. – ljgww Aug 30 '12 at 18:23
  • everything started by trying to do: `$ GOARCH=386 GOOS=linux go build app.go` and getting error `# runtime /usr/local/go/src/pkg/runtime/extern.go:137: undefined: theGoos /usr/local/go/src/pkg/runtime/extern.go:137: cannot use theGoos as type string in const initializer` – ljgww Aug 30 '12 at 18:38
  • second confusion of mine was: I assumed that `make` will build target app for me, not what actually `make` does - building a compiler for target compilation. These facts shall be clearly explained. – ljgww Aug 30 '12 at 18:45
  • 32
    Go's package in Homebrew has an option "--cross-compile-all" which will automatically build all cross compilers. – nimrodm Apr 28 '14 at 19:30
  • 8
    great tip @nimrodm! to recompile your go installation, you need to run `brew reinstall go --cross-compile-all` – linqu Jan 14 '15 at 17:06
  • @ljgww After running this, I get: `go install runtime/cgo: open /usr/local/go/pkg/linux_amd64/runtime/cgo.a: permission denied` when doing `go get` or `go install`, what's the fix? – Nuno Silva Oct 05 '15 at 19:46
  • @Nuno Silva I'd guess you are trying to do this at user level, perhaps you need 'sudo' – ljgww Oct 08 '15 at 08:11
  • 1
    @ljgww 'sudo' does not have ENV configured. I ended up using chown on /usr/local/go/pkg/linux_amd64/ – Nuno Silva Oct 09 '15 at 12:51
  • Can confirm this worked on Mac OSX El Capitan 10.11.2 for exporting to linux. Are there other file extensions i can rename .linux too though? – ThatGuy343 Jan 09 '16 at 23:43
  • Please update this answer to reflect the newest version of Go. meonlol's [answer below](http://stackoverflow.com/a/35878611/461597) is all that is needed on a current Go. – Unapiedra Mar 31 '16 at 22:13
  • kindly note that this thread applies to early releases of go (1.0.2) - as it was at the time of asking. – ljgww Apr 04 '16 at 06:46
63

If you use Homebrew on OS X, then you have a simpler solution:

$ brew install go --with-cc-common # Linux, Darwin, and Windows

or..

$ brew install go --with-cc-all # All the cross-compilers

Use reinstall if you already have go installed.

docwhat
  • 11,435
  • 6
  • 55
  • 54
  • 3
    Noting the updated switches are: --cross-compile-all Build the cross-compilers and runtime support for all supported platforms --cross-compile-common Build the cross-compilers and runtime support for darwin, linux and windows – Brian Tol Feb 17 '15 at 16:48
  • 3
    `--cross-compile-all` is now `--with-cc-all` – gianebao May 13 '15 at 08:32
  • 1
    These flags no longer exist from what I can tell. The only relevant option I see is --without-cgo :( – rdegges Sep 24 '15 at 05:23
  • 6
    Since Go 1.5, there are no separate cross-compilers, you only use the flags now https://tip.golang.org/doc/go1.5#compiler_and_tools – chazkii Jul 04 '16 at 14:27
25

You can do this pretty easily using Docker, so no extra libs required. Just run this command:

docker run --rm -it -v "$GOPATH":/go -w /go/src/github.com/iron-io/ironcli golang:1.4.2-cross sh -c '
for GOOS in darwin linux windows; do
  for GOARCH in 386 amd64; do
    echo "Building $GOOS-$GOARCH"
    export GOOS=$GOOS
    export GOARCH=$GOARCH
    go build -o bin/ironcli-$GOOS-$GOARCH
  done
done
'

You can find more details in this post: https://medium.com/iron-io-blog/how-to-cross-compile-go-programs-using-docker-beaa102a316d

Travis Reeder
  • 38,611
  • 12
  • 87
  • 87
  • 1
    Why would anyone want to install Docker to do this when they could just do a shell loop over `env GOOS=x GOARCH=y go install something/...` and end up with the appropriate binaries under `$GOPATH/bin/$GOOS_$GOARCH`?? And BTW, Go supports more than the three OSes you list, why no love for the BSDs? – Dave C Jul 09 '15 at 13:03
  • 8
    You wouldn't install Docker just to do this, but if you have it, this is easier and cleaner than the alternatives. – Travis Reeder Jul 09 '15 at 23:38
11

The process of creating executables for many platforms can be a little tedious, so I suggest to use a script:

#!/usr/bin/env bash

package=$1
if [[ -z "$package" ]]; then
  echo "usage: $0 <package-name>"
  exit 1
fi
package_name=$package

#the full list of the platforms: https://golang.org/doc/install/source#environment
platforms=(
"darwin/386"
"dragonfly/amd64"
"freebsd/386"
"freebsd/amd64"
"freebsd/arm"
"linux/386"
"linux/amd64"
"linux/arm"
"linux/arm64"
"netbsd/386"
"netbsd/amd64"
"netbsd/arm"
"openbsd/386"
"openbsd/amd64"
"openbsd/arm"
"plan9/386"
"plan9/amd64"
"solaris/amd64"
"windows/amd64"
"windows/386" )

for platform in "${platforms[@]}"
do
    platform_split=(${platform//\// })
    GOOS=${platform_split[0]}
    GOARCH=${platform_split[1]}
    output_name=$package_name'-'$GOOS'-'$GOARCH
    if [ $GOOS = "windows" ]; then
        output_name+='.exe'
    fi

    env GOOS=$GOOS GOARCH=$GOARCH go build -o $output_name $package
    if [ $? -ne 0 ]; then
        echo 'An error has occurred! Aborting the script execution...'
        exit 1
    fi
done

I checked this script on OSX only

gist - go-executable-build.sh

Dima Kozhevin
  • 3,602
  • 9
  • 39
  • 52
  • Exactly what I was looking for... I have dockerized it :) https://gist.github.com/marcellodesales/fbd7adbe7916c87440cf526ee84a29ba#file-golang-cross-compile-dockerfile-L25 – Marcello DeSales Aug 22 '19 at 13:47
9

for people who need CGO enabled and cross compile from OSX targeting windows

I needed CGO enabled while compiling for windows from my mac since I had imported the https://github.com/mattn/go-sqlite3 and it needed it. Compiling according to other answers gave me and error:

/usr/local/go/src/runtime/cgo/gcc_windows_amd64.c:8:10: fatal error: 'windows.h' file not found

If you're like me and you have to compile with CGO. This is what I did:

1.We're going to cross compile for windows with a CGO dependent library. First we need a cross compiler installed like mingw-w64

brew install mingw-w64

This will probably install it here /usr/local/opt/mingw-w64/bin/.

2.Just like other answers we first need to add our windows arch to our go compiler toolchain now. Compiling a compiler needs a compiler (weird sentence) compiling go compiler needs a separate pre-built compiler. We can download a prebuilt binary or build from source in a folder eg: ~/Documents/go now we can improve our Go compiler, according to top answer but this time with CGO_ENABLED=1 and our separate prebuilt compiler GOROOT_BOOTSTRAP(Pooya is my username):

cd /usr/local/go/src
sudo GOOS=windows GOARCH=amd64 CGO_ENABLED=1 GOROOT_BOOTSTRAP=/Users/Pooya/Documents/go ./make.bash --no-clean
sudo GOOS=windows GOARCH=386 CGO_ENABLED=1 GOROOT_BOOTSTRAP=/Users/Pooya/Documents/go ./make.bash --no-clean

3.Now while compiling our Go code use mingw to compile our go file targeting windows with CGO enabled:

GOOS="windows" GOARCH="386" CGO_ENABLED="1" CC="/usr/local/opt/mingw-w64/bin/i686-w64-mingw32-gcc" go build hello.go
GOOS="windows" GOARCH="amd64" CGO_ENABLED="1" CC="/usr/local/opt/mingw-w64/bin/x86_64-w64-mingw32-gcc" go build hello.go
Pouya Sanooei
  • 1,012
  • 1
  • 13
  • 22
2

Since go 1.17.1 you must explicitly set go env variables.

I've done it in a shell script

#!/bin/bash

GOOS=linux 
GOARCH=amd64
go.exe get -d -v ./...
go.exe env -w GOOS=$GOOS
go.exe env -w GOARCH=$GOARCH
go.exe build -v ./cmd/tecnoalarm/main.go

1

for people who need CGO enabled and cross-compile from another system

There is one docker solution golang-crossbuild

Here is one example to build linux/armv7 on Windows/MacOS

docker run -it --rm \
  -v $GOPATH/src/github.com/user/go-project:/go/src/github.com/user/go-project \
  -w /go/src/github.com/user/go-project \
  -e CGO_ENABLED=1 \
  docker.elastic.co/beats-dev/golang-crossbuild:1.16.4-armhf \
  --build-cmd "make build" \
  -p "linux/armv7"
zangw
  • 43,869
  • 19
  • 177
  • 214