[インデックス 11143] ファイルの概要
このコミットは、Go言語のコマンドラインツールであるgo
コマンド自体(cmd/go
ディレクトリに存在する)が、自身のディレクトリ内で引数なしのgo install
コマンドを実行した場合に正しく動作するように修正するものです。具体的には、ローカルファイルシステムのパスを適切に処理し、cmd
ディレクトリ内のパッケージを正しく解決できるように改善されました。
コミット
commit 4505ae3863d363a9ef76e6ee4ead162493c0b143
Author: Russ Cox <rsc@golang.org>
Date: Thu Jan 12 15:27:57 2012 -0800
cmd/go: handle path to cmd directory
Now it works to run 'go install' (no args) in cmd/go.
Fixes #2679.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5543046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4505ae3863d363a9ef76e6ee4ead162493c0b143
元コミット内容
cmd/go: handle path to cmd directory
Now it works to run 'go install' (no args) in cmd/go.
Fixes #2679.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5543046
変更の背景
このコミットが行われる前は、Go言語のソースコードリポジトリ内のsrc/cmd/go
ディレクトリ(go
コマンド自身のソースコードが置かれている場所)で、引数なしのgo install
コマンドを実行すると、正しく動作しませんでした。これは、go
コマンドが自身のパッケージを解決する際に、ローカルファイルシステムのパス(例: ./
や../
で始まるパス、または絶対パス)の扱いが不十分であったためです。
この問題は、Goの旧来の課題追跡システムで「Issue 2679」として報告されていました。このコミットは、この特定のシナリオ(cmd/go
ディレクトリ内でのgo install
)を修正し、go
コマンドが自身のビルドとインストールを適切に行えるようにすることを目的としています。
前提知識の解説
go install
コマンド
go install
コマンドは、Goのパッケージをコンパイルし、その結果生成される実行可能ファイル(コマンドの場合)またはアーカイブファイル(ライブラリの場合)をGOPATH/bin
またはGOROOT/bin
(コマンドの場合)やGOPATH/pkg
またはGOROOT/pkg
(ライブラリの場合)にインストールするために使用されます。引数なしで実行された場合、現在のディレクトリにあるパッケージをインストールしようとします。
Goのパッケージ管理とインポートパス
Goのパッケージは、ファイルシステム上のディレクトリ構造と対応しています。Goのソースコードは通常GOPATH/src
またはGOROOT/src
以下に配置され、インポートパス(例: "fmt"
, "net/http"
)はこれらのルートからの相対パスとして解決されます。
cmd/go
ディレクトリの特殊性
src/cmd/go
ディレクトリは、Go言語のツールチェインの中核であるgo
コマンド自身のソースコードを格納しています。そのため、このディレクトリ内でgo install
を実行するというシナリオは、go
コマンドが自分自身をビルド・インストールするという、やや特殊なケースとなります。
Go標準ライブラリ関数
strings.HasPrefix(s, prefix string) bool
: 文字列s
が指定されたprefix
で始まるかどうかを判定します。strings.Contains(s, substr string) bool
: 文字列s
が指定されたsubstr
を含むかどうかを判定します。filepath.IsAbs(path string) bool
: 指定されたパスが絶対パスであるかどうかを判定します。filepath.Join(elem ...string) string
: 複数のパス要素を結合して、プラットフォーム固有のパス区切り文字で区切られた単一のパスを生成します。filepath.Separator
: プラットフォーム固有のパス区切り文字(Windowsでは\\
、Unix系では/
)。os.Stat(name string) (FileInfo, error)
: 指定されたファイルまたはディレクトリの情報を返します。st.IsDir() bool
:os.Stat
が返すFileInfo
インターフェースのメソッドで、それがディレクトリであるかどうかを判定します。exec.Command(name string, arg ...string) *Cmd
: 指定されたコマンドと引数で新しいCmd
構造体を作成します。Cmd.CombinedOutput() ([]byte, error)
: コマンドを実行し、その標準出力と標準エラー出力を結合したバイトスライスとして返します。
...
(glob pattern)
Goのコマンドラインツールでは、パッケージパスに...
を使用することで、特定のディレクトリ以下のすべてのパッケージを意味するグロブパターンとして機能します。例えば、./...
は現在のディレクトリ以下のすべてのパッケージを、cmd/...
はcmd
ディレクトリ以下のすべてのパッケージを指します。
技術的詳細
このコミットは、主にsrc/cmd/go/main.go
とsrc/cmd/go/pkg.go
の2つのファイルを変更しています。
src/cmd/go/main.go
の変更点
isLocalPath
関数の導入: 新しいヘルパー関数isLocalPath(arg string) bool
が追加されました。この関数は、引数arg
がローカルファイルシステム上のディレクトリを示すインポートパス(つまり、.
、..
、./
、../
のいずれかで始まるパス)である場合にtrue
を返します。func isLocalPath(arg string) bool { return arg == "." || arg == ".." || strings.HasPrefix(arg, "./") || strings.HasPrefix(arg, "../") }
importPaths
関数の修正:importPaths
関数内で、strings.HasPrefix(a, "./") || strings.HasPrefix(a, "../")
という条件が、新しく導入されたisLocalPath(a)
に置き換えられました。これにより、ローカルパスの判定ロジックがより簡潔かつ正確になりました。特に、./...
や../...
のようなパターンを処理する際に、ローカルパスとして認識されるようになります。runOut
関数の導入:runOut
という新しいヘルパー関数が追加されました。これは、外部コマンドを実行し、その標準出力と標準エラー出力を結合した結果をバイトスライスとして返すものです。コマンドの実行に失敗した場合は、エラーメッセージを標準エラー出力に書き込み、エラーを発生させます。これは、go
コマンドが内部的に他のコマンド(例えば、go tool compile
など)を実行する際に利用されます。
src/cmd/go/pkg.go
の変更点
-
loadPackage
関数の修正:loadPackage
関数は、与えられた引数からGoパッケージをロードする役割を担っています。この関数に、cmd
ディレクトリ内のパッケージをローカルパスとして扱うための重要な変更が加えられました。-
標準コマンドのパス解決の改善: 以前は、
cmd/
で始まるパスが標準コマンドであると判断される条件に!filepath.IsAbs(arg)
(引数が絶対パスではない)が含まれていました。このコミットでは、この!filepath.IsAbs(arg)
の条件が削除されました。これにより、cmd/
で始まるパスが絶対パスであっても、標準コマンドとして適切に処理される可能性が広がりました。 -
ローカルパスとしての
cmd
ディレクトリの検出: 新たに、引数が絶対パスまたはisLocalPath
で判定されるローカルパスである場合に、それがGOROOT/src/cmd
ディレクトリ以下のパスであるかをチェックするロジックが追加されました。 具体的には、引数を絶対パスに変換し、それがGOROOT/src/cmd
ディレクトリのパスで始まるかどうか、そしてそれがディレクトリであるかどうかを確認します。この条件が満たされた場合、そのパスは標準コマンドのソースディレクトリとして認識され、適切なTree
とimportPath
が設定されます。これにより、cmd/go
ディレクトリ内でgo install
を実行した際に、go
コマンド自身が自身のパッケージを正しく見つけられるようになります。
-
これらの変更により、go
コマンドは、ローカルファイルシステム上のパス、特にcmd
ディレクトリ内のパスをより柔軟かつ正確に解釈できるようになり、go install
コマンドの動作が改善されました。
コアとなるコードの変更箇所
src/cmd/go/main.go
--- a/src/cmd/go/main.go
+++ b/src/cmd/go/main.go
@@ -192,7 +192,7 @@ func importPaths(args []string) []string {
}
var out []string
for _, a := range args {
- if (strings.HasPrefix(a, "./") || strings.HasPrefix(a, "../")) && strings.Contains(a, "...") {
+ if isLocalPath(a) && strings.Contains(a, "...") {
out = append(out, allPackagesInFS(a)...)
continue
}
@@ -246,6 +246,17 @@ func run(cmdargs ...interface{}) {
}
}
+func runOut(cmdargs ...interface{}) []byte {
+ cmdline := stringList(cmdargs...)
+ out, err := exec.Command(cmdline[0], cmdline[1:]...).CombinedOutput()
+ if err != nil {
+ os.Stderr.Write(out)
+ errorf("%v", err)
+ out = nil
+ }
+ return out
+}
+
// matchPattern(pattern)(name) reports whether
// name matches pattern. Pattern is a limited glob
// pattern in which '...' means 'any string' and there
@@ -422,3 +433,10 @@ func stringList(args ...interface{}) []string {
}
return x
}
+
+// isLocalPath returns true if arg is an import path denoting
+// a local file system directory. That is, it returns true if the
+// path begins with ./ or ../ .
+func isLocalPath(arg string) bool {
+ return arg == "." || arg == ".." || strings.HasPrefix(arg, "./") || strings.HasPrefix(arg, "../")
+}
src/cmd/go/pkg.go
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -79,7 +79,7 @@ func loadPackage(arg string) (*Package, error) {
t, importPath, err := build.FindTree(arg)
dir := ""
// Maybe it is a standard command.
- if err != nil && !filepath.IsAbs(arg) && strings.HasPrefix(arg, "cmd/") {
+ if err != nil && strings.HasPrefix(arg, "cmd/") {
goroot := build.Path[0]
p := filepath.Join(goroot.Path, "src", arg)
if st, err1 := os.Stat(p); err1 == nil && st.IsDir() {
@@ -89,6 +89,19 @@ func loadPackage(arg string) (*Package, error) {
}
}
+ // Maybe it is a path to a standard command.
+ if err != nil && (filepath.IsAbs(arg) || isLocalPath(arg)) {
+ arg, _ := filepath.Abs(arg)
+ goroot := build.Path[0]
+ cmd := filepath.Join(goroot.Path, "src", "cmd") + string(filepath.Separator)
+ if st, err1 := os.Stat(arg); err1 == nil && st.IsDir() && strings.HasPrefix(arg, cmd) {
+ t = goroot
+ importPath = filepath.FromSlash(arg[len(cmd):])
+ dir = arg
+ err = nil
+ }
+ }
+
if err != nil {
return nil, err
}
コアとなるコードの解説
src/cmd/go/main.go
-
isLocalPath
関数: この関数は、Goのインポートパスがファイルシステム上のローカルディレクトリを指すかどうかを判定します。具体的には、パスが.
、..
、./
、または../
のいずれかである場合にtrue
を返します。これにより、go
コマンドが./...
のようなローカルのグロブパターンを正しく解釈できるようになります。 -
importPaths
関数の変更:importPaths
関数は、コマンドライン引数からインポートパスのリストを生成します。以前は、ローカルパスの判定にstrings.HasPrefix
を直接使用していましたが、isLocalPath
関数を導入することで、このロジックがより明確になり、保守性が向上しました。これにより、go install ./...
のようなコマンドが、現在のディレクトリ以下のすべてのパッケージを正しく対象とできるようになります。 -
runOut
関数: この関数は、外部コマンドを実行し、その標準出力と標準エラー出力をまとめて取得するためのユーティリティです。コマンドの実行中にエラーが発生した場合、そのエラーメッセージを標準エラー出力に表示し、エラーを返します。これは、go
コマンドが内部でコンパイラやリンカなどの他のツールを呼び出す際に、その出力を効率的に処理するために使用されます。
src/cmd/go/pkg.go
loadPackage
関数の変更:loadPackage
関数は、指定された引数からGoパッケージの情報をロードします。- 標準コマンドの絶対パス対応: 以前のコードでは、
cmd/
で始まるパスが標準コマンドであると判断される際に、そのパスが絶対パスではないという条件(!filepath.IsAbs(arg)
)がありました。この条件が削除されたことで、go
コマンドはcmd/go
のような標準コマンドのパスが絶対パスとして与えられた場合でも、それを正しく標準コマンドとして認識できるようになりました。 - ローカルな
cmd
ディレクトリの検出: 新しく追加されたブロックは、引数が絶対パスであるか、またはisLocalPath
で判定されるローカルパスである場合に、それがGOROOT/src/cmd
ディレクトリ以下のパスであるかを詳細にチェックします。このチェックでは、os.Stat
でパスが実際にディレクトリであること、そしてstrings.HasPrefix
でGOROOT/src/cmd
のパスで始まることを確認します。これにより、go
コマンドがsrc/cmd/go
ディレクトリ内で実行された際に、自身のパッケージをローカルパスとして正しく解決し、ビルド・インストールプロセスを進めることができるようになります。
- 標準コマンドの絶対パス対応: 以前のコードでは、
これらの変更は、go
コマンドが自身のソースコードを扱う際の堅牢性を高め、開発者がgo
コマンドのソースディレクトリ内で直接作業する際の利便性を向上させました。
関連リンク
- Go Gerrit Change: https://golang.org/cl/5543046
- Go Issue 2679 (旧トラッカー): このコミットが修正した課題の元の番号。
参考にした情報源リンク
- Go Gerrit Code Review: https://golang.org/cl/5543046
- Go言語の公式ドキュメント (Goコマンド、パッケージ管理など): https://go.dev/doc/ (一般的なGoの概念理解のため)
os
パッケージのドキュメント: https://pkg.go.dev/ospath/filepath
パッケージのドキュメント: https://pkg.go.dev/path/filepathstrings
パッケージのドキュメント: https://pkg.go.dev/stringsos/exec
パッケージのドキュメント: https://pkg.go.dev/os/exec