All,
I spent a fair amount of time researching this topic and couldn't really find a comprehensive answer on the topic. The closest I came was this thread on StackOverflow (so big kudos to OneOfOne there!):
Using the information from there, I managed to put together a relatively simple process of building Windows binaries under Linux. Not only does my project require CGo but it also has to load DLLs behind the scenes and call functions in those DLLs. So it is really exercising the toolchain.
I actually have a Dockerfile for my build environment that I could clean up if anybody is interested in an image like that.
I just wanted to post a summary of what I found here in case anybody else comes along and is interested in doing what I did.
The first step was to download Go from source so I could build it myself, i.e.,
$ cd go
$ git checkout go1.4.1
I also needed to make sure I had several different gcc packages installed on my Ubuntu machine to support this process. I installed them with:
$ sudo apt-get install gcc-multilib
$ sudo apt-get install gcc-mingw-w64
The trickiest part though was building the various bits. I used make.bash. Perhaps there is a way to do this with all.bash. But I wasn't sure what that actually did and whether it would pick up the particular environment variables I wanted to specify. So I ran make.bash multiple times. I start by building the Windows stuff (I'll explain why in a sec):
$ cd src
$ GOOS=windows GOARCH=386 CGO_ENABLED=1 CXX_FOR_TARGET=i686-w64-mingw32-g++ CC_FOR_TARGET=i686-w64-mingw32-gcc ./make.bash
This enabled CGO and specifies special C and C++ compilers to use in order to build the native exes. This is for the win32 platform. Then, I run the command again for win64 but add the --no-clean option:
$ GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CXX_FOR_TARGET=x86_64-w64-mingw32-g++ CC_FOR_TARGET=x86_64-w64-mingw32-gcc ./make.bash --no-clean
Finally, I do a build for native Linux (again with --no-clean)
$ GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CXX_FOR_TARGET=g++ CC_FOR_TARGET=gcc ./make.bash --no-clean
Now you might wonder why I built them in this order. I played around with this quite a bit actually. The issue is that these builds are somehow "stateful". The last build seems to set some defaults somewhere or something. You'd think that it would be sufficient to just specify GOOS and GOARCH whenever you ran the go tool, but it isn't. If I build Linux first and Windows last, the Linux version wouldn't work (because the last build was Windows and that messed up Linux). And yes, building Linux last messes up the Windows builds. BUT, that can be overcome by setting some environment variables.
The way I dealt with this was to create some special scripts:
$ echo 'export GOOS=windows; export GOARCH=386; export CGO_ENABLED=1; export CXX=i686-w64-mingw32-g++; export CC=i686-w64-mingw32-gcc' > go-win32
$ echo 'export GOOS=windows; export GOARCH=amd64; export CGO_ENABLED=1; export CXX=x86_64-w64-mingw32-g++; export CC=x86_64-w64-mingw32-gcc' > go-win64
Notice the fact that I explicitly set the values of CXX and CC. What I normally do is just source these, i.e., (NOTE THE '.')
$ . go-win32
This sets the environment variables needed to perform a win32 build. After that, I can just use the go tool as usual, e.g.
Sourcing these files is a minor annoyance. I wish I could just get away with:
$ GOOS=windows GOARCH=386 go build
But it doesn't work. You even see a hint of this in the StackOverflow question when the OP talks about the error:
gcc: error: unrecognized command line option ‘-mthreads’
That is a symptom of the "statefulness" I referred to earlier. I suspect someone who understand the Go build process and the way the environment is managed for cross-compilation could probably figure out a way to get rid of the statefulness and make the cross-compilation easier (or maybe I'm an idiot and there is already a way to do this...entirely possible).
One final point worth making is that if you want to test stuff, you won't be able to...at least not in the way you expect. This process is for building native executables under Windows. It doesn't let you run them. Fortunately, you can install Wine and then run them. What I do is first build the test exe with:
$ go test -c
...and then I run them with just:
$ wine pkgname.test.exe
You might ask...why not just use Wine to do the build. Well, there are a couple of reasons. First, you have to create a development toolchain under Windows which means installing some Windows stuff under Wine (git, mercurial, TDM...and, yes, I know about Winstrap). That part, while tedious, is doable. But the main reason is that it doesn't actually work. What I mean by that is that it may very well work in general. But it didn't work in my case. I mentioned that I'm using CGo and DLLs. For whatever reason, this combination is quite problematic. Plus, I really find this approach much simpler. As I said, I can easily make a Dockerfile that builds to complete environment and then just fire it up for builds.
Whew! The main point here is that it took a fair amount of time for me to figure this out even after a lot of Googling so I'm going to assume that this isn't obvious (at least to everyone) so I wanted to make a note of what I figured out in case somebody finds themselves in the same boat as me. I would, of course, be thrilled to find that this can be made even more streamlined. So if you have experience with this, please comment.
Thanks!
--
Mike