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

[インデックス 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のリリースに向けて、ビルドプロセスの効率化と統合が進められ、コンパイラ (6ggc) が直接アーカイブを作成する機能 (-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 ツールに新しいアーカイブ作成機能を追加し、既存のビルドワークフローとの互換性を確保するための複数の技術的変更を含んでいます。

  1. 'c' コマンドの追加:

    • src/cmd/pack/doc.go に、新しい操作 'c' が「append files (from the file system) to a new archive」(ファイルシステムからファイルを新しいアーカイブに追加する)として記述されました。これは、既存の 'r' コマンド(既存のアーカイブにファイルを追記する)とは異なり、常に新しいアーカイブを作成する点が重要です。
    • src/cmd/pack/pack.gomain 関数内の switch ステートメントに case 'c' が追加されました。このケースでは、os.O_TRUNC フラグ(ファイルが存在する場合にその内容を切り詰めて空にする)を指定してアーカイブファイルを開くことで、新しいアーカイブが作成されることを保証しています。
    • ar.addPkgdef() が呼び出され、アーカイブに __.PKGDEF エントリが追加されます。
    • ar.addFiles() が呼び出され、指定されたファイルがアーカイブに追加されます。
  2. grc エイリアスのサポート:

    • src/cmd/pack/pack.gosetOp 関数に、入力引数 arg"grc" の場合に arg"c" に書き換えるロジックが追加されました。これにより、Go 1.2時代の go tool pack grc というコマンドが、内部的には新しい 'c' コマンドとして処理され、後方互換性が実現されます。
  3. __.PKGDEF の処理の改善:

    • Goのパッケージアーカイブは、パッケージのメタデータを含む __.PKGDEF という特別なエントリを持つ必要があります。このコミットでは、addPkgdef および readPkgdef という新しい関数が導入され、__.PKGDEF の扱いがより明示的かつ堅牢になりました。
    • addPkgdef 関数は、アーカイブに追加される最初のGoオブジェクトファイルから __.PKGDEF データを抽出し、それをアーカイブの先頭に __.PKGDEF という名前のエントリとして追加します。これは、リンカがアーカイブを正しく解釈するために不可欠です。
    • readPkgdef 関数は、Goオブジェクトファイル(.o ファイル)を読み込み、その先頭にある go object ... で始まり ! で終わるセクションを __.PKGDEF データとして抽出します。これは、Goのコンパイラが出力するオブジェクトファイルのフォーマットに依存しています。
  4. アーカイブエントリ書き込みの抽象化:

    • addFile 関数内のアーカイブエントリのヘッダ書き込みとパディング処理が、startFileendFile という新しいヘルパー関数に分割されました。
    • startFile は、ファイル名、タイムスタンプ、パーミッション、サイズなどのエントリヘッダをアーカイブに書き込みます。
    • endFile は、ファイルサイズが奇数バイトの場合に必要なパディングバイト(0x00)を書き込みます。これにより、アーカイブ内の各エントリが偶数バイトでアラインされるというGoアーカイブフォーマットの要件が満たされます。このパディングは、Archive 構造体に新しく追加された pad フィールドによって管理されます。
  5. テストの追加:

    • src/cmd/pack/pack_test.goTestHello という新しいテストケースが追加されました。このテストは、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 フラグでのファイルオープン、addPkgdefaddFiles の呼び出し) が実装されました。
    • setOp 関数:
      • if arg == "grc" の条件が追加され、grcc のエイリアスとして扱われるようになりました。
      • switch r ステートメントに 'c' が追加され、有効な操作文字として認識されるようになりました。
    • Archive 構造体:
      • pad int フィールドが追加され、ファイルエントリのパディング管理に使用されます。
    • archive 関数:
      • os.O_TRUNC フラグの扱いが変更され、新しいアーカイブ作成時のファイルオープンロジックが調整されました。
    • addFile 関数:
      • ファイルヘッダの書き込みとパディング処理が startFileendFile に委譲されました。
    • startFile 関数 (新規追加):
      • アーカイブエントリのヘッダを書き込むための関数です。
    • endFile 関数 (新規追加):
      • アーカイブエントリの末尾にパディングバイトを書き込むための関数です。
    • addPkgdef 関数 (新規追加):
      • Goオブジェクトファイルから __.PKGDEF を抽出し、アーカイブに追加するための関数です。
    • readPkgdef 関数 (新規追加):
      • Goオブジェクトファイルから __.PKGDEF データを実際に読み取るためのヘルパー関数です。
  • src/cmd/pack/pack_test.go:
    • TestHello 関数 (新規追加):
      • pack ツールが作成したアーカイブが、Goのコンパイラとリンカによって正しく処理されることを検証する統合テストです。

コアとなるコードの解説

src/cmd/pack/pack.go の主要な変更点

  1. 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:]) をアーカイブに追加します。
  2. 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からの後方互換性を維持するための重要な部分です。

  3. 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 関数はよりシンプルになり、ヘッダとパディングのロジックが分離されました。

  4. addPkgdefreadPkgdef による __.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 の主要な変更点

  1. 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のビルドシステム全体とシームレスに統合されていることを確認するための重要なテストです。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分: https://github.com/golang/go/commit/0649a736067ddc865ac7baa011afd5c3babf813d
  • Go言語のツールチェインに関する一般的な知識 (Goの公式ドキュメントやブログ記事など)
  • Goのアーカイブフォーマットや __.PKGDEF に関する情報 (Goのソースコードや関連する議論など)
  • go tool pack の歴史的背景に関する情報 (Goのメーリングリストや古いドキュメントなど)