[インデックス 12176] ファイルの概要
src/cmd/dist/build.c
は、Go言語のビルドシステムの一部である dist
ツールの中核をなすC言語のソースファイルです。このファイルは、Goのツールチェイン(コンパイラ、リンカなど)や標準ライブラリ、さらにはGoで書かれたコマンド(go
コマンド自体など)をビルドおよびインストールする際のロジックを定義しています。具体的には、様々な種類のターゲット(Cライブラリ、Goパッケージ、Goコマンド、Cコマンドなど)に対するコンパイルとリンクの処理を管理しています。
コミット
dist: treat CC as one unit
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7ae6872fc02557c46f8b9550a0697db3b9f19207
元コミット内容
dist: treat CC as one unit
Fixes #3112.
R=golang-dev, 0xe2.0x9a.0x9b, ality, rsc, rsc
CC=golang-dev
https://golang.org/cl/5700044
変更の背景
このコミットは、GoのIssue #3112「dist
tool should treat CC
as one unit」を修正するために行われました。
Goのビルドシステム、特にdist
ツールは、C言語で書かれた部分(例えば、cmd/cgo
やcmd/go
の一部、あるいはCライブラリ)をコンパイル・リンクするために、外部のCコンパイラ(通常はgcc
)を利用します。この際、dist
ツールはコンパイラのコマンドライン引数を構築します。
以前の実装では、Cコマンドをリンクする際に、gccargs
という変数に格納されたコンパイラ引数を、先頭の要素(通常はコンパイラ名自体、例: gcc
)とそれ以降の要素(オプションや入力ファイルなど)に分割して処理していました。具体的には、link.p[2]
というハードコードされたインデックスを使って、リンク結果の出力ファイルパスを指していました。
しかし、CC
環境変数やgccargs
の内容が、コンパイラ名だけでなく、複数の引数(例: clang -target x86_64-apple-darwin11
)を含む場合、この分割処理が問題を引き起こしました。gccargs.p[0]
だけをコピーし、残りをgccargs.p+1
からコピーするという方法は、CC
が単一の実行ファイル名でない場合に、正しくコンパイラを呼び出せない、あるいは引数の順序が崩れる原因となりました。
このコミットの目的は、CC
(Cコンパイラとその引数)を単一の論理的な単位として扱い、dist
ツールがCコマンドをビルドする際に、gccargs
の内容をより柔軟かつ正確にリンクコマンドに渡せるようにすることです。これにより、CC
環境変数に複雑な値が設定されていても、Goのビルドが正しく行われるようになります。
前提知識の解説
-
Goの
dist
ツール:dist
はGoのソースコードからGoツールチェイン全体をビルドするために使われる低レベルのツールです。Goのブートストラッププロセスにおいて重要な役割を果たします。- Goのソースツリーの
src/cmd/dist
ディレクトリにあります。 - Goのコンパイラ、リンカ、アセンブラ、
go
コマンド自体など、Goの基本的なツール群を構築します。 - C言語で書かれており、GoのビルドプロセスにおけるC言語部分のコンパイル・リンクも担当します。
-
GoのビルドプロセスとC言語の統合:
- Goは多くの部分がGo言語で書かれていますが、一部の低レベルな部分や、特定のシステムコールへのアクセス、あるいはC言語で書かれた既存のライブラリとの連携のためにC言語(またはアセンブリ言語)が使われています。
cmd/cgo
はGoとCの相互運用を可能にするツールであり、そのビルドにはCコンパイラが必要です。dist
ツールは、これらのC言語部分をコンパイル・リンクする際に、システムにインストールされているCコンパイラ(gcc
やclang
など)を利用します。
-
CC
環境変数:CC
は、Cコンパイラを指定するための標準的な環境変数です。例えば、CC=clang
と設定すると、ビルドシステムはgcc
の代わりにclang
を使用します。- より複雑なケースでは、
CC="clang -target x86_64-apple-darwin11"
のように、コンパイラ名だけでなく、追加のコンパイラオプションも含むことがあります。これは、クロスコンパイル環境などで特に重要になります。
-
link.p
とVec
構造体:Vec
は、dist
ツール内で使われる動的配列のようなデータ構造です。C言語で実装されており、文字列のリスト(コマンドライン引数など)を格納するために使われます。link
は、最終的なリンクコマンドの引数を格納するVec
型の変数です。例えば、gcc -o output_file input_file.o
のようなコマンドを構築する際に、link
には"gcc"
,"-o"
,"output_file"
,"input_file.o"
といった要素が順に格納されます。link.p
はそのVec
が保持する文字列ポインタの配列を指します。
-
gccargs
変数:gccargs
もVec
型の変数で、CC
環境変数から取得したCコンパイラとそのデフォルト引数を格納します。- 例えば、
CC="clang -target x86_64-apple-darwin11"
の場合、gccargs
には"clang"
,"-target"
,"x86_64-apple-darwin11"
といった要素が格納されます。
-
targ
変数:- このコミットで導入された新しい変数です。
link
コマンドライン引数の中で、最終的な出力ファイル(ターゲットファイル)のパスが格納されているインデックスを動的に保持するために使用されます。 - 以前は
link.p[2]
のようにハードコードされていましたが、この変更により、link.p[targ]
として柔軟にアクセスできるようになります。
- このコミットで導入された新しい変数です。
技術的詳細
このコミットの核心は、dist
ツールがCコマンドをリンクする際のコマンドライン引数の構築方法の改善にあります。
以前のinstall
関数内のCコマンドの処理ブロックでは、以下のようなロジックでした。
// C command.
// Use gccargs, but ensure that link.p[2] is output file,
// as noted above.
vadd(&link, gccargs.p[0]); // gccargsの最初の要素(コンパイラ名)を追加
vadd(&link, "-o"); // -o オプションを追加
vadd(&link, bpathf(&b, "%s/%s%s", tooldir, name, exe)); // 出力ファイルパスを追加
vcopy(&link, gccargs.p+1, gccargs.len-1); // gccargsの残りの要素を追加
このアプローチの問題点は、gccargs.p[0]
とgccargs.p+1
に分割してコピーしている点です。
例えば、CC="clang -target x86_64-apple-darwin11"
の場合、gccargs
は以下のようになります。
gccargs.p[0]
= "clang"
gccargs.p[1]
= "-target"
gccargs.p[2]
= "x86_64-apple-darwin11"
このとき、上記のコードは以下のようなコマンドを構築しようとします。
clang -o output_file -target x86_64-apple-darwin11
しかし、もしCC
が"my_custom_compiler_script --some-flag"
のような形式だった場合、gccargs.p[0]
は"my_custom_compiler_script"
、gccargs.p[1]
は"--some-flag"
となります。この場合、my_custom_compiler_script -o output_file --some-flag
というコマンドが構築されますが、これはmy_custom_compiler_script
が--some-flag
をoutput_file
の後に期待する場合など、引数の順序が問題になる可能性があります。
新しいアプローチでは、vcopy(&link, gccargs.p, gccargs.len);
を使用して、gccargs
の全要素を一度にlink
にコピーします。これにより、CC
環境変数で指定されたコンパイラとその引数が、link
コマンドの先頭にまとめて追加されることになります。
// C command. Use gccargs.
vcopy(&link, gccargs.p, gccargs.len); // gccargsの全要素を一度にコピー
vadd(&link, "-o"); // -o オプションを追加
targ = link.len; // 出力ファイルパスのインデックスをtargに保存
vadd(&link, bpathf(&b, "%s/%s%s", tooldir, name, exe)); // 出力ファイルパスを追加
この変更により、CC="clang -target x86_64-apple-darwin11"
の場合、link
はまず"clang"
, "-target"
, "x86_64-apple-darwin11"
を受け取り、その後に"-o"
と出力ファイルパスが追加されます。
結果として、clang -target x86_64-apple-darwin11 -o output_file
のような、より自然で正しいコマンドラインが構築されるようになります。
また、出力ファイルパスのインデックスをハードコードされた2
から、動的に計算されるtarg
変数に変更することで、link
配列の構造が将来変更されても、コードの堅牢性が保たれます。targ
は、vadd
で出力ファイルパスが追加される直前のlink.len
(現在の要素数)に設定されるため、常に正しいインデックスを指します。
この変更は、Goのビルドシステムが、より多様なCコンパイラの設定やクロスコンパイル環境に柔軟に対応できるようになることを意味します。
コアとなるコードの変更箇所
--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -539,7 +539,7 @@ install(char *dir)
Buf b, b1, path;
Vec compile, files, link, go, missing, clean, lib, extra;
Time ttarg, t;
- int i, j, k, n, doclean;\n+\tint i, j, k, n, doclean, targ;
if(vflag) {
\tif(!streq(goos, gohostos) || !streq(goarch, gohostarch))
@@ -601,7 +601,7 @@ install(char *dir)
\texe = ".exe";
\
// Start final link command line.
- // Note: code below knows that link.p[2] is the target.\n+\t// Note: code below knows that link.p[targ] is the target.
if(islib) {
\t// C library.
\tvadd(&link, "ar");
@@ -609,6 +609,7 @@ install(char *dir)
\tprefix = "";
\tif(!hasprefix(name, "lib"))
\t\tprefix = "lib";
+\t\ttarg = link.len;
\tvadd(&link, bpathf(&b, "%s/pkg/obj/%s_%s/%s%s.a", goroot, gohostos, gohostarch, prefix, name));
} else if(ispkg) {
\t// Go library (package).
@@ -617,6 +618,7 @@ install(char *dir)
\tp = bprintf(&b, "%s/pkg/%s_%s/%s", goroot, goos, goarch, dir+4);
\t*xstrrchr(p, '/') = '\0';
\txmkdirall(p);
+\t\ttarg = link.len;
\tvadd(&link, bpathf(&b, "%s/pkg/%s_%s/%s.a", goroot, goos, goarch, dir+4));
} else if(streq(dir, "cmd/go") || streq(dir, "cmd/cgo")) {
\t// Go command.
@@ -625,21 +627,20 @@ install(char *dir)
\telem = name;
\tif(streq(elem, "go"))
\t\telem = "go_bootstrap";
+\t\ttarg = link.len;
\tvadd(&link, bpathf(&b, "%s/%s%s", tooldir, elem, exe));
} else {
-\t\t// C command.
-\t\t// Use gccargs, but ensure that link.p[2] is output file,
-\t\t// as noted above.
-\t\tvadd(&link, gccargs.p[0]);
+\t\t// C command. Use gccargs.
+\t\tvcopy(&link, gccargs.p, gccargs.len);
\tvadd(&link, "-o");
+\t\ttarg = link.len;
\tvadd(&link, bpathf(&b, "%s/%s%s", tooldir, name, exe));
-\t\tvcopy(&link, gccargs.p+1, gccargs.len-1);
\tif(streq(gohostarch, "amd64"))
\t\tvadd(&link, "-m64");
\telse if(streq(gohostarch, "386"))
\t\tvadd(&link, "-m32");
}\n-\tttarg = mtime(link.p[2]);
+\tttarg = mtime(link.p[targ]);
// Gather files that are sources for this target.
// Everything in that directory, and any target-specific
@@ -926,7 +927,7 @@ install(char *dir)
}
// Remove target before writing it.
-\txremove(link.p[2]);
+\txremove(link.p[targ]);
runv(nil, nil, CheckExit, &link);
コアとなるコードの解説
-
int i, j, k, n, doclean, targ;
の追加:install
関数のローカル変数宣言にtarg
が追加されました。これは、リンクコマンドの引数リスト内で、出力ファイル(ターゲット)のパスが格納されているインデックスを保持するための整数型変数です。
-
コメントの変更
// Note: code below knows that link.p[2] is the target.
から// Note: code below knows that link.p[targ] is the target.
:- 以前は、
link.p
配列のインデックス2
が常にターゲットファイルパスを指すという前提がありました。この変更により、その前提がtarg
変数によって動的に管理されるようになったことを示しています。
- 以前は、
-
targ = link.len;
の追加 (Cライブラリ、Goパッケージ、Goコマンドのセクション):islib
(Cライブラリ),ispkg
(Goパッケージ),streq(dir, "cmd/go") || streq(dir, "cmd/cgo")
(Goコマンド) の各セクションで、vadd
でターゲットファイルパスがlink
に追加される直前に、targ = link.len;
が追加されています。link.len
はVec
の現在の要素数(つまり、次に要素が追加されるインデックス)を返します。これにより、targ
は追加されるターゲットファイルパスの正確なインデックスを記録します。
-
Cコマンドの処理ロジックの変更:
- 削除された行:
vadd(&link, gccargs.p[0]); // ... vcopy(&link, gccargs.p+1, gccargs.len-1);
- これは、
gccargs
の最初の要素(コンパイラ名)を個別に追加し、残りの要素をgccargs.p+1
からコピーするという、以前の分割処理です。
- これは、
- 追加された行:
// C command. Use gccargs. vcopy(&link, gccargs.p, gccargs.len); vadd(&link, "-o"); targ = link.len; vadd(&link, bpathf(&b, "%s/%s%s", tooldir, name, exe));
vcopy(&link, gccargs.p, gccargs.len);
により、gccargs
の全要素(コンパイラ名とそれに続く引数)がlink
にまとめてコピーされます。これにより、CC
環境変数に複数の引数が含まれていても、それらが正しくコンパイラコマンドの先頭に配置されます。- その後、
-o
オプションと出力ファイルパスが追加され、出力ファイルパスのインデックスがtarg
に保存されます。
- 削除された行:
-
mtime(link.p[2]);
からmtime(link.p[targ]);
への変更:- ターゲットファイルの最終更新時刻を取得する際に、ハードコードされたインデックス
2
ではなく、動的に設定されたtarg
変数を使用するように変更されました。これにより、link
配列の構造変更に強くなります。
- ターゲットファイルの最終更新時刻を取得する際に、ハードコードされたインデックス
-
xremove(link.p[2]);
からxremove(link.p[targ]);
への変更:- ターゲットファイルを削除する際も、同様に
targ
変数を使用するように変更されました。
- ターゲットファイルを削除する際も、同様に
これらの変更により、dist
ツールはCコンパイラの呼び出しをより正確に、かつ柔軟に行えるようになり、CC
環境変数に複雑な値が設定されている場合でも、Goのビルドが安定して動作するようになりました。
関連リンク
- Go Issue #3112: https://code.google.com/p/go/issues/detail?id=3112 (古いGoogle Codeのリンクですが、元のIssueです)
- Go CL (Change List) 5700044: https://golang.org/cl/5700044 (Goのコードレビューシステムにおけるこの変更のページ)
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/dist/build.c
の周辺コード) - Go Issue Tracker (Issue #3112 の詳細)
- Go Code Review (CL 5700044 の詳細)
- C言語の動的配列、ポインタ操作に関する一般的な知識
- Unix/Linuxにおける
CC
環境変数とビルドシステムに関する一般的な知識