[インデックス 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
の変更点
-
パス処理の改善:
bpathf
関数の追加: この関数はbprintf
に似ていますが、結果文字列内のすべての/
をWindowsのパス区切り文字である\
に置換します。これにより、Goの内部でUnixスタイルのパスが使われていても、Windowsのコマンドラインで正しく解釈されるパスを生成できるようになります。contains
関数の修正: コマンドライン引数をクォートする際に、バックスラッシュの扱いを修正しています。特に、末尾のバックスラッシュや二重バックスラッシュの処理が改善され、Windowsのコマンドラインパーサーが正しく引数を解釈するようにしています。
-
プロセス実行と並列処理のサポート:
run
,runv
,bgrunv
関数の導入:run
とrunv
は、指定されたコマンドを同期的に実行します。runv
は引数をVec
構造体で受け取ります。bgrunv
は、コマンドをバックグラウンドで非同期に実行します。これは、複数のビルドタスクを並行して実行し、ビルド時間を短縮するために重要です。
- バックグラウンドジョブ管理:
MAXBG
定数(最大4つのバックグラウンドジョブ)とbg
配列、nbg
変数を使用して、実行中のバックグラウンドプロセスを追跡します。bgwait1
関数は、実行中のバックグラウンドジョブのいずれか一つが完了するまで待機し、そのプロセス情報をクリーンアップします。bgwait
関数は、すべてのバックグラウンドジョブが完了するまでbgwait1
を繰り返し呼び出します。
- パイプの作成と標準出力のリダイレクト:
CreatePipe
関数を呼び出す際に、SECURITY_ATTRIBUTES
構造体を適切に設定し、パイプハンドルが子プロセスに継承されるようにしています。これにより、子プロセスの標準出力を親プロセスでキャプチャできるようになります。ReadFile
のループ内で、パイプからの読み取りが終了した場合(n == 0
)にループを抜けるように修正し、パイプの読み取りがより堅牢になっています。
- エラーハンドリングの改善:
fatal
関数がcbuild
ではなくgo tool dist
というプレフィックスでエラーメッセージを出力するように変更され、よりユーザーフレンドリーになっています。
-
ファイルシステム操作の改善:
isdir
,isfile
関数の修正:GetFileAttributesW
の戻り値チェックをattr >= 0
からattr != INVALID_FILE_ATTRIBUTES
に変更し、より正確なエラーチェックを行っています。mtime
関数でFindClose
を呼び出すように修正し、ファイル検索ハンドルのリークを防いでいます。readfile
,writefile
関数にvflag
(verboseフラグ)によるデバッグ出力が追加され、ファイル操作の可視性が向上しています。
-
システム情報の取得:
main
関数内でGetSystemInfo
を呼び出し、現在のプロセッサアーキテクチャ(amd64
または386
)を検出してgohostarch
変数に設定するように変更されています。これにより、Goのビルドが実行されている環境のアーキテクチャを自動的に判別できるようになります。
バッチファイルの導入 (all.bat
, make.bat
, run.bat
)
これらのバッチファイルは、GoのビルドプロセスをWindows環境で自動化するためのエントリポイントとなります。
all.bat
:make.bat
とrun.bat
を順に呼び出し、Goのビルドとテストを自動的に実行します。GOBUILDFAIL
変数を使用して、各ステップの成功/失敗をチェックし、エラーが発生した場合はビルドを停止します。make.bat
:dist.exe
ブートストラップツールのコンパイル: MinGW/GCCを使用してdist.exe
をコンパイルします。この際、現在のディレクトリからGOROOT
を自動的に検出し、DEFAULT_GOROOT
マクロとしてdist.exe
に埋め込みます。- Goコンパイラとツールのビルド:
dist bootstrap -v
コマンドを実行して、Goのコンパイラとその他のツールをビルドします。 - パッケージとコマンドのビルド:
go_bootstrap clean std
とgo_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
は、runv
とbgrunv
の両方から呼び出される内部ヘルパー関数で、実際のプロセス生成ロジックを含んでいます。
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()
を呼び出し、同時に実行されるバックグラウンドジョブの数を制限している点です。これにより、システムリソースの枯渇を防ぎながら、ある程度の並列実行を実現しています。
bgrunv
はgenrun
をwait=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ビルドプロセスが自動化され、より堅牢で効率的なものになっています。
関連リンク
- https://github.com/golang/go/commit/4c1abd6c64085a005b0d9d54eee97cd0c06151b2
- Go Code Review 5630047: build: dist-based build for windows (このコミットの元となったコードレビューへのリンク)
参考にした情報源リンク
- Goのビルドシステムに関する公式ドキュメントやブログ記事 (当時の情報) (一般的なGoのビルドプロセスに関する情報源として)
- Windows APIに関するMicrosoftのドキュメント (Win32 API関数の詳細について)
- MinGWプロジェクトのウェブサイト (MinGWに関する情報について)
- Stack Overflowや技術ブログ (Windowsバッチファイルの構文や、C言語でのWindows API利用に関する一般的な情報について)