[インデックス 12580] ファイルの概要
このコミットは、Go言語のgo
コマンドにおけるgo get
サブコマンドの挙動を改善するものです。具体的には、go get new.code/...
のように、まだローカルに存在しない新しいコードベースに対してワイルドカード(...
)を含むパスを指定した場合に、正しく動作するように修正が加えられています。これにより、新しいリポジトリやパッケージ群を一度に取得する際の利便性と信頼性が向上しました。
コミット
commit 4e18bfb9306e80fd16522bfb6a4a98c3f2b42c0d
Author: Russ Cox <rsc@golang.org>
Date: Mon Mar 12 16:35:15 2012 -0400
cmd/go: make go get new.code/... work
Fixes #2909.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5796072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4e18bfb9306e80fd16522bfb6a4a98c3f2b42c0d
元コミット内容
cmd/go: make go get new.code/... work
Fixes #2909.
変更の背景
go get
コマンドは、Goパッケージの依存関係を解決し、リモートリポジトリからソースコードを取得してローカルのGOPATH
に配置するための重要なツールです。このコミットが修正する問題は、go get
がワイルドカード(...
)を含むパスを処理する際の既存の制限に関連しています。
以前のgo get
の実装では、new.code/...
のように、まだローカルにクローンされていない新しいリポジトリに対してワイルドカードを使用した場合、期待通りに動作しないケースがありました。これは、go get
がワイルドカードを展開する際に、ローカルに存在するパッケージ情報に依存していたためと考えられます。つまり、パッケージがまだダウンロードされていない場合、ワイルドカードが正しく解釈されず、結果として目的のコードが取得できないという問題が発生していました。
この問題は、特に新しいプロジェクトを開始する際や、大規模なライブラリ群を一度に取得する際に、ユーザーエクスペリエンスを著しく損なうものでした。コミットメッセージにあるFixes #2909
は、この特定のバグ報告に対応するものであることを示しています。この修正により、go get
は、ワイルドカードを含むパスが指定された場合でも、まずリポジトリをダウンロードし、その後でワイルドカードを適切に展開して、関連するすべてのパッケージを取得できるようになります。
前提知識の解説
go get
コマンド
go get
は、Go言語のビルドツールチェーンの一部であり、リモートリポジトリからGoパッケージとその依存関係をダウンロードし、ローカルのGOPATH
(またはGo Modulesが有効な場合はモジュールキャッシュ)にインストールするために使用されます。これにより、開発者は外部のライブラリやツールを簡単にプロジェクトに組み込むことができます。
Goのインポートパスとワイルドカード(...
)
Go言語では、パッケージはインポートパスによって識別されます。これは通常、バージョン管理システムのリポジトリパスと、そのリポジトリ内のパッケージのディレクトリパスを組み合わせたものです(例: github.com/user/repo/package
)。
ワイルドカード...
は、Goのツールにおいて「任意のサブディレクトリとファイル」を意味するために使用されます。例えば、example.com/repo/...
はexample.com/repo
以下のすべてのパッケージ(サブディレクトリを含む)を指します。これは、複数の関連するパッケージを一度に操作したい場合に非常に便利です。
go/build
パッケージ
go/build
パッケージは、Goのソースコードを解析し、パッケージのビルドに関する情報(インポートパス、ソースファイルのリスト、依存関係など)を提供する標準ライブラリです。go get
のようなツールは、このパッケージを利用してGoのプロジェクト構造を理解し、ビルドプロセスを管理します。
build.IsLocalImport(path string) bool
: この関数は、与えられたインポートパスがローカルファイルシステム上のパッケージを指しているかどうかを判断します。例えば、相対パス(例:./mypackage
)や、GOPATH
内のパスなどがローカルインポートと見なされます。
packageCache
go
コマンド内部では、パッケージ情報をキャッシュして、同じパッケージの情報を何度も解析し直すことを避けています。packageCache
は、このキャッシュメカニズムの一部であり、パッケージのインポートパスやディレクトリパスをキーとして、解析済みの*Package
構造体を格納します。
技術的詳細
このコミットは、主にsrc/cmd/go/get.go
、src/cmd/go/http.go
、src/cmd/go/vcs.go
の3つのファイルに変更を加えています。
src/cmd/go/get.go
の変更
最も重要な変更はget.go
に集中しています。
-
downloadPaths
関数の導入:runGet
関数内で、これまではimportPaths(args)
を直接呼び出していましたが、新たにdownloadPaths(args)
を呼び出すように変更されました。downloadPaths
は、引数リストをdownload
関数に渡す前に準備する新しい関数です。- この関数は、ワイルドカード(
...
)を含むパスを事前に展開しようとします。 build.IsLocalImport
を使用してローカルインポートかどうかを判断し、matchPackagesInFS
(ファイルシステム内のパッケージをマッチング)またはmatchPackages
(インポートパスに基づいてパッケージをマッチング)を呼び出してワイルドカードを展開します。- 重要なのは、ワイルドカードが展開できない場合(例: まだダウンロードされていない新しいリポジトリの場合)でも、そのパスを結果リストに残すことです。これにより、
download
関数がリポジトリを特定し、ダウンロードする機会が与えられます。
-
ダウンロードとインストールフェーズの分離と再評価:
runGet
の処理フローが再構築されました。- Phase 1 (Download/Update):
downloadPaths
で準備されたパスに基づいて、download
関数が呼び出され、パッケージのダウンロードまたは更新が行われます。 - Phase 2 (Rescan packages and reevaluate args list): ダウンロードが完了した後、
packageCache
から関連するエントリが削除され、importPaths(args)
が再度呼び出されます。これにより、新しくダウンロードされたパッケージ情報が反映され、ワイルドカードが正しく展開されるようになります。 - Phase 3 (Install):
getD
フラグ(ダウンロードのみ)が設定されていない場合に、runInstall
が呼び出され、パッケージのインストールが行われます。getD
のチェックがこのフェーズに遅延されたのは、importPaths
がエラーを出力する機会を与えるためです。
-
download
関数の改善:download
関数内で、パッケージがダウンロードされた後、ワイルドカードを含む引数(arg
)が指定されていた場合に、そのワイルドカードを再評価するロジックが追加されました。wildcardOkay
フラグ(スタックが空の場合にtrue)とstrings.Contains(arg, "...")
のチェックにより、ワイルドカードの再評価が必要かどうかが判断されます。- 再評価の際も、
build.IsLocalImport
に基づいてmatchPackagesInFS
またはmatchPackages
が使用されます。 - ダウンロード後に
packageCache
をクリアする処理が追加され、新しいパッケージ情報が確実にロードされるようにしています。 fix
ツールの実行とそれに続くパッケージのリロードのロジックが、ワイルドカード展開後の複数のパッケージに対応できるようにループ内に移動されました。
src/cmd/go/http.go
の変更
httpsOrHTTP
関数内で、HTTPフェッチが失敗した場合のlog.Printf("http fetch failed")
という冗長なログ出力が削除されました。これは、エラーが既に返されているため、追加のログは不要と判断されたためです。
src/cmd/go/vcs.go
の変更
repoRootForImportPath
関数に、ワイルドカード(...
)がリポジトリルート自体に含まれている場合の追加のチェックが導入されました。strings.Contains(importPath, "...") && strings.Contains(rr.root, "...")
という条件が追加され、もしインポートパスとリポジトリルートの両方にワイルドカードが含まれている場合、エラーを返すようになりました。これは、リポジトリルート自体にワイルドカードが含まれることは許可されないという制約を強制するためです。例えば、example.com/repo/.../sub
のようなパスは不正と見なされます。
これらの変更により、go get
は、ワイルドカードを含む新しいインポートパスに対しても、より堅牢かつ正確に動作するようになりました。
コアとなるコードの変更箇所
src/cmd/go/get.go
におけるrunGet
とdownloadPaths
の変更
--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -57,19 +58,13 @@ func init() {
func runGet(cmd *Command, args []string) {
// Phase 1. Download/update.
- args = importPaths(args)
var stk importStack
- for _, arg := range args {
+ for _, arg := range downloadPaths(args) {
download(arg, &stk)
}
exitIfErrors()
- if *getD {
- // download only
- return
- }
-
- // Phase 2. Install.
+ // Phase 2. Rescan packages and reevaluate args list.
// Code we downloaded and all code that depends on it
// needs to be evicted from the package cache so that
@@ -80,9 +75,48 @@ func runGet(cmd *Command, args []string) {
delete(packageCache, name)
}
+ args = importPaths(args)
+
+ // Phase 3. Install.
+ if *getD {
+ // Download only.
+ // Check delayed until now so that importPaths
+ // has a chance to print errors.
+ return
+ }
+
runInstall(cmd, args)
}
+// downloadPath prepares the list of paths to pass to download.
+// It expands ... patterns that can be expanded. If there is no match
+// for a particular pattern, downloadPaths leaves it in the result list,
+// in the hope that we can figure out the repository from the
+// initial ...-free prefix.
+func downloadPaths(args []string) []string {
+ args = importPathsNoDotExpansion(args)
+ var out []string
+ for _, a := range args {
+ if strings.Contains(a, "...") {
+ var expand []string
+ // Use matchPackagesInFS to avoid printing
+ // warnings. They will be printed by the
+ // eventual call to importPaths instead.
+ if build.IsLocalImport(a) {
+ expand = matchPackagesInFS(a)
+ } else {
+ expand = matchPackages(a)
+ }
+ if len(expand) > 0 {
+ out = append(out, expand...)
+ continue
+ }
+ }
+ out = append(out, a)
+ }
+ return out
+}
+
// downloadCache records the import paths we have already
// considered during the download, to avoid duplicate work when
// there is more than one dependency sequence leading to
src/cmd/go/vcs.go
におけるワイルドカードのルートチェック
--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -323,6 +323,23 @@ func repoRootForImportPath(importPath string) (*repoRoot, error) {
rr, err := repoRootForImportPathStatic(importPath, "")
if err == errUnknownSite {
rr, err = repoRootForImportDynamic(importPath)
+
+ // repoRootForImportDynamic returns error detail
+ // that is irrelevant if the user didn't intend to use a
+ // dynamic import in the first place.
+ // Squelch it.
+ if err != nil {
+ if buildV {
+ log.Printf("import %q: %v", importPath, err)
+ }
+ err = fmt.Errorf("unrecognized import path %q", importPath)
+ }
+ }
+
+ if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.root, "...") {
+ // Do not allow wildcards in the repo root.
+ rr = nil
+ err = fmt.Errorf("cannot expand ... in %q", importPath)
}
return rr, err
}
コアとなるコードの解説
runGet
とdownloadPaths
の変更
以前のrunGet
では、最初にimportPaths(args)
を呼び出して引数を処理していました。このimportPaths
は、ワイルドカードを展開する際に、ローカルに存在するパッケージ情報に依存していました。そのため、まだダウンロードされていない新しいリポジトリに対してnew.code/...
のようなパスが指定された場合、ワイルドカードが正しく展開されず、download
関数に適切な引数が渡されないという問題がありました。
新しいrunGet
では、まずdownloadPaths(args)
を呼び出すように変更されています。
downloadPaths
関数は、この問題を解決するために導入されました。
importPathsNoDotExpansion(args)
: まず、引数からワイルドカードを展開せずにインポートパスを処理します。これは、ワイルドカードがまだ展開できない可能性があるためです。- ワイルドカードの事前展開試行: 各引数
a
について、strings.Contains(a, "...")
でワイルドカードが含まれているかを確認します。 build.IsLocalImport(a)
: パスがローカルインポートであるか(例:./mypackage
)を判断します。matchPackagesInFS(a)
/matchPackages(a)
: ローカルインポートであればファイルシステムから、そうでなければインポートパスに基づいてワイルドカードを展開しようとします。- 展開できなかった場合の処理:
len(expand) > 0
で展開が成功したかを確認します。もし展開できた場合は、その展開されたパスをout
に追加します。重要なのは、展開できなかった場合でも、元のワイルドカードを含むパスa
をout
に追加することです。 これにより、download
関数がそのパスを受け取り、リポジトリを特定してダウンロードを試みることができます。
このdownloadPaths
の導入により、go get
は、ワイルドカードを含む新しいコードパスが指定された場合でも、まずリポジトリをダウンロードし、その後でワイルドカードを適切に再評価して、関連するすべてのパッケージを取得できるようになりました。
ダウンロードが完了した後、runGet
はpackageCache
をクリアし、再度importPaths(args)
を呼び出します。この再評価により、新しくダウンロードされたコードベース内のワイルドカードが正しく展開され、その後のインストールフェーズで適切なパッケージが処理されるようになります。
vcs.go
におけるワイルドカードのルートチェック
repoRootForImportPath
関数は、与えられたインポートパスからリポジトリのルートを特定する役割を担っています。このコミットでは、動的なインポートパス解決(repoRootForImportDynamic
)の後に、以下の新しいチェックが追加されました。
if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.root, "...") {
// Do not allow wildcards in the repo root.
rr = nil
err = fmt.Errorf("cannot expand ... in %q", importPath)
}
このコードは、以下の条件がすべて真の場合にエラーを発生させます。
err == nil
: これまでの処理でエラーが発生していない。strings.Contains(importPath, "...")
: 元のインポートパスにワイルドカードが含まれている。strings.Contains(rr.root, "...")
: 特定されたリポジトリのルートパスにもワイルドカードが含まれている。
これは、リポジトリのルート自体にワイルドカードが含まれるようなインポートパスは不正であるという制約を強制するためのものです。例えば、example.com/repo/.../subpackage
のようなパスは、repoRootForImportPath
がexample.com/repo/...
をリポジトリルートとして特定した場合に、このチェックによってエラーとなります。リポジトリルートは明確なものでなければならず、ワイルドカードを含むべきではないという設計思想に基づいています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/4e18bfb9306e80fd16522bfb6a4a98c3f2b42c0d
- Go CL (Code Review): https://golang.org/cl/5796072
参考にした情報源リンク
- コミットメッセージと変更されたソースコード
- Go言語の公式ドキュメント(
go get
、go/build
パッケージに関する一般的な知識) - Go言語のワイルドカード(
...
)に関する一般的な情報 - GoのIssueトラッカー(#2909の具体的な内容は、公開されている情報からは特定できませんでしたが、コミットメッセージからその目的を推測しました。)