[インデックス 19450] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go
におけるエラーハンドリングの改善に関するものです。具体的には、インポートパスに "cmd/something"
のような文字列が含まれる場合に、go get
コマンドがエラーを適切に報告しない問題を修正しています。
コミット
commit eeb87c3660932cb0dcc6db2e3784a66b6d06a82a
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date: Tue May 27 23:58:03 2014 -0400
cmd/go: do not miss an error if import path contains "cmd/something"
Fixes #7638
LGTM=rsc
R=rsc, adg, robert.hencke, bradfitz
CC=golang-codereviews
https://golang.org/cl/89280043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/eeb87c3660932cb0dcc6db2e3784a66b6d06a82a
元コミット内容
cmd/go: do not miss an error if import path contains "cmd/something"
このコミットは、インポートパスに "cmd/something"
が含まれる場合にエラーを見逃さないようにするものです。
変更の背景
Go言語では、cmd/...
で始まるインポートパスはGoの標準コマンドのために予約されています。これは、ユーザーが誤って標準ライブラリの内部パッケージと同じ名前のパッケージを作成し、それが衝突するのを防ぐための設計上の制約です。
しかし、このコミット以前の cmd/go
ツールでは、go get
コマンドがパッケージをダウンロードする際に、インポートパスがこの予約されたパターンに一致する場合でも、エラーを適切に伝播させない問題がありました。具体的には、loadPackage
関数が PackageError
を返しても、download
関数がそのエラーを「ソフトエラー」として扱い、無視してしまうことがありました。これにより、ユーザーは無効なインポートパスを使用しているにもかかわらず、エラーメッセージを受け取ることができず、デバッグが困難になる可能性がありました。
この問題は、GoのIssue #7638として報告され、このコミットによって修正されました。
前提知識の解説
cmd/go
ツール: Go言語のビルド、テスト、パッケージ管理などを行うための主要なコマンドラインツールです。go get
はこのツールの一部で、リモートリポジトリからパッケージをダウンロードしてインストールするために使用されます。- インポートパス: Goのソースコード内でパッケージを識別するために使用される文字列です。例えば、
"fmt"
や"github.com/user/repo/package"
などがあります。 cmd/...
予約語: Go言語の設計において、cmd/
で始まるインポートパスはGoの標準コマンド(例:cmd/go
,cmd/vet
など)のために予約されています。これは、ユーザーが独自のパッケージでこの命名規則を使用することを防ぎ、標準コマンドとの名前の衝突を避けるためのものです。PackageError
:cmd/go
ツール内部で使用されるエラー構造体です。パッケージのロードや解析中に発生したエラーに関する情報(エラーメッセージ、スタックトレースなど)を保持します。- ソフトエラーとハードエラー: このコミットで導入された概念です。
- ソフトエラー (soft error):
cmd/go
ツールが処理を続行できるような、比較的軽微なエラー。場合によっては無視されることがあります。 - ハードエラー (hard error):
cmd/go
ツールが処理を続行できないような、致命的なエラー。通常、即座にエラーとして報告され、処理が中断されます。このコミットでは、cmd/...
のインポートパスに関するエラーをハードエラーとして扱うように変更しています。
- ソフトエラー (soft error):
技術的詳細
このコミットの主要な変更点は、PackageError
構造体に hard
フィールドを追加し、特定のタイプのエラー(特に予約された cmd/...
インポートパスの使用に関するエラー)を「ハードエラー」として明示的にマークするようにしたことです。そして、download
関数が loadPackage
から返された PackageError
をチェックし、それが hard
エラーである場合には、そのエラーを即座に報告して処理を中断するように変更されました。
具体的な変更は以下の2つのファイルで行われています。
-
src/cmd/go/pkg.go
:PackageError
構造体にbool
型のhard
フィールドが追加されました。このフィールドは、エラーがソフトエラーかハードエラーかを示します。loadPackage
関数内で、インポートパスがcmd/...
のパターンに一致する場合に生成されるPackageError
オブジェクトのhard
フィールドがtrue
に設定されるようになりました。これにより、この種のエラーがハードエラーとして識別されます。
-
src/cmd/go/get.go
:download
関数内で、loadPackage
から返されたPackage
オブジェクトのError
フィールド(PackageError
型)がチェックされます。- もし
p.Error
がnil
でなく、かつp.Error.hard
がtrue
であれば、errorf
関数を使ってエラーメッセージを標準エラー出力に表示し、return
することで関数の実行を即座に終了します。これにより、ハードエラーが適切に処理され、無視されることがなくなります。
この変更により、cmd/...
のような予約されたインポートパスを使用しようとした際に、go get
コマンドは明確なエラーメッセージをユーザーに提供し、不適切な動作を防ぐことができるようになりました。
コアとなるコードの変更箇所
src/cmd/go/get.go
--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -140,6 +140,10 @@ var downloadRootCache = map[string]bool{}
// for the package named by the argument.
func download(arg string, stk *importStack, getTestDeps bool) {
p := loadPackage(arg, stk)
+ if p.Error != nil && p.Error.hard {
+ errorf("%s", p.Error)
+ return
+ }
// There's nothing to do if this is a package in the standard library.
if p.Standard {
src/cmd/go/pkg.go
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -139,6 +139,7 @@ type PackageError struct {
Pos string // position of error
Err string // the error itself
isImportCycle bool // the error is an import cycle
+ hard bool // whether the error is soft or hard; soft errors are ignored in some places
}
func (p *PackageError) Error() string {
@@ -715,6 +716,7 @@ func loadPackage(arg string, stk *importStack) *Package {
\t\t\t\tError: &PackageError{
\t\t\t\t\tImportStack: stk.copy(),
\t\t\t\t\tErr: fmt.Sprintf("invalid import path: cmd/... is reserved for Go commands"),
+ \t\t\t\thard: true,
\t\t\t\t},\n \t\t\t}\n \t\t\treturn p\n
コアとなるコードの解説
src/cmd/go/get.go
の変更
download
関数は、指定されたインポートパスのパッケージをダウンロードする役割を担っています。この関数はまず loadPackage
を呼び出してパッケージ情報をロードします。
追加されたコードブロック:
if p.Error != nil && p.Error.hard {
errorf("%s", p.Error)
return
}
この if
文は、loadPackage
がエラーを返した場合に、そのエラーが hard
エラーであるかどうかをチェックします。
p.Error != nil
:loadPackage
が何らかのエラーを返したことを確認します。p.Error.hard
: 返されたエラーがPackageError
型であり、そのhard
フィールドがtrue
であることを確認します。 もし両方の条件が真であれば、errorf
関数(Goツールの内部エラー報告関数)を使ってエラーメッセージを表示し、return
で関数の実行を終了します。これにより、cmd/...
のような予約されたパスに関するエラーが即座にユーザーに報告され、go get
の処理が中断されるようになります。
src/cmd/go/pkg.go
の変更
-
PackageError
構造体へのhard
フィールドの追加:type PackageError struct { // ... 既存のフィールド ... hard bool // whether the error is soft or hard; soft errors are ignored in some places }
hard
フィールドはbool
型で、このエラーが「ハード」なエラー(無視すべきではないエラー)であるかどうかを示します。コメントにもあるように、ソフトエラーは一部の場所で無視される可能性があります。 -
loadPackage
関数内でのhard
フィールドの設定:// ... Error: &PackageError{ ImportStack: stk.copy(), Err: fmt.Sprintf("invalid import path: cmd/... is reserved for Go commands"), hard: true, // ここでhardをtrueに設定 }, // ...
loadPackage
関数は、インポートパスがcmd/...
の予約されたパターンに一致する場合に、invalid import path: cmd/... is reserved for Go commands
というエラーメッセージを含むPackageError
を生成します。このコミットでは、この特定のPackageError
のhard
フィールドをtrue
に明示的に設定しています。これにより、このエラーがdownload
関数で適切に捕捉され、処理が中断されるようになります。
これらの変更により、Goツールは予約されたインポートパスの誤用に対してより堅牢なエラーハンドリングを提供するようになりました。
関連リンク
- Go Issue #7638 (このコミットが修正した問題): https://github.com/golang/go/issues/7638 (※注: 2014年当時のGoのIssueトラッカーのリンクであり、現在のGitHubリポジトリのIssue番号とは直接対応しない可能性がありますが、コミットメッセージに記載されているため関連情報として記載します。)
- Go Gerrit Change-ID 89280043 (このコミットのGerritレビューページ): https://golang.org/cl/89280043
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/go/get.go
およびsrc/cmd/go/pkg.go
の該当コミット時点のバージョン) - Go言語の公式ドキュメント (Goコマンドの動作に関する一般的な情報)
- Go言語のIssueトラッカー (Issue #7638に関する情報)
- Go Gerrit Code Review (Change-ID 89280043に関する情報)