[インデックス 11519] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go
に対する複数の改善をまとめています。主な変更点として、go run
コマンドでのビルドエラー出力の改善、go test
コマンドでのテスト出力のストリーミング化、テスト依存関係をインストールするための go test -i
オプションの追加、exitStatus
におけるデータ競合の修正、およびツールパスの修正が含まれます。これらの改善は、Go開発者の体験を向上させ、ツールの信頼性と使いやすさを高めることを目的としています。
コミット
commit 64a73b0355ade719894894a4d192fbd6207e4387
Author: Russ Cox <rsc@golang.org>
Date: Tue Jan 31 15:08:20 2012 -0500
cmd/go: improvements
Print build errors to stderr during 'go run'.
Stream test output during 'go test' (no args). Fixes issue 2731.
Add go test -i to install test dependencies. Fixes issue 2685.
Fix data race in exitStatus. Fixes issue 2709.
Fix tool paths. Fixes issue 2817.
R=golang-dev, bradfitz, n13m3y3r, r
CC=golang-dev
https://golang.org/cl/5591045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/64a73b0355ade719894894a4d192fbd6207e4387
元コミット内容
このコミットは、cmd/go
ツールに対する以下の改善を実装しています。
go run
実行中に発生したビルドエラーを標準エラー出力 (stderr) に出力するように変更。- 引数なしの
go test
実行時にテスト出力をストリーミングするよう変更。これにより、Issue 2731 が修正されます。 - テスト依存関係をインストールするための
go test -i
オプションを追加。これにより、Issue 2685 が修正されます。 exitStatus
変数におけるデータ競合を修正。これにより、Issue 2709 が修正されます。- ツールパスの解決方法を修正。これにより、Issue 2817 が修正されます。
変更の背景
このコミットは、Go言語の公式ツールチェーンの中核である cmd/go
のユーザビリティ、信頼性、およびパフォーマンスを向上させることを目的としています。具体的には、以下の問題に対処しています。
go run
のエラー出力の改善: 以前はgo run
でビルドエラーが発生した場合、その出力が標準出力 (stdout) に混ざってしまうことがあり、エラーメッセージの識別が困難でした。これを標準エラー出力に統一することで、エラー処理の標準的な慣習に従い、スクリプトなどでのエラー解析を容易にします。go test
の出力ストリーミング: 大規模なテストスイートを実行する際、go test
がすべての出力をバッファリングしてから一度に表示すると、テストの進行状況が分かりにくく、特に長時間実行されるテストでは不便でした。出力をストリーミングすることで、テストのリアルタイムな進捗を把握できるようになります。これは Issue 2731 で報告された問題への対応です。- テスト依存関係の管理:
go test
を実行する際に、テストに必要なパッケージがインストールされていない場合、テストが失敗するか、手動でのインストールが必要でした。go test -i
オプションの導入により、テスト実行前に必要な依存関係を自動的にインストールできるようになり、開発ワークフローが簡素化されます。これは Issue 2685 で報告された問題への対応です。 exitStatus
のデータ競合:cmd/go
内部で、プログラムの終了ステータスを管理するexitStatus
変数へのアクセスが複数のゴルーチンから同時に行われる可能性があり、データ競合が発生していました。これにより、予期せぬ終了ステータスが返されるなどの不安定な挙動を引き起こす可能性がありました。これは Issue 2709 で報告された問題への対応です。- ツールパスの解決:
go tool
コマンドが内部ツール(例:go tool compile
,go tool link
など)を見つけるためのパス解決に問題があり、特定の環境でツールが見つからない、または誤ったツールが実行される可能性がありました。これは Issue 2817 で報告された問題への対応です。
これらの改善は、Go開発者がより効率的かつ安定して作業できる環境を提供するために不可欠でした。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語および関連ツールの基本的な概念を理解しておく必要があります。
cmd/go
: Go言語の公式コマンドラインツールであり、Goプログラムのビルド、テスト、実行、依存関係の管理など、Go開発における主要なタスクを実行します。- 標準出力 (stdout) と標準エラー出力 (stderr): プログラムが情報を出力するための2つの主要なストリームです。stdoutは通常のプログラム出力に使用され、stderrはエラーメッセージや診断情報に使用されます。シェルスクリプトなどでは、これらを別々にリダイレクトして処理することが一般的です。
- Goパッケージ (Package): Go言語におけるコードの再利用可能な単位です。関連するソースファイルがまとめられ、インポートパスによって識別されます。
go run
: Goソースファイルをコンパイルして実行するコマンドです。一時的な実行可能ファイルを生成し、それを実行します。go test
: Goパッケージのテストを実行するコマンドです。テスト関数を検出し、実行し、結果を報告します。- データ競合 (Data Race): 複数のゴルーチン(Goの軽量スレッド)が同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みである場合に発生するプログラミング上のバグです。データ競合は予測不能な動作やクラッシュを引き起こす可能性があります。Goでは
sync
パッケージのミューテックス (sync.Mutex
) などを使用してデータ競合を防ぎます。 sync.Mutex
: Goのsync
パッケージで提供される排他ロックプリミティブです。共有リソースへのアクセスを同期するために使用され、データ競合を防ぎます。Lock()
メソッドでロックを取得し、Unlock()
メソッドでロックを解放します。filepath.Join
: パス要素を結合して、オペレーティングシステムに適したパスを生成するGoの関数です。runtime.GOOS
とruntime.GOARCH
: Goプログラムが実行されているオペレーティングシステムとアーキテクチャを示す定数です。クロスコンパイルの際に重要になります。build.Context
: Goのビルドシステムがパッケージを検索し、ビルドするために使用する環境情報(GOOS, GOARCH, GOPATHなど)をカプセル化した構造体です。os/exec
パッケージ: 外部コマンドを実行するためのGoの標準ライブラリパッケージです。exec.Command
を使用してコマンドを構築し、Run()
やStart()
メソッドで実行します。- Issue Tracker: ソフトウェア開発プロジェクトでバグ報告、機能要求、タスクなどを追跡するために使用されるシステムです。Go言語プロジェクトでは、GitHub Issues (以前はGoの独自のIssue Tracker) が使用されています。
技術的詳細
このコミットは、cmd/go
の内部実装にいくつかの重要な変更を加えています。
-
go run
のエラー出力の変更 (src/cmd/go/run.go
,src/cmd/go/build.go
):builder
構造体にprint func(args ...interface{}) (int, error)
フィールドが追加されました。これにより、ビルド出力の書き込み先を柔軟に制御できるようになります。builder.init()
でb.print = fmt.Print
がデフォルトで設定されます。runRun
関数(go run
コマンドのエントリポイント)内で、b.print = printStderr
が設定されます。printStderr
はfmt.Fprint(os.Stderr, args...)
を呼び出す関数で、これによりgo run
のビルドエラーが標準エラー出力にリダイレクトされます。builder.showcmd
およびbuilder.showOutput
メソッドがfmt.Println
やfmt.Print
の代わりにb.print
を使用するように変更され、出力がprintStderr
にルーティングされるようになります。
-
go test
の出力ストリーミングと-i
オプション (src/cmd/go/test.go
,src/cmd/go/testflag.go
):testStreamOutput
という新しいブール型フラグが導入されました。これは、テスト出力をバッファリングせずにリアルタイムでストリーミングするかどうかを制御します。testStreamOutput
は、引数なしのgo test
(現在のディレクトリのテスト) またはベンチマーク (-bench
フラグ) が実行される場合にtrue
に設定されます。また、単一パッケージのテストで-v
フラグが指定されている場合もtrue
になります。builder.runTest
関数内で、cmd.Stdout
とcmd.Stderr
がtestStreamOutput
の値に基づいてos.Stdout
およびos.Stderr
に直接設定されるか、bytes.Buffer
にバッファリングされるかが決定されます。testI
(-i
フラグ) がtestflag.go
に追加され、go test -i
が有効になります。runTest
関数内でtestI
がtrue
の場合、テスト対象パッケージとそのテスト依存関係を収集し、それらのパッケージをmodeInstall
モードでビルドおよびインストールするロジックが追加されました。これにより、テスト実行前に依存関係が自動的にインストールされます。
-
exitStatus
のデータ競合修正 (src/cmd/go/main.go
,src/cmd/go/build.go
,src/cmd/go/test.go
,src/cmd/go/testflag.go
,src/cmd/go/tool.go
):main.go
にexitMu sync.Mutex
とsetExitStatus(n int)
関数が追加されました。setExitStatus
関数は、exitMu.Lock()
とexitMu.Unlock()
を使用してexitStatus
変数へのアクセスを同期します。これにより、複数のゴルーチンが同時にexitStatus
を変更しようとした際のデータ競合が防止されます。errorf
関数や、ビルドエラー、テスト失敗、ツール実行エラーなどでexitStatus
を設定するすべての箇所がsetExitStatus
を呼び出すように変更されました。
-
ツールパスの修正 (
src/cmd/go/build.go
,src/cmd/go/pkg.go
,src/cmd/go/tool.go
,src/cmd/go/fix.go
,src/cmd/go/vet.go
,src/cmd/go/get.go
):tool.go
にtool(name string)
ヘルパー関数が導入されました。この関数は、goroot/bin/go-tool
ディレクトリ内の指定されたツール名に対応する絶対パスを返します。Windows環境では.exe
拡張子も考慮されます。build.go
内のコンパイラ (gc
), アセンブラ (asm
), パッカー (pack
), リンカ (ld
), Cコンパイラ (cc
) などのツールパスが、ハードコードされたfilepath.Join(goroot, "bin/go-tool/", ...)
からtool(...)
ヘルパー関数を使用するように変更されました。fix.go
,get.go
,vet.go
内のgofix
やgovet
などのツール呼び出しもtool("fix")
やtool("vet")
を使用するように変更されました。pkg.go
では、クロスコンパイルされたバイナリのインストールパスの決定ロジックが簡素化され、runtime.GOOS
の代わりにtoolGOOS
が使用されるようになりました。tool.go
内のrunTool
およびlistTools
関数も、新しいtool
ヘルパー関数とtoolDir
変数を使用するように更新され、ツールパスの解決が一貫して行われるようになりました。
これらの変更は、Goツールチェーンの堅牢性と保守性を高め、クロスプラットフォームでの動作をよりスムーズにするためのものです。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、以下のファイルに集中しています。
src/cmd/go/build.go
:builder
構造体にprint func(args ...interface{}) (int, error)
フィールドを追加。builder.init()
でb.print = fmt.Print
を設定。builder.showcmd
とbuilder.showOutput
がb.print
を使用するように変更。setExitStatus(2)
の呼び出しを追加。toolIsWindows
の使用箇所を修正。goToolchain
の各ツール(gc
,asm
,pack
,ld
,cc
)のパス解決にtool(...)
ヘルパー関数を使用するように変更。b.goos != runtime.GOOS
をb.goos != toolGOOS
に変更。
src/cmd/go/main.go
:sync
パッケージをインポート。exitMu sync.Mutex
とsetExitStatus(n int)
関数を追加。errorf
およびfatalf
内でexitStatus = ...
の代わりにsetExitStatus(...)
を呼び出すように変更。
src/cmd/go/run.go
:fmt
とos
パッケージをインポート。printStderr
関数を追加 (fmt.Fprint(os.Stderr, args...)
を呼び出す)。runRun
関数内でb.print = printStderr
を設定。
src/cmd/go/test.go
:sort
パッケージをインポート。testI
(-i
フラグ) とtestStreamOutput
フラグを追加。testShowPass
のロジックを変更。testStreamOutput
の設定ロジックを追加。testI
がtrue
の場合の依存関係インストールロジックを追加。fmt.Fprintf(os.Stderr, "installing these packages with 'go test -i' will speed future tests.\\n\\n")
にメッセージを変更。builder.runTest
関数内で、cmd.Stdout
とcmd.Stderr
をtestStreamOutput
に応じてos.Stdout
/os.Stderr
またはbytes.Buffer
に設定するロジックを追加。setExitStatus(1)
の呼び出しを追加。
src/cmd/go/testflag.go
:testI
フラグの定義を追加。setBoolFlag(&testI, value)
の処理を追加。
src/cmd/go/tool.go
:runtime
パッケージをインポート。toolGOOS
,toolGOARCH
,toolIsWindows
,toolDir
変数をruntime
パッケージに基づいて初期化するように変更。tool(name string)
ヘルパー関数を追加。runTool
およびlistTools
関数内で、ツールパスの解決にtool(...)
ヘルパー関数を使用するように変更。setExitStatus(...)
の呼び出しを追加。
src/cmd/go/pkg.go
:runtime
パッケージのインポートを削除。- クロスコンパイルされたバイナリのインストールパス決定ロジックで
ctxt.GOOS != runtime.GOOS
をctxt.GOOS != toolGOOS
に変更。
src/cmd/go/fix.go
,src/cmd/go/get.go
,src/cmd/go/vet.go
:gofix
,govet
などのツール呼び出しをtool("fix")
,tool("vet")
などに変更。
コアとなるコードの解説
このコミットの核となる変更は、cmd/go
の内部でどのように出力が処理され、終了ステータスが管理され、ツールが発見されるかという点にあります。
-
柔軟な出力制御:
builder
構造体にprint
フィールドが追加されたことで、cmd/go
のビルドプロセスからの出力を、標準出力、標準エラー出力、またはその他の任意のio.Writer
に柔軟にリダイレクトできるようになりました。go run
の場合はprintStderr
関数を割り当てることで、ビルドエラーが確実に標準エラー出力に送られるようになります。これは、GoツールがよりUnixの哲学(エラーはstderrへ)に従うようにするための重要なステップです。 -
安全な終了ステータス管理:
exitStatus
変数へのアクセスをsync.Mutex
で保護するsetExitStatus
関数が導入されたことは、Goの並行処理モデルにおけるデータ競合の典型的な解決策です。cmd/go
のような複雑なツールでは、複数の並行タスク(例えば、複数のパッケージのビルドやテスト)が同時に実行される可能性があり、それぞれが終了ステータスに影響を与える可能性があります。ミューテックスを使用することで、exitStatus
の値が常に一貫性を保ち、予測可能な終了コードが返されることが保証されます。 -
一貫したツールパス解決:
tool(name string)
ヘルパー関数とtoolGOOS
,toolGOARCH
の導入は、Goツールチェーン内の他のツール(コンパイラ、リンカ、アセンブラなど)へのパス解決を中央集権化し、より堅牢にするものです。以前は、これらのパスが複数の場所でハードコードされていたため、変更やクロスコンパイル環境での問題が発生しやすかったのですが、この変更により、すべてのツールがgoroot/bin/go-tool
ディレクトリから一貫した方法で発見されるようになります。これにより、Goツールチェーンの内部構造がより整理され、将来のメンテナンスが容易になります。 -
go test
のユーザビリティ向上:go test -i
オプションは、開発者のワークフローを大幅に改善します。テストの実行前に手動で依存関係をインストールする手間が省け、特にCI/CD環境や新しい開発環境のセットアップ時に有用です。また、testStreamOutput
フラグによるテスト出力のストリーミングは、大規模なテストスイートの実行時にリアルタイムのフィードバックを提供し、開発者がテストの進行状況をよりよく理解できるようにします。これは、開発体験を向上させるための細やかながらも重要な改善です。
これらの変更は、Goツールチェーンの内部的な堅牢性を高めると同時に、開発者にとっての使いやすさを向上させるという、Goプロジェクトの継続的な取り組みを反映しています。
関連リンク
- Go Issue 2731:
go test
の出力ストリーミングに関する議論。 - Go Issue 2685:
go test -i
オプションの提案と議論。 - Go Issue 2709:
exitStatus
のデータ競合に関する議論。 - Go Issue 2817: ツールパスの解決に関する議論。
- Gerrit Change-ID 5591045: このコミットに対応するGoのGerritレビュー。
参考にした情報源リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語のIssue Tracker: https://github.com/golang/go/issues
- Go言語の
sync
パッケージドキュメント: https://pkg.go.dev/sync - Go言語の
os/exec
パッケージドキュメント: https://pkg.go.dev/os/exec - Go言語の
path/filepath
パッケージドキュメント: https://pkg.go.dev/path/filepath - Go言語の
runtime
パッケージドキュメント: https://pkg.go.dev/runtime - Go言語の
go/build
パッケージドキュメント: https://pkg.go.dev/go/build - 標準出力と標準エラー出力に関する一般的な情報 (Unix/Linux): https://ja.wikipedia.org/wiki/%E6%A8%99%E6%BA%96%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%A0
- データ競合に関する一般的な情報: https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E7%AB%B6%E5%90%88