`go install` で、コマンドのパッケージパスとバージョンを保持しておきたい

どうも。昨日初冠雪した富士山を眺めつつ記事を書いている、アユタヤドットコムの那賀です。

go 1.16 から、グローバルへのツールのインストールには go install を使い、開発中のライブラリの管理には go get を使うということで、きっちり分かりやすくなりました(参考: Go1.16からの go get と go install について - Qiita

しかしこの go install は、 $GOPATH/bin にパッケージ末尾の名前でコロンと実行ファイルを転げてくれるだけというとても思い切りの良い仕様なので、いくつかの情報が落ちます。 // go command - cmd/go - pkg.go.dev

具体的には、以下がやや辛い。特に、今入っているコマンドが最新なのかを確認したい場合や、バージョンアップを検討したい時などに。まあ、定期的に全部のコマンドを、問答無用で @latest で上書きするような go install を列挙したスクリプトを実行するのでも、運用上は特に困らないとは思うんですけども。

  • パッケージ名のフルパスが分からなくなるので、どこから入れたか分からなくなる
  • どのバージョンを入れたか分からなくなる

簡易的にそれらの情報をファイル名として保持し、コマンド名へシンボリックリンクを張るようなシェルスクリプトを書いてみました。

$ go-install github.com/ericchiang/pup@v0.3.8
$ ls -l ~/go/bin/pup*
pup -> pup-github.com%2Fericchiang%2Fpup@v0.3.8
pup-github.com%2Fericchiang%2Fpup@v0.3.8
$ go-install github.com/ericchiang/pup@latest
$ ls -l ~/go/bin/pup*
pup -> pup-github.com%2Fericchiang%2Fpup@v0.4.0
pup-github.com%2Fericchiang%2Fpup@v0.3.8
pup-github.com%2Fericchiang%2Fpup@v0.4.0

要点としては、パッケージ名を後ろ側から切り詰めながらモジュール名を探すところと、 go list -m --versions で対象のモジュールのバージョン情報を取り寄せているところだけです。簡易なものなので、複数指定に対応していない、インポートパスのワイルドカードを扱えない等の問題がありますが、ひとまず用をなすのでこのまま使ってみます。

#!/bin/bash
set -o nounset -o errexit -o pipefail

: ${bindir:=$(go env BINDIR)}
: ${bindir:=$(go env GOPATH)/bin}
: ${bindir:=$HOME/go/bin}
read -r package target_ver <<< $(echo "$1" | sed -nEe 's/^([^@]+)(@(.*))?/\1 \3/p')
module="$package"
while true
do
  modversions=($(go list -m --versions "$module"))
  test ${#modversions[@]} -gt 1 && break
  module=$(echo "$module"| sed -nEe 's@/[^/]+$@@p')
done
latest_ver="${modversions[${#modversions[@]}-1]}"
test -z "$target_ver" -o "$target_ver" = "latest" && target_ver=${latest_ver}
basename=$(basename ${package})
rm -f ${bindir}/${basename}
go install ${package}@${target_ver}
name=$(echo "${basename}-${package}@${target_ver}" | sed -Ee 's@/@%2F@g')
mv -f ${bindir}/${basename} ${bindir}/${name}
ln -sf ${name} ${bindir}/${basename}

本当は、Golang のライブラリでパッケージ情報を問い合わせることができれば Go でツールを書こうかと思ったのですが、それらは軒並み Go 本体の internal の機能なので、呼べないんですよね…どこかに良いライブラリは無いかしら。

では。