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

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

このコミットは、Go言語のビルドシステムにおける重要なシェルスクリプトである src/make.bash の堅牢性を向上させるための変更です。具体的には、cmd/dist ツールが失敗した場合にビルドプロセスが即座に終了するように修正されています。これにより、ビルドエラーの早期発見とデバッグの効率化が図られます。

コミット

commit e8140bd03c154f8f9e894f5cb4be8b854c944412
Author: Rob Pike <r@golang.org>
Date:   Mon Aug 19 11:18:43 2013 +1000

    make.bash: exit if dist fails
    The shell's -e doesn't work across "eval"; need to error-check by hand.
    The recent spate of Darwin build failures pointed out that if the first
    run of cmd/dist fails, we keep going. We shouldn't.
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/13098043

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

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

元コミット内容

make.bash: exit if dist fails The shell's -e doesn't work across "eval"; need to error-check by hand. The recent spate of Darwin build failures pointed out that if the first run of cmd/dist fails, we keep going. We shouldn't.

変更の背景

この変更の背景には、Go言語のビルドプロセスにおける特定の脆弱性がありました。src/make.bash スクリプトはGoのビルドをオーケストレーションする中心的な役割を担っていますが、その中で cmd/dist ツールを実行し、その出力を eval コマンドで評価する部分がありました。

通常のシェルスクリプトでは、set -e (または #!/bin/bash -e など) を使用することで、コマンドが非ゼロの終了ステータスを返した場合にスクリプトが即座に終了するようになります。これはエラーハンドリングを簡素化し、問題の早期発見に役立ちます。しかし、このコミットの作者であるRob Pikeが指摘しているように、eval コマンドの内部で実行されるコマンドの失敗は、親シェルの set -e の影響を受けないという、シェルの挙動の微妙な側面がありました。

結果として、cmd/dist が何らかの理由で失敗した場合でも、make.bash スクリプトはエラーを検知せずに処理を続行していました。これにより、ビルドプロセスは後続のステップでさらに多くのエラーを発生させ、最終的なビルド失敗に至るものの、根本原因である cmd/dist の失敗がすぐに特定できないという問題が発生していました。特に、Darwin (macOS) 環境でのビルドにおいて、この問題が頻繁に発生し、開発者のデバッグ作業を困難にしていたことが、この修正の直接的な動機となっています。

このコミットは、ビルドプロセスの信頼性とデバッグの容易性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のシェルスクリプトとGo言語のビルドに関する基本的な知識が必要です。

  1. make.bash: Go言語のソースコードからGoツールチェイン全体をビルドするための主要なシェルスクリプトです。Goのビルドプロセスは複雑であり、このスクリプトが様々なツールやコンポーネントのビルド順序を管理しています。
  2. cmd/dist: Goのビルドシステムの一部であり、Goの環境変数(GOROOT, GOHOSTOS, GOHOSTARCH など)を決定したり、ビルドに必要な情報を生成したりするツールです。make.bash はこの dist ツールを使用して、ビルド環境をセットアップします。
  3. eval コマンド: シェル組み込みコマンドの一つで、引数として与えられた文字列をコマンドとして解釈し、実行します。例えば、eval "echo hello"echo hello を実行します。eval は動的にコマンドを生成して実行する際に強力ですが、その挙動には注意が必要です。特に、eval の内部で実行されるコマンドの終了ステータスが、親シェルのエラーハンドリング(例: set -e)に直接伝播しない場合があります。
  4. set -e (または errexit オプション): シェルスクリプトのオプションの一つで、コマンドが非ゼロの終了ステータス(エラーを示す)を返した場合に、スクリプトの実行を即座に終了させるようにします。これにより、エラーが発生した際にスクリプトが予期せぬ動作を続けることを防ぎ、デバッグを容易にします。しかし、前述の通り eval のような特定のコンテキストでは、このオプションが期待通りに機能しないことがあります。
  5. シェルスクリプトのエラーハンドリング: シェルスクリプトでは、コマンドの成功/失敗は終了ステータス(exit status)によって判断されます。0は成功、非ゼロは失敗を示します。通常、if [ $? -ne 0 ] のように $? 変数(直前のコマンドの終了ステータス)をチェックすることで、エラーをハンドリングします。

技術的詳細

このコミットの技術的な核心は、eval コマンドと set -e オプションの相互作用に関するシェルの挙動の理解と、それに対する堅牢なエラーハンドリングの実装です。

元の src/make.bash スクリプトでは、以下のような行がありました。

eval $(./cmd/dist/dist env -p)

ここで、./cmd/dist/dist env -p はGoのビルドに必要な環境変数をシェルスクリプトの形式で標準出力に出力します。例えば、export GOROOT=/path/to/go のような形式です。この出力が $(...) コマンド置換によって取得され、その結果の文字列が eval コマンドに渡されて実行されます。これにより、make.bash スクリプトの現在のシェル環境にGoの環境変数が設定されます。

問題は、もし ./cmd/dist/dist env -p コマンド自体が何らかの理由で失敗し、非ゼロの終了ステータスを返した場合に発生しました。通常の set -e が有効なシェルであれば、$(...) の内部でコマンドが失敗した場合、スクリプトはそこで終了するはずです。しかし、eval のコンテキストでは、このエラーが適切に伝播されず、make.bashdist コマンドが失敗したにもかかわらず、その後の処理を続行してしまっていました。

このコミットでは、この問題を解決するために、eval の直後に手動でエラーチェックを行うメカニズムが導入されました。

変更後のコードは以下のようになります。

eval $(./cmd/dist/dist env -p || echo FAIL=true)
if [ "$FAIL" = true ]; then
	exit 1
fi

この変更のポイントは以下の通りです。

  1. || echo FAIL=true: これはシェルスクリプトの論理OR演算子 (||) を利用したテクニックです。./cmd/dist/dist env -p コマンドが成功した場合(終了ステータスが0)、|| の右側の echo FAIL=true は実行されません。しかし、./cmd/dist/dist env -p が失敗した場合(終了ステータスが非ゼロ)、|| の右側の echo FAIL=true が実行され、FAIL=true という文字列が標準出力に出力されます。
  2. eval $(...): eval コマンドは、./cmd/dist/dist env -p の出力(成功時は環境変数、失敗時は FAIL=true)を評価します。
    • 成功時: eval "export GOROOT=..." のように環境変数が設定されます。FAIL 変数は設定されません。
    • 失敗時: eval "FAIL=true" が実行され、シェル環境に FAIL という変数が true という値で設定されます。
  3. if [ "$FAIL" = true ]; then exit 1; fi: eval の直後に、FAIL 変数が true に設定されているかどうかを手動でチェックします。もし FAILtrue であれば、それは cmd/dist が失敗したことを意味するため、スクリプトは exit 1 を実行して非ゼロの終了ステータスで終了します。これにより、ビルドプロセス全体が即座に停止し、エラーが明確に通知されるようになります。

この修正により、eval の特殊な挙動を回避しつつ、cmd/dist の失敗を確実に捕捉し、ビルドの堅牢性を高めることができました。

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

src/make.bash ファイルの以下の部分が変更されました。

--- a/src/make.bash
+++ b/src/make.bash
@@ -117,7 +117,12 @@ if [ "$(uname)" == "Darwin" ]; then
 fi
 ${CC:-gcc} $mflag -O2 -Wall -Werror -o cmd/dist/dist -Icmd/dist "$DEFGOROOT" cmd/dist/*.c
 
-eval $(./cmd/dist/dist env -p)
+# -e doesn't propagate out of eval, so check success by hand.
+eval $(./cmd/dist/dist env -p || echo FAIL=true)
+if [ "$FAIL" = true ]; then
+	exit 1
+fi
+
 echo
 
 if [ "$1" = "--dist-tool" ]; then

コアとなるコードの解説

変更されたコードは、cmd/dist/dist コマンドの実行とその結果の評価、そしてその後のエラーチェックのロジックを含んでいます。

  1. eval $(./cmd/dist/dist env -p || echo FAIL=true):

    • ./cmd/dist/dist env -p: Goのビルドに必要な環境変数を標準出力に出力するコマンドです。
    • || echo FAIL=true: この部分は、直前のコマンド (./cmd/dist/dist env -p) が失敗した場合(終了ステータスが非ゼロの場合)にのみ実行されます。実行されると、FAIL=true という文字列が標準出力に出力されます。
    • $(...): コマンド置換です。./cmd/dist/dist env -p または echo FAIL=true の出力結果を文字列として取得します。
    • eval: 取得した文字列をシェルコマンドとして実行します。
      • ./cmd/dist/dist env -p が成功した場合: evalexport GOROOT=... のような環境変数設定コマンドを実行し、現在のシェル環境にGoの環境変数を設定します。この場合、FAIL 変数は設定されません。
      • ./cmd/dist/dist env -p が失敗した場合: evalFAIL=true というコマンドを実行し、現在のシェル環境に FAIL という変数を true という値で設定します。
  2. if [ "$FAIL" = true ]; then exit 1; fi:

    • if [ "$FAIL" = true ]: 直前の eval コマンドの実行後、FAIL というシェル変数の値が true であるかどうかをチェックします。
    • then exit 1; fi: もし FAIL 変数が true であれば、それは cmd/dist が失敗したことを意味するため、スクリプトは exit 1 を実行して非ゼロの終了ステータスで終了します。これにより、ビルドプロセス全体が停止し、エラーが上位のビルドシステムやCI/CDパイプラインに適切に伝達されます。

この一連の変更により、eval の内部で発生するエラーが外部に伝播しないというシェルの特性を回避し、cmd/dist の実行結果を確実にチェックして、ビルドの失敗を早期に検出できるようになりました。

関連リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Goのビルドプロセスに関するドキュメント(Goのバージョンによって異なる場合がありますが、make.bash の役割について言及されていることがあります)

参考にした情報源リンク

  • Go言語のソースコード(特に src/make.bash
  • シェルスクリプトの eval コマンドと set -e オプションに関するドキュメントや解説記事(例: Bashのmanページ、オンラインのシェルスクリプトチュートリアル)
  • Goのコミット履歴とコードレビューコメント(GoのCLシステム: https://golang.org/cl/13098043