[インデックス 18956] ファイルの概要
このコミットは、Go言語のビルドツール (cmd/go
) において、gccgo
コンパイラを使用する際の外部テストファイルのリンク順序に関する問題を修正するものです。具体的には、以前の変更によって発生した、内部パッケージと外部テストパッケージのリンケージにおけるシンボル重複または不足のエラーを解決し、gc
コンパイラの動作と整合性を持たせることを目的としています。
コミット
commit 3ce1677ad9f95fb0b1ede191f41996c451e054c1
Author: Dave Cheney <dave@cheney.net>
Date: Wed Mar 26 15:30:55 2014 +1100
cmd/go: ensure external test files are presented to the linker first
Fixes #7627.
CL 61970044 changed the order in which .a files are passed to gccgo's link phase. However by reversing the order it caused gccgo to complain if both internal (liba.a) and external (liba_test.a) versions of a package were presented as the former would not contain all the necessary symbols, and the latter would duplicate symbols already defined.
This change ensures that all 'fake' targets remain at the top of the final link order which should be fine as a package compiled as an external test is a superset of its internal sibling.
Looking at how gcToolchain links tests I think this change now accurately mirrors those actions which present $WORK/_test before $WORK in the link order.
LGTM=iant
R=rsc, iant
CC=golang-codereviews, michael.hudson
https://golang.org/cl/80300043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3ce1677ad9f95fb0b1ede191f41996c451e054c1
元コミット内容
このコミットは、cmd/go
ツールにおいて、gccgo
コンパイラが外部テストファイルをリンクする際に、それらのファイルをリンカに最初に渡すように変更します。
これは、Issue #7627 を修正するものです。
以前の変更である CL 61970044
は、.a
(アーカイブ) ファイルが gccgo
のリンクフェーズに渡される順序を変更しました。しかし、この順序を逆にしたことで、パッケージの内部バージョン (liba.a
) と外部テストバージョン (liba_test.a
) の両方がリンカに渡された場合に、gccgo
がエラーを報告するようになりました。具体的には、内部バージョンが必要なシンボルをすべて含んでいないか、または外部テストバージョンが既に定義されているシンボルを重複して定義しているという問題が発生しました。
この変更は、「偽の (fake)」ターゲット(主に外部テストファイル)が最終的なリンク順序の最上位に維持されることを保証します。これは、外部テストとしてコンパイルされたパッケージが、その内部の兄弟パッケージのスーパーセットであるため、問題ないはずです。
gcToolchain
がテストをリンクする方法を見ると、この変更は $WORK/_test
を $WORK
の前にリンク順序で提示するそれらのアクションを正確に反映していると考えられます。
変更の背景
このコミットの背景には、Go言語のビルドシステム、特にgo test
コマンドと、gc
およびgccgo
という二つの主要なコンパイラの挙動の違いが深く関わっています。
Go言語では、テストコードは通常、対象となるパッケージと同じディレクトリに_test.go
というサフィックスを持つファイルとして配置されます。これらのテストは大きく分けて二種類あります。
- 内部テスト (Internal Tests):
package mypackage
のように、テスト対象のパッケージと同じパッケージ名を持つテストファイルです。これらのテストは、パッケージの内部実装にアクセスできます。 - 外部テスト (External Tests):
package mypackage_test
のように、テスト対象のパッケージとは異なるパッケージ名を持つテストファイルです。これらのテストは、パッケージが外部からどのように見えるかをテストするために使用され、パッケージのエクスポートされたAPIのみにアクセスできます。
go test
コマンドは、これらのテストファイルをコンパイルし、テスト実行可能なバイナリを生成します。この際、gc
(標準のGoコンパイラ)とgccgo
(GCCベースのGoコンパイラ)では、内部的なリンケージの挙動に違いがありました。
問題の発端は、CL 61970044
という変更にあります。この変更は、gccgo
が.a
(アーカイブ)ファイルをリンカに渡す順序を変更しました。リンカの動作において、ライブラリやオブジェクトファイルの順序は非常に重要です。シンボル(関数や変数など)の定義が複数のファイルに存在する場合、リンカは通常、コマンドラインで最初に見つかった定義を採用します。また、あるファイルが別のファイルで定義されたシンボルを参照する場合、参照するファイルが参照されるファイルよりも前にリンカに渡されると、シンボルが見つからないというエラーが発生する可能性があります。
CL 61970044
による順序の変更は、gccgo
がパッケージの内部バージョン(例: liba.a
)と外部テストバージョン(例: liba_test.a
)の両方を処理する際に問題を引き起こしました。
- 内部バージョン (
liba.a
): これは通常のパッケージコードを含みます。 - 外部テストバージョン (
liba_test.a
): これは外部テストコードを含み、通常は内部パッケージのシンボルをインポートして使用します。外部テストパッケージは、内部パッケージの「スーパーセット」と見なされることがあります。つまり、外部テストパッケージは内部パッケージのすべてのシンボルを含み、さらにテストに必要な追加のシンボルも持っている可能性があります。
CL 61970044
によってリンカへの入力順序が逆になった結果、gccgo
は以下のいずれかのエラーを報告するようになりました。
- シンボル不足: 内部パッケージ (
liba.a
) がリンカに先に渡された場合、外部テスト (liba_test.a
) が必要とする特定のシンボルが内部パッケージには含まれていないため、リンカがシンボルを見つけられない。 - シンボル重複: 外部テストパッケージ (
liba_test.a
) が先に渡された場合、それが内部パッケージのシンボルを既に含んでいるため、後から渡される内部パッケージ (liba.a
) の同じシンボルと重複してしまう。
この問題は、Goのビルドシステムが異なるコンパイラ(gc
とgccgo
)間で一貫した動作を提供する必要があるという原則に反していました。特に、gcToolchain
(gc
コンパイラを使用するツールチェーン)は、テストをリンクする際に、$WORK/_test
(外部テストのビルド成果物)を$WORK
(通常のパッケージのビルド成果物)よりも先にリンカに提示していました。これは、外部テストが内部パッケージのスーパーセットであるという性質を考慮した、正しいリンケージ順序です。
このコミットは、gccgo
のリンケージ動作をgcToolchain
の動作に合わせることで、この問題を解決し、go test
コマンドの信頼性と一貫性を向上させることを目的としています。
前提知識の解説
1. Go言語のビルドシステム (go build
, go test
)
Go言語のビルドシステムは、go
コマンドによって統合されています。
go build
: ソースコードをコンパイルし、実行可能なバイナリやライブラリを生成します。go test
: テストコードをコンパイルし、テストを実行するための特別なバイナリを生成します。このコマンドは、テスト対象のパッケージとテストファイル(_test.go
)を自動的に検出し、ビルドプロセスを管理します。
2. gc
と gccgo
コンパイラ
Go言語には主に二つのコンパイラ実装が存在します。
gc
(Go Compiler): Goプロジェクトの公式かつデフォルトのコンパイラです。Go言語で書かれており、高速なコンパイルと最適化が特徴です。gccgo
: GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。C/C++など他の言語と同じGCCのバックエンドを利用するため、既存のGCCツールチェーンとの統合が容易であり、特定のプラットフォームや最適化のニーズに対応できる場合があります。しかし、gc
とは異なるビルド・リンケージの挙動を示すことがあります。
3. 内部テストと外部テスト
Goのテストファイルは、ファイル名が_test.go
で終わることで識別されます。これらのテストは、パッケージの構造によって「内部テスト」と「外部テスト」に分類されます。
- 内部テスト:
package mypackage
のように、テスト対象のパッケージと同じパッケージ名を持つテストファイルです。これにより、テストコードはパッケージ内の非エクスポート(プライベート)な関数や変数にもアクセスできます。 - 外部テスト:
package mypackage_test
のように、テスト対象のパッケージとは異なるパッケージ名を持つテストファイルです。これにより、テストコードはパッケージのエクスポートされた(パブリックな)APIのみにアクセスでき、パッケージが外部からどのように利用されるかをより正確にシミュレートできます。go test
コマンドは、外部テストをコンパイルする際に、元のパッケージとは別の「偽の (fake)」パッケージとして扱います。
4. リンカの動作とライブラリのリンク順序
リンカは、コンパイラによって生成されたオブジェクトファイル(.o
)やアーカイブファイル(.a
、ライブラリの集合)を結合し、最終的な実行可能ファイルや共有ライブラリを生成するツールです。リンカの動作において、入力ファイルの順序は非常に重要です。
- シンボル解決: リンカは、あるオブジェクトファイルが参照するシンボル(関数や変数)の定義を、入力された他のオブジェクトファイルやライブラリの中から探します。
- 順序依存性: 一般的に、リンカはコマンドラインで指定された順序でファイルを処理します。あるファイルが別のファイルで定義されたシンボルを参照する場合、参照されるファイルが参照するファイルよりも後にリンカに渡されると、シンボルが見つからないエラー(未定義参照)が発生します。逆に、同じシンボルが複数のファイルで定義されている場合、リンカは通常、コマンドラインで最初に見つかった定義を採用します。
- アーカイブファイル (
.a
): 複数のオブジェクトファイルをまとめた静的ライブラリです。リンカは、アーカイブファイルの中から必要なシンボル定義のみを抽出してリンクします。
5. $WORK
と $WORK/_test
Goのビルドプロセスでは、一時的な作業ディレクトリが作成されます。
$WORK
: 通常のパッケージのビルド成果物(オブジェクトファイルやアーカイブファイル)が配置されるディレクトリを指します。$WORK/_test
: 外部テストパッケージのビルド成果物が配置されるディレクトリを指します。外部テストは、通常のパッケージとは別の独立したパッケージとして扱われるため、専用の作業ディレクトリが割り当てられます。
技術的詳細
このコミットが修正しようとしている問題は、gccgo
コンパイラがGoの外部テストをリンクする際の、.a
ファイルの処理順序に起因するものです。
CL 61970044
による問題の発生
以前の変更であるCL 61970044
は、gccgo
がリンカに渡す.a
ファイルの順序を逆転させました。この変更の具体的な意図はコミットメッセージからは不明ですが、結果として、パッケージの内部バージョン(例: mypackage.a
)と外部テストバージョン(例: mypackage_test.a
)が同時にリンカに渡された際に、gccgo
がエラーを報告するようになりました。
この問題は、リンカのシンボル解決のメカニズムと、外部テストパッケージの性質に深く関連しています。
-
外部テストパッケージの性質: 外部テストパッケージ(
mypackage_test
)は、テスト対象の内部パッケージ(mypackage
)の公開されたAPIを利用します。多くの場合、外部テストパッケージは、内部パッケージのコンパイル済みコードをインポートし、それに加えてテスト固有のコードを含んでいます。このため、外部テストパッケージのアーカイブファイル(mypackage_test.a
)は、内部パッケージのアーカイブファイル(mypackage.a
)に含まれるシンボルの多くを「スーパーセット」として含んでいるか、あるいはそれらのシンボルを参照しています。 -
リンカの順序依存性:
CL 61970044
後の問題:CL 61970044
によってリンカへの入力順序が逆になった結果、もし内部パッケージのアーカイブ(mypackage.a
)が外部テストパッケージのアーカイブ(mypackage_test.a
)よりも後にリンカに渡されると、mypackage_test.a
がmypackage.a
内のシンボルを参照しているにもかかわらず、リンカがそれらのシンボルを解決できないという「シンボル不足」のエラーが発生する可能性がありました。- あるいは、もし
mypackage_test.a
がmypackage.a
のシンボルを既に含んでいる(スーパーセットである)場合、mypackage_test.a
が先に渡され、その後にmypackage.a
が渡されると、同じシンボルが二重に定義されているとリンカが判断し、「シンボル重複」のエラーが発生する可能性がありました。
コミットメッセージは、この問題が「内部バージョンが必要なシンボルをすべて含んでいないか、または外部テストバージョンが既に定義されているシンボルを重複して定義している」という形で現れたと説明しています。これは、リンカの順序が不適切であったために、シンボル解決の競合が発生したことを示唆しています。
「偽の (fake)」ターゲットとリンケージ順序
このコミットでは、「偽の (fake)」ターゲットという概念が導入されています。これは、主に外部テストパッケージのように、通常のパッケージとは異なる特別なリンケージ要件を持つビルド成果物を指します。コミットメッセージは、「外部テストとしてコンパイルされたパッケージは、その内部の兄弟パッケージのスーパーセットである」と述べています。この性質を考慮すると、リンカは外部テストパッケージのシンボル定義を、通常のパッケージのシンボル定義よりも優先して処理する必要があります。
gcToolchain
との整合性
このコミットの重要な動機の一つは、gccgo
のリンケージ動作をgcToolchain
の動作に合わせることです。gcToolchain
は、テストをリンクする際に、$WORK/_test
(外部テストのビルド成果物)を$WORK
(通常のパッケージのビルド成果物)よりも先にリンカに提示していました。これは、外部テストが内部パッケージのスーパーセットであるという事実に基づいた、正しい戦略です。外部テストが先に処理されることで、その中に含まれるシンボルが優先され、後から通常のパッケージが処理されてもシンボル重複の問題は発生せず、また外部テストが参照するシンボルも適切に解決されます。
このコミットは、gccgo
のリンケージロジックを修正し、gcToolchain
と同様に「偽の」ターゲット(外部テストファイル)をリンカへの入力順序の最上位に配置することで、この問題を解決します。これにより、gccgo
とgc
の両方のコンパイラで、go test
コマンドが期待通りに動作するようになります。
コアとなるコードの変更箇所
変更は src/cmd/go/build.go
ファイルの gccgoToolchain
構造体の ld
メソッド内で行われています。
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1867,7 +1867,12 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
if !a.p.Standard {
if a.p != nil && !apackagesSeen[a.p] {
apackagesSeen[a.p] = true
- afiles = append(afiles, a.target)
+ if a.p.fake {
+ // move _test files to the top of the link order
+ afiles = append([]string{a.target}, afiles...)
+ } else {
+ afiles = append(afiles, a.target)
+ }
}
}
}
コアとなるコードの解説
この変更は、gccgoToolchain
のld
(リンカ)メソッド内で、リンカに渡すアーカイブファイル(.a
ファイル)のリストafiles
を構築するロジックを修正しています。
元のコードでは、a.target
(現在のアーカイブファイルのパス)は常にafiles
リストの末尾に追加されていました。
afiles = append(afiles, a.target)
この変更により、a.p.fake
という条件が追加されました。
a.p
は、現在処理しているパッケージを表すPackage
構造体です。a.p.fake
は、そのパッケージが「偽の」パッケージ、つまり主に外部テストパッケージであるかどうかを示すブール値のフィールドです。
新しいロジックは以下のようになります。
if a.p.fake {
// move _test files to the top of the link order
afiles = append([]string{a.target}, afiles...)
} else {
afiles = append(afiles, a.target)
}
-
if a.p.fake
の場合:a.p.fake
がtrue
、つまり現在のパッケージが外部テストのような「偽の」パッケージである場合、a.target
はafiles
リストの先頭に追加されます。append([]string{a.target}, afiles...)
という構文は、新しいスライスを作成し、その最初の要素としてa.target
を置き、その後に既存のafiles
のすべての要素を展開して追加します。これにより、a.target
がリストの最上位(リンカに最初に渡される位置)に配置されることが保証されます。- コメント
// move _test files to the top of the link order
が、この変更の意図を明確に示しています。
-
else
の場合:a.p.fake
がfalse
、つまり通常のパッケージである場合、a.target
はこれまで通りafiles
リストの末尾に追加されます。
この変更の核心は、リンカに渡すファイルの順序を制御することにあります。外部テストファイル(_test.a
)は、テスト対象の通常のパッケージファイル(.a
)のスーパーセットであるため、リンカがシンボルを解決する際に、外部テストファイル内の定義を優先させる必要があります。この修正により、gccgo
はgcToolchain
と同様に、外部テストファイルを通常のパッケージファイルよりも先にリンカに提示するようになり、シンボル重複や未定義シンボルの問題を回避できるようになりました。
関連リンク
- Go Issue #7627 (コミットメッセージで参照されているが、GitHub上では見つからなかった): https://github.com/golang/go/issues/7627 (直接のリンクは機能しない可能性がありますが、コミットメッセージに記載されているため含めます)
- Go CL 80300043 (このコミットのGo Code Reviewサイトのリンク): https://golang.org/cl/80300043
- Go CL 61970044 (問題を引き起こした以前の変更): https://golang.org/cl/61970044 (このCLの具体的な内容は、コミットメッセージの記述とWeb検索結果で食い違いがあるため、注意が必要です。コミットメッセージの記述を優先しています。)
参考にした情報源リンク
- Go言語の公式ドキュメント (Goのビルド、テスト、コンパイラに関する一般的な情報): https://go.dev/
- GCCのリンカに関する一般的な情報 (リンカの順序依存性について):
- Goのテストに関する情報 (内部テストと外部テストの概念):
gccgo
に関する情報:- Web検索結果 (上記「Web Search for context」セクションで実行した検索の出力):
golang/go issue 7627
の検索結果CL 61970044 golang
の検索結果gccgo linker order go external test files
の検索結果gcToolchain links tests golang
の検索結果 (これらの検索結果は、一般的な背景知識の収集に利用しましたが、特定のCLやIssueに関する記述は、コミットメッセージの情報を優先しました。)
I have generated the detailed explanation in Markdown format, following all the specified instructions and chapter structure. I have incorporated the information from the commit message and the general knowledge from the web searches. I have also addressed the discrepancies found during the web search by prioritizing the commit message's information.
The output is now ready to be presented to the user.
[インデックス 18956] ファイルの概要
このコミットは、Go言語のビルドツール (cmd/go
) において、gccgo
コンパイラを使用する際の外部テストファイルのリンク順序に関する問題を修正するものです。具体的には、以前の変更によって発生した、内部パッケージと外部テストパッケージのリンケージにおけるシンボル重複または不足のエラーを解決し、gc
コンパイラの動作と整合性を持たせることを目的としています。
コミット
commit 3ce1677ad9f95fb0b1ede191f41996c451e054c1
Author: Dave Cheney <dave@cheney.net>
Date: Wed Mar 26 15:30:55 2014 +1100
cmd/go: ensure external test files are presented to the linker first
Fixes #7627.
CL 61970044 changed the order in which .a files are passed to gccgo's link phase. However by reversing the order it caused gccgo to complain if both internal (liba.a) and external (liba_test.a) versions of a package were presented as the former would not contain all the necessary symbols, and the latter would duplicate symbols already defined.
This change ensures that all 'fake' targets remain at the top of the final link order which should be fine as a package compiled as an external test is a superset of its internal sibling.
Looking at how gcToolchain links tests I think this change now accurately mirrors those actions which present $WORK/_test before $WORK in the link order.
LGTM=iant
R=rsc, iant
CC=golang-codereviews, michael.hudson
https://golang.org/cl/80300043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3ce1677ad9f95fb0b1ede191f41996c451e054c1
元コミット内容
このコミットは、cmd/go
ツールにおいて、gccgo
コンパイラが外部テストファイルをリンクする際に、それらのファイルをリンカに最初に渡すように変更します。
これは、Issue #7627 を修正するものです。
以前の変更である CL 61970044
は、.a
(アーカイブ) ファイルが gccgo
のリンクフェーズに渡される順序を変更しました。しかし、この順序を逆にしたことで、パッケージの内部バージョン (liba.a
) と外部テストバージョン (liba_test.a
) の両方がリンカに渡された場合に、gccgo
がエラーを報告するようになりました。具体的には、内部バージョンが必要なシンボルをすべて含んでいないか、または外部テストバージョンが既に定義されているシンボルを重複して定義しているという問題が発生しました。
この変更は、「偽の (fake)」ターゲット(主に外部テストファイル)が最終的なリンク順序の最上位に維持されることを保証します。これは、外部テストとしてコンパイルされたパッケージが、その内部の兄弟パッケージのスーパーセットであるため、問題ないはずです。
gcToolchain
がテストをリンクする方法を見ると、この変更は $WORK/_test
を $WORK
の前にリンク順序で提示するそれらのアクションを正確に反映していると考えられます。
変更の背景
このコミットの背景には、Go言語のビルドシステム、特にgo test
コマンドと、gc
およびgccgo
という二つの主要なコンパイラの挙動の違いが深く関わっています。
Go言語では、テストコードは通常、対象となるパッケージと同じディレクトリに_test.go
というサフィックスを持つファイルとして配置されます。これらのテストは大きく分けて二種類あります。
- 内部テスト (Internal Tests):
package mypackage
のように、テスト対象のパッケージと同じパッケージ名を持つテストファイルです。これらのテストは、パッケージの内部実装にアクセスできます。 - 外部テスト (External Tests):
package mypackage_test
のように、テスト対象のパッケージとは異なるパッケージ名を持つテストファイルです。これらのテストは、パッケージが外部からどのように見えるかをテストするために使用され、パッケージのエクスポートされたAPIのみにアクセスできます。
go test
コマンドは、これらのテストファイルをコンパイルし、テスト実行可能なバイナリを生成します。この際、gc
(標準のGoコンパイラ)とgccgo
(GCCベースのGoコンパイラ)では、内部的なリンケージの挙動に違いがありました。
問題の発端は、CL 61970044
という変更にあります。この変更は、gccgo
が.a
(アーカイブ)ファイルをリンカに渡す順序を変更しました。リンカの動作において、ライブラリやオブジェクトファイルの順序は非常に重要です。シンボル(関数や変数など)の定義が複数のファイルに存在する場合、リンカは通常、コマンドラインで最初に見つかった定義を採用します。また、あるファイルが別のファイルで定義されたシンボルを参照する場合、参照するファイルが参照されるファイルよりも前にリンカに渡されると、シンボルが見つからないというエラーが発生する可能性があります。
CL 61970044
によってリンカへの入力順序が逆になった結果、gccgo
はパッケージの内部バージョン(例: liba.a
)と外部テストバージョン(例: liba_test.a
)の両方を処理する際に問題を引き起こしました。
- 内部バージョン (
liba.a
): これは通常のパッケージコードを含みます。 - 外部テストバージョン (
liba_test.a
): これは外部テストコードを含み、通常は内部パッケージのシンボルをインポートして使用します。外部テストパッケージは、内部パッケージの「スーパーセット」と見なされることがあります。つまり、外部テストパッケージは内部パッケージのすべてのシンボルを含み、さらにテストに必要な追加のシンボルも持っている可能性があります。
CL 61970044
によってリンカへの入力順序が逆になった結果、gccgo
は以下のいずれかのエラーを報告するようになりました。
- シンボル不足: 内部パッケージ (
liba.a
) がリンカに先に渡された場合、外部テスト (liba_test.a
) が必要とする特定のシンボルが内部パッケージには含まれていないため、リンカがシンボルを見つけられない。 - シンボル重複: 外部テストパッケージ (
liba_test.a
) が先に渡された場合、それが内部パッケージのシンボルを既に含んでいるため、後から渡される内部パッケージ (liba.a
) の同じシンボルと重複してしまう。
この問題は、Goのビルドシステムが異なるコンパイラ(gc
とgccgo
)間で一貫した動作を提供する必要があるという原則に反していました。特に、gcToolchain
(gc
コンパイラを使用するツールチェーン)は、テストをリンクする際に、$WORK/_test
(外部テストのビルド成果物)を$WORK
(通常のパッケージのビルド成果物)よりも先にリンカに提示していました。これは、外部テストが内部パッケージのスーパーセットであるという性質を考慮した、正しいリンケージ順序です。
このコミットは、gccgo
のリンケージ動作をgcToolchain
の動作に合わせることで、この問題を解決し、go test
コマンドの信頼性と一貫性を向上させることを目的としています。
前提知識の解説
1. Go言語のビルドシステム (go build
, go test
)
Go言語のビルドシステムは、go
コマンドによって統合されています。
go build
: ソースコードをコンパイルし、実行可能なバイナリやライブラリを生成します。go test
: テストコードをコンパイルし、テストを実行するための特別なバイナリを生成します。このコマンドは、テスト対象のパッケージとテストファイル(_test.go
)を自動的に検出し、ビルドプロセスを管理します。
2. gc
と gccgo
コンパイラ
Go言語には主に二つのコンパイラ実装が存在します。
gc
(Go Compiler): Goプロジェクトの公式かつデフォルトのコンパイラです。Go言語で書かれており、高速なコンパイルと最適化が特徴です。gccgo
: GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。C/C++など他の言語と同じGCCのバックエンドを利用するため、既存のGCCツールチェーンとの統合が容易であり、特定のプラットフォームや最適化のニーズに対応できる場合があります。しかし、gc
とは異なるビルド・リンケージの挙動を示すことがあります。
3. 内部テストと外部テスト
Goのテストファイルは、ファイル名が_test.go
で終わることで識別されます。これらのテストは、パッケージの構造によって「内部テスト」と「外部テスト」に分類されます。
- 内部テスト:
package mypackage
のように、テスト対象のパッケージと同じパッケージ名を持つテストファイルです。これにより、テストコードはパッケージ内の非エクスポート(プライベート)な関数や変数にもアクセスできます。 - 外部テスト:
package mypackage_test
のように、テスト対象のパッケージとは異なるパッケージ名を持つテストファイルです。これにより、テストコードはパッケージのエクスポートされた(パブリックな)APIのみにアクセスでき、パッケージが外部からどのように利用されるかをより正確にシミュレートできます。go test
コマンドは、外部テストをコンパイルする際に、元のパッケージとは別の「偽の (fake)」パッケージとして扱います。
4. リンカの動作とライブラリのリンク順序
リンカは、コンパイラによって生成されたオブジェクトファイル(.o
)やアーカイブファイル(.a
、ライブラリの集合)を結合し、最終的な実行可能ファイルや共有ライブラリを生成するツールです。リンカの動作において、入力ファイルの順序は非常に重要です。
- シンボル解決: リンカは、あるオブジェクトファイルが参照するシンボル(関数や変数)の定義を、入力された他のオブジェクトファイルやライブラリの中から探します。
- 順序依存性: 一般的に、リンカはコマンドラインで指定された順序でファイルを処理します。あるファイルが別のファイルで定義されたシンボルを参照する場合、参照されるファイルが参照するファイルよりも後にリンカに渡されると、シンボルが見つからないエラー(未定義参照)が発生します。逆に、同じシンボルが複数のファイルで定義されている場合、リンカは通常、コマンドラインで最初に見つかった定義を採用します。
- アーカイブファイル (
.a
): 複数のオブジェクトファイルをまとめた静的ライブラリです。リンカは、アーカイブファイルの中から必要なシンボル定義のみを抽出してリンクします。
5. $WORK
と $WORK/_test
Goのビルドプロセスでは、一時的な作業ディレクトリが作成されます。
$WORK
: 通常のパッケージのビルド成果物(オブジェクトファイルやアーカイブファイル)が配置されるディレクトリを指します。$WORK/_test
: 外部テストパッケージのビルド成果物が配置されるディレクトリを指します。外部テストは、通常のパッケージとは別の独立したパッケージとして扱われるため、専用の作業ディレクトリが割り当てられます。
技術的詳細
このコミットが修正しようとしている問題は、gccgo
コンパイラがGoの外部テストをリンクする際の、.a
ファイルの処理順序に起因するものです。
CL 61970044
による問題の発生
以前の変更であるCL 61970044
は、gccgo
がリンカに渡す.a
ファイルの順序を逆転させました。この変更の具体的な意図はコミットメッセージからは不明ですが、結果として、パッケージの内部バージョン(例: mypackage.a
)と外部テストバージョン(例: mypackage_test.a
)が同時にリンカに渡された際に、gccgo
がエラーを報告するようになりました。
この問題は、リンカのシンボル解決のメカニズムと、外部テストパッケージの性質に深く関連しています。
-
外部テストパッケージの性質: 外部テストパッケージ(
mypackage_test
)は、テスト対象の内部パッケージ(mypackage
)の公開されたAPIを利用します。多くの場合、外部テストパッケージは、内部パッケージのコンパイル済みコードをインポートし、それに加えてテスト固有のコードを含んでいます。このため、外部テストパッケージのアーカイブファイル(mypackage_test.a
)は、内部パッケージのアーカイブファイル(mypackage.a
)に含まれるシンボルの多くを「スーパーセット」として含んでいるか、あるいはそれらのシンボルを参照しています。 -
リンカの順序依存性:
CL 61970044
後の問題:CL 61970044
によってリンカへの入力順序が逆になった結果、もし内部パッケージのアーカイブ(mypackage.a
)が外部テストパッケージのアーカイブ(mypackage_test.a
)よりも後にリンカに渡されると、mypackage_test.a
がmypackage.a
内のシンボルを参照しているにもかかわらず、リンカがそれらのシンボルを解決できないという「シンボル不足」のエラーが発生する可能性がありました。- あるいは、もし
mypackage_test.a
がmypackage.a
のシンボルを既に含んでいる(スーパーセットである)場合、mypackage_test.a
が先に渡され、その後にmypackage.a
が渡されると、同じシンボルが二重に定義されているとリンカが判断し、「シンボル重複」のエラーが発生する可能性がありました。
コミットメッセージは、この問題が「内部バージョンが必要なシンボルをすべて含んでいないか、または外部テストバージョンが既に定義されているシンボルを重複して定義している」という形で現れたと説明しています。これは、リンカの順序が不適切であったために、シンボル解決の競合が発生したことを示唆しています。
「偽の (fake)」ターゲットとリンケージ順序
このコミットでは、「偽の (fake)」ターゲットという概念が導入されています。これは、主に外部テストパッケージのように、通常のパッケージとは異なる特別なリンケージ要件を持つビルド成果物を指します。コミットメッセージは、「外部テストとしてコンパイルされたパッケージは、その内部の兄弟パッケージのスーパーセットである」と述べています。この性質を考慮すると、リンカは外部テストパッケージのシンボル定義を、通常のパッケージのシンボル定義よりも優先して処理する必要があります。
gcToolchain
との整合性
このコミットの重要な動機の一つは、gccgo
のリンケージ動作をgcToolchain
の動作に合わせることです。gcToolchain
は、テストをリンクする際に、$WORK/_test
(外部テストのビルド成果物)を$WORK
(通常のパッケージのビルド成果物)よりも先にリンカに提示していました。これは、外部テストが内部パッケージのスーパーセットであるという事実に基づいた、正しい戦略です。外部テストが先に処理されることで、その中に含まれるシンボルが優先され、後から通常のパッケージが処理されてもシンボル重複の問題は発生せず、また外部テストが参照するシンボルも適切に解決されます。
このコミットは、gccgo
のリンケージロジックを修正し、gcToolchain
と同様に「偽の」ターゲット(外部テストファイル)をリンカへの入力順序の最上位に配置することで、この問題を解決します。これにより、gccgo
とgc
の両方のコンパイラで、go test
コマンドが期待通りに動作するようになります。
コアとなるコードの変更箇所
変更は src/cmd/go/build.go
ファイルの gccgoToolchain
構造体の ld
メソッド内で行われています。
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1867,7 +1867,12 @@ func (tools gccgoToolchain) ld(b *builder, p *Package, out string, allactions []
if !a.p.Standard {
if a.p != nil && !apackagesSeen[a.p] {
apackagesSeen[a.p] = true
- afiles = append(afiles, a.target)
+ if a.p.fake {
+ // move _test files to the top of the link order
+ afiles = append([]string{a.target}, afiles...)
+ } else {
+ afiles = append(afiles, a.target)
+ }
}
}
}
コアとなるコードの解説
この変更は、gccgoToolchain
のld
(リンカ)メソッド内で、リンカに渡すアーカイブファイル(.a
ファイル)のリストafiles
を構築するロジックを修正しています。
元のコードでは、a.target
(現在のアーカイブファイルのパス)は常にafiles
リストの末尾に追加されていました。
afiles = append(afiles, a.target)
この変更により、a.p.fake
という条件が追加されました。
a.p
は、現在処理しているパッケージを表すPackage
構造体です。a.p.fake
は、そのパッケージが「偽の」パッケージ、つまり主に外部テストパッケージであるかどうかを示すブール値のフィールドです。
新しいロジックは以下のようになります。
if a.p.fake {
// move _test files to the top of the link order
afiles = append([]string{a.target}, afiles...)
} else {
afiles = append(afiles, a.target)
}
-
if a.p.fake
の場合:a.p.fake
がtrue
、つまり現在のパッケージが外部テストのような「偽の」パッケージである場合、a.target
はafiles
リストの先頭に追加されます。append([]string{a.target}, afiles...)
という構文は、新しいスライスを作成し、その最初の要素としてa.target
を置き、その後に既存のafiles
のすべての要素を展開して追加します。これにより、a.target
がリストの最上位(リンカに最初に渡される位置)に配置されることが保証されます。- コメント
// move _test files to the top of the link order
が、この変更の意図を明確に示しています。
-
else
の場合:a.p.fake
がfalse
、つまり通常のパッケージである場合、a.target
はこれまで通りafiles
リストの末尾に追加されます。
この変更の核心は、リンカに渡すファイルの順序を制御することにあります。外部テストファイル(_test.a
)は、テスト対象の通常のパッケージファイル(.a
)のスーパーセットであるため、リンカがシンボルを解決する際に、外部テストファイル内の定義を優先させる必要があります。この修正により、gccgo
はgcToolchain
と同様に、外部テストファイルを通常のパッケージファイルよりも先にリンカに提示するようになり、シンボル重複や未定義シンボルの問題を回避できるようになりました。
関連リンク
- Go Issue #7627 (コミットメッセージで参照されているが、GitHub上では見つからなかった): https://github.com/golang/go/issues/7627 (直接のリンクは機能しない可能性がありますが、コミットメッセージに記載されているため含めます)
- Go CL 80300043 (このコミットのGo Code Reviewサイトのリンク): https://golang.org/cl/80300043
- Go CL 61970044 (問題を引き起こした以前の変更): https://golang.org/cl/61970044 (このCLの具体的な内容は、コミットメッセージの記述とWeb検索結果で食い違いがあるため、注意が必要です。コミットメッセージの記述を優先しています。)
参考にした情報源リンク
- Go言語の公式ドキュメント (Goのビルド、テスト、コンパイラに関する一般的な情報): https://go.dev/
- GCCのリンカに関する一般的な情報 (リンカの順序依存性について):
- Goのテストに関する情報 (内部テストと外部テストの概念):
gccgo
に関する情報:- Web検索結果 (上記「Web Search for context」セクションで実行した検索の出力):
golang/go issue 7627
の検索結果CL 61970044 golang
の検索結果gccgo linker order go external test files
の検索結果gcToolchain links tests golang
の検索結果 (これらの検索結果は、一般的な背景知識の収集に利用しましたが、特定のCLやIssueに関する記述は、コミットメッセージの情報を優先しました。)