[インデックス 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:ImportDirshould 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.