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

[インデックス 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 installgo 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 と同じ値を持つことになりますが、そのディレクトリは実際には存在しません。

変更後:

  1. p, err := ctxt.Import(".", dir, mode): まず、これまで通り ctxt.Import を呼び出します。
  2. 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 がディレクトリであるかどうかをチェックする内部ヘルパー関数です。
  3. err = fmt.Errorf("%q is not a directory", p.Dir): もし p.Dir が存在しないディレクトリであった場合、新しいエラーを生成し、それを err 変数に代入します。このエラーメッセージは、ユーザーに対して「指定されたパスがディレクトリではない」ことを明確に伝えます。
  4. 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)を返すことがありました。この場合、返された *PackageDir フィールドは、存在しないパスを指していました。

変更後、ImportDir は以下のロジックを追加しました。

  1. まず、p, err := ctxt.Import(".", dir, mode) を実行し、通常のパッケージインポートを試みます。
  2. 次に、if err == nil && !ctxt.isDir(p.Dir) という条件文で、ctxt.Import がエラーを返さなかった場合 (err == nil) かつ、返されたパッケージのディレクトリ p.Dir が実際に存在しない場合 (!ctxt.isDir(p.Dir)) をチェックします。
    • ctxt.isDir(path) は、os.Stat(path) を使ってパスの情報を取得し、それがディレクトリであるかどうかを判断する内部ヘルパー関数です。
  3. 上記の条件が真の場合、つまり、Import は成功したように見えたが、実際には存在しないディレクトリを指していた場合、err = fmt.Errorf("%q is not a directory", p.Dir) を使って新しいエラーオブジェクトを作成し、既存の err 変数に上書きします。このエラーメッセージは、ユーザーに対して問題の根源(指定されたパスがディレクトリではないこと)を明確に伝えます。
  4. 最後に、return p, err で、パッケージ情報と、必要に応じて更新されたエラー情報を返します。

この変更により、ImportDir は、存在しないディレクトリが指定された場合に、より具体的で役立つエラーメッセージを返すようになり、Goのビルドツールの堅牢性とユーザーフレンドリーさが向上しました。

src/pkg/go/build/build_test.go の変更

このファイルには、go/build パッケージのテストケースが含まれています。 新しいテスト関数 TestBogusDirectory が追加されました。

  1. const dir = "/foo/bar/baz/gopher": 存在しないことが確実な架空のディレクトリパスを定義します。
  2. _, err := ImportDir(dir, FindOnly): 定義した存在しないディレクトリパスを ImportDir に渡し、エラーをキャプチャします。FindOnly モードは、パッケージの検索のみを行い、実際のビルドは行わないことを示します。
  3. want := fmt.Sprintf("%q is not a directory", dir): 期待されるエラーメッセージを定義します。これは、build.go で追加された新しいエラーメッセージと一致するはずです。
  4. if err == nil || err.Error() != want: 以下の2つの条件をチェックします。
    • err == nil: エラーが返されなかった場合。これは予期しない挙動です。
    • err.Error() != want: 返されたエラーメッセージが期待されるメッセージと一致しない場合。
  5. t.Error("got error %q, want %q", err, want): 上記の条件のいずれかが真の場合、テストは失敗し、実際のエラーと期待されるエラーメッセージを出力します。

このテストの追加により、ImportDir が存在しないディレクトリに対して正しくエラーを返すという新しい挙動が検証され、将来のコード変更によってこの重要な機能が損なわれることを防ぎます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント: go/build パッケージ - https://pkg.go.dev/go/build
  • Go言語のエラーハンドリングに関する公式ブログ記事やドキュメント (一般的なGoのエラーハンドリングの理解のため)
  • os.Stat 関数のドキュメント (ファイルやディレクトリの情報を取得するため) - https://pkg.go.dev/os#Stat
  • fmt.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.