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

[インデックス 12296] ファイルの概要

このコミットは、Go言語のバイナリディストリビューションパッケージングスクリプトを、既存のBashスクリプト(dist.bash)からGo言語で書かれた新しいツール(bindist.go)に移行するものです。これにより、GoのディストリビューションプロセスがGo自身の言語で統一され、クロスプラットフォームでのビルドとパッケージングの信頼性と保守性が向上します。特に、macOS (Darwin) および Linux/FreeBSD 向けのパッケージ作成ロジックがGoコードに集約されました。

コミット

commit 984780a589fa46197674a6b6e56a4647954ec08a
Author: Andrew Gerrand <adg@golang.org>
Date:   Thu Mar 1 15:49:37 2012 +1100

    misc/dist: implement binary distribution scripts in go
    
    R=golang-dev, r, alex.brainman, r, mike.rosset
    CC=golang-dev
    https://golang.org/cl/5697050

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/984780a589fa46197674a6b6e56a4647954ec08a

元コミット内容

misc/dist: implement binary distribution scripts in go

このコミットは、Go言語のバイナリ配布スクリプトをGo言語で実装し直すものです。

変更の背景

Goプロジェクトでは、様々なオペレーティングシステム(OS)とアーキテクチャ向けにGoコンパイラとツールチェインのバイナリディストリビューションを提供しています。以前は、これらのバイナリパッケージを作成するために、プラットフォーム固有のBashスクリプト(例: misc/dist/darwin/dist.bash, misc/dist/linux/dist.bash)が使用されていました。

しかし、シェルスクリプトはプラットフォーム間の差異を吸収するのが難しく、エラーハンドリングや複雑なロジックの実装において限界がありました。また、Goプロジェクトのツールチェイン自体をGo言語で記述するという哲学に沿うため、ディストリビューションスクリプトもGo言語に移行することが望まれました。

この変更の主な目的は以下の通りです。

  1. クロスプラットフォーム対応の強化: Go言語のクロスコンパイル能力を活用し、単一のGoプログラムで複数のOS/アーキテクチャ向けのパッケージを作成できるようにする。
  2. 保守性の向上: シェルスクリプトよりもGo言語の方が、複雑なロジックの実装、テスト、デバッグが容易であり、長期的な保守性が向上する。
  3. 一貫性の確保: Goプロジェクトのツールチェイン全体をGo言語で統一することで、開発体験とコードベースの一貫性を高める。
  4. エラーハンドリングの改善: Go言語の強力なエラーハンドリング機構により、パッケージングプロセスにおけるエラーをより堅牢に処理できる。
  5. Google Codeへのアップロード機能の統合: 当時Goのバイナリ配布に利用されていたGoogle CodeのファイルアップロードAPIを直接Goツールに組み込むことで、配布プロセスを自動化する。

前提知識の解説

Mercurial (hg)

Mercurial(略称 hg)は、Gitと同様の分散型バージョン管理システム(DVCS)です。このコミットが作成された2012年当時、GoプロジェクトはMercurialを使用してソースコードを管理していました。コミットメッセージやコード内のhg clonehg updateといったコマンドは、Mercurialリポジトリの操作を示しています。Goプロジェクトは後にGitに移行しましたが、このコミットの時点ではMercurialが主流でした。

PackageMaker

PackageMakerは、Appleが提供していたmacOSアプリケーション開発ツールの一部で、macOS向けのインストーラパッケージ(.pkgファイル)を作成するためのツールです。このコミットのbindist.go内でPackageMakerが呼び出されているのは、macOS向けのGoバイナリをインストール可能な形式でパッケージングするためです。PackageMakerはXcode 4.3以降で非推奨となり、現在はproductbuildなどのコマンドラインツールが後継となっています。

Google Code

Google Codeは、Googleが提供していたオープンソースプロジェクトのホスティングサービスです。このコミットが作成された2012年当時、Goプロジェクトのソースコードはcode.google.com/p/goでホストされており、バイナリディストリビューションもgo.googlecode.com/filesを通じて提供されていました。bindist.go内のuploadURLや認証情報の読み込みロジックは、このGoogle CodeのファイルアップロードAPIを利用するためのものです。Google Codeは2015年に閉鎖され、多くのプロジェクトがGitHubなどの他のプラットフォームに移行しました。

Goのビルドプロセス (make.bash, all.bash)

GoのソースコードからGoコンパイラや標準ライブラリをビルドする際には、src/make.bash(またはWindowsではmake.bat)スクリプトが使用されます。これはGoのブートストラッププロセスの一部であり、GoのソースコードからGoのツールチェインを構築します。all.bashは、make.bashを呼び出し、さらにテストを実行するなど、より包括的なビルドと検証のプロセスを実行します。bindist.goは、これらの既存のビルドスクリプトを内部で呼び出すことで、Goのビルドプロセスを統合しています。

技術的詳細

新しいbindist.goツールは、Go言語で書かれた単一の実行ファイルとして機能し、以下の主要なステップを実行します。

  1. 作業ディレクトリの準備: 一時ディレクトリを作成し、その中にGoのソースリポジトリをクローンします。これにより、クリーンな環境でビルドが行われます。
  2. Mercurialリポジトリの操作: 指定されたタグ(例: weekly)にリポジトリを更新します。これは、特定のバージョンのGoをビルドするために必要です。
  3. Goのビルド: クローンしたGoリポジトリのsrcディレクトリ内でmake.bash(または対応するビルドスクリプト)を実行し、Goツールチェインをビルドします。
  4. バージョン情報の取得と書き込み: ビルドされたgoコマンドを使用してバージョン文字列を取得し、VERSIONファイルとしてGoのルートディレクトリに書き込みます。これは、パッケージに含まれるバージョン情報を標準化するためです。
  5. クリーンアップ: Mercurialのメタデータファイル(.hg, .hgtags, .hgignore)や一時的なキャッシュファイルなどを削除し、配布パッケージをクリーンな状態にします。
  6. パッケージの作成:
    • Linux/FreeBSD: tarコマンドを使用して、ビルドされたGoのルートディレクトリを.tar.gz形式のアーカイブに圧縮します。
    • macOS (Darwin):
      • Goのインストールパスを/usr/local/goに配置するために、一時ディレクトリ内でファイルシステム構造を再配置します。
      • PackageMakerツールを呼び出し、Goのバイナリと関連ファイルをmacOSインストーラパッケージ(.pkgファイル)として作成します。この際、インストーラのタイトル、バージョン、識別子、スクリプト(インストール前後の処理など)が指定されます。
  7. Google Codeへのアップロード: オプションで、.gobuildkeyファイルから読み込んだ認証情報(ユーザー名とパスワード)を使用して、作成されたパッケージファイルをGoogle Codeのファイルホスティングサービスにアップロードします。これはnet/httpパッケージとmime/multipartパッケージを使用してHTTP POSTリクエストとして実装されています。

bindist.goは、os/execパッケージを使用して外部コマンド(hg, bash, tar, PackageMakerなど)を実行し、その標準出力と標準エラーをキャプチャしてエラーハンドリングを行います。また、os.Environ()とカスタムの環境変数設定ロジックを使用して、ビルドプロセスに必要なGOOS, GOARCH, GOROOT, GOROOT_FINALなどの環境変数を適切に設定します。

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

このコミットの主要な変更は、以下のファイルの追加と削除です。

  • 追加:
    • misc/dist/bindist.go: Go言語で書かれた新しいバイナリディストリビューションツール。これがこのコミットの核心です。
  • 削除:
    • misc/dist/README: 古いディストリビューションスクリプトに関する説明ファイル。
    • misc/dist/darwin/README: macOS向けディストリビューションスクリプトに関する説明ファイル。
    • misc/dist/darwin/dist.bash: macOS向けのバイナリパッケージを作成するためのBashスクリプト。
    • misc/dist/linux/dist.bash: Linux向けのバイナリパッケージを作成するためのBashスクリプト。

特にbindist.goの追加は、304行のコード追加を伴い、既存のBashスクリプト(合計131行削除)の機能を完全に置き換えています。

bindist.goの主要な構造は以下の通りです。

  • main関数: コマンドライン引数を解析し、ターゲット(例: linux-amd64, darwin-amd64)ごとにBuild構造体のDoメソッドを呼び出します。
  • Build構造体: ビルド対象のOSとアーキテクチャ、および一時的な作業ルートパスを保持します。
  • Doメソッド: 実際のビルドとパッケージングのロジックをカプセル化します。一時ディレクトリの作成、リポジトリのクローン、Goのビルド、パッケージの作成、アップロードまでの一連の処理を調整します。
  • runメソッド: 外部コマンドを実行するためのヘルパー関数。コマンドの実行、標準出力/エラーのキャプチャ、エラーハンドリングを行います。
  • envメソッド: ビルドプロセスに必要な環境変数(GOOS, GOARCH, GOROOTなど)を設定します。
  • uploadメソッド: Google CodeのファイルアップロードAPIを使用して、作成されたパッケージをアップロードします。
  • exists関数: ファイルまたはディレクトリの存在を確認するヘルパー関数。
  • readCredentials関数: ホームディレクトリの.gobuildkeyファイルからGoogle Codeの認証情報を読み込みます。

コアとなるコードの解説

bindist.goの核心は、Build構造体のDoメソッドにあります。このメソッドは、指定されたOSとアーキテクチャに対してGoのバイナリディストリビューションを作成する一連のプロセスをオーケストレートします。

func (b *Build) Do() error {
	work, err := ioutil.TempDir("", "bindist") // 一時作業ディレクトリの作成
	if err != nil {
		return err
	}
	defer os.RemoveAll(work) // 関数終了時に一時ディレクトリをクリーンアップ
	b.root = filepath.Join(work, "go") // Goのルートディレクトリパスを設定

	// Goリポジトリのクローンとタグへの更新
	_, err = b.run(work, "hg", "clone", "-q", *repo, b.root)
	if err != nil {
		return err
	}
	_, err = b.run(b.root, "hg", "update", *tag)
	if err != nil {
		return err
	}

	// Goのビルド
	_, err = b.run(filepath.Join(work, "go/src"), "bash", "make.bash")
	if err != nil {
		return err
	}

	// バージョン文字列の取得とVERSIONファイルへの書き込み
	version, err := b.run("", filepath.Join(b.root, "bin/go"), "version")
	if err != nil {
		return err
	}
	v := bytes.SplitN(version, []byte(" "), 4)
	version = bytes.Join(v[2:], []byte(" "))
	err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), version, 0644)
	if err != nil {
		return err
	}

	// Goルートのクリーンアップ(Mercurialメタデータなどを削除)
	for _, name := range cleanFiles {
		err = os.RemoveAll(filepath.Join(b.root, name))
		if err != nil {
			return err
		}
	}

	// パッケージの作成(OSによって処理を分岐)
	targ := fmt.Sprintf("go.%s.%s-%s", v[2], b.OS, b.Arch)
	switch b.OS {
	case "linux", "freebsd":
		// tar.gzアーカイブの作成
		targ += ".tar.gz"
		_, err = b.run("", "tar", "czf", targ, "-C", work, "go")
	case "darwin":
		// macOSインストーラパッケージの作成
		// ... (macOS固有のファイル配置とPackageMaker呼び出しロジック)
	}

	// Google Codeへのアップロード(認証情報がある場合)
	if err == nil && password != "" {
		err = b.upload(string(v[2]), targ)
	}
	return err
}

このコードは、Go言語の標準ライブラリ(os, os/exec, io/ioutil, path/filepath, bytes, fmt, log, net/http, mime/multipart, encoding/base64)を効果的に利用して、ファイルシステム操作、外部コマンド実行、ネットワーク通信といった複雑なタスクを処理しています。特に、runヘルパー関数は、外部プロセスからの出力をキャプチャし、エラーが発生した場合に詳細なエラーメッセージを生成することで、堅牢な実行環境を提供しています。

uploadメソッドは、Google CodeのファイルアップロードAPIと連携するために、HTTP multipart/form-dataリクエストを構築し、Basic認証ヘッダーを付加してファイルを送信します。これは、当時のGoogle CodeのAPI仕様に合わせた実装です。

全体として、このコミットはGoのディストリビューションプロセスをGo言語自体で記述するという重要な一歩を踏み出し、将来のGoツールの開発における自己ホスティングの原則を強化しました。

関連リンク

参考にした情報源リンク