[インデックス 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