[インデックス 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
の説明に「IfGOBIN
is set, thego command
installs 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.go
gobin
変数の初期化方法の変更。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.go
Package.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
について)