🍣

あえて`go install`を使わないユースケースに出会った時のメモ

2022/09/30に公開

go1.16からモジュール対応モードがデフォルトになり、時を同じくしてgo getの機能であったツールのビルド&インストールがgo installに明確に切り出されたことは良く知られていると思います。

そんな折、あるプロジェクトのREADMEを読んでいたところ、少し前までgo installでツールをインストールさせていたのに、go run -mod=modを使うように修正していることに気が付きました。はて、敢えて?と疑問に思って経緯を調べたので、その覚書を残しておきます。

本記事では以下の内容に触れます。

  • あえてgo installを使わないのはどんな時?
  • -mod=modオプションについて深堀(これは蛇足)

あえてgo installを使わないのはどんな時?

RDBのORMであるentの使い方を見てみましょう。

https://entgo.io/docs/getting-started/#create-your-first-schema

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 installgo getと違ってgo.modを更新しないから、まっさらなプロジェクトで上記を実行するとモジュールが見つからなくてエラーになるよ
  • じゃあgo runするときに-mod=modオプションをつけたら解決するね。(詳細は後述)そうすればそもそもgo installいらなくない?

https://github.com/ent/ent/commit/43ceed9b6fe6acc149debeb7cbc6796b05be05ee
https://github.com/ent/ent/issues/2880

go install対応にわたわたとしている感じが伝わってきますが、結果的に上記のように変わるとプロジェクトで使うentgo.io/entのバージョンと、コード生成ツールのバージョンが一致させられる嬉しさがあるねという結論にIssueの中で至っています。その点はなるほど?と思いました。

バージョンの一致という点について分かりづらいので、少し補足説明します。

  1. あるプロジェクトでmodule/A v1.0.0を使用。module/Amodule/A/cmd/aを提供している。
  2. module/A v1.1.0がリリースされる。
  3. プロジェクトに新規参加したメンバーが環境を構築する。go get ./...して必要なモジュールを取得し、go install module/A/cmd/a@latestでツールをインストールする。そうすると使用するモジュールはv1.0.0、ツールはv1.1.0のものとなる。

モジュールとツールが密に結合する場合だと、上記のバージョン不一致は問題になるかもしれませんね。そうした場合は現在のentの例と同様にgo run -mod=modとするのが良いのかもしれないなぁと思いました。

-mod=modオプションについて

私自身は-modオプションを使ったことはなかったので、このオプションによって何が変わるのか知りたくなりました。

-modオプションはビルドオプションの一つです。詳細な説明は下記リンク先にあります。

https://golang.org/ref/mod#build-commands

当該箇所を抜粋すると次の通りです。

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というのは何なのでしょうか?詳細は以下のリンク先を確認頂きますが、ざっくりいうと次のことを行います。

https://go.dev/ref/mod#go-mod-file-updates

  • 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の時点でフラグを無効化されています。今のところこの挙動はドキュメントと一致していないのですが、そのうち修正される模様です。

おわりに

なかなかディープなところまで行きましたね…。ビルドフラグについて調べる機会が無かったので、個人的には良い学びになりました。自作のコマンドを配布する時の方法として、ユースケースによりけりですが、本記事に書いたような方法もあるのだなーと学べてよかったです。

GitHubで編集を提案

Discussion