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

[インデックス 16548] ファイルの概要

このコミットは、Go言語のツールチェインの一部であるcmd/goパッケージ内のbuild.goファイルに対する変更です。具体的には、Goのコードカバレッジツール(go tool cover)の呼び出し方法が更新され、その出力処理が改善されています。

コミット

commit bc7e26621e12796ca9284e3fc3b4d91761c221c6
Author: Rob Pike <r@golang.org>
Date:   Tue Jun 11 20:47:35 2013 -0700

    cmd/go: use -o option of cover tool
    Separates correct from erroneous output so errors running the tool will appear
    in the log.

    R=rsc
    CC=golang-dev
    https://golang.org/cl/10191043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/bc7e26621e12796ca9284e3fc3b4d91761c221c6

元コミット内容

cmd/go: use -o option of cover tool Separates correct from erroneous output so errors running the tool will appear in the log.

このコミットは、go tool coverコマンドの-oオプションを使用するように変更することで、ツールの正常な出力とエラー出力を分離し、エラーがログに明確に表示されるようにすることを目的としています。

変更の背景

Go言語のgo tool coverは、ソースコードにカバレッジ計測用のコードを挿入し、その結果を新しいソースファイルとして出力するツールです。このツールは、テスト実行時にどのコードが実行されたかを追跡するために使用されます。

このコミット以前は、go tool coverの出力は標準出力(stdout)に直接書き込まれていました。goコマンドがこのツールを実行する際、b.runOutというヘルパー関数を使用してgo tool coverの標準出力をキャプチャし、その内容をioutil.WriteFileで目的のファイルに書き込んでいました。

この方式の問題点は、go tool coverが正常な処理結果(カバレッジ計測コードが挿入されたソースコード)を出力する場合も、何らかのエラーが発生した場合の診断メッセージを出力する場合も、すべて同じ標準出力ストリームに混在してしまうことでした。これにより、goコマンドの実行ログを解析する際に、ツールの実行が成功したのか、それともエラーが発生したのかを判別しにくくなる可能性がありました。特に、エラーメッセージが大量の正常な出力の中に埋もれてしまうと、問題の特定が困難になります。

この問題を解決するため、go tool coverが提供する-oオプションを利用する変更が導入されました。-oオプションは、ツールの出力を標準出力ではなく、指定されたファイルに直接書き込む機能を提供します。これにより、go tool coverの正常な出力は指定されたファイルに送られ、エラーメッセージや警告は引き続き標準エラー出力(stderr)に送られるようになります。結果として、goコマンドの実行ログにはエラーメッセージのみが残り、ツールの実行状態をより明確に把握できるようになります。

前提知識の解説

  1. go tool cover: Go言語の標準ツールチェーンに含まれるコマンドで、ソースコードのカバレッジを計測するために使用されます。具体的には、Goのソースファイルを受け取り、各ステートメントの実行回数を記録するためのコード(プロファイリングフック)を挿入した新しいソースファイルを生成します。この生成されたファイルをコンパイルして実行することで、テストがどのコードパスをカバーしたかを後で分析できます。

    • -mode: カバレッジの計測モードを指定します(例: set, count, atomic)。
    • -count: カバレッジ計測用のカウンタ変数の名前を指定します。
    • -pos: カバレッジ計測用の位置情報変数の名前を指定します。
    • -o: 出力ファイルを指定します。このオプションが指定されない場合、go tool coverは結果を標準出力に書き込みます。
  2. 標準入出力 (Standard I/O): Unix系システムにおけるプログラムの基本的な入出力メカニズムです。

    • 標準入力 (stdin): プログラムがデータを読み込むためのデフォルトの入力ストリーム。通常はキーボードに接続されています。
    • 標準出力 (stdout): プログラムが正常な結果を出力するためのデフォルトの出力ストリーム。通常はターミナルに表示されます。
    • 標準エラー出力 (stderr): プログラムがエラーメッセージや診断情報を出力するためのデフォルトの出力ストリーム。通常はターミナルに表示されますが、stdoutとは独立しており、リダイレクトなどで分離して扱うことができます。
  3. os.FileMode: Go言語のosパッケージで定義されている型で、ファイルのパーミッション(読み取り、書き込み、実行権限など)を表します。

  4. ioutil.WriteFile (Go 1.16でos.WriteFileに移行): Go言語のio/ioutilパッケージ(現在はosパッケージに統合)の関数で、指定されたバイトスライスをファイルに書き込みます。ファイルが存在しない場合は作成し、存在する場合は上書きします。

  5. builder構造体とrun/runOutメソッド: Goのビルドプロセスを管理する内部的な構造体とメソッドです。

    • b.runOut(dir, cmd, env, args...): 指定されたディレクトリでコマンドを実行し、その標準出力をバイトスライスとして返します。コマンドの実行中にエラーが発生した場合、エラーを返します。
    • b.run(dir, cmd, env, args...): 指定されたディレクトリでコマンドを実行します。runOutとは異なり、標準出力をキャプチャせず、コマンドの実行が成功したかどうかのエラーのみを返します。

技術的詳細

このコミットの技術的な核心は、src/cmd/go/build.goファイル内のbuilder構造体のcoverメソッドの変更にあります。

変更前は、coverメソッドはb.runOutを呼び出してgo tool coverを実行していました。b.runOutは実行されたコマンドの標準出力をout変数にキャプチャし、そのout変数の内容をioutil.WriteFileを使ってdst(出力ファイルパス)に書き込んでいました。

// 変更前
func (b *builder) cover(a *action, dst, src string, perm os.FileMode, count, pos string) error {
	// go tool cover の標準出力をキャプチャ
	out, err := b.runOut(a.objdir, "cover "+a.p.ImportPath, nil, tool("cover"), "-mode="+a.p.coverMode, "-count="+count, "-pos="+pos, src)
	if err != nil {
		return err // エラーがあれば即座に返す
	}
	// キャプチャした出力をファイルに書き込む
	return ioutil.WriteFile(dst, out, perm)
}

この方式では、go tool coverがエラーメッセージを標準出力に書き込んだ場合、そのエラーメッセージもout変数にキャプチャされ、dstファイルに書き込まれてしまう可能性がありました。また、b.runOutはコマンドの終了コードに基づいてエラーを検出しますが、標準出力に混在するエラーメッセージを区別することはできません。

変更後は、coverメソッドはb.runを呼び出すように変更され、go tool coverコマンドの引数に新たに-o dstが追加されました。

// 変更後
func (b *builder) cover(a *action, dst, src string, perm os.FileMode, count, pos string) error {
	// go tool cover に -o オプションを渡し、出力を直接 dst ファイルに書き込ませる
	return b.run(a.objdir, "cover "+a.p.ImportPath, nil,
		tool("cover"),
		"-mode", a.p.coverMode,
		"-count", count,
		"-pos", pos,
		"-o", dst, // ここが新しい引数
		src)
}

この変更により、go tool coverはカバレッジ計測コードが挿入されたソースコードを直接dstファイルに書き込むようになります。b.runはコマンドの標準出力をキャプチャしないため、go tool coverが標準エラー出力に書き込むエラーメッセージは、b.runの呼び出し元(goコマンドのログシステムなど)によって適切に処理され、ログに記録されるようになります。これにより、正常な出力とエラー出力が明確に分離され、エラーの検出と診断が容易になります。

引数の渡し方も、"-mode="+a.p.coverModeのような単一の文字列から、"-mode", a.p.coverModeのようにオプションと値を別々の引数として渡す形式に変更されています。これは、コマンドライン引数のパースをより堅牢にするための一般的なプラクティスです。

コアとなるコードの変更箇所

src/cmd/go/build.goファイルのcover関数が変更されています。

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -1110,14 +1110,15 @@ func (b *builder) copyFile(a *action, dst, src string, perm os.FileMode) error {
 }
 
 // cover runs, in effect,
-//	go tool cover -mode=b.coverMode -count="count" -pos="pos" src.go >dst.go
+//	go tool cover -mode=b.coverMode -count="count" -pos="pos" -o dst.go src.go
 func (b *builder) cover(a *action, dst, src string, perm os.FileMode, count, pos string) error {
-	out, err := b.runOut(a.objdir, "cover "+a.p.ImportPath, nil, tool("cover"), "-mode="+a.p.coverMode, "-count="+count, "-pos="+pos, src)
-	if err != nil {
-		return err
-	}
-	// Output is processed source code. Write it to destination.
-	return ioutil.WriteFile(dst, out, perm)
+	return b.run(a.objdir, "cover "+a.p.ImportPath, nil,
+		tool("cover"),
+		"-mode", a.p.coverMode,
+		"-count", count,
+		"-pos", pos,
+		"-o", dst,
+		src)
 }
 
 var objectMagic = [][]byte{

コアとなるコードの解説

変更の核心は、cover関数内でgo tool coverコマンドを実行する方法の変更です。

  1. b.runOutからb.runへの変更:

    • 変更前: out, err := b.runOut(...) を使用していました。runOutは実行したコマンドの標準出力をout変数にキャプチャします。
    • 変更後: return b.run(...) を使用しています。runはコマンドの標準出力をキャプチャせず、コマンドの実行が成功したかどうかのエラーのみを返します。これにより、go tool coverの正常な出力がgoコマンドの内部で一時的に保持される必要がなくなり、直接ファイルに書き込まれるようになります。
  2. -o dstオプションの追加:

    • 変更前: go tool coverの出力は標準出力に送られ、それをioutil.WriteFile(dst, out, perm)でファイルに書き込んでいました。
    • 変更後: go tool coverの引数リストに "-o", dst が追加されました。これにより、go tool cover自身が、カバレッジ計測コードが挿入されたソースコードを直接dstパスのファイルに書き込むようになります。
  3. ioutil.WriteFileの削除: go tool coverが直接ファイルに書き込むようになったため、以前のioutil.WriteFileによる書き込み処理は不要となり、削除されました。

これらの変更により、go tool coverの正常な出力は-oオプションによって指定されたファイルに直接送られ、エラーや警告メッセージは標準エラー出力に分離されます。これにより、goコマンドのログからエラーをより容易に識別できるようになり、デバッグや自動化されたビルドシステムの信頼性が向上します。

関連リンク

  • Go言語の公式ドキュメント: https://golang.org/
  • go tool coverに関するドキュメント(Goのバージョンによって異なる場合がありますが、基本的な機能は共通です):
    • go doc cmd/go または go help cover コマンドで詳細を確認できます。
    • Goのソースコードリポジトリ: https://github.com/golang/go

参考にした情報源リンク