[インデックス 12656] ファイルの概要
コミット
このコミットは、Go言語のコマンドラインツール cmd/go
におけるビルドエラーの表示方法を改善することを目的としています。具体的には、エラーメッセージがより明確に、そしてユーザーにとって理解しやすい形で表示されるように変更が加えられました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7a84fb3a85366a33fb14695263b3340d3a4d7fa7
元コミット内容
commit 7a84fb3a85366a33fb14695263b3340d3a4d7fa7
Author: Russ Cox <rsc@golang.org>
Date: Thu Mar 15 17:35:57 2012 -0400
cmd/go: make build errors more visible
Fixes #3324.
Robert suggested not reporting errors until the end of the output.
which I'd also like to do, but errPrintedOutput makes that a bigger
change than I want to do before Go 1. This change should at least
remove the confusion we had.
# Building packages and commands for linux/amd64.
runtime
errors
sync/atomic
unicode
unicode/utf8
math
sync
unicode/utf16
crypto/subtle
io
syscall
hash
crypto
crypto/md5
hash/crc32
crypto/cipher
crypto/hmac
crypto/sha1
go install unicode: copying /tmp/go-build816525784/unicode.a to /home/rsc/g/go/pkg/linux_amd64/unicode.a: short write
hash/adler32
container/list
container/ring
...
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5837054
変更の背景
このコミットは、Go言語のIssue #3324を修正するために行われました。当時のgo build
コマンドは、ビルド中にエラーが発生した場合、そのエラーメッセージが他のビルド出力(例えば、コンパイル中のパッケージ名リスト)の中に埋もれてしまい、ユーザーがエラーを見落としやすかったという問題がありました。
コミットメッセージによると、Robertという人物が「エラーを全ての出力の最後に報告する」という提案をしており、コミッターのRuss Coxもそのアイデアに賛同しています。しかし、errPrintedOutput
という既存のメカニズムがその変更をGo 1リリース前に行うには大きすぎる変更であると判断されました。そのため、このコミットでは、より限定的な変更として、少なくとも既存の混乱を解消し、エラーメッセージをより目立たせることに焦点を当てています。
具体的には、go install
やgo build
の実行中に発生するファイルコピーエラーやディレクトリ読み取りエラーなどが、単に「short write」や「permission denied」といったメッセージで表示されるだけでなく、どのコマンド(go build
やgo install
)が、どのパッケージ(ImportPath
)で、どのようなエラー(元のエラーメッセージ)が発生したのかを明示的に示すように改善されています。これにより、ユーザーはエラーの原因と発生箇所を迅速に特定できるようになります。
前提知識の解説
- Go言語のビルドシステム (
cmd/go
): Go言語には、ソースコードのコンパイル、パッケージの管理、テストの実行などを行うための公式コマンドラインツールgo
があります。go build
は実行可能ファイルをビルドし、go install
はビルドしたパッケージやコマンドをGOPATHのpkg
やbin
ディレクトリにインストールします。 - パッケージのインポートパス (ImportPath): Go言語では、パッケージはファイルシステム上のパスと対応するインポートパスによって識別されます。例えば、
"fmt"
は標準ライブラリのフォーマットパッケージを指し、"github.com/user/repo/mypackage"
は外部のパッケージを指します。 - エラーハンドリング (Error Handling in Go): Go言語では、エラーは
error
インターフェースを実装する値として扱われます。関数は通常、最後の戻り値としてerror
を返します。呼び出し元は、このerror
がnil
でない場合にエラーが発生したと判断し、適切に処理します。fmt.Errorf
は、フォーマットされた文字列から新しいerror
値を生成するための関数です。 defer
ステートメント: Go言語のdefer
ステートメントは、それを囲む関数がreturnする直前に、指定された関数呼び出しを延期します。これは、リソースの解放(ファイルのクローズ、ロックの解除など)や、エラーハンドリングの共通処理を記述するのに非常に便利です。このコミットでは、defer
を使ってエラーが発生した場合に共通のエラーメッセージの整形処理を行うことで、コードの重複を避け、一貫したエラー報告を実現しています。os.FileMode
: Unix系のファイルシステムにおけるファイルパーミッション(読み取り、書き込み、実行権限など)を表す型です。
技術的詳細
このコミットの主要な変更点は、cmd/go
のビルドおよびインストールプロセスにおけるエラー報告の改善です。具体的には、以下の関数に修正が加えられています。
-
builder.build
関数 (src/cmd/go/build.go
):- この関数は、単一のパッケージまたはコマンドをビルドするアクションを担当します。
- 変更前は
func (b *builder) build(a *action) error
というシグネチャでしたが、変更後はfunc (b *builder) build(a *action) (err error)
となり、名前付き戻り値err
が導入されました。 defer
ステートメントが追加され、関数が終了する際にerr
がnil
でない場合、fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
という形式でエラーメッセージを整形するようになりました。これにより、どのgo build
コマンドが、どのインポートパスでエラーを起こしたのかが明確になります。
-
builder.install
関数 (src/cmd/go/build.go
):- この関数は、単一のパッケージまたは実行可能ファイルをインストールするアクションを担当します。
build
関数と同様に、名前付き戻り値err
が導入され、defer
ステートメントが追加されました。- エラーが発生した場合、
fmt.Errorf("go install %s: %v", a.p.ImportPath, err)
という形式でエラーメッセージが整形されます。これにより、どのgo install
コマンドが、どのインポートパスでエラーを起こしたのかが明確になります。
-
builder.copyFile
関数 (src/cmd/go/build.go
):- この関数は、ファイルをコピーする際に使用されます。
- ファイルコピー中にエラーが発生した場合、以前は単に元のエラー
err
を返していました。 - 変更後は、
fmt.Errorf("copying %s to %s: %v", src, dst, err)
という形式でエラーメッセージを整形するようになりました。これにより、どのファイルをどこにコピーしようとしてエラーが発生したのかが具体的に示されます。
-
clean
関数 (src/cmd/go/clean.go
):- この関数は、
go clean
コマンドの処理を担当します。 - ディレクトリの読み取り中にエラーが発生した場合、以前は
errorf("%v", err)
とだけ出力していました。 - 変更後は、
errorf("go clean %s: %v", p.Dir, err)
という形式でエラーメッセージを整形するようになりました。これにより、どのgo clean
コマンドが、どのディレクトリでエラーを起こしたのかが明確になります。
- この関数は、
これらの変更により、Goのビルドツールが報告するエラーメッセージは、単なる技術的なエラーコードや短い説明だけでなく、そのエラーがどの操作(go build
、go install
、go clean
、ファイルコピーなど)の、どの対象(パッケージのインポートパス、ファイルパス、ディレクトリパスなど)で発生したのかを明示的に示すようになります。これは、ユーザーがエラーをデバッグし、問題を解決する上で非常に役立ちます。
コアとなるコードの変更箇所
src/cmd/go/build.go
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -604,7 +604,12 @@ func (b *builder) do(root *action) {
}
// build is the action for building a single package or command.
-func (b *builder) build(a *action) error {
+func (b *builder) build(a *action) (err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
+ }
+ }()
if buildN {
// In -n mode, print a banner between packages.
// The banner is five lines so that when changes to
@@ -753,7 +758,12 @@ func (b *builder) build(a *action) error {
}
// install is the action for installing a single package or executable.
-func (b *builder) install(a *action) error {
+func (b *builder) install(a *action) (err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("go install %s: %v", a.p.ImportPath, err)
+ }
+ }()
a1 := a.deps[0]
perm := os.FileMode(0666)
if a1.link {
@@ -874,7 +884,7 @@ func (b *builder) copyFile(a *action, dst, src string, perm os.FileMode) error {
df.Close()
if err != nil {
os.Remove(dst)
- return err
+ return fmt.Errorf("copying %s to %s: %v", src, dst, err)
}
return nil
}
src/cmd/go/clean.go
--- a/src/cmd/go/clean.go
+++ b/src/cmd/go/clean.go
@@ -110,7 +110,7 @@ func clean(p *Package) {
}
dirs, err := ioutil.ReadDir(p.Dir)
if err != nil {
- errorf("%v", err)
+ errorf("go clean %s: %v", p.Dir, err)
return
}
コアとなるコードの解説
上記の変更箇所は、Go言語のdefer
ステートメントとfmt.Errorf
関数を効果的に利用して、エラーメッセージのコンテキストを強化しています。
-
build
およびinstall
関数の変更:- これらの関数は、戻り値として
error
を返すように変更され、そのerror
は名前付き戻り値err
として宣言されています。 defer func() { ... }()
という形式で無名関数がdefer
されています。この無名関数は、build
またはinstall
関数が正常終了するか、エラーで終了するかにかかわらず、常に実行されます。if err != nil
のチェックにより、関数内でエラーが発生した場合にのみ、エラーメッセージの整形処理が実行されます。err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
(またはgo install
の場合)という行が重要です。fmt.Errorf
は、新しいエラー値を生成します。"go build %s: %v"
というフォーマット文字列は、エラーがgo build
コマンドによって発生したこと、%s
プレースホルダーにはa.p.ImportPath
(エラーが発生したパッケージのインポートパス)が挿入されること、そして%v
プレースホルダーには元のエラーerr
の詳細が挿入されることを示しています。- これにより、例えば「
go build unicode: copying /tmp/go-build...: short write
」のような、より具体的で分かりやすいエラーメッセージが生成されます。
- これらの関数は、戻り値として
-
copyFile
関数の変更:- ファイルコピー中にエラーが発生した場合、
return fmt.Errorf("copying %s to %s: %v", src, dst, err)
という行が実行されます。 - これは、どの
src
ファイルからどのdst
ファイルへのコピー中にエラーが発生したのかを明示的に示すメッセージを生成します。例えば、「copying /path/to/source.go to /path/to/destination.go: permission denied
」のようなメッセージになります。
- ファイルコピー中にエラーが発生した場合、
-
clean
関数の変更:- ディレクトリの読み取り中にエラーが発生した場合、
errorf("go clean %s: %v", p.Dir, err)
という行が実行されます。 - これは、
go clean
コマンドがどのディレクトリでエラーを起こしたのかを明確にします。例えば、「go clean /home/user/project: no such file or directory
」のようなメッセージになります。
- ディレクトリの読み取り中にエラーが発生した場合、
これらの変更は、Goのエラーハンドリングの慣習に沿っており、エラーが発生した場所と原因をより詳細に伝えることで、開発者のデバッグ体験を大幅に向上させます。
関連リンク
- Go Issue #3324: https://code.google.com/p/go/issues/detail?id=3324 (古いGoのIssueトラッカーのリンクですが、当時の議論の背景を理解するのに役立ちます)
- Go言語の
defer
ステートメントに関する公式ドキュメント: https://go.dev/tour/flowcontrol/12 - Go言語の
fmt.Errorf
に関する公式ドキュメント: https://pkg.go.dev/fmt#Errorf
参考にした情報源リンク
- コミットハッシュ: 7a84fb3a85366a33fb14695263b3340d3a4d7fa7
- GitHubコミットページ: https://github.com/golang/go/commit/7a84fb3a85366a33fb14695263b3340d3a4d7fa7
- Go言語の公式ドキュメント (エラーハンドリング、deferなど): https://go.dev/
- Go言語のIssueトラッカー (当時のIssue #3324の議論): https://code.google.com/p/go/issues/detail?id=3324
- Go言語の
cmd/go
ソースコード: https://github.com/golang/go/tree/master/src/cmd/go