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

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

このコミットは、Go言語のビルドおよび配布ツールである cmd/dist における詳細な(verbose)メッセージの出力先を、標準出力(stdout)から標準エラー出力(stderr)に変更するものです。これにより、診断メッセージと通常のプログラム出力が分離され、スクリプト処理やログ解析が容易になります。

コミット

commit 34b10d7482b4a83cf066c313a201503126393293
Author: Pieter Droogendijk <pieter@binky.org.uk>
Date:   Fri Jul 6 15:00:18 2012 +1000

    cmd/dist: Make verbose messages print to stderr
    
    Made the following changes:
     - Export errprintf() from all three OS-specific modules
     - Added errprintf() to a.h
     - Moved errprintf() in windows.c under xprintf(), since they are so similar
     - Replaced all instances of xprintf() with errprintf() where a vflag check is done
    Fixes #3788.
    
    R=golang-dev, alex.brainman
    CC=golang-dev
    https://golang.org/cl/6346056

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

https://github.com/golang/go/commit/34b10d7482b4a83cf066c313a201503126393293

元コミット内容

cmd/dist: 詳細メッセージを標準エラー出力に表示する

以下の変更を行いました:

  • errprintf() を3つのOS固有モジュールすべてからエクスポートしました。
  • a.herrprintf() を追加しました。
  • windows.cerrprintf()xprintf() の下に移動しました。これらは非常に似ているためです。
  • vflag のチェックが行われるすべての xprintf() のインスタンスを errprintf() に置き換えました。 Issue #3788 を修正します。

変更の背景

この変更の背景には、コマンドラインツールの標準的な出力慣習と、cmd/dist のようなビルドツールの特性があります。一般的に、UNIX系のシステムでは、プログラムの通常の出力は標準出力(stdout)に、エラーメッセージや診断情報、デバッグ情報などの補助的な出力は標準エラー出力(stderr)に送るのが慣例です。

cmd/dist はGo言語のビルドと配布を管理する内部ツールであり、その実行中に多くの詳細なメッセージ(verbose messages)を出力することがあります。これらのメッセージが標準出力に混ざってしまうと、以下のような問題が発生します。

  1. スクリプト処理の困難さ: cmd/dist の出力を他のコマンドにパイプで渡して処理する場合、詳細メッセージが混入していると、意図したデータだけを抽出するのが難しくなります。
  2. ログ解析の複雑化: ビルドログを解析する際に、通常のビルド結果と診断情報が混在していると、エラーや警告の特定が困難になります。
  3. ユーザー体験の低下: ユーザーが通常のビルド結果のみを求めている場合でも、詳細メッセージが大量に表示されることで、必要な情報が見つけにくくなることがあります。

Issue #3788 は、まさにこの問題、つまり cmd/dist の詳細メッセージが標準出力に表示されてしまうことに対する修正要求であったと考えられます。このコミットは、この問題を解決し、cmd/dist の出力がより標準的な慣習に沿うようにすることで、ツールの使いやすさとスクリプトとの連携を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

  1. cmd/dist: Go言語のソースコードリポジトリ内に存在する内部ツールです。Go言語のコンパイラ、ツールチェイン、標準ライブラリなどのビルド、テスト、インストールといったGoディストリビューション全体の管理を行います。一般的なGo開発者が直接使用するツールではなく、Goプロジェクト自体の開発やメンテナンスにおいて重要な役割を果たします。

  2. 標準出力(stdout)と標準エラー出力(stderr): UNIX系OSにおけるプロセスが持つ2つの主要な出力ストリームです。

    • 標準出力(stdout): プログラムの通常の出力(結果、データなど)が送られる場所です。デフォルトではターミナルに表示されますが、パイプ(|)やリダイレクト(>)で他のプログラムやファイルに送ることができます。
    • 標準エラー出力(stderr): プログラムのエラーメッセージ、警告、診断情報などが送られる場所です。デフォルトではターミナルに表示されますが、標準出力とは独立してリダイレクト(2>)することができます。これにより、通常の出力とエラー出力を分離して処理することが可能になります。
  3. vflag: 多くのコマンドラインツールで採用されている、詳細度(verbosity)を制御するためのフラグ(変数)です。通常、vflag の値が大きいほど、より多くの詳細なメッセージが出力されます。このコミットでは、vflag の値に基づいて出力されるメッセージが xprintf() から errprintf() に切り替えられています。

  4. xprintf()errprintf(): このコミットで変更の対象となっている、cmd/dist 内部で使用される出力関数です。

    • xprintf(): 従来の出力関数で、おそらく標準出力にメッセージを出力していました。
    • errprintf(): このコミットで導入または変更された関数で、標準エラー出力にメッセージを出力するように設計されています。
  5. va_list, vfprintf(), WriteFile(), GetStdHandle(): C言語における可変引数関数(printf のような関数)の実装に関連する概念です。

    • va_list: 可変引数リストを操作するための型です。
    • vfprintf(): fprintf() の可変引数バージョンで、FILE* ストリームにフォーマットされた文字列を出力します。このコミットでは stderr ストリーム(標準エラー出力)に対して使用されています。
    • WriteFile(): Windows API関数で、指定されたファイルまたはI/Oデバイスにデータを書き込みます。
    • GetStdHandle(): Windows API関数で、標準入力、標準出力、または標準エラー出力のハンドルを取得します。STD_ERROR_HANDLE を引数に渡すことで、標準エラー出力のハンドルを取得できます。

これらの知識を前提として、コミットの変更内容を詳細に見ていきます。

技術的詳細

このコミットの技術的な核心は、cmd/dist 内部の診断メッセージの出力メカニズムを xprintf から errprintf へと切り替えることにあります。この変更は、Goがサポートする複数のOS(Plan 9, Unix, Windows)に対応するため、各OS固有のコードベースで同様の修正が施されています。

具体的な変更点は以下の通りです。

  1. errprintf() の導入とエクスポート:

    • src/cmd/dist/plan9.csrc/cmd/dist/unix.c に、vfprintf(stderr, fmt, arg); を使用して標準エラー出力にメッセージを書き込む errprintf 関数が新しく追加されました。これは、C標準ライブラリの機能を利用した一般的な実装です。
    • src/cmd/dist/windows.c では、既存の errprintf 関数が xprintf 関数の近くに移動され、その実装が WriteFile(GetStdHandle(STD_ERROR_HANDLE), p, n, &w, 0); を使用してWindowsの標準エラーハンドルに直接書き込む形になっています。これはWindows APIに特化した実装です。
    • これらの errprintf 関数は、各OS固有のモジュールから外部にエクスポート(利用可能に)されています。
  2. a.h への errprintf() の宣言追加: src/cmd/dist/a.hcmd/dist の共通ヘッダファイルであり、他のソースファイルから利用される関数のプロトタイプ宣言が含まれています。このファイルに void errprintf(char*, ...); の宣言が追加され、cmd/dist の他の部分から errprintf を呼び出せるようになりました。

  3. xprintf() から errprintf() への置き換え: src/cmd/dist/build.c, src/cmd/dist/plan9.c, src/cmd/dist/unix.c, src/cmd/dist/windows.c の各ファイルにおいて、vflag(詳細度フラグ)のチェックが行われる箇所で xprintf() の呼び出しが errprintf() に置き換えられました。

    • vflag は、cmd/dist の実行時に -v オプションなどで設定される詳細度レベルを制御する変数です。
    • vflag > 1vflag > 2 のような条件分岐の内部にある xprintf() は、通常、デバッグ情報や詳細な実行ステップを示すメッセージを出力するために使われていました。これらのメッセージが標準エラー出力に送られるように変更されたのです。

この変更により、cmd/dist が出力する通常のビルド結果は引き続き標準出力に送られ、詳細な診断メッセージやデバッグ情報は標準エラー出力に分離されることになります。

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

このコミットで変更された主要なファイルと、その中でのコアとなる変更箇所は以下の通りです。

  1. src/cmd/dist/a.h:

    --- a/src/cmd/dist/a.h
    +++ b/src/cmd/dist/a.h
    @@ -108,6 +108,7 @@ void	xmain(int argc, char **argv);
     
     // portability layer (plan9.c, unix.c, windows.c)
     bool	contains(char *p, char *sep);
    +void	errprintf(char*, ...);
     void	fatal(char *msg, ...);
     bool	hasprefix(char *p, char *prefix);
     bool	hassuffix(char *p, char *suffix);
    

    errprintf 関数のプロトタイプ宣言が追加されました。

  2. src/cmd/dist/build.c: vflag のチェックが行われる複数の箇所で xprintferrprintf に置き換えられています。例:

    --- a/src/cmd/dist/build.c
    +++ b/src/cmd/dist/build.c
    @@ -147,7 +147,7 @@ static void
     rmworkdir(void)
     {
      if(vflag > 1)
    -		xprintf("rm -rf %s\n", workdir);
    +		errprintf("rm -rf %s\n", workdir);
      xremoveall(workdir);
     }
    

    rmworkdir 関数内で、作業ディレクトリ削除時の詳細メッセージの出力先が変更されています。同様の変更が install 関数や copy 関数内でも行われています。

  3. src/cmd/dist/plan9.c: errprintf 関数の実装が追加され、vflag のチェックが行われる箇所で xprintferrprintf に置き換えられています。

    --- a/src/cmd/dist/plan9.c
    +++ b/src/cmd/dist/plan9.c
    @@ -661,6 +661,17 @@ xprintf(char *fmt, ...)
      va_end(arg);
     }
     
    +// errprintf prints a message to standard output.
    +void
    +errprintf(char *fmt, ...)
    +{
    +	va_list arg;
    +	
    +	va_start(arg, fmt);
    +	vfprintf(stderr, fmt, arg);
    +	va_end(arg);
    +}
    +
     // xsetenv sets the environment variable $name to the given value.
     void
     xsetenv(char *name, char *value)
    

    errprintf の実装が追加され、genrun, xremove, xremoveall 関数内で xprintferrprintf に置き換えられています。

  4. src/cmd/dist/unix.c: plan9.c と同様に、errprintf 関数の実装が追加され、vflag のチェックが行われる箇所で xprintferrprintf に置き換えられています。

    --- a/src/cmd/dist/unix.c
    +++ b/src/cmd/dist/unix.c
    @@ -627,6 +627,17 @@ xprintf(char *fmt, ...)\n \tva_end(arg);\n }\n \n+// errprintf prints a message to standard output.\n+void\n+errprintf(char *fmt, ...)\n+{\n+\tva_list arg;\n+\t\n+\tva_start(arg, fmt);\n+\tvfprintf(stderr, fmt, arg);\n+\tva_end(arg);\n+}\n+\n // xsetenv sets the environment variable $name to the given value.\n void\n xsetenv(char *name, char *value)\n    ```
    `errprintf` の実装が追加され、`genrun`, `xremove`, `xremoveall` 関数内で `xprintf` が `errprintf` に置き換えられています。
    
    
  5. src/cmd/dist/windows.c: 既存の errprintf の位置が変更され、vflag のチェックが行われる箇所で xprintferrprintf に置き換えられています。

    --- a/src/cmd/dist/windows.c
    +++ b/cmd/dist/windows.c
    @@ -121,22 +121,6 @@ errstr(void)\n \treturn bstr(&b);  // leak but we\'re dying anyway\n }\n \n-static void\n-errprintf(char *fmt, ...) {\n-\tva_list arg;\n-\tchar *p;\n-\tDWORD n, w;\n-\n-\tva_start(arg, fmt);\n-\tn = vsnprintf(NULL, 0, fmt, arg);\n-\tp = xmalloc(n+1);\n-\tvsnprintf(p, n+1, fmt, arg);\n-\tva_end(arg);\n-\tw = 0;\n-\tWriteFile(GetStdHandle(STD_ERROR_HANDLE), p, n, &w, 0);\n-\txfree(p);\n-}\n-\n void\n xgetenv(Buf *b, char *name)\n {\
    

    errprintf の定義が xprintf の近くに移動され、genrun, readfile, writefile 関数内で xprintferrprintf に置き換えられています。

コアとなるコードの解説

このコミットのコアとなる変更は、cmd/dist が出力する詳細なメッセージのストリームを切り替えるための errprintf 関数の導入と、その関数への既存の xprintf 呼び出しの置き換えです。

errprintf 関数の役割: errprintf は、可変引数を受け取り、フォーマットされた文字列を標準エラー出力に書き込むためのユーティリティ関数です。

  • Unix/Plan 9 の実装:

    void errprintf(char *fmt, ...) {
        va_list arg;
        va_start(arg, fmt);
        vfprintf(stderr, fmt, arg); // 標準エラー出力に書き込む
        va_end(arg);
    }
    

    この実装は、C標準ライブラリの vfprintf 関数を stderr ストリーム(標準エラー出力)に対して使用しています。これは、Unix系システムでエラーメッセージを出力する際の標準的な方法です。

  • Windows の実装:

    void errprintf(char *fmt, ...) {
        va_list arg;
        char *p;
        DWORD n, w;
    
        va_start(arg, fmt);
        n = vsnprintf(NULL, 0, fmt, arg); // 出力に必要なバッファサイズを計算
        p = xmalloc(n+1);                 // バッファを確保
        vsnprintf(p, n+1, fmt, arg);      // フォーマットされた文字列をバッファに書き込む
        va_end(arg);
        w = 0;
        WriteFile(GetStdHandle(STD_ERROR_HANDLE), p, n, &w, 0); // 標準エラーハンドルに直接書き込む
        xfree(p);                         // バッファを解放
    }
    

    Windowsでは、vfprintf の代わりにWindows APIの WriteFile 関数を直接使用しています。これは、GetStdHandle(STD_ERROR_HANDLE) で取得した標準エラー出力のハンドルに対して、フォーマットされた文字列を書き込むためです。vsnprintf を使ってまず文字列をメモリに構築し、その内容を WriteFile で出力するという手順を踏んでいます。これは、Windows環境での低レベルなI/O操作の一般的なパターンです。

xprintf から errprintf への置き換え: cmd/dist のコードベース全体で、vflag の値(詳細度レベル)に基づいて出力されるメッセージのほとんどが xprintf から errprintf に変更されました。例えば、rm -rf %s のようなファイル操作のログや、クロスコンパイル時のスキップメッセージ、ファイル生成のログなどがこれに該当します。

この変更により、cmd/dist の実行結果をスクリプトで処理する際に、通常の成功メッセージやビルド成果物のパスなどは標準出力から取得し、デバッグや診断に役立つ詳細なログは標準エラー出力から取得するといった、より柔軟な処理が可能になります。これは、コマンドラインツールの設計におけるベストプラクティスの一つであり、ツールの使いやすさと自動化のしやすさを向上させます。

関連リンク

  • Go CL (Code Review) ページ: https://golang.org/cl/6346056
  • GitHub Issue #3788 (関連する可能性のあるGoプロジェクトのIssue): このコミットメッセージに記載されている Fixes #3788 は、GoプロジェクトのIssueトラッカーにおける特定の課題を指しています。具体的な内容はコミットメッセージからは読み取れませんが、通常は詳細メッセージの出力に関する問題であったと推測されます。

参考にした情報源リンク