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

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

このコミットは、Go言語のビルドシステムにおいて、Windows環境でのビルドプロセスを改善し、distツールをベースとしたビルドメカニズムを導入することを目的としています。具体的には、Windows向けのビルドスクリプト(all.bat, make.bat, run.bat)を新規追加・更新し、src/cmd/dist/windows.cにWindows固有のプロセス管理やファイル操作に関する機能拡張を行っています。これにより、GoのクロスコンパイルおよびWindows上でのネイティブビルドの堅牢性と効率が向上しています。

コミット

commit 4c1abd6c64085a005b0d9d54eee97cd0c06151b2
Author: Russ Cox <rsc@golang.org>
Date:   Sat Feb 4 00:48:31 2012 -0500

    build: dist-based build for windows
    
    R=golang-dev, bradfitz, iant, alex.brainman, go.peter.90
    CC=golang-dev
    https://golang.org/cl/5630047

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

https://github.com/golang/go/commit/4c1abd6c64085a005b0d9d54eee97cd0c06151b2

元コミット内容

build: dist-based build for windows

変更の背景

Go言語は、その設計思想の一つとして「クロスコンパイルの容易さ」を掲げています。しかし、初期のGoのビルドシステムは、Unix系の環境を強く意識して設計されており、Windows環境でのビルドプロセスは、特に複雑な依存関係やパスの扱いの違いから、課題を抱えていました。

このコミットが行われた2012年当時、Goはまだ比較的新しい言語であり、様々なプラットフォームでの安定したビルド環境の確立が重要でした。特にWindowsは、多くの開発者が利用する主要なOSであるため、GoをWindows上でネイティブにビルドできること、あるいはUnix系OSからWindows向けにクロスコンパイルできることは、Goの普及にとって不可欠でした。

このコミットは、GoのビルドツールであるdistをWindows環境に適合させ、より堅牢で自動化されたビルドプロセスを確立することを目的としています。具体的には、Windows特有のパス区切り文字(\ vs /)、環境変数の扱い、プロセス起動と待機メカニズム、そして並列ビルドのサポートといった課題に対処しています。これにより、WindowsユーザーがGoをより簡単にビルドし、開発できるようになることが期待されました。

前提知識の解説

Goのビルドシステムとdistツール

Go言語のビルドシステムは、goコマンドの内部で利用される低レベルなツール群によって支えられています。その中でもdistツールは、Goのソースコードからコンパイラ、リンカ、標準ライブラリなどをビルドするためのブートストラッププロセスを管理する重要な役割を担っています。distは、Goのツールチェイン自体をビルドするために使われるため、Goのソースコードをダウンロードした後に最初に実行されるツールの一つです。

バッチファイル (.bat)

Windows環境でコマンドを実行するためのスクリプトファイルです。Unix系のシェルスクリプト(.sh)に相当します。このコミットでは、all.bat, make.bat, run.batといったバッチファイルがGoのビルドプロセスを自動化するために使用されています。

  • all.bat: Goのビルド、テスト、および最終的なバイナリの生成を含む、完全なビルドプロセスをオーケストレーションします。
  • make.bat: Goのコンパイラ、ツール、標準ライブラリをビルドする主要なスクリプトです。
  • run.bat: ビルドされたGoのツールチェインを使用して、Goの標準パッケージのテストを実行します。

Windows API (Win32 API)

Windowsオペレーティングシステム上で動作するアプリケーションを開発するためのAPI群です。C言語で記述されており、プロセス管理、ファイルシステム操作、メモリ管理など、OSの低レベルな機能にアクセスするために使用されます。

  • PROCESS_INFORMATION: CreateProcess関数によって作成された新しいプロセスの識別情報(プロセスハンドル、スレッドハンドル、プロセスID、スレッドID)を格納する構造体です。
  • CreatePipe: 親プロセスと子プロセスの間で通信するための匿名パイプを作成する関数です。標準入力/出力のリダイレクトによく使用されます。
  • WaitForMultipleObjects: 複数のカーネルオブジェクト(プロセス、スレッド、イベントなど)のいずれかがシグナル状態になるまで待機する関数です。このコミットでは、複数のバックグラウンドプロセスが完了するのを待つために使用されています。
  • GetExitCodeProcess: 指定されたプロセスの終了コードを取得する関数です。プロセスの成功/失敗を判断するために使用されます。
  • GetFileAttributesW: 指定されたファイルまたはディレクトリの属性を取得する関数です。ファイルが存在するか、それがディレクトリであるかなどを判断するために使用されます。Wサフィックスは、ワイド文字(UTF-16)パスを受け取るバージョンであることを示します。
  • INVALID_FILE_ATTRIBUTES: GetFileAttributesW関数が失敗した場合に返される値です。
  • SYSTEM_INFO: 現在のシステムに関する情報(プロセッサアーキテクチャ、ページサイズなど)を格納する構造体です。
  • PROCESSOR_ARCHITECTURE_AMD64 / PROCESSOR_ARCHITECTURE_INTEL: SYSTEM_INFO構造体に含まれるプロセッサアーキテクチャを示す定数です。それぞれx64(AMD64)とx86(Intel 386)アーキテクチャに対応します。

MinGW/GCC

MinGW (Minimalist GNU for Windows) は、Windows上でGCC (GNU Compiler Collection) を使用するための開発環境です。Goのビルドプロセスでは、ブートストラップツール(distツールなど)をコンパイルするためにGCCが使用されることがあります。

技術的詳細

このコミットの主要な変更点は、src/cmd/dist/windows.cにおけるWindows固有の処理の強化と、GoのビルドプロセスをWindows環境に適合させるためのバッチファイルの導入です。

src/cmd/dist/windows.cの変更点

  1. パス処理の改善:

    • bpathf関数の追加: この関数はbprintfに似ていますが、結果文字列内のすべての/をWindowsのパス区切り文字である\に置換します。これにより、Goの内部でUnixスタイルのパスが使われていても、Windowsのコマンドラインで正しく解釈されるパスを生成できるようになります。
    • contains関数の修正: コマンドライン引数をクォートする際に、バックスラッシュの扱いを修正しています。特に、末尾のバックスラッシュや二重バックスラッシュの処理が改善され、Windowsのコマンドラインパーサーが正しく引数を解釈するようにしています。
  2. プロセス実行と並列処理のサポート:

    • run, runv, bgrunv関数の導入:
      • runrunvは、指定されたコマンドを同期的に実行します。runvは引数をVec構造体で受け取ります。
      • bgrunvは、コマンドをバックグラウンドで非同期に実行します。これは、複数のビルドタスクを並行して実行し、ビルド時間を短縮するために重要です。
    • バックグラウンドジョブ管理:
      • MAXBG定数(最大4つのバックグラウンドジョブ)とbg配列、nbg変数を使用して、実行中のバックグラウンドプロセスを追跡します。
      • bgwait1関数は、実行中のバックグラウンドジョブのいずれか一つが完了するまで待機し、そのプロセス情報をクリーンアップします。
      • bgwait関数は、すべてのバックグラウンドジョブが完了するまでbgwait1を繰り返し呼び出します。
    • パイプの作成と標準出力のリダイレクト:
      • CreatePipe関数を呼び出す際に、SECURITY_ATTRIBUTES構造体を適切に設定し、パイプハンドルが子プロセスに継承されるようにしています。これにより、子プロセスの標準出力を親プロセスでキャプチャできるようになります。
      • ReadFileのループ内で、パイプからの読み取りが終了した場合(n == 0)にループを抜けるように修正し、パイプの読み取りがより堅牢になっています。
    • エラーハンドリングの改善: fatal関数がcbuildではなくgo tool distというプレフィックスでエラーメッセージを出力するように変更され、よりユーザーフレンドリーになっています。
  3. ファイルシステム操作の改善:

    • isdir, isfile関数の修正: GetFileAttributesWの戻り値チェックをattr >= 0からattr != INVALID_FILE_ATTRIBUTESに変更し、より正確なエラーチェックを行っています。
    • mtime関数でFindCloseを呼び出すように修正し、ファイル検索ハンドルのリークを防いでいます。
    • readfile, writefile関数にvflag(verboseフラグ)によるデバッグ出力が追加され、ファイル操作の可視性が向上しています。
  4. システム情報の取得:

    • main関数内でGetSystemInfoを呼び出し、現在のプロセッサアーキテクチャ(amd64または386)を検出してgohostarch変数に設定するように変更されています。これにより、Goのビルドが実行されている環境のアーキテクチャを自動的に判別できるようになります。

バッチファイルの導入 (all.bat, make.bat, run.bat)

これらのバッチファイルは、GoのビルドプロセスをWindows環境で自動化するためのエントリポイントとなります。

  • all.bat: make.batrun.batを順に呼び出し、Goのビルドとテストを自動的に実行します。GOBUILDFAIL変数を使用して、各ステップの成功/失敗をチェックし、エラーが発生した場合はビルドを停止します。
  • make.bat:
    • dist.exeブートストラップツールのコンパイル: MinGW/GCCを使用してdist.exeをコンパイルします。この際、現在のディレクトリからGOROOTを自動的に検出し、DEFAULT_GOROOTマクロとしてdist.exeに埋め込みます。
    • Goコンパイラとツールのビルド: dist bootstrap -vコマンドを実行して、Goのコンパイラとその他のツールをビルドします。
    • パッケージとコマンドのビルド: go_bootstrap clean stdgo_bootstrap install -a -v stdを実行して、標準ライブラリとコマンドをビルドします。
  • run.bat:
    • 環境変数の設定: dist env -wpコマンドを実行して、Goのビルドに必要な環境変数(PATHなど)を一時的に設定します。
    • パッケージとコマンドの再ビルド(オプション): go install -a -v stdを実行して、必要に応じてパッケージとコマンドを再ビルドします。
    • テストの実行: go test std, go test runtime, go test syncなど、Goの標準パッケージのテストを実行します。-shortフラグは短いテストのみを実行し、-timeoutはテストのタイムアウトを設定します。-cpuフラグは、テストを特定のCPUコア数で実行するように指定します。

これらの変更により、Windows上でのGoのビルドプロセスが大幅に改善され、より信頼性が高く、効率的なものになりました。特に、並列ビルドのサポートとWindows固有のパス処理の改善は、大規模なGoプロジェクトのビルドにおいて重要な役割を果たします。

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

  • src/all.bat: 新規追加
  • src/cmd/dist/windows.c: 大幅な変更
  • src/make.bat: 新規追加
  • src/run.bat: 新規追加

src/cmd/dist/windows.cにおける主要な変更関数/セクション

  • torune関数: int i, n; の削除
  • xgetenv関数: char *p; の削除
  • bwritef関数: 新規追加 (Bufに書式付き文字列を書き込む)
  • bpathf関数: 新規追加 (Bufに書式付き文字列を書き込み、/\に変換)
  • breadfrom関数: ReadFileのエラーハンドリング修正 (パイプ読み取り時のbreak)
  • run関数: 新規追加 (コマンドを同期的に実行)
  • runv関数: 既存関数の大幅な変更 (コマンド実行ロジックの再構築、並列実行の準備)
  • bgrunv関数: 新規追加 (コマンドをバックグラウンドで非同期実行)
  • bgwait1関数: 新規追加 (単一のバックグラウンドジョブの完了を待機)
  • bgwait関数: 新規追加 (すべてのバックグラウンドジョブの完了を待機)
  • genrun関数: runvから分離された内部ヘルパー関数 (プロセス生成と待機ロジック)
  • CreatePipe呼び出し: SECURITY_ATTRIBUTES構造体の使用
  • WaitForSingleObjectからWaitForMultipleObjectsへの変更 (並列実行のため)
  • rgetwd関数: GetCurrentDirectoryからGetCurrentDirectoryWへの変更
  • xrealwd関数: int n; の削除
  • isdir, isfile関数: GetFileAttributesWの戻り値チェックの修正
  • mtime関数: FindCloseの追加
  • readfile, writefile関数: vflagによるデバッグ出力の追加
  • fatal関数: エラーメッセージのプレフィックス変更
  • xprintf関数: fflush(stdout)の削除
  • main関数: GetSystemInfoによるプロセッサアーキテクチャの検出とgohostarchの設定

コアとなるコードの解説

src/cmd/dist/windows.c

このファイルは、GoのビルドツールであるdistがWindows環境で動作するために必要な低レベルな処理を実装しています。

bpathf関数

char*
bpathf(Buf *b, char *fmt, ...)
{
	int i;
	va_list arg;
	char buf[4096];

	breset(b);
	va_start(arg, fmt);
	vsnprintf(buf, sizeof buf, fmt, arg);
	va_end(arg);
	bwritestr(b, buf);

	for(i=0; i<b->len; i++)
		if(b->p[i] == '/')
			b->p[i] = '\\\\';

	return bstr(b);
}

この関数は、Goの内部で慣習的に使われるUnixスタイルのパス(/区切り)を、Windowsで認識されるパス(\区切り)に変換する役割を担います。vsnprintfで書式付き文字列をバッファに書き込んだ後、ループで各文字をチェックし、/が見つかれば\に置換します。これにより、生成されるコマンドライン引数やファイルパスがWindows環境で正しく解釈されるようになります。

プロセス実行と並列処理 (genrun, bgrunv, bgwait1, bgwait)

Goのビルドは多くのサブプロセスを起動するため、効率的なプロセス管理が不可欠です。特にWindowsでは、Unix系のfork/execとは異なるCreateProcessを使用するため、独自のラッパーが必要です。

genrunは、runvbgrunvの両方から呼び出される内部ヘルパー関数で、実際のプロセス生成ロジックを含んでいます。

static void
genrun(Buf *b, char *dir, int mode, Vec *argv, int wait)
{
    // ... (引数のクォート処理など) ...

    // バックグラウンドジョブの最大数に達している場合、いずれかのジョブが完了するまで待機
    while(nbg >= nelem(bg))
        bgwait1();

    // CreateProcessWを呼び出して新しいプロセスを生成
    // ...

    // プロセス情報をbg配列に格納し、nbgをインクリメント
    bg[nbg].pi = pi;
    bg[nbg].mode = mode;
    bg[nbg].cmd = btake(&cmd); // コマンド文字列を所有
    nbg++;

    // 同期実行の場合、すべてのバックグラウンドジョブが完了するまで待機
    if(wait)
        bgwait();

    bfree(&cmd);
}

genrunは、CreateProcessWを使用して新しいプロセスを起動します。注目すべきは、nbg >= nelem(bg)の条件でbgwait1()を呼び出し、同時に実行されるバックグラウンドジョブの数を制限している点です。これにより、システムリソースの枯渇を防ぎながら、ある程度の並列実行を実現しています。

bgrunvgenrunwait=0(非同期)で呼び出し、コマンドをバックグラウンドで実行します。

void
bgrunv(char *dir, int mode, Vec *argv)
{
	genrun(nil, dir, mode, argv, 0); // wait=0 で非同期実行
}

bgwait1は、複数のバックグラウンドジョブの中から一つが完了するのを待機します。

static void
bgwait1(void)
{
	int i, mode;
	char *cmd;
	HANDLE bgh[MAXBG];
	DWORD code;

	if(nbg == 0)
		fatal("bgwait1: nothing left");

	// 実行中のすべてのプロセスのハンドルを配列に格納
	for(i=0; i<nbg; i++)
		bgh[i] = bg[i].pi.hProcess;

	// いずれかのプロセスがシグナル状態になるまで待機
	i = WaitForMultipleObjects(nbg, bgh, FALSE, INFINite);
	// ... (エラーチェック) ...

	cmd = bg[i].cmd;
	mode = bg[i].mode;
	// 終了コードの取得とエラーチェック
	if(!GetExitCodeProcess(bg[i].pi.hProcess, &code))
		fatal("GetExitCodeProcess: %s", errstr());
	if(mode==CheckExit && code != 0)
		fatal("FAILED: %s", cmd);

	// ハンドルのクローズとbg配列からの削除
	CloseHandle(bg[i].pi.hProcess);
	CloseHandle(bg[i].pi.hThread);
	bg[i] = bg[--nbg]; // 完了したジョブを配列の末尾と入れ替えて削除
}

WaitForMultipleObjectsを使用することで、複数のプロセスを効率的に監視し、いずれかが終了した時点で処理を進めることができます。これにより、ビルドの並列化が実現されています。

bgwaitは、すべてのバックグラウンドジョブが完了するまでbgwait1を繰り返し呼び出します。

void
bgwait(void)
{
	while(nbg > 0)
		bgwait1();
}

src/make.bat

このバッチファイルは、Goのビルドプロセスにおける主要なステップを自動化します。

:: Grab default $GOROOT, escape \ for C string.
:: The expression %CD:\\=\\% means to take %CD%
:: and apply the substitution \ = \\, escaping the
:: backslashes.  Then we wrap that in quotes to create
:: a C string.
cd ..
set DEFGOROOT=-DDEFAULT_GOROOT=\"\\\"%CD:\\=\\\\%\\\"\"
cd src

echo # Building C bootstrap tool.
if not exist ..\bin\tool mkdir ..\bin\tool
:: Windows has no glob expansion, so spell out cmd/dist/*.c.
gcc -O2 -Wall -Werror -o ../bin/tool/dist.exe -Icmd/dist %DEFGOROOT% cmd/dist/buf.c cmd/dist/build.c cmd/dist/buildgc.c cmd/dist/buildruntime.c cmd/dist/goc2c.c cmd/dist/main.c cmd/dist/windows.c
if errorlevel 1 goto fail
echo .

echo # Building compilers and Go bootstrap tool.
..\bin\tool\dist bootstrap -v
if errorlevel 1 goto fail
echo .

echo # Building packages and commands.
..\bin\tool\go_bootstrap clean std
if errorlevel 1 goto fail
..\bin\tool\go_bootstrap install -a -v std
if errorlevel 1 goto fail
del ..\bin\tool\go_bootstrap.exe
echo .

このスクリプトは、まずdist.exeというブートストラップツールをGCCでコンパイルします。ここで注目すべきは、%CD:\\=\\\\%という複雑な構文を使って、現在のディレクトリパスをC言語の文字列リテラルとしてdist.exeに埋め込んでいる点です。これは、dist.exeが自身の実行パスからGOROOTを推測できるようにするためです。

その後、コンパイルされたdist.exeを使用して、Goのコンパイラ、ツール、標準ライブラリを段階的にビルドしていきます。go_bootstrapは、まだ完全にビルドされていないGoのツールチェインの初期バージョンを指します。

src/run.bat

このバッチファイルは、ビルドされたGoのツールチェインを使用して、標準パッケージのテストを実行します。

set GOOLDPATH=%PATH%
set GOBUILDFAIL=0

..\bin\tool\dist env -wp >env.bat
if errorlevel 1 goto fail
call env.bat
del env.bat

rem TODO avoid rebuild if possible

if x%1==x--no-rebuild goto norebuild
echo # Building packages and commands.
go install -a -v std
if errorlevel 1 goto fail
echo .
:norebuild

echo # Testing packages.
go test std -short -timeout=120s
if errorlevel 1 goto fail
echo .

echo # runtime -cpu=1,2,4
go test runtime -short -timeout=120s -cpu=1,2,4
if errorlevel 1 goto fail
echo .

echo # sync -cpu=10
go test sync -short -timeout=120s -cpu=10
if errorlevel 1 goto fail
echo .

このスクリプトは、まずdist env -wpを実行して、Goのビルドに必要な環境変数をenv.batという一時ファイルに書き出し、それをcallコマンドで読み込むことで、現在のシェルセッションに適用します。これにより、goコマンドが正しく動作するための環境が整えられます。

その後、go installでパッケージを再ビルドし、go testコマンドで様々な標準パッケージのテストを実行します。-short-timeout-cpuといったフラグは、テストの実行方法を制御するために使用されます。

これらのバッチファイルとwindows.cの変更が連携することで、GoのWindowsビルドプロセスが自動化され、より堅牢で効率的なものになっています。

関連リンク

参考にした情報源リンク