Goブログ
Goモジュールの公開
はじめに
この投稿は、シリーズのパート3です。
- パート1 — Goモジュールの使用
- パート2 — Goモジュールへの移行
- パート3 — Goモジュールの公開 (この記事)
- パート4 — Goモジュール:v2以降
- パート5 — モジュールの互換性を維持する
注:モジュールの開発に関するドキュメントについては、モジュールの開発と公開を参照してください。
この投稿では、他のモジュールが依存できるように、モジュールを作成および公開する方法について説明します。
注意:この投稿では、v1
までを含めた開発について説明します。v2
に興味がある場合は、Goモジュール:v2以降を参照してください。
この投稿では、例としてGitを使用します。Mercurial、Bazaarなどもサポートされています。
プロジェクトのセットアップ
この投稿では、例として使用する既存のプロジェクトが必要です。したがって、Goモジュールの使用記事の最後に記載されているファイルから始めます。
$ cat go.mod
module example.com/hello
go 1.12
require rsc.io/quote/v3 v3.1.0
$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
$ cat hello.go
package hello
import "rsc.io/quote/v3"
func Hello() string {
return quote.HelloV3()
}
func Proverb() string {
return quote.Concurrency()
}
$ cat hello_test.go
package hello
import (
"testing"
)
func TestHello(t *testing.T) {
want := "Hello, world."
if got := Hello(); got != want {
t.Errorf("Hello() = %q, want %q", got, want)
}
}
func TestProverb(t *testing.T) {
want := "Concurrency is not parallelism."
if got := Proverb(); got != want {
t.Errorf("Proverb() = %q, want %q", got, want)
}
}
$
次に、新しいgit
リポジトリを作成し、最初のコミットを追加します。独自のプロジェクトを公開する場合は、LICENSE
ファイルを含めるようにしてください。go.mod
を含むディレクトリに移動してから、リポジトリを作成します
$ git init
$ git add LICENSE go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: initial commit"
$
セマンティックバージョンとモジュール
go.mod
で必要なすべてのモジュールには、セマンティックバージョンがあります。これは、モジュールをビルドするために使用する依存関係の最小バージョンです。
セマンティックバージョンの形式はvMAJOR.MINOR.PATCH
です。
- モジュールの公開APIに下位互換性がない変更を加える場合は、
MAJOR
バージョンをインクリメントします。これは、どうしても必要な場合にのみ行う必要があります。 - 依存関係の変更や、新しい関数、メソッド、構造体フィールド、または型の追加など、APIへの下位互換性のある変更を行う場合は、
MINOR
バージョンをインクリメントします。 - モジュールの公開APIまたは依存関係に影響を与えない、バグの修正などの小さな変更を加えたら、
PATCH
バージョンをインクリメントします。
ハイフンとドットで区切られた識別子を追加することで、プレリリースバージョンを指定できます(例:v1.0.1-alpha
またはv2.2.2-beta.2
)。通常のリリースはgo
コマンドによってプレリリースバージョンよりも優先されるため、モジュールに通常のリリースがある場合は、ユーザーがプレリリースバージョンを明示的に要求する必要があります(例:go get example.com/hello@v1.0.1-alpha
)。
v0
メジャーバージョンとプレリリースバージョンは、下位互換性を保証しません。これらを使用すると、ユーザーに安定性のコミットメントを行う前にAPIを調整できます。ただし、v1
メジャーバージョン以降は、そのメジャーバージョン内での下位互換性が必要です。
go.mod
で参照されるバージョンは、リポジトリでタグ付けされた明示的なリリース(例:v1.5.2
)であるか、特定のコミットに基づく疑似バージョン(例:v0.0.0-20170915032832-14c0d48ead0c
)である場合があります。疑似バージョンは、特殊なタイプのプレリリースバージョンです。疑似バージョンは、セマンティックバージョンのタグを公開していないプロジェクトに依存する必要がある場合、またはまだタグ付けされていないコミットに対して開発する場合に便利ですが、疑似バージョンは安定した、または十分にテストされたAPIを提供すると想定すべきではありません。モジュールに明示的なバージョンでタグ付けすることは、特定のバージョンが完全にテストされており、使用する準備ができていることをユーザーに示します。
リポジトリにバージョンをタグ付けし始めたら、モジュールを開発する際に新しいリリースをタグ付けし続けることが重要です。ユーザーがモジュールの新しいバージョンをリクエストすると(go get -u
またはgo get example.com/hello
を使用)、go
コマンドは利用可能な最大のセマンティックリリースバージョンを選択します。そのバージョンが数年前のものであり、プライマリブランチから多くの変更があってもです。新しいリリースをタグ付けし続けることで、ユーザーは継続的な改善を利用できるようになります。
リポジトリからバージョンのタグを削除しないでください。バージョンにバグまたはセキュリティの問題が見つかった場合は、新しいバージョンをリリースします。削除したバージョンに依存している人がいる場合、ビルドが失敗する可能性があります。同様に、バージョンをリリースしたら、変更または上書きしないでください。モジュールミラーおよびチェックサムデータベースは、モジュール、そのバージョン、および署名された暗号化ハッシュを保存して、特定のバージョンのビルドが時間の経過とともに再現可能であることを保証します。
v0:最初の不安定なバージョン
v0
セマンティックバージョンでモジュールにタグを付けましょう。v0
バージョンは安定性を保証しないため、ほぼすべてのプロジェクトは、公開APIを調整する際にv0
から始める必要があります。
新しいバージョンにタグを付けるにはいくつかの手順があります
-
go mod tidy
を実行します。これにより、モジュールに累積した不要な依存関係が削除されます。 -
すべてが機能していることを確認するために、最後に
go test ./...
を実行します。 -
git tag
を使用して、新しいバージョンでプロジェクトにタグを付けます。 -
新しいタグをオリジンリポジトリにプッシュします。
$ go mod tidy
$ go test ./...
ok example.com/hello 0.015s
$ git add go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: changes for v0.1.0"
$ git tag v0.1.0
$ git push origin v0.1.0
$
これで、他のプロジェクトはexample.com/hello
のv0.1.0
に依存できるようになりました。独自のモジュールについては、go list -m example.com/hello@v0.1.0
を実行して、最新バージョンが利用可能であることを確認できます(この例のモジュールは存在しないため、バージョンは利用できません)。最新バージョンがすぐに表示されない場合、Goモジュールプロキシを使用している(Go 1.13以降のデフォルト)場合は、プロキシが新しいバージョンをロードする時間を少し待ってから、再度試してください。
公開APIに追加したり、v0
モジュールに破壊的な変更を加えたり、依存関係のマイナーバージョンまたはバージョンをアップグレードしたりする場合は、次のリリースのMINOR
バージョンをインクリメントします。たとえば、v0.1.0
の次のリリースはv0.2.0
になります。
既存のバージョンでバグを修正する場合は、PATCH
バージョンをインクリメントします。たとえば、v0.1.0
の次のリリースはv0.1.1
になります。
v1:最初の安定バージョン
モジュールのAPIが安定していると確信したら、v1.0.0
をリリースできます。v1
メジャーバージョンは、モジュールのAPIに互換性のない変更は行われないことをユーザーに伝えます。ユーザーは、新しいv1
マイナーリリースおよびパッチリリースにアップグレードでき、コードが壊れることはありません。関数とメソッドのシグネチャは変更されず、エクスポートされた型は削除されません。APIに変更がある場合、それらは下位互換性があり(たとえば、構造体に新しいフィールドを追加するなど)、新しいマイナーリリースに含まれます。バグ修正がある場合(たとえば、セキュリティ修正)、それらはパッチリリース(またはマイナーリリースの一部として)に含まれます。
場合によっては、下位互換性を維持すると、APIがぎこちなくなることがあります。それでもかまいません。不完全なAPIは、ユーザーの既存のコードを壊すよりも優れています。
標準ライブラリのstrings
パッケージは、APIの一貫性を犠牲にして下位互換性を維持している好例です。
Split
は、区切り文字で区切られたすべてのサブストリングに文字列をスライスし、それらの区切り文字間のサブストリングのスライスを返します。SplitN
は、返すサブストリングの数を制御するために使用できます。
ただし、Replace
は、先頭から置き換える文字列のインスタンスの数をカウントしました(Split
とは異なります)。
Split
とSplitN
が与えられた場合、Replace
やReplaceN
のような関数が予想されます。しかし、既存のReplace
は、呼び出し元を壊さずに変更することはできませんでした。そのため、Go 1.12で、新しい関数ReplaceAll
を追加しました。結果のAPIは少し奇妙です。Split
とReplace
の動作が異なるためですが、その不整合は破壊的な変更よりも優れています。
example.com/hello
のAPIに満足し、最初の安定バージョンとしてv1
をリリースするとします。
v1
にタグ付けすることは、v0
バージョンにタグ付けするのと同じプロセスを使用します。つまり、go mod tidy
とgo test ./...
を実行し、バージョンにタグを付け、タグをオリジンリポジトリにプッシュします
$ go mod tidy
$ go test ./...
ok example.com/hello 0.015s
$ git add go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: changes for v1.0.0"
$ git tag v1.0.0
$ git push origin v1.0.0
$
この時点で、example.com/hello
のv1
APIが固定されます。これは、APIが安定しており、快適に使用できることを全員に伝えます。
結論
この投稿では、セマンティックバージョンでモジュールにタグを付けるプロセスと、v1
をリリースするタイミングについて説明しました。今後の投稿では、v2
以降でモジュールを維持および公開する方法について説明します。
Goの依存関係管理の将来を形作るためのフィードバックを提供するには、バグレポートまたはエクスペリエンスレポートをお送りください。
Goモジュールの改善にご協力いただきありがとうございます。
次の記事:Go 1.13でのエラーの処理
前の記事:Go 1.13がリリースされました
ブログインデックス