🛠️

gup: go installしたバイナリの一括アップデートコマンド(仕様と今後)

2022/03/13に公開約8,500字

前書き

本記事は、gupコマンド(golang製)の仕様を説明する記事です。gupは、"$ go install"によってインストールしたコマンド($GOPATH/bin以下もしくは$GOBIN以下のコマンド)を一括で最新版に更新するコマンドです。現状、バージョン指定の更新機能はありません。

ネット上で「遅い」と評されていたので、v0.9.3で処理を並行化しています。そのため、現在は類似ツールの中でアップデート速度が速い方です。
デモ

過去に、個人ブログにgupの仕様を記載しましたが、改善要望を受け入れているうちに仕様が大幅に変わりました。そちらの記事をリライトするより、新規に書き直す方法を選び、Zennに初投稿しています。余談ですが「何度も記事を書く理由」は、使用者が増えるとツールに対する改善要望が増え、よりより仕様に改善できるチャンスが増えるからです。

インストール方法

現在は、"go install"によるインストール方法のみ対応しています。golang用のツールなので十分だと考えていますが、要望があれば.debや.rpmに対応します。

go install github.com/nao1215/gup@latest

なお、gupコマンドは、クロスプラットフォーム(Linux、Mac、Windows)対応です。

gupのサブコマンド一覧

gupコマンドは、Version 0.9.1時点で下表のサブコマンドを備えています。サブコマンドを指定しない場合は、ヘルプメッセージが表示されます。

サブコマンド 機能
update $GOPATH/bin以下もしくは$GOBIN以下のコマンドを最新版に更新
list $GOPATH/bin以下もしくは$GOBIN以下のコマンド情報を表示
export $GOPATH/bin以下もしくは$GOBIN以下のコマンド情報をgup.confへ書き出す
import gup.confの内容に従って、コマンドをインストールする
remove $GOPATH/bin以下もしくは$GOBIN以下のコマンドを削除
check $GOPATH/bin以下もしくは$GOBIN以下のコマンドが最新かどうかをチェック

updateサブコマンドの仕様

gupコマンドのメイン機能であり、$GOPATH/bin以下もしくは$GOBIN以下のコマンドを最新版に更新します。既に最新版の場合は "Already up-to-date" と表示し、gupコマンドによって更新した場合は "${旧バージョン} to ${最新バージョン}" と表示します。

以下、実行例と実行結果です。実行結果には存在しませんが、"$ go get"でインストールしたコマンドは、必ず更新失敗します(コマンドのパス情報が取得できないため)

$ gup update
gup:INFO : update all binary under $GOPATH/bin or $GOBIN
gup:INFO : [ 1/32] update success: github.com/cheat/cheat/cmd/cheat (Already up-to-date: v0.0.0-20211009161301-12ffa4cb5c87)
gup:INFO : [ 2/32] update success: fyne.io/fyne/v2/cmd/fyne_demo (Already up-to-date: v2.1.3)
gup:INFO : [ 3/32] update success: github.com/nao1215/gal/cmd/gal (v1.0.0 to v1.2.0)
 :
 : (省略)
 :
gup:INFO : [32/32] update success: github.com/nao1215/ubume/cmd/ubume (Already up-to-date: v1.5.0)

更新完了後は、デスクトップ通知が飛びます。デスクトップ通知用のアイコン画像は、go:embedでgupコマンドで埋め込まれており、gupコマンドの初回起動時に$HOME/.config/gup/assets以下にデプロイされます。

updateサブコマンドの引数としてコマンド名を渡すと、引数で指定したコマンドのみを最新バージョンに更新します。

$ gup update ubume lazygit
gup:INFO : update all binary under $GOPATH/bin or $GOBIN
gup:INFO : [1/2] update success: github.com/jesseduffield/lazygit (Already up-to-date: v0.32.2)
gup:INFO : [2/2] update success: github.com/nao1215/ubume/cmd/ubume (Already up-to-date: v1.5.0)

gupコマンドは、--dry-runオプションを備えているため、「どんな結果になるか知りたい(でも、更新はしたくない)」という場合は使ってみてください。

updateサブコマンドの内部仕様

gupコマンドの仕組みは非常に単純であり、$GOPATH/bin以下もしくは$GOBIN以下のコマンドに対して"$ go install ${コマンドパス}@latest"を順次実行しているだけです。

コマンドを更新するには、更新対象コマンドのimport path(例:github.com/nao1215/gup)が必要です。gupコマンドは、"$ go versin -m ${コマンドが格納されているパス}"の実行結果からimport pathを取得しています。

)$ go version -m ~/.go/bin/gup
/home/nao/.go/bin/gup: go1.17
	path	github.com/nao1215/gup  ★ ここがimport path情報
	mod	github.com/nao1215/gup	v0.7.3 ★ バージョン情報はここから取得
	(省略)

--dry-runオプションが有効の場合は、$GOPATH/binもしくは$GOBINを一時的に別のパスへ変更した後、ビルドを実行しています。--dry-runオプションが無効の場合と比較して、--dry-runオプションが有効時はフルビルドなので時間がかかります。

listサブコマンド

$GOPATH/bin以下もしくは$GOBIN以下のコマンド情報をリストアップします。表示する情報は、以下の3点です。

  • コマンド名
  • import path
  • バージョン
$ gup list
       cheat: github.com/cheat/cheat/cmd/cheat@v0.0.0-20211009161301-12ffa4cb5c87
   fyne_demo: fyne.io/fyne/v2/cmd/fyne_demo@v2.1.3
         gal: github.com/nao1215/gal/cmd/gal@v1.2.0
   germanium: github.com/matsuyoshi30/germanium/cmd/germanium@v1.2.2
      ginkgo: github.com/onsi/ginkgo/ginkgo@v1.16.5
  git-chglog: github.com/git-chglog/git-chglog/cmd/git-chglog@v0.15.1

export / importサブコマンド

export / importサブコマンドは、仕様不備の名残です。gupコマンドの初期仕様では、import pathをShellの実行履歴から取得し、 ~/.config/gup/gup.confにimport pathを残す仕様でした。そして、gup.confの内容をもとに更新を実施していました。

gup.confは"KEY = VALUE"の書式であり、KEYがコマンド名、VALUEがimport pathです。

$ cat ~/.config/gup/gup.conf 
cheat = github.com/cheat/cheat/cmd/cheat
fyne_demo = fyne.io/fyne/v2/cmd/fyne_demo
germanium = github.com/matsuyoshi30/germanium/cmd/germanium
ginkgo = github.com/onsi/ginkgo/ginkgo

仕様不備の内容は、「Shellの履歴は消えるため、全てのimport pathが得られない」という点です。この課題解決のために、現在の"go version -m"を用いる仕様に変わっています。

仕様不備を改善する際に、「環境Aでgup.confを作り、環境Bでgup.confの情報をもとにコマンドをインストールすれば、環境構築が楽になる」と思いつきました。イメージ的には、dotfilesを複数環境で共有する感じです。

そこで、現在はexportサブコマンドでgup.confを生成し、importサブコマンドでgup.confをもとにコマンドをインストールできるようになっています。

$ gup export
gup:INFO : Export /home/nao/.config/gup/gup.conf

$ gup import
gup:INFO : start update based on /home/nao/.config/gup/gup.conf
gup:INFO : update all binary under $GOPATH/bin or $GOBIN
gup:INFO : [ 1/32] update success: github.com/cheat/cheat/cmd/cheat (<from gup.conf> to v0.0.0-20211009161301-12ffa4cb5c87)
gup:INFO : [ 2/32] update success: fyne.io/fyne/v2/cmd/fyne_demo (<from gup.conf> to v2.1.3)
	(省略)

removeサブコマンド

removeサブコマンドは、$GOPATH/bin以下もしくは$GOBIN以下にあるコマンド名を引数として、当該コマンドを削除します。お試しでコマンドをインストールした時に、サクッと削除するために加えた機能です。

$ gup remove gal ubume subaru
gup:CHECK: remove /home/nao/.go/bin/gal? [Y/n] Y
gup:INFO : removed /home/nao/.go/bin/gal
gup:CHECK: remove /home/nao/.go/bin/ubume? [Y/n] Y
gup:INFO : removed /home/nao/.go/bin/ubume
gup:CHECK: remove /home/nao/.go/bin/subaru? [Y/n] Y
gup:INFO : removed /home/nao/.go/bin/subaru

--forceオプションを付与すると、削除確認がなくなり、コマンドを強制削除します。

$ gup remove --force lazygit
gup:INFO : removed /home/nao/.go/bin/lazygit

checkサブコマンド

checkサブコマンドは、$GOPATH/bin以下もしくは$GOBIN以下にあるコマンドが最新版かどうかをチェックします。この機能は、「何故gupコマンドは、更新不要なコマンドをインストール対象に含めるの?時間かかるだけじゃん」という意見があったため、実装しました。

内部的には、go list -m -f "{{.Version}}" $MODULE_PATH@latestで、最新バージョンを取得しています。

$ go list -m -f "{{.Version}}" github.com/nao1215/gup@latest
v0.9.0

上記のコマンドは、ネットワーク経由で最新バージョンを取得するため、遅い処理です。また、gupコマンドがupdateサブコマンドで用いる"go install"は、コマンドが最新版であればアップデートをスキップします。つまり、何も考えずにupdateサブコマンド("go install")を実行した方が速いです。

そのため、最新版かどうかを確認する機能をupdateサブコマンドに組み込まず、別のサブコマンド(checkサブコマンド)としました。

updateサブコマンドは、引数なしの場合は$GOPATH/bin以下もしくは$GOBIN以下にあるコマンド全てをチェックします。

$ gup check
gup:INFO : check binary under $GOPATH/bin or $GOBIN
gup:INFO : [ 1/33] check success: github.com/cheat/cheat (Already up-to-date: v0.0.0-20211009161301-12ffa4cb5c87)
gup:INFO : [ 2/33] check success: fyne.io/fyne/v2 (current: v2.1.3, latest: v2.1.4)
   :
gup:INFO : [33/33] check success: github.com/nao1215/ubume (Already up-to-date: v1.5.0)

gup:INFO : If you want to update binaries, the following command.
           $ gup update fyne_demo gup mimixbox 

他のサブコマンド同様、指定コマンドのみのバージョンチェックもできます。

$ gup check gal
gup:INFO : check binary under $GOPATH/bin or $GOBIN
gup:INFO : [1/1] check success: github.com/nao1215/gal (current: v1.0.0, latest: v1.2.0)

gup:INFO : If you want to update binaries, run the following command.
           $ gup update gal 

シェル補完ファイルの自動生成機能

皆さんは、ターミナル作業中に[TAB]キーを押して、入力中の単語を補完していると思います。gupコマンドは、必要であればbash、fish、zsh向けのshell completionファイルを自動生成し、gupコマンドに関係する単語を補完できるようにしています。

$ gup 
gup:INFO : create bash-completion file: /home/nao/.bash_completion
gup:INFO : create fish-completion file: /home/nao/.config/fish/completions/gup.fish
gup:INFO : create zsh-completion file: /home/nao/.zsh/completion/_gup

gupは、spf13/cobraを利用しているので、cobraが自動生成するshell completionをそのまま利用しています。cobraユーザーはご存知だと思われますが、cobraが面倒を見てくれるのはshell completion(文字列)を作成するところまでです。

そのため、gupコマンドではshell completion(文字列)をファイル化する処理を実装しています。どこにファイルを置けばいいかが分かっていれば難しい処理ではありません(調査が面倒でした)

今後(追加しそうな機能)

個人的に必要かなと考えている機能は、以下の通りです。

  • インストール時のビルドオプション対応
  • 簡単にgup.confを別環境へコピー
    • Gistを使えば実現できるが、あまり需要がなさそう

余談:自作OSSの宣伝は適度に必要

今まで、自作OSSの宣伝は自身の技術ブログに書くのみに留め、積極的な宣伝をしてきませんでした。しかし、gupコマンドはmattn氏にツイートされた後、GitHub Starが増え、機能に対する意見をいただくようになりました。

Starはモチベに繋がり、意見はソフトの改善に繋がり、貴重です。しかし、自作OSSは紹介記事がアップされた時やツイートされた時ぐらいしか注目されません。誰かが紹介記事を書いてくれる事は基本的にありえないので、「(自分で)Zennに書くしかねぇ!」と考えた次第です。

(ただし、自分しか使わなそうな自作OSSは、宣伝すると検索妨害かなと思わんでもない)

最後に、頂いた意見の一部を紹介して終わります。全部その通りだと思い、即座に修正しました。

  • $ gupを実行して更新開始するより、$ gup updateを実行して更新を開始する仕様が好ましい。この仕様変更で、不意に更新が開始される事を防止できる
  • 指定バイナリを更新する時、--fileオプションでコマンド名を指定するよりも、引数でそのまま指定した方が自然
  • 更新前後のバージョン情報を出力した方が良い
  • ユーザーに伝わらないエラーログ(gup:WARN : $GOPATH/bin or $GOBIN contains the directory)がでているよ

Discussion

ログインするとコメントできます