あえて`go install`を使わないユースケースに出会った時のメモ
go1.16
からモジュール対応モードがデフォルトになり、時を同じくしてgo get
の機能であったツールのビルド&インストールがgo install
に明確に切り出されたことは良く知られていると思います。
そんな折、あるプロジェクトのREADMEを読んでいたところ、少し前までgo install
でツールをインストールさせていたのに、go run -mod=mod
を使うように修正していることに気が付きました。はて、敢えて?と疑問に思って経緯を調べたので、その覚書を残しておきます。
本記事では以下の内容に触れます。
- あえて
go install
を使わないのはどんな時? -
-mod=mod
オプションについて深堀(これは蛇足)
go install
を使わないのはどんな時?
あえてRDBのORMであるent
の使い方を見てみましょう。
ent
はコード生成ツールを提供してくれています。その利用方法として次の例がGetting startedに記載されています。
go run -mod=mod entgo.io/ent/cmd/ent init Todo
あれ?私が以前に見た時は異なる記述だった気がします。幸いent
のWebサイトはGithubで管理されていますのでblameを確認してみます。やはり次のような説明でした。
# Install
go install entgo.io/ent/cmd/ent@latest
# Run. 事前に$GOBINを$PATHに加えておく必要あり。
ent init Todo
これが数回の変遷を経て現在の形に落ち着いたようです。Issueを確認すると、次のような検討があったみたいですね。
-
$GOBIN
を$PATH
に追加しておく説明はやめて、go install entgo.io/ent/cmd/ent@latest
したあとにgo run entgo.io/ent/cmd/ent
してねってだけ書いてあれば良いんじゃない?(良くないと思うんですが何故こうなった) -
go install
はgo get
と違ってgo.mod
を更新しないから、まっさらなプロジェクトで上記を実行するとモジュールが見つからなくてエラーになるよ - じゃあ
go run
するときに-mod=mod
オプションをつけたら解決するね。(詳細は後述)そうすればそもそもgo install
いらなくない?
go install
対応にわたわたとしている感じが伝わってきますが、結果的に上記のように変わるとプロジェクトで使うentgo.io/ent
のバージョンと、コード生成ツールのバージョンが一致させられる嬉しさがあるねという結論にIssueの中で至っています。その点はなるほど?と思いました。
バージョンの一致という点について分かりづらいので、少し補足説明します。
- あるプロジェクトで
module/A v1.0.0
を使用。module/A
はmodule/A/cmd/a
を提供している。 -
module/A v1.1.0
がリリースされる。 - プロジェクトに新規参加したメンバーが環境を構築する。
go get ./...
して必要なモジュールを取得し、go install module/A/cmd/a@latest
でツールをインストールする。そうすると使用するモジュールはv1.0.0
、ツールはv1.1.0
のものとなる。
モジュールとツールが密に結合する場合だと、上記のバージョン不一致は問題になるかもしれませんね。そうした場合は現在のent
の例と同様にgo run -mod=mod
とするのが良いのかもしれないなぁと思いました。
-mod=mod
オプションについて
私自身は-mod
オプションを使ったことはなかったので、このオプションによって何が変わるのか知りたくなりました。
-mod
オプションはビルドオプションの一つです。詳細な説明は下記リンク先にあります。
当該箇所を抜粋すると次の通りです。
The -mod flag controls whether go.mod may be automatically updated and whether the vendor directory is used.
- -mod=mod tells the go command to ignore the vendor directory and to automatically update go.mod, for example, when an imported package is not provided by any known module.
- -mod=readonly tells the go command to ignore the vendor directory and to report an error if go.mod needs to be updated.
- -mod=vendor tells the go command to use the vendor directory. In this mode, the go command will not use the network or the module cache.
- By default, if the go version in go.mod is 1.14 or higher and a vendor directory is present, the go command acts as if -mod=vendor were used. Otherwise, the go command acts as if -mod=readonly were used.
なるほど、go.mod
の自動更新とvendor directory
使用の有無を切り替えるのですね。go1.14以上では次の動作がデフォルトです。
-mod 明示 |
vendor 有無 |
挙動 |
---|---|---|
有 | - |
-mod オプションに準じる |
無 | 有 |
vendor を使う |
無 | 無 | go.modのチェックはするが自動更新はしない |
ここで、上記引用中のautomatically update go.mod
というのは何なのでしょうか?詳細は以下のリンク先を確認頂きますが、ざっくりいうと次のことを行います。
-
go.mod
に記述されたモジュールのバージョンがセマンティックバージョニングに則っているか確認する。則っていない場合、利用可能なバージョンを探して修正する。 -
go.mod
内の重複するインポートを解決する。 -
go.mod
のフォーマットを行う。
つまり-mod=mod
を指定すると、go1.16以上のgo get
と似た挙動をするようです。
(ドキュメントから読み取り切れませんでしたが、実際はモジュールのダウンロードも行われていました。ほぼgo get
のような気もしますが、細かな挙動は違いました。その差異がどこから来るかはまだ調べきれていません…。)
上記のentの例では、まっさらなプロジェクトだとgo.mod
に何のrequire
も記載されていないのでgo run entgo.io/ent/cmd/ent
を実行するとエラーになりますが、-mod=mod
オプションをつけることで先にgo.mod
の修正、モジュールの取得を行なうことができるんですね?🤔
おまけ
この-mod
オプションはビルドコマンドなので、様々なgo
コマンドと組み合わせることができます。え、じゃあgo get
の挙動も-mod
オプションで変更できるんでしょうか?
go get -mod=readonly entgo.io/ent
# flag provided but not defined: -mod
フラグはあるけど未定義…?
go get
の挙動は上述のように-mod=mod
しか意味を成さないのでgo1.14の時点でフラグを無効化されています。今のところこの挙動はドキュメントと一致していないのですが、そのうち修正される模様です。
おわりに
なかなかディープなところまで行きましたね…。ビルドフラグについて調べる機会が無かったので、個人的には良い学びになりました。自作のコマンドを配布する時の方法として、ユースケースによりけりですが、本記事に書いたような方法もあるのだなーと学べてよかったです。
Discussion