[インデックス 18034] ファイルの概要
このコミットは、Go言語のビルドシステムにおける重要な変更を導入しています。具体的には、cmd/dist
ツールがgo tool pack
コマンドへの依存を排除し、Goコンパイラ自体が-pack
オプションを通じてアーカイブファイルを直接生成できるようにするとともに、Cやアセンブリ言語で書かれた追加のファイルがある場合には、それらを直接アーカイブに連結する新しいメカニズムを導入しています。これにより、将来的にcmd/pack
ツール全体をGo言語で再実装するための道が開かれました。
コミット
commit b2d43caa7a1a2697f09187a133ef724d9f822634
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 17 21:44:18 2013 -0500
cmd/dist: avoid use of 'go tool pack'
All packages now use the -pack option to the compiler.
For a pure Go package, that's enough.
For a package with additional C and assembly files, the extra
archive entries can be added directly (by concatenation)
instead of by invoking go tool pack.
These changes make it possible to rewrite cmd/pack in Go.
R=iant, r
CC=golang-dev
https://golang.org/cl/42890043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b2d43caa7a1a2697f09187a133ef724d9f822634
元コミット内容
このコミットは、src/cmd/dist/build.c
ファイルに対して57行の追加と5行の削除を行っています。主な変更点は以下の通りです。
install
関数内で、go tool pack
コマンドを直接呼び出す代わりに、Goコンパイラに-pack
オプションを渡すように変更。- Cやアセンブリファイルを含むパッケージの場合に、コンパイラが生成したアーカイブにこれらの追加ファイルを直接連結するための新しいヘルパー関数
dopack
を導入。 dopack
関数は、Unixのar
アーカイブフォーマットを理解し、ファイルエントリを適切に追加するロジックを含んでいます。
変更の背景
この変更の背景には、Go言語のツールチェインをGo言語自身で記述するという、より広範な「セルフホスティング」の取り組みがあります。初期のGoコンパイラやツールの一部はC言語で書かれていましたが、Go言語が成熟するにつれて、そのツールチェイン自体をGoで書き直すことが目標とされました。
go tool pack
は、Goのパッケージ(特にCやアセンブリのソースを含むもの)をアーカイブファイル(.a
ファイル)にまとめる役割を担っていました。このツールは、Go言語の初期段階ではC言語で実装されていた可能性があり、Goツールチェインのセルフホスティングを完全に実現するためには、このpack
の機能をGo言語で再実装する必要がありました。
このコミットは、cmd/dist
(Goのビルドプロセスをオーケストレーションするツール)からgo tool pack
への直接的な依存を取り除くことで、その再実装を可能にするための前提条件を整えるものです。具体的には、Goコンパイラにアーカイブ生成の責任の一部を移譲し、残りのアーカイブ操作(追加ファイルの連結)をcmd/dist
内で直接C言語で実装することで、外部のpack
コマンドを呼び出す必要がなくなりました。これにより、cmd/pack
をGoで書き直しても、既存のビルドプロセスに影響を与えないようにすることが可能になります。
前提知識の解説
Go言語のビルドプロセスとcmd/dist
Go言語のビルドプロセスは、go build
コマンドによって抽象化されていますが、その内部ではcmd/dist
というツールが重要な役割を担っています。cmd/dist
は、Goのソースコードから実行可能ファイルやライブラリを構築するための低レベルなビルドスクリプトのようなものです。Goのコンパイラ(例: gc
、6g
、8g
など)、アセンブラ(例: 6a
、8a
など)、リンカ(例: 6l
、8l
など)といった様々なツールを呼び出し、適切な順序でビルドステップを実行します。
go tool pack
go tool pack
は、Go言語の初期のツールチェインの一部であり、主にGoのパッケージをアーカイブファイル(.a
ファイル)にまとめるために使用されていました。Goのソースファイルから生成されたオブジェクトファイル(.o
ファイル)や、C/アセンブリ言語のソースから生成されたオブジェクトファイルを、単一のライブラリアーカイブに結合する役割を担っていました。これは、Unix系のシステムで広く使われているar
(archiver)コマンドに似た機能を提供します。
ar
アーカイブフォーマット
ar
(archiver)は、複数のファイルを単一のアーカイブファイルにまとめるためのUnix標準のユーティリティおよびファイルフォーマットです。主に静的ライブラリ(.a
ファイル)の作成に使用されます。ar
アーカイブは、ヘッダとそれに続くファイルデータで構成され、各ファイルエントリにはファイル名、サイズ、パーミッションなどのメタデータが含まれます。dopack
関数が手動でこのフォーマットのヘッダを構築しているのは、ar
アーカイブに直接ファイルを連結するためです。
セルフホスティング (Self-hosting)
セルフホスティングとは、プログラミング言語のコンパイラやツールチェインが、その言語自身で書かれている状態を指します。Go言語の場合、初期のコンパイラはC言語で書かれていましたが、Go言語が成熟するにつれて、コンパイラ、アセンブラ、リンカ、その他のビルドツールをGo言語自身で書き直すという目標が掲げられました。これにより、Go言語の進化に合わせてツールチェインもGo言語の機能を利用できるようになり、開発の効率化や保守性の向上が期待されます。
技術的詳細
このコミットの核心は、Goのビルドプロセスにおけるアーカイブ生成の責任を再分配することにあります。
-
コンパイラへの
-pack
オプションの導入: 以前は、Goコンパイラ(例:6g
、8g
)はGoソースコードをコンパイルしてオブジェクトファイル(.o
)を生成し、その後go tool pack
がこれらのオブジェクトファイルをアーカイブにまとめていました。この変更により、Goコンパイラ自体が-pack
オプションを受け取るようになり、コンパイル時に直接アーカイブファイル(.a
)を生成できるようになりました。純粋なGoパッケージの場合、これによりgo tool pack
の呼び出しが完全に不要になります。 -
dopack
関数の導入と手動アーカイブ連結: GoパッケージがCやアセンブリ言語のソースファイルを含む場合、それらのソースは別のコンパイラ(Cコンパイラやアセンブラ)によってオブジェクトファイルにコンパイルされます。これらのオブジェクトファイルは、Goコンパイラが生成したGoのアーカイブに結合される必要があります。 このコミットでは、dopack
という新しいC言語関数がsrc/cmd/dist/build.c
に追加されました。この関数は、Goコンパイラが生成したベースのアーカイブファイルと、追加で結合すべきC/アセンブリのオブジェクトファイルのリストを受け取ります。dopack
関数は、ar
アーカイブフォーマットの構造を直接操作します。具体的には、追加される各オブジェクトファイルに対して、ar
アーカイブのメンバーヘッダ(ファイル名、サイズ、パーミッションなどを含む)を手動で構築し、その後にファイルの内容を連結します。これにより、外部のgo tool pack
コマンドを呼び出すことなく、必要なすべてのオブジェクトファイルを単一のアーカイブにまとめることができます。
このアプローチにより、cmd/dist
はgo tool pack
という特定の外部コマンドに依存することなく、アーカイブ生成のロジックを内部で完結させることができます。これは、cmd/pack
をGo言語で再実装する際に、cmd/dist
がその新しいGo実装を透過的に利用できるようになるための重要なステップです。
コアとなるコードの変更箇所
変更はsrc/cmd/dist/build.c
ファイルに集中しています。
-
install
関数内の変更:ispackcmd
というブール変数が追加され、現在のビルドターゲットがpack
コマンドの処理を必要とするかどうかを追跡します。- Goパッケージのビルドパスで、以前は
pack
コマンドへのパスを追加していた箇所が、ispackcmd = 1; vadd(&link, "pack");
というプレースホルダー的な記述に変わっています。これは、pack
という名前がまだlink
ベクトルに残るものの、実際の実行はdopack
関数に委ねられることを示唆しています。 - Goコンパイラ(
%sg
)の呼び出しに-pack
オプションが追加され、出力ファイル名が.a
拡張子を持つように変更されました。// Old: // bpathf(&b, "%s/_go_.%s", workdir, gochar); // New: bpathf(&b, "%s/_go_.a", workdir); vadd(&compile, "-pack");
- コンパイラ実行後、
ispackcmd
が真の場合に新しいdopack
関数が呼び出され、その後goto nobuild;
で通常のリンク処理をスキップするロジックが追加されました。if(ispackcmd) { xremove(link.p[targ]); dopack(link.p[targ], bstr(&b), &link.p[targ+1], link.len - (targ+1)); goto nobuild; }
-
dopack
関数の新規追加:copy
関数の後に、dopack
という静的関数が追加されました。static void dopack(char *dst, char *src, char **extra, int nextra) { // ... (実装の詳細) ... }
この関数は、
dst
(最終的なアーカイブのパス)、src
(Goコンパイラが生成したベースアーカイブのパス)、extra
(追加するファイルのパスの配列)、nextra
(追加するファイルの数)を引数に取ります。
コアとなるコードの解説
dopack
関数の詳細
dopack
関数は、Goコンパイラが生成したアーカイブに、Cやアセンブリのオブジェクトファイルなどの追加ファイルを連結する役割を担います。その実装は、Unixのar
アーカイブフォーマットの知識に基づいています。
-
初期化:
Buf b, bdst;
という2つのバッファを初期化します。bdst
は最終的なアーカイブの内容を構築するために使われ、b
は一時的に追加ファイルのコンテンツを読み込むために使われます。 -
ベースアーカイブの読み込み:
readfile(&bdst, src);
Goコンパイラが-pack
オプションで生成したベースのアーカイブファイル(src
)の内容をbdst
バッファに読み込みます。これが新しいアーカイブの基盤となります。 -
追加ファイルの連結:
for(i=0; i<nextra; i++) { ... }
extra
配列に含まれる各ファイル(C/アセンブリのオブジェクトファイルなど)についてループ処理を行います。-
ファイルの読み込み:
readfile(&b, extra[i]);
現在の追加ファイルの内容を一時バッファb
に読み込みます。 -
メンバー名の抽出:
p = xstrrchr(extra[i], '/');
q = xstrrchr(extra[i], '\\');
p
とq
を使って、ファイルパスから最後の要素(ファイル名)を抽出します。これは、アーカイブ内のメンバー名として使用されます。 -
ar
ヘッダの書き込み:bwritef(&bdst, "%-16.16s%-12d%-6d%-6d%-8o%-10d
\n", p, 0, 0, 0, 0644, b.len);これが
dopackの最も重要な部分です。Unixの
arアーカイブフォーマットの各メンバーエントリのヘッダを手動で構築し、
bdst`バッファに書き込みます。%-16.16s
: メンバー名(16バイト、左詰め、切り詰め)%-12d
: 最終変更時刻(Unixタイムスタンプ、12バイト、左詰め) - ここでは0
%-6d
: 所有者ID(6バイト、左詰め) - ここでは0
%-6d
: グループID(6バイト、左詰め) - ここでは0
%-8o
: ファイルモード(パーミッション、8バイト、8進数、左詰め) - ここでは0644
(読み取り/書き込み可能)%-10d
: ファイルサイズ(10バイト、左詰め) -b.len
(読み込んだファイルのバイト数)`\n
:ar
ヘッダの終端を示すマジック文字列
-
ファイル内容の書き込み:
bwriteb(&bdst, &b);
読み込んだファイルの内容(b
バッファ)をbdst
バッファに連結します。 -
パディング:
if(b.len&1) { c = 0; bwrite(&bdst, &c, 1); }
ar
アーカイブフォーマットでは、各メンバーエントリのデータは偶数バイトにパディングされる必要があります。もしファイルサイズが奇数であれば、1バイトのヌル文字(0
)を追加してパディングします。
-
-
最終アーカイブの書き込み:
writefile(&bdst, dst, 0);
すべてのファイルが連結されたbdst
バッファの内容を、最終的なアーカイブファイル(dst
)として書き込みます。
このdopack
関数は、go tool pack
が内部で行っていたアーカイブ操作の一部を、cmd/dist
のC言語コード内で直接再現することで、外部ツールへの依存を排除しています。
関連リンク
- Go言語のセルフホスティングに関する議論や歴史:
- Go 1.5 Release Notes (Self-compilation): https://go.dev/doc/go1.5 (このコミットはGo 1.5より前のものですが、セルフホスティングの文脈で重要です)
- Unix
ar
コマンドとアーカイブフォーマット:ar
man page (Linux):man ar
- Wikipedia: https://en.wikipedia.org/wiki/Ar_(Unix)
参考にした情報源リンク
- Go言語のソースコード(特に
src/cmd/dist
ディレクトリ) - Go言語の公式ドキュメントとリリースノート
- Unixの
ar
アーカイブフォーマットに関する一般的な情報源 - Go言語のツールチェインの歴史に関するブログ記事や議論