golang でのクロスコンパイルの留意事項

golang (for Windows) でクロスコンパイルする際にハマったポイントと、 解決方法を紹介します。

TL;DR

golang のクロスコンパイルを準備する場合には、以下の点に留意してください。

  • (Windows のみ) gccは32ビット版か64ビット版か、使いたい方を正しく選択する
  • 2つ以上の環境へクロスコンパイルする場合には、make.bat/make.bash 実行時に --no-clean を指定する

クロスコンパイルの準備をする

golang を用いるとクロスコンパイルが容易なことはよく知られています。例えば、Windows上のgolangであっても、OSX向けのバイナリを生成したり、EdisonやRaspberry Pi用のバイナリを生成できたりするのです。ただし、以下に示す、ちょっとした事前準備が必要です。

  1. 環境変数 GOOS, GOARCH を設定し
  2. %GOROOT%\src\make.bat ($GOROOT/src/make.bash for not Windows) を実行する (2度目以降は不要)

本記事が取り上げるのは、この make.bat です。

make.bat で体験したこと

まずは私が make.bat を使った際に体験したエピソードを2つ紹介しましょう。

64ビットを使っていたと思ったら、いつの間にかのに32ビットだった

な…何を言っているのかわからねーと(ry

体験したことは以下のとおりです。

  1. Windows の64ビット版 golang をインストールして使用
  2. あるプログラムのWindows 64ビット版をビルドし、正しくビルドできた
  3. OSX向けのビルド環境を make.bat でセットアップ
  4. 同プログラムのOSX版をビルドし、正しくビルドできた
  5. 再度、同プログラムのWindows 64ビット版のビルドをしたら、32ビットexeができてしまった (GOOSとGOARCHは未指定)
  6. GOOSとGOARCHでWinodws 64ビット版を明示してビルドしたら、エラーになった

クロスコンパイルを準備したと思ったら、いつの間にか使えなくなっていた

な…何を言っているのかわからねーと(ry

体験したことは以下のとおりです。

  1. Windows の64ビット版 golang をインストールして使用
  2. あるプログラムのWindows 64ビット版をビルドし、正しくビルドできた
  3. OSX向けのビルド環境を make.bat でセットアップ
  4. 同プログラムのOSX版をビルドし、正しくビルドできた
  5. Linux向けのビルド環境を make.bat でセットアップ
  6. 同プログラムのLinux版をビルドし、正しくビルドできた
  7. 再度、同プログラムのOSX版のビルドをしたら、エラーになった

原因調査と対策

この2つのエピソードは、両方共に、make.bat が何をしているかを少し詳しく調べることで、解決方法が見いだせました。以下は make.bat からの抜粋です。

echo # Building C bootstrap tool.
echo cmd/dist
if not exist ..\bin\tool mkdir ..\bin\tool
:: Windows has no glob expansion, so spell out cmd/dist/*.c.
gcc -O2 -Wall -Werror -o cmd/dist/dist.exe -Icmd/dist %DEFGOROOT% cmd/dist/buf.c cmd/dist/build.c cmd/dist/buildgc.c cmd/dist/buildgo.c cmd/dist/buildruntime.c cmd/dist/main.c cmd/dist/windows.c cmd/dist/arm.c
if errorlevel 1 goto fail
.\cmd\dist\dist env -wp >env.bat
if errorlevel 1 goto fail
call env.bat

ここでは dist.exe というC言語で書かれたプログラムをコンパイルして、実行環境を出力するサブコマンド&オプション env -wp を付与して実行しています。そうして得た env.bat が以下です。

set CC=gcc
set CC_FOR_TARGET=gcc
set GOROOT=d:\Go\go1.4.2.windows-amd64
set GOBIN=d:\Go\go1.4.2.windows-amd64\bin
set GOARCH=386
set GOOS=windows
set GOHOSTARCH=386
set GOHOSTOS=windows
set GOTOOLDIR=d:\Go\go1.4.2.windows-amd64\pkg\tool\windows_386
set GOCHAR=8
set GO386=sse2
set PATH=(省略)

これは Windows 64ビット環境で実行した結果です。注目すべきは set GOHOSTARCH=386 の行です。一般にクロスコンパイル時には、ホスト(コンパイルを実行する)環境とターゲット(ビルドした実行ファイルを実際に使用する)環境を正しく指定される必要があります。Windows 64ビット環境がホストであるならば、ここは set GOHOSTARCH=amd64 になるはずです。これが「いつの間にか32ビットだった」ことに関係していそうです。

これに make.bat もう1つの仕組みが加わります。一般的なC言語などのクロスコンパイル環境の構築では、ターゲット用のツール類だけをビルドします。しかしgolangの make.bat は、ホスト用のツールもビルドするのです。よって、GOHOSTARCH に想定外の値が入っているということは、ホスト用のツールが変わってしまう、すなわち32ビットになってしまうことを意味しています。

あとは cmd/dist/*.c の内容を追えばわかるのですが、結論を急ぎましょう。使っているgccのバージョンが原因でした。PATHの通った gccが64ビット版だと GOHOSTARCH=amd64 になり、32ビット版だと GOHOSTARCH=386 になってしまいます。私の環境では32ビット版のMinGWが使われていました。

よって解決方法は簡単で、tdm-gcc から正しいバージョン(golangに合わせた)ものをダウンロードし、インストールましょう。make.bat 実行時に PATH を書き換えて、正しく選択するだけでも大丈夫です。

golang を使っているはずなのに、gcc のバージョンに依存するというは、盲点になりやすいですね。

残るは「(準備したはずのクロスコンパイル環境が)いつの間にか使えなくなっていた」のほうですが、これも make.bat を読み進めることで解決できます。該当箇所を示しましょう。

echo # Building compilers and Go bootstrap tool.
set buildall=-a
if x%1==x--no-clean set buildall=
.\cmd\dist\dist bootstrap %buildall% -v

dist.exebootstrap サブコマンドに -a オプションを指定すると、bootstrap の本体処理の前に cmd/dist/build.c 内の clean() 関数が実行されます。過去にビルドしたツールやランタイムを、この関数が全て消し去ってしまいます。

よって2つ目以降のクロスコンパイル環境を整備する際には make.bat --no-clean というように、make.bat--no-clean フラグを指定しましょう。

まとめ

make.bat は、何も指定しなければホスト環境用のツールとランタイムをビルドします。

ホスト環境はgccにより決定するので、意図と異なるgccを利用すると、誤ったホスト環境用のツールとランタイムに書き換わってしまいます。

またツールとランタイムのビルドでは、--no-clean を指定しなければ過去のビルドの成果物を全て削除します。これは過去のビルドの残骸が、なにか悪さをするの — C言語あたりを使ったことがあれば、誰もが1度は経験するアレ — を防ぐ目的があるのかもしれません。

加えてクロスコンパイル時には、make.bat はホスト環境用とターゲット環境用の、ツールとランタイムをビルドします。前述の削除と組み合わさって、2つめのターゲット環境用のツールとランタイムをビルドすると、1つめのそれらが消えてしまうという結果になります。