[インデックス 10856] ファイルの概要
コミット
このコミットは、Go言語のパッケージ管理ツールである goinstall
コマンドの動作を改善するものです。具体的には、古いGoogle Codeのインポートパス(例: project.googlecode.com/...
)が検出された際に、goinstall -fix
オプションの使用を提案するメッセージの表示ロジックを洗練しています。この変更により、問題のあるインポートパスがコマンドラインで直接指定されたものではなく、他のパッケージの依存関係として現れた場合にのみ、-fix
の提案が行われるようになります。これにより、ユーザーへのガイダンスがより適切かつ的確になり、不必要な提案を避けることができます。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/76a078332173ab49e0a9d3fad4854960ee0b1c50
元コミット内容
goinstall: only suggest -fix for bad imports when appropriate
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5495073
変更の背景
Go言語の初期の段階では、多くのオープンソースプロジェクトがGoogle Code上でホストされていました。Goのパッケージインポートパスは、通常、そのパッケージのソースコードがホストされているリポジトリのURLに似た形式を取ります。例えば、projectname.googlecode.com/svn/path/to/package
のような形式が使われていました。しかし、Goのツールチェーンは後に、より統一された code.google.com/p/projectname/path/to/package
という形式を推奨し、この形式への移行が進められました。
goinstall
は、Goパッケージをダウンロードし、ビルドしてインストールするための初期のコマンドラインツールでした(現在の go get
コマンドの前身)。この goinstall
は、古い googlecode.com
形式のインポートパスに遭遇した場合、新しい code.google.com/p/
形式に修正する必要があることをユーザーに通知し、さらに goinstall -fix
コマンドを実行することで自動的に修正できることを提案していました。
しかし、この -fix
の提案は、常にユーザーにとって最適なタイミングで表示されるわけではありませんでした。特に、ユーザーがコマンドラインで直接古い形式のインポートパスを指定した場合、そのパスはユーザーが意図的に指定したものである可能性があり、その場で -fix
の実行を促すメッセージは冗長であったり、ユーザーを混乱させたりする可能性がありました。
このコミットの目的は、このようなユーザー体験の課題を解決することにあります。goinstall
が、古いインポートパスの問題が、ユーザーが直接指定したパッケージではなく、そのパッケージが依存している別のパッケージ(つまり、間接的な依存関係)で発生した場合にのみ、-fix
の提案を行うようにロジックを調整することで、より賢く、より適切なタイミングでユーザーにガイダンスを提供することを目指しています。これにより、エラーメッセージの関連性が高まり、ユーザーは本当に修正が必要な場合にのみ、その提案を受け取ることができるようになります。
前提知識の解説
goinstall
コマンド
goinstall
は、Go言語の初期に開発者がパッケージを管理するために使用していたコマンドラインツールです。その主な機能は、指定されたインポートパスに基づいて、リモートのバージョン管理システム(Mercurial, Git, Subversionなど)からGoパッケージのソースコードをダウンロードし、コンパイルして、GOPATH
環境変数で指定されたワークスペースにインストールすることでした。このコマンドは、Goエコシステムが成熟するにつれて、より高機能で統合された go get
コマンドに置き換えられ、現在ではほとんど使用されていません。しかし、このコミットが作成された2011年当時は、Goパッケージ管理の主要なツールの一つでした。
Goのインポートパスの仕組み
Go言語では、パッケージは一意のインポートパスによって識別されます。このパスは、通常、パッケージのソースコードがホストされているリポジトリのURLに直接対応しています。例えば、github.com/user/repo/package
のような形式です。Goのツールチェーン(goinstall
や go get
)は、このインポートパスを解析し、対応するリポジトリからソースコードを取得します。この仕組みにより、Goのパッケージは分散された環境で簡単に共有・再利用できるようになっています。
Google Code と Goプロジェクトのホスティングの歴史
Google Codeは、かつてGoogleが提供していた無料のオープンソースプロジェクトホスティングサービスです。Go言語が誕生した初期の頃、多くのGoプロジェクトがこのGoogle Code上でホストされていました。Google CodeのリポジトリURLは、projectname.googlecode.com/svn
や projectname.googlecode.com/git
のような形式でした。しかし、Go言語のツールチェーンは、より簡潔で標準的な code.google.com/p/projectname
という形式を推奨し、多くのプロジェクトがこの新しい形式に移行しました。この移行期間中、古い形式のインポートパスが既存のコードベースや依存関係の中に残存していることがあり、これが goinstall
が対処すべき問題の一つとなっていました。
-fix
オプションと gofix
ツール
Go言語は、言語仕様や標準ライブラリの進化に伴い、既存のコードを新しいAPIや慣習に自動的に適合させるためのツールを提供しています。その代表的なものが gofix
です。gofix
は、Goのソースコードを解析し、非推奨となった構文やAPIの使用箇所を自動的に修正する機能を持っています。goinstall
コマンドの -fix
オプションは、この gofix
ツールを内部的に呼び出すものでした。つまり、goinstall -fix
を実行すると、ダウンロードされたパッケージのコードに対して gofix
が適用され、古いGoogle Codeのインポートパスのような既知の互換性の問題を自動的に修正することが可能でした。
技術的詳細
このコミットの技術的な核心は、Goのエラーハンドリングにおける「エラーの型」を利用した条件分岐の洗練にあります。これにより、goinstall
は特定のエラー状況をより正確に識別し、それに応じた適切なユーザーフィードバックを提供できるようになりました。
-
errOldGoogleRepo
型の導入:src/cmd/goinstall/download.go
ファイルに、errOldGoogleRepo
という新しいカスタムエラー型が定義されました。type errOldGoogleRepo struct { fixedPath string } func (e *errOldGoogleRepo) Error() string { return fmt.Sprintf("unsupported import path; should be %q", e.fixedPath) }
この構造体は、古いGoogle Codeのインポートパスが検出されたことを示すために使用されます。
fixedPath
フィールドには、修正後の正しいインポートパス(例:code.google.com/p/projectname
)が格納されます。また、Error()
メソッドを実装しているため、Goの組み込みerror
インターフェースを満たします。これにより、このカスタムエラーは通常のerror
型として扱われつつも、その具体的な型情報を保持し、後続の処理で型アサーションによって識別することが可能になります。 -
download
関数でのエラー生成の変更:download
関数は、指定されたインポートパスに基づいてパッケージをダウンロードする役割を担っています。この関数内で、oldGoogleRepo
という正規表現がインポートパスにマッチした場合(つまり、古いGoogle Codeの形式が検出された場合)、以前はfmt.Errorf
を使用して、修正後のパスと-fix
オプションの実行を促すメッセージを含む汎用的なエラー文字列を生成していました。 このコミットにより、その汎用的なエラーメッセージの生成が削除され、代わりに新しく定義されたerrOldGoogleRepo
型のインスタンスが返されるようになりました。// 変更前: 汎用的なエラーメッセージを生成 // err = fmt.Errorf( // "unsupported import path; should be %q\n" + // "\tRun goinstall with -fix to gofix the code.", // fixedPath, // ) // 変更後: 特定のエラー型を返す err = &errOldGoogleRepo{fixedPath}
この変更により、エラーが発生した時点では
-fix
の提案はエラーメッセージに含まれず、エラーの「種類」と「修正後のパス」という情報のみが、型情報として伝達されることになります。 -
install
関数でのエラーハンドリングの改善:src/cmd/goinstall/main.go
のinstall
関数は、パッケージのインストールプロセス全体をオーケストレーションします。この関数内でdownload
関数が呼び出され、その結果として返されるエラーが処理されます。 変更されたコードブロックは以下の通りです。if err != nil { // only suggest -fix if the bad import was not on the command line if e, ok := err.(*errOldGoogleRepo); ok && parent != "" { err = fmt.Errorf("%v\nRun goinstall with -fix to gofix the code.", e) } return &DownloadError{pkg, tree.Goroot, err} }
ここで重要なのは、
if e, ok := err.(*errOldGoogleRepo); ok
というGoの型アサーション構文です。これは、download
関数から返されたerr
が*errOldGoogleRepo
型であるかどうかをチェックします。もしそうであれば、そのエラーの具体的な値が変数e
に代入され、ok
はtrue
となります。 さらに、parent != ""
という条件が追加されています。parent
引数は、現在処理中のパッケージが、別のパッケージの依存関係としてダウンロードされている場合に、その親パッケージのインポートパスを保持します。もしparent
が空文字列 (""
) であれば、そのパッケージはユーザーがコマンドラインで直接指定したものであることを意味します。 したがって、このif
文は、以下の両方の条件が満たされた場合にのみ真となります。- ダウンロード中に古いGoogle Codeのインポートパスに関するエラーが発生し、それが
errOldGoogleRepo
型である。 - そのエラーが発生したパッケージが、コマンドラインで直接指定されたものではなく、他のパッケージの依存関係としてダウンロードされている。
これらの条件が満たされた場合にのみ、元の
errOldGoogleRepo
のエラーメッセージ(e.Error()
が返す文字列、例:unsupported import path; should be "code.google.com/p/..."
)に加えて、「Run goinstall with -fix to gofix the code.
」という-fix
の提案が追加された新しいエラーがfmt.Errorf
によって生成され、err
に再代入されます。 - ダウンロード中に古いGoogle Codeのインポートパスに関するエラーが発生し、それが
この一連の変更により、goinstall
は、ユーザーが直接指定したインポートパスが古い形式であっても、すぐに冗長な -fix
の提案を行うことを避け、依存関係の解決中に発生した古いインポートパスの問題に対してのみ、より適切なタイミングで、かつ具体的な修正方法を促すメッセージを提供するようになりました。これは、エラーメッセージの関連性を高め、ユーザー体験を向上させるための、Goのエラーハンドリングのベストプラクティスに沿った洗練された改善と言えます。
コアとなるコードの変更箇所
src/cmd/goinstall/download.go
--- a/src/cmd/goinstall/download.go
+++ b/src/cmd/goinstall/download.go
@@ -367,6 +367,14 @@ func (v *vcs) findURL(root string) (string, error) {
var oldGoogleRepo = regexp.MustCompile(`^([a-z0-9\\-]+)\\.googlecode\\.com/(svn|git|hg)(/[a-z0-9A-Z_.\\-/]+)?$`)
+type errOldGoogleRepo struct {
+ fixedPath string
+}
+
+func (e *errOldGoogleRepo) Error() string {
+ return fmt.Sprintf("unsupported import path; should be %q", e.fixedPath)
+}
+
// download checks out or updates the specified package from the remote server.
func download(importPath, srcDir string) (public bool, err error) {
if strings.Contains(importPath, "..") {
@@ -376,11 +384,7 @@ func download(importPath, srcDir string) (public bool, err error) {
if m := oldGoogleRepo.FindStringSubmatch(importPath); m != nil {
fixedPath := "code.google.com/p/" + m[1] + m[3]
- err = fmt.Errorf(
- "unsupported import path; should be %q\\n"+
- "\tRun goinstall with -fix to gofix the code.",
- fixedPath,
- )
+ err = &errOldGoogleRepo{fixedPath}
return
}
src/cmd/goinstall/main.go
--- a/src/cmd/goinstall/main.go
+++ b/src/cmd/goinstall/main.go
@@ -249,6 +249,10 @@ func install(pkg, parent string) error {
printf("%s: download\\n", pkg)
public, err = download(pkg, tree.SrcDir())
if err != nil {
+ // only suggest -fix if the bad import was not on the command line
+ if e, ok := err.(*errOldGoogleRepo); ok && parent != "" {
+ err = fmt.Errorf("%v\\nRun goinstall with -fix to gofix the code.", e)
+ }
return &DownloadError{pkg, tree.Goroot, err}
}
} else {
コアとなるコードの解説
src/cmd/goinstall/download.go
の変更点詳細
-
errOldGoogleRepo
型の追加: この新しいGoの構造体型は、Go言語におけるカスタムエラーの典型的な実装パターンを示しています。fixedPath
という文字列フィールドを持ち、これは古いGoogle Codeのインポートパスが検出された際に、そのパッケージの正しい新しいインポートパスを保持するために使用されます。func (e *errOldGoogleRepo) Error() string
メソッドの実装により、この構造体はGoの組み込みerror
インターフェースを満たします。このメソッドは、エラーが発生した際に表示される基本的なメッセージ(例:"unsupported import path; should be \"code.google.com/p/project\""
)を生成します。この段階では、まだ-fix
オプションに関する具体的な提案は含まれていません。これは、エラーの「種類」と「修正後のパス」という純粋な情報のみをカプセル化することを目的としています。 -
download
関数内のエラー生成ロジックの変更:download
関数は、Goパッケージのソースコードをリモートリポジトリから取得する主要なロジックを含んでいます。この関数内で、oldGoogleRepo
という正規表現(project.googlecode.com/...
のような古い形式のインポートパスを検出するためのもの)が、現在処理中のimportPath
にマッチした場合の処理が変更されました。 変更前は、fmt.Errorf
を直接使用して、修正後のパスと-fix
オプションの実行を促すメッセージを組み合わせた、単一の文字列エラーを生成していました。この方法はシンプルですが、エラーの具体的な「種類」をプログラム的に識別することが困難でした。 変更後は、err = &errOldGoogleRepo{fixedPath}
という行により、errOldGoogleRepo
型の新しいインスタンスが作成され、そのfixedPath
フィールドに正しいインポートパスが設定されます。そして、このカスタムエラーインスタンスがdownload
関数の戻り値として返されます。これにより、エラーハンドリングを行う呼び出し元(この場合はinstall
関数)は、返されたエラーが単なる文字列エラーではなく、特定の意味を持つerrOldGoogleRepo
型のエラーであることを型アサーションによって識別できるようになります。
src/cmd/goinstall/main.go
の変更点詳細
install
関数内のエラーハンドリングロジックの改善:install
関数は、goinstall
コマンドの主要なインストールフローを管理し、その中でdownload
関数を呼び出します。download
関数からエラーが返された場合、このinstall
関数がそのエラーを処理します。 変更されたエラー処理ブロックは、Goの強力な型アサーション機能と条件分岐を組み合わせています。if err != nil { // download関数からエラーが返された場合 // only suggest -fix if the bad import was not on the command line // エラーがerrOldGoogleRepo型であり、かつ、そのパッケージがコマンドラインで直接指定されたものではない場合のみ-fixを提案 if e, ok := err.(*errOldGoogleRepo); ok && parent != "" { // errOldGoogleRepoのエラーメッセージに、-fixの提案を追加して新しいエラーを生成 err = fmt.Errorf("%v\nRun goinstall with -fix to gofix the code.", e) } // 最終的なエラーをDownloadErrorとしてラップして返す return &DownloadError{pkg, tree.Goroot, err} }
if e, ok := err.(*errOldGoogleRepo); ok
: これはGoの型アサーションです。download
関数から返されたerr
が*errOldGoogleRepo
型であるかどうかをチェックします。もしそうであれば、ok
はtrue
となり、err
の値はe
という*errOldGoogleRepo
型の変数に代入されます。これにより、エラーの具体的な内容(fixedPath
など)にアクセスできるようになります。parent != ""
: この条件は、現在処理中のパッケージが、コマンドラインで直接指定されたものではなく、他のパッケージの依存関係としてダウンロードされているかどうかを判断します。parent
変数は、親パッケージのインポートパスを保持しており、もしparent
が空文字列 (""
) であれば、それは最上位の(コマンドラインで指定された)パッケージであることを意味します。if
文の条件が両方とも真(つまり、古いGoogle Codeのインポートパスに関するエラーが依存関係で発生した場合)であれば、fmt.Errorf("%v\nRun goinstall with -fix to gofix the code.", e)
という行が実行されます。ここで、%v
フォーマット動詞はe
(*errOldGoogleRepo
型)のError()
メソッドを呼び出し、その結果(例:"unsupported import path; should be \"code.google.com/p/project\""
)を取得します。その後に、具体的な-fix
オプションの実行を促すメッセージが追加され、この新しいエラーがerr
変数に再代入されます。
このロジックにより、goinstall
は、ユーザーが直接指定したインポートパスに対しては、単に「このパスはサポートされていません、修正後のパスはこれです」というメッセージのみを表示し、冗長な -fix
の提案は行いません。しかし、依存関係の解決中に古いインポートパスが検出された場合には、より詳細なエラーメッセージと、自動修正のための -fix
オプションの実行を促すガイダンスを提供します。これは、ユーザーが直面している問題の文脈に応じて、最も役立つ情報を提供するという点で、ユーザーインターフェースとエラー報告の品質を大幅に向上させる変更です。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語の
gofix
コマンドに関するドキュメント: https://go.dev/cmd/gofix/ - Google Code (現在は閉鎖されていますが、当時のGoプロジェクトのホスティングに利用されていました): https://code.google.com/
参考にした情報源リンク
- Go言語の公式GitHubリポジトリ: https://github.com/golang/go
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/5495073
は、このGerritシステム上の変更リストへのリンクです。)