はじめに (対象読者・この記事でわかること)
この記事は、Go 言語で実務レベルのビルドを行うエンジニア、特にライブラリやプラグインを自前で静的アーカイブ(.a ファイル)として生成したいと考えている方を対象としています。Go のビルドプロセスに慣れた方でも、-buildmode=archive の具体的な挙動や適用シーンは意外と把握しづらいものです。本稿を読むことで、以下ができるようになります。
-buildmode=archiveが何をするのか、内部的な動きを理解できる- 単体テストや CI パイプラインでアーカイブを生成し、他のプロジェクトとリンクする手順が実装できる
- ビルド時に遭遇しやすいエラーや落とし穴を回避するコツが身につく
Go のパッケージ管理や go.mod が当たり前になっている現代において、低レベルなアーカイブ生成はパフォーマンス最適化やバイナリサイズ削減に有効です。記事執筆のきっかけは、社内プロジェクトで外部 C ライブラリと Go コードを組み合わせる際に、アーカイブ化の正しい手順が社内ドキュメントに無く、同僚が手探りで時間を浪費していたことです。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- Go 言語の基本的な構文と go build の標準的な使い方
- ターミナル(bash/zsh)での基本的なコマンド操作
- C 言語の静的ライブラリ(.a)やヘッダーファイルの概念(必須ではないが理解が深まります)
-buildmode=archive の概要と利用シーン
go build には様々なビルドモードが用意されており、-buildmode=archive はその中でも「静的アーカイブ」生成専用のモードです。通常、go build は実行可能バイナリ(executable)や共有ライブラリ(.so)を出力しますが、archive モードでは .a 形式のファイルを生成し、他の Go パッケージや外部リンカ(gcc、clang 等)からリンクできる形にします。
主な利用シーン
-
複数モジュール間での再利用
大規模プロジェクトでは、共通ロジックを別パッケージとして切り出し、アーカイブ化して他のモジュールからリンクすることでビルド時間を短縮できます。 -
C 言語との相互運用
cgoを使って Go と C を混在させる際、C 側の静的ライブラリに Go のコードをアーカイブとして組み込むことで、単一の .a にまとめられ、リンクが容易になります。 -
CI/CD パイプラインでのキャッシュ
ビルドステップで生成したアーカイブをキャッシュとして保存し、後続ステージで再利用すれば、インクリメンタルビルドが実現できます。
生成されるファイルの特徴
- 拡張子 .a: Go のビルドツールが内部的に使用するアーカイブ形式です。
go tool packで内容を確認できます。 - アーカイブ内部: オブジェクトファイル(.o)と、パッケージ情報を含む
__.PKGDEFが格納されています。 - リンク対象: 他の Go パッケージからは
go build/go test時に自動で参照されますが、外部ツールからは-lオプションで明示的に指定します。
具体的な手順と実装例
以下では、実際に -buildmode=archive を利用してライブラリを作成し、別プロジェクトからリンクするまでの流れをステップごとに解説します。
ステップ 1: 基本的なパッケージを作成
まずはアーカイブ化したい機能を持つシンプルなパッケージ mylib を用意します。
Bash$ mkdir -p $GOPATH/src/example/mylib $ cd $GOPATH/src/example/mylib
mylib.go:
Gopackage mylib // Add は 2 つの整数の合計を返します。 func Add(a, b int) int { return a + b }
この状態で通常のビルドを行うと、実行ファイルが生成されますが、ここではアーカイブ化します。
Bash$ go build -buildmode=archive -o mylib.a
実行すると mylib.a が作成されます。go tool pack で内容を確認できます。
Bash$ go tool pack r -p example/mylib mylib.a mylib.go $ go tool pack list mylib.a
ステップ 2: 別プロジェクトからのリンク
次に、main パッケージから mylib.a を利用する例です。
Bash$ mkdir -p $GOPATH/src/example/app $ cd $GOPATH/src/example/app
main.go:
Gopackage main /* #cgo LDFLAGS: -L../mylib -lmylib */ import "C" import ( "fmt" "example/mylib" ) func main() { fmt.Println("3 + 5 =", mylib.Add(3, 5)) }
ここで重要なのは -L と -l の指定です。-L../mylib でアーカイブがあるディレクトリを、-lmylib でファイル名(lib 接頭辞は不要)を指しています。
go build の際に CGO_LDFLAGS 環境変数でリンク情報を渡す方法もあります。
Bash$ export CGO_LDFLAGS="-L../mylib -lmylib" $ go build -o app $ ./app 3 + 5 = 8
このように、-buildmode=archive で生成した .a は cgo 経由でも普通の C 静的ライブラリ同様に扱えることが分かります。
ステップ 3: CI パイプラインでのキャッシュ活用
GitHub Actions の例を示します。キャッシュキーは go-mod-${{ hashFiles('go.sum') }} とし、ビルドしたアーカイブを保存します。
Yamlname: Go CI on: push: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v4 with: go-version: '^1.22' - name: Cache Go modules uses: actions/cache@v3 with: path: | ~/.cache/go-build ~/go/pkg/mod key: go-mod-${{ hashFiles('go.sum') }} restore-keys: | go-mod- - name: Build archive run: | cd $GITHUB_WORKSPACE/src/example/mylib go build -buildmode=archive -o mylib.a - name: Cache archive uses: actions/cache@v3 with: path: $GITHUB_WORKSPACE/src/example/mylib/mylib.a key: mylib-archive-${{ github.sha }} restore-keys: | mylib-archive- - name: Build application env: CGO_LDFLAGS: "-L$GITHUB_WORKSPACE/src/example/mylib -lmylib" run: | cd $GITHUB_WORKSPACE/src/example/app go build -o app - name: Run tests run: go test ./...
この構成では、アーカイブの生成とキャッシュを分離し、再ビルドの頻度を最小化できます。
ハマった点やエラー解決
1. cannot find package "example/mylib" エラー
- 原因:
go.modにreplaceが無かった、もしくは$GOPATHが正しく設定されていない。 - 解決策:
go.modのrequireにexample/mylib v0.0.0と書き、replaceでローカルパスを指す。
Gorequire example/mylib v0.0.0 replace example/mylib => ./../mylib
2. undefined reference to 'runtime.malg' エラー
- 原因: アーカイブに必要なランタイムシンボルが抜けている。
-buildmode=archiveだけだと Go ランタイムの一部は含まれません。 - 解決策:
-linkmode=externalと併用し、go build -buildmode=archive -linkmode=externalとするか、go build -buildmode=c-archiveを使用して Go ランタイムも同梱する。
3. アーカイブサイズが想定より大きい
- 原因: デバッグ情報(DWARF)が入っている。
- 解決策:
go build -buildmode=archive -trimpath -ldflags="-s -w"を付与し、シンボルテーブルとデバッグ情報を削除。
解決策のまとめ
| エラー | 原因 | 解決策 |
|---|---|---|
| cannot find package | go.mod の設定不備 | replace でローカルパス指定 |
| undefined reference | ランタイムシンボル欠如 | -linkmode=external か c-archive |
| 大き過ぎるサイズ | デバッグ情報 | -ldflags="-s -w" |
まとめ
本記事では go build -buildmode=archive の役割と具体的な活用方法 を解説しました。
- 概要: 静的アーカイブ(.a)を生成し、Go パッケージ間や C 言語とのリンクに利用できる
- 実装手順: パッケージ作成 → アーカイブビルド → 別プロジェクトからリンク → CI でのキャッシュ活用
- 注意点:
go.modの設定、ランタイムシンボルの扱い、デバッグ情報削除が重要
この手法を取り入れることで、ビルド時間の短縮やバイナリサイズの最適化、CI の効率化が期待できます。次回は -buildmode=c-archive と c-shared の比較 について、実際のプロダクション事例とともに掘り下げていく予定です。
参考資料
