[インデックス 18575] ファイルの概要
このコミットは、Go言語のツールチェインの一部である cmd/pack
コマンドに、新しい操作モードである 'c'
(create archive) を追加するものです。この変更の主な目的は、Go 1.3リリース時に、既存のGo 1.2ビルドスクリプトが go tool pack grc
コマンドを引き続き使用できるように、後方互換性を提供することにあります。これにより、ユーザーは新しい効率的なビルド方法 (6g -pack
) への移行をより円滑に行うことができます。また、Goアーカイブファイルに特有の __.PKGDEF
エントリの扱いが改善され、アーカイブ作成のロジックがより堅牢になっています。
コミット
commit 0649a736067ddc865ac7baa011afd5c3babf813d
Author: Russ Cox <rsc@golang.org>
Date: Wed Feb 19 17:08:44 2014 -0500
cmd/pack: add 'c' command to create archive
When Go 1.3 is released, this will keep existing
Go 1.2 build scripts that use 'go tool pack grc' working.
For efficiency, such scripts should be changed to
use 6g -pack instead, but keeping the old behavior
available enables a more graceful transition.
LGTM=r
R=r
CC=golang-codereviews
https://golang.org/cl/66130043
---
src/cmd/pack/doc.go | 3 +-
src/cmd/pack/pack.go | 98 ++++++++++++++++++++++++++++++++++++++++++-----
src/cmd/pack/pack_test.go | 44 +++++++++++++++++++++
3 files changed, 135 insertions(+), 10 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0649a736067ddc865ac7baa011afd5c3babf813d
元コミット内容
cmd/pack: add 'c' command to create archive
When Go 1.3 is released, this will keep existing
Go 1.2 build scripts that use 'go tool pack grc' working.
For efficiency, such scripts should be changed to
use 6g -pack instead, but keeping the old behavior
available enables a more graceful transition.
LGTM=r
R=r
CC=golang-codereviews
https://golang.org/cl/66130043
変更の背景
この変更の背景には、Go言語のビルドシステムにおける移行期の課題がありました。Go 1.2の時代には、Goのパッケージアーカイブ(.a
ファイル)を作成するために go tool pack grc
というコマンドが一般的に使用されていました。しかし、Go 1.3のリリースに向けて、ビルドプロセスの効率化と統合が進められ、コンパイラ (6g
や gc
) が直接アーカイブを作成する機能 (-pack
フラグ) を持つようになりました。これにより、go tool pack
を介さずに、より直接的かつ効率的にアーカイブを作成できるようになります。
しかし、多くの既存のビルドスクリプトやMakefileが go tool pack grc
に依存していたため、Go 1.3へのアップグレードによってこれらのスクリプトが動作しなくなる可能性がありました。このコミットは、go tool pack
に新しい 'c'
コマンドを追加し、さらに grc
という文字列を 'c'
のエイリアスとして認識させることで、Go 1.2時代のビルドスクリプトがGo 1.3環境でも引き続き機能するように互換性を維持することを目的としています。これにより、ユーザーは既存のスクリプトをすぐに変更する必要がなくなり、新しい効率的なビルド方法への移行を段階的に行うことが可能になります。
前提知識の解説
go tool pack
: Go言語の初期のバージョン(Go 1.0からGo 1.3頃まで)で、Goのパッケージアーカイブ(.a
ファイル)を操作するために使用されたコマンドラインツールです。Unixのar
(archiver) コマンドに似た機能を提供し、ファイルの追加、抽出、リスト表示などを行いました。Go 1.3以降、その機能の多くはgo build
やコンパイラ自体に統合され、go tool pack
の直接的な使用は推奨されなくなりましたが、後方互換性のために残されました。- Goパッケージアーカイブ (
.a
ファイル): Go言語のコンパイル済みパッケージやオブジェクトファイルが格納されるアーカイブファイルです。C/C++における静的ライブラリ(.lib
や.a
)に相当します。Goのビルドプロセスでは、依存関係にあるパッケージがこれらのアーカイブとしてコンパイルされ、最終的な実行可能ファイルにリンクされます。 6g
(またはgc
): Go言語のコンパイラです。Go 1.5以前は、ターゲットアーキテクチャに応じて6g
(amd64),8g
(386),5g
(arm) のように命名されていましたが、Go 1.5でツールチェインがGo自身で書かれるようになり、gc
という単一のコマンドに統合されました。このコミットが作成された2014年時点では6g
が主流でした。-pack
フラグは、コンパイルと同時に結果をアーカイブに直接書き込む機能を提供します。go tool
: Go言語の標準ツールチェインに含まれる様々な補助ツールを実行するためのコマンドです。例えば、go tool vet
(静的解析),go tool pprof
(プロファイリング) などがあります。go tool pack
もその一つでした。__.PKGDEF
: Goのパッケージアーカイブ(.a
ファイル)内に含まれる特別なエントリです。これはパッケージの定義情報(エクスポートされた型、関数、変数など)を格納しており、リンカがパッケージ間の依存関係を解決するために使用します。Goのオブジェクトファイル(.o
ファイル)の先頭に埋め込まれたメタデータとして存在し、アーカイブ作成時に抽出されて__.PKGDEF
という名前のエントリとしてアーカイブに格納されます。
技術的詳細
このコミットは、cmd/pack
ツールに新しいアーカイブ作成機能を追加し、既存のビルドワークフローとの互換性を確保するための複数の技術的変更を含んでいます。
-
'c'
コマンドの追加:src/cmd/pack/doc.go
に、新しい操作'c'
が「append files (from the file system) to a new archive」(ファイルシステムからファイルを新しいアーカイブに追加する)として記述されました。これは、既存の'r'
コマンド(既存のアーカイブにファイルを追記する)とは異なり、常に新しいアーカイブを作成する点が重要です。src/cmd/pack/pack.go
のmain
関数内のswitch
ステートメントにcase 'c'
が追加されました。このケースでは、os.O_TRUNC
フラグ(ファイルが存在する場合にその内容を切り詰めて空にする)を指定してアーカイブファイルを開くことで、新しいアーカイブが作成されることを保証しています。ar.addPkgdef()
が呼び出され、アーカイブに__.PKGDEF
エントリが追加されます。ar.addFiles()
が呼び出され、指定されたファイルがアーカイブに追加されます。
-
grc
エイリアスのサポート:src/cmd/pack/pack.go
のsetOp
関数に、入力引数arg
が"grc"
の場合にarg
を"c"
に書き換えるロジックが追加されました。これにより、Go 1.2時代のgo tool pack grc
というコマンドが、内部的には新しい'c'
コマンドとして処理され、後方互換性が実現されます。
-
__.PKGDEF
の処理の改善:- Goのパッケージアーカイブは、パッケージのメタデータを含む
__.PKGDEF
という特別なエントリを持つ必要があります。このコミットでは、addPkgdef
およびreadPkgdef
という新しい関数が導入され、__.PKGDEF
の扱いがより明示的かつ堅牢になりました。 addPkgdef
関数は、アーカイブに追加される最初のGoオブジェクトファイルから__.PKGDEF
データを抽出し、それをアーカイブの先頭に__.PKGDEF
という名前のエントリとして追加します。これは、リンカがアーカイブを正しく解釈するために不可欠です。readPkgdef
関数は、Goオブジェクトファイル(.o
ファイル)を読み込み、その先頭にあるgo object ...
で始まり!
で終わるセクションを__.PKGDEF
データとして抽出します。これは、Goのコンパイラが出力するオブジェクトファイルのフォーマットに依存しています。
- Goのパッケージアーカイブは、パッケージのメタデータを含む
-
アーカイブエントリ書き込みの抽象化:
addFile
関数内のアーカイブエントリのヘッダ書き込みとパディング処理が、startFile
とendFile
という新しいヘルパー関数に分割されました。startFile
は、ファイル名、タイムスタンプ、パーミッション、サイズなどのエントリヘッダをアーカイブに書き込みます。endFile
は、ファイルサイズが奇数バイトの場合に必要なパディングバイト(0x00)を書き込みます。これにより、アーカイブ内の各エントリが偶数バイトでアラインされるというGoアーカイブフォーマットの要件が満たされます。このパディングは、Archive
構造体に新しく追加されたpad
フィールドによって管理されます。
-
テストの追加:
src/cmd/pack/pack_test.go
にTestHello
という新しいテストケースが追加されました。このテストは、go tool pack c
(またはgrc
) を使用してGoソースファイルからアーカイブを作成し、そのアーカイブがGoコンパイラ (6g
) とリンカ (6l
) によって正しく処理され、最終的に実行可能なバイナリが生成されることを検証します。これは、新しい'c'
コマンドがGoのビルドエコシステム全体と正しく連携することを確認するための重要な統合テストです。
これらの変更により、cmd/pack
はGo 1.3以降のビルドシステムにおける役割を調整しつつ、既存のGo 1.2ベースのビルドスクリプトとの互換性を維持するという、重要な移行期の要件を満たしています。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルと箇所に集中しています。
src/cmd/pack/doc.go
:c
コマンドのドキュメントが追加されました。v
(verbose) オプションの説明がc
コマンドにも適用されるように更新されました。
src/cmd/pack/pack.go
:main
関数:case 'c':
ブロックが追加され、新しいアーカイブの作成ロジック (os.O_RDWR|os.O_TRUNC
フラグでのファイルオープン、addPkgdef
、addFiles
の呼び出し) が実装されました。
setOp
関数:if arg == "grc"
の条件が追加され、grc
がc
のエイリアスとして扱われるようになりました。switch r
ステートメントに'c'
が追加され、有効な操作文字として認識されるようになりました。
Archive
構造体:pad int
フィールドが追加され、ファイルエントリのパディング管理に使用されます。
archive
関数:os.O_TRUNC
フラグの扱いが変更され、新しいアーカイブ作成時のファイルオープンロジックが調整されました。
addFile
関数:- ファイルヘッダの書き込みとパディング処理が
startFile
とendFile
に委譲されました。
- ファイルヘッダの書き込みとパディング処理が
startFile
関数 (新規追加):- アーカイブエントリのヘッダを書き込むための関数です。
endFile
関数 (新規追加):- アーカイブエントリの末尾にパディングバイトを書き込むための関数です。
addPkgdef
関数 (新規追加):- Goオブジェクトファイルから
__.PKGDEF
を抽出し、アーカイブに追加するための関数です。
- Goオブジェクトファイルから
readPkgdef
関数 (新規追加):- Goオブジェクトファイルから
__.PKGDEF
データを実際に読み取るためのヘルパー関数です。
- Goオブジェクトファイルから
src/cmd/pack/pack_test.go
:TestHello
関数 (新規追加):pack
ツールが作成したアーカイブが、Goのコンパイラとリンカによって正しく処理されることを検証する統合テストです。
コアとなるコードの解説
src/cmd/pack/pack.go
の主要な変更点
-
main
関数における'c'
オプションのハンドリング:case 'c': ar = archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:]) ar.addPkgdef() ar.addFiles()
このブロックは、
pack
コマンドが'c'
オプションで実行された際の挙動を定義します。archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:])
: 指定されたアーカイブファイル名 (os.Args[2]
) でArchive
オブジェクトを初期化します。os.O_TRUNC
フラグが重要で、これによりファイルが既に存在してもその内容が破棄され、新しい空のアーカイブとして扱われます。ar.addPkgdef()
: 新しく追加された関数で、アーカイブに__.PKGDEF
エントリを追加します。これはGoのアーカイブが正しく機能するために必須のメタデータです。ar.addFiles()
: コマンドラインで指定されたファイル (os.Args[3:]
) をアーカイブに追加します。
-
setOp
関数におけるgrc
エイリアス:func setOp(arg string) { // Recognize 'go tool pack grc' because that was the // formerly canonical way to build a new archive // from a set of input files. Accepting it keeps old // build systems working with both Go 1.2 and Go 1.3. if arg == "grc" { arg = "c" } // ... (既存のロジック) switch r { case 'c', 'p', 'r', 't', 'x': // 'c' が追加された // ... } }
この変更により、
go tool pack grc
という古い形式のコマンドが、内部的にgo tool pack c
と同じ処理パスを通るようになります。これはGo 1.2からの後方互換性を維持するための重要な部分です。 -
Archive
構造体とファイル書き込みの抽象化:type Archive struct { fd *os.File // Open file descriptor. files []string // Explicit list of files to be processed. pad int // Padding bytes required at end of current archive file } // startFile writes the archive entry header. func (ar *Archive) startFile(name string, mtime int64, uid, gid int, mode os.FileMode, size int64) { n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size) if err != nil || n != entryLen { log.Fatal("writing entry header: ", err) } ar.pad = int(size & 1) // ファイルサイズが奇数なら1、偶数なら0 } // endFile writes the archive entry tail (a single byte of padding, if the file size was odd). func (ar *Archive) endFile() { if ar.pad != 0 { _, err := ar.fd.Write([]byte{0}) // 1バイトのパディング(0x00)を書き込む if err != nil { log.Fatal("writing archive: ", err) } ar.pad = 0 } }
Archive
構造体にpad
フィールドが追加され、ファイルエントリのパディング状態を管理します。startFile
はアーカイブエントリのヘッダを書き込み、ファイルサイズに基づいてパディングが必要かどうかをar.pad
に設定します。endFile
は、ar.pad
が1の場合に1バイトのヌルバイトを書き込み、Goアーカイブフォーマットの偶数バイトアラインメント要件を満たします。これにより、addFile
関数はよりシンプルになり、ヘッダとパディングのロジックが分離されました。 -
addPkgdef
とreadPkgdef
による__.PKGDEF
処理:// addPkgdef adds the __.PKGDEF file to the archive, copied // from the first Go object file on the file list, if any. // The archive is known to be empty. func (ar *Archive) addPkgdef() { for _, file := range ar.files { pkgdef, err := readPkgdef(file) if err != nil { continue // Goオブジェクトファイルでなければスキップ } // ... (verbose出力) ar.startFile("__.PKGDEF", 0, 0, 0, 0644, int64(len(pkgdef))) _, err = ar.fd.Write(pkgdef) // ... (エラーハンドリング) ar.endFile() break // 最初のGoオブジェクトファイルから抽出したら終了 } } // readPkgdef extracts the __.PKGDEF data from a Go object file. func readPkgdef(file string) (data []byte, err error) { f, err := os.Open(file) // ... (エラーハンドリング、defer f.Close()) var buf bytes.Buffer scan := bufio.NewScanner(f) for scan.Scan() { line := scan.Text() if buf.Len() == 0 && !strings.HasPrefix(line, "go object ") { return nil, errors.New("not a Go object file") // Goオブジェクトファイルではない } if line == "!" { break // "!" でヘッダの終わり } buf.WriteString(line) buf.WriteString("\n") } return buf.Bytes(), nil }
addPkgdef
は、アーカイブに追加されるファイルリストの中から最初のGoオブジェクトファイルを見つけ、そこから__.PKGDEF
データを抽出してアーカイブの先頭に書き込みます。readPkgdef
は、Goオブジェクトファイルの特定のフォーマット(go object
で始まり!
で終わるヘッダ部分)を解析し、その内容を__.PKGDEF
データとして返します。これにより、Goのアーカイブがリンカによって正しく認識されるために必要なメタデータが確実に含まれるようになります。
src/cmd/pack/pack_test.go
の主要な変更点
TestHello
関数:
このテストは、func TestHello(t *testing.T) { // ... (一時ディレクトリとhello.goファイルの作成) run := func(args ...string) string { cmd := exec.Command(args[0], args[1:]...) cmd.Dir = dir out, err := cmd.CombinedOutput() // ... (エラーハンドリング) return string(out) } // ... (GOCHARの取得) run("go", "build", "cmd/pack") // packバイナリをビルド run("go", "tool", char+"g", "hello.go") // hello.goをコンパイル (hello.<GOCHAR> が生成される) run("./pack", "grc", "hello.a", "hello."+char) // pack grc でアーカイブを作成 run("go", "tool", char+"l", "-o", "a.out", "hello.a") // リンカで実行可能ファイルを生成 out = run("./a.out") // 実行 if out != "hello world\\n" { t.Fatalf("incorrect output: %q, want %q", out, "hello world\\n", out, "hello world\\n") } }
go tool pack grc
(またはc
) を使用してGoのソースファイルからアーカイブを作成し、そのアーカイブがGoのコンパイラ (6g
) とリンカ (6l
) によって正しく処理され、最終的に期待される出力 ("hello world\n"
) を生成する実行可能ファイルが作成されることをエンドツーエンドで検証します。これは、新しいc
コマンドとgrc
エイリアスがGoのビルドシステム全体とシームレスに統合されていることを確認するための重要なテストです。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go 1.3 リリースノート (関連情報が含まれる可能性): https://golang.org/doc/go1.3
- Unix
ar
コマンド (Goのpack
ツールが影響を受けた可能性のあるアーカイブフォーマット): https://en.wikipedia.org/wiki/Ar_(Unix)
参考にした情報源リンク
- コミットメッセージと差分: https://github.com/golang/go/commit/0649a736067ddc865ac7baa011afd5c3babf813d
- Go言語のツールチェインに関する一般的な知識 (Goの公式ドキュメントやブログ記事など)
- Goのアーカイブフォーマットや
__.PKGDEF
に関する情報 (Goのソースコードや関連する議論など) go tool pack
の歴史的背景に関する情報 (Goのメーリングリストや古いドキュメントなど)