[インデックス 17535] ファイルの概要
このコミットは、Go言語のコマンドラインツール(cmd/go)において、GOPATH内でディレクトリがシャドウ(隠蔽)されている場合に、より分かりやすいエラーメッセージを提供する改善を目的としています。具体的には、go installコマンドが、インストール場所を見つけられない際に、その原因が別のディレクトリによって隠蔽されていることにある場合、その隠蔽しているディレクトリのパスをエラーメッセージに含めるように変更されています。
コミット
commit d5fbad0de86169d996fcb136cb6c1e57a3649c43
Author: Russ Cox <rsc@golang.org>
Date: Tue Sep 10 13:17:21 2013 -0400
cmd/go: better error for shadowed directories in GOPATH
Fixes #5774.
R=golang-dev, adg, r, bradfitz
CC=golang-dev
https://golang.org/cl/9164043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d5fbad0de861669d996fcb136cb6c1e57a3649c43
元コミット内容
cmd/go: better error for shadowed directories in GOPATH
Fixes #5774.
R=golang-dev, adg, r, bradfitz
CC=golang-dev
https://golang.org/cl/9164043
変更の背景
Go言語のビルドシステムでは、GOPATH環境変数によってGoのソースコード、パッケージ、実行可能ファイルが配置される場所が定義されます。GOPATHはコロン(Windowsではセミコロン)で区切られた複数のディレクトリパスを持つことができ、Goツールはこれらのパスを左から順に検索してパッケージを解決します。
この検索順序の特性上、GOPATH内に同じインポートパスを持つ複数のパッケージが存在する場合、先にリストされているディレクトリ内のパッケージが優先され、後続のディレクトリ内のパッケージは「シャドウされる(隠蔽される)」ことになります。
以前のgo installコマンドでは、シャドウされたディレクトリ内のパッケージをインストールしようとした際に、単に「インストール場所が見つからない」という一般的なエラーメッセージが表示されていました。このメッセージだけでは、ユーザーはなぜインストールできないのか、どのディレクトリが問題を引き起こしているのかを特定するのが困難でした。
このコミットは、Issue #5774("cmd/go: better error for shadowed directories in GOPATH")を解決するために作成されました。ユーザーがシャドウされたパッケージを誤って参照している場合に、より具体的で役立つエラーメッセージを提供することで、デバッグの労力を軽減することが目的です。
前提知識の解説
GOPATH
GOPATHは、Go言語のワークスペースのルートディレクトリを指定する環境変数です。Go 1.11でGo Modulesが導入されるまでは、Goプロジェクトの依存関係管理とビルドにおいて中心的な役割を担っていました。現在でも、Go Modulesを使用しないレガシーなプロジェクトや、特定の開発環境では引き続き使用されます。
GOPATHは通常、bin、pkg、srcの3つのサブディレクトリを持ちます。
src: Goのソースコードが配置されます。各パッケージはsrc以下のディレクトリに、そのインポートパスに対応する形で格納されます(例:github.com/user/repoは$GOPATH/src/github.com/user/repoに配置)。pkg: コンパイルされたパッケージオブジェクト(.aファイル)が配置されます。bin: コンパイルされた実行可能ファイルが配置されます。
GOPATHに複数のパスが設定されている場合(例: export GOPATH=/home/user/go1:/home/user/go2)、Goツールはこれらのパスを左から右へと順に検索します。
パッケージの解決とシャドウイング
Goツールが特定のインポートパス(例: github.com/user/repo/mypackage)を持つパッケージを探す際、GOPATHの各エントリのsrcディレクトリ内を検索します。最初に見つかったパッケージが使用され、それ以降のGOPATHエントリにある同じインポートパスのパッケージは無視されます。これが「シャドウイング」です。
例えば、GOPATH=/path/to/root1:/path/to/root2という設定で、
/path/to/root1/src/foo/foo.go/path/to/root2/src/foo/foo.goという2つのfooパッケージが存在する場合、Goツールは/path/to/root1/src/foo/foo.goを優先的に使用し、/path/to/root2/src/foo/foo.goはシャドウされます。
go installコマンド
go installコマンドは、指定されたパッケージをコンパイルし、その結果生成されるパッケージオブジェクト(.aファイル)を$GOPATH/pkgに、実行可能ファイル(コマンドの場合)を$GOPATH/binにインストールします。このコマンドは、パッケージの依存関係を解決し、必要に応じて再ビルドも行います。
技術的詳細
このコミットの主要な変更点は、go/buildパッケージのPackage構造体にConflictDirフィールドを追加し、パッケージ解決時にシャドウが発生した場合にその情報を格納するようにしたことです。そして、cmd/goのgo installコマンドが、このConflictDir情報を利用して、より詳細なエラーメッセージを出力するように修正されています。
go/build.Package構造体へのConflictDirの追加
src/pkg/go/build/build.go内のPackage構造体に、以下のフィールドが追加されました。
type Package struct {
// ... 既存のフィールド ...
ConflictDir string // this directory shadows Dir in $GOPATH
}
ConflictDirは、現在のPackage.DirがGOPATH内の別のディレクトリによってシャドウされている場合に、そのシャドウしているディレクトリのパスを保持します。
パッケージ解決ロジックの変更
src/pkg/go/build/build.goの(*Context).Importメソッド内で、パッケージを検索する際にシャドウが発生した場合にConflictDirを設定するロジックが追加されました。
具体的には、GOPATHの各エントリを順に検索し、目的のパッケージが見つかった場合、それより前のGOPATHエントリ(またはGOROOT)に同じインポートパスのディレクトリが存在するかどうかを確認します。もし存在すれば、そのディレクトリのパスをp.ConflictDirに設定します。
cmd/go/pkg.Package構造体へのConflictDirの追加とコピー
src/cmd/go/pkg.go内のPackage構造体にも、同様にConflictDirフィールドが追加されました。これは、go/buildパッケージのPackage構造体と情報を同期させるためです。
type Package struct {
// ... 既存のフィールド ...
ConflictDir string `json:",omitempty"` // Dir is hidden by this other directory
}
また、(*Package).copyBuildメソッドが更新され、go/build.Packageからcmd/go/pkg.Packageへ情報をコピーする際に、ConflictDirもコピーされるようになりました。
go installのエラーメッセージの改善
src/cmd/go/build.go内のrunInstall関数が修正され、p.Target == ""(インストール場所が見つからない)かつp.ConflictDir != ""(シャドウされていることが原因)の場合に、新しいエラーメッセージを出力するように変更されました。
変更前:
go install: no install location for directory %s outside GOPATH
変更後:
go install: no install location for %s: hidden by %s
この変更により、ユーザーは「どのディレクトリが(%s)どのディレクトリ(%s)によって隠蔽されているためインストールできないのか」という具体的な情報を得られるようになります。
テストケースの追加
src/cmd/go/test.bashに、シャドウイングのロジックと新しいエラーメッセージを検証するためのテストケースが追加されました。
GOPATHをroot1:root2として設定し、root1とroot2にそれぞれfooパッケージを配置。root1のmathパッケージが標準ライブラリのmathによってシャドウされることを確認。root2のfooパッケージがroot1のfooによってシャドウされることを確認。- シャドウされた
root2のfooパッケージをgo installしようとした際に、期待されるエラーメッセージ(シャドウしているディレクトリのパスを含む)が出力されることを確認。
これらのテストケースは、変更が正しく機能し、意図した通りにシャドウイングを検出し、適切なエラーメッセージを生成することを確認します。
コアとなるコードの変更箇所
src/cmd/go/build.go
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -311,7 +311,11 @@ func runInstall(cmd *Command, args []string) {
for _, p := range pkgs {
if p.Target == "" && (!p.Standard || p.ImportPath != "unsafe") {
- errorf("go install: no install location for directory %s outside GOPATH", p.Dir)
+ if p.ConflictDir != "" {
+ errorf("go install: no install location for %s: hidden by %s", p.Dir, p.ConflictDir)
+ } else {
+ errorf("go install: no install location for directory %s outside GOPATH", p.Dir)
+ }
}
}
exitIfErrors()
src/cmd/go/pkg.go
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -25,15 +25,16 @@ type Package struct {
// Note: These fields are part of the go command's public API.
// See list.go. It is okay to add fields, but not to change or
// remove existing ones. Keep in sync with list.go
- Dir string `json:",omitempty"` // directory containing package sources
- ImportPath string `json:",omitempty"` // import path of package in dir
- Name string `json:",omitempty"` // package name
- Doc string `json:",omitempty"` // package documentation string
- Target string `json:",omitempty"` // install path
- Goroot bool `json:",omitempty"` // is this package found in the Go root?
- Standard bool `json:",omitempty"` // is this package part of the standard Go library?
- Stale bool `json:",omitempty"` // would 'go install' do anything for this package?
- Root string `json:",omitempty"` // Go root or Go path dir containing this package
+ Dir string `json:",omitempty"` // directory containing package sources
+ ImportPath string `json:",omitempty"` // import path of package in dir
+ Name string `json:",omitempty"` // package name
+ Doc string `json:",omitempty"` // package documentation string
+ Target string `json:",omitempty"` // install path
+ Goroot bool `json:",omitempty"` // is this package found in the Go root?
+ Standard bool `json:",omitempty"` // is this package part of the standard Go library?
+ Stale bool `json:",omitempty"` // would 'go install' do anything for this package?
+ Root string `json:",omitempty"` // Go root or Go path dir containing this package
+ ConflictDir string `json:",omitempty"` // Dir is hidden by this other directory
// Source files
GoFiles []string `json:",omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
@@ -102,6 +103,7 @@ func (p *Package) copyBuild(pp *build.Package) {
p.Name = pp.Name
p.Doc = pp.Doc
p.Root = pp.Root
+ p.ConflictDir = pp.ConflictDir
// TODO? Target
p.Goroot = pp.Goroot
p.Standard = p.Goroot && p.ImportPath != "" && !strings.Contains(p.ImportPath, ".")
src/pkg/go/build/build.go
--- a/src/pkg/go/build/build.go
+++ b/src/pkg/go/build/build.go
@@ -339,17 +339,18 @@ const (
// A Package describes the Go package found in a directory.
type Package struct {
- Dir string // directory containing package sources
- Name string // package name
- Doc string // documentation synopsis
- ImportPath string // import path of package ("" if unknown)
- Root string // root of Go tree where this package lives
- SrcRoot string // package source root directory ("" if unknown)
- PkgRoot string // package install root directory ("" if unknown)
- BinDir string // command install directory ("" if unknown)
- Goroot bool // package found in Go root
- PkgObj string // installed .a file
- AllTags []string // tags that can influence file selection in this directory
+ Dir string // directory containing package sources
+ Name string // package name
+ Doc string // documentation synopsis
+ ImportPath string // import path of package ("" if unknown)
+ Root string // root of Go tree where this package lives
+ SrcRoot string // package source root directory ("" if unknown)
+ PkgRoot string // package install root directory ("" if unknown)
+ BinDir string // command install directory ("" if unknown)
+ Goroot bool // package found in Go root
+ PkgObj string // installed .a file
+ AllTags []string // tags that can influence file selection in this directory
+ ConflictDir string // this directory shadows Dir in $GOPATH
// Source files
GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
@@ -476,11 +477,13 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa
// else first.
if ctxt.GOROOT != "" {
if dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", sub); ctxt.isDir(dir) {
+ p.ConflictDir = dir
goto Found
}
}
for _, earlyRoot := range all[:i] {
if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) {
+ p.ConflictDir = dir
goto Found
}
}
コアとなるコードの解説
src/cmd/go/build.goの変更
runInstall関数は、go installコマンドの主要なロジックを処理します。この変更では、パッケージpのTargetが空(つまり、インストール先が決定できない)であり、かつp.ConflictDirが空でない(つまり、別のディレクトリによってシャドウされている)場合に、より具体的なエラーメッセージを出力するように条件分岐が追加されました。これにより、ユーザーは問題の原因がシャドウイングにあることを明確に理解できます。
src/cmd/go/pkg.goの変更
cmd/goパッケージのPackage構造体は、goコマンドが内部的にパッケージ情報を扱うためのものです。go/buildパッケージのPackage構造体と密接に関連しており、その情報をコピーして使用します。ConflictDirフィールドの追加とcopyBuildメソッドでのコピーは、goコマンドがシャドウイングに関する情報を取得し、利用できるようにするために必要です。
src/pkg/go/build/build.goの変更
go/buildパッケージは、Goのソースコードのビルドに関する低レベルな機能を提供します。Package構造体は、Goパッケージのメタデータを定義します。ConflictDirフィールドの追加は、パッケージの解決プロセス中にシャドウイングの情報を捕捉するための基盤となります。
(*Context).Importメソッドは、指定されたインポートパスに対応するパッケージを検索し、その情報をPackage構造体に格納します。このメソッド内の変更は、GOPATHの各エントリを検索する際に、シャドウイングが発生したかどうかを検出し、もし発生していれば、シャドウしているディレクトリのパスをp.ConflictDirに設定するロジックを追加しています。goto Foundは、パッケージが見つかった場合に、残りの検索をスキップして処理を続行するためのGoの構文です。
関連リンク
- Go Modules: https://go.dev/blog/using-go-modules
GOPATHに関する公式ドキュメント (Go 1.11以前の文脈): https://go.dev/doc/code (現在はGo Modulesが推奨されているため、古い情報として参照)- Issue #5774: https://github.com/golang/go/issues/5774
参考にした情報源リンク
- Go言語の公式ドキュメント
- GitHubのgolang/goリポジトリのコミット履歴と関連するIssue
- Go言語のビルドシステムに関する一般的な知識
go/buildパッケージのドキュメント (GoDoc)cmd/goパッケージのドキュメント (GoDoc)GOPATHとGo Modulesに関するブログ記事やチュートリアル