Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18189] ファイルの概要

このコミットは、Go言語のツールチェイン、特にcmd/goコマンドにおけるテスト実行時の問題修正に関するものです。具体的には、go testコマンドがgccgoコンパイラを使用する際に、テスト対象のパッケージ名が標準ライブラリのパッケージ名と衝突する場合に発生するオブジェクトファイル配置の問題を解決します。

コミット

commit 7178c05d05f2372c0c2b027c16b5e904d4259f6e
Author: Michael Hudson-Doyle <michael.hudson@linaro.org>
Date:   Tue Jan 7 23:53:16 2014 -0500

    cmd/go: test: do not put object files where later steps will find them
    
    When recompiling a package whose basename is the name of a standard
    package for testing with gccgo, a .o file with the basename of the
    package being tested was being placed in the _test/ directory where the
    compilation of the test binary then found it when looking for the
    standard library package.
    
    This change puts the object files in a separate directory.
    
    Fixes #6793
    
    R=golang-codereviews, dave, gobot, rsc, iant
    CC=golang-codereviews
    https://golang.org/cl/27650045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/7178c05d05f2372c0c2b027c16b5e904d4259f6e

元コミット内容

cmd/go: test: do not put object files where later steps will find them

このコミットは、cmd/goツールにおけるテスト実行時の挙動を修正するものです。特に、gccgoを使用してテストを行う際に、テスト対象のパッケージのベース名が標準パッケージの名前と一致する場合に問題が発生していました。具体的には、テスト対象パッケージのオブジェクトファイル(.oファイル)が_test/ディレクトリに配置され、その後のテストバイナリのコンパイル時に、標準ライブラリのパッケージを探しているコンパイラが誤ってこのオブジェクトファイルを見つけてしまうという問題です。この変更は、オブジェクトファイルを別の専用ディレクトリに配置することで、この問題を解決します。

変更の背景

Go言語のビルドシステムは、パッケージのコンパイル時に生成される中間ファイル(オブジェクトファイルなど)を特定のディレクトリに配置します。go testコマンドは、テストを実行するために、テスト対象のパッケージとそのテストコードをコンパイルし、テストバイナリを生成します。このプロセスにおいて、一時的なディレクトリ(通常は_test/のような名前)が使用されます。

問題は、gccgoコンパイラを使用している場合に顕在化しました。gccgoは、GoのコードをGCCのバックエンドを使用してコンパイルする代替コンパイラです。特定のシナリオ、特にテスト対象のパッケージのベース名(例: fmtnet/httpなど)がGoの標準ライブラリのパッケージ名と偶然一致する場合に、コンパイル時の名前解決の衝突が発生していました。

具体的には、go testがテスト対象パッケージをコンパイルして生成したオブジェクトファイル(例: fmt.o)が、テストバイナリをリンクする際に_test/ディレクトリに存在していました。gccgoは、標準ライブラリのfmtパッケージを探す際に、このfmt.oファイルを誤って見つけてしまい、本来の標準ライブラリのfmtパッケージではなく、テスト対象パッケージのオブジェクトファイルを使用してしまい、結果としてビルドエラーや予期せぬ動作を引き起こしていました。

このコミットは、このオブジェクトファイルの配置に関する競合を解消し、テストプロセスの堅牢性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のビルドとテストに関する基本的な知識が必要です。

  1. Goのパッケージとビルドプロセス:

    • Goのコードは「パッケージ」として組織されます。各パッケージは、通常、ディレクトリに対応し、そのディレクトリ内のソースファイルがパッケージを構成します。
    • go buildコマンドは、Goのソースコードをコンパイルして実行可能ファイルやライブラリを生成します。この際、中間ファイルとしてオブジェクトファイル(.oファイル)が生成されます。
    • Goのビルドシステムは、依存関係を解決し、必要なパッケージを再帰的にコンパイルします。
  2. go testコマンド:

    • go testは、Goのパッケージのテストを実行するためのコマンドです。
    • テストを実行する際、go testはテスト対象のパッケージと、_test.goで終わるテストファイルをコンパイルし、単一のテストバイナリを生成します。
    • このテストバイナリは、テスト対象のパッケージのコードとテストコードの両方を含みます。
    • テストのコンパイルと実行のために、一時的な作業ディレクトリが作成されます。このディレクトリは通常、パッケージのソースディレクトリ内に_test/のような名前で作成されます。
  3. gccgoコンパイラ:

    • Go言語には、公式のコンパイラであるgc(Go Compiler)と、GCCのフロントエンドとして実装されたgccgoの2つの主要なコンパイラがあります。
    • gcはGo言語で書かれており、Goの標準的なビルドツールチェーンの一部です。
    • gccgoはC/C++コンパイラであるGCCのフレームワークを利用しており、異なる最適化やプラットフォームサポートを提供することがあります。
    • この問題は、特にgccgoのビルドプロセスにおけるオブジェクトファイルの検索パスと名前解決の挙動に関連していました。
  4. オブジェクトファイルとリンク:

    • コンパイラはソースコードをオブジェクトファイルに変換します。オブジェクトファイルには、コンパイルされた機械語コードと、他のオブジェクトファイルやライブラリへの参照(シンボル)が含まれます。
    • リンカは、複数のオブジェクトファイルとライブラリを結合して、最終的な実行可能ファイルや共有ライブラリを生成します。この際、リンカは必要なシンボルを解決するために、指定されたパスからオブジェクトファイルやライブラリを検索します。

このコミットは、go testgccgoを使用する際に、テスト対象パッケージのオブジェクトファイルが、リンカが標準ライブラリのオブジェクトファイルを検索するパスと衝突するという、特定のビルド環境におけるエッジケースを扱っています。

技術的詳細

この問題の核心は、go testがテスト対象パッケージをコンパイルする際に生成するオブジェクトファイル(.o)の配置場所と、gccgoが標準ライブラリのオブジェクトファイルを検索するメカニズムの間の相互作用にありました。

通常、go testはテスト実行のために一時的なディレクトリ(例: _test/)を作成し、その中にコンパイルされたオブジェクトファイルを配置します。例えば、my_packageというパッケージをテストする場合、my_package.oのようなオブジェクトファイルが_test/ディレクトリに生成されます。

問題が発生したのは、テスト対象のパッケージのベース名が、Goの標準ライブラリのパッケージ名と偶然一致した場合です。例えば、ユーザーがfmtという名前のカスタムパッケージを作成し、それをテストしようとしたとします。go test_test/fmt.oを生成します。

gccgoは、テストバイナリをリンクする際に、依存する標準ライブラリ(例: fmtパッケージ)のオブジェクトファイルを検索します。この検索パスには、一時的な_test/ディレクトリが含まれることがありました。その結果、gccgoは標準ライブラリのfmtパッケージの代わりに、ユーザーが作成したカスタムfmtパッケージの_test/fmt.oを誤って見つけてしまい、リンクエラーや不正な動作を引き起こしていました。これは、コンパイラが「最も近い」または「最初に発見した」同名のオブジェクトファイルを優先してしまう挙動に起因します。

このコミットの解決策はシンプルかつ効果的です。オブジェクトファイルの配置ディレクトリを、テストバイナリのコンパイル時に標準ライブラリの検索パスと衝突しないように変更します。具体的には、オブジェクトファイルを_test/ディレクトリのサブディレクトリ(例: _test/_obj_test/_test/_obj_xtest/)に移動させることで、名前の衝突を回避します。これにより、gccgoは正しく標準ライブラリのオブジェクトファイルを見つけることができ、テストプロセスが正常に完了するようになります。

この変更は、Goのビルドシステムが、異なるコンパイラや複雑なパッケージ構造の下でも堅牢に動作するための、細かながらも重要な改善点と言えます。

コアとなるコードの変更箇所

変更はsrc/cmd/go/test.goファイルに集中しています。

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -711,7 +711,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
 
 	if ptest != p {
 		a := b.action(modeBuild, modeBuild, ptest)
-		a.objdir = testDir + string(filepath.Separator)
+		a.objdir = testDir + string(filepath.Separator) + "_obj_test" + string(filepath.Separator)
 		a.objpkg = ptestObj
 		a.target = ptestObj
 		a.link = false
@@ -719,7 +719,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
 
 	if pxtest != nil {
 		a := b.action(modeBuild, modeBuild, pxtest)
-		a.objdir = testDir + string(filepath.Separator)
+		a.objdir = testDir + string(filepath.Separator) + "_obj_xtest" + string(filepath.Separator)
 		a.objpkg = buildToolchain.pkgpath(testDir, pxtest)
 		a.target = a.objpkg
 	}

コアとなるコードの解説

このコミットの変更は、builder構造体のtestメソッド内で行われています。このメソッドは、go testコマンドがテストバイナリをビルドする際のロジックをカプセル化しています。

変更の対象となっているのは、a.objdirというフィールドです。objdirは、コンパイルされたオブジェクトファイルが配置されるディレクトリパスを指定します。

元のコードでは、ptest(内部テストパッケージ)とpxtest(外部テストパッケージ)の両方について、objdirが単にtestDir + string(filepath.Separator)と設定されていました。これは、テスト用の一時ディレクトリ(例: _test/)のルートにオブジェクトファイルを直接配置することを意味します。

変更後のコードでは、objdirに新しいサブディレクトリが追加されています。

  • ptest(内部テストパッケージ)の場合: a.objdir = testDir + string(filepath.Separator) + "_obj_test" + string(filepath.Separator) これにより、内部テストパッケージのオブジェクトファイルは、_test/_obj_test/のようなパスに配置されるようになります。

  • pxtest(外部テストパッケージ)の場合: a.objdir = testDir + string(filepath.Separator) + "_obj_xtest" + string(filepath.Separator) 同様に、外部テストパッケージのオブジェクトファイルは、_test/_obj_xtest/のようなパスに配置されるようになります。

この変更により、テスト対象パッケージのオブジェクトファイルが、_test/ディレクトリのルートではなく、その下の専用サブディレクトリに隔離されます。これにより、gccgoが標準ライブラリのパッケージを検索する際に、_test/ディレクトリのルートにある同名のオブジェクトファイルを誤って見つけてしまうという問題が解消されます。リンカは、標準ライブラリのオブジェクトファイルを正しく見つけ、テストバイナリのコンパイルとリンクが正常に行われるようになります。

filepath.Separatorは、オペレーティングシステムに応じたパス区切り文字(Windowsでは\、Unix系では/)を挿入するために使用されています。

関連リンク

参考にした情報源リンク

  • コミットメッセージ自体
  • Go言語のソースコード(src/cmd/go/test.go
  • Go言語のビルドシステムとgo testの一般的な知識
  • gccgoの動作に関する一般的な知識

(注: コミットメッセージに記載されているFixes #6793のGitHub Issueは、現在の検索では直接見つけることができませんでした。これは、非常に古いIssueであるか、またはIssueトラッカーの移行などにより参照が変更された可能性があります。)