Go 1.16におけるモジュールの変更点

New module changes in Go 1.16 by Jay Conrod

Go 1.16をお楽しみいただけていることと思います。今回のリリースには多くの新機能があり、特にモジュール関連の機能が充実しています。リリースノートではこれらの変更点を簡単に説明していますが、ここではいくつかの機能を詳しく見ていきましょう。

モジュールがデフォルトで有効に

go コマンドは、go.mod が存在しない場合でも、デフォルトでモジュール対応モードでパッケージをビルドするようになりました。これはすべてのプロジェクトでモジュールを使うようにするための大きな一歩です。

GO111MODULE 環境変数を off に設定すれば、これまで通りGOPATHモードでパッケージをビルドすることも可能です。また GO111MODULEauto に設定すると、カレントディレクトリまたはその親ディレクトリのいずれかに go.mod ファイルが存在する場合にのみモジュール対応モードを有効にできます。これは以前のデフォルトの挙動でした。なお、go env -w を使うことで、GO111MODULE や他の変数を永続的に設定できることに注意してください。

go env -w GO111MODULE=auto

私たちはGo 1.17でGOPATHモードのサポートを打ち切る計画です。つまり、Go 1.17では GO111MODULE は無視されるようになります。もしモジュール対応モードでビルドできないプロジェクトをお持ちなら、今が移行のタイミングです。移行を妨げる問題がある場合は、issueexperience reportの提出をご検討ください。

go.modとgo.sumへの自動的な変更の廃止

以前は、go コマンドが go.modgo.sum の問題(require ディレクティブの欠落やチェックサムの欠落など)を検出すると、その問題を自動的に修正しようとしていました。この挙動は、特に通常は副作用を持たないはずの go list のようなコマンドにおいて予期しないものであるという声を多くいただきました。この自動修正は必ずしも望ましいものではありませんでした。もしインポートされたパッケージがどの必須モジュールにも提供されていない場合、go コマンドは新しい依存関係を追加し、場合によっては共通の依存関係のアップグレードを引き起こしてしまうことがありました。インポートパスのスペルミスでさえ、失敗に終わるネットワーク検索を引き起こしていました。

Go 1.16では、モジュール対応のコマンドは go.modgo.sum の問題を発見すると、それを自動的に修正しようとするのではなく、エラーを報告するようになりました。多くの場合、エラーメッセージには問題を修正するためのコマンドが提示されます。

$ go build
example.go:3:8: no required module provides package golang.org/x/net/html; to add it:
    go get golang.org/x/net/html
$ go get golang.org/x/net/html
$ go build

これまでと同様に、go コマンドは vendor ディレクトリが存在すればそれを使用します(詳細はVendoringを参照してください)。go getgo mod tidy といったコマンドは、その主な目的が依存関係の管理であるため、引き続き go.modgo.sum を変更します。

特定バージョンの実行可能ファイルのインストール

go install コマンドは、@version サフィックスを指定することで、特定バージョンの実行可能ファイルをインストールできるようになりました。

go install golang.org/x/tools/gopls@v0.6.5

この構文を使うと、go install はカレントディレクトリや親ディレクトリにある go.mod ファイルを無視して、指定した正確なモジュールバージョンからコマンドをインストールします。(@version サフィックスを指定しない場合、go install はこれまで通り、カレントモジュールの go.mod に記載されているバージョン要件とリプレースを使ってプログラムをビルドします。)

以前は実行可能ファイルをインストールするために go get -u program を推奨していましたが、この用法は go.mod 内のモジュールバージョン要件を追加・変更するという go get 本来の意味との間で多くの混乱を招いてしまいました。そして go.mod を誤って変更してしまわないように、次のようなより複雑なコマンドを提案する人たちも出てきました。

cd $HOME; GO111MODULE=on go get program@latest

これからは go install program@latest を使えばよいのです。詳細はgo installを参照してください。

どのバージョンが使われるかについての曖昧さを排除するため、このインストール構文を使う際にプログラムの go.mod ファイルに記述できるディレクティブにはいくつかの制約があります。特に、少なくとも現時点では replace ディレクティブと exclude ディレクティブは許可されていません。長期的には、新しい go install program@version が十分に多くのユースケースでうまく機能するようになった時点で、go get がコマンドバイナリのインストールを行わないようにする計画です。詳細はissue 43684を参照してください。

モジュールのリトラクション(取り消し)

準備が整う前にうっかりモジュールのバージョンを公開してしまった経験はありませんか。あるいは、バージョンを公開した直後に、早急に修正が必要な問題を発見したことは。公開されたバージョンの間違いを訂正するのは難しいものです。モジュールのビルドを決定的なものにし続けるため、一度公開されたバージョンはその後変更できません。たとえバージョンタグを削除したり変更したりしても、proxy.golang.orgや他のプロキシは、すでに元のバージョンをキャッシュしている可能性が高いのです。

モジュールの作者は、go.mod 内の retract ディレクティブを使って、モジュールのバージョンをリトラクト(取り消し)できるようになりました。リトラクトされたバージョンは引き続き存在し、ダウンロードもできます(そのため、そのバージョンに依存しているビルドが壊れることはありません)が、go コマンドは @latest のようなバージョン解決の際にそのバージョンを自動的には選択しなくなります。go getgo list -m -u は、既存の利用について警告を表示します。

たとえば、人気のライブラリ example.com/lib の作者が v1.0.5 をリリースした後、新たなセキュリティ上の問題を発見したとしましょう。作者は次のようなディレクティブを go.mod ファイルに追加できます。

// Remote-triggered crash in package foo. See CVE-2021-01234.
retract v1.0.5

次に、作者は新しい最高バージョンとなる v1.0.6 にタグを付けてプッシュします。これ以降、すでに v1.0.5 に依存しているユーザーは、更新を確認したときや依存先のパッケージをアップグレードしたときに、リトラクションについて通知されます。この通知メッセージには、retract ディレクティブの上に書かれたコメントの内容が含まれることがあります。

$ go list -m -u all
example.com/lib v1.0.0 (retracted)
$ go get .
go: warning: example.com/lib@v1.0.5: retracted by module author:
    Remote-triggered crash in package foo. See CVE-2021-01234.
go: to switch to the latest unretracted version, run:
    go get example.com/lib@latest

インタラクティブなブラウザベースのガイドについては、play-with-go.devRetract Module Versionsを参照してください。構文の詳細はretract ディレクティブのドキュメントを参照してください。

GOVCSによるバージョン管理ツールの制御

go コマンドは、proxy.golang.orgのようなミラーからモジュールのソースコードをダウンロードすることも、githgsvnbzrfossil を使ってバージョン管理リポジトリから直接ダウンロードすることもできます。バージョン管理への直接アクセスは、プロキシから入手できないプライベートモジュールにとって特に重要ですが、それと同時にセキュリティ上の問題にもなり得ます。バージョン管理ツールのバグが、悪意あるサーバーによって意図しないコードの実行に悪用されるかもしれないからです。

Go 1.16では、GOVCS という新しい設定変数が導入されました。これにより、どのモジュールがどのバージョン管理ツールの使用を許可されるかをユーザーが指定できます。GOVCS はカンマ区切りの pattern:vcslist ルールのリストを受け取ります。pattern はモジュールパスの先頭の1つ以上の要素にマッチするpath.Matchパターンです。特別なパターンである publicprivate は、それぞれパブリックモジュールとプライベートモジュールにマッチします(privateGOPRIVATE のパターンにマッチするモジュールとして定義され、public はそれ以外のすべてです)。vcslist は、許可されるバージョン管理コマンドのパイプ区切りのリスト、またはキーワード alloff です。

たとえば、次のように設定します。

GOVCS=github.com:git,evil.com:off,*:git|hg

この設定では、github.com 上のパスを持つモジュールは git を使ってダウンロードでき、evil.com 上のパスはどのバージョン管理コマンドを使ってもダウンロードできず、それ以外のすべてのパス(* はすべてにマッチします)は git または hg を使ってダウンロードできます。

GOVCS が設定されていない場合、あるいはモジュールがどのパターンにもマッチしない場合、go コマンドは次のデフォルト設定を使います。パブリックモジュールには githg が許可され、プライベートモジュールにはすべてのツールが許可されます。GitとMercurialのみを許可している理由は、この2つのシステムが、信頼できないサーバーのクライアントとして実行される際の問題に最も注意が払われてきたからです。それに対して、Bazaar、Fossil、Subversionは主に信頼された認証済みの環境で使われてきており、攻撃対象領域としてはそれほど精査されていません。つまり、デフォルトの設定は次の通りです。

GOVCS=public:git|hg,private:all

詳細はGOVCSによるバージョン管理ツールの制御を参照してください。

今後の展望

これらの機能がお役に立てば幸いです。私たちはすでにGo 1.17に向けた次のモジュール機能群に着手しており、特に遅延モジュールロード(lazy module loading)に取り組んでいます。これにより、モジュールのロード処理がより高速で安定したものになるはずです。いつものように、新しいバグに遭遇した場合はissueトラッカーでお知らせください。それでは、良いコーディングを。

By Jay Conrod