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

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

このコミットは、Goコマンドラインツールに対する複数の改善を導入しています。主な変更点として、go cleanコマンドの追加、go buildおよびgo test -cの出力ファイル名の変更、そしてgo installにおけるクロスコンパイルされたバイナリの配置場所の改善が含まれます。これらの変更は、Go開発者がビルド成果物をより効率的に管理し、異なる環境での開発をスムーズに行えるようにすることを目的としています。

コミット

commit 00e9a54dad85724961dce513efbc835fd8365d5e
Author: Russ Cox <rsc@golang.org>
Date:   Mon Jan 30 23:42:41 2012 -0500

    go: improvements

    Add 'go clean'.
    Make 'go build' write to pkgname, not a.out.
    Make 'go test -c' write to pkgname.test, not test.out.
    Make 'go install' write alternate binaries to .../bin/goos_goarch/.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5600048

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

https://github.com/golang/go/commit/00e9a54dad85724961dce513efbc835fd8365d5e

元コミット内容

このコミットは、Goツールチェインの使いやすさと機能性を向上させるための複数の改善をまとめています。具体的には以下の点が挙げられます。

  • go cleanコマンドの追加: ビルドによって生成されたオブジェクトファイルや実行ファイルを削除するための新しいコマンドが導入されました。これにより、クリーンなビルド環境を維持しやすくなります。
  • go buildの出力ファイル名の変更: 実行可能ファイルのデフォルト名が、従来のa.outから、ビルド対象のパッケージ名(Windowsでは.exe拡張子付き)に変更されました。これにより、生成されるバイナリの識別が容易になります。
  • go test -cの出力ファイル名の変更: テストバイナリのデフォルト名が、従来のtest.outから、パッケージ名に.testを付加した形式(例: pkgname.test)に変更されました。これもまた、テストバイナリの識別性を高めます。
  • go installにおけるクロスコンパイルされたバイナリの配置場所の改善: 異なるOSやアーキテクチャ向けにクロスコンパイルされたバイナリが、$GOBIN配下のgoos_goarchサブディレクトリに配置されるようになりました。これにより、複数のターゲット向けのバイナリが整理されて管理されます。

変更の背景

このコミットが行われた背景には、Go言語の初期段階におけるツールチェインの成熟と、開発者の利便性向上のニーズがありました。

  1. ビルド成果物の管理の複雑化: Goのビルドシステムは、一時ディレクトリに多くのオブジェクトファイルを生成しますが、手動でのビルドや他のツール(例: Makefile)によって生成された古い成果物が残ることがありました。これらが蓄積されると、ディスクスペースの消費や、意図しない古いバイナリの実行といった問題を引き起こす可能性がありました。go cleanの導入は、これらの不要なファイルを一掃し、クリーンな状態を保つための標準的なメカニズムを提供します。
  2. 出力ファイル名の汎用性: 従来のa.outtest.outといった汎用的な出力ファイル名は、複数の実行可能ファイルやテストバイナリを扱う際に、名前の衝突や識別性の問題を引き起こす可能性がありました。特に、異なるパッケージのバイナリが同じディレクトリに置かれる場合、どのファイルがどのパッケージに属するのかを判別するのが困難でした。パッケージ名に基づいた命名規則への変更は、この問題を解決し、より直感的で管理しやすいファイル名を提供します。
  3. クロスコンパイルの利便性向上: Goは設計当初からクロスコンパイルを強力にサポートしていますが、異なるターゲット向けのバイナリがすべて同じ$GOBINディレクトリに置かれると、管理が煩雑になるという課題がありました。goos_goarchサブディレクトリへの配置は、クロスコンパイルされたバイナリをターゲット環境ごとに明確に分離し、開発者が特定の環境向けのバイナリを容易に見つけられるようにするための改善です。これは、特に組み込みシステムや異なるプラットフォームへのデプロイを行う開発者にとって大きな利点となります。
  4. Goツールチェインの標準化: これらの変更は、Goの公式ツールチェインが提供する機能の範囲を広げ、より一貫性のある開発体験を提供するための取り組みの一環です。特に、go cleanのような基本的なメンテナンスコマンドの追加は、Goエコシステム全体の健全性を高める上で重要でした。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびGoツールチェインに関する基本的な知識が必要です。

  1. Goパッケージとモジュール:
    • パッケージ: Goのコードはパッケージにまとめられます。関連する機能は同じパッケージに属し、パッケージはディレクトリ構造に対応します。mainパッケージは実行可能なプログラムのエントリポイントです。
    • インポートパス: パッケージを一意に識別するためのパスです(例: fmt, github.com/user/repo/pkg)。
  2. Goツールチェインの基本コマンド:
    • go build: ソースコードをコンパイルして実行可能バイナリを生成します。通常、カレントディレクトリのmainパッケージをビルドし、実行可能ファイルを生成します。
    • go test: パッケージのテストを実行します。-cフラグを付けると、テストを実行せずにテストバイナリをコンパイルして出力します。
    • go install: パッケージをコンパイルし、その結果生成される実行可能バイナリやアーカイブファイルを、$GOBINまたは$GOPATH/bin(実行可能ファイルの場合)、$GOPATH/pkg(ライブラリの場合)などの標準的なインストール場所に配置します。
  3. Go環境変数:
    • GOROOT: Goのインストールディレクトリを指します。Goの標準ライブラリやツールチェインのバイナリがここに格納されています。
    • GOBIN: go installコマンドによって生成された実行可能バイナリが配置されるディレクトリを指します。設定されていない場合、$GOPATH/binがデフォルトで使用されます。
    • GOOSGOARCH: ビルドターゲットのオペレーティングシステム(例: linux, windows, darwin)とアーキテクチャ(例: amd64, arm, 386)を指定する環境変数です。これらの変数を設定することで、クロスコンパイルが可能になります。
  4. ビルド成果物の種類:
    • オブジェクトファイル (.o, .5, .6, .8): コンパイラによって生成される中間ファイルで、アセンブリコードや機械語に変換されたソースコードの断片が含まれます。
    • アーカイブファイル (.a): 複数のオブジェクトファイルをまとめたライブラリファイルです。
    • 実行可能ファイル (.exeまたは拡張子なし): リンクによって生成される、直接実行可能なプログラムです。
    • テストバイナリ: go test -cによって生成される、テストコードを含む実行可能ファイルです。
  5. Makefile: 以前のGoプロジェクトでは、ビルドプロセスを管理するためにMakefileが使用されることがありました。このコミットのgo cleanは、Makefileによって残された古い成果物も対象としています。

技術的詳細

このコミットは、Goツールチェインの内部実装にいくつかの重要な変更を加えています。

  1. go cleanコマンドの実装 (src/cmd/go/clean.goの追加):

    • 新しいファイルsrc/cmd/go/clean.goが追加され、go cleanコマンドのロジックが実装されています。
    • このコマンドは、指定されたインポートパスに対応するソースディレクトリから、以下の種類のファイルを削除します。
      • 古いMakefileベースのビルドによって残されたディレクトリやファイル(例: _obj/, _test/, _testmain.go, test.out, build.out, *.[568ao])。
      • go buildによって生成された実行可能ファイル(例: DIR, DIR.exe)。ここでDIRはディレクトリの最終要素名です。
      • go test -cによって生成されたテストバイナリ(例: DIR.test, DIR.test.exe)。
      • go build MAINFILE.goのように特定のGoソースファイルを指定してビルドした場合に生成される実行可能ファイル(例: MAINFILE, MAINFILE.exe)。
    • -iフラグは、go installによってインストールされたアーカイブやバイナリも削除します。
    • -nフラグは、削除コマンドを実行せずに表示します。
    • -rフラグは、指定されたパッケージの依存関係を再帰的にクリーンアップします。
    • -xフラグは、削除コマンドを実行しながら表示します。
    • clean関数は、パッケージのディレクトリを読み込み、削除対象のファイルを特定し、os.Removeos.RemoveAllを使用して削除を実行します。
  2. go buildの出力ファイル名の変更 (src/cmd/go/build.go):

    • runBuild関数内で、mainパッケージをビルドし、かつ-oフラグが指定されていない場合、出力ファイル名がa.outから変更されます。
    • 変更後のファイル名は、パッケージのインポートパスの最終要素(パッケージ名)になります。Windows環境では.exe拡張子が自動的に付加されます。
    • これにより、go buildはデフォルトで./myprogram(または./myprogram.exe)のような、より意味のある名前の実行可能ファイルを生成するようになります。
    // src/cmd/go/build.go の変更点
    if len(pkgs) == 1 && pkgs[0].Name == "main" && *buildO == "" {
        _, *buildO = path.Split(pkgs[0].ImportPath)
        if b.goos == "windows" {
            *buildO += ".exe"
        }
    }
    
  3. go test -cの出力ファイル名の変更 (src/cmd/go/test.go, src/cmd/go/doc.go, src/cmd/go/testflag.go):

    • go test -cコマンドの出力ファイル名がtest.outからpkgname.testに変更されました。
    • src/cmd/go/test.gotest関数内で、テストバイナリのターゲット名がtestBinary変数によって決定されるようになり、これがパッケージのインポートパスの最終要素に.testを付加したものになります。
    • ドキュメント (src/cmd/go/doc.go) やテストフラグの定義 (src/cmd/go/testflag.go) も、この新しい命名規則に合わせて更新されています。
    // src/cmd/go/test.go の変更点
    // Use last element of import path, not package name.
    // They differ when package name is "main".
    _, elem := path.Split(p.ImportPath)
    testBinary := elem + ".test"
    
    // ...
    
    // Action for building pkg.test.
    pmain = &Package{
        Name:    "main",
        Dir:     testDir,
        // ...
    }
    a := b.action(modeBuild, modeBuild, pmain)
    a.objdir = testDir + string(filepath.Separator)
    a.objpkg = filepath.Join(testDir, "main.a")
    a.target = filepath.Join(testDir, testBinary) + b.exe // ここで testBinary が使われる
    
  4. go installにおけるクロスコンパイルされたバイナリの配置場所の改善 (src/cmd/go/pkg.go):

    • scanPackage関数内で、go installがクロスコンパイルを行う場合(ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH)、バイナリのインストールパスが$GOBIN直下ではなく、$GOBIN/goos_goarch/サブディレクトリに設定されるようになりました。
    • これにより、例えばLinux上でWindows向けのバイナリをビルドしてインストールすると、$GOBIN/windows_amd64/のようなディレクトリに配置されます。
    // src/cmd/go/pkg.go の変更点
    if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH {
        // Install cross-compiled binaries to subdirectories of bin.
        elem = ctxt.GOOS + "_" + ctxt.GOARCH + "/" + elem
    }
    p.target = filepath.Join(t.BinDir(), elem)
    
  5. GOROOTGOBINの扱いの一貫性向上 (src/cmd/go/build.go):

    • builder構造体からgorootgobinフィールドが削除され、代わりにグローバル変数gorootgobinが導入されました。
    • これにより、GOROOTGOBINのパスを参照する際に、builderインスタンスのフィールドではなく、グローバルな定義を使用するようになり、コード全体での一貫性が向上しました。特に、fmtcmd関数でのパスの置換処理や、Goツールチェインのバイナリ(go-tool/6g, go-tool/6aなど)へのパス構築において、この変更が適用されています。
    // src/cmd/go/build.go の変更点
    // builder struct から goroot と gobin フィールドが削除
    type builder struct {
        work        string               // the temporary work directory (ends in filepath.Separator)
        arch        string               // e.g., "6"
        goarch      string               // the $GOARCH
        goos        string               // the $GOOS
        exe         string               // the executable suffix - "" or ".exe"
        gcflags     []string             // additional flags for Go compiler
        actionCache map[cacheKey]*action // a cache of already-constructed actions
    }
    
    // グローバル変数として定義
    var (
        gobin  = build.Path[0].BinDir()
        goroot = build.Path[0].Path
    )
    
    // fmtcmd 関数での使用例
    // cmd = strings.Replace(cmd, b.gobin, "$GOBIN", -1) から
    cmd = strings.Replace(cmd, gobin, "$GOBIN", -1)
    // cmd = strings.Replace(cmd, b.goroot, "$GOROOT", -1) から
    cmd = strings.Replace(cmd, goroot, "$GOROOT", -1)
    

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

go buildの出力ファイル名変更

src/cmd/go/build.gorunBuild関数内での変更。

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -113,7 +114,10 @@ func runBuild(cmd *Command, args []string) {
 	}

 	if len(pkgs) == 1 && pkgs[0].Name == "main" && *buildO == "" {
-		*buildO = "a.out"
+		_, *buildO = path.Split(pkgs[0].ImportPath)
+		if b.goos == "windows" {
+			*buildO += ".exe"
+		}
 	}

 	if *buildO != "" {

go cleanコマンドの追加

src/cmd/go/clean.goの新規追加ファイルの一部。

// src/cmd/go/clean.go (抜粋)
package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var cmdClean = &Command{
	UsageLine: "clean [-i] [-r] [-n] [-x] [importpath...]",
	Short:     "remove object files",
	Long: `
Clean removes object files from package source directories.
The go command builds most objects in a temporary directory,
so go clean is mainly concerned with object files left by other
tools or by manual invocations of go build.

Specifically, clean removes the following files from each of the
source directories corresponding to the import paths:

	_obj/            old object directory, left from Makefiles
	_test/           old test directory, left from Makefiles
	_testmain.go     old gotest file, left from Makefiles
	test.out         old test log, left from Makefiles
	build.out        old test log, left from Makefiles
	*.[568ao]        object files, left from Makefiles

	DIR(.exe)        from go build
	DIR.test(.exe)   from go test -c
	MAINFILE(.exe)   from go build MAINFILE.go

In the list, DIR represents the final path element of the
directory, and MAINFILE is the base name of any Go source
file in the directory that is not included when building
the package.

The -i flag causes clean to remove the corresponding installed
archive or binary (what 'go install' would create).

The -n flag causes clean to print the remove commands it would execute,
but not run them.

The -r flag causes clean to be applied recursively to all the
dependencies of the packages named by the import paths.

The -x flag causes clean to print remove commands as it executes them.
	`,
}

// ... (フラグ変数とinit関数、runClean関数など)

func clean(p *Package) {
	// ... (ディレクトリの読み込み、削除対象の特定ロジック)

	// go build や go test -c で生成されたファイルの削除ロジック
	_, elem := filepath.Split(p.Dir)
	allRemove := []string{
		elem,
		elem + ".exe",
		elem + ".test",
		elem + ".test.exe",
	}
	for _, dir := range dirs {
		name := dir.Name()
		if packageFile[name] {
			continue
		}
		if !dir.IsDir() && strings.HasSuffix(name, ".go") {
			base := name[:len(name)-len(".go")]
			allRemove = append(allRemove, base, base+".exe")
		}
	}
	// ... (実際のファイル削除処理)
}

go installにおけるクロスコンパイルバイナリの配置変更

src/cmd/go/pkg.goscanPackage関数内での変更。

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -273,6 +275,10 @@ func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string
 		if t.Goroot && isGoTool[p.ImportPath] {
 			p.target = filepath.Join(t.Path, "bin/go-tool", elem)
 		} else {
+			if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH {
+				// Install cross-compiled binaries to subdirectories of bin.
+				elem = ctxt.GOOS + "_" + ctxt.GOARCH + "/" + elem
+			}
 			p.target = filepath.Join(t.BinDir(), elem)
 		}
 		if ctxt.GOOS == "windows" {

コアとなるコードの解説

go buildの出力ファイル名変更 (src/cmd/go/build.go)

この変更は、go buildコマンドがmainパッケージをビルドする際のデフォルトの出力ファイル名を制御します。

  • len(pkgs) == 1 && pkgs[0].Name == "main" && *buildO == ""という条件は、単一のmainパッケージがビルドされ、かつユーザーが-oフラグで明示的な出力ファイル名を指定していない場合に真となります。
  • path.Split(pkgs[0].ImportPath)は、パッケージのインポートパス(例: github.com/user/myprogram)から、最後の要素(myprogram)を抽出します。これが新しいデフォルトの実行可能ファイル名となります。
  • if b.goos == "windows"の条件は、ビルドターゲットがWindowsである場合に、自動的に.exe拡張子をファイル名に追加します。これにより、Windows上での実行可能ファイルの慣習に適合します。 この変更により、go buildはより直感的で、パッケージ名に紐づいた実行可能ファイルを生成するようになり、複数のプロジェクトやバイナリを扱う際の識別性が向上しました。

go cleanコマンドの追加 (src/cmd/go/clean.go)

src/cmd/go/clean.goは、Goプロジェクトのビルド成果物をクリーンアップするための新しいgo cleanコマンドを定義しています。

  • cmdClean構造体は、コマンドの利用方法、短い説明、長い説明(詳細な動作とフラグの説明を含む)を定義しています。特に、_obj/_test/のような古いMakefileベースの成果物、そしてgo buildgo test -cによって生成される特定のファイル(例: DIR, DIR.test)を削除対象とすることが明記されています。
  • clean関数は、実際にファイルの削除ロジックを実装しています。この関数は、パッケージのディレクトリをスキャンし、allRemoveスライスに削除対象のファイル名を収集します。これには、ディレクトリ名に基づく実行可能ファイルやテストバイナリ、および.goファイル名に基づく実行可能ファイルが含まれます。
  • cleanDircleanFilecleanExtといったマップは、削除すべき特定のディレクトリ、ファイル、拡張子を定義しており、主に古いビルドシステムからの残骸をターゲットにしています。
  • -i, -n, -r, -xといったフラグは、クリーンアップ動作を細かく制御するためのオプションを提供します。例えば、-nはドライラン(削除せずに表示のみ)、-rは依存関係の再帰的なクリーンアップを可能にします。 このコマンドの導入により、Go開発者はビルド環境を簡単にリセットし、不要なファイルを削除できるようになり、プロジェクトの管理がより容易になりました。

go installにおけるクロスコンパイルバイナリの配置変更 (src/cmd/go/pkg.go)

この変更は、go installコマンドがクロスコンパイルされたバイナリを配置する際の動作を改善します。

  • if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCHの条件は、現在の実行環境のOS/アーキテクチャと、ビルドターゲットのOS/アーキテクチャが異なる場合に真となります。これはクロスコンパイルが行われていることを意味します。
  • この条件が真の場合、elem変数(通常はパッケージ名)の前にctxt.GOOS + "_" + ctxt.GOARCH + "/"というプレフィックスが追加されます。これにより、最終的なインストールパスは$GOBIN/goos_goarch/パッケージ名のようになります。 この変更により、異なるターゲット向けのバイナリが$GOBINディレクトリ内で明確に分離され、開発者は特定のプラットフォーム向けのバイナリを容易に識別し、管理できるようになりました。これは、特に複数の環境にデプロイするアプリケーションを開発する際に非常に有用です。

関連リンク

  • Go Code Review 5600048: このコミットの元のコードレビューページ。詳細な議論や変更の背景が確認できます。 https://golang.org/cl/5600048
  • Go Command Documentation: Goコマンド全般に関する公式ドキュメント。go build, go test, go install, go cleanなどのコマンドの詳細が記載されています。 https://pkg.go.dev/cmd/go

参考にした情報源リンク