[インデックス 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言語のビルドに関する基本的な知識が必要です。
make.bash: Go言語のソースコードからGoツールチェイン全体をビルドするための主要なシェルスクリプトです。Goのビルドプロセスは複雑であり、このスクリプトが様々なツールやコンポーネントのビルド順序を管理しています。cmd/dist: Goのビルドシステムの一部であり、Goの環境変数(GOROOT,GOHOSTOS,GOHOSTARCHなど)を決定したり、ビルドに必要な情報を生成したりするツールです。make.bashはこのdistツールを使用して、ビルド環境をセットアップします。evalコマンド: シェル組み込みコマンドの一つで、引数として与えられた文字列をコマンドとして解釈し、実行します。例えば、eval "echo hello"はecho helloを実行します。evalは動的にコマンドを生成して実行する際に強力ですが、その挙動には注意が必要です。特に、evalの内部で実行されるコマンドの終了ステータスが、親シェルのエラーハンドリング(例:set -e)に直接伝播しない場合があります。set -e(またはerrexitオプション): シェルスクリプトのオプションの一つで、コマンドが非ゼロの終了ステータス(エラーを示す)を返した場合に、スクリプトの実行を即座に終了させるようにします。これにより、エラーが発生した際にスクリプトが予期せぬ動作を続けることを防ぎ、デバッグを容易にします。しかし、前述の通りevalのような特定のコンテキストでは、このオプションが期待通りに機能しないことがあります。- シェルスクリプトのエラーハンドリング: シェルスクリプトでは、コマンドの成功/失敗は終了ステータス(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.bash は dist コマンドが失敗したにもかかわらず、その後の処理を続行してしまっていました。
このコミットでは、この問題を解決するために、eval の直後に手動でエラーチェックを行うメカニズムが導入されました。
変更後のコードは以下のようになります。
eval $(./cmd/dist/dist env -p || echo FAIL=true)
if [ "$FAIL" = true ]; then
exit 1
fi
この変更のポイントは以下の通りです。
|| echo FAIL=true: これはシェルスクリプトの論理OR演算子 (||) を利用したテクニックです。./cmd/dist/dist env -pコマンドが成功した場合(終了ステータスが0)、||の右側のecho FAIL=trueは実行されません。しかし、./cmd/dist/dist env -pが失敗した場合(終了ステータスが非ゼロ)、||の右側のecho FAIL=trueが実行され、FAIL=trueという文字列が標準出力に出力されます。eval $(...):evalコマンドは、./cmd/dist/dist env -pの出力(成功時は環境変数、失敗時はFAIL=true)を評価します。- 成功時:
eval "export GOROOT=..."のように環境変数が設定されます。FAIL変数は設定されません。 - 失敗時:
eval "FAIL=true"が実行され、シェル環境にFAILという変数がtrueという値で設定されます。
- 成功時:
if [ "$FAIL" = true ]; then exit 1; fi:evalの直後に、FAIL変数がtrueに設定されているかどうかを手動でチェックします。もしFAILがtrueであれば、それは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 コマンドの実行とその結果の評価、そしてその後のエラーチェックのロジックを含んでいます。
-
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が成功した場合:evalはexport GOROOT=...のような環境変数設定コマンドを実行し、現在のシェル環境にGoの環境変数を設定します。この場合、FAIL変数は設定されません。./cmd/dist/dist env -pが失敗した場合:evalはFAIL=trueというコマンドを実行し、現在のシェル環境にFAILという変数をtrueという値で設定します。
-
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)