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

[インデックス 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/cgocmd/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のビルドが正しく行われるようになります。

前提知識の解説

  1. Goの dist ツール:

    • distはGoのソースコードからGoツールチェイン全体をビルドするために使われる低レベルのツールです。Goのブートストラッププロセスにおいて重要な役割を果たします。
    • Goのソースツリーのsrc/cmd/distディレクトリにあります。
    • Goのコンパイラ、リンカ、アセンブラ、goコマンド自体など、Goの基本的なツール群を構築します。
    • C言語で書かれており、GoのビルドプロセスにおけるC言語部分のコンパイル・リンクも担当します。
  2. GoのビルドプロセスとC言語の統合:

    • Goは多くの部分がGo言語で書かれていますが、一部の低レベルな部分や、特定のシステムコールへのアクセス、あるいはC言語で書かれた既存のライブラリとの連携のためにC言語(またはアセンブリ言語)が使われています。
    • cmd/cgoはGoとCの相互運用を可能にするツールであり、そのビルドにはCコンパイラが必要です。
    • distツールは、これらのC言語部分をコンパイル・リンクする際に、システムにインストールされているCコンパイラ(gccclangなど)を利用します。
  3. CC 環境変数:

    • CCは、Cコンパイラを指定するための標準的な環境変数です。例えば、CC=clangと設定すると、ビルドシステムはgccの代わりにclangを使用します。
    • より複雑なケースでは、CC="clang -target x86_64-apple-darwin11"のように、コンパイラ名だけでなく、追加のコンパイラオプションも含むことがあります。これは、クロスコンパイル環境などで特に重要になります。
  4. link.pVec 構造体:

    • Vecは、distツール内で使われる動的配列のようなデータ構造です。C言語で実装されており、文字列のリスト(コマンドライン引数など)を格納するために使われます。
    • linkは、最終的なリンクコマンドの引数を格納するVec型の変数です。例えば、gcc -o output_file input_file.oのようなコマンドを構築する際に、linkには"gcc", "-o", "output_file", "input_file.o"といった要素が順に格納されます。
    • link.pはそのVecが保持する文字列ポインタの配列を指します。
  5. gccargs 変数:

    • gccargsVec型の変数で、CC環境変数から取得したCコンパイラとそのデフォルト引数を格納します。
    • 例えば、CC="clang -target x86_64-apple-darwin11"の場合、gccargsには"clang", "-target", "x86_64-apple-darwin11"といった要素が格納されます。
  6. 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-flagoutput_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);
  

コアとなるコードの解説

  1. int i, j, k, n, doclean, targ; の追加:

    • install関数のローカル変数宣言にtargが追加されました。これは、リンクコマンドの引数リスト内で、出力ファイル(ターゲット)のパスが格納されているインデックスを保持するための整数型変数です。
  2. コメントの変更 // 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変数によって動的に管理されるようになったことを示しています。
  3. 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.lenVecの現在の要素数(つまり、次に要素が追加されるインデックス)を返します。これにより、targは追加されるターゲットファイルパスの正確なインデックスを記録します。
  4. 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に保存されます。
  5. mtime(link.p[2]); から mtime(link.p[targ]); への変更:

    • ターゲットファイルの最終更新時刻を取得する際に、ハードコードされたインデックス2ではなく、動的に設定されたtarg変数を使用するように変更されました。これにより、link配列の構造変更に強くなります。
  6. xremove(link.p[2]); から xremove(link.p[targ]); への変更:

    • ターゲットファイルを削除する際も、同様にtarg変数を使用するように変更されました。

これらの変更により、distツールはCコンパイラの呼び出しをより正確に、かつ柔軟に行えるようになり、CC環境変数に複雑な値が設定されている場合でも、Goのビルドが安定して動作するようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/dist/build.c の周辺コード)
  • Go Issue Tracker (Issue #3112 の詳細)
  • Go Code Review (CL 5700044 の詳細)
  • C言語の動的配列、ポインタ操作に関する一般的な知識
  • Unix/LinuxにおけるCC環境変数とビルドシステムに関する一般的な知識