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

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

このコミットは、Go言語のディストリビューションツールである misc/dist/bindist.go に、ソースアーカイブを準備する機能を追加するものです。これにより、バイナリディストリビューションだけでなく、ソースコードのパッケージングもこのツールで行えるようになります。

コミット

commit 243ac1613e625d73fac19f45edea68b603a26346
Author: Andrew Gerrand <adg@golang.org>
Date:   Wed Mar 7 13:13:26 2012 +1100

    misc/dist: prepare source archives
    
    Fixes #95.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5756066

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

https://github.com/golang/go/commit/243ac1613e625d73fac19f45edea68b603a26346

元コミット内容

misc/dist: prepare source archives

このコミットは、Go言語の配布ツールにソースアーカイブを準備する機能を追加します。

Fixes #95.

これは、Goプロジェクトの古い課題トラッカー(code.google.com/p/go)におけるIssue #95を修正するものです。このIssueは、GoディストリビューションのZIPファイルが簡単にダウンロードできないことに関するものでした。このコミットにより、ソースコードの配布形式が改善され、より簡単に利用できるようになることが期待されます。

変更の背景

Go言語の初期のディストリビューションプロセスでは、主にコンパイル済みのバイナリパッケージの作成に焦点が当てられていました。しかし、開発者やユーザーがGoのソースコード自体をダウンロードし、ビルドしたり、特定の環境で利用したりするニーズも存在します。特に、Goのソースコードからビルドを行う場合、ビルドによって生成される中間ファイルやバイナリがソースツリー内に残ってしまうと、クリーンなソースアーカイブを作成することが困難になります。

このコミットは、このような背景から、misc/dist ツールにソースアーカイブを生成する機能を追加し、その際に不要なビルド成果物(binディレクトリやpkgディレクトリなど)を適切にクリーンアップすることで、配布に適したクリーンなソースパッケージを提供できるようにすることを目的としています。これにより、Goのソースコードからのビルドや配布がより効率的かつ容易になります。また、Fixes #95 とあるように、Goの配布形式に関する既存の課題を解決する一環でもあります。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識があると役立ちます。

  1. Go言語のビルドシステム: Go言語は、go build コマンドや make.bash (Unix系) / make.bat (Windows) スクリプトを使用して、ソースコードからバイナリをビルドします。これらのスクリプトは、Goのツールチェイン自体をビルドするためにも使用されます。
  2. GoのワークスペースとGOPATH: Go 1.0以降のバージョンでは、GOPATHという環境変数によってワークスペースが定義され、ソースコード、パッケージ、バイナリが配置される構造が推奨されていました。binディレクトリには実行可能ファイル、pkgディレクトリにはコンパイル済みパッケージが格納されます。
  3. ディストリビューションツール: ソフトウェアプロジェクトでは、リリース可能なパッケージ(バイナリ、ソースコード、ドキュメントなど)を作成するための専用ツールやスクリプトがしばしば使用されます。Goプロジェクトにおける misc/dist は、このようなディストリビューションパッケージを作成するための内部ツールです。
  4. tar.gzzip: これらは一般的なアーカイブ形式であり、複数のファイルを一つのファイルにまとめ、圧縮するために使用されます。Unix系システムでは tar.gz が、Windowsでは zip がよく使われます。
  5. filepath.Join: Go言語の path/filepath パッケージにある関数で、OS固有のパス区切り文字を使用してパス要素を結合します。これにより、クロスプラットフォームでのパス操作が可能になります。
  6. os.RemoveAll: Go言語の os パッケージにある関数で、指定されたパスのファイルまたはディレクトリ(およびその内容)を再帰的に削除します。
  7. ioutil.WriteFile: Go言語の io/ioutil パッケージにある関数で、指定されたファイルにバイトスライスを書き込みます。
  8. filepath.Glob: Go言語の path/filepath パッケージにある関数で、パターンにマッチするファイルパスを検索します。
  9. Issue Tracker: ソフトウェア開発プロジェクトでバグ報告や機能要望などを管理するためのシステム。Goプロジェクトでは、かつて code.google.com/p/go が使用されていました。

技術的詳細

このコミットの主要な技術的変更点は、misc/dist/bindist.go ツールが、従来のバイナリディストリビューションの作成に加えて、ソースコードのディストリビューション(ソースアーカイブ)を作成する機能を持つようになったことです。

具体的には、以下の点が変更されています。

  1. sourceCleanFiles の導入: cleanFiles とは別に、ソースアーカイブ作成時に削除すべきファイルやディレクトリのリスト sourceCleanFiles が追加されました。これには、ビルドによって生成される bin (バイナリ) と pkg (コンパイル済みパッケージ) ディレクトリが含まれます。これにより、ソースアーカイブがクリーンな状態に保たれます。

  2. Build 構造体の拡張: Build 構造体に Source bool フィールドが追加されました。このフィールドが true の場合、そのビルドがソースアーカイブ作成のためのものであることを示し、OSArch フィールドは空になります。

  3. コマンドライン引数の解析の変更: main 関数内で、コマンドライン引数(ターゲット)の解析ロジックが変更されました。従来の OS-Arch 形式のターゲットに加えて、source という新しいターゲットが認識されるようになりました。source ターゲットが指定された場合、Build 構造体の Source フィールドが true に設定されます。

  4. ビルドプロセスの分岐: Build.Do() メソッド内で、b.Source の値に基づいてビルドプロセスが分岐するようになりました。

    • b.Sourcetrue の場合: make.bash --dist-tool を実行して、ディストリビューションツール自体のみをビルドします。これは、ソースアーカイブには完全なGoツールチェインのバイナリを含める必要がなく、ソースからビルドするための最小限のツールがあれば十分であるためです。
    • b.Sourcefalse の場合: 従来の make.bat (Windows) または make.bash (Unix系) を実行して、完全なGoツールチェインをビルドします。
  5. バージョン文字列の取得方法の変更: バージョン文字列の取得ロジックがより堅牢になりました。以前は bin/go version の出力から直接バージョンを抽出していましたが、変更後は pkg/tool/*/dist パターンでディストリビューションツール(dist)のパスを検索し、その dist ツールに version コマンドを実行してフルバージョン文字列を取得するようになりました。これにより、バージョン情報の取得がより正確になります。

  6. クリーンアップ処理の改善: Build.Do() メソッド内で、クリーンアップ処理が b.clean(files []string) という新しいヘルパー関数に委譲されるようになりました。また、b.Sourcetrue の場合は、cleanFiles に加えて sourceCleanFiles もクリーンアップ対象として実行されます。

  7. パッケージ名の生成ロジックの変更: 生成されるアーカイブファイルの名前(targ)のフォーマットが変更されました。ソースアーカイブの場合、go.<version>.src.tar.gz のような形式になります。

  8. アップロードメタデータの調整: Build.upload() メソッド内で、アップロードするパッケージのメタデータ(ラベルやサマリー)が、ソースアーカイブの場合に適切に設定されるようになりました。例えば、Type-Source ラベルが追加され、サマリーも「Go (source only)」のような形式になります。

これらの変更により、bindist.go はバイナリとソースの両方のディストリビューションを柔軟に作成できる、より汎用的なツールへと進化しました。

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

変更の中心は misc/dist/bindist.go ファイルです。

--- a/misc/dist/bindist.go
+++ b/misc/dist/bindist.go
@@ -43,6 +43,11 @@ var cleanFiles = []string{
 	"VERSION.cache",
 }
 
+var sourceCleanFiles = []string{
+	"bin",
+	"pkg",
+}
+
 func main() {
 	flag.Usage = func() {
 		fmt.Fprintf(os.Stderr, "usage: %s [flags] targets...\\n", os.Args[0])
@@ -57,12 +62,18 @@ func main() {
 		log.Println("readCredentials:", err)
 	}
 	for _, targ := range flag.Args() {
-		p := strings.SplitN(targ, "-", 2)
-		if len(p) != 2 {
-			log.Println("Ignoring unrecognized target:", targ)
-			continue
+		var b Build
+		if targ == "source" {
+			b.Source = true
+		} else {
+			p := strings.SplitN(targ, "-", 2)
+			if len(p) != 2 {
+				log.Println("Ignoring unrecognized target:", targ)
+				continue
+			}
+			b.OS = p[0]
+			b.Arch = p[1]
 		}
-		b := Build{OS: p[0], Arch: p[1]}\n 		if err := b.Do(); err != nil {
 		if err := b.Do(); err != nil {
 			log.Printf("%s: %v", targ, err)
 		}
@@ -70,9 +81,10 @@ func main() {
 }
 
 type Build struct {
-	OS   string
-	Arch string
-	root string
+	Source bool // if true, OS and Arch must be empty
+	OS     string
+	Arch   string
+	root   string
 }
 
 func (b *Build) Do() error {
@@ -93,44 +105,66 @@ func (b *Build) Do() error {
 		return err
 	}
 
-	// Build.
-	if b.OS == "windows" {
-		_, err = b.run(filepath.Join(b.root, "src"), "cmd", "/C", "make.bat")
+	src := filepath.Join(b.root, "src")
+	if b.Source {
+		// Build dist tool only.
+		_, err = b.run(src, "bash", "make.bash", "--dist-tool")
 	} else {
-		_, err = b.run(filepath.Join(b.root, "src"), "bash", "make.bash")
+		// Build.
+		if b.OS == "windows" {
+			_, err = b.run(src, "cmd", "/C", "make.bat")
+		} else {
+			_, err = b.run(src, "bash", "make.bash")
+		}
 	}
 	if err != nil {
 		return err
 	}
 
-	// Get version string.
-	version, err := b.run("", filepath.Join(b.root, "bin/go"), "version")
+	// Get version strings.
+	var (
+		version     string // "weekly.2012-03-04"
+		fullVersion []byte // "weekly.2012-03-04 9353aa1efdf3"
+	)
+	pat := b.root + "/pkg/tool/*/dist"
+	m, err := filepath.Glob(pat)
 	if err != nil {
 		return err
 	}
-	v := bytes.SplitN(version, []byte(" "), 4)
-	version = bytes.Join(v[2:], []byte(" "))
-	ver := string(v[2])
+	if len(m) == 0 {
+		return fmt.Errorf("couldn't find dist in %q", pat)
+	}
+	fullVersion, err = b.run("", m[0], "version")
+	if err != nil {
+		return err
+	}
+	v := bytes.SplitN(fullVersion, []byte(" "), 2)
+	version = string(v[0])
 
 	// Write VERSION file.
-	err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), version, 0644)
+	err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), fullVersion, 0644)
 	if err != nil {
 		return err
 	}
 
 	// Clean goroot.
-	for _, name := range cleanFiles {
-		err = os.RemoveAll(filepath.Join(b.root, name))
-		if err != nil {
+	if err := b.clean(cleanFiles); err != nil {
+		return err
+	}
+	if b.Source {
+		if err := b.clean(sourceCleanFiles); err != nil {
 			return err
 		}
 	}
 
 	// Create packages.
-	targ := fmt.Sprintf("go.%s.%s-%s", ver, b.OS, b.Arch)
+	targ := fmt.Sprintf("go.%s.%s-%s", version, b.OS, b.Arch)
 	switch b.OS {
-	case "linux", "freebsd":
+	case "linux", "freebsd", "":
 		// build tarball
+		if b.Source {
+			targ = fmt.Sprintf("go.%s.src", version)
+		}
 		targ += ".tar.gz"
 		_, err = b.run("", "tar", "czf", targ, "-C", work, "go")
 	case "darwin":
@@ -187,7 +221,7 @@ func (b *Build) Do() error {
 		// Build package.
 		_, err = b.run(work, "candle",
 			"-nologo",
-			"-dVersion="+ver,
+			"-dVersion="+version,
 			"-dArch="+b.Arch,
 			"-dSourceDir=go",
 			installer, appfiles)
@@ -210,7 +244,7 @@ func (b *Build) Do() error {
 	err = cp(targ, msi)
 	if err == nil && password != "" {
-		err = b.upload(string(v[2]), targ)
+		err = b.upload(version, targ)
 	}
 	return err
 }
@@ -265,7 +299,7 @@ func (b *Build) env() []string {
 
 func (b *Build) upload(version string, filename string) error {
 	// Prepare upload metadata.
-	labels := []string{"Arch-" + b.Arch}
+	var labels []string
 	os_, arch := b.OS, b.Arch
 	switch b.Arch {
 	case "386":
@@ -273,6 +307,9 @@ func (b *Build) upload(version string, filename string) error {
 	case "amd64":
 		arch = "64-bit"
 	}
+	if arch != "" {
+		labels = append(labels, "Arch-"+b.Arch)
+	}
 	switch b.OS {
 	case "linux":
 		os_ = "Linux"
@@ -288,6 +325,10 @@ func (b *Build) upload(version string, filename string) error {
 		labels = append(labels, "Type-Installer", "OpSys-Windows")
 	}
 	summary := fmt.Sprintf("Go %s %s (%s)", version, os_, arch)
+	if b.Source {
+		labels = append(labels, "Type-Source")
+		summary = fmt.Sprintf("Go %s (source only)", version)
+	}
 
 	// Open file to upload.
 	f, err := os.Open(filename)
@@ -341,6 +382,16 @@ func (b *Build) upload(version string, filename string) error {
 	return nil
 }
 
+func (b *Build) clean(files []string) error {
+	for _, name := range files {
+		err := os.RemoveAll(filepath.Join(b.root, name))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 func exists(path string) bool {
 	_, err := os.Stat(path)
 	return err == nil

コアとなるコードの解説

上記の差分は、misc/dist/bindist.go がどのようにソースアーカイブの生成をサポートするように変更されたかを示しています。

  1. sourceCleanFiles の追加:

    var sourceCleanFiles = []string{
    	"bin",
    	"pkg",
    }
    

    これは、ソースアーカイブを作成する際に削除すべきディレクトリのリストです。bin ディレクトリにはコンパイルされた実行可能ファイルが、pkg ディレクトリにはコンパイルされたGoパッケージが格納されます。これらを削除することで、配布されるソースアーカイブがクリーンな状態に保たれます。

  2. main 関数でのターゲット解析の変更:

    	for _, targ := range flag.Args() {
    		var b Build
    		if targ == "source" {
    			b.Source = true
    		} else {
    			p := strings.SplitN(targ, "-", 2)
    			if len(p) != 2 {
    				log.Println("Ignoring unrecognized target:", targ)
    				continue
    			}
    			b.OS = p[0]
    			b.Arch = p[1]
    		}
    		if err := b.Do(); err != nil {
    			log.Printf("%s: %v", targ, err)
    		}
    	}
    

    main 関数はコマンドライン引数として渡されたターゲットを処理します。以前は OS-Arch 形式のみを想定していましたが、この変更により source という特別なターゲットが認識されるようになりました。source が指定された場合、Build 構造体の Source フィールドが true に設定され、ソースアーカイブ作成モードに入ります。

  3. Build 構造体への Source フィールド追加:

    type Build struct {
    	Source bool // if true, OS and Arch must be empty
    	OS     string
    	Arch   string
    	root   string
    }
    

    Source フィールドは、現在のビルドがソースアーカイブを対象としているかどうかを示すフラグです。これが true の場合、OSArch は無視されます。

  4. Build.Do() メソッドでのビルドロジックの分岐:

    	src := filepath.Join(b.root, "src")
    	if b.Source {
    		// Build dist tool only.
    		_, err = b.run(src, "bash", "make.bash", "--dist-tool")
    	} else {
    		// Build.
    		if b.OS == "windows" {
    			_, err = b.run(src, "cmd", "/C", "make.bat")
    		} else {
    			_, err = b.run(src, "bash", "make.bash")
    		}
    	}
    

    Build.Do() メソッドは、Source フィールドの値に基づいて異なるビルドコマンドを実行します。ソースアーカイブの場合 (b.Sourcetrue)、make.bash --dist-tool を実行して、Goツールチェイン全体ではなく、ディストリビューションツール自体のみをビルドします。これは、ソースアーカイブには完全なビルド済みツールチェインは不要であり、ソースからビルドするための最小限のツールがあれば十分であるためです。

  5. バージョン文字列取得の改善:

    	var (
    		version     string // "weekly.2012-03-04"
    		fullVersion []byte // "weekly.2012-03-04 9353aa1efdf3"
    	)
    	pat := b.root + "/pkg/tool/*/dist"
    	m, err := filepath.Glob(pat)
    	// ...
    	fullVersion, err = b.run("", m[0], "version")
    	// ...
    	v := bytes.SplitN(fullVersion, []byte(" "), 2)
    	version = string(v[0])
    

    以前は bin/go version の出力からバージョンを抽出していましたが、この変更により、pkg/tool/*/dist パターンで dist ツールを見つけ、その dist ツールに version コマンドを実行してフルバージョン文字列を取得するようになりました。これにより、より正確で安定した方法でバージョン情報を取得できます。

  6. クリーンアップ処理の委譲とソース固有のクリーンアップ:

    	if err := b.clean(cleanFiles); err != nil {
    		return err
    	}
    	if b.Source {
    		if err := b.clean(sourceCleanFiles); err != nil {
    			return err
    		}
    	}
    // ...
    func (b *Build) clean(files []string) error {
    	for _, name := range files {
    		err := os.RemoveAll(filepath.Join(b.root, name))
    		if err != nil {
    			return err
    		}
    	}
    	return nil
    }
    

    クリーンアップロジックが b.clean ヘルパー関数にまとめられました。ソースアーカイブの場合 (b.Sourcetrue)、通常のクリーンアップファイル (cleanFiles) に加えて、sourceCleanFiles (つまり binpkg ディレクトリ) も削除されます。

  7. パッケージ名の生成とアップロードメタデータの調整:

    	// Create packages.
    	targ := fmt.Sprintf("go.%s.%s-%s", version, b.OS, b.Arch)
    	switch b.OS {
    	case "linux", "freebsd", "": // Added "" for source builds
    		// build tarball
    		if b.Source {
    			targ = fmt.Sprintf("go.%s.src", version) // Source archive name
    		}
    		targ += ".tar.gz"
    		// ...
    	}
    // ...
    func (b *Build) upload(version string, filename string) error {
    	// ...
    	if b.Source {
    		labels = append(labels, "Type-Source")
    		summary = fmt.Sprintf("Go %s (source only)", version)
    	}
    	// ...
    }
    

    生成されるアーカイブファイルの名前が、ソースアーカイブの場合は go.<version>.src.tar.gz のような形式になるように調整されました。また、アップロード時のメタデータ(ラベルやサマリー)も、ソースアーカイブであることを示すように変更されています。

これらの変更により、bindist.go はGo言語のソースコード配布を自動化し、クリーンで適切な形式のソースアーカイブを生成できるようになりました。

関連リンク

参考にした情報源リンク