[インデックス 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に関するブログ記事やチュートリアル