#79427accepted
decouple export data formats used by compiler and x/tools
ステータス変更: likely_accept accepted
要約
AIによる要約であり、誤りを含む場合があります。
概要
Go コンパイラが使う export data(エクスポートデータ)の形式と、x/tools(gopls や静的解析ツール)が使う形式を完全に切り離す提案です。export data とは、パッケージの型情報をバイナリにシリアライズしたもので、概念的には types.Package をそのまま保存したものにあたります。
ステータス変更
likely_accept → accepted
likely accept の段階で、委員会は「proposal として審査すべき部分はもう残っていない」と判断していました。その後も合意は変わらなかったため、accepted になりました。go コマンドのワーキンググループ(@matloob)からも反対はありませんでした。決定内容は次の3点です。
go list -exportのExportフィールドはツール向けの形式を指すようにする。指す先は、新しいツールcmd/exportが生成するファイルになる。- Go 1.28 になる見込みのリリース以降、コンパイラが出力する形式を読む手段は公式にはサポートしない。
- 古いコンパイラが出力した形式については、x/tools は通常のサポート期間(直近2リリースと tip)のあいだ読み込みを続ける。
技術的背景
現状の問題点
現在 export data には2つの形式があります。
- u(unified)形式: コンパイラが書き込み、コンパイラが読み込む。
- i(indexed)形式: x/tools が書き込み、x/tools が読み込む。
ところがgo list -exportはコンパイラを実行して export data を作ります。そのため x/tools は u 形式も読めなければなりません。結果として、コンパイラが export data の形式を変えるたびに、別リポジトリにある x/tools のリーダーも同時に直す必要があり、両者が強く結びついていました。ジェネリックなメソッドのような言語変更があると、cmd/compile、go/types、x/tools のそれぞれでリーダーを更新する作業が発生していました。
提案された解決策
- コンパイラは、同じバージョンのツールチェーンが書いた独自形式(議論では「p 形式」と呼ばれています)だけを読み書きします。この形式はどのコミットでも自由に変更でき、互換性の保証は一切ありません。
go list -exportはコンパイラではなく、新しく作るcmd/exportを実行します。cmd/exportは go/ast、go/types、vendor された x/tools を使い、ソースを型チェックして i 形式を出力します。実質的には unitchecker から解析部分を取り除いたもので、引数は cmd/compile と同じ形で受け取ります。関数本体の型チェックを省略できるため、コンパイラより高速で、ビルドのクリティカルパスも短くなります。go/importerも、vendor した x/tools のデコーダを使って i 形式を読むようになります。go vetは、export data を得るためにコンパイラを実行するのをやめます。unitchecker が型情報を自分で書き出し、解析結果である facts と同じ.vetxファイルに収めます(CL 824004、CL 823832)。
これによって何ができるようになるか
- コンパイラ開発の自由度が上がる: export data の形式を、x/tools 側と調整せずに変更できます。言語機能を追加するときのリポジトリをまたいだ作業が減ります。
- ツールの安定性: gopls や各種 analyzer は、x/tools が管理する i 形式だけを扱えば済みます。
- ビルドグラフの単純化:
go vetなどはコンパイラを経由せずに型情報を得られます。
一方で、次のような場合は対応が必要です。いずれもサポート外の使い方です。 - コンパイラを直接実行し、出力された
.aファイルを読んでいるツール go build -o runtime.aの出力をgo/importerで読んでいるツール- GOPATH モードで
Targetフィールドを使っているツール - Bazel の rules_go(nogo)のように、独自のビルドシステムで解析ツールに export data を渡しているもの
コード例
// Before: コンパイラが生成した .a アーカイブから u 形式を直接読む(今後サポートされない)
f, _ := os.Open("pkg.a") // go tool compile などの出力
r, _ := gcexportdata.NewReader(f) // 非推奨化された(CL 826645)
pkg, _ := gcexportdata.Read(r, fset, imports, "example.com/pkg")
// After: go list -export が返す Export ファイル(cmd/export が生成する i 形式)を読む
// $ go list -export -json example.com/pkg -> "Export": "/.../xxxx-d"
f, _ := os.Open(exportPath)
pkg, _ := gcexportdata.Read(bufio.NewReader(f), fset, imports, "example.com/pkg")
議論のハイライト
- 後方互換をどこまで守るか: 「i 形式を後方互換にすれば、ツールチェーンを更新してもツールが壊れないのでは」という意見がありました。これに対しては、新しい言語機能を使うパッケージは古いリーダーでは正しく扱えず、バージョン不一致のエラーを出す以外にないという理由で退けられました。
- 影響範囲の段階論: @mrkfrmn は2つの案を比較しました。(1) コンパイラの形式を go リポジトリ内に閉じ込める案と、(2)
cmd/compile内に閉じ込める案です。参考値として、go/importerの利用者は約2275件、gcexportdataの利用者は約282件と示されました。@adonovan はこれを i、u、p の3形式で整理しました。 - 早めに壊して問題を表面化させる: 委員会の方針は、互換性をサイクルの早い段階で意図的に壊すことです。オプトインではなくオプトアウトにして、影響を早く把握します。影響を受ける利用者は
go list -exportに切り替えれば、Go 1.27 でもそれ以降でも動くコードにできます。 - 実際に影響が出た例: master を使っていた @mvdan の環境で、CL 835025 の取り込み後に
go testが壊れました。x/tools 側は CL 840647 で対応済みで、新しいタグも公開されています。 - Bazel rules_go の移行: @adonovan は、CL 824004 の unitchecker と同じように、ツール自身が型情報を読み書きする方式へ移ることを勧めました。