[インデックス 17518] ファイルの概要
コミット
このコミットは、Go言語のビルドツール (cmd/go
) において、C++ソースファイルが含まれるGoパッケージをビルドする際に、C++標準ライブラリが自動的にリンクされるように変更を加えるものです。具体的には、C++ソースファイルが存在する場合に、デフォルトの外部リンカとして g++
を使用するか、gccgo
ツールチェインの場合は -lstdc++
フラグをリンカに渡すように調整されます。これにより、ユーザーが明示的に cgo
の LDFLAGS
オプションを設定することなく、C++コードを含むGoプログラムをより簡単にビルドできるようになります。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f0ff63ea64c878ebe67db70be8f41f73f21bbaff
元コミット内容
cmd/go: if there are C++ sources, use g++ as default external linker
This will bring in the C++ standard library without requiring
any special #cgo LDFLAGS options.
When using gccgo, just add -lstdc++ to link line; this should
do no harm if it is not needed.
No tests, since we don't want to assume a C++ compiler.
Update #5629
R=golang-dev, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/13394045
変更の背景
Go言語はC言語との相互運用性を提供するために cgo
というメカニズムを持っています。これにより、GoプログラムからCの関数を呼び出したり、Cのライブラリをリンクしたりすることが可能です。しかし、C++のコードをGoプログラムに組み込む場合、C++のコンパイラやリンカ、そしてC++標準ライブラリのリンクに関する考慮が必要になります。
従来のGoのビルドシステムでは、C++ソースファイルを含むGoパッケージをビルドする際に、C++標準ライブラリ(libstdc++
など)をリンクするために、ユーザーが cgo
の LDFLAGS
に -lstdc++
のようなリンカフラグを明示的に追加する必要がありました。これは、特にC++に不慣れなGo開発者にとっては障壁となる可能性がありました。
このコミットの背景には、Go Issue #5629 があります。このIssueでは、C++ソースファイルが存在する場合に go build
が自動的に g++
を外部リンカとして使用し、C++標準ライブラリをリンクするように求める要望が議論されていました。この変更は、C++コードとの連携をよりシームレスにし、開発者の手間を軽減することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が役立ちます。
- Go言語のビルドプロセス:
go build
コマンドは、Goソースコードをコンパイルし、実行可能ファイルを生成します。このプロセスには、コンパイル、アセンブル、リンクの各フェーズが含まれます。 - cgo: GoプログラムからC言語のコードを呼び出すためのGoの機能です。
import "C"
を使用してCコードをGoに組み込むことができます。cgo
は、GoコンパイラとC/C++コンパイラ(通常はGCCやClang)を連携させ、GoとC/C++の間の関数呼び出しを処理します。 - 外部リンカ (External Linker): Goのビルドプロセスでは、GoのオブジェクトファイルとC/C++のオブジェクトファイルを結合して最終的な実行可能ファイルを生成するために、外部のリンカ(通常はGCCやClangのリンカ)を使用することがあります。特に
cgo
を使用する場合や、静的・動的ライブラリをリンクする際に重要になります。 LDFLAGS
: リンカに渡されるフラグ(オプション)を指定するための環境変数またはビルド設定です。ライブラリのパス (-L
) やリンクするライブラリ名 (-l
) などを指定するのに使われます。- C++標準ライブラリ (
libstdc++
またはlibc++
): C++の標準機能(文字列、コンテナ、アルゴリズムなど)を提供するライブラリです。C++プログラムをコンパイルして実行可能ファイルを生成する際には、通常このライブラリがリンクされます。 g++
: GNU Compiler Collection (GCC) のC++コンパイラです。C++ソースコードのコンパイルとリンクの両方を行うことができます。C++標準ライブラリを自動的にリンクする機能を持っています。gccgo
: Go言語のフロントエンドを持つGCCのバージョンです。GoコードをGCCのバックエンドでコンパイルし、C/C++コードと連携させることができます。gccgo
は、Goの標準コンパイラ (gc
) とは異なるビルドプロセスとリンカの挙動を持つことがあります。CXX
環境変数: C++コンパイラを指定するための環境変数です。この変数が設定されている場合、ビルドシステムは通常、その値で指定されたコンパイラを使用します。
技術的詳細
このコミットは、src/cmd/go/build.go
ファイル内の gcToolchain.ld
および gccgoToolchain.ld
という2つのリンカ関連の関数に変更を加えています。これらの関数は、それぞれ標準のGoコンパイラ (gc
) と gccgo
コンパイラを使用する際のリンク処理を担当します。
gcToolchain.ld
の変更点
-
C++ソースファイルの検出:
gcToolchain.ld
関数内で、cxx
という新しいブーリアン変数が導入されました。この変数は、ビルド対象のパッケージ (p
) またはその依存関係 (allactions
) のいずれかにC++ソースファイル (a.p.CXXFiles
) が含まれている場合にtrue
に設定されます。 -
外部リンカの自動設定:
cxx
がtrue
の場合、つまりC++ソースファイルが存在する場合に、Goのビルドシステムは外部リンカの挙動を調整します。- まず、既存の
ldflags
に-extld
または-extld=
で始まるフラグが既に含まれているかを確認します。これは、ユーザーが既に外部リンカを明示的に指定しているかどうかをチェックするためです。 - もしユーザーが外部リンカを明示的に指定していない場合、GoはデフォルトのC++リンカを決定します。
CXX
環境変数の値がチェックされます。もしCXX
が設定されており、かつ値がある場合、その値がC++コンパイラとして使用されます。CXX
が設定されていないか、空の場合、デフォルトでg++
が使用されます。- 決定されたコンパイラ名(例:
g++
またはclang++
)は、-extld=
フラグとしてldflags
に追加されます。
- さらに、
CXX
環境変数が複数の単語で構成されている場合(例:CXX="clang++ -std=c++11"
)、最初の単語がコンパイラ名として使用され、残りの単語は-extldflags
としてリンカに渡されます。これにより、ユーザーがCXX
環境変数を通じて追加のリンカオプションを指定できるようになります。既存の-extldflags
があればそれに追記し、なければ新規に作成します。
- まず、既存の
gccgoToolchain.ld
の変更点
-
C++ソースファイルの検出:
gccgoToolchain.ld
関数でも同様にcxx
ブーリアン変数が導入され、C++ソースファイルの存在が検出されます。 -
-lstdc++
の追加:cxx
がtrue
の場合、gccgoToolchain.ld
は無条件に-lstdc++
フラグをldflags
に追加します。gccgo
はGCCのフロントエンドであるため、C++標準ライブラリをリンクするにはこのフラグを明示的に渡すのが一般的です。コミットメッセージにもあるように、このフラグは必要ない場合でも害はないとされています。
これらの変更により、GoのビルドシステムはC++ソースファイルの存在を認識し、適切なリンカとライブラリを自動的に選択・追加することで、C++コードを含むGoプログラムのビルドプロセスを簡素化します。
コアとなるコードの変更箇所
変更は src/cmd/go/build.go
ファイルに集中しています。
src/cmd/go/build.go
の gcToolchain.ld
関数
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1553,6 +1553,7 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action,\n importArgs := b.includeArgs("-L", allactions)\n swigDirs := make(map[string]bool)\n swigArg := []string{}\n+ cxx := false
for _, a := range allactions {
if a.p != nil && a.p.usesSwig() {
sd := a.p.swigDir(&buildContext)
@@ -1564,8 +1565,50 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action,\n }\n swigDirs[sd] = true\n }\n+ if a.p != nil && len(a.p.CXXFiles) > 0 {
+ cxx = true
+ }\n+ }\n+ ldflags := buildLdflags
+ if cxx {
+ // The program includes C++ code. If the user has not
+ // specified the -extld option, then default to
+ // linking with the compiler named by the CXX
+ // environment variable, or g++ if CXX is not set.\n+ extld := false
+ for _, f := range ldflags {
+ if f == "-extld" || strings.HasPrefix(f, "-extld=") {
+ extld = true
+ break
+ }
+ }
+ if !extld {
+ compiler := strings.Fields(os.Getenv("CXX"))
+ if len(compiler) == 0 {
+ compiler = []string{"g++"}
+ }
+ ldflags = append(ldflags, "-extld="+compiler[0])
+ if len(compiler) > 1 {
+ extldflags := false
+ add := strings.Join(compiler[1:], " ")
+ for i, f := range ldflags {
+ if f == "-extldflags" && i+1 < len(ldflags) {
+ ldflags[i+1] = add + " " + ldflags[i+1]
+ extldflags = true
+ break
+ } else if strings.HasPrefix(f, "-extldflags=") {
+ ldflags[i] = "-extldflags=" + add + " " + ldflags[i][len("-extldflags="):]
+ extldflags = true
+ break
+ }
+ }
+ if !extldflags {
+ ldflags = append(ldflags, "-extldflags="+add)
+ }
+ }
+ }
}\n-\treturn b.run(\".\", p.ImportPath, nil, tool(archChar+\"l\"), \"-o\", out, importArgs, swigArg, buildLdflags, mainpkg)\n+\treturn b.run(\".\", p.ImportPath, nil, tool(archChar+\"l\"), \"-o\", out, importArgs, swigArg, ldflags, mainpkg)\n }\n \n func (gcToolchain) cc(b *builder, p *Package, objdir, ofile, cfile string) error {\n```
### `src/cmd/go/build.go` の `gccgoToolchain.ld` 関数
```diff
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1641,6 +1684,7 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []\n ldflags := b.gccArchArgs()\n cgoldflags := []string{}\n usesCgo := false\n+ cxx := false
for _, a := range allactions {\n if a.p != nil {\n if !a.p.Standard {\n@@ -1660,6 +1704,9 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []\n }\n usesCgo = true\n }\n+ if len(a.p.CXXFiles) > 0 {
+ cxx = true
+ }\n }\n }\n for _, afile := range afiles {\n@@ -1672,6 +1719,9 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []\n if usesCgo && goos == "linux" {\n ldflags = append(ldflags, "-Wl,-E")\n }\n+ if cxx {
+ ldflags = append(ldflags, "-lstdc++")
+ }\n return b.run(".", p.ImportPath, nil, "gccgo", "-o", out, ofiles, "-Wl,-(", ldflags, "-Wl,)", buildGccgoflags)\n }\n \n```
## コアとなるコードの解説
### `gcToolchain.ld` の変更解説
1. **`cxx := false`**:
この行で、C++ソースファイルが存在するかどうかを示すフラグ `cxx` が初期化されます。
2. **`for _, a := range allactions { ... if a.p != nil && len(a.p.CXXFiles) > 0 { cxx = true } }`**:
ビルド対象のパッケージとその依存関係を表す `allactions` をループし、各アクション (`a`) に関連付けられたパッケージ (`a.p`) がC++ソースファイル (`CXXFiles`) を持っているかをチェックします。もし一つでもC++ファイルが見つかれば、`cxx` フラグを `true` に設定します。
3. **`ldflags := buildLdflags`**:
既存のリンカフラグ `buildLdflags` を `ldflags` 変数にコピーします。この `ldflags` が以降の処理で変更される可能性があります。
4. **`if cxx { ... }`**:
`cxx` が `true` の場合、つまりC++ソースファイルが存在する場合にのみ、以下のロジックが実行されます。
5. **外部リンカのチェックと設定**:
```go
extld := false
for _, f := range ldflags {
if f == "-extld" || strings.HasPrefix(f, "-extld=") {
extld = true
break
}
}
if !extld {
// ... 外部リンカの決定と追加 ...
}
```
このブロックでは、ユーザーが既に `-extld` オプションを使って外部リンカを明示的に指定しているかどうかを確認します。もし指定されていなければ、Goは自動的にC++リンカを設定します。
6. **C++コンパイラの決定**:
```go
compiler := strings.Fields(os.Getenv("CXX"))
if len(compiler) == 0 {
compiler = []string{"g++"}
}
ldflags = append(ldflags, "-extld="+compiler[0])
```
`CXX` 環境変数を読み込み、その値(スペースで区切られた単語のリスト)を `compiler` スライスに格納します。もし `CXX` が設定されていないか空であれば、`compiler` は `{"g++"}` に設定されます。そして、`compiler` の最初の要素(コンパイラ名)を `-extld=` フラグとして `ldflags` に追加します。
7. **追加のリンカフラグの処理**:
```go
if len(compiler) > 1 {
// ... -extldflags の処理 ...
}
```
`CXX` 環境変数が `g++ -std=c++11` のように複数の単語を含んでいた場合、最初の単語がコンパイラ名として使われ、残りの単語 (`-std=c++11`) は追加のリンカフラグとして扱われます。これらの追加フラグは、既存の `-extldflags` に追記されるか、新しい `-extldflags` として `ldflags` に追加されます。
8. **`return b.run(...)`**:
最終的に、変更された `ldflags` が `b.run` メソッドに渡され、リンカが実行されます。
### `gccgoToolchain.ld` の変更解説
1. **`cxx := false`**:
`gcToolchain.ld` と同様に、C++ソースファイルが存在するかどうかを示すフラグ `cxx` が初期化されます。
2. **`for _, a := range allactions { ... if len(a.p.CXXFiles) > 0 { cxx = true } }`**:
こちらも同様に、`allactions` をループしてC++ソースファイルの存在をチェックし、`cxx` フラグを設定します。
3. **`if cxx { ldflags = append(ldflags, "-lstdc++") }`**:
`cxx` が `true` の場合、つまりC++ソースファイルが存在する場合に、無条件に `-lstdc++` フラグを `ldflags` に追加します。`gccgo` の場合、C++標準ライブラリをリンクするためにこのフラグが必要です。
4. **`return b.run(...)`**:
変更された `ldflags` が `b.run` メソッドに渡され、`gccgo` リンカが実行されます。
これらの変更により、GoのビルドシステムはC++ソースファイルの存在を検出し、適切なリンカオプションを自動的に適用することで、C++コードとの連携をよりスムーズに行えるようになります。
## 関連リンク
* Go Issue #5629: [cmd/go: if there are C++ sources, use g++ as default external linker](https://github.com/golang/go/issues/5629)
* Go CL 13394045: [cmd/go: if there are C++ sources, use g++ as default external linker](https://go.dev/cl/13394045)
## 参考にした情報源リンク
* [Go Programming Language Documentation](https://go.dev/doc/)
* [cgo documentation](https://go.dev/blog/cgo)
* [GCC documentation](https://gcc.gnu.org/onlinedocs/)
* [GNU `g++` documentation](https://gcc.gnu.org/onlinedocs/gcc/G_002b_002b-and-GCC.html)
* [Go source code on GitHub](https://github.com/golang/go)
* [Go issue tracker](https://github.com/golang/go/issues)
* [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments)
* [Go Wiki: Gccgo](https://go.dev/wiki/Gccgo)
* [Go Wiki: ExternalLinker](https://go.dev/wiki/ExternalLinker)
* [Stack Overflow: What is the difference between -lstdc++ and -lc++?](https://stackoverflow.com/questions/10343370/what-is-the-difference-between-lstdc-and-lc)
* [Stack Overflow: What is the CXX environment variable?](https://stackoverflow.com/questions/10343370/what-is-the-difference-between-lstdc-and-lc)