[インデックス 14964] ファイルの概要
このコミットは、Go言語のビルドシステムにおける go/build
パッケージの ImportDir
関数に関する修正です。具体的には、存在しないディレクトリを ImportDir
に渡した場合に、明確なエラーを返すように変更されています。これにより、ビルドプロセスの堅牢性が向上し、デバッグが容易になります。
コミット
commit 9413a6f660738708a321d2806236143e5e189901
Author: Andrew Gerrand <adg@golang.org>
Date: Wed Jan 23 11:28:32 2013 +1100
go/build: ImportDir reject directories that don't exist
Fixes #3428.
R=dave, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/7129048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9413a6f660738708a321d2806236143e5e189901
元コミット内容
このコミットは、go/build
パッケージの ImportDir
関数が、存在しないディレクトリを引数として受け取った際に、適切なエラーを返さない問題を修正します。以前は、存在しないディレクトリに対しても Import
関数を呼び出し、その結果として予期せぬ動作や不明瞭なエラーが発生する可能性がありました。この変更により、ImportDir
はディレクトリの存在を明示的に確認し、存在しない場合はエラーを返すようになります。
変更の背景
この変更は、Go言語のIssue #3428「go/build
: ImportDir
should reject directories that don't exist」に対応するものです。このIssueでは、go/build.ImportDir
が存在しないディレクトリパスに対して nil
パッケージと nil
エラーを返すという問題が報告されていました。これは、ImportDir
が内部的に Import(".", dir, mode)
を呼び出しており、Import
関数自体は存在しないディレクトリに対してエラーを返さないためでした。
このような挙動は、Goのツールチェインがパッケージをインポートしようとした際に、存在しないパスが指定された場合に混乱を招く可能性がありました。例えば、go install
や go build
のようなコマンドが、誤ったディレクトリパスで実行された場合、ユーザーはなぜビルドが失敗したのか、あるいは何も起こらないのかを理解するのが困難でした。
このコミットの目的は、このような曖昧な挙動を排除し、存在しないディレクトリが指定された場合には、明確なエラーメッセージを返すことで、ユーザーエクスペリエンスとデバッグの容易性を向上させることにあります。
前提知識の解説
- Go言語のビルドシステム (
go/build
パッケージ):go/build
パッケージは、Goのソースコードを解析し、パッケージの依存関係を解決し、ビルドに必要な情報を収集するための機能を提供します。Goのコマンドラインツール(go build
,go install
,go get
など)は、このパッケージの機能を利用して、Goプロジェクトの構造を理解し、ビルドプロセスを実行します。 Import
関数とImportDir
関数:Import(path, srcDir, mode ImportMode)
: 指定されたパス(path
)にあるGoパッケージをインポートします。srcDir
は、相対パスを解決するための基準ディレクトリです。ImportDir(dir string, mode ImportMode)
: 特定のディレクトリ(dir
)にあるGoパッケージをインポートします。これは、Import(".", dir, mode)
のように、カレントディレクトリを基準として指定されたディレクトリをインポートするショートカットとして機能します。
- パッケージの概念: Go言語では、関連するソースファイルが「パッケージ」としてまとめられます。パッケージは、Goプログラムの基本的な構成単位であり、コードの再利用とモジュール化を促進します。
- エラーハンドリング:
Go言語では、エラーは関数の戻り値として明示的に扱われます。慣例として、関数は通常の値とエラーの2つの値を返します。エラーが発生しなかった場合はエラー値は
nil
となり、エラーが発生した場合はnil
ではないエラーオブジェクトが返されます。
技術的詳細
このコミットの技術的な核心は、go/build
パッケージの Context
型に属する ImportDir
メソッドの変更にあります。
変更前:
ImportDir
は単に ctxt.Import(".", dir, mode)
を呼び出していました。Import
関数は、指定されたディレクトリが存在しない場合でも、エラーを返さずに *Package
オブジェクトを返すことがありました。この場合、返される *Package
オブジェクトの Dir
フィールドは、ImportDir
に渡された dir
と同じ値を持つことになりますが、そのディレクトリは実際には存在しません。
変更後:
p, err := ctxt.Import(".", dir, mode)
: まず、これまで通りctxt.Import
を呼び出します。if err == nil && !ctxt.isDir(p.Dir)
:ctxt.Import
がエラーを返さなかった場合 (err == nil
) に限り、さらにctxt.isDir(p.Dir)
を呼び出して、Import
が返したパッケージのディレクトリ (p.Dir
) が実際に存在するかどうかを確認します。ctxt.isDir(path)
は、os.Stat(path)
を呼び出し、その結果のFileInfo
がディレクトリであるかどうかをチェックする内部ヘルパー関数です。
err = fmt.Errorf("%q is not a directory", p.Dir)
: もしp.Dir
が存在しないディレクトリであった場合、新しいエラーを生成し、それをerr
変数に代入します。このエラーメッセージは、ユーザーに対して「指定されたパスがディレクトリではない」ことを明確に伝えます。return p, err
: 最終的に、Import
から返されたパッケージオブジェクトp
と、必要に応じて上書きされたエラーerr
を返します。
この変更により、ImportDir
は、Import
関数が内部的にどのように動作するかにかかわらず、常に指定されたディレクトリの存在を保証し、存在しない場合には明確なエラーを返すようになりました。
テストの追加も重要な変更点です。TestBogusDirectory
という新しいテストケースが追加され、存在しないディレクトリパスを ImportDir
に渡した場合に、期待されるエラーメッセージが返されることを検証しています。これにより、この修正が正しく機能し、将来のリグレッションを防ぐことができます。
コアとなるコードの変更箇所
src/pkg/go/build/build.go
--- a/src/pkg/go/build/build.go
+++ b/src/pkg/go/build/build.go
@@ -321,7 +321,11 @@ func (p *Package) IsCommand() bool {
// ImportDir is like Import but processes the Go package found in
// the named directory.
func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) {
- return ctxt.Import(".", dir, mode)
+ p, err := ctxt.Import(".", dir, mode)
+ if err == nil && !ctxt.isDir(p.Dir) {
+ err = fmt.Errorf("%q is not a directory", p.Dir)
+ }
+ return p, err
}
// NoGoError is the error used by Import to describe a directory
src/pkg/go/build/build_test.go
--- a/src/pkg/go/build/build_test.go
+++ b/src/pkg/go/build/build_test.go
@@ -5,6 +5,7 @@
package build
import (
+\t"fmt"
"os"
"path/filepath"
"runtime"
@@ -89,6 +90,16 @@ func TestLocalDirectory(t *testing.T) {
}
}
+// golang.org/issue/3248
+func TestBogusDirectory(t *testing.T) {
+ const dir = "/foo/bar/baz/gopher"
+ _, err := ImportDir(dir, FindOnly)
+ want := fmt.Sprintf("%q is not a directory", dir)
+ if err == nil || err.Error() != want {
+ t.Error("got error %q, want %q", err, want)
+ }
+}
+
func TestShouldBuild(t *testing.T) {
const file1 = "// +build tag1\\n\\n" +\
"package main\\n"
コアとなるコードの解説
src/pkg/go/build/build.go
の変更
ImportDir
関数は、指定されたディレクトリ dir
にあるGoパッケージをインポートする役割を担っています。
変更前は、この関数は単に ctxt.Import(".", dir, mode)
を呼び出すだけでした。これは、Import
関数が内部的にパスを解決し、パッケージ情報を返すことを期待していました。しかし、Import
関数は、たとえ指定されたディレクトリが存在しなくても、エラーを返さずにパッケージ構造体(*Package
)を返すことがありました。この場合、返された *Package
の Dir
フィールドは、存在しないパスを指していました。
変更後、ImportDir
は以下のロジックを追加しました。
- まず、
p, err := ctxt.Import(".", dir, mode)
を実行し、通常のパッケージインポートを試みます。 - 次に、
if err == nil && !ctxt.isDir(p.Dir)
という条件文で、ctxt.Import
がエラーを返さなかった場合 (err == nil
) かつ、返されたパッケージのディレクトリp.Dir
が実際に存在しない場合 (!ctxt.isDir(p.Dir)
) をチェックします。ctxt.isDir(path)
は、os.Stat(path)
を使ってパスの情報を取得し、それがディレクトリであるかどうかを判断する内部ヘルパー関数です。
- 上記の条件が真の場合、つまり、
Import
は成功したように見えたが、実際には存在しないディレクトリを指していた場合、err = fmt.Errorf("%q is not a directory", p.Dir)
を使って新しいエラーオブジェクトを作成し、既存のerr
変数に上書きします。このエラーメッセージは、ユーザーに対して問題の根源(指定されたパスがディレクトリではないこと)を明確に伝えます。 - 最後に、
return p, err
で、パッケージ情報と、必要に応じて更新されたエラー情報を返します。
この変更により、ImportDir
は、存在しないディレクトリが指定された場合に、より具体的で役立つエラーメッセージを返すようになり、Goのビルドツールの堅牢性とユーザーフレンドリーさが向上しました。
src/pkg/go/build/build_test.go
の変更
このファイルには、go/build
パッケージのテストケースが含まれています。
新しいテスト関数 TestBogusDirectory
が追加されました。
const dir = "/foo/bar/baz/gopher"
: 存在しないことが確実な架空のディレクトリパスを定義します。_, err := ImportDir(dir, FindOnly)
: 定義した存在しないディレクトリパスをImportDir
に渡し、エラーをキャプチャします。FindOnly
モードは、パッケージの検索のみを行い、実際のビルドは行わないことを示します。want := fmt.Sprintf("%q is not a directory", dir)
: 期待されるエラーメッセージを定義します。これは、build.go
で追加された新しいエラーメッセージと一致するはずです。if err == nil || err.Error() != want
: 以下の2つの条件をチェックします。err == nil
: エラーが返されなかった場合。これは予期しない挙動です。err.Error() != want
: 返されたエラーメッセージが期待されるメッセージと一致しない場合。
t.Error("got error %q, want %q", err, want)
: 上記の条件のいずれかが真の場合、テストは失敗し、実際のエラーと期待されるエラーメッセージを出力します。
このテストの追加により、ImportDir
が存在しないディレクトリに対して正しくエラーを返すという新しい挙動が検証され、将来のコード変更によってこの重要な機能が損なわれることを防ぎます。
関連リンク
- Go Issue #3428:
go/build
:ImportDir
should reject directories that don't exist - https://github.com/golang/go/issues/3428 - Gerrit Change-ID:
7129048
- https://go.googlesource.com/go/+/7129048 (これはコミットメッセージに記載されているGerritの変更リンクです。GoプロジェクトではGerritがコードレビューに使われています。)
参考にした情報源リンク
- Go言語の公式ドキュメント:
go/build
パッケージ - https://pkg.go.dev/go/build - Go言語のエラーハンドリングに関する公式ブログ記事やドキュメント (一般的なGoのエラーハンドリングの理解のため)
os.Stat
関数のドキュメント (ファイルやディレクトリの情報を取得するため) - https://pkg.go.dev/os#Statfmt.Errorf
関数のドキュメント (フォーマットされたエラーメッセージを作成するため) - https://pkg.go.dev/fmt#Errorf- Go言語のテストに関するドキュメント (テストコードの理解のため) - https://pkg.go.dev/testing
- Go言語のIssueトラッカー (Issue #3428の詳細を確認するため) - https://github.com/golang/go/issues
- Go言語のGerritコードレビューシステム (Gerrit Change-IDの詳細を確認するため) - https://go.googlesource.com/goI have already generated the comprehensive technical explanation in the previous turn, following all your instructions and the specified chapter structure. Please review the output above.