[インデックス 18035] ファイルの概要
このコミットは、Go言語のビルドコマンド (cmd/go
) の中核をなす src/cmd/go/build.go
ファイルに対する変更です。build.go
は、Goパッケージのコンパイル、アセンブル、アーカイブ化、リンクといったビルドプロセス全体をオーケストレーションする役割を担っています。具体的には、ソースファイルの依存関係解決、適切なツールチェイン(コンパイラ、アセンブラ、リンカなど)の選択と実行、中間ファイルの管理、最終的な実行可能ファイルやライブラリの生成といった複雑なロジックが含まれています。
コミット
commit c86fc68ac6f95b2a50abe419dce6d1649adf4cd9
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 17 21:44:36 2013 -0500
cmd/go: 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/42910043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c86fc68ac6f95b2a50abe419dce6d1649adf4cd9
元コミット内容
このコミットの目的は、cmd/go
が go tool pack
の使用を避けるようにすることです。
変更の要点は以下の通りです。
- すべてのパッケージは、コンパイラに
-pack
オプションを使用するようになりました。 - 純粋なGoパッケージの場合、これだけで十分です。
- Cやアセンブリファイルなどの追加ファイルを持つパッケージの場合、追加のアーカイブエントリは
go tool pack
を呼び出す代わりに、直接(連結によって)追加できるようになりました。 - これらの変更により、
cmd/pack
をGo言語で書き直すことが可能になります。
変更の背景
このコミットの背景には、Go言語のツールチェインをよりGo言語自身で完結させるという設計思想があります。初期のGoツールチェインには、C言語で書かれたり、既存のUnixツール(例: ar
アーカイバ)をラップしたりする部分がいくつか存在しました。go tool pack
もその一つで、GoのオブジェクトファイルやC/アセンブリのオブジェクトファイルをまとめて .a
(アーカイブ) ファイルを作成・操作するためのユーティリティでした。
go tool pack
は、Goのビルドプロセスにおいて、コンパイルされたオブジェクトファイルをライブラリとしてまとめるために使用されていました。しかし、外部ツールへの依存は、ビルドプロセスの複雑性を増し、クロスコンパイルの難易度を上げ、Goツールチェイン全体のポータビリティを低下させる可能性がありました。
このコミットの主な動機は以下の通りです。
- 外部依存の削減:
go tool pack
への依存を減らすことで、Goツールチェインをより自己完結型にする。 - Go言語でのツールチェイン実装の推進:
cmd/pack
をGo言語で書き直すための基盤を整える。これにより、ツールチェインの保守性、理解しやすさ、そして将来的な機能拡張が容易になる。 - ビルドプロセスの効率化:
go tool pack
の呼び出しを減らすことで、ビルド時間の短縮や、より直接的なアーカイブ操作を可能にする。
特に、Goコンパイラが直接アーカイブを生成する機能(-pack
オプション)と、Go以外のオブジェクトファイルを既存のアーカイブに直接追記する機能の実装は、この目標達成のための重要なステップでした。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドプロセスと関連ツールの知識が必要です。
-
Goのビルドプロセス:
- Goのソースコード (
.go
ファイル) は、Goコンパイラ (go tool compile
またはgc
) によってオブジェクトファイル (.o
または.a
ファイル) にコンパイルされます。 - C言語やアセンブリ言語のソースコード (
.c
,.s
ファイル) は、Cコンパイラ (go tool cgo
やgo tool asm
) によってオブジェクトファイルにコンパイルされます。 - これらのオブジェクトファイルは、最終的にリンカ (
go tool link
またはld
) によって結合され、実行可能ファイルや共有ライブラリが生成されます。 - 中間段階で、複数のオブジェクトファイルを一つにまとめる「アーカイブ」というステップがあります。これは通常、静的ライブラリ (
.a
ファイル) の形式で行われます。
- Goのソースコード (
-
go tool pack
:- Go 1.0の時代から存在したユーティリティで、Goのビルドシステムの一部として、オブジェクトファイルをアーカイブ (
.a
ファイル) にまとめる役割を担っていました。これはUnix系のシステムにおけるar
コマンドに似た機能を提供していました。 pack
は、オブジェクトファイルをアーカイブに追加したり、アーカイブから抽出したりする機能を持っていました。
- Go 1.0の時代から存在したユーティリティで、Goのビルドシステムの一部として、オブジェクトファイルをアーカイブ (
-
.a
ファイル (アーカイブファイル):- Unix系システムで一般的な静的ライブラリの形式です。複数のオブジェクトファイル (
.o
ファイル) を一つにまとめたものです。 - リンカは、これらのアーカイブファイルから必要なオブジェクトコードを抽出し、最終的な実行可能ファイルに結合します。
- 内部的には、各オブジェクトファイルのメタデータ(ファイル名、サイズ、パーミッションなど)と実際のファイル内容が連続して格納されています。このフォーマットはAR (Archive) フォーマットとして知られています。
- Unix系システムで一般的な静的ライブラリの形式です。複数のオブジェクトファイル (
-
cmd/go
:- Goコマンドラインツールの中核であり、
go build
,go run
,go test
などのサブコマンドを実行します。 - 内部的には、Goのビルドプロセスを管理し、適切なコンパイラ、アセンブラ、リンカ、そして以前は
pack
などのツールを呼び出して、ユーザーの要求に応じたビルドを実行します。
- Goコマンドラインツールの中核であり、
このコミットは、cmd/go
が go tool pack
を呼び出す代わりに、Goコンパイラ自身にアーカイブ生成の一部を担わせ、残りの部分を cmd/go
内部で直接処理するように変更することで、外部ツールへの依存を減らそうとしています。
技術的詳細
このコミットの技術的な核心は、Goのビルドプロセスにおけるアーカイブファイルの生成方法を変更し、go tool pack
への依存を最小限に抑えることです。
1. Goコンパイラによるアーカイブ生成 (-pack
オプション)
- Goコンパイラ (
gc
) に-pack
オプションが追加されました。このオプションが指定されると、コンパイラはGoのオブジェクトファイルを生成するだけでなく、そのオブジェクトファイルを直接.a
(アーカイブ) ファイルとして出力するようになります。 - これにより、純粋なGoパッケージ(Cやアセンブリファイルを含まないパッケージ)の場合、コンパイラが生成した
.a
ファイルがそのまま最終的なパッケージアーカイブとなり、別途go tool pack
を呼び出す必要がなくなります。
2. Go以外のオブジェクトファイルの直接追記
- Cやアセンブリファイルを含むパッケージの場合、GoコンパイラはGoのオブジェクトを含むアーカイブを生成します。その後、Cやアセンブリからコンパイルされたオブジェクトファイル (
.o
ファイル) を、この既存のアーカイブに追記する必要があります。 - この追記処理は、
go tool pack
を呼び出す代わりに、src/cmd/go/build.go
内に新しく実装されたpackInternal
関数によって直接行われます。 packInternal
は、既存のアーカイブファイルを開き、新しいオブジェクトファイルのヘッダ情報(ファイル名、サイズなど)と内容を、ARアーカイブフォーマットに従って直接追記します。これにより、外部のpack
ツールを介さずに、Goのコード内でアーカイブ操作が完結します。
src/cmd/go/build.go
の具体的な変更点
builder.build
関数の変更:buildToolchain.gc
の呼び出しに、出力先のアーカイブファイル名 (a.objpkg
) が新しい引数として渡されるようになりました。これにより、コンパイラは直接そのファイルにアーカイブを書き込むことができます。buildToolchain.pack
の呼び出しが条件付きになりました。len(objects) > 0
の場合にのみpack
が呼び出されます。objects
にはGo以外のソースから生成されたオブジェクトファイルが含まれます。つまり、Go以外のオブジェクトファイルが存在する場合にのみ、アーカイブへの追記処理が必要となるため、pack
が呼び出されます。
toolchain
インターフェースとgcToolchain
の変更:toolchain
インターフェースのgc
メソッドのシグネチャが変更され、archive
という新しい引数が追加されました。gcToolchain.gc
の実装では、archive
引数が空でなければ、コンパイラの出力ファイル (ofile
) をそのarchive
に設定します。ofile
がarchive
と同じ場合(つまり、コンパイラが直接アーカイブを生成する場合)、コンパイラコマンドの引数に-pack
が追加されます。
gcToolchain.pack
メソッドの変更:- このメソッドは、アーカイブファイル (
afile
) が既に存在するかどうかをos.Stat
で確認します。 - もし存在する場合 (
appending = true
)、それはGoコンパイラによって既に作成されたアーカイブであり、Go以外のオブジェクトファイルを追記する必要があることを意味します。この場合、新しく追加されたpackInternal
関数が呼び出され、オブジェクトファイルが直接アーカイブに追記されます。 - もしアーカイブファイルが存在しない場合(例: Goソースファイルが全くなく、C/アセンブリファイルのみで構成されるパッケージの場合)、従来の
go tool pack
コマンドが呼び出され、新規にアーカイブが作成されます。
- このメソッドは、アーカイブファイル (
packInternal
関数の新規追加:- この関数は、指定されたアーカイブファイル (
afile
) に、指定されたオブジェクトファイル (ofiles
) を直接追記するロジックを実装しています。 - アーカイブファイルを
os.O_WRONLY|os.O_APPEND
モードで開き、bufio.NewWriter
を使用して効率的に書き込みます。 - 各オブジェクトファイルについて、そのファイル名、サイズ、パーミッションなどのメタデータをARアーカイブフォーマットのヘッダとして書き込み、その後にオブジェクトファイルの内容をコピーします。
- ARフォーマットの要件(例: 偶数バイトへのパディング)も考慮されています。
- この関数は、指定されたアーカイブファイル (
moveOrCopyFile
関数の導入:- これはビルドプロセスにおけるファイル操作の最適化です。
os.Rename
を試み、それが失敗した場合にcopyFile
にフォールバックします。os.Rename
は通常、同じファイルシステム内であればアトミックで高速な操作であるため、パフォーマンス向上に寄与します。
- これはビルドプロセスにおけるファイル操作の最適化です。
これらの変更により、go tool pack
の使用は、Goコンパイラがアーカイブを生成しない特殊なケース(例: Goソースファイルがないパッケージ)に限定され、ほとんどのケースでGoツールチェイン内部でアーカイブ操作が完結するようになりました。
コアとなるコードの変更箇所
src/cmd/go/build.go
の主要な変更箇所は以下の通りです。
-
import
文の追加:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -5,6 +5,7 @@ package main import ( + "bufio" "bytes" "container/heap" "errors"
bufio
パッケージが追加され、packInternal
関数での効率的なファイル書き込みに使用されます。 -
builder.build
関数内のgc
呼び出しの変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -879,7 +880,7 @@ func (b *builder) build(a *action) (err error) { // Compile Go. if len(gofiles) > 0 { - ofile, out, err := buildToolchain.gc(b, a.p, obj, inc, gofiles) + ofile, out, err := buildToolchain.gc(b, a.p, a.objpkg, obj, inc, gofiles) if len(out) > 0 { b.showOutput(a.p.Dir, a.p.ImportPath, b.processOutput(out)) if err != nil { @@ -889,7 +890,9 @@ func (b *builder) build(a *action) (err error) { if err != nil { return err } - objects = append(objects, ofile) + if ofile != a.objpkg { + objects = append(objects, ofile) + } }
buildToolchain.gc
にa.objpkg
(最終的なアーカイブファイル名) が渡されるようになりました。また、ofile
がa.objpkg
と同じ場合は、objects
リストに追加しないようになりました。これは、コンパイラが直接アーカイブを生成した場合、そのアーカイブ自体がGo以外のオブジェクトを追記する対象となるため、Goオブジェクトファイルとして別途リストに含める必要がないことを意味します。 -
builder.build
関数内のpack
呼び出しの変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -952,9 +955,15 @@ func (b *builder) build(a *action) (err error) { objects = append(objects, filepath.Join(a.p.Dir, syso))\n \t}\n \n-\t// Pack into archive in obj directory\n-\tif err := buildToolchain.pack(b, a.p, obj, a.objpkg, objects); err != nil {\n-\t\treturn err\n+\t// Pack into archive in obj directory.\n+\t// If the Go compiler wrote an archive, we only need to add the\n+\t// object files for non-Go sources to the archive.\n+\t// If the Go compiler wrote an archive and the package is entirely\n+\t// Go sources, there is no pack to execute at all.\n+\tif len(objects) > 0 {\n+\t\tif err := buildToolchain.pack(b, a.p, obj, a.objpkg, objects); err != nil {\n+\t\t\treturn err\n+\t\t}\n }\n ``` `pack` の呼び出しが `if len(objects) > 0` で囲まれました。これは、Go以外のオブジェクトファイルが存在する場合にのみ `pack` 処理が必要であることを示しています。
-
install
関数内のファイル移動処理の変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1016,7 +1025,7 @@ func (b *builder) install(a *action) (err error) { }\n \t}\n \n-\treturn b.copyFile(a, a.target, a1.target, perm)\n+\treturn b.moveOrCopyFile(a, a.target, a1.target, perm)\n }\n ``` `copyFile` の代わりに新しく追加された `moveOrCopyFile` が使用されるようになりました。
-
moveOrCopyFile
関数の新規追加:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1062,6 +1071,27 @@ func (b *builder) includeArgs(flag string, all []*action) []string {\n \treturn inc\n }\n \n +// moveOrCopyFile is like 'mv src dst' or 'cp src dst'.\n +func (b *builder) moveOrCopyFile(a *action, dst, src string, perm os.FileMode) error {\n +\tif buildN {\n +\t\tb.showcmd("", "mv %s %s", src, dst)\n +\t\treturn nil\n +\t}\n +\n +\t// If we can update the mode and rename to the dst, do it.\n +\t// Otherwise fall back to standard copy.\n +\tif err := os.Chmod(src, perm); err == nil {\n +\t\tif err := os.Rename(src, dst); err == nil {\n +\t\t\tif buildX {\n +\t\t\t\tb.showcmd("", "mv %s %s", src, dst)\n +\t\t\t}\n +\t\t\treturn nil\n +\t\t}\n +\t}\n +\n +\treturn b.copyFile(a, dst, src, perm)\n +}\n +\n // copyFile is like 'cp src dst'.\n func (b *builder) copyFile(a *action, dst, src string, perm os.FileMode) error {\n \tif buildN || buildX {
ファイル移動の最適化のためのヘルパー関数です。
-
toolchain.gc
インターフェースのシグネチャ変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1432,7 +1462,7 @@ type toolchain interface {\n // gc runs the compiler in a specific directory on a set of files\n // and returns the name of the generated output file.\n // The compiler runs in the directory dir.\n - gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, out []byte, err error)\n + gc(b *builder, p *Package, archive, obj string, importArgs []string, gofiles []string) (ofile string, out []byte, err error)\n // cc runs the toolchain's C compiler in a directory on a C file\n // to produce an output file.\n cc(b *builder, p *Package, objdir, ofile, cfile string) error
archive
という新しい引数が追加されました。 -
gcToolchain.gc
の実装変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1505,9 +1535,14 @@ func (gcToolchain) linker() string {\n return tool(archChar + "l")\n }\n \n -func (gcToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, output []byte, err error) {\n - out := "_go_." + archChar\n - ofile = obj + out\n +func (gcToolchain) gc(b *builder, p *Package, archive, obj string, importArgs []string, gofiles []string) (ofile string, output []byte, err error) {\n + if archive != "" {\n + ofile = archive\n + } else {\n + out := "_go_." + archChar\n + ofile = obj + out\n + }\n +\n gcargs := []string{"-p", p.ImportPath}\n if p.Standard && p.ImportPath == "runtime" {\n // runtime compiles with a special 6g flag to emit\n @@ -1534,6 +1569,9 @@ func (gcToolchain) gc(b *builder, p *Package, obj string, importArgs []string, g\n }\n \n args := stringList(tool(archChar+"g"), "-o", ofile, buildGcflags, gcargs, "-D", p.localPrefix, importArgs)\n + if ofile == archive {\n + args = append(args, "-pack")\n + }\n for _, f := range gofiles {\n args = append(args, mkAbs(p.Dir, f))\n }\
archive
引数に基づいて出力ファイル名 (ofile
) を設定し、ofile
がarchive
と同じ場合はコンパイラに-pack
オプションを追加しています。 -
gcToolchain.pack
の実装変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1557,7 +1595,83 @@ func (gcToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []s\n for _, f := range ofiles {\n absOfiles = append(absOfiles, mkAbs(objDir, f))\n }\n - return b.run(p.Dir, p.ImportPath, nil, tool("pack"), "grcP", b.work, mkAbs(objDir, afile), absOfiles)\n + cmd := "grcP"\n + absAfile := mkAbs(objDir, afile)\n + appending := false\n + if _, err := os.Stat(absAfile); err == nil {\n + appending = true\n + cmd = "rqP"\n + }\n +\n + cmdline := stringList("pack", cmd, b.work, absAfile, absOfiles)\n +\n + if appending {\n + if buildN || buildX {\n + b.showcmd(p.Dir, "%s # internal", joinUnambiguously(cmdline))\n + }\n + if buildN {\n + return nil\n + }\n + if err := packInternal(b, absAfile, absOfiles); err != nil {\n + b.showOutput(p.Dir, p.ImportPath, err.Error()+"\n")\n + return errPrintedOutput\n + }\n + return nil\n + }\n +\n + // Need actual pack.\n + cmdline[0] = tool("pack")\n + return b.run(p.Dir, p.ImportPath, nil, cmdline)\n }\n +\n +func packInternal(b *builder, afile string, ofiles []string) error {\n + dst, err := os.OpenFile(afile, os.O_WRONLY|os.O_APPEND, 0)\n + if err != nil {\n + return err\n + }\n + defer dst.Close() // only for error returns or panics\n + w := bufio.NewWriter(dst)\n +\n + for _, ofile := range ofiles {\n + src, err := os.Open(ofile)\n + if err != nil {\n + return err\n + }\n + fi, err := src.Stat()\n + if err != nil {\n + src.Close()\n + return err\n + }\n + // Note: Not using %-16.16s format because we care\n + // about bytes, not runes.\n + name := fi.Name()\n + if len(name) > 16 {\n + name = name[:16]\n + } else {\n + name += strings.Repeat(" ", 16-len(name))\n + }\n + size := fi.Size()\n + fmt.Fprintf(w, "%s%-12d%-6d%-6d%-8o%-10d`\\n",\n + name, 0, 0, 0, 0644, size)\n + n, err := io.Copy(w, src)\n + src.Close()\n + if err == nil && n < size {\n + err = io.ErrUnexpectedEOF\n + } else if err == nil && n > size {\n + err = fmt.Errorf("file larger than size reported by stat")\n + }\n + if err != nil {\n + return fmt.Errorf("copying %s to %s: %v", ofile, afile, err)\n + }\n + if size&1 != 0 {\n + w.WriteByte(0)\n + }\n + }\n +\n + if err := w.Flush(); err != nil {\n + return err\n + }\n + return dst.Close()\n }\n ``` `pack` メソッドは、アーカイブファイルが既に存在するかどうかをチェックし、存在する場合は新しく追加された `packInternal` 関数を呼び出してオブジェクトファイルを追記します。存在しない場合は、引き続き `go tool pack` を呼び出して新規アーカイブを作成します。
-
gccgoToolchain.gc
およびswigIntSize
のgc
呼び出しの変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1650,7 +1764,7 @@ func (gccgoToolchain) linker() string {\n return gccgoBin\n }\n \n -func (gccgoToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, output []byte, err error) {\n +func (gccgoToolchain) gc(b *builder, p *Package, archive, obj string, importArgs []string, gofiles []string) (ofile string, output []byte, err error) {\n out := p.Name + ".o"\n ofile = obj + out\n gcargs := []string{"-g"}\
--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -2220,7 +2334,7 @@ func (b *builder) swigIntSize(obj string) (intsize string, err error) {\n \n p := goFilesPackage(srcs)\n \n - if _, _, e := buildToolchain.gc(b, p, obj, nil, srcs); e != nil {\n + if _, _, e := buildToolchain.gc(b, p, "", obj, nil, srcs); e != nil {\n return "32", nil\n }\n return "64", nil
これらの箇所でも
gc
メソッドのシグネチャ変更に合わせて引数が更新されています。swigIntSize
の場合は、アーカイブを生成しないため、空文字列""
が渡されています。
コアとなるコードの解説
このコミットの最も重要な変更は、gcToolchain.pack
メソッドのロジック変更と、新しく導入された packInternal
関数です。
gcToolchain.pack
の新しいロジック
以前は、Go以外のオブジェクトファイル(Cやアセンブリ)をアーカイブに追加する際も、常に go tool pack
を呼び出していました。このコミットでは、まず os.Stat(absAfile)
を使って、最終的なアーカイブファイル (.a
ファイル) が既に存在するかどうかを確認します。
-
アーカイブが既に存在する場合 (
appending = true
): これは、Goコンパイラが既に-pack
オプションを使ってGoのオブジェクトを含むアーカイブを生成済みであることを意味します。この場合、go tool pack
を呼び出す代わりに、新しく定義されたpackInternal
関数が呼び出されます。packInternal
は、Go以外のオブジェクトファイル (ofiles
) を、既存のアーカイブ (absAfile
) に直接追記します。これにより、外部ツールへの依存が回避されます。 -
アーカイブがまだ存在しない場合: これは、Goソースファイルが全くなく、Cやアセンブリファイルのみで構成されるパッケージの場合などに発生します。この場合、Goコンパイラはアーカイブを生成していないため、
go tool pack
コマンドが呼び出され、新規にアーカイブが作成されます。
packInternal
関数の詳細
packInternal
関数は、Go言語でARアーカイブフォーマットの簡易的な追記処理を実装しています。
-
アーカイブファイルのオープン:
dst, err := os.OpenFile(afile, os.O_WRONLY|os.O_APPEND, 0)
os.OpenFile
をos.O_APPEND
フラグ付きで呼び出すことで、既存のアーカイブファイルの末尾にデータを追記できるようにします。 -
オブジェクトファイルの追記ループ:
for _, ofile := range ofiles
ループで、追記対象の各オブジェクトファイルについて以下の処理を行います。-
オブジェクトファイルのオープンと情報取得:
src, err := os.Open(ofile) fi, err := src.Stat()
追記するオブジェクトファイルを開き、
os.Stat()
でファイルサイズやファイル名などのメタデータを取得します。 -
ARヘッダの生成:
name := fi.Name() if len(name) > 16 { name = name[:16] } else { name += strings.Repeat(" ", 16-len(name)) } size := fi.Size() fmt.Fprintf(w, "%s%-12d%-6d%-6d%-8o%-10d`\\n", name, 0, 0, 0, 0644, size)
ARアーカイブフォーマットでは、各ファイルの内容の前に固定長のヘッダが付きます。このヘッダには、ファイル名(16バイトにパディング)、ファイルサイズ、パーミッションなどの情報が含まれます。
packInternal
は、これらの情報を整形して書き込みます。特に、ファイル名が16バイトを超える場合は切り詰め、短い場合はスペースでパディングします。 -
オブジェクトファイル内容のコピー:
n, err := io.Copy(w, src)
io.Copy
を使って、オブジェクトファイルの内容をアーカイブファイルに直接コピーします。 -
パディングの処理:
if size&1 != 0 { w.WriteByte(0) }
ARアーカイブフォーマットの慣例として、各ファイルのエントリは偶数バイト境界で終わる必要があります。オブジェクトファイルのサイズが奇数の場合、1バイトのヌル文字 (
0
) を追記してパディングします。
-
この packInternal
関数により、Goツールチェインは go tool pack
に頼ることなく、Go以外のオブジェクトファイルを既存のアーカイブに効率的に追記できるようになりました。これは、Goツールチェインの自己完結性を高め、将来的な cmd/pack
のGo言語での再実装への道を開く重要な一歩です。
関連リンク
- Go Issue/Change List (CL):
- https://golang.org/cl/42910043 - このコミットに対応するGoのChange Listページです。詳細なレビューコメントや変更履歴を確認できます。
参考にした情報源リンク
- Go言語の公式ドキュメント: Goのビルドプロセスやツールチェインに関する一般的な情報。
- AR (ファイルフォーマット): Wikipediaやその他の技術文書でARアーカイブフォーマットの仕様を確認しました。
packInternal
の実装がこのフォーマットに準拠していることを理解するために必要でした。 - Goのソースコード:
src/cmd/go/build.go
および関連ファイルの変更履歴とコメント。 - Goのメーリングリストやフォーラム: 過去の議論で
go tool pack
の役割やGoツールチェインの進化に関する情報。 - Goのビルドシステムに関するブログ記事や解説: Goのビルドプロセスの内部動作を解説している記事。
- 例: "Inside the Go build system" (特定の記事を指すものではなく、一般的な情報源として)
[インデックス 18035] ファイルの概要
このコミットは、Go言語のビルドコマンド (cmd/go
) の中核をなす src/cmd/go/build.go
ファイルに対する変更です。build.go
は、Goパッケージのコンパイル、アセンブル、アーカイブ化、リンクといったビルドプロセス全体をオーケストレーションする役割を担っています。具体的には、ソースファイルの依存関係解決、適切なツールチェイン(コンパイラ、アセンブラ、リンカなど)の選択と実行、中間ファイルの管理、最終的な実行可能ファイルやライブラリの生成といった複雑なロジックが含まれています。
コミット
commit c86fc68ac6f95b2a50abe419dce6d1649adf4cd9
Author: Russ Cox <rsc@golang.org>
Date: Tue Dec 17 21:44:36 2013 -0500
cmd/go: 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/42910043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c86fc68ac6f95b2a50abe419dce6d1649adf4cd9
元コミット内容
このコミットの目的は、cmd/go
が go tool pack
の使用を避けるようにすることです。
変更の要点は以下の通りです。
- すべてのパッケージは、コンパイラに
-pack
オプションを使用するようになりました。 - 純粋なGoパッケージの場合、これだけで十分です。
- Cやアセンブリファイルなどの追加ファイルを持つパッケージの場合、追加のアーカイブエントリは
go tool pack
を呼び出す代わりに、直接(連結によって)追加できるようになりました。 - これらの変更により、
cmd/pack
をGo言語で書き直すことが可能になります。
変更の背景
このコミットの背景には、Go言語のツールチェインをよりGo言語自身で完結させるという設計思想があります。初期のGoツールチェインには、C言語で書かれたり、既存のUnixツール(例: ar
アーカイバ)をラップしたりする部分がいくつか存在しました。go tool pack
もその一つで、Goのビルドプロセスにおいて、コンパイルされたオブジェクトファイルやC/アセンブリのオブジェクトファイルをまとめて .a
(アーカイブ) ファイルを作成するためのユーティリティでした。
go tool pack
は、Goのビルドプロセスにおいて、コンパイルされたオブジェクトファイルをライブラリとしてまとめるために使用されていました。しかし、外部ツールへの依存は、ビルドプロセスの複雑性を増し、クロスコンパイルの難易度を上げ、Goツールチェイン全体のポータビリティを低下させる可能性がありました。
このコミットの主な動機は以下の通りです。
- 外部依存の削減:
go tool pack
への依存を減らすことで、Goツールチェインをより自己完結型にする。 - Go言語でのツールチェイン実装の推進:
cmd/pack
をGo言語で書き直すための基盤を整える。これにより、ツールチェインの保守性、理解しやすさ、そして将来的な機能拡張が容易になる。 - ビルドプロセスの効率化:
go tool pack
の呼び出しを減らすことで、ビルド時間の短縮や、より直接的なアーカイブ操作を可能にする。
特に、Goコンパイラが直接アーカイブを生成する機能(-pack
オプション)と、Go以外のオブジェクトファイルを既存のアーカイブに直接追記する機能の実装は、この目標達成のための重要なステップでした。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドプロセスと関連ツールの知識が必要です。
-
Goのビルドプロセス:
- Goのソースコード (
.go
ファイル) は、Goコンパイラ (go tool compile
またはgc
) によってオブジェクトファイル (.o
または.a
ファイル) にコンパイルされます。 - C言語やアセンブリ言語のソースコード (
.c
,.s
ファイル) は、Cコンパイラ (go tool cgo
やgo tool asm
) によってオブジェクトファイルにコンパイルされます。 - これらのオブジェクトファイルは、最終的にリンカ (
go tool link
またはld
) によって結合され、実行可能ファイルや共有ライブラリが生成されます。 - 中間段階で、複数のオブジェクトファイルを一つにまとめる「アーカイブ」というステップがあります。これは通常、静的ライブラリ (
.a
ファイル) の形式で行われます。
- Goのソースコード (
-
go tool pack
:- Go 1.0の時代から存在したユーティリティで、Goのビルドシステムの一部として、オブジェクトファイルをアーカイブ (
.a
ファイル) にまとめる役割を担っていました。これはUnix系のシステムにおけるar
コマンドに似た機能を提供していました。 pack
は、オブジェクトファイルをアーカイブに追加したり、アーカイブから抽出したりする機能を持っていました。
- Go 1.0の時代から存在したユーティリティで、Goのビルドシステムの一部として、オブジェクトファイルをアーカイブ (
-
.a
ファイル (アーカイブファイル):- Unix系システムで一般的な静的ライブラリの形式です。複数のオブジェクトファイル (
.o
ファイル) を一つにまとめたものです。 - リンカは、これらのアーカイブファイルから必要なオブジェクトコードを抽出し、最終的な実行可能ファイルに結合します。
- 内部的には、各オブジェクトファイルのメタデータ(ファイル名、サイズ、パーミッションなど)と実際のファイル内容が連続して格納されています。このフォーマットはAR (Archive) フォーマットとして知られています。
- Unix系システムで一般的な静的ライブラリの形式です。複数のオブジェクトファイル (
-
cmd/go
:- Goコマンドラインツールの中核であり、
go build
,go run
,go test
などのサブコマンドを実行します。 - 内部的には、Goのビルドプロセスを管理し、適切なコンパイラ、アセンブラ、リンカ、そして以前は
pack
などのツールを呼び出して、ユーザーの要求に応じたビルドを実行します。
- Goコマンドラインツールの中核であり、
このコミットは、cmd/go
が go tool pack
を呼び出す代わりに、Goコンパイラ自身にアーカイブ生成の一部を担わせ、残りの部分を cmd/go
内部で直接処理するように変更することで、外部ツールへの依存を減らそうとしています。
技術的詳細
このコミットの技術的な核心は、Goのビルドプロセスにおけるアーカイブファイルの生成方法を変更し、go tool pack
への依存を最小限に抑えることです。
1. Goコンパイラによるアーカイブ生成 (-pack
オプション)
- Goコンパイラ (
gc
) に-pack
オプションが追加されました。このオプションが指定されると、コンパイラはGoのオブジェクトファイルを生成するだけでなく、そのオブジェクトファイルを直接.a
(アーカイブ) ファイルとして出力するようになります。 - これにより、純粋なGoパッケージ(Cやアセンブリファイルを含まないパッケージ)の場合、コンパイラが生成した
.a
ファイルがそのまま最終的なパッケージアーカイブとなり、別途go tool pack
を呼び出す必要がなくなります。
2. Go以外のオブジェクトファイルの直接追記
- Cやアセンブリファイルを含むパッケージの場合、GoコンパイラはGoのオブジェクトを含むアーカイブを生成します。その後、Cやアセンブリからコンパイルされたオブジェクトファイル (
.o
ファイル) を、この既存のアーカイブに追記する必要があります。 - この追記処理は、
go tool pack
を呼び出す代わりに、src/cmd/go/build.go
内に新しく実装されたpackInternal
関数によって直接行われます。 packInternal
は、既存のアーカイブファイルを開き、新しいオブジェクトファイルのヘッダ情報(ファイル名、サイズなど)と内容を、ARアーカイブフォーマットに従って直接追記します。これにより、外部のpack
ツールを介さずに、Goのコード内でアーカイブ操作が完結します。
src/cmd/go/build.go
の具体的な変更点
builder.build
関数の変更:buildToolchain.gc
の呼び出しに、出力先のアーカイブファイル名 (a.objpkg
) が新しい引数として渡されるようになりました。これにより、コンパイラは直接そのファイルにアーカイブを書き込むことができます。buildToolchain.pack
の呼び出しが条件付きになりました。len(objects) > 0
の場合にのみpack
が呼び出されます。objects
にはGo以外のソースから生成されたオブジェクトファイルが含まれます。つまり、Go以外のオブジェクトファイルが存在する場合にのみ、アーカイブへの追記処理が必要となるため、pack
が呼び出されます。
toolchain
インターフェースとgcToolchain
の変更:toolchain
インターフェースのgc
メソッドのシグネチャが変更され、archive
という新しい引数が追加されました。gcToolchain.gc
の実装では、archive
引数が空でなければ、コンパイラの出力ファイル (ofile
) をそのarchive
に設定します。ofile
がarchive
と同じ場合(つまり、コンパイラが直接アーカイブを生成する場合)、コンパイラコマンドの引数に-pack
が追加されます。
gcToolchain.pack
メソッドの変更:- このメソッドは、アーカイブファイル (
afile
) が既に存在するかどうかをos.Stat
で確認します。 - もし存在する場合 (
appending = true
)、それはGoコンパイラによって既に作成されたアーカイブであり、Go以外のオブジェクトファイルを追記する必要があることを意味します。この場合、新しく追加されたpackInternal
関数が呼び出され、オブジェクトファイルが直接アーカイブに追記されます。 - もしアーカイブファイルが存在しない場合(例: Goソースファイルが全くなく、C/アセンブリファイルのみで構成されるパッケージの場合)、従来の
go tool pack
コマンドが呼び出され、新規にアーカイブが作成されます。
- このメソッドは、アーカイブファイル (
packInternal
関数の新規追加:- この関数は、指定されたアーカイブファイル (
afile
) に、指定されたオブジェクトファイル (ofiles
) を直接追記するロジックを実装しています。 - アーカイブファイルを
os.O_WRONLY|os.O_APPEND
モードで開き、bufio.NewWriter
を使用して効率的に書き込みます。 - 各オブジェクトファイルについて、そのファイル名、サイズ、パーミッションなどのメタデータをARアーカイブフォーマットのヘッダとして書き込み、その後にオブジェクトファイルの内容をコピーします。
- ARフォーマットの要件(例: 偶数バイトへのパディング)も考慮されています。
- この関数は、指定されたアーカイブファイル (
moveOrCopyFile
関数の導入:- これはビルドプロセスにおけるファイル操作の最適化です。
os.Rename
を試み、それが失敗した場合にcopyFile
にフォールバックします。os.Rename
は通常、同じファイルシステム内であればアトミックで高速な操作であるため、パフォーマンス向上に寄与します。
- これはビルドプロセスにおけるファイル操作の最適化です。
これらの変更により、go tool pack
の使用は、Goコンパイラがアーカイブを生成しない特殊なケース(例: Goソースファイルがないパッケージ)に限定され、ほとんどのケースでGoツールチェイン内部でアーカイブ操作が完結するようになりました。
コアとなるコードの変更箇所
src/cmd/go/build.go
の主要な変更箇所は以下の通りです。
-
import
文の追加:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -5,6 +5,7 @@ package main import ( + "bufio" "bytes" "container/heap" "errors"
bufio
パッケージが追加され、packInternal
関数での効率的なファイル書き込みに使用されます。 -
builder.build
関数内のgc
呼び出しの変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -879,7 +880,7 @@ func (b *builder) build(a *action) (err error) { // Compile Go. if len(gofiles) > 0 { - ofile, out, err := buildToolchain.gc(b, a.p, obj, inc, gofiles) + ofile, out, err := buildToolchain.gc(b, a.p, a.objpkg, obj, inc, gofiles) if len(out) > 0 { b.showOutput(a.p.Dir, a.p.ImportPath, b.processOutput(out)) if err != nil { @@ -889,7 +890,9 @@ func (b *builder) build(a *action) (err error) { if err != nil { return err } - objects = append(objects, ofile) + if ofile != a.objpkg { + objects = append(objects, ofile) + } }
buildToolchain.gc
にa.objpkg
(最終的なアーカイブファイル名) が渡されるようになりました。また、ofile
がa.objpkg
と同じ場合は、objects
リストに追加しないようになりました。これは、コンパイラが直接アーカイブを生成した場合、そのアーカイブ自体がGo以外のオブジェクトを追記する対象となるため、Goオブジェクトファイルとして別途リストに含める必要がないことを意味します。 -
builder.build
関数内のpack
呼び出しの変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -952,9 +955,15 @@ func (b *builder) build(a *action) (err error) { objects = append(objects, filepath.Join(a.p.Dir, syso))\n \t}\n \n-\t// Pack into archive in obj directory\n-\tif err := buildToolchain.pack(b, a.p, obj, a.objpkg, objects); err != nil {\n-\t\treturn err\n+\t// Pack into archive in obj directory.\n+\t// If the Go compiler wrote an archive, we only need to add the\n+\t// object files for non-Go sources to the archive.\n+\t// If the Go compiler wrote an archive and the package is entirely\n+\t// Go sources, there is no pack to execute at all.\n+\tif len(objects) > 0 {\n+\t\tif err := buildToolchain.pack(b, a.p, obj, a.objpkg, objects); err != nil {\n+\t\t\treturn err\n+\t\t}\n }\n ``` `pack` の呼び出しが `if len(objects) > 0` で囲まれました。これは、Go以外のオブジェクトファイルが存在する場合にのみ `pack` 処理が必要であることを示しています。
-
install
関数内のファイル移動処理の変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1016,7 +1025,7 @@ func (b *builder) install(a *action) (err error) { }\n \t}\n \n-\treturn b.copyFile(a, a.target, a1.target, perm)\n+\treturn b.moveOrCopyFile(a, a.target, a1.target, perm)\n }\n ``` `copyFile` の代わりに新しく追加された `moveOrCopyFile` が使用されるようになりました。
-
moveOrCopyFile
関数の新規追加:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1062,6 +1071,27 @@ func (b *builder) includeArgs(flag string, all []*action) []string {\n \treturn inc\n }\n \n +// moveOrCopyFile is like 'mv src dst' or 'cp src dst'.\n +func (b *builder) moveOrCopyFile(a *action, dst, src string, perm os.FileMode) error {\n +\tif buildN {\n +\t\tb.showcmd("", "mv %s %s", src, dst)\n +\t\treturn nil\n +\t}\n +\n +\t// If we can update the mode and rename to the dst, do it.\n +\t// Otherwise fall back to standard copy.\n +\tif err := os.Chmod(src, perm); err == nil {\n +\t\tif err := os.Rename(src, dst); err == nil {\n +\t\t\tif buildX {\n +\t\t\t\tb.showcmd("", "mv %s %s", src, dst)\n +\t\t\t}\n +\t\t\treturn nil\n +\t\t}\n +\t}\n +\n +\treturn b.copyFile(a, dst, src, perm)\n +}\n +\n // copyFile is like 'cp src dst'.\n func (b *builder) copyFile(a *action, dst, src string, perm os.FileMode) error {\n \tif buildN || buildX {
ファイル移動の最適化のためのヘルパー関数です。
-
toolchain.gc
インターフェースのシグネチャ変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1432,7 +1462,7 @@ type toolchain interface {\n // gc runs the compiler in a specific directory on a set of files\n // and returns the name of the generated output file.\n // The compiler runs in the directory dir.\n - gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, out []byte, err error)\n + gc(b *builder, p *Package, archive, obj string, importArgs []string, gofiles []string) (ofile string, out []byte, err error)\n // cc runs the toolchain's C compiler in a directory on a C file\n // to produce an output file.\n cc(b *builder, p *Package, objdir, ofile, cfile string) error
archive
という新しい引数が追加されました。 -
gcToolchain.gc
の実装変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1505,9 +1535,14 @@ func (gcToolchain) linker() string {\n return tool(archChar + "l")\n }\n \n -func (gcToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, output []byte, err error) {\n - out := "_go_." + archChar\n - ofile = obj + out\n +func (gcToolchain) gc(b *builder, p *Package, archive, obj string, importArgs []string, gofiles []string) (ofile string, output []byte, err error) {\n + if archive != "" {\n + ofile = archive\n + } else {\n + out := "_go_." + archChar\n + ofile = obj + out\n + }\n +\n gcargs := []string{"-p", p.ImportPath}\n if p.Standard && p.ImportPath == "runtime" {\n // runtime compiles with a special 6g flag to emit\n @@ -1534,6 +1569,9 @@ func (gcToolchain) gc(b *builder, p *Package, obj string, importArgs []string, g\n }\n \n args := stringList(tool(archChar+"g"), "-o", ofile, buildGcflags, gcargs, "-D", p.localPrefix, importArgs)\n + if ofile == archive {\n + args = append(args, "-pack")\n + }\n for _, f := range gofiles {\n args = append(args, mkAbs(p.Dir, f))\n }\
archive
引数に基づいて出力ファイル名 (ofile
) を設定し、ofile
がarchive
と同じ場合はコンパイラに-pack
オプションを追加しています。 -
gcToolchain.pack
の実装変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1557,7 +1595,83 @@ func (gcToolchain) pack(b *builder, p *Package, objDir, afile string, ofiles []s\n for _, f := range ofiles {\n absOfiles = append(absOfiles, mkAbs(objDir, f))\n }\n - return b.run(p.Dir, p.ImportPath, nil, tool("pack"), "grcP", b.work, mkAbs(objDir, afile), absOfiles)\n +\tcmd := "grcP"\n +\tabsAfile := mkAbs(objDir, afile)\n +\tappending := false\n +\tif _, err := os.Stat(absAfile); err == nil {\n +\t\tappending = true\n +\t\tcmd = "rqP"\n +\t}\n +\n +\tcmdline := stringList("pack", cmd, b.work, absAfile, absOfiles)\n +\n +\tif appending {\n +\t\tif buildN || buildX {\n +\t\t\tb.showcmd(p.Dir, "%s # internal", joinUnambiguously(cmdline))\n +\t\t}\n +\t\tif buildN {\n +\t\t\treturn nil\n +\t\t}\n +\t\tif err := packInternal(b, absAfile, absOfiles); err != nil {\n +\t\t\tb.showOutput(p.Dir, p.ImportPath, err.Error()+"\n")\n +\t\t\treturn errPrintedOutput\n +\t\t}\n +\t\treturn nil\n +\t}\n +\n +\t// Need actual pack.\n +\tcmdline[0] = tool("pack")\n +\treturn b.run(p.Dir, p.ImportPath, nil, cmdline)\n }\n +\n +func packInternal(b *builder, afile string, ofiles []string) error {\n + dst, err := os.OpenFile(afile, os.O_WRONLY|os.O_APPEND, 0)\n + if err != nil {\n + return err\n + }\n + defer dst.Close() // only for error returns or panics\n + w := bufio.NewWriter(dst)\n +\n + for _, ofile := range ofiles {\n + src, err := os.Open(ofile)\n + if err != nil {\n + return err\n + }\n + fi, err := src.Stat()\n + if err != nil {\n + src.Close()\n + return err\n + }\n + // Note: Not using %-16.16s format because we care\n + // about bytes, not runes.\n + name := fi.Name()\n + if len(name) > 16 {\n + name = name[:16]\n + } else {\n + name += strings.Repeat(" ", 16-len(name))\n + }\n + size := fi.Size()\n + fmt.Fprintf(w, "%s%-12d%-6d%-6d%-8o%-10d`\\n",\n + name, 0, 0, 0, 0644, size)\n + n, err := io.Copy(w, src)\n + src.Close()\n + if err == nil && n < size {\n + err = io.ErrUnexpectedEOF\n + } else if err == nil && n > size {\n + err = fmt.Errorf("file larger than size reported by stat")\n + }\n + if err != nil {\n + return fmt.Errorf("copying %s to %s: %v", ofile, afile, err)\n + }\n + if size&1 != 0 {\n + w.WriteByte(0)\n + }\n + }\n +\n + if err := w.Flush(); err != nil {\n + return err\n + }\n + return dst.Close()\n }\n ``` `pack` メソッドは、アーカイブファイルが既に存在するかどうかをチェックし、存在する場合は新しく追加された `packInternal` 関数を呼び出してオブジェクトファイルを追記します。存在しない場合は、引き続き `go tool pack` を呼び出して新規アーカイブを作成します。
-
gccgoToolchain.gc
およびswigIntSize
のgc
呼び出しの変更:--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1650,7 +1764,7 @@ func (gccgoToolchain) linker() string {\n return gccgoBin\n }\n \n -func (gccgoToolchain) gc(b *builder, p *Package, obj string, importArgs []string, gofiles []string) (ofile string, output []byte, err error) {\n +func (gccgoToolchain) gc(b *builder, p *Package, archive, obj string, importArgs []string, gofiles []string) (ofile string, output []byte, err error) {\n out := p.Name + ".o"\n ofile = obj + out\n gcargs := []string{"-g"}\
--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -2220,7 +2334,7 @@ func (b *builder) swigIntSize(obj string) (intsize string, err error) {\n \n p := goFilesPackage(srcs)\n \n - if _, _, e := buildToolchain.gc(b, p, obj, nil, srcs); e != nil {\n + if _, _, e := buildToolchain.gc(b, p, "", obj, nil, srcs); e != nil {\n return "32", nil\n }\n return "64", nil
これらの箇所でも
gc
メソッドのシグネチャ変更に合わせて引数が更新されています。swigIntSize
の場合は、アーカイブを生成しないため、空文字列""
が渡されています。
コアとなるコードの解説
このコミットの最も重要な変更は、gcToolchain.pack
メソッドのロジック変更と、新しく導入された packInternal
関数です。
gcToolchain.pack
の新しいロジック
以前は、Go以外のオブジェクトファイル(Cやアセンブリ)をアーカイブに追加する際も、常に go tool pack
を呼び出していました。このコミットでは、まず os.Stat(absAfile)
を使って、最終的なアーカイブファイル (.a
ファイル) が既に存在するかどうかを確認します。
-
アーカイブが既に存在する場合 (
appending = true
): これは、Goコンパイラが既に-pack
オプションを使ってGoのオブジェクトを含むアーカイブを生成済みであることを意味します。この場合、go tool pack
を呼び出す代わりに、新しく定義されたpackInternal
関数が呼び出されます。packInternal
は、Go以外のオブジェクトファイル (ofiles
) を、既存のアーカイブ (absAfile
) に直接追記します。これにより、外部ツールへの依存が回避されます。 -
アーカイブがまだ存在しない場合: これは、Goソースファイルが全くなく、Cやアセンブリファイルのみで構成されるパッケージの場合などに発生します。この場合、Goコンパイラはアーカイブを生成していないため、
go tool pack
コマンドが呼び出され、新規にアーカイブが作成されます。
packInternal
関数の詳細
packInternal
関数は、Go言語でARアーカイブフォーマットの簡易的な追記処理を実装しています。
-
アーカイブファイルのオープン:
dst, err := os.OpenFile(afile, os.O_WRONLY|os.O_APPEND, 0)
os.OpenFile
をos.O_APPEND
フラグ付きで呼び出すことで、既存のアーカイブファイルの末尾にデータを追記できるようにします。 -
オブジェクトファイルの追記ループ:
for _, ofile := range ofiles
ループで、追記対象の各オブジェクトファイルについて以下の処理を行います。-
オブジェクトファイルのオープンと情報取得:
src, err := os.Open(ofile) fi, err := src.Stat()
追記するオブジェクトファイルを開き、
os.Stat()
でファイルサイズやファイル名などのメタデータを取得します。 -
ARヘッダの生成:
name := fi.Name() if len(name) > 16 { name = name[:16] } else { name += strings.Repeat(" ", 16-len(name)) } size := fi.Size() fmt.Fprintf(w, "%s%-12d%-6d%-6d%-8o%-10d`\\n", name, 0, 0, 0, 0644, size)
ARアーカイブフォーマットでは、各ファイルの内容の前に固定長のヘッダが付きます。このヘッダには、ファイル名(16バイトにパディング)、ファイルサイズ、パーミッションなどの情報が含まれます。
packInternal
は、これらの情報を整形して書き込みます。特に、ファイル名が16バイトを超える場合は切り詰め、短い場合はスペースでパディングします。 -
オブジェクトファイル内容のコピー:
n, err := io.Copy(w, src)
io.Copy
を使って、オブジェクトファイルの内容をアーカイブファイルに直接コピーします。 -
パディングの処理:
if size&1 != 0 { w.WriteByte(0) }
ARアーカイブフォーマットの慣例として、各ファイルのエントリは偶数バイト境界で終わる必要があります。オブジェクトファイルのサイズが奇数の場合、1バイトのヌル文字 (
0
) を追記してパディングします。
-
この packInternal
関数により、Goツールチェインは go tool pack
に頼ることなく、Go以外のオブジェクトファイルを既存のアーカイブに効率的に追記できるようになりました。これは、Goツールチェインの自己完結性を高め、将来的な cmd/pack
のGo言語での再実装への道を開く重要な一歩です。
関連リンク
- Go Issue/Change List (CL):
- https://golang.org/cl/42910043 - このコミットに対応するGoのChange Listページです。詳細なレビューコメントや変更履歴を確認できます。
参考にした情報源リンク
- Go言語の公式ドキュメント: Goのビルドプロセスやツールチェインに関する一般的な情報。
- AR (ファイルフォーマット): Wikipediaやその他の技術文書でARアーカイブフォーマットの仕様を確認しました。
packInternal
の実装がこのフォーマットに準拠していることを理解するために必要でした。 - Goのソースコード:
src/cmd/go/build.go
および関連ファイルの変更履歴とコメント。 - Goのメーリングリストやフォーラム: 過去の議論で
go tool pack
の役割やGoツールチェインの進化に関する情報。 - Goのビルドシステムに関するブログ記事や解説: Goのビルドプロセスの内部動作を解説している記事。
- 例: "Inside the Go build system" (特定の記事を指すものではなく、一般的な情報源として)