あえて`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