※ GoのロゴはGo's New Brandより引用
※ QEMUのアイコンはWelcome to QEMU’s documentation!より引用
この記事では
なお、本記事の内容とは関係ありませんがGithub Actions上でQEMUを利用する例をhttps://github.com/t-katsumura/qemu-actionsで公開しています。
要約
- Goでは、GOOS/GOARCHを用いて異なるCPUアーキテクチャ向けのバイナリをビルドできる
- クロスコンパイルしたバイナリの実行環境がなくてもある程度はエミュレータ(QEMU)で実行できる
- ただし簡単に実行できるのはLinuxとFreeBSDの環境に限る
- GOのテストは
-c
オプションでテストバイナリを作成してからQEMUで実行すると簡単
1. 異なるプラットフォーム向けにクロスコンパイルする
1.1 GOOS
とGOARCH
GOでバイナリをクロスコンパイルする際はGOOS
とGOARCH
の2つの環境変数を利用します。
環境変数に関する説明はInstalling Go from source #environmentに記載されています。
各CPUアーキテクチャの設定を行うためにGO<ARCH>
の環境変数も存在しています。
なお、本記事内ではOSは常にlinux
であることを前提とします。
またGO<ARCH>
形式の環境変数も扱わないのでご了承ください。
したがって、本記事内で設定するのはGOARCH
のみとなります。
GOOS/GOARCH
の組み合わせに関しては、Installing Go from source #environmentに記載がありますが、go tool dist list
コマンドで確認することもできます。
筆者の環境では以下のように表示されます。
$ go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
freebsd/arm64
freebsd/riscv64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/loong64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
netbsd/386
netbsd/amd64
netbsd/arm
netbsd/arm64
openbsd/386
openbsd/amd64
openbsd/arm
openbsd/arm64
openbsd/ppc64
openbsd/riscv64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
wasip1/wasm
windows/386
windows/amd64
windows/arm
windows/arm64
例えばwindows/arm
向けにビルドする際はGOOS=windows
、GOARCH=arm
の環境変数を設定します。
Linux上でクロスコンパイルするのであれば、例えば以下のようなコマンドになります。
$ GOOS=windows GOARCH=arm go build ./...
1.2 クロスコンパイルしてみる
検証に使用するコードを以下に示します。
このコードではok
と返すだけのHTTPサーバをポート:8080
で起動しています。
package main
import (
"net/http"
"runtime"
)
func main() {
println("GOOS: " + runtime.GOOS)
println("GOARCH: " + runtime.GOARCH)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
println("server listening on :8080")
http.ListenAndServe(":8080", nil)
}
ではまずネイティブコンパイルしてみます。
※ ビルド環境はlinux/amd64
となっています。
$ go build -o server main.go
$ ./server
GOOS: linux
GOARCH: amd64
server listening on :8080
問題なくサーバを起動できました。
もちろん以下のようにcurlでリクエストを投げるとレスポンスが返ってきます。
$ curl localhost:8080
ok
続いてクロスコンパイルで作成したバイナリを実行してみます。
GOOSはlinuxのまま(設定されなければビルド環境と同じ)、GOARCH=arm
を指定することとします。
$ GOARCH=arm go build -o server main.go
$ ./server
-bash: ./server: cannot execute binary file: Exec format error
cannot execute binary file: Exec format error
とエラーが発生しました。
実行環境はlinux/amd64なのにたいして、バイナリがlinux/arm用になっているので当然の結果です。
ではこのlinux/arm用のバイナリをlinux/amd64の環境で実行したいときにはどうすればよいか?
ここでエミュレータであるQEMUの登場です。
QEMUの使い方は次のセキュションに記載します。
2. クロスコンパイルしたバイナリを実行する。
2.1 QEMUのインストール
ここではQEMUのインストール方法を示します。
バイナリを実行するだけであれば、QEMU User space emulatorが便利なのでqemu-user
をインストールします。
User space emulatorを利用すると、バイナリを実行するのにわざわざ仮想マシンを作成する必要がありません。
ただし、ドキュメントに記載されている通り、User space emulatorは以下の環境でしか使えません。
MacやWindowsでは使えないのでご注意ください。
- Linux (referred as qemu-linux-user)
- BSD (referred as qemu-bsd-user)
なお、筆者の検証環境はlinux/amd64
(Ubuntu)なのでUser space emulatorが使えます。
User space emulatorは以下のようにインストールします。
OSのバージョンやパッケージマネージャによっては、かなり古いバージョンのQEMUがインストールされることがあるのでご注意ください。
バージョンが古すぎるとバイナリの実行に失敗することがあります。
# できるだけ新しいバージョンのQEMUをインストールするためにレポジトリを追加。
sudo add-apt-repository -y ppa:canonical-server/server-backports
sudo apt -y update
sudo apt -y install qemu-user
2.2 QEMUでバイナリを実行する
QEMUがインストールできたところで1.2 クロスコンパイルしてみるでビルドしたlinux/arm向けのバイナリを実行してみます。
利用するコマンドはqemu-arm
になります。
$ qemu-arm ./server
GOOS: linux
GOARCH: arm
server listening on :8080
linux/amd64の環境でlinux/armのバイナリを実行できました!!
もちろん、curlでリクエストを投げるとちゃんとレスポンスが返ってきます。
$ curl localhost:8080
ok
今回はarm向けのバイナリを実行するのでqemu-arm
を利用しましたが、対象のCPUアーキテクチャによってコマンドは異なります。
利用可能なコマンドはQEMU User space emulatorで確認できます。
例えば
- qemu-arm
- qemu-i386
- qemu-x86_64
- qemu-mips64
- qemu-ppc64
- qemu-riscv64
などになります。
2.3 Goのテストを実行する
さて、クロスコンパイルしたバイナリはQEMU User space emulatorで実行できることが確認できましたが、
linux/amd64
の環境でlinux/arm
のテストを実行したいときはどうすればよいでしょうか。
まずはとりあえず、qemu-arm go
のコマンドを実行してみます。
結果は以下の通り、エラーが発生しました。
$ qemu-arm go test ./...
Error while loading go: No such file or directory
goの実行ファイルがないと怒られているので、goバイナリを絶対パスで指定します。
結果は以下の通り、またしてもエラーが発生しました。
$qemu-arm /usr/local/go/bin/go test ./...
qemu-arm: /usr/local/go/bin/go: Invalid ELF image for this architecture
goバイナリ自体はlinux/amd64
ようなのでqemu-arm
コマンドに渡しても実行できないのは当然ではあります。
そこで別の方法を試してみます。
クロスコンパイルでテスト用のバイナリを生成し、生成したバイナリをqemu-arm
へ渡してみます。
GOではgo test
コマンドに-c
オプションを付けるとテスト用のバイナリが生成されます。
なお、この時生成されたバイナリの拡張子は.test
となります。
以下は適当なテストコードを実行した結果となります。
$ GOARCH=arm go test -c ./...
$ qemu-arm cross_compile.test
PASS
上記の結果からテストが正常に実行されていることがわかります。
つまり、特定のCPUアーキテクチャにおけるGOのテストは、テストバイナリを生成した後にエミュレータで実行することで実現できます。
まとめ
GOではクロスコンパイルする際にGOOS
とGOARCH
を設定します。
LinuxやFreeBSDでは、クロスコンパイルしたバイナリをQEMU User space emulatorを利用して簡単に実行することができます。
エミュレータ上でGOのテストを実行したいときはgo test -c
コマンドでテスト用のバイナリを生成したうえで、テストバイナリをQEMUに渡すことで実現できます。
GOでは簡単にクロスコンパイルができますが、テスト環境を用意することは容易ではありません。
ただ、オープンソースの開発などで複数のCPUアーキテクチャ向けのバイナリを配布したいケースはあるので、QEMUなどのエミュレータをうまく活用していきたいと思いました、