[インデックス 11068] ファイルの概要
このコミットは、Go言語のgo
コマンドにおいて、パッケージのインポートパス引数に...
(ワイルドカード)パターンを追加する機能の実装です。これにより、ユーザーは特定のディレクトリツリー内のすべてのパッケージや、パターンに一致するパッケージを簡単に指定できるようになります。
コミット
- コミットハッシュ:
b8615a0931b05c37c7d8cf3c0e11a858888483de
- 作者: Russ Cox rsc@golang.org
- 日付: Mon Jan 9 16:23:00 2012 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b8615a0931b05c37c7d8cf3c0e11a858888483de
元コミット内容
go: add ... patterns in import path arguments
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5530058
---
src/cmd/go/help.go | 7 ++++\n src/cmd/go/main.go | 118 ++++++++++++++++++++++++++++++++++++++++++++++-------\n 2 files changed, 110 insertions(+), 15 deletions(-)
変更の背景
このコミット以前のgo
コマンド(例: go build
, go install
)では、パッケージを指定する際に、個々のパッケージパスを明示的に記述するか、あるいは特別なキーワード(all
やstd
)を使用する必要がありました。しかし、これは大規模なプロジェクトや、特定のサブディレクトリ以下にある多数のパッケージに対して一括で操作を行いたい場合に不便でした。
例えば、encoding
パッケージとそのサブパッケージ(encoding/json
, encoding/xml
など)すべてに対してgo test
を実行したい場合、以前はそれぞれのパッケージパスを列挙する必要がありました。このコミットは、encoding/...
のようなワイルドカードパターンを導入することで、この問題を解決し、ユーザーがより柔軟かつ効率的にパッケージを指定できるようにすることを目的としています。これにより、開発者は関連するパッケージ群に対して、より簡潔なコマンドで操作を実行できるようになりました。
前提知識の解説
Go言語のパッケージ管理
Go言語では、コードは「パッケージ」という単位で整理されます。パッケージは関連する機能の集合であり、他のパッケージからimport
文を使って利用されます。各パッケージはファイルシステム上のディレクトリに対応し、そのディレクトリパスがパッケージのインポートパスとなります。例えば、fmt
パッケージはGoの標準ライブラリの一部であり、import "fmt"
として利用されます。
GOPATH
とGOROOT
Goのワークスペースは、主に以下の2つの環境変数によって構成されます。
GOROOT
: Goのインストールディレクトリを指します。Goの標準ライブラリのソースコード(例:fmt
,net/http
など)は$GOROOT/src
以下に配置されています。GOPATH
: ユーザーが開発するプロジェクトのワークスペースを指します。通常、複数のディレクトリパスを設定できます。go get
コマンドでダウンロードされた外部パッケージや、ユーザー自身のプロジェクトのソースコードは$GOPATH/src
以下に配置されます。
go
コマンドは、これらのGOROOT
とGOPATH
のパスを探索して、指定されたパッケージを見つけ出します。
go
コマンド
go
コマンドは、Go言語のビルド、テスト、依存関係管理などを行うための主要なツールです。例えば、go build
はパッケージをコンパイルし、go install
はコンパイルしたバイナリをインストールします。これらのコマンドは、引数としてパッケージのインポートパスを受け取ります。
ワイルドカードパターン(...
)
一般的なファイルシステムやシェルでは、*
や?
などのワイルドカードがファイル名やパスのパターンマッチングに利用されます。Goのgo
コマンドにおける...
は、これに似た概念ですが、Goのパッケージパスに特化したワイルドカードです。
...
は「任意の文字列」にマッチします。これには空文字列やスラッシュ(/
)を含む文字列も含まれます。- 例えば、
encoding/...
はencoding
ディレクトリ以下にあるすべてのパッケージ(encoding/json
,encoding/xml
など)にマッチします。 .../x
のようなパターンも可能で、パスの途中に...
を置くことで、任意の深さのディレクトリを横断してx
という名前のパッケージにマッチさせることができます。
このコミットは、この...
パターンをgo
コマンドの引数として解釈し、対応するパッケージ群を自動的に特定する機能を追加するものです。
技術的詳細
このコミットの主要な変更は、src/cmd/go/main.go
ファイルに集中しており、特にimportPaths
、allPackages
、そして新しく導入されたmatchPattern
、allPackagesInFS
関数が中心となります。
-
src/cmd/go/help.go
の更新:go help packages
の出力に、...
ワイルドカードパターンの説明が追加されました。これにより、ユーザーはこの新機能の存在と使い方を公式ドキュメントを通じて知ることができます。--- a/src/cmd/go/help.go +++ b/src/cmd/go/help.go @@ -30,6 +30,13 @@ lists all the packages on the local system. The special import path "std" is like all but expands to just the packages in the standard Go library. +An import path is a pattern if it includes one or more "..." wildcards, +each of which can match any string, including the empty string and +strings containing slashes. Such a pattern expands to all package +directories found in the GOPATH trees with names matching the +patterns. For example, encoding/... expands to all packages +in the encoding tree. + An import path can also name a package to be downloaded from a remote repository. Run 'go help remote' for details.
-
src/cmd/go/main.go
の変更:-
importPaths
関数の変更: この関数は、go
コマンドに渡された引数を解析し、実際に処理すべきパッケージのインポートパスのリストを返します。変更前は、引数がall
またはstd
の場合にallPackages
を呼び出すか、単に引数をそのまま返すだけでした。 変更後は、引数に...
が含まれる場合、または./
や../
で始まる相対パスと...
が組み合わされた場合に、allPackages
または新しく導入されたallPackagesInFS
を呼び出すように拡張されました。これにより、ワイルドカードパターンが適切に展開されるようになりました。func importPaths(args []string) []string { var out []string for _, a := range args { // ./... or ../... の形式のパターンを処理 if (strings.HasPrefix(a, "./") || strings.HasPrefix(a, "../")) && strings.Contains(a, "...") { out = append(out, allPackagesInFS(a)...) continue } // all, std, または ... を含むパターンを処理 if a == "all" || a == "std" || strings.Contains(a, "...") { out = append(out, allPackages(a)...) continue } // それ以外の通常のパス out = append(out, a) } return out }
-
matchPattern
関数の導入: この新しいヘルパー関数は、...
を含むパターン文字列を受け取り、そのパターンに名前がマッチするかどうかを判定する関数を返します。内部的には、...
を正規表現の.*
に変換し、regexp
パッケージを使用してパターンマッチングを行います。これにより、柔軟なワイルドカードマッチングが可能になります。// matchPattern(pattern)(name) reports whether // name matches pattern. Pattern is a limited glob // pattern in which '...' means 'any string' and there // is no other special syntax. func matchPattern(pattern string) func(name string) bool { re := regexp.QuoteMeta(pattern) // パターン内の特殊文字をエスケープ re = strings.Replace(re, `\.`, `.*`, -1) // `...`を正規表現の`.*`に変換 reg := regexp.MustCompile(`^` + re + `$`) // パターン全体にマッチするように`^`と`$`を追加 return func(name string) bool { return reg.MatchString(name) } }
-
allPackages
関数の変更: この関数は、GOROOT
とGOPATH
以下からすべてのパッケージ、または指定されたパターンにマッチするパッケージを検索します。変更前はall
またはstd
キーワードのみを扱っていましたが、変更後はmatchPattern
関数を利用して、...
を含む任意のパターンにマッチするパッケージをフィルタリングできるようになりました。また、.
で始まるディレクトリ(例:.git
)やtestdata
ディレクトリをスキップするロジックも追加され、不要なディレクトリの走査を避けるようになりました。func allPackages(pattern string) []string { match := func(string) bool { return true } // デフォルトはすべてマッチ if pattern != "all" && pattern != "std" { match = matchPattern(pattern) // パターンが指定された場合はmatchPatternを使用 } // ... (既存のパッケージ探索ロジック) ... // パッケージ名がパターンにマッチする場合のみ追加 if match(name) { pkgs = append(pkgs, name) } // ... (ディレクトリ走査時のフィルタリング) ... // .foo や testdata ディレクトリを避ける _, elem := filepath.Split(path) if strings.HasPrefix(elem, ".") || elem == "testdata" { return filepath.SkipDir } // ... }
-
allPackagesInFS
関数の導入: この関数は、./
または../
で始まる相対パスと...
を組み合わせたパターン(例:./myproject/...
)を処理するために導入されました。これは、GOPATH
やGOROOT
全体ではなく、特定のファイルシステム上のディレクトリツリーを起点としてパッケージを検索します。filepath.Walk
を使用して指定されたディレクトリ以下を再帰的に走査し、matchPattern
でフィルタリングを行います。// allPackagesInFS is like allPackages but is passed a pattern // beginning ./ or ../, meaning it should scan the tree rooted // at the given directory. There are ... in the pattern too. func allPackagesInFS(pattern string) []string { // スキャンを開始するディレクトリを特定 i := strings.Index(pattern, "...") dir, _ := path.Split(pattern[:i]) prefix := "" if strings.HasPrefix(pattern, "./") { prefix = "./" // ./ を保持してマッチングと結果パスに利用 } match := matchPattern(pattern) var pkgs []string filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { if err != nil || !fi.IsDir() { return nil } // .foo や testdata ディレクトリを避ける _, elem := filepath.Split(path) if strings.HasPrefix(elem, ".") || elem == "testdata" { return filepath.SkipDir } name := prefix + filepath.ToSlash(path) // パッケージ名を構築 if !match(name) { // パターンにマッチしない場合はスキップ return nil } if _, err = build.ScanDir(path); err != nil { // パッケージとして有効かチェック return nil } pkgs = append(pkgs, name) return nil }) if len(pkgs) == 0 { fmt.Fprintf(os.Stderr, "warning: %q matched no packages\\n", pattern) } return pkgs }
-
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルにあります。
-
src/cmd/go/help.go
:go help packages
の出力に、...
ワイルドカードパターンに関する説明が7行追加されました。
-
src/cmd/go/main.go
:import
文にpath
とregexp
パッケージが追加されました。importPaths
関数が大幅に修正され、...
パターンや相対パス(./
,../
)を含む引数を処理するロジックが追加されました。matchPattern
関数が新しく追加されました。これは、...
を含むパターン文字列を正規表現に変換し、文字列がそのパターンにマッチするかどうかを判定するクロージャを返します。allPackages
関数が修正され、matchPattern
を利用して指定されたパターンにマッチするパッケージのみを返すようになりました。また、.
で始まるディレクトリやtestdata
ディレクトリをスキップするロジックが改善されました。allPackagesInFS
関数が新しく追加されました。これは、./
や../
で始まる相対パスと...
を組み合わせたパターンを処理し、ファイルシステム上の特定のディレクトリツリーを走査してパッケージを見つけます。
コアとなるコードの解説
importPaths
関数の拡張
importPaths
関数は、go
コマンドの引数として渡されたパッケージパスを解釈する中心的な役割を担います。このコミットにより、引数が単なるパッケージパスだけでなく、all
、std
、そして新しい...
パターンを含むことができるようになりました。
strings.HasPrefix(a, "./") || strings.HasPrefix(a, "../")
: 引数が相対パス(カレントディレクトリまたは親ディレクトリからのパス)で始まるかどうかをチェックします。strings.Contains(a, "...")
: 引数にワイルドカード...
が含まれているかどうかをチェックします。- これらの条件に基づいて、
allPackagesInFS
(相対パスと...
の組み合わせ)またはallPackages
(all
,std
, または絶対パスの...
パターン)を呼び出し、適切なパッケージリストを取得します。
matchPattern
関数の実装
この関数は、...
ワイルドカードを正規表現の.*
に変換することで、柔軟なパターンマッチングを実現しています。
regexp.QuoteMeta(pattern)
: 入力されたpattern
文字列内の正規表現の特殊文字(例:.
,*
,+
など)をエスケープします。これにより、...
以外の文字がリテラルとして扱われるようになります。strings.Replace(re,
.,
.*, -1)
: エスケープされた...
(\.
)を正規表現の「任意の0文字以上の文字」にマッチする.*
に置換します。-1
はすべての出現を置換することを意味します。regexp.MustCompile(
^+ re +
$)
: 変換された正規表現文字列をコンパイルします。^
と$
は、パターンが文字列全体にマッチする必要があることを示します。- 返される関数は、与えられた
name
がコンパイルされた正規表現にマッチするかどうかを判定します。
allPackages
とallPackagesInFS
の連携
allPackages
: 主にGOPATH
とGOROOT
全体を対象にパッケージを探索します。matchPattern
関数を利用して、指定されたpattern
(例:encoding/...
)に合致するパッケージのみを結果に含めます。また、filepath.SkipDir
を利用して、.
で始まる隠しディレクトリやtestdata
ディレクトリなど、パッケージとして扱われるべきではないディレクトリを走査から除外することで、パフォーマンスを向上させています。allPackagesInFS
: 相対パス(例:./myutil/...
)が指定された場合に、その相対パスが指すファイルシステム上のディレクトリツリーのみを対象にパッケージを探索します。これもfilepath.Walk
とmatchPattern
を組み合わせて、効率的に目的のパッケージを見つけ出します。build.ScanDir
を呼び出すことで、実際にGoのソースファイルが存在し、パッケージとして有効であるかどうかのチェックも行っています。
これらの変更により、go
コマンドはより強力で柔軟なパッケージ指定機能を手に入れ、開発者の利便性が大幅に向上しました。
関連リンク
- Go Code Review: https://golang.org/cl/5530058
参考にした情報源リンク
- Go言語公式ドキュメント: https://golang.org/doc/
go help packages
コマンドの出力 (このコミットによって追加された説明を含む)- Go言語の
path/filepath
パッケージドキュメント: https://golang.org/pkg/path/filepath/ - Go言語の
regexp
パッケージドキュメント: https://golang.org/pkg/regexp/ - Go言語の
go/build
パッケージドキュメント: https://golang.org/pkg/go/build/ - Go言語の
strings
パッケージドキュメント: https://golang.org/pkg/strings/ - Go言語の
os
パッケージドキュメント: https://golang.org/pkg/os/