[インデックス 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
のドキュメント)