Goブログ

Goモジュールの公開

タイラー・ブイ=パルスリッチ
2019年9月26日

はじめに

この投稿は、シリーズのパート3です。

注:モジュールの開発に関するドキュメントについては、モジュールの開発と公開を参照してください。

この投稿では、他のモジュールが依存できるように、モジュールを作成および公開する方法について説明します。

注意:この投稿では、v1までを含めた開発について説明します。v2に興味がある場合は、Goモジュール:v2以降を参照してください。

この投稿では、例としてGitを使用します。MercurialBazaarなどもサポートされています。

プロジェクトのセットアップ

この投稿では、例として使用する既存のプロジェクトが必要です。したがって、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から始める必要があります。

新しいバージョンにタグを付けるにはいくつかの手順があります

  1. go mod tidyを実行します。これにより、モジュールに累積した不要な依存関係が削除されます。

  2. すべてが機能していることを確認するために、最後にgo test ./...を実行します。

  3. git tagを使用して、新しいバージョンでプロジェクトにタグを付けます。

  4. 新しいタグをオリジンリポジトリにプッシュします。

$ 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/hellov0.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とは異なります)。

SplitSplitNが与えられた場合、ReplaceReplaceNのような関数が予想されます。しかし、既存のReplaceは、呼び出し元を壊さずに変更することはできませんでした。そのため、Go 1.12で、新しい関数ReplaceAllを追加しました。結果のAPIは少し奇妙です。SplitReplaceの動作が異なるためですが、その不整合は破壊的な変更よりも優れています。

example.com/helloのAPIに満足し、最初の安定バージョンとしてv1をリリースするとします。

v1にタグ付けすることは、v0バージョンにタグ付けするのと同じプロセスを使用します。つまり、go mod tidygo 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/hellov1APIが固定されます。これは、APIが安定しており、快適に使用できることを全員に伝えます。

結論

この投稿では、セマンティックバージョンでモジュールにタグを付けるプロセスと、v1をリリースするタイミングについて説明しました。今後の投稿では、v2以降でモジュールを維持および公開する方法について説明します。

Goの依存関係管理の将来を形作るためのフィードバックを提供するには、バグレポートまたはエクスペリエンスレポートをお送りください。

Goモジュールの改善にご協力いただきありがとうございます。

次の記事:Go 1.13でのエラーの処理
前の記事:Go 1.13がリリースされました
ブログインデックス