メインコンテンツへスキップ
#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 と同じように、ツール自身が型情報を読み書きする方式へ移ることを勧めました。

関連リンク