はじめに
この記事はシリーズの第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"
$
セマンティックバージョンとモジュール
セマンティックバージョンは`````vMAJOR.MINOR.PATCH`````の形式を持ちます。
- 公開APIに[後方互換性のない](https://golang.org/doc/go1compat)変更を加えた場合は、`````MAJOR`````バージョンをインクリメントします。これは絶対に必要な場合にのみ行うべきです。
- APIに対して後方互換性のある変更を加えた場合(依存関係の変更や新しい関数、メソッド、構造体フィールド、または型の追加など)、`````MINOR`````バージョンをインクリメントします。
- モジュールの公開APIや依存関係に影響を与えない小さな変更(バグ修正など)を行った後は、`````PATCH`````バージョンをインクリメントします。
ハイフンとドットで区切られた識別子を追加することで、プレリリースバージョンを指定できます(例:`````v1.0.1-alpha`````または`````v2.2.2-beta.2`````)。通常のリリースは、`````go`````コマンドによってプレリリースバージョンよりも優先されるため、ユーザーはモジュールに通常のリリースがある場合、プレリリースバージョンを明示的に要求する必要があります(例:`````go get example.com/[email protected]`````)。
`````v0`````メジャーバージョンとプレリリースバージョンは、後方互換性を保証しません。これにより、ユーザーに対して安定性の約束をする前にAPIを洗練させることができます。ただし、`````v1`````メジャーバージョン以降は、そのメジャーバージョン内で後方互換性が必要です。
`````go.mod`````で参照されるバージョンは、リポジトリ内でタグ付けされた明示的なリリース(例:`````v1.5.2`````)であるか、特定のコミットに基づく[擬似バージョン](7e2d4c469f5d263b.md#pseudo-versions)(例:`````v0.0.0-20170915032832-14c0d48ead0c`````)である可能性があります。擬似バージョンはプレリリースバージョンの特別なタイプです。擬似バージョンは、ユーザーがセマンティックバージョンタグを公開していないプロジェクトに依存する必要がある場合や、まだタグ付けされていないコミットに対して開発する必要がある場合に便利ですが、ユーザーは擬似バージョンが安定したり十分にテストされたAPIを提供するとは限らないことを前提にすべきではありません。モジュールに明示的なバージョンでタグ付けすることは、特定のバージョンが完全にテストされて使用可能であることをユーザーに示します。
リポジトリにバージョンでタグ付けを開始したら、モジュールを開発する際に新しいリリースにタグ付けを続けることが重要です。ユーザーがモジュールの新しいバージョンを要求すると(`````go get -u`````または`````go get example.com/hello`````を使用)、`````go`````コマンドは、たとえそのバージョンが数年前のものであり、主要ブランチから多くの変更がある場合でも、利用可能な最も大きなセマンティックリリースバージョンを選択します。新しいリリースにタグ付けを続けることで、進行中の改善をユーザーに提供できます。
リポジトリからバージョンタグを削除しないでください。バージョンにバグやセキュリティの問題が見つかった場合は、新しいバージョンをリリースしてください。削除したバージョンに依存している人々のビルドが失敗する可能性があります。同様に、一度バージョンをリリースしたら、それを変更したり上書きしたりしないでください。[モジュールミラーとチェックサムデータベース](https://golang.org/blog/module-mirror-launch)は、モジュール、そのバージョン、および署名された暗号ハッシュを保存して、特定のバージョンのビルドが時間の経過とともに再現可能であることを保証します。
<a name="v0-the-initial-unstable-version"></a>
## v0: 初期の不安定なバージョン
モジュールに`````v0`````セマンティックバージョンでタグ付けしましょう。`````v0`````バージョンは安定性の保証を行わないため、ほとんどのプロジェクトは公開APIを洗練させる際に`````v0`````から始めるべきです。
新しいバージョンにタグ付けするにはいくつかのステップがあります:
- 1*.* `````go mod tidy`````を実行して、モジュールがもはや必要でない依存関係を削除します。
- 2*.* すべてが正常に動作していることを確認するために、`````go test ./...`````を最終的に実行します。
- 3*.* [`````git tag`````](https://git-scm.com/docs/git-tag)を使用して新しいバージョンでプロジェクトにタグ付けします。
- 4*.* 新しいタグをオリジンリポジトリにプッシュします。
``````bash
$ 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
$
`
これで、他のプロジェクトはv0.1.0
のexample.com/hello
に依存できます。自分のモジュールについては、go list -m example.com/[email protected]
を実行して最新のバージョンが利用可能であることを確認できます(この例のモジュールは存在しないため、利用可能なバージョンはありません)。最新のバージョンがすぐに表示されない場合は、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の一貫性のコストで後方互換性を維持する良い例です。
しかし、Replace
は、Split
とは異なり、文字列を置き換えるインスタンスの数を最初から数えました。
Split
とSplitN
を考えると、Replace
やReplaceN
のような関数が期待されるでしょう。しかし、私たちは呼び出し元を壊さないように既存のReplace
を変更することができませんでした。したがって、Go 1.12では新しい関数ReplaceAll
を追加しました。結果として得られたAPIは少し奇妙ですが、Split
とReplace
が異なる動作をするため、その不一致は破壊的な変更よりも良いです。
`````v1`````にタグ付けするプロセスは、`````v0`````バージョンにタグ付けするプロセスと同じです:`````go mod tidy`````と`````go test ./...`````を実行し、バージョンにタグ付けし、タグをオリジンリポジトリにプッシュします:
``````bash
$ 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
$
`
この時点で、v1
のexample.com/hello
APIは確立されました。これは、私たちのAPIが安定していることを皆に伝え、安心して使用できることを示します。
結論
この記事では、セマンティックバージョンでモジュールにタグ付けするプロセスと、v1
をリリースするタイミングについて説明しました。今後の記事では、v2
以降のモジュールの維持と公開について説明します。
Goにおける依存関係管理の未来を形作るためにフィードバックを提供し、バグレポートや体験レポートをお送りください。
Goモジュールの改善に向けたすべてのフィードバックと支援に感謝します。