WSL2でGo 1.20のAPIをARM64向けにコンパイルした

## はじめに
最近、Go言語でAPIを開発しています。このAPIのバイナリを ARM64/AMD64向けに 自動でコンパイル&リリースするGitlab CIアクションを作りたくなったので、まずは ローカルのWSL2(Linux)でARM64向けのバイナリをコンパイルする方法を調べました。

## 前提
- Windows10 (AMD64)
- WSL2 (Ubuntu 20.04)
- Go 1.20.4

## やりたいこと
- Goで書いたAPIを `Linux(WSL2) AMD64`で `Linux ARM64`向けにコンパイルする。
- `CGO_ENABLED=1` でコンパイルする。 (github.com/mattn/go-sqlite3 を利用しているため)

## 手順

### 1. まずGoの実行環境を WSL2側に用意する
GoがそもそもWindowsにしか入っておらず、WSL2側に無かったので Go 1.20.4 をインストールします

```bash:WSL2シェル
# 前提として:　パッケージを更新
sudo apt-get update 
sudo apt-get -y upgrade

# 公式サイト上に書いてあるリンク先からバイナリをダウンロードする
wget https://go.dev/dl/go1.20.4.linux-amd64.tar.gz
# 展開する
sudo tar -xvf go1.20.4.linux-amd64.tar.gz
# ローカルユーザーのアプリ保存場所に移動
sudo mv go /usr/local
# (任意のエディタでユーザー設定ファイルを開く)
nano ~/.profile

# 下記コマンドを 末尾に追加する
# 作業ファイルの保存先
export GOPATH=$HOME/go-projects
# Go実行ファイルの位置
export GOROOT=/usr/local/go
# PATHを通す
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH 
```
※ Windows領域(/mnt配下)で実行すると時間がかかるので WSL領域(/home配下)で実行がおすすめです。
※ インストール前に 単にgoを実行しようとすると `もしかして: apt-get install golang` と出ますが そのコマンドだと古いバージョンがインストールされるため注意

ターミナルを開き直して、下記コマンドで Goのバージョンが確認できたらOK。
```bash
$ go version
go version go1.20.4 linux/amd64
```


### 2. APIをコンパイルしてみる。

#### 普通にコンパイル
Goで マルチアーキテクチャビルドするのは(通常)かなり簡単になっています。
とてもシンプルにビルドターゲットを指定するだけでビルドできます。

```bash
# 環境変数で 対象OSと 対象アーキテクチャを指定し ビルドするだけ
GOOS=linux   GOARCH=arm64 go build -o api_linux_arm64 main.go
GOOS=linux   GOARCH=amd64 go build -o api_linux_amd64 main.go
```

#### CGO_ENABLED=1でコンパイル
が、今回はCGO_ENABLED=1でビルドする必要があったため 下記のように ビルドしてみる。

```bash
$ CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -o server-linux-arm64 main.go
# runtime/cgo
gcc_arm64.S: Assembler messages:
gcc_arm64.S:30: Error: no such instruction: `stp x29,x30,[sp,'
gcc_arm64.S:34: Error: too many memory references for `mov'
gcc_arm64.S:36: Error: no such instruction: `stp x19,x20,[sp,'
gcc_arm64.S:39: Error: no such instruction: `stp x21,x22,[sp,'
gcc_arm64.S:42: Error: no such instruction: `stp x23,x24,[sp,'
gcc_arm64.S:45: Error: no such instruction: `stp x25,x26,[sp,'
gcc_arm64.S:48: Error: no such instruction: `stp x27,x28,[sp,'
gcc_arm64.S:52: Error: too many memory references for `mov'
gcc_arm64.S:53: Error: too many memory references for `mov'
gcc_arm64.S:54: Error: too many memory references for `mov'
gcc_arm64.S:56: Error: no such instruction: `blr x20'
gcc_arm64.S:57: Error: no such instruction: `blr x19'
gcc_arm64.S:59: Error: no such instruction: `ldp x27,x28,[sp,'
gcc_arm64.S:62: Error: no such instruction: `ldp x25,x26,[sp,'
gcc_arm64.S:65: Error: no such instruction: `ldp x23,x24,[sp,'
gcc_arm64.S:68: Error: no such instruction: `ldp x21,x22,[sp,'
gcc_arm64.S:71: Error: no such instruction: `ldp x19,x20,[sp,'
gcc_arm64.S:74: Error: no such instruction: `ldp x29,x30,[sp],'
```

うーん、謎のエラーが... 検索したところ [このIssue](https://github.com/golang/go/issues/28966
) がヒットするも、Goの問題ではない? ようで、クローズされてしまっていた。

更に調べるとこのページがヒットし、どうやらCCという環境変数でクロスコンパイラを指定する必要があることがわかった。

https://qiita.com/nakayamaatsushi/items/31a8e7f51fc550202e75

https://www.bytebase.com/blog/how-to-cross-compile-with-cgo-use-goreleaser-and-github-action/

#### CCを指定してコンパイル
```bash
$ CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build -o s
erver-linux-arm64 main.go
# runtime/cgo
cgo: C compiler "aarch64-linux-gnu-gcc" not found: exec: "aarch64-linux-gnu-gcc": executable file not found in $PATH
```

エラーが変わり、別途コンパイラの実行ファイルが必要ということがわかったため 更に検索した。

すると `aarch64-linux-gnu-gcc`には `g++-aarch64-linux-gnu` というコンパイラをインストールすると良いことがわかった。

https://nyonyo.hatenablog.com/entry/2019/07/07/222358

#### 必要なコンパイラをインストールした上でコンパイル

```
$ CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build -o server-linux-arm64 main.go
```

若干待ったあと、エラーなく 実行したフォルダ内にバイナリが生成された!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254984/173aa81e-6d0a-9420-a608-ddcdce09ede8.png)

## 感想
やっぱり環境整えるのは コード書くより大変! 書いている途中で調べたところ、今使っているSQLiteがCGO必須なだけで、[CGO不要なGoにポートしたSQLiteライブラリ](https://gitlab.com/cznic/sqlite
)もあったみたいです。(後で試してみます)


## 参考
- Ubuntu20.04にGoをインストールする手順
  - https://tecadmin.net/how-to-install-go-on-ubuntu-20-04/
- .profileファイルとは
  - https://www.wakuwakubank.com/posts/389-linux-bashrc/
- golang で複数プラットフォーム向けにクロスコンパイルする例
  - https://sig9.org/archives/4330
- Goのコンパイルと環境変数フラグ(公式)
  - https://go.dev/doc/tutorial/compile-install
  - https://pkg.go.dev/cmd/go#hdr-Environment_variables
- この記事と同じことをDockerコンテナ内で行う場合の方法
  - https://qiita.com/nakayamaatsushi/items/31a8e7f51fc550202e75
- CGO_ENABLED=1で同じエラーに詰まっている質問
  - https://stackoverflow.com/questions/51475992/cgo-cross-compiling-from-amd64linux-to-arm64linux
- Githubアクションを使い クロスコンパイルする方法の紹介
  - https://www.bytebase.com/blog/how-to-cross-compile-with-cgo-use-goreleaser-and-github-action/
  - https://github.com/goreleaser/goreleaser-cross
- `g++-aarch64-linux-gnu`について
  - https://packages.debian.org/ja/sid/g++-aarch64-linux-gnu