compile/link/asm/cgo/cover/packのレスポンスファイル(@file)解析がGCC互換の引用符・エスケープ構文に刷新された
Tools
概要
compile、link、asm、cgo、cover、packの各ツールで、レスポンスファイル(@file)の解析がGCC互換のトークナイズ方式に刷新された。レスポンスファイルの中身は空白区切りの引数列として解釈され、シングルクォート(リテラル文字列)とダブルクォート(エスケープシーケンス対応)、およびバックスラッシュ+改行による行継続をサポートする。フォーマットはGCCのレスポンスファイル実装と互換になっており、既存のビルドシステムとの相互運用性を確保している。
導入経緯
従来のcmd/internal/objabi.expandArgsによるレスポンスファイル実装は行ベースの独自フォーマットで、\nや\\をエスケープとして扱っていた。このため、Windowsパス(バックスラッシュを含む)をそのまま書いたレスポンスファイルを渡すとinternal compiler error: panic: badly formatted inputでパニックする問題がIssue #77177で報告された。
BuckやBazelのようなビルドシステムは、コンパイラ呼び出しの際に引数をレスポンスファイル経由で渡す際、標準的なクォート付き引数ファイル(シェル風のクォート・エスケープ)を前提としている。調査の結果、GCCやClangは生のバックスラッシュ(MSVC固有の挙動)をサポートせず、エスケープ済みバックスラッシュを使うクォート形式のみをサポートすることが分かり、Go側もGCC互換の形式を採用する方針となった。
フォーマット変更はレスポンスファイルの互換性を破壊するが、Ian Lance Taylor氏は「独自にレスポンスファイルを書いているユーザーはほぼおらず(go tool compileや-gcflags経由の利用が主)、Microsoft形式相当への変更は本来最初からそうすべきだった」とコメントし、変更を容認した。公式のMicrosoft仕様が見つからなかったため、実装はGCCのlibiberty/argv.cを参考実装として採用している。最終的な実装はCL 737500で行われた。
議論のハイライト
- 当初報告されたのは「既にレスポンスファイルはサポート済みでは」という誤解によるクローズ・再オープンで、その後Windowsパスのバックスラッシュが未対応という具体的な再現例が示され議論が本格化した。
- GCC・Clangともに引用符外の生バックスラッシュを解釈しないため、「生バックスラッシュ対応」ではなく「クォート引数対応」の方向で合意が形成された。
- フォーマット変更が既存ユーザーを壊さないかという懸念に対し、
go buildが内部生成するレスポンスファイル側(後述)も同時に新形式へ追従済みであること、独自レスポンスファイルの利用者はごく少数と見積もられることから、変更が承認された。 cmd/internal/objabiの実装をいずれ標準ライブラリのflagパッケージ(flag.ResponseFiles案)に移すことも議論されたが、それは別途proposalが必要であり本Issueのスコープ外とされた。
移行時の注意
go tool compile @fileや-gcflags経由などで独自にレスポンスファイルを記述している場合、旧形式(1行1引数、\nや\\によるエスケープ)は新形式に書き換える必要がある。例えば旧形式の
arg1\nwith\nnewlines
は新形式では
"arg1\nwith\nnewlines"
のようにダブルクォートで囲む必要がある。Windowsパスはシングルクォートでリテラルに保持するか('C:\Users\test\file.go')、ダブルクォート内でバックスラッシュをエスケープする("C:\\Users\\test\\file.go")。
なお、go build自身が長い引数リストのために内部生成するレスポンスファイルはこの変更に合わせて更新済みのため、通常のgo build利用では対応不要。影響を受けるのはgo tool compile等を直接レスポンスファイルとともに呼び出しているユーザーのみである。
実装解説
GCC互換のトークナイズはcmd/internal/objabi.ParseArgsが行う。シングルクォート内はエスケープ処理をせず文字をそのまま取り込み、ダブルクォート内は\\、\"、\$、\`、および行継続としての\<LF>・\<CR><LF>(Windows向け拡張)を認識する。クォート外ではバックスラッシュが直後の1文字をエスケープし、バックスラッシュに続く改行は行継続として扱われる。
@file引数の展開自体はexpandArgsが担い、ファイル内容をParseArgsでトークナイズしたうえで再帰的に展開する(レスポンスファイルの中で別のレスポンスファイルを参照できる)。この関数はobjabi.Flagparseから呼ばれ、compile/link/asm/cgo/cover/packの各コマンドの起動時フラグ解析に組み込まれている。
go build側でも、長い引数リストをレスポンスファイルに逃がす際の出力形式が更新された。cmd/go/internal/work/exec.goのencodeArgが、特殊文字を含む引数をダブルクォートで囲みGCC互換のエスケープを施して書き出す。どのツールに対してレスポンスファイルを使うかはuseResponseFileがツール名のホワイトリストで管理しており、対象はcompile/link/cgo/asm/cover/packに限られる。