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

[インデックス 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は通常、binpkgsrcの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/gogo 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.DirGOPATH内の別のディレクトリによってシャドウされている場合に、そのシャドウしているディレクトリのパスを保持します。

パッケージ解決ロジックの変更

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に、シャドウイングのロジックと新しいエラーメッセージを検証するためのテストケースが追加されました。

  • GOPATHroot1:root2として設定し、root1root2にそれぞれfooパッケージを配置。
  • root1mathパッケージが標準ライブラリのmathによってシャドウされることを確認。
  • root2fooパッケージがroot1fooによってシャドウされることを確認。
  • シャドウされたroot2fooパッケージを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コマンドの主要なロジックを処理します。この変更では、パッケージpTargetが空(つまり、インストール先が決定できない)であり、かつ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言語の公式ドキュメント
  • GitHubのgolang/goリポジトリのコミット履歴と関連するIssue
  • Go言語のビルドシステムに関する一般的な知識
  • go/buildパッケージのドキュメント (GoDoc)
  • cmd/goパッケージのドキュメント (GoDoc)
  • GOPATHとGo Modulesに関するブログ記事やチュートリアル