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

[インデックス 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のコンパイラ(例: gc6g8gなど)、アセンブラ(例: 6a8aなど)、リンカ(例: 6l8lなど)といった様々なツールを呼び出し、適切な順序でビルドステップを実行します。

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のビルドプロセスにおけるアーカイブ生成の責任を再分配することにあります。

  1. コンパイラへの-packオプションの導入: 以前は、Goコンパイラ(例: 6g8g)はGoソースコードをコンパイルしてオブジェクトファイル(.o)を生成し、その後go tool packがこれらのオブジェクトファイルをアーカイブにまとめていました。この変更により、Goコンパイラ自体が-packオプションを受け取るようになり、コンパイル時に直接アーカイブファイル(.a)を生成できるようになりました。純粋なGoパッケージの場合、これによりgo tool packの呼び出しが完全に不要になります。

  2. dopack関数の導入と手動アーカイブ連結: GoパッケージがCやアセンブリ言語のソースファイルを含む場合、それらのソースは別のコンパイラ(Cコンパイラやアセンブラ)によってオブジェクトファイルにコンパイルされます。これらのオブジェクトファイルは、Goコンパイラが生成したGoのアーカイブに結合される必要があります。 このコミットでは、dopackという新しいC言語関数がsrc/cmd/dist/build.cに追加されました。この関数は、Goコンパイラが生成したベースのアーカイブファイルと、追加で結合すべきC/アセンブリのオブジェクトファイルのリストを受け取ります。 dopack関数は、arアーカイブフォーマットの構造を直接操作します。具体的には、追加される各オブジェクトファイルに対して、arアーカイブのメンバーヘッダ(ファイル名、サイズ、パーミッションなどを含む)を手動で構築し、その後にファイルの内容を連結します。これにより、外部のgo tool packコマンドを呼び出すことなく、必要なすべてのオブジェクトファイルを単一のアーカイブにまとめることができます。

このアプローチにより、cmd/distgo tool packという特定の外部コマンドに依存することなく、アーカイブ生成のロジックを内部で完結させることができます。これは、cmd/packをGo言語で再実装する際に、cmd/distがその新しいGo実装を透過的に利用できるようになるための重要なステップです。

コアとなるコードの変更箇所

変更はsrc/cmd/dist/build.cファイルに集中しています。

  1. 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;
      }
      
  2. 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アーカイブフォーマットの知識に基づいています。

  1. 初期化: Buf b, bdst;という2つのバッファを初期化します。bdstは最終的なアーカイブの内容を構築するために使われ、bは一時的に追加ファイルのコンテンツを読み込むために使われます。

  2. ベースアーカイブの読み込み: readfile(&bdst, src); Goコンパイラが-packオプションで生成したベースのアーカイブファイル(src)の内容をbdstバッファに読み込みます。これが新しいアーカイブの基盤となります。

  3. 追加ファイルの連結: for(i=0; i<nextra; i++) { ... } extra配列に含まれる各ファイル(C/アセンブリのオブジェクトファイルなど)についてループ処理を行います。

    • ファイルの読み込み: readfile(&b, extra[i]); 現在の追加ファイルの内容を一時バッファbに読み込みます。

    • メンバー名の抽出: p = xstrrchr(extra[i], '/'); q = xstrrchr(extra[i], '\\'); pqを使って、ファイルパスから最後の要素(ファイル名)を抽出します。これは、アーカイブ内のメンバー名として使用されます。

    • 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)を追加してパディングします。

  4. 最終アーカイブの書き込み: 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コマンドとアーカイブフォーマット:

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/cmd/distディレクトリ)
  • Go言語の公式ドキュメントとリリースノート
  • Unixのarアーカイブフォーマットに関する一般的な情報源
  • Go言語のツールチェインの歴史に関するブログ記事や議論