[インデックス 19326] ファイルの概要
このコミットは、Goコマンドラインツール(cmd/go
)におけるエラーハンドリングの改善を目的としています。具体的には、テスト専用のパッケージ(Goのソースコードを含まず、テストファイルのみを含むパッケージ)をgo install
しようとした際に、より分かりやすいエラーメッセージを返すように変更が加えられました。これにより、ユーザーが意図しない操作を行った場合に、その原因を迅速に特定できるようになります。
コミット
commit f409681925407db843d4c9e314ca9edadd6a625b
Author: Russ Cox <rsc@golang.org>
Date: Mon May 12 11:04:28 2014 -0400
cmd/go: better error for install of 'test-only' package
Fixes #7915.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/96210044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f409681925407db843d4c9e314ca9edadd6a625b
元コミット内容
cmd/go
: テスト専用パッケージのインストールに対するより良いエラーメッセージ
Issue #7915 を修正。
変更の背景
Goのパッケージ管理システムでは、通常のGoソースファイル(.go
ファイル)とテストファイル(_test.go
ファイル)は区別されます。テストファイルは、パッケージのテストを実行するためにのみ使用され、通常はパッケージのビルドやインストール時には含まれません。
このコミットが修正しようとした問題は、Goのパッケージが通常のGoソースファイルを含まず、テストファイル(_test.go
)のみを含む場合、つまり「テスト専用パッケージ」である場合に発生しました。このようなパッケージに対してgo install
コマンドを実行すると、以前のバージョンでは曖昧なエラーメッセージが表示されるか、あるいは期待通りに動作しない可能性がありました。
具体的には、go install
は実行可能なバイナリやライブラリをビルドしてインストールすることを目的としていますが、テスト専用パッケージにはビルド対象となる通常のGoソースコードが存在しません。このため、go install
が失敗した場合に、ユーザーがその原因を正確に理解し、適切な対処を行うことが困難でした。
このコミットは、このような状況でgo install
が実行された際に、より明確で分かりやすいエラーメッセージ(例:「no buildable Go source files in ...」)を返すようにすることで、ユーザーエクスペリエンスを向上させることを目的としています。これにより、ユーザーはテスト専用パッケージをインストールしようとしていることに気づき、go test
などの適切なコマンドを使用するように促されます。
また、この変更はGoのコードレビューシステム(Gerrit)におけるCL 96210044として提出され、Russ Coxによって作成され、Brad Fitzpatrickによってレビューされました。コミット後、darwin-amd64-race-cheney
ビルダでビルドが失敗するという問題が発生しましたが、これは後続のコミット(CL 100410044)で修正されました。これは、変更が意図しない副作用を引き起こす可能性があり、継続的なテストと監視が重要であることを示しています。
前提知識の解説
- Goパッケージ: Go言語では、関連するソースファイルの集まりをパッケージと呼びます。パッケージは、
import
パスによって識別され、再利用可能なコードの単位となります。 - Goソースファイルとテストファイル:
- 通常のGoソースファイル(例:
main.go
,util.go
): パッケージの主要なロジックを実装します。 - テストファイル(例:
my_package_test.go
): ファイル名が_test.go
で終わるファイルで、パッケージのテストコードを含みます。これらのファイルは、go build
やgo install
の際には通常無視され、go test
コマンドによってのみコンパイル・実行されます。
- 通常のGoソースファイル(例:
go build
コマンド: Goソースファイルをコンパイルして実行可能なバイナリやパッケージを生成します。go install
コマンド:go build
と同様にコンパイルを行いますが、生成されたバイナリやパッケージをGOPATH/bin
またはGOBIN
にインストールします。build.NoGoError
: Goのビルドシステム内で定義されているエラータイプの一つで、ビルド対象のディレクトリにGoのソースファイルが見つからない場合に発生します。このコミットでは、このエラーをより適切なタイミングで返すように変更されています。cmd/go
: Go言語の公式ツールチェーンに含まれるコマンドラインツールで、Goプログラムのビルド、テスト、インストール、フォーマットなど、様々な操作を行います。
技術的詳細
このコミットの主要な変更は、src/cmd/go/build.go
ファイル内のbuilder.build
メソッドにあります。このメソッドは、Goパッケージをビルドする際の中心的なロジックを担っています。
変更前は、gofiles
(通常のGoソースファイルのリスト)の長さが0より大きい場合にのみ、Goコンパイラ(buildToolchain.gc
)を呼び出す条件分岐がありました。しかし、この条件では、テストファイルのみを含むパッケージの場合に、gofiles
が空になり、コンパイラが呼び出されないまま処理が進んでしまう可能性がありました。この場合、最終的にビルドが失敗しても、その原因が「Goソースファイルがないため」であることが明確に伝わらない可能性がありました。
このコミットでは、以下の2つの主要な変更が行われました。
-
build.NoGoError
の早期検出と返却:builder.build
メソッドの冒頭近くに、gofiles
の長さが0であるかをチェックする新しい行が追加されました。if len(gofiles) == 0 { return &build.NoGoError{a.p.Dir} }
この変更により、パッケージ内にビルド可能なGoソースファイルが一つも存在しない場合(つまり、テストファイルのみが存在する場合など)、Goコンパイラを呼び出す前に、明示的に
build.NoGoError
が返されるようになりました。これにより、エラーメッセージがより具体的になり、ユーザーは「Goソースファイルがない」という明確な理由を知ることができます。 -
buildToolchain.gc
呼び出しの条件分岐の削除: 以前はif len(gofiles) > 0
という条件で囲まれていたbuildToolchain.gc
の呼び出しが、この条件分岐から外されました。// 変更前: // if len(gofiles) > 0 { // ofile, out, err := buildToolchain.gc(b, a.p, a.objpkg, obj, inc, gofiles) // // ... // } // 変更後: ofile, out, err := buildToolchain.gc(b, a.p, a.objpkg, obj, inc, gofiles) // ...
この変更は、上記1の変更と組み合わさることで、よりロジックを簡潔にしています。
gofiles
が空の場合でもbuildToolchain.gc
が呼び出されることになりますが、その前にbuild.NoGoError
が返されるため、実際にはこのパスは実行されません。これにより、コードの可読性が向上し、冗長なチェックが排除されました。
また、この変更の動作を検証するために、src/cmd/go/test.bash
に新しいテストケースが追加されました。このテストケースは、testdata/testonly
という新しいディレクトリに配置された、テストファイル(p_test.go
)のみを含むパッケージに対してgo build
を実行し、期待されるエラーメッセージ(no buildable Go
)が返されることを確認します。
src/cmd/go/testdata/testonly/p_test.go
は、package p
という非常にシンプルな内容で、通常のGoソースファイルを含まない「テスト専用パッケージ」の最小限の例として機能します。
コアとなるコードの変更箇所
src/cmd/go/build.go
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -882,6 +882,10 @@ func (b *builder) build(a *action) (err error) {
gofiles = append(gofiles, outGo...)
}
+ if len(gofiles) == 0 {
+ return &build.NoGoError{a.p.Dir}
+ }
+
// If we're doing coverage, preprocess the .go files and put them in the work directory
if a.p.coverMode != "" {
for i, file := range gofiles {
@@ -915,21 +919,19 @@ func (b *builder) build(a *action) (err error) {
inc := b.includeArgs("-I", a.deps)
// Compile Go.
- if len(gofiles) > 0 {
- ofile, out, err := buildToolchain.gc(b, a.p, a.objpkg, obj, inc, gofiles)
- if len(out) > 0 {
- b.showOutput(a.p.Dir, a.p.ImportPath, b.processOutput(out))
- if err != nil {
- return errPrintedOutput
- }
- }
+ ofile, out, err := buildToolchain.gc(b, a.p, a.objpkg, obj, inc, gofiles)
+ if len(out) > 0 {
+ b.showOutput(a.p.Dir, a.p.ImportPath, b.processOutput(out))
if err != nil {
- return err
- }
- if ofile != a.objpkg {
- objects = append(objects, ofile)
+ return errPrintedOutput
}
+ }
+ if err != nil {
+ return err
+ }
+ if ofile != a.objpkg {
+ objects = append(objects, ofile)
+ }
// Copy .h files named for goos or goarch or goos_goarch
// to names using GOOS and GOARCH.
src/cmd/go/test.bash
--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -759,6 +759,17 @@ fi
rm -rf $d
unset GOPATH
+TEST 'go build in test-only directory fails with a good error'
+if ./testgo build ./testdata/testonly 2>testdata/err.out; then
+ echo "go build ./testdata/testonly succeeded, should have failed"
+ ok=false
+elif ! grep 'no buildable Go' testdata/err.out >/dev/null; then
+ echo "go build ./testdata/testonly produced unexpected error:"
+ cat testdata/err.out
+ ok=false
+fi
+rm -f testdata/err.out
+
# clean up
if $started; then stop; fi
rm -rf testdata/bin testdata/bin1
src/cmd/go/testdata/testonly/p_test.go
--- /dev/null
+++ b/src/cmd/go/testdata/testonly/p_test.go
@@ -0,0 +1 @@
+package p
コアとなるコードの解説
src/cmd/go/build.go
の変更
-
if len(gofiles) == 0 { return &build.NoGoError{a.p.Dir} }
: この行が追加されたことで、builder.build
関数は、パッケージ内にコンパイル対象となるGoソースファイル(gofiles
)が一つも存在しない場合、すぐにbuild.NoGoError
を返します。a.p.Dir
は、現在処理しているパッケージのディレクトリパスを示しており、エラーメッセージに含めることで、どのディレクトリに問題があるのかをユーザーに明確に伝えます。これにより、例えば_test.go
ファイルしか含まないディレクトリに対してgo build
やgo install
を実行した場合に、より適切なエラーメッセージが表示されるようになります。 -
buildToolchain.gc
呼び出しの条件分岐の削除: 以前はif len(gofiles) > 0
という条件で囲まれていたbuildToolchain.gc
(Goコンパイラを呼び出す部分)が、条件分岐なしで直接呼び出されるようになりました。これは、上記のlen(gofiles) == 0
のチェックが追加されたため、gofiles
が空である場合には既にエラーが返されているため、この条件分岐が不要になったためです。これにより、コードがよりシンプルになり、冗長なチェックが排除されました。
src/cmd/go/test.bash
の変更
- 新しいテストケースの追加:
このシェルスクリプトには、新しい
TEST
ブロックが追加されました。これは、go build ./testdata/testonly
コマンドを実行し、その出力(標準エラー出力)をtestdata/err.out
にリダイレクトします。if ./testgo build ./testdata/testonly 2>testdata/err.out; then ...
:testgo
はGoテストフレームワークの一部で、Goコマンドのテスト版を実行します。このコマンドが成功した場合(つまり、エラーを返さなかった場合)、それは予期せぬ動作であるため、テストは失敗とマークされます。elif ! grep 'no buildable Go' testdata/err.out >/dev/null; then ...
:testgo
コマンドがエラーを返した場合、そのエラー出力(testdata/err.out
)に「no buildable Go
」という文字列が含まれているかをgrep
で確認します。この文字列が含まれていない場合、それは期待されるエラーメッセージではないため、テストは失敗とマークされます。rm -f testdata/err.out
:テスト後に一時ファイルtestdata/err.out
を削除します。 このテストケースは、build.go
の変更が意図通りに機能し、「テスト専用パッケージ」に対してgo build
が実行された際に、適切なエラーメッセージが返されることを保証します。
src/cmd/go/testdata/testonly/p_test.go
の追加
- テスト専用パッケージの定義:
このファイルは、
package p
という非常にシンプルな内容で構成されています。ファイル名が_test.go
で終わるため、Goツールチェーンによってテストファイルとして認識されます。このディレクトリには他の.go
ファイルが存在しないため、このディレクトリは「テスト専用パッケージ」として機能し、build.go
の変更をテストするための具体的なシナリオを提供します。
これらの変更により、Goツールチェーンは、テスト専用パッケージのビルドやインストールに関するユーザーの誤解を減らし、より明確なフィードバックを提供するようになりました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goコマンドのドキュメント: https://golang.org/cmd/go/
- Goのパッケージとモジュールに関するドキュメント: https://go.dev/doc/modules/
参考にした情報源リンク
- Go Code Review 96210044: https://golang.org/cl/96210044
- Go Code Review 100410044 (関連する修正): https://golang.org/cl/100410044 (これはこのコミットによって引き起こされた問題の修正コミットであり、直接このコミットの一部ではありませんが、背景情報として重要です。)
- GitHub Goリポジトリ: https://github.com/golang/go
- Go issue tracker (一般的な情報源として): https://github.com/golang/go/issues
build.NoGoError
の定義 (Goソースコード内): https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/go/build/build.go;l=100 (Goのバージョンによって行番号は異なる可能性があります)cmd/go
のソースコード (一般的な情報源として): https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/cmd/go/