Go 1.18 で go run -mod=mod の非互換変更に気がついて報告した

Go 1.18 がリリースされて一か月程経ちました。 先日 go run を (go generate経由で) 使っているコードの挙動を見ていたら、モジュール周りの挙動が Go 1.17 と異なることに気が付きました。

Go 1.16 以降、go getgo mod tidy のような一部のコマンドを除くと、go.mod に足りない依存パッケージがあるときはエラーになります。 go rungo testなどでもgo.modを更新したい場合は、-mod=modというフラグを指定します。

go runは、Goのファイルのコンパイルと実行を行うコマンドです。 パッケージを引数として利用されることが多いコマンドですが、Goのファイルを指定して実行することもできます。

❯ cat main.go
package main

func main() {
        println("Hello, world!")
}

❯ cat go.mod
module tmp

go 1.18

❯ go run .
Hello, world!

❯ go run main.go
Hello, world!

Goのファイルを指定して実行する場合はcommand-line-argumentsパッケージという特殊な名前が付けられています。 これはほとんどドキュメントに記述されていませんが、go listのようなコマンドで確認することができます。

❯ go list .
tmp

❯ go list main.go
command-line-arguments

さて、コードが依存しているパッケージがgo.modにない場合はどうなるでしょうか。 go runコマンドはデフォルトではgo.modを更新しないので、依存パッケージが足りない場合はエラーになります。

❯ cat main.go
package main

import "golang.org/x/sys/unix"

func main() {
        println(unix.Getpid())
}

❯ cat go.mod
module tmp

go 1.18

❯ go run .
main.go:3:8: no required module provides package golang.org/x/sys/unix; to add it:
        go get golang.org/x/sys/unix

❯ go run main.go
main.go:3:8: no required module provides package golang.org/x/sys/unix; to add it:
        go get golang.org/x/sys/unix

では -mod=mod をつけるとどうなるでしょうか。 足りない依存をgo.modgo.sumに追加するフラグですので、go.modgolang.org/x/sys/unixパッケージが追加されます。

❯ go run -mod=mod .
go: finding module for package golang.org/x/sys/unix
go: found golang.org/x/sys/unix in golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
10664

❯ cat go.mod
module tmp

go 1.18

require golang.org/x/sys v0.0.0-20220412211240-33da011f77ad

ファイルを指定した時も同じ挙動を期待しますよね。 しかし、Go 1.18.1の挙動は以下のようになります。

❯ cat go.mod
module tmp

go 1.18

❯ go run -mod=mod main.go
go: finding module for package golang.org/x/sys/unix
10820

❯ cat go.mod
module tmp

go 1.18

あれれ、go.modが更新されませんね。 足りない依存パッケージの検索やダウンロードはされ、コードは実行されましたが、go.modは更新されません。 これはgo buildなど、他のコマンドも同じ挙動になります。

実は Go 1.17 ではこの挙動ではありませんでした。 ファイルを指定した場合も-mod=modを指定すればgo.modが更新されていました。 この挙動に依存していたわけではなく、違いに気がついたのは偶然でした (Dockerイメージをpullしておらず、ホストマシンのGoとバージョンがずれていた)。

Go 1.18のリリースノートマニュアルを確認しましたが、いずれにもこの挙動に関する記載はありません。 たしかにcommand-line-argumentsパッケージは特殊な立ち位置で、特にモジュールとの兼ね合いは難しくなっています。 しかし、ユーザーに影響がある挙動の変更ならば、リリースノートに書いて欲しいですよね。 そういう気持ちを込めて、バグトラッカーに報告してみました。 github.com 無事、リグレッションの判断がされたので、Go 1.18へのバックポートもされるようです。 すぐに修正CLが投稿されました。 go runコマンドで-mod=modとGoファイルを指定した時の挙動の変更に気がついて報告したという話でした。