[インデックス 15047] ファイルの概要
このコミットは、Go言語のビルドツール (cmd/go) において、go build -n コマンドがCgoを有効にしたパッケージに対して正しく動作しない問題を修正するものです。具体的には、libgcc のパスを決定する際に、go build -n が実際のコマンド実行を抑制しつつも、そのコマンドライン出力をキャプチャし、ビルドスクリプト内で $LIBGCC 変数として利用できるようにする変更が加えられています。
コミット
commit 6d2d2ae4d9af028ff13819497baeecfd984dc3e6
Author: Anthony Martin <ality@pbrane.org>
Date: Wed Jan 30 15:09:34 2013 -0800
cmd/go: fix build -n for cgo enabled packages
R=golang-dev, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/7095067
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6d2d2ae4d9af028ff13819497baeecfd984dc3e6
元コミット内容
cmd/go: fix build -n for cgo enabled packages
R=golang-dev, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/7095067
変更の背景
Go言語のビルドコマンド go build には -n フラグがあります。このフラグは、実際にビルドを実行する代わりに、実行されるであろうコマンドラインを表示する機能を提供します。これは、デバッグやビルドプロセスの理解に非常に役立ちます。
しかし、Cgo(GoとC言語の相互運用機能)を有効にしたパッケージをビルドする際、go build -n は問題を抱えていました。Cgoパッケージのビルドプロセスでは、GCCコンパイラが使用され、その際に libgcc というライブラリのパスが必要になることがあります。libgcc のパスは通常、gcc -print-libgcc-file-name コマンドを実行することで取得されます。
従来の go build -n の実装では、この gcc -print-libgcc-file-name コマンドも表示対象となり、実際に実行はされませんでした。その結果、libgcc のパスが取得できず、ビルドスクリプトが不完全な状態になり、ユーザーがそのスクリプトをコピーして手動で実行しようとした場合に失敗するという問題がありました。
このコミットは、go build -n がCgoパッケージのビルドスクリプトを生成する際に、libgcc のパスを正しく解決し、生成されるスクリプトが $LIBGCC のような変数を使って、後で実行可能な形式になるように修正することを目的としています。これにより、go build -n の出力がより実用的で、デバッグや自動化のシナリオで役立つようになります。
前提知識の解説
Go言語のビルドシステム (cmd/go)
cmd/go は、Go言語のソースコードをコンパイル、テスト、インストールなどを行うための公式コマンドラインツールです。Goのモジュール管理、依存関係解決、クロスコンパイルなど、Go開発のほとんどの側面を管理します。
go build -n フラグ
go build -n は、Goのビルドコマンドに渡されるオプションの一つです。このフラグを使用すると、go build は実際にバイナリをコンパイルしたりリンクしたりする代わりに、ビルドプロセス中に実行されるであろうすべての外部コマンド(コンパイラ、リンカ、アセンブラなど)を標準出力に表示します。これは、ビルドがどのように行われるかを理解したり、特定のビルド問題をデバッグしたりする際に非常に有用です。
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、既存のCライブラリをGoプロジェクトに統合したり、パフォーマンスが重要な部分をCで記述したりすることができます。Cgoを使用するGoパッケージは、通常のGoコンパイラだけでなく、Cコンパイラ(通常はGCC)も必要とします。
libgcc
libgcc は、GCC (GNU Compiler Collection) のランタイムサポートライブラリです。これは、GCCが生成するコードが依存する低レベルのルーチン(例えば、整数除算、浮動小数点演算、例外処理など)を提供します。Cgoを通じてCコードをGoプログラムにリンクする場合、GCCが使用されるため、libgcc が必要になることがあります。gcc -print-libgcc-file-name コマンドは、システム上の libgcc ライブラリの絶対パスを出力するために使用されます。
builder 構造体と b.print, b.runOut
Goのビルドツール内部では、ビルドプロセスを管理するために builder のような構造体が使用されます。
b.print: これは、ビルドプロセス中に実行されるコマンドを標準出力(または-nフラグが指定された場合はキャプチャバッファ)に「表示」するための内部関数です。b.runOut: これは、外部コマンドを実行し、その標準出力をキャプチャするための内部関数です。このコミットの文脈では、gcc -print-libgcc-file-nameのようなコマンドを実行するために使用されます。
技術的詳細
このコミットの核心は、go build -n モードでの libgcc パスの取得方法の改善にあります。
libgcc のパスを取得する libgcc メソッドは、通常 b.runOut を使用して gcc -print-libgcc-file-name コマンドを実行し、その出力を直接取得します。しかし、buildN (つまり -n フラグが有効な場合) は、実際のコマンド実行を抑制します。
この修正では、buildN が true の場合に以下のロジックが追加されています。
b.printの一時的なスワップ:b.print関数は、通常、実行されるコマンドを直接出力するために使用されます。しかし、-nモードでは、この出力を一時的にbytes.Bufferにリダイレクトします。これにより、gcc -print-libgcc-file-nameコマンド自体がbytes.Bufferにキャプチャされ、標準出力には表示されなくなります。- コマンドラインのキャプチャ:
b.runOutは引き続き呼び出されますが、-nモードでは実際にはコマンドは実行されず、そのコマンドラインがb.printを通じてbufに書き込まれます。 $LIBGCC変数の生成:gcc -print-libgcc-file-nameコマンドがbufにキャプチャされた後、buildNがtrueの場合、bufに格納されたコマンドラインを使用してLIBGCC=$(...)という形式のシェル変数が生成されます。この$(...)はシェルスクリプトのコマンド置換構文であり、括弧内のコマンドの出力を変数に代入します。- 元の
b.printの復元と出力: 一時的にスワップされたb.printは元の関数に戻され、生成されたLIBGCC=$(...)の行がb.printを通じて標準出力に書き込まれます。これにより、go build -nの出力には、libgccのパスを動的に解決するためのシェルコマンドが含まれるようになります。 $LIBGCCの返却:libgccメソッドは、実際のlibgccパスではなく、シェル変数$LIBGCCを返します。これにより、生成されるビルドスクリプトは、後で実行されたときにlibgccのパスを正しく解決できるようになります。
この変更により、go build -n はCgoパッケージに対しても、実行可能なビルドスクリプトを生成できるようになり、デバッグや自動化のワークフローが改善されます。
コアとなるコードの変更箇所
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1534,10 +1534,28 @@ func gccgoCleanPkgpath(p *Package) string {
// libgcc returns the filename for libgcc, as determined by invoking gcc with
// the -print-libgcc-file-name option.
func (b *builder) libgcc(p *Package) (string, error) {
+ var buf bytes.Buffer
+
+ prev := b.print
+ if buildN {
+ // In -n mode we temporarily swap out the builder's
+ // print function to capture the command-line. This
+ // let's us assign it to $LIBGCC and produce a valid
+ // buildscript for cgo packages.
+ b.print = func(a ...interface{}) (n int, err error) {
+ return fmt.Fprint(&buf, a...)
+ }
+ }
f, err := b.runOut(p.Dir, p.ImportPath, b.gccCmd(p.Dir), "-print-libgcc-file-name")
if err != nil {
return "", fmt.Errorf("gcc -print-libgcc-file-name: %v (%s)", err, f)
}\n+ if buildN {
+ s := fmt.Sprintf("LIBGCC=$(%s)\\n", buf.Next(buf.Len()-1))
+ b.print = prev
+ b.print(s)
+ return "$LIBGCC", nil
+ }
return strings.Trim(string(f), "\\r\\n"), nil
}
コアとなるコードの解説
src/cmd/go/build.go ファイル内の (*builder).libgcc メソッドが変更されています。
var buf bytes.Buffer:bytes.Buffer型のbuf変数が宣言されます。これは、-nモードでコマンドライン出力を一時的にキャプチャするためのバッファとして機能します。prev := b.print: 現在のb.print関数(ビルドコマンドの出力を担当)がprev変数に保存されます。これは、後で元の関数に戻すために必要です。if buildN { ... }:buildNグローバル変数(-nフラグが指定されているかどうかを示す)がtrueの場合にのみ、以下のロジックが実行されます。b.print = func(a ...interface{}) (n int, err error) { return fmt.Fprint(&buf, a...) }:b.print関数が一時的に上書きされます。新しいb.printは、引数を受け取り、それらを標準出力ではなくbufに書き込みます。これにより、gcc -print-libgcc-file-nameコマンド自体がbufにキャプチャされます。
f, err := b.runOut(p.Dir, p.ImportPath, b.gccCmd(p.Dir), "-print-libgcc-file-name"):b.runOutが呼び出され、gcc -print-libgcc-file-nameコマンドを実行(または-nモードではそのコマンドラインを生成)します。-nモードでは、この呼び出しによってコマンドラインが一時的に上書きされたb.printを通じてbufに書き込まれます。if err != nil { ... }: エラーハンドリングは変更されていません。if buildN { ... }: 再びbuildNがtrueの場合にのみ、以下のロジックが実行されます。s := fmt.Sprintf("LIBGCC=$(%s)\\n", buf.Next(buf.Len()-1)):bufにキャプチャされたコマンドライン(gcc -print-libgcc-file-name)を使用して、LIBGCC=$(...)という形式の文字列sが生成されます。buf.Next(buf.Len()-1)は、バッファの最後の改行文字を除いた内容を取得します。b.print = prev:b.print関数が、ステップ2で保存しておいた元の関数に戻されます。b.print(s): 生成されたLIBGCC=$(...)の行が、元のb.print関数を通じて標準出力に書き込まれます。これがgo build -nの出力の一部となります。return "$LIBGCC", nil:libgccメソッドは、実際のlibgccパスではなく、シェル変数$LIBGCCを返します。これにより、生成されるビルドスクリプトは、この変数を使用してlibgccのパスを参照するようになります。
return strings.Trim(string(f), "\\r\\n"), nil:buildNがfalseの場合(通常のビルドモード)、b.runOutから返された実際のlibgccパスがトリムされて返されます。
この変更により、go build -n はCgoパッケージのビルドスクリプトを生成する際に、libgcc のパスを動的に解決するためのシェルコマンドを埋め込むことができるようになり、生成されるスクリプトの実行可能性が向上します。
関連リンク
- Go CL 7095067: cmd/go: fix build -n for cgo enabled packages
- Go issue #4899: cmd/go: go build -n for cgo enabled packages (このコミットが解決した可能性のある関連issue)
参考にした情報源リンク
- Go Command Documentation
- Cgo Documentation
- GCC -print-libgcc-file-name (GCCのドキュメントで
-print-libgcc-file-nameオプションについて言及されている箇所) - Shell Command Substitution (シェルスクリプトのコマンド置換に関する一般的な情報)
- bytes.Buffer in Go (Goの
bytes.Bufferのドキュメント)