素朴に(Git, GitHubのみで)Go製CLIツールをリリースする

4 min read読了の目安(約4400字

はじめに

この記事は、初学者がGo製のCLIツールをリリースする方法を調べ、まとめたものになります。
GoReleaser, GitHubActionsなどを使用せずに、Git, GitHubのみで行う初歩的なものになります。
この記事の目標は、初学者が、自作のCLIツールをGitHubでバイナリのダウンロード、go installでツールをインストール可能な状態にし、リリースについて少しだけ理解することです。
クロスコンパイルについても扱いません。

この記事が他の人の参考になれば幸いです。
また、この記事の内容に間違った記載がありましたら、指摘していただけるとありがたいです。

環境

名前 バージョン
macOS Big Sur 11.2.1
Go 1.16

サンプルコード

サンプルとして、"Hello, World!"と出力するだけのhelloツールをリリースします。
Git, GitHubを利用し、リモートのリポジトリにもソースコードをあげている状態です。
外部パッケージとしてgo.uber.org/zapを使用してます。

helloツールのディレクトリ構造とソースコードは以下のようになっています。

hello
├── cmd
│   └── hello
│       └── main.go
├── go.mod
├── go.sum
└── hello.go

2 directories, 4 files
hello/cmd/hello/main.go
package main

import "github.com/minguu42/hello"

func main() {
	hello.Hello()
}
hello/hello.go
package hello

import (
	"go.uber.org/zap"
)

func Hello() {
	logger, _ := zap.NewDevelopment()
	logger.Info("Hello, World!")
}
hello/go.mod
module github.com/minguu42/hello

go 1.16

require (
	go.uber.org/multierr v1.6.0 // indirect
	go.uber.org/zap v1.16.0 // indirect
)

以下のように実行できます。

$ go run ./cmd/hello 
2021-02-26T13:23:08.053+0900    INFO    hello/hello.go:9        Hello, World!

バイナリにバージョンを埋め込む

基本的にGoのCLIツールをリリースするときは、ツールのバイナリを配布します。
配布されたバイナリからバージョンを確認できないと使用上不便になるので、バイナリにバージョンを埋め込むようにします。
go installでインストールした場合でも、バージョンを表示するために、バージョンはソースコードに明示的に記述し、リビジョンはビルド時に埋め込むという形を取ります。
flagパッケージを使用して、-v, --versionオプションでバージョンとリビジョンを表示するようにします。
ソースコードを以下のように変更します。

hello/cmd/hello/main.go
package main

import (
	"flag"
	"fmt"
	"github.com/minguu42/hello"
)

const version = "0.1.0"

var revision = ""

func main() {
	var showVersion bool
	flag.BoolVar(&showVersion, "v", false, "show version")
	flag.BoolVar(&showVersion, "version", false, "show version")
	flag.Parse()
	if showVersion {
		fmt.Println("version:", version)
		fmt.Println("revision:", revision)
		return
	}
	hello.Hello()
}

これで以下のようにバージョンを表示することができるようになりました。

$ go run ./cmd/hello --version
version: 0.1.0
revision: 

Gitでバージョンを表すタグをつける

Gitでバージョンを管理するためにタグをつけます。

# コメントなしで、タグをつける場合
$ git tag v0.1.0

# コメントをありで、タグをつける場合
$ git tag -a v0.1.0 -m "コメント"

# タグをGitHubにプッシュ
$ git push origin v0.1.0

# タグをつけたことを確認
$ git log --oneline
ffa7ac6 (HEAD -> main, tag: v0.1.0, origin/main) -v, --versionオプションの実装
e2d3338 リポジトリの初期化

# タグ一覧
$ git tag
v0.1.0

ソースコードをビルドし、バイナリを作成する

ソースコードをビルドして、GitHubに配置するようのバイナリを作成します。
ビルド時に、-ldflagsオプションを使用して、リビジョンの情報として、Gitのコミットのハッシュ値を埋め込みます。

$ go build -ldflags "-X main.revision=$(git rev-parse --short HEAD)" ./cmd/hello/

# バイナリが作成されているのを確認
$ ls
cmd             go.mod          go.sum          hello           hello.go        main

# revisionが埋め込まれていることを確認
$ ./hello -v
version: 0.1.0
revision: ffa7ac6

GitHubでリリースする

GitHubでリリースします。
リポジトリページの右側のReleasesCreate a new releaseをクリックします。
Tagsをクリックし、先ほどあげたタグが存在するので、タグの右側にある...からCreate releaseをクリックします。

Release TitleDescribe this releaseに適当な文字を入力し、
Attach binaries by dropping them here or selecting themに先ほど作成したバイナリを入れてください。
プレリリースの場合はThis is a pre-releaseにチェックをつけます。

入力し終わったらPublish releaseをクリックします。
GitHubでのリリースが完了しました。
リポジトリの右側のReleasesとして表示されているのが確認できると思います。

go installでインストールする

GitHubでのリリースは完了しましたが、go installでインストールできるか確認します。

# コマンドがないことを確認
$ hello
zsh: command not found: hello

# インストール
$ go install github.com/minguu42/hello/cmd/hello

# コマンドを実行
$ hello
2021-02-26T14:50:02.346+0900    INFO    hello/hello.go:9        Hello, World!

# バージョンを確認
$ hello -v
version: 0.1.0
revision:

go installでインストールできました!
なお、go installではビルド時にrevisionを埋め込んでいないので、空になっていることに注意してください。

課題

上記の方法では、go install github.com/minguu42/hello/cmd/hello@latestgo install github.com/minguu42/hello/cmd/hello@v0.1.0のようにサフィックスによるバージョン指定に対応することができていません。

これについて何か分かる方がいればコメントを頂けると幸いです。

参考