🚀

パッケージとアプリを同repoで公開し、アプリはgo installできるようにしたいときのTips

2022/02/03に公開約3,100字

問題設定

Go 1.16からgo installコマンドが利用可能になり、それなりの年月が経過しました。go installによりGoで書かれたプログラムを簡単にバイナリ形式で開発環境に導入することが可能になり、大変ありがたく思っています。

さて、次のようなディレクトリ構成を取ることが多々あるかと思います。プロジェクトのルートディレクトリにはmainを伴わないパッケージのソースコードを起き、それを使うアプリをcmd以下に配置するケースです。

./
 |- cmd/
 |   |- main.go
 |- package.go
 |- go.mod
 |- go.sum
package.go
package hoge

func fuga(){
}
go.mod
package github.com/tenkoh/hoge
cmd/main.go
package main

import "github.com/tenkoh/hoge"

func main(){
    hoge.fuga()
}

この時、cmd/の中身をgo installするにはどうしたら良いのでしょうか?私はなかなか分からず悩んでしまいました。

解決方法

何はともあれまずは公式ドキュメントを参照してみましょう。Go Modules Referenceに鍵がありそうです。

https://go.dev/ref/mod#go-install

例としてgoplsのインストール手順が紹介されています。

# Install the latest version of a program,
# ignoring go.mod in the current directory (if any).
$ go install golang.org/x/tools/gopls@latest

module名の形式を見る限り、goplsは今回の問題設定と同じように、プロジェクトルート以下のサブディレクトリ内のアプリをgo installさせているようです。ということはgoplsを参考にすれば、今回の悩み事を解決できそうです。

goplsの親ディレクトリであるtools@master branch内の構成は以下の通りです。

https://cs.opensource.google/go/x/tools/+/master:gopls/
./
 |- gopls/
 |    |- # (略)
 |    |- go.mod
 |- # (略)
 |- go.mod

想定はしていたのですが、go insatllでサブディレクトリを指し示す以上、やはりそのディレクトリにはgo.modがいるようです。

念のためgopls/go.modの中身も確認してみます。

https://cs.opensource.google/go/x/tools/+/master:gopls/go.mod
go.mod
module golang.org/x/tools/gopls

go 1.18

require (
	// 省略
)

replace golang.org/x/tools => ../

あれ、replaceディレクティブを使っています。公式リファレンスを読んだ際に次のような記述があったのですが、相反している気がします。

If the module containing packages named on the command line has a go.mod file, it must not contain directives (replace and exclude) that would cause it to be interpreted differently if it were the main module.

まあでも試してみましょう。ということで同じようにreplaceディレクトリを含むgo.modをサブディレクトリ内に配置し、githubへpushして、go installすると…

The go.mod file for the module providing named packages contains one or
more replace directives. It must not contain directives that would cause
it to be interpreted differently than if it were the main module.

全く同じ趣旨の警告が返ってきました。やっぱり…。何でだろう…と5分ほど悩みましたが、goplsのmaster branchのソースコードではなく、@latest (現時点ではv0.7.5)のソースコードを確認すべきなのでは? と気が付くことができました。 確認してみると、やはりそちらにはreplaceディレクティブがありません。

https://cs.opensource.google/go/x/tools/+/refs/tags/gopls/v0.7.5:gopls/go.mod

ということで、replaceディレクティブ無しのgo.modをサブディレクトリ内に配置し、再度push⇒go installしたところ、無事にバイナリファイルがインストールできました。めでたし。

補足:バージョン管理について

自分でも忘れそうなので記載しておきます。
アプリ側のgo.modにおいて使用するパッケージ側のバージョンを指定しつつ、アプリ側にも明示的にバージョンを設定する方法です。

  1. パッケージにgitのtagを設定しリモートにpush(例:v0.0.1)
  2. アプリ側のgo.modに上記tagで設定したバージョンを指定
  3. アプリ側でgo mod tidyしておく
  4. この状態で新しくtagを設定する(例:cmd/v0.0.1)。tagはアプリの相対パス+セマンティックバージョニングにする必要がある。
  5. そのtagをリモートにpush

追記:説明が分かり辛かったので…。簡略化すると、プロジェクトのルートディレクトリにおいて次の操作をするという意味です。

git tag -a v0.0.1 -m "alpha release"
git push origin --tags
cd ./cmd
vim go.mod
# packageのバージョンに0.0.1を指定して保存
go mod tidy
cd ..
git add .
git commit
git tag -a cmd/v0.0.1 -m "alpha release of app"
git push
git push origin --tags

お疲れ様でした。これでcmd@latest => cmd@v0.0.1となります。

GitHubで編集を提案

Discussion

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