go-release について
go-releaseは、Goモジュールやバイナリのリリース計画を支援するエンジニアリングスキルです。メンテナー向けに、API互換性の評価、非推奨化と移行、go.modの管理、リリース前の確認事項を扱います。
主な機能
- 01Go API変更の互換性評価
- 02非推奨化と移行経路の計画
- 03go.modのバージョンと依存関係の管理
- 04バイナリ配布方法の確認
- 05リリース前検証項目の活用
活用シーン
- 01Goモジュールのリリース計画
- 02公開API変更が互換性を損なうかの判断
- 03非推奨化と移行案内の準備
- 04リリースタグ前のビルドとテストの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ spf13/ go-skills --skill go-releaseインストール後、Claude Code で/go-releaseを入力して読み込まれたか確認します。
同梱ファイル.claude-plugin/plugin.json
詳細
Go リリースエンジニアリング:バージョン、互換性、配布
リリースは約束です。バージョンにタグを付け、モジュールプロキシ経由で取得できるようにした時点で、そのバージョンは変更できなくなり、誰かが永続的に依存することになります。このスキルでは、その約束を適切に果たす方法を、ライブラリ(API 互換性)とアプリケーション(バイナリ配布)の両方について説明します。
有効にするタイミング
- Go モジュールまたはバイナリのリリースを作成、計画するとき
- 公開済みライブラリに対して、変更が破壊的かどうかをレビューするとき
- エクスポートされた API を非推奨にする、または削除するとき
- 依存先に影響する形で
go.modを編集するとき(go ディレクティブ、依存関係) - バイナリ配布を設定するとき(GoReleaser、ldflags、マルチプラットフォームビルド)
- 不適切なリリースから復旧するとき
互換性の約束
Go のバージョニングの意味
- v0.x.y — 互換性の約束はありません。自由に破壊的変更を加えられますが、リリースノートにその内容を記載してください。広く使われているライブラリがいつまでも v0 にとどまるのは責任逃れです。依存している側には約束を受ける権利があります。
- v1.x.y — 約束する内容は、v1.0.0 向けに書かれたコードが、それ以降のすべての v1 リリースでもコンパイルできることです。マイナーバージョンでは機能を追加し、パッチバージョンでは修正します。何も削除せず、互換性を損なう変更も一切加えません。
- v2 以降 — 異なるインポートパス(
module github.com/you/lib/v2としてモジュールを定義し、.../lib/v2としてインポートする)を持つ、別のモジュールです。これはセマンティックインポートバージョニングと呼ばれ、任意ではありません。/v2のモジュールパスを使わずに v2 タグを付けると、モジュールモードの利用者にとって壊れた状態になります。
メジャーバージョンは v1 のまま維持する計画を立ててください。 メジャーバージョンを上げるとエコシステムが分断され、依存先はすべて手動で移行しなければならず、トランジティブ依存関係では同じビルドに v1 と v2 が混在します。Cobra、pflag、標準ライブラリは、置き換えるのではなく機能を拡張することで、10 年間 v1 を維持してきました。加算的な変更では本当に修正できない API がある場合に限って v2 を検討してください。そして、新しいライブラリを立ち上げるのと同じコストがかかるものとして扱ってください。
実際に破壊的変更となるもの
LLM も人間も、これを誤って判断しがちです。コンパイラーから見て破壊的な変更は次のとおりです。
| 変更 | 破壊的? | 理由 |
|---|---|---|
| エクスポートされた識別子の削除または名前変更 | はい | 明白です |
| 関数/メソッドのシグネチャ変更 | はい | 可変長引数パラメーターの追加も含みます。シグネチャが変わります |
| エクスポートされたインターフェースへのメソッド追加 | はい | 外部の実装者がそのインターフェースを満たさなくなります |
| 構造体へのフィールド追加 | 通常はいいえ | キーなしの複合リテラルを使う呼び出し側を破壊します。また、型が完全に一致する必要がある代入にも影響します。キー付きリテラルのみを使うよう文書化していれば安全です |
| 構造体へのメソッド追加 | いいえ | 相手側のコードで、埋め込み型からプロモートされたメソッドと衝突する場合を除きます。これはまれで、許容されるリスクです |
| 新しい関数、型、パッケージの追加 | いいえ | そのためにマイナーバージョンがあります |
| 型指定なし定数の値の変更 | はい | 呼び出し側のバイナリにコンパイル時に組み込まれます |
| 入力検証の厳格化 | 動作上ははい | 以前は動作していたコードがエラーになるため、破壊的変更として扱ってください |
| エラーメッセージの変更 | いいえ | ただし、文字列の一致を判定している利用者には破壊的です。その代わりに errors.Is で判定できるセンチネルエラーを用意してください |
go.mod の go ディレクティブを引き上げる | 実質的にははい | 古いツールチェーンを使う依存先ではビルドできなくなります。go.mod の適切な管理を参照してください |
インターフェースの落とし穴は強調しておく必要があります。 ライブラリが利用者によって実装されるインターフェースをエクスポートしている場合、v1 では変更できません。これを前提に設計してください。エクスポートするインターフェースを小さく安定したものにするか、非エクスポートのメソッドを追加して自分だけが実装できるようにするか、インターフェース自体を定義せずに関数や構造体を受け取るようにしてください(インターフェースを定義するのは利用者側です。go スキルを参照)。
目視ではなく、機械的に検証する
# Before every tag — compares against the latest released version
go run golang.org/x/exp/cmd/gorelease@latest
# Or compare two specific API surfaces
go run golang.org/x/exp/cmd/apidiff@latest old/ new/
gorelease は互換性のない変更を報告し、次に付けるべき正しいバージョン番号を提案します。エクスポートされた識別子に触れるすべての PR で CI 上で実行してください。レビュー時に破壊的変更を見つけるのは無料ですが、タグを付けた後で見つけると、メジャーバージョンへの移行かリリースの撤回が必要になります。
非推奨化:追加機能としての回避策
削除はできませんが、非推奨にはできます。この規約は機械可読で、gopls、staticcheck、pkg.go.dev によって認識されます。
// Deprecated: Use [ParseContext] instead, which supports cancellation.
// This function will be maintained but not extended.
func Parse(input string) (*Result, error) { ... }
- 認識されるには、段落を正確に
Deprecated:で始める必要があります。 - 非推奨 API は v1 で永久に動作し続けます。非推奨化は新しいコードを別の方法へ誘導するものであり、削除を許可するものではありません。
- 代替手段を必ず明示してください。移行経路のない非推奨化は、ただの侮辱です。
- 非推奨化はマイナーリリースで行い、パッチリリースで黙って行ってはいけません。
代替手段の名前だけでなく、移行手段も提供する
代替手段の名前を示すのは、非推奨化の半分にすぎません。Go 1.26 以降では、go fix が Go の モダナイザー の実行場所になります。これは、go vet 形式のアナライザースイートと、//go:fix inline ディレクティブによって動作するソースレベルのインライナーを備えています(従来のフィクサーは 1.26 で削除されました)。薄い非推奨ラッパーに注釈を付けると、go fix が呼び出し元のコードを代替手段に書き換えます。
// Deprecated: Use [ParseContext] instead, which supports cancellation.
//
//go:fix inline
func Parse(input string) (*Result, error) {
return ParseContext(context.Background(), input)
}
- 本体が代替手段への単一の委譲になっているラッパーにディレクティブを付けてください。インライナーは各呼び出し箇所を代替手段を使う形に書き換えます。
- Go 1.26 以降を使う依存先向けの移行手順として、リリースノートに
go fix ./...を記載してください。実行可能な書き換えを提供する非推奨化は 1 回のコミットで解消できますが、文章による提案だけのものは何年も残り続けます。 - Go 1.27 では
atomictypes、embedlit、slicesbackward、unsafefuncsのモダナイザーが追加され、waitgroupアナライザーの名前がwaitgroupgoに変更されます。fmtappendfアナライザーはスタイル上の理由で削除されたため、もう実行されません。これを待つ必要はありません。
リリースに向けた go.mod の管理
go.mod は API の一部です。最小バージョン選択により、自分の最小バージョンが依存先の最小バージョンになります。
goディレクティブは、すべての依存先に課す下限です。 実際に必要とする最も古いバージョンを設定し、たまたま使っている最新バージョンを設定しないでください。標準ライブラリの新しいヘルパーを 1 つ使うためだけにこれを引き上げると、下流の依存関係ツリー全体にアップグレードを強いることになります。ライブラリの場合、これは既定の選択ではなく、判断を伴う決定です。Go 1.26 以降では、go mod initはすでに低めのバージョンを設定します。1.N.Xのツールチェーンはgo 1.(N-1).0を記述し、1.Nのプレリリースはgo 1.(N-2).0を記述します。生成される下限は保守的に設定されています。ディレクティブが自分のマシンにインストールされたツールチェーンと一致すると決めつけず、リリースノートで変更を明記したうえで、意図的にのみ引き上げてください。toolchainディレクティブはローカルの選好であり、下限ではありません。 使用したいツールチェーンを記録するもので、依存先を制限しません。CI で固定するのは問題ありませんが、到達可能なブートストラップ経路を確保してください。Go 1.26 のブートストラップには Go 1.24.6 以降が必要で、Go 1.28 では Go 1.26 以降のマイナーリリースが必要になる見込みです。replaceディレクティブを含むリリースには、決してタグを付けないでください。 依存先のビルドでは無視されるため、自分がテストしたモジュールと、依存先が取得するモジュールが異なります。go.workの前提についても同様です。ワークスペースは配布されません。- タグを付ける前に
go mod tidyを実行してください。 go.mod に不要な記述があると、下流の全員に不要な要件を配布することになります。Go 1.27 以降では、tidy はgoディレクティブが 1.27 以降のモジュールについて、重複するrequireブロックも統合します。コメントブロックを維持しながら、ブロックを最大 2 つ(直接依存と間接依存)にします。そのため、1.27 を採用してから初めて tidy を実行すると、go.mod の広い範囲が書き換えられる場合があります。この再編成は想定どおりであり、レビューで指摘する問題ではありません。差分を読みやすく保つため、独立した変更としてコミットしてください。 - 依存関係の最小バージョンは低く保ってください。 すべての依存関係で最新パッチを要求すると、依存先に更新を強いることになります。必要な機能を備えた最も古いバージョンを要求してください。
GODEBUG と GOEXPERIMENT:期限であり、逃げ道ではありません
Go で、黙って変更できない動作を変更する場合、GODEBUG 設定または GOEXPERIMENT フラグで変更を段階的に導入し、1、2 リリース後にその回避策を削除します。削除は予定されており、変更の余地はありません。
asynctimerchan(Go 1.23 で追加)は Go 1.27 で完全に削除されます。timeパッケージのチャネルは常にバッファなしになります。goroutineleakプロファイルは実験段階を終了します。Go 1.26 ではGOEXPERIMENT=goroutineleakprofileで実験的に利用でき、Go 1.27 ではruntime/pprofおよび/debug/pprof/goroutineleakで一般公開され、GOEXPERIMENT は削除されます。GOEXPERIMENT=nogreenteagc(Go 1.26、Green Tea GC の無効化)とGOEXPERIMENT=nosizespecializedmalloc(Go 1.27、サイズ特化型アロケーションの無効化)は、どちらも一時的なものとして告知されています。前者は Go 1.27、後者は Go 1.28 で削除される予定です。GOEXPERIMENT=nojsonv2(Go 1.27)は v2 より前のencoding/json実装に戻すもので、同様に将来のリリースで削除される予定です。
無効化オプションは移行期限であり、永続的な逃げ道ではありません。
- ライブラリは依存側の動作を固定できません。 go.mod の
godebugスタンザやソース内の//go:debugコメントはメインモジュールに適用されます。依存側のビルドには引き継がれません。無効化オプションによって利用者に古い動作を維持するとリリースノートで約束しているなら、その約束はすでに破られています。この期間を利用して自分のコードを移行し、新しい動作をリリースしてください。 - 変更を事前に試すには
godebugエントリーを使います。 go.mod のgodebugスタンザで設定を 新しい 値にして、デフォルトになる動作を前提に開発してください。この期間は、リリースに合わせるのではなく、自分のスケジュールで移行できるよう設けられています。 - Go 1.27 では、古い設定の固定がビルドエラーになります。
goコマンドは、サポートが削除されたGODEBUG設定について、最終的なデフォルト値に設定されている場合に限り認識します。それより古い値に固定したgodebugエントリーや//go:debugコメントがあると、ビルドに失敗します。これが期限を強制する仕組みです。削除された回避策を固定しても動作は維持されず、コンパイルできなくなります。 - 削除されるリリースを自分のリリースノートに明記してください。 古い動作を廃止して新しい動作を採用する場合、どの Go リリースで回避策が削除されるのか、また依存側で何を変更する必要があるのかを記載してください。これは非推奨 API について求められる移行経路の提示と同じ義務です。
テスト用の設定も同じルールに従います。テストで暗号学的な乱数を決定的にするには、サポートされている方法として testing/cryptotest.SetGlobalRandom を使います(Go 1.26)。cryptocustomrand=1 GODEBUG は古い動作を一時的に復元するためだけに存在します。これを基盤としたテストインフラを構築しないでください。
リリースチェックリスト
go mod tidy && git diff --exit-code go.mod go.sum # no surprises
go build ./...
go vet ./...
go test -race ./...
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
go run golang.org/x/exp/cmd/gorelease@latest # libraries: API check + version suggestion
go 1.27 ディレクティブを指定した Go 1.27 以降では、go mod tidy により重複した require ブロックが統合されることがあります(直接依存と間接依存の最大 2 つにまとめられ、コメントは保持されます)。これは想定される動作なので、内容を確認してください。また、この再編成を無関係な依存関係の変更と捉えないでください。
続いて:
git tag -a v1.4.0 -m "v1.4.0" # annotated tag, full semver, leading v
git push origin v1.4.0
- モジュールのルートにタグを付けます。複数モジュールのリポジトリでは、モジュールのディレクトリを接頭辞として付けます。
submod/v1.4.0 - 人向けのリリースノートを作成します。新機能、非推奨になったもの、動作変更を記載してください。非推奨になった項目には、移行ガイドへのリンクを付けます。
//go:fix inlineラッパーを含めてリリースした非推奨項目では、移行手順は 1 行で済みます。Go 1.26 以降を使う依存側はgo fix ./...を実行します。必ず明記してください。このディレクティブは、コマンドの実行が必要だと知らない人には何もしてくれません。- プラットフォームや最低 OS バージョンの変更があれば、同じリリースで明記してください(下記参照)。API に変更がなくても、サポートに関する声明の変更にあたります。
プロキシは永続します:不適切なリリースを修正する
モジュールプロキシは、タグ付けされたすべてのバージョンを永久にキャッシュします。公開済みのバージョンを削除したり、タグを付け直したりしないでください — プロキシは元のバージョンを配信し続けるため、タグとプロキシの内容が食い違い、すべてのユーザーでチェックサムの検証に失敗します。
正しい修正方法は retract です。
// go.mod of a NEW release (e.g. v1.4.2)
retract (
v1.4.1 // Broke Parse on empty input; use v1.4.2.
)
次のリリースで撤回を公開してください。go get は撤回されたバージョンをスキップし、go list -m -u はそのバージョンを使っているユーザーに警告します。バージョン自体を撤回することもできます(v1.5.0 の go.mod に retract v1.5.0 を記述し、v1.5.1 として公開すると、両方が撤回されます)。撤回は、唯一存在する助言的な取り下げ方法です。
アプリケーションのリリース:バイナリの配布
CLI やサービスでは、リリース成果物はバイナリです。標準は GoReleaser です — シェルスクリプトや Makefile でクロスコンパイルのマトリックスを独自に組み立てないでください。
バージョン情報の埋め込み
ビルド時のバージョン情報を、CLI ですでに公開している変数に渡します(Version の扱いについては、cobra-viper スキルを参照してください)。
# .goreleaser.yaml
builds:
- env: [CGO_ENABLED=0]
flags: [-trimpath]
ldflags:
- -s -w
- -X github.com/you/app/cmd.version={{.Version}}
- -X github.com/you/app/cmd.commit={{.ShortCommit}}
- -X github.com/you/app/cmd.date={{.Date}}
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
CGO_ENABLED=0にすると、静的でポータブルなバイナリになります。依存関係で本当に必要な場合に限り、cgo を有効にしてください。-trimpathはバイナリからローカルのパスを取り除きます。サイズが小さくなり、再現性が高まり、パニック時に/home/you/src/...が漏れることもありません。-s -wはシンボルテーブルを取り除きます。リリースバイナリでは標準的な設定です。利用可能なコアダンプが必要な場合は省略してください。
より簡単な代替手段として、debug.ReadBuildInfo() を使う方法があります。go install でインストールした場合、ldflags を使わずにモジュールのバージョンと VCS リビジョンを取得できます。GoReleaser が必要になるまでは、妥当な既定の方法です。
配布チェックリスト
- チェックサム: GoReleaser は既定で
checksums.txtを生成します。公開してください。 - 署名/来歴: プロジェクトのユーザーが増え、サプライチェーンに関する質問が出てきたら、cosign による鍵不要の署名か、SLSA 来歴ワークフローを導入してください。週末のプロジェクトに、考えなしにこれを詰め込まないでください。
go installが常に動作すること:go install github.com/you/app@latestは、インフラ不要のインストール方法です。インストール先のパスを推測しやすいよう、mainはリポジトリのルートかcmd/appの下に置き、replace ディレクティブでインストールを壊さないでください。- パッケージマネージャー(Homebrew tap、deb/rpm 用の nfpm、Scoop):GoReleaser は設定からこれらすべてを生成できます。ユーザーから要望があってから追加してください。
プラットフォームの最低要件はリリースノートに記載する
サポート対象と最小 OS バージョンは Go のリリースごとに変わります。サポート終了済みのターゲットを含むクロスコンパイルのマトリックスからは、誰も実行できない成果物が生成されます。マトリックスやサポート方針を公開する前に、次の点を確認してください。
- macOS: Go 1.26 は、ツールチェーンが macOS 12 Monterey 上で動作する最後のリリースです。Go 1.27 では macOS 13 Ventura 以降が必要です。CI で
darwinの成果物をビルドする場合、保証する最低バージョンを明記してください。 windows/arm(32 ビット)は Go 1.26 で削除されました —.goreleaser.yamlのgoos/goarchから削除してください。linux/ppc64(ビッグエンディアン)は Go 1.27 で ELFv2 ABI に移行しました — このターゲット向けの cgo と手書きのアセンブリは、サポートを表明する前に更新が必要になる場合があります。freebsd/riscv64は動作しないものとしてマークされています — サポート対象として宣伝したり、成果物を配布したりしないでください。- 追跡する価値のあるその他のターゲット変更:Go 1.26 で
linux/riscv64に race detector が追加され、s390xでは引数と戻り値がレジスターで受け渡されるようになりました。
よくある間違い
- マイナーリリースでエクスポートされたインターフェースにメソッドを追加する — 典型的な、気付かれにくい互換性破壊です。
goreleaseを実行すれば検出できます。 - リリースに失敗した後でタグを付け直す — プロキシにはすでに古いバイト列が保存されているため、チェックサムを世界中で壊すことになります。リリースを撤回して、次のバージョンを公開してください。
- モジュールパスを
/v2に変更せずに v2.0.0 にタグを付ける — タグは存在しても、モジュールモードの利用者は使えません。 - go ディレクティブを安易に引き上げる — すべての依存先にツールチェーンのアップグレードを強いることになります。
go.modにreplaceを含めたままリリースする — 依存先では、テストしたものとは異なるバージョンの依存関係が使われます。- ドキュメントやテストでエラーメッセージの文言を安定した API として扱う — センチネルエラーをエクスポートしてください。メッセージの文言は自由に変更できます。
- CI で
GOOS/GOARCHのビルドループを手作業で作る — GoReleaser があるため、独自のマトリックスを用意しても複雑になるだけで、メリットはありません。 - タグを付ける前に
go mod tidyを実行しない — 公開後には修正できない、唯一の品質管理手順です。