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

[インデックス 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 のユーザビリティ、信頼性、およびパフォーマンスを向上させることを目的としています。具体的には、以下の問題に対処しています。

  1. go run のエラー出力の改善: 以前は go run でビルドエラーが発生した場合、その出力が標準出力 (stdout) に混ざってしまうことがあり、エラーメッセージの識別が困難でした。これを標準エラー出力に統一することで、エラー処理の標準的な慣習に従い、スクリプトなどでのエラー解析を容易にします。
  2. go test の出力ストリーミング: 大規模なテストスイートを実行する際、go test がすべての出力をバッファリングしてから一度に表示すると、テストの進行状況が分かりにくく、特に長時間実行されるテストでは不便でした。出力をストリーミングすることで、テストのリアルタイムな進捗を把握できるようになります。これは Issue 2731 で報告された問題への対応です。
  3. テスト依存関係の管理: go test を実行する際に、テストに必要なパッケージがインストールされていない場合、テストが失敗するか、手動でのインストールが必要でした。go test -i オプションの導入により、テスト実行前に必要な依存関係を自動的にインストールできるようになり、開発ワークフローが簡素化されます。これは Issue 2685 で報告された問題への対応です。
  4. exitStatus のデータ競合: cmd/go 内部で、プログラムの終了ステータスを管理する exitStatus 変数へのアクセスが複数のゴルーチンから同時に行われる可能性があり、データ競合が発生していました。これにより、予期せぬ終了ステータスが返されるなどの不安定な挙動を引き起こす可能性がありました。これは Issue 2709 で報告された問題への対応です。
  5. ツールパスの解決: 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.GOOSruntime.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 の内部実装にいくつかの重要な変更を加えています。

  1. 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 が設定されます。printStderrfmt.Fprint(os.Stderr, args...) を呼び出す関数で、これにより go run のビルドエラーが標準エラー出力にリダイレクトされます。
    • builder.showcmd および builder.showOutput メソッドが fmt.Printlnfmt.Print の代わりに b.print を使用するように変更され、出力が printStderr にルーティングされるようになります。
  2. go test の出力ストリーミングと -i オプション (src/cmd/go/test.go, src/cmd/go/testflag.go):

    • testStreamOutput という新しいブール型フラグが導入されました。これは、テスト出力をバッファリングせずにリアルタイムでストリーミングするかどうかを制御します。
    • testStreamOutput は、引数なしの go test (現在のディレクトリのテスト) またはベンチマーク (-bench フラグ) が実行される場合に true に設定されます。また、単一パッケージのテストで -v フラグが指定されている場合も true になります。
    • builder.runTest 関数内で、cmd.Stdoutcmd.StderrtestStreamOutput の値に基づいて os.Stdout および os.Stderr に直接設定されるか、bytes.Buffer にバッファリングされるかが決定されます。
    • testI ( -i フラグ) が testflag.go に追加され、go test -i が有効になります。
    • runTest 関数内で testItrue の場合、テスト対象パッケージとそのテスト依存関係を収集し、それらのパッケージを modeInstall モードでビルドおよびインストールするロジックが追加されました。これにより、テスト実行前に依存関係が自動的にインストールされます。
  3. 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.goexitMu sync.MutexsetExitStatus(n int) 関数が追加されました。
    • setExitStatus 関数は、exitMu.Lock()exitMu.Unlock() を使用して exitStatus 変数へのアクセスを同期します。これにより、複数のゴルーチンが同時に exitStatus を変更しようとした際のデータ競合が防止されます。
    • errorf 関数や、ビルドエラー、テスト失敗、ツール実行エラーなどで exitStatus を設定するすべての箇所が setExitStatus を呼び出すように変更されました。
  4. ツールパスの修正 (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.gotool(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 内の gofixgovet などのツール呼び出しも 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.showcmdbuilder.showOutputb.print を使用するように変更。
    • setExitStatus(2) の呼び出しを追加。
    • toolIsWindows の使用箇所を修正。
    • goToolchain の各ツール(gc, asm, pack, ld, cc)のパス解決に tool(...) ヘルパー関数を使用するように変更。
    • b.goos != runtime.GOOSb.goos != toolGOOS に変更。
  • src/cmd/go/main.go:
    • sync パッケージをインポート。
    • exitMu sync.MutexsetExitStatus(n int) 関数を追加。
    • errorf および fatalf 内で exitStatus = ... の代わりに setExitStatus(...) を呼び出すように変更。
  • src/cmd/go/run.go:
    • fmtos パッケージをインポート。
    • printStderr 関数を追加 (fmt.Fprint(os.Stderr, args...) を呼び出す)。
    • runRun 関数内で b.print = printStderr を設定。
  • src/cmd/go/test.go:
    • sort パッケージをインポート。
    • testI ( -i フラグ) と testStreamOutput フラグを追加。
    • testShowPass のロジックを変更。
    • testStreamOutput の設定ロジックを追加。
    • testItrue の場合の依存関係インストールロジックを追加。
    • fmt.Fprintf(os.Stderr, "installing these packages with 'go test -i' will speed future tests.\\n\\n") にメッセージを変更。
    • builder.runTest 関数内で、cmd.Stdoutcmd.StderrtestStreamOutput に応じて 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.GOOSctxt.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 の内部でどのように出力が処理され、終了ステータスが管理され、ツールが発見されるかという点にあります。

  1. 柔軟な出力制御: builder 構造体に print フィールドが追加されたことで、cmd/go のビルドプロセスからの出力を、標準出力、標準エラー出力、またはその他の任意の io.Writer に柔軟にリダイレクトできるようになりました。go run の場合は printStderr 関数を割り当てることで、ビルドエラーが確実に標準エラー出力に送られるようになります。これは、GoツールがよりUnixの哲学(エラーはstderrへ)に従うようにするための重要なステップです。

  2. 安全な終了ステータス管理: exitStatus 変数へのアクセスを sync.Mutex で保護する setExitStatus 関数が導入されたことは、Goの並行処理モデルにおけるデータ競合の典型的な解決策です。cmd/go のような複雑なツールでは、複数の並行タスク(例えば、複数のパッケージのビルドやテスト)が同時に実行される可能性があり、それぞれが終了ステータスに影響を与える可能性があります。ミューテックスを使用することで、exitStatus の値が常に一貫性を保ち、予測可能な終了コードが返されることが保証されます。

  3. 一貫したツールパス解決: tool(name string) ヘルパー関数と toolGOOS, toolGOARCH の導入は、Goツールチェーン内の他のツール(コンパイラ、リンカ、アセンブラなど)へのパス解決を中央集権化し、より堅牢にするものです。以前は、これらのパスが複数の場所でハードコードされていたため、変更やクロスコンパイル環境での問題が発生しやすかったのですが、この変更により、すべてのツールが goroot/bin/go-tool ディレクトリから一貫した方法で発見されるようになります。これにより、Goツールチェーンの内部構造がより整理され、将来のメンテナンスが容易になります。

  4. go test のユーザビリティ向上: go test -i オプションは、開発者のワークフローを大幅に改善します。テストの実行前に手動で依存関係をインストールする手間が省け、特にCI/CD環境や新しい開発環境のセットアップ時に有用です。また、testStreamOutput フラグによるテスト出力のストリーミングは、大規模なテストスイートの実行時にリアルタイムのフィードバックを提供し、開発者がテストの進行状況をよりよく理解できるようにします。これは、開発体験を向上させるための細やかながらも重要な改善です。

これらの変更は、Goツールチェーンの内部的な堅牢性を高めると同時に、開発者にとっての使いやすさを向上させるという、Goプロジェクトの継続的な取り組みを反映しています。

関連リンク

参考にした情報源リンク