[インデックス 12582] ファイルの概要
このコミットは、Go言語のgoコマンドにおけるGOBIN環境変数の挙動を修正し、改善するものです。具体的には、go installコマンドによってビルドされたバイナリのインストール先を決定する際に、$GOBIN環境変数が常に尊重されるように変更されました。これにより、ユーザーは$GOROOT内のソースコードからビルドされたバイナリだけでなく、すべてのGoコンパイル済みバイナリを任意の指定されたディレクトリにインストールできるようになり、以前の混乱を解消し、利便性を向上させています。
コミット
commit bccafa72107a60c1443bd405849df94349d3302e
Author: Russ Cox <rsc@golang.org>
Date: Mon Mar 12 16:49:12 2012 -0400
cmd/go: respect $GOBIN always
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.
Fixes #3269.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5754088
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bccafa72107a60c1443bd405849df94349d3302e
元コミット内容
このコミットは、cmd/go(Goコマンドラインツール)が$GOBIN環境変数を常に尊重するように変更します。
以前は、$GOBINは$GOROOT内で見つかったソースコードに対してのみ参照されていましたが、これは説明が難しく、あまり有用ではありませんでした。新しい挙動では、ユーザーがGOBIN=$HOME/binのように設定することで、Goによってコンパイルされたすべてのバイナリがその場所にインストールされるようになります。
これはIssue #3269を修正します。
変更の背景
この変更の主な背景は、GOBIN環境変数の以前の挙動がユーザーにとって混乱を招き、期待通りの動作をしなかった点にあります。
Goのビルドシステムにおいて、go installコマンドはソースコードをコンパイルし、その結果生成される実行可能ファイルを特定のディレクトリに配置します。この配置先は、通常は$GOPATH/binまたは$GOROOT/binですが、ユーザーはGOBIN環境変数を使ってこれを上書きできると期待します。
しかし、このコミット以前のgoコマンドの実装では、GOBINが尊重されるのは、コンパイル対象のソースコードが$GOROOT(Goのインストールディレクトリ)内に存在する場合に限られていました。つまり、ユーザーが自身のプロジェクトやサードパーティのライブラリ(通常は$GOPATH以下に配置される)をgo installでビルドした場合、GOBINを設定していても、そのバイナリは$GOPATH/binにインストールされてしまい、GOBINで指定した場所にはインストールされませんでした。
この挙動は、ユーザーがGoのバイナリを一元的に管理したい場合に不便であり、「なぜGOBINを設定しているのに、ここにインストールされないのか?」という混乱を生じさせていました。Issue #3269は、まさにこの問題点を指摘しており、GOBINが常に尊重されるべきであるという要望が挙げられていました。
このコミットは、この一貫性のない挙動を修正し、ユーザーがGOBINを設定すれば、どのソースコードからビルドされたバイナリであっても、指定されたディレクトリにインストールされるようにすることで、Go開発者の体験を向上させることを目的としています。
前提知識の解説
このコミットの理解を深めるために、以下のGo言語の基本的な概念と環境変数について解説します。
-
GOROOT:- Go言語のSDK(Software Development Kit)がインストールされているルートディレクトリを指します。
- Goの標準ライブラリのソースコードや、
goコマンド自体を含むツール群がこのディレクトリ以下に配置されます。 - 通常、Goをインストールすると自動的に設定されるか、手動で設定します。
-
GOPATH:- Go 1.11以前のモジュールシステム導入前は、Goのワークスペースとして機能する重要な環境変数でした。現在でも、モジュール外のコードや特定のレガシーなビルドプロセスで利用されます。
- Goのソースコード、パッケージ、およびコンパイル済みバイナリを配置するためのディレクトリのリスト(コロンまたはセミコロン区切り)を指定します。
- 典型的な
GOPATHディレクトリ構造は以下のようになります。src/: ソースコード(.goファイル)が配置されます。pkg/: コンパイルされたパッケージアーカイブ(.aファイル)が配置されます。bin/:go installコマンドで生成された実行可能ファイルが配置されます。
go getコマンドで外部パッケージをダウンロードする際も、デフォルトでは$GOPATH/src以下に配置されます。
-
GOBIN:go installコマンドによって生成された実行可能ファイル(バイナリ)がインストールされるディレクトリを指定する環境変数です。- このコミット以前は、
$GOROOT内のソースからビルドされたバイナリにのみ適用されるという制限がありました。 - このコミットにより、
GOBINが設定されていれば、$GOROOTや$GOPATHの区別なく、すべてのGoコンパイル済みバイナリがこのディレクトリにインストールされるようになります。 GOBINが設定されていない場合、バイナリはデフォルトで$GOPATH/bin(または$GOROOT/bin)にインストールされます。
-
go installコマンド:- Goのソースコードをコンパイルし、その結果生成された実行可能ファイル(
mainパッケージの場合)またはパッケージアーカイブ(ライブラリパッケージの場合)を、適切なインストールディレクトリ(GOBIN、GOPATH/bin、GOPATH/pkgなど)に配置するコマンドです。 - このコマンドは、開発者が自身のツールやアプリケーションをシステムパスに追加して、どこからでも実行できるようにするために頻繁に利用されます。
- Goのソースコードをコンパイルし、その結果生成された実行可能ファイル(
これらの環境変数とコマンドは、Goプロジェクトのビルド、インストール、および依存関係管理において中心的な役割を果たします。
技術的詳細
このコミットの技術的な変更は、主にsrc/cmd/go/build.goとsrc/cmd/go/pkg.goの2つのファイルに集中しており、GOBIN環境変数の解釈とバイナリのインストールパス決定ロジックが修正されています。また、関連するドキュメントも更新されています。
1. GOBINの初期化ロジックの変更 (src/cmd/go/build.go)
-
変更前:
var ( goroot = filepath.Clean(runtime.GOROOT()) gobin = defaultGobin() // defaultGobin() 関数でGOBINを決定 gorootSrcPkg = filepath.Join(goroot, "src/pkg") // ... ) func defaultGobin() string { if s := os.Getenv("GOBIN"); s != "" { return s } return filepath.Join(goroot, "bin") }変更前は、
gobin変数の初期化にdefaultGobin()関数が使われていました。この関数は、GOBIN環境変数が設定されていればその値を使用し、設定されていなければ$GOROOT/binをデフォルトとしていました。しかし、このgobin変数が実際にバイナリのインストールパスとしてどのように使われるかには、後述する制限がありました。 -
変更後:
var ( gobin = os.Getenv("GOBIN") // 環境変数から直接GOBINを取得 goroot = filepath.Clean(runtime.GOROOT()) gorootSrcPkg = filepath.Join(goroot, "src/pkg") // ... )defaultGobin()関数が削除され、gobin変数はos.Getenv("GOBIN")によって直接初期化されるようになりました。これにより、GOBINが設定されていればその値が、設定されていなければ空文字列がgobinに格納されます。この変更自体は、GOBINが設定されていない場合のデフォルトパスの決定ロジックを直接変更するものではなく、後続のロジックでgobinの値がどのように利用されるかに影響を与えます。
2. バイナリのターゲットパス決定ロジックの変更 (src/cmd/go/build.go)
goFilesPackage関数内で、mainパッケージ(実行可能ファイルを生成するパッケージ)のターゲットパス(pkg.target)を決定するロジックが変更されました。
-
変更前:
pkg.targetは、*buildO(-oフラグで指定された出力ファイル名)が設定されていない場合、pkg.Name + ".a"(ライブラリの場合)またはソースファイル名から派生した実行ファイル名(mainパッケージの場合)に設定されていました。GOBINの考慮はここにはありませんでした。 -
変更後:
if pkg.Name == "main" { _, elem := filepath.Split(gofiles[0]) exe := elem[:len(elem)-len(".go")] + exeSuffix if *buildO == "" { *buildO = exe } if gobin != "" { // GOBINが設定されている場合 pkg.target = filepath.Join(gobin, exe) // GOBINを優先してターゲットパスを設定 } } else { if *buildO == "" { *buildO = pkg.Name + ".a" } } pkg.Target = pkg.target // pkg.Target に pkg.target の値を代入mainパッケージの場合、GOBINが空文字列でない(つまり設定されている)場合、pkg.targetはfilepath.Join(gobin, exe)として明示的にGOBINディレクトリと実行ファイル名を結合したパスに設定されるようになりました。これにより、GOBINが設定されていれば、そのパスがバイナリのインストール先として優先されるようになります。
3. パッケージロード時のBinDir設定の変更 (src/cmd/go/pkg.go)
Package.load関数内で、ビルドコンテキストのBinDir(バイナリの出力ディレクトリ)を設定するロジックが変更されました。
-
変更前:
loadPackage関数内で、$GOROOT内のパッケージをロードする際に、bp.BinDir = gobinという行がありました。これは、$GOROOT内のバイナリに対してのみGOBINを適用しようとする意図があったことを示唆しています。 -
変更後:
func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package { if gobin != "" { // GOBINが設定されている場合 bp.BinDir = gobin // build.Package の BinDir を GOBIN に設定 } p.copyBuild(bp) // ... }Package.load関数内で、gobinが空文字列でない場合、bp.BinDir(ビルドパッケージのバイナリディレクトリ)がgobinの値に設定されるようになりました。これにより、$GOROOT内外に関わらず、すべてのパッケージのビルドコンテキストにおいて、GOBINが設定されていればその値がバイナリの出力先として考慮されるようになります。 また、loadPackage関数内のbp.BinDir = gobinという行は削除されました。これは、Package.load関数での一元的な設定に移行したことを意味します。
4. ドキュメントの更新 (doc/install-source.html, src/cmd/go/doc.go, src/cmd/go/help.go)
doc/install-source.html:GOBINの説明に「IfGOBINis set, thego commandinstalls all commands there.」(GOBINが設定されている場合、goコマンドはすべてのコマンドをそこにインストールします。)という文言が追加され、新しい挙動が明記されました。src/cmd/go/doc.goとsrc/cmd/go/help.go:go installのドキュメントに、「If the GOBIN environment variable is set, commands are installed to the directory it names instead of DIR/bin.」(GOBIN環境変数が設定されている場合、コマンドはDIR/binの代わりに、それが指定するディレクトリにインストールされます。)という説明が追加されました。
これらの変更により、GOBINの挙動がより一貫性のあるものとなり、ユーザーが期待する通りにすべてのGoコンパイル済みバイナリを単一の指定されたディレクトリにインストールできるようになりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルとセクションに集中しています。
-
src/cmd/go/build.gogobin変数の初期化方法の変更。defaultGobin()関数の削除。goFilesPackage関数内でのpkg.targetの決定ロジックの変更。
--- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -302,20 +304,13 @@ const ( ) var ( + gobin = os.Getenv("GOBIN") // 変更: 環境変数から直接取得 goroot = filepath.Clean(runtime.GOROOT()) - gobin = defaultGobin() // 削除: defaultGobin() の呼び出し 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 @@ -387,18 +382,24 @@ 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 { - *buildO = pkg.Name + ".a" - } - } - pkg.target = "" // 削除: ここでの初期化は不要に - pkg.Target = "" // 削除: ここでの初期化は不要に + if pkg.Name == "main" { // main パッケージの場合 + _, elem := filepath.Split(gofiles[0]) + exe := elem[:len(elem)-len(".go")] + exeSuffix + if *buildO == "" { + *buildO = exe + } + if gobin != "" { // GOBIN が設定されている場合 + pkg.target = filepath.Join(gobin, exe) // GOBIN を使ってターゲットパスを設定 + } + } else { // main パッケージ以外の場合 (ライブラリなど) + if *buildO == "" { + *buildO = pkg.Name + ".a" + } + } pkg.Stale = true + pkg.Target = pkg.target // 変更: pkg.target の値を代入 computeStale(pkg) return pkg @@ -462,13 +463,13 @@ func (b *builder) action(mode buildMode, depMode buildMode, p *Package) *action return a } - if p.local { // 削除: この条件は変更 + a.link = p.Name == "main" // 追加: link の設定を移動 + if p.local && (!a.link || p.target == "") { // 変更: 新しい条件 // Imported via local path. No permanent target. mode = modeBuild } a.objdir = filepath.Join(b.work, a.p.ImportPath, "_obj") + string(filepath.Separator) a.objpkg = buildToolchain.pkgpath(b.work, a.p) - a.link = p.Name == "main" // 削除: link の設定を移動 switch mode { case modeInstall: -
src/cmd/go/pkg.goPackage.load関数内でのbp.BinDirの設定ロジックの変更。loadPackage関数内でのbp.BinDirの設定行の削除。
--- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -276,6 +276,9 @@ func expandScanner(err error) error { // load populates p using information from bp, err, which should // be the result of calling build.Context.Import. func (p *Package) load(stk *importStack, bp *build.Package, err error) *Package { + if gobin != "" { // 追加: GOBIN が設定されている場合 + bp.BinDir = gobin // build.Package の BinDir を GOBIN に設定 + } p.copyBuild(bp) // The localPrefix is the path we interpret ./ imports relative to. @@ -538,7 +541,6 @@ 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.Root = goroot bp.SrcRoot = gorootSrc p := new(Package) -
ドキュメントファイル (
doc/install-source.html,src/cmd/go/doc.go,src/cmd/go/help.go)GOBINの挙動に関する説明文の追加または修正。
--- a/doc/install-source.html +++ b/doc/install-source.html @@ -393,11 +393,12 @@ For example, you should not set <code>$GOHOSTARCH</code> to <p><code>$GOBIN</code> <p> -The location where binaries from the main repository will be installed. -XXX THIS MAY CHANGE TO BE AN OVERRIDE EVEN FOR GOPATH ENTRIES XXX +The location where Go binaries will be installed. // 変更: より一般的な説明に The default is <code>$GOROOT/bin</code>. After installing, you will want to arrange to add this directory to your <code>$PATH</code>, so you can use the tools. +If <code>$GOBIN</code> is set, the <a href="/cmd/go">go command</a> // 追加: GOBIN が設定されている場合の挙動 +installs all commands there. // 追加: すべてのコマンドがそこにインストールされる </p>--- a/src/cmd/go/doc.go +++ b/src/cmd/go/doc.go @@ -453,7 +453,9 @@ the final element, not the entire path. That is, the command with source in DIR/src/foo/quux is installed into DIR/bin/quux, not DIR/bin/foo/quux. The foo/ is stripped so that you can add DIR/bin to your PATH to get at the -installed commands. +installed commands. If the GOBIN environment variable is // 追加: GOBIN の挙動に関する説明 +set, commands are installed to the directory it names instead // 追加: GOBIN が設定されている場合の挙動 +of DIR/bin. // 追加: GOBIN が設定されている場合の挙動 Here's an example directory layout:src/cmd/go/help.goも同様のドキュメント変更が行われています。
コアとなるコードの解説
このコミットの核心は、GOBIN環境変数の扱いを、$GOROOT内のソースコードに限定せず、Goによってコンパイルされるすべてのバイナリに適用されるように変更した点にあります。
-
gobin変数の初期化とdefaultGobin()の削除 (src/cmd/go/build.go):- 以前は、
gobin変数はdefaultGobin()関数によって初期化されていました。この関数は、GOBIN環境変数が設定されていればその値を使用し、設定されていなければ$GOROOT/binをデフォルトとしていました。 - しかし、この
gobin変数が実際にバイナリのインストールパスとして使われるロジックが不完全だったため、GOBINが常に尊重されるわけではありませんでした。 - 変更後、
defaultGobin()関数は完全に削除され、gobin変数は単にos.Getenv("GOBIN")の結果を保持するようになりました。これにより、GOBINが設定されていればその値が、設定されていなければ空文字列がgobinに格納されます。この変更自体は、デフォルトパスの決定ロジックを直接変更するものではなく、後続のロジックでgobinの値がどのように利用されるかに影響を与えます。
- 以前は、
-
goFilesPackage関数におけるpkg.targetの決定ロジックの改善 (src/cmd/go/build.go):goFilesPackage関数は、Goのソースファイルからパッケージ情報を構築する際に、そのパッケージが生成するバイナリの最終的なターゲットパス(pkg.target)を決定します。- 変更前は、
mainパッケージ(実行可能ファイルを生成する)であっても、GOBINが考慮されず、単にソースファイル名から派生した実行ファイル名が*buildOに設定されるだけでした。pkg.targetは初期化されていませんでした。 - 変更後、
pkg.Name == "main"(つまり実行可能ファイル)の場合に、gobin変数が空文字列でない(GOBINが設定されている)ことを確認し、その場合にpkg.targetをfilepath.Join(gobin, exe)として明示的にGOBINディレクトリと実行ファイル名を結合したパスに設定するようになりました。 - これにより、
go installが実行可能ファイルをビルドする際、GOBINが設定されていれば、そのパスが最優先でインストール先として使用されるようになります。
-
Package.load関数におけるbp.BinDirの設定 (src/cmd/go/pkg.go):Package.load関数は、ビルドシステムがパッケージをロードする際に、そのパッケージに関する詳細情報(ビルドコンテキストなど)を設定します。- 以前は、
loadPackage関数内で$GOROOT内のパッケージをロードする際にのみbp.BinDir = gobinという行があり、GOBINの適用が$GOROOTに限定されていました。 - 変更後、
Package.load関数内で、gobinが空文字列でない場合にbp.BinDir = gobinという行が追加されました。この変更により、$GOROOT内外に関わらず、すべてのパッケージのビルドコンテキストにおいて、GOBINが設定されていればその値がバイナリの出力先として考慮されるようになりました。これにより、go installが$GOPATH内のパッケージをビルドする場合でも、GOBINが尊重されるようになります。 loadPackage関数内の古いbp.BinDir = gobinの行は削除され、Package.load関数での一元的な設定に移行しました。
これらの変更の組み合わせにより、goコマンドはGOBIN環境変数をより一貫性のある方法で解釈し、ユーザーが期待する通りにすべてのGoコンパイル済みバイナリを単一の指定されたディレクトリにインストールできるようになりました。これは、Go開発者のワークフローを簡素化し、環境設定の混乱を減らす上で重要な改善です。
関連リンク
- Go Issue #3269: cmd/go: respect $GOBIN always · Issue #3269 · golang/go
- Gerrit Change List: go: cmd/go: respect $GOBIN always - go.googlesource.com
参考にした情報源リンク
- Go Command Documentation: https://golang.org/cmd/go/
- Go Environment Variables: https://golang.org/doc/code.html#GOPATH (特に
GOBINに関するセクション) - Go Installation Documentation: https://golang.org/doc/install
- Go Modules Reference: https://go.dev/blog/using-go-modules (GOPATHの文脈での現代的なGoのプロジェクト管理について)
- filepath package: https://pkg.go.dev/path/filepath (Goのパス操作に関する標準ライブラリ)
- os package: https://pkg.go.dev/os (GoのOSインタラクションに関する標準ライブラリ、特に
os.Getenvについて) - runtime package: https://pkg.go.dev/runtime (Goのランタイム情報に関する標準ライブラリ、特に
runtime.GOROOTについて)