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

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

このコミットは、Go言語のビルドツールであるcmd/goにおいて、外部テストパッケージ(_testサフィックスを持つパッケージ)が依存するパッケージが適切に再コンパイルされない問題を修正するものです。また、ビルドプロセスの効率化のため、computeStale関数の呼び出しを一度にまとめるリファクタリングも含まれています。

コミット

  • コミットハッシュ: 2b64e00f164e951f24221c0d4c5b3fb66a604531
  • Author: Russ Cox rsc@golang.org
  • Date: Thu Mar 8 08:32:38 2012 -0500

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

https://github.com/golang/go/commit/2b64e00f164e951f24221c0d4c5b3fb66a604531

元コミット内容

cmd/go: rebuild external test package dependencies

Was missing recompilation of packages imported only
by external test packages (package foo_test), primarily
because Root was not set, so those packages looked like
they were from a different Go tree, so they were not
recompiled if they already existed.

Also clean things up so that only one call to computeStale
is needed.

Fixes #3238.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5786048

変更の背景

このコミットの主な背景は、Goのビルドシステムにおける外部テストパッケージの依存関係の取り扱いに関するバグ修正です。具体的には、go testコマンドで外部テストパッケージ(例: package foo_test)を実行する際、そのテストパッケージがインポートしている他のパッケージが、すでにコンパイル済みであっても、変更があった場合に適切に再コンパイルされないという問題がありました。

この問題の根本原因は、外部テストパッケージのビルド時に、そのパッケージのRootフィールドが正しく設定されていなかったことにあります。Rootが設定されていないと、ビルドシステムはそれらのパッケージを現在のGoツリーとは異なる場所にあるものと誤認し、結果として、すでに存在するコンパイル済みバイナリを再利用してしまい、最新の変更が反映されないという事態が発生していました。これは、開発者がコードを変更してもテストが古いバイナリで実行され、期待通りの結果が得られないという、非常に混乱を招く挙動につながります。

また、このコミットは、computeStale関数の呼び出しが複数箇所で行われていたのを、一度の呼び出しに集約することで、コードの整理と効率化を図ることも目的としています。

前提知識の解説

Goのパッケージとテスト

Go言語では、コードは「パッケージ」という単位で管理されます。各パッケージは、通常、ディレクトリに対応し、そのディレクトリ内の.goファイルがパッケージを構成します。

Goのテストは、go testコマンドによって実行されます。テストファイルは、テスト対象のパッケージと同じディレクトリに配置され、ファイル名の末尾に_test.goが付きます。テストには大きく分けて二つの種類があります。

  1. 内部テストパッケージ (Internal Test Packages):

    • テスト対象のパッケージと同じパッケージ名(例: package foo)を持つテストファイルです。
    • テスト対象のパッケージの内部(非エクスポート)の関数や変数にアクセスできます。
    • ビルド時には、テスト対象のパッケージとテストコードが一緒にコンパイルされます。
  2. 外部テストパッケージ (External Test Packages):

    • テスト対象のパッケージとは異なるパッケージ名(例: package foo_test)を持つテストファイルです。
    • テスト対象のパッケージをインポートしてテストを行います。
    • テスト対象のパッケージのエクスポートされた(大文字で始まる)関数や変数のみにアクセスできます。
    • これにより、ユーザーがパッケージをインポートして利用するのと同じ視点でテストを行うことができます。
    • このコミットで問題となっていたのは、この外部テストパッケージの依存関係の再コンパイルです。

GoのビルドシステムとRoot

Goのビルドシステムは、ソースコードから実行可能ファイルやライブラリを生成する役割を担っています。このシステムは、パッケージの依存関係を解決し、必要に応じて再コンパイルを行います。

Rootは、Goのビルドシステムにおいて、パッケージがどのGoのワークスペース(GOPATH)またはGoモジュールに属しているかを示す重要な情報です。ビルドシステムは、このRoot情報を用いて、パッケージのパスを解決し、そのパッケージが「古い」(stale)かどうか、つまり再コンパイルが必要かどうかを判断します。

もしパッケージのRootが正しく設定されていない場合、ビルドシステムはそのパッケージを現在のビルドコンテキストとは異なる場所にあるものと誤認し、そのパッケージの依存関係が変更されていても、すでにコンパイル済みのバイナリが存在すれば、それを再利用してしまう可能性があります。これが、外部テストパッケージの依存関係が再コンパイルされない問題の直接的な原因でした。

computeStale関数

computeStale関数は、Goのビルドシステム内部で使用される関数で、特定のパッケージが「古い」(stale)状態にあるかどうか、つまり再コンパイルが必要かどうかを計算します。この関数は、ソースファイルのタイムスタンプや依存関係の変更などを考慮して、ビルドの必要性を判断します。

このコミットでは、computeStaleが複数回呼び出されていた箇所を整理し、一度の呼び出しに集約することで、ビルドプロセスの効率化とコードの可読性向上を図っています。

技術的詳細

このコミットは、src/cmd/go/test.goファイルに対して行われ、主に以下の二つの技術的な変更を含んでいます。

  1. 外部テストパッケージ (pxtest) への Root の設定:

    • 以前のコードでは、外部テストパッケージを表すpxtest構造体の生成時にRootフィールドが設定されていませんでした。
    • このコミットでは、pxtestの生成時に、元のパッケージpRootpxtest.Rootにコピーするように変更されました。
    • これにより、ビルドシステムは外部テストパッケージが現在のGoツリーの一部であることを正しく認識し、その依存関係が変更された場合に適切に再コンパイルを行うようになります。
  2. computeStale呼び出しの集約とビルドアクションの再配置:

    • 変更前は、ptest(内部テストパッケージ)とpxtest(外部テストパッケージ)のそれぞれに対してcomputeStaleが呼び出され、その直後にそれぞれのビルドアクションが定義されていました。
    • このコミットでは、ptestpxtestに対するcomputeStaleの呼び出しと、それに対応するビルドアクションの定義が、pmain(テスト実行用のメインパッケージ)に対するcomputeStaleの呼び出しの後に移動されました。
    • これにより、computeStaleの呼び出しが論理的に一箇所に集約され、コードの流れがより明確になりました。また、ビルドアクションの定義も、関連するパッケージの準備が整った後にまとめて行われるようになり、コードの整理に貢献しています。

これらの変更により、go testコマンドが外部テストパッケージの依存関係を正しく処理し、開発者が期待する通りに最新のコード変更がテストに反映されるようになりました。

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

src/cmd/go/test.go ファイルにおける変更点は以下の通りです。

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -258,6 +258,9 @@ func runTest(cmd *Command, args []string) {
 			for _, path := range p.TestImports {
 				deps[path] = true
 			}
+			for _, path := range p.XTestImports {
+				deps[path] = true
+			}
 		}
 
 		// translate C to runtime/cgo
@@ -454,12 +457,6 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 			m[k] = append(m[k], v...)\n 		}\n 		ptest.build.ImportPos = m\n-\t\tcomputeStale(ptest)\n-\t\ta := b.action(modeBuild, modeBuild, ptest)\n-\t\ta.objdir = testDir + string(filepath.Separator)\n-\t\ta.objpkg = ptestObj\n-\t\ta.target = ptestObj\n-\t\ta.link = false
 \t} else {\n \t\tptest = p\n \t}\n@@ -470,6 +467,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 			Name:        p.Name + "_test",\n 			ImportPath:  p.ImportPath + "_test",\n 			localPrefix: p.localPrefix,\n+\t\t\tRoot:        p.Root,\n \t\t\tDir:         p.Dir,\n \t\t\tGoFiles:     p.XTestGoFiles,\n \t\t\tImports:     p.XTestImports,\n@@ -481,11 +479,6 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 			fake:    true,\n 			Stale:   true,\n 		}\n-\t\tcomputeStale(pxtest)\n-\t\ta := b.action(modeBuild, modeBuild, pxtest)\n-\t\ta.objdir = testDir + string(filepath.Separator)\n-\t\ta.objpkg = buildToolchain.pkgpath(testDir, pxtest)\n-\t\ta.target = a.objpkg
 \t}\n \n \t// Action for building pkg.test.\n@@ -494,6 +487,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 		Dir:        testDir,\n 		GoFiles:    []string{"_testmain.go"},\n 		ImportPath: "testmain",\n+\t\tRoot:       p.Root,\n 		imports:    []*Package{ptest},\n 		build:      &build.Package{},\n 		fake:       true,\n@@ -516,6 +510,21 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 	pmain.imports = append(pmain.imports, ptesting, pregexp)\n 	computeStale(pmain)\n \n+\tif ptest != p {\n+\t\ta := b.action(modeBuild, modeBuild, ptest)\n+\t\ta.objdir = testDir + string(filepath.Separator)\n+\t\ta.objpkg = ptestObj\n+\t\ta.target = ptestObj\n+\t\ta.link = false\n+\t}\n+\n+\tif pxtest != nil {\n+\t\ta := b.action(modeBuild, modeBuild, pxtest)\n+\t\ta.objdir = testDir + string(filepath.Separator)\n+\t\ta.objpkg = buildToolchain.pkgpath(testDir, pxtest)\n+\t\ta.target = a.objpkg\n+\t}\n+\n \ta := b.action(modeBuild, modeBuild, pmain)\n \ta.objdir = testDir + string(filepath.Separator)\n \ta.objpkg = filepath.Join(testDir, "main.a")

コアとなるコードの解説

1. p.XTestImports の追加 (行 258-261)

@@ -258,6 +258,9 @@ func runTest(cmd *Command, args []string) {
 			for _, path := range p.TestImports {
 				deps[path] = true
 			}
+			for _, path := range p.XTestImports {
+				deps[path] = true
+			}
 		}

runTest関数内で、テストパッケージが依存するパッケージのリストdepsを構築する際に、p.XTestImports(外部テストパッケージがインポートするパッケージ)も追加されるようになりました。これにより、外部テストパッケージの依存関係もビルドシステムによって適切に追跡されるようになります。

2. ptest および pxtest のビルドアクションの移動と Root の設定 (行 454-483, 494-520)

@@ -454,12 +457,6 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 			m[k] = append(m[k], v...)\n 		}\n 		ptest.build.ImportPos = m\n-\t\tcomputeStale(ptest)\n-\t\ta := b.action(modeBuild, modeBuild, ptest)\n-\t\ta.objdir = testDir + string(filepath.Separator)\n-\t\ta.objpkg = ptestObj\n-\t\ta.target = ptestObj\n-\t\ta.link = false
 \t} else {\n \t\tptest = p\n \t}\n@@ -470,6 +467,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 			Name:        p.Name + "_test",\n 			ImportPath:  p.ImportPath + "_test",\n 			localPrefix: p.localPrefix,\n+\t\t\tRoot:        p.Root,\n \t\t\tDir:         p.Dir,\n \t\t\tGoFiles:     p.XTestGoFiles,\n \t\t\tImports:     p.XTestImports,\n@@ -481,11 +479,6 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 			fake:    true,\n 			Stale:   true,\n 		}\n-\t\tcomputeStale(pxtest)\n-\t\ta := b.action(modeBuild, modeBuild, pxtest)\n-\t\ta.objdir = testDir + string(filepath.Separator)\n-\t\ta.objpkg = buildToolchain.pkgpath(testDir, pxtest)\n-\t\ta.target = a.objpkg
 \t}\n \n \t// Action for building pkg.test.\n@@ -494,6 +487,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 		Dir:        testDir,\n 		GoFiles:    []string{"_testmain.go"},\n 		ImportPath: "testmain",\n+\t\tRoot:       p.Root,\n 		imports:    []*Package{ptest},\n 		build:      &build.Package{},\n 		fake:       true,\n@@ -516,6 +510,21 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,\
 	pmain.imports = append(pmain.imports, ptesting, pregexp)\n 	computeStale(pmain)\n \n+\tif ptest != p {\n+\t\ta := b.action(modeBuild, modeBuild, ptest)\n+\t\ta.objdir = testDir + string(filepath.Separator)\n+\t\ta.objpkg = ptestObj\n+\t\ta.target = ptestObj\n+\t\ta.link = false\n+\t}\n+\n+\tif pxtest != nil {\n+\t\ta := b.action(modeBuild, modeBuild, pxtest)\n+\t\ta.objdir = testDir + string(filepath.Separator)\n+\t\ta.objpkg = buildToolchain.pkgpath(testDir, pxtest)\n+\t\ta.target = a.objpkg\n+\t}\n+\n \ta := b.action(modeBuild, modeBuild, pmain)\n \ta.objdir = testDir + string(filepath.Separator)\n \ta.objpkg = filepath.Join(testDir, "main.a")
  • Root: p.Root, の追加 (行 473, 497):
    • 外部テストパッケージpxtestとテスト実行用のメインパッケージpmainの生成時に、元のパッケージpRootフィールドをコピーするように変更されました。これにより、これらのパッケージが現在のGoツリーの一部として正しく認識され、依存関係の再コンパイルが適切に行われるようになります。
  • computeStale呼び出しとビルドアクションの移動:
    • 以前はptestpxtestの定義直後にそれぞれcomputeStaleとビルドアクションの定義がありましたが、これらが削除されました。
    • 代わりに、pmainに対するcomputeStale(pmain)の呼び出しの後に、ptestpxtestに対するビルドアクションの定義がまとめて追加されました。
    • この変更により、computeStaleの呼び出しがpmainの準備ができた後の一箇所に集約され、コードの論理的な流れが改善されました。また、ビルドアクションの定義も一箇所にまとまり、コードの可読性と保守性が向上しています。

これらの変更は、Goのビルドシステムが外部テストパッケージの依存関係をより正確に管理し、開発者が期待する再コンパイルの挙動を実現するために不可欠でした。

関連リンク

参考にした情報源リンク

  • 上記のGitHubコミットページとGo CL (Change List) ページ
  • Go言語の公式ドキュメント(パッケージ、テスト、ビルドシステムに関する一般的な情報)
  • Go言語のソースコード(src/cmd/go/test.goの変更前後の比較)
  • Go言語のIssueトラッカー(Issue #3238の議論)