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

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

このコミットは、Go言語のコマンドラインツールgoの動作を改善し、環境変数$GOBINの扱いを一貫させることを目的としています。以前は$GOBIN$GOROOT内のソースコードに対してのみ考慮されていましたが、この変更により、$GOBINが常に尊重され、Goでコンパイルされたすべてのバイナリが指定されたディレクトリにインストールされるようになります。これにより、ユーザーは$GOBIN=$HOME/binのように設定することで、すべてのGoバイナリを単一の場所に集約できるようになり、使い勝手が向上します。

コミット

  • コミットハッシュ: 9d7076b178e3b688a8421a8ce02466a3701d31a0
  • 作者: Russ Cox rsc@golang.org
  • 日付: 2012年3月27日 火曜日 11:57:39 -0400
  • 件名: cmd/go: respect $GOBIN always

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

https://github.com/golang/go/commit/9d7076b178e3b688a8421a8ce02466a3701d31a0

元コミット内容

commit 9d7076b178e3b688a8421a8ce02466a3701d31a0
Author: Russ Cox <rsc@golang.org>
Date:   Tue Mar 27 11:57:39 2012 -0400

    cmd/go: respect $GOBIN always
    
    Another attempt at https://golang.org/cl/5754088.
    
    Before, we only consulted $GOBIN for source code
    found in $GOROOT, but that's confusing to explain
    and less useful.  The new behavior lets users set
    GOBIN=$HOME/bin and have all go-compiled binaries
    installed there.
    
    Tested a few cases in test.bash.
    
    Ran all.bash with and without $GOBIN and it works.
    Even so, I expect it to break the builders,
    like it did last time, we can debug from there.
    
    Fixes #3269 (again).
    Fixes #3396.
    Fixes #3397.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5927051

変更の背景

この変更は、以前のhttps://golang.org/cl/5754088での試みの再挑戦です。以前のgoコマンドの動作では、$GOBIN環境変数は$GOROOT(Goのインストールディレクトリ)内に存在するソースコードからビルドされるバイナリに対してのみ考慮されていました。これは、ユーザーにとって理解しにくく、また、Goでコンパイルされたすべての実行ファイルを一元的に管理したいというニーズに対して不便でした。

具体的には、以下のIssueを解決することを目的としています。

  • Issue #3269 (再度): $GOBINの挙動に関する混乱や不整合。
  • Issue #3396: $GOBINが常に尊重されないことによる問題。
  • Issue #3397: 同上。

このコミットは、ユーザーが$GOBINを例えば$HOME/binに設定することで、go installコマンドによって生成されるすべてのバイナリがその指定されたディレクトリにインストールされるようにし、より直感的で便利な動作を提供することを目指しています。

前提知識の解説

このコミットを理解するためには、Go言語のビルドシステムと環境変数に関する基本的な知識が必要です。

  • goコマンド: Go言語のソースコードのビルド、テスト、インストール、フォーマットなどを行うための主要なコマンドラインツールです。
  • go install: Goのパッケージやコマンドをコンパイルし、実行可能ファイルを適切な場所にインストールするコマンドです。
  • $GOROOT: Go言語のSDKがインストールされているルートディレクトリを指す環境変数です。Goの標準ライブラリやツール群がここに格納されています。
  • $GOPATH: Goのワークスペースのルートディレクトリを指す環境変数です。Goのソースコード、パッケージ、およびコンパイルされたバイナリがここに配置されます。通常、$GOPATHは複数のディレクトリパスを持つことができます。
    • $GOPATH/src: ソースコードが置かれる場所。
    • $GOPATH/pkg: コンパイルされたパッケージアーカイブが置かれる場所。
    • $GOPATH/bin: go installによって生成された実行可能バイナリが置かれるデフォルトの場所。
  • $GOBIN: go installコマンドによって生成された実行可能バイナリがインストールされるディレクトリを明示的に指定するための環境変数です。この変数が設定されている場合、go install$GOPATH/binではなく、$GOBINで指定されたディレクトリにバイナリを配置します。

このコミット以前は、$GOBINが設定されていても、$GOROOT外のソースコードからビルドされたバイナリは$GOPATH/binにインストールされるという、一貫性のない挙動がありました。この変更は、この不整合を解消し、$GOBINが常に優先されるようにします。

技術的詳細

このコミットの技術的な核心は、goコマンドがバイナリのインストール先を決定するロジックを変更することにあります。

  1. $GOBINの読み込み方法の変更:

    • src/cmd/go/build.goにおいて、gobin変数の初期化方法が変更されました。以前はdefaultGobin()関数を通じて$GOBINを読み込み、設定されていなければ$GOROOT/binをデフォルトとしていましたが、新しいコードでは直接os.Getenv("GOBIN")を呼び出し、$GOBINが設定されていればその値を、設定されていなければ空文字列を使用するように変更されました。これにより、$GOBINが設定されていない場合のデフォルトの挙動は、後続のロジックで決定されることになります。
    • gorootBinという新しい変数が導入され、$GOROOT/binのパスを保持するようになりました。
  2. バイナリのターゲットパス決定ロジックの変更:

    • src/cmd/go/build.gogoFilesPackage関数内で、pkg.target(最終的なバイナリのインストールパス)の決定ロジックが修正されました。
    • 以前は、mainパッケージ(実行可能ファイル)の場合にのみ、*buildO(出力ファイル名)が設定され、pkg.targetは常に空文字列でした。
    • 変更後、mainパッケージの場合、まず実行可能ファイル名(exe)を決定します。そして、$GOBINが設定されている場合、pkg.target$GOBINexeを結合したパスに設定されます。これにより、$GOBINが優先的に使用されるようになります。
    • pkg.Target = pkg.targetという行が追加され、Package構造体のTargetフィールドに最終的なインストールパスが確実に設定されるようになりました。
  3. パッケージロード時のBinDir設定の変更:

    • src/cmd/go/pkg.goloadImport関数およびloadPackage関数において、build.Package構造体のBinDirフィールドの設定ロジックが変更されました。
    • loadImportでは、$GOBINが設定されている場合にbp.BinDir$GOBINに設定されるようになりました。
    • loadPackageでは、$GOROOT内のパッケージの場合、デフォルトでgorootBinbp.BinDirに設定されますが、$GOBINが設定されている場合はそれが優先されるようになりました。
  4. ドキュメントの更新:

    • doc/install-source.htmlsrc/cmd/go/doc.gosrc/cmd/go/help.goのドキュメントが更新され、$GOBINが設定されている場合にgo commandがすべてのコマンドをそのディレクトリにインストールするという新しい挙動が明記されました。また、go help gopathを参照するように促す記述も追加されました。
  5. テストの追加:

    • src/cmd/go/test.bashに新しいテストケースが追加されました。これらのテストは、$GOBINが設定されていない場合と設定されている場合の両方で、go installがバイナリを正しくインストールすることを確認します。特に、$GOPATH外のソースコード(例: testdata/src/go-cmd-test/helloworld.go)をgo installする際の挙動が検証されています。

これらの変更により、go installコマンドは、ソースコードの場所($GOROOT内か$GOPATH内か)に関わらず、常に$GOBIN環境変数を尊重してバイナリをインストールするようになります。

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

主要な変更は以下のファイルに集中しています。

  • src/cmd/go/build.go:
    • gobin変数の初期化ロジックの変更。
    • defaultGobin()関数の削除。
    • goFilesPackage関数におけるpkg.targetの決定ロジックの修正。
  • src/cmd/go/pkg.go:
    • loadImportおよびloadPackage関数におけるbp.BinDirの設定ロジックの変更。
  • doc/install-source.html, src/cmd/go/doc.go, src/cmd/go/help.go:
    • $GOBINの挙動に関するドキュメントの更新。
  • src/cmd/go/test.bash:
    • $GOBINの挙動を検証するための新しいテストケースの追加。
  • src/cmd/go/testdata/src/go-cmd-test/helloworld.go:
    • テスト用のシンプルなGoプログラムの追加。

具体的なコードの変更箇所は以下の通りです。

src/cmd/go/build.go

--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -304,19 +306,13 @@ const (
 
 var (
  	goroot       = filepath.Clean(runtime.GOROOT())
-	gobin        = defaultGobin()
+	gobin        = os.Getenv("GOBIN")
+	gorootBin    = filepath.Join(goroot, "bin")
  	gorootSrcPkg = filepath.Join(goroot, "src/pkg")
  	gorootPkg    = filepath.Join(goroot, "pkg")
  	gorootSrc    = filepath.Join(goroot, "src")
 )
 
-func defaultGobin() string {
-	if s := os.Getenv("GOBIN"); s != "" {
-		return s
-	}
-	return filepath.Join(goroot, "bin")
-}
-
 func (b *builder) init() {
  	var err error
  	b.print = fmt.Print
@@ -388,17 +384,23 @@ func goFilesPackage(gofiles []string) *Package {
  	pkg.load(&stk, bp, err)
  	pkg.localPrefix = dirToImportPath(dir)
  	pkg.ImportPath = "command-line-arguments"
+	pkg.target = ""
  
-	if *buildO == "" {
-		if pkg.Name == "main" {
-			_, elem := filepath.Split(gofiles[0])
-			*buildO = elem[:len(elem)-len(".go")] + exeSuffix
-		} else {
+	if pkg.Name == "main" {
+		_, elem := filepath.Split(gofiles[0])
+		exe := elem[:len(elem)-len(".go")] + exeSuffix
+		if *buildO == "" {
+			*buildO = exe
+		}
+		if gobin != "" {
+			pkg.target = filepath.Join(gobin, exe)
+		}
+	} else {
+		if *buildO == "" {
  			*buildO = pkg.Name + ".a"
  		}
  	}
-	pkg.target = ""
-	pkg.Target = ""
+	pkg.Target = pkg.target
  	pkg.Stale = true
  
  	computeStale(pkg)
@@ -463,7 +465,7 @@ func (b *builder) action(mode buildMode, depMode buildMode, p *Package) *action
  		return a
  	}
  
-	if p.local {
+	if p.local && p.target == "" {
  		// Imported via local path.  No permanent target.
  		mode = modeBuild
  	}

src/cmd/go/pkg.go

--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -222,6 +222,9 @@ func loadImport(path string, srcDir string, stk *importStack, importPos []token.
  	// See issue 3268 for mistakes to avoid.
  	bp, err := buildContext.Import(path, srcDir, 0)
  	bp.ImportPath = importPath
+	if gobin != "" {
+		bp.BinDir = gobin
+	}
  	p.load(stk, bp, err)
  	if p.Error != nil && len(importPos) > 0 {
  	\tpos := importPos[0]
@@ -552,7 +555,10 @@ func loadPackage(arg string, stk *importStack) *Package {
  		bp, err := build.ImportDir(filepath.Join(gorootSrc, arg), 0)
  		bp.ImportPath = arg
  		bp.Goroot = true
-		bp.BinDir = gobin
+		bp.BinDir = gorootBin
+		if gobin != "" {
+			bp.BinDir = gobin
+		}
  		bp.Root = goroot
  		bp.SrcRoot = gorootSrc
  		p := new(Package)

コアとなるコードの解説

このコミットの核心は、goコマンドがバイナリのインストール先を決定する際の$GOBINの優先順位を明確にすることです。

  1. src/cmd/go/build.goの変更:

    • gobin変数がos.Getenv("GOBIN")から直接値を取得するように変更されたことで、$GOBINが設定されていればその値が、設定されていなければ空文字列がgobinに格納されます。これにより、$GOBINが設定されていない場合のデフォルトのインストールパスの決定は、後続のロジックに委ねられることになります。
    • goFilesPackage関数は、Goのソースファイルからパッケージ情報を構築する役割を担っています。この関数内で、mainパッケージ(実行可能ファイル)の場合に、pkg.targetフィールドに最終的なインストールパスを設定するロジックが追加されました。具体的には、$GOBINが空でなければ、$GOBINと実行可能ファイル名を結合したパスがpkg.targetに設定されます。これにより、go installが実行可能ファイルをビルドする際に、$GOBINで指定されたディレクトリに配置されることが保証されます。
    • pkg.Target = pkg.targetという行は、Package構造体のTargetフィールド(ビルド結果の最終的なパス)に、上記で決定されたpkg.targetの値を確実に反映させるためのものです。
    • action関数内のif p.local && p.target == ""という条件は、ローカルパスでインポートされたパッケージで、かつ永続的なターゲットパスが設定されていない場合にのみ、ビルドモードをmodeBuild(一時的なビルド)に設定するという意味です。これは、$GOBINによって永続的なターゲットパスが設定されるようになった新しい挙動に対応するための調整です。
  2. src/cmd/go/pkg.goの変更:

    • loadImportおよびloadPackage関数は、Goのパッケージをロードする際に、そのパッケージのビルド情報(build.Package構造体)を設定します。この変更により、$GOBINが設定されている場合、build.PackageBinDirフィールドに$GOBINの値が設定されるようになりました。BinDirは、そのパッケージのバイナリがインストールされるべきディレクトリを示します。これにより、パッケージのロード段階で$GOBINの指定がビルドシステム全体に伝播されるようになります。特に、$GOROOT内のパッケージであっても$GOBINが優先されるようにロジックが修正されています。

これらの変更により、goコマンドは、ユーザーが$GOBINを設定している場合、その設定を常に尊重し、Goでコンパイルされたすべての実行可能ファイルを指定されたディレクトリにインストールするようになります。これにより、ユーザーはGoバイナリの管理をより柔軟に行えるようになり、以前の混乱や不便さが解消されます。

関連リンク

参考にした情報源リンク