Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 installgo buildの実行中に発生するファイルコピーエラーやディレクトリ読み取りエラーなどが、単に「short write」や「permission denied」といったメッセージで表示されるだけでなく、どのコマンド(go buildgo install)が、どのパッケージ(ImportPath)で、どのようなエラー(元のエラーメッセージ)が発生したのかを明示的に示すように改善されています。これにより、ユーザーはエラーの原因と発生箇所を迅速に特定できるようになります。

前提知識の解説

  • Go言語のビルドシステム (cmd/go): Go言語には、ソースコードのコンパイル、パッケージの管理、テストの実行などを行うための公式コマンドラインツールgoがあります。go buildは実行可能ファイルをビルドし、go installはビルドしたパッケージやコマンドをGOPATHのpkgbinディレクトリにインストールします。
  • パッケージのインポートパス (ImportPath): Go言語では、パッケージはファイルシステム上のパスと対応するインポートパスによって識別されます。例えば、"fmt"は標準ライブラリのフォーマットパッケージを指し、"github.com/user/repo/mypackage"は外部のパッケージを指します。
  • エラーハンドリング (Error Handling in Go): Go言語では、エラーはerrorインターフェースを実装する値として扱われます。関数は通常、最後の戻り値としてerrorを返します。呼び出し元は、このerrornilでない場合にエラーが発生したと判断し、適切に処理します。fmt.Errorfは、フォーマットされた文字列から新しいerror値を生成するための関数です。
  • deferステートメント: Go言語のdeferステートメントは、それを囲む関数がreturnする直前に、指定された関数呼び出しを延期します。これは、リソースの解放(ファイルのクローズ、ロックの解除など)や、エラーハンドリングの共通処理を記述するのに非常に便利です。このコミットでは、deferを使ってエラーが発生した場合に共通のエラーメッセージの整形処理を行うことで、コードの重複を避け、一貫したエラー報告を実現しています。
  • os.FileMode: Unix系のファイルシステムにおけるファイルパーミッション(読み取り、書き込み、実行権限など)を表す型です。

技術的詳細

このコミットの主要な変更点は、cmd/goのビルドおよびインストールプロセスにおけるエラー報告の改善です。具体的には、以下の関数に修正が加えられています。

  1. builder.build 関数 (src/cmd/go/build.go):

    • この関数は、単一のパッケージまたはコマンドをビルドするアクションを担当します。
    • 変更前はfunc (b *builder) build(a *action) errorというシグネチャでしたが、変更後はfunc (b *builder) build(a *action) (err error)となり、名前付き戻り値errが導入されました。
    • deferステートメントが追加され、関数が終了する際にerrnilでない場合、fmt.Errorf("go build %s: %v", a.p.ImportPath, err)という形式でエラーメッセージを整形するようになりました。これにより、どのgo buildコマンドが、どのインポートパスでエラーを起こしたのかが明確になります。
  2. builder.install 関数 (src/cmd/go/build.go):

    • この関数は、単一のパッケージまたは実行可能ファイルをインストールするアクションを担当します。
    • build関数と同様に、名前付き戻り値errが導入され、deferステートメントが追加されました。
    • エラーが発生した場合、fmt.Errorf("go install %s: %v", a.p.ImportPath, err)という形式でエラーメッセージが整形されます。これにより、どのgo installコマンドが、どのインポートパスでエラーを起こしたのかが明確になります。
  3. builder.copyFile 関数 (src/cmd/go/build.go):

    • この関数は、ファイルをコピーする際に使用されます。
    • ファイルコピー中にエラーが発生した場合、以前は単に元のエラーerrを返していました。
    • 変更後は、fmt.Errorf("copying %s to %s: %v", src, dst, err)という形式でエラーメッセージを整形するようになりました。これにより、どのファイルをどこにコピーしようとしてエラーが発生したのかが具体的に示されます。
  4. clean 関数 (src/cmd/go/clean.go):

    • この関数は、go cleanコマンドの処理を担当します。
    • ディレクトリの読み取り中にエラーが発生した場合、以前はerrorf("%v", err)とだけ出力していました。
    • 変更後は、errorf("go clean %s: %v", p.Dir, err)という形式でエラーメッセージを整形するようになりました。これにより、どのgo cleanコマンドが、どのディレクトリでエラーを起こしたのかが明確になります。

これらの変更により、Goのビルドツールが報告するエラーメッセージは、単なる技術的なエラーコードや短い説明だけでなく、そのエラーがどの操作(go buildgo installgo 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のエラーハンドリングの慣習に沿っており、エラーが発生した場所と原因をより詳細に伝えることで、開発者のデバッグ体験を大幅に向上させます。

関連リンク

参考にした情報源リンク