[インデックス 11476] ファイルの概要
このコミットは、Go言語のビルドシステムにおける回帰バグを修正するものです。具体的には、gccgo
ツールチェインのサポートが追加された際に発生した、外部コマンド(Goツリーに含まれないコマンド)のインストールパスが誤って上書き(clobber)される問題に対処しています。これにより、外部コマンドが本来インストールされるべきバイナリディレクトリではなく、パッケージディレクトリにインストールされてしまうという不具合が解消されます。
コミット
commit d2599b431e80a3824cc587f8b23e3783fd241f3a
Author: Anthony Martin <ality@pbrane.org>
Date: Mon Jan 30 13:54:22 2012 -0500
go: don't clobber command install paths
This fixes a regression that was made when adding
support for building with gccgo (in d6a14e6fac0c).
External commands (those not from the Go tree) were
being installed to the package directory instead of
the binary directory.
R=golang-dev, rsc, adg, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/5564072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d2599b431e80a3824cc587f8b23e3783fd241f3a
元コミット内容
このコミットは、Go言語のビルドシステムにおいて、gccgo
ツールチェインのサポート導入によって引き起こされた回帰バグを修正するものです。具体的には、Goのソースツリー外のコマンド(外部コマンド)が、本来インストールされるべきバイナリディレクトリではなく、誤ってパッケージディレクトリにインストールされてしまう問題に対処しています。この問題は、コミットd6a14e6fac0c
でgccgo
サポートが追加された際に発生しました。
変更の背景
この変更の背景には、Go言語のビルドシステムが複数のコンパイラツールチェイン(標準のgc
とgccgo
)をサポートするようになった経緯があります。
Go言語の初期のビルドシステムは、主にGoチームが開発した公式コンパイラであるgc
(Go Compiler)を前提としていました。しかし、Go言語の普及に伴い、GCC(GNU Compiler Collection)をバックエンドとして利用するgccgo
という代替コンパイラが開発されました。gccgo
は、既存のGCCインフラストラクチャを活用することで、Goプログラムをより多くのプラットフォームでコンパイルできるようにすることを目指していました。
コミットメッセージに記載されているd6a14e6fac0c
は、Goのビルドツール(go
コマンド)にgccgo
を統合するための変更でした。この統合自体は、Go言語の柔軟性と移植性を高める上で重要なステップでしたが、その過程で予期せぬ副作用が発生しました。
具体的には、gccgo
のビルドパスの処理方法が、Goツリー外のコマンド(例えば、go get
でインストールされるサードパーティ製のツールなど)のインストールパスの決定ロジックに影響を与えてしまいました。結果として、これらの外部コマンドが、ユーザーの$GOBIN
(バイナリ実行ファイルが置かれるべき場所)ではなく、$GOPATH/pkg
(コンパイル済みパッケージが置かれる場所)のようなパッケージディレクトリに誤って配置されるという回帰バグが発生しました。
この問題は、ユーザーがgo install
やgo get
を使って外部ツールをインストールした際に、それらのツールが期待されるパスに存在せず、実行できないという形で現れました。このコミットは、この回帰バグを修正し、gccgo
を使用している場合でも、外部コマンドが正しいバイナリディレクトリにインストールされるようにすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドシステムと関連する概念についての知識が必要です。
-
Go言語のワークスペースと環境変数:
GOPATH
: Go 1.11以前のGoプロジェクトのワークスペースのルートディレクトリを指定する環境変数です。Goのソースコード、パッケージ、バイナリがこのディレクトリ構造内に配置されます。src
(ソースコード)、pkg
(コンパイル済みパッケージ)、bin
(コンパイル済みバイナリ)の3つのサブディレクトリが慣例的に存在します。GOBIN
: コンパイルされたGoプログラムの実行可能ファイルがインストールされるディレクトリを指定する環境変数です。GOBIN
が設定されていない場合、実行可能ファイルはGOPATH/bin
にインストールされます。go install
: ソースコードをコンパイルし、その結果生成されたパッケージ(.a
ファイルなど)をGOPATH/pkg
に、実行可能ファイル(コマンド)をGOPATH/bin
またはGOBIN
にインストールするGoコマンドです。go get
: リモートリポジトリからGoパッケージをダウンロードし、ビルドしてインストールするGoコマンドです。
-
Goのビルドツール (
go
コマンド):- Go言語のビルド、テスト、パッケージ管理などを行うための主要なコマンドラインツールです。
src/cmd/go
ディレクトリにそのソースコードがあります。 - このツールは、Goのソースコードをコンパイルし、実行可能ファイルを生成する際に、どのディレクトリに何を配置するかを決定するロジックを含んでいます。
- Go言語のビルド、テスト、パッケージ管理などを行うための主要なコマンドラインツールです。
-
Goのパッケージとコマンド:
- パッケージ: Goのコードの基本的な構成単位です。他のパッケージからインポートして利用されます。コンパイルされると、通常は
.a
(アーカイブ)ファイルとしてGOPATH/pkg
に保存されます。 - コマンド:
main
パッケージを持ち、main
関数を含むGoプログラムは、実行可能なコマンドとしてビルドされます。これらは通常、GOPATH/bin
またはGOBIN
にインストールされます。
- パッケージ: Goのコードの基本的な構成単位です。他のパッケージからインポートして利用されます。コンパイルされると、通常は
-
Goのツールチェイン:
- Goのソースコードをコンパイルし、実行可能ファイルを生成するための一連のツール(コンパイラ、アセンブラ、リンカなど)の集合です。
gc
(Go Compiler): Goチームが開発した標準のGoコンパイラです。Goの公式リリースに同梱されています。gccgo
: GCCのフロントエンドとして実装されたGoコンパイラです。GCCの最適化やバックエンドのサポートを利用できます。gccgo
は、gc
とは異なる内部的なパス構造や命名規則を持つことがあります。
-
build.Context
とbuild.Tree
:- Goのビルドツール内部で使用される構造体で、ビルド環境のコンテキスト(OS、アーキテクチャ、
GOPATH
など)や、ビルドツリー(ソースディレクトリ、パッケージディレクトリ、バイナリディレクトリなど)の情報を保持します。 t.PkgDir()
:build.Tree
オブジェクトのメソッドで、コンパイル済みパッケージが置かれるべきディレクトリのパスを返します。
- Goのビルドツール内部で使用される構造体で、ビルド環境のコンテキスト(OS、アーキテクチャ、
-
p.target
:- Goのビルドツール内部で、ビルド対象の最終的な出力ファイル(パッケージのアーカイブファイルや実行可能ファイル)のパスを保持するフィールドです。このフィールドの値が、最終的なファイルの配置場所を決定します。
-
filepath.Join
とfilepath.FromSlash
:- Goの標準ライブラリ
path/filepath
パッケージの関数です。 filepath.Join
: 複数のパス要素を結合して、OS固有のパス区切り文字(Windowsでは\
、Unix系では/
)を使用して正しいパスを生成します。filepath.FromSlash
: スラッシュ区切りのパスを、現在のOSのパス区切り文字に変換します。
- Goの標準ライブラリ
これらの概念を理解することで、コミットがなぜ必要とされ、どのように問題を解決しているのかが明確になります。特に、gccgo
が導入されたことで、従来のgc
とは異なるパスの扱いが必要になった点が重要です。
技術的詳細
このコミットが修正している問題は、gccgo
ツールチェインが導入された際に、Goのビルドツール(go
コマンド)が外部コマンドのインストールパスを誤って計算してしまうという回帰バグです。
問題の核心は、src/cmd/go/pkg.go
内のscanPackage
関数にあります。この関数は、Goパッケージのビルドターゲットパス(p.target
)を決定する役割を担っています。
元のコードでは、p.target
の初期設定は以下のようになっていました。
// (省略)
} else {
p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
}
// For gccgo, rewrite p.target with the expected library name. We won't do
// that for the standard library for the moment.
if !p.Standard {
dir := t.PkgDir()
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
}
このロジックの問題点は以下の通りです。
-
初期の
p.target
設定:p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
の行は、すべてのパッケージ(コマンドを含む)に対して、デフォルトでパッケージディレクトリ(t.PkgDir()
、通常は$GOPATH/pkg
)をベースとしたパスを設定していました。これは、コマンド(実行可能ファイル)が最終的に$GOBIN
または$GOPATH/bin
にインストールされるべきであるというGoの慣習と矛盾します。本来、コマンドのp.target
はバイナリディレクトリを指すべきです。 -
gccgo
特有のパス書き換えロジック: その後のif !p.Standard
ブロック内で、gccgo
ツールチェインが使用されている場合にp.target
を書き換えるロジックがありました。dir := t.PkgDir()
: ここで、パッケージディレクトリをベースとしています。dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
:gccgo
は、標準のGoツールチェインとは異なるディレクトリ構造を使用することがあるため、gccgo
固有のパッケージディレクトリパスを構築していました。例えば、$GOPATH/pkg/linux_amd64
が$GOPATH/pkg/gccgo/linux_amd64
のようになることを意図していたと考えられます。p.target = buildToolchain.pkgpath(dir, p)
: 最終的に、gccgo
ツールチェインのpkgpath
メソッドを使って、gccgo
の命名規則に沿ったパッケージパスを生成し、p.target
に設定していました。
この組み合わせにより、特にGoツリー外のコマンド(p.Standard
がfalse
のケース)の場合に問題が発生しました。コマンドであっても、初期段階でp.target
がパッケージディレクトリを指すように設定され、その後のgccgo
固有のパス書き換えもパッケージディレクトリをベースに行われるため、最終的にp.target
がバイナリディレクトリではなく、gccgo
のパッケージディレクトリ(例: $GOPATH/pkg/gccgo/...
)を指してしまい、実行可能ファイルが誤った場所にインストールされる結果となりました。
修正内容:
このコミットは、p.target
の初期設定ロジックを修正し、gccgo
のパス処理をより適切に統合することで、この問題を解決しています。
修正後のコードは以下のようになります。
// (省略)
} else {
// 変更点1: p.targetの初期設定が削除された
// p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
}
// 変更点2: dirの初期化がif/elseブロックの外に移動し、常にt.PkgDir()から始まる
dir := t.PkgDir()
// For gccgo, rewrite p.target with the expected library name.
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
// 変更点3: 標準ライブラリに対するgccgoの特別な処理が追加された
// NB. Currently we have gccgo install the standard libraries
// in the "usual" location, where the Go toolchain puts them.
if p.Standard {
if _, ok := buildToolchain.(gccgoToolchain); ok {
p.target = goToolchain{}.pkgpath(dir, p)
}
}
主要な変更点は以下の通りです。
p.target
の初期設定の削除:else
ブロック内のp.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
という行が削除されました。これにより、コマンドの場合に誤ってパッケージディレクトリを指す初期値が設定されることがなくなりました。dir
の初期化とp.target
の計算ロジックの変更:dir := t.PkgDir()
が、if !p.Standard
ブロックの外に移動しました。これにより、すべてのパッケージ(標準ライブラリと外部パッケージの両方)に対して、t.PkgDir()
をベースとしたdir
が初期化されます。- その後の
gccgo
固有のdir
の書き換えロジックはそのまま残っています。 - そして、
p.target = buildToolchain.pkgpath(dir, p)
が、if !p.Standard
の条件なしに実行されるようになりました。これは、buildToolchain.pkgpath
が、現在のツールチェイン(gc
またはgccgo
)に応じて適切なパスを生成することを期待しているためです。gc
ツールチェインの場合、pkgpath
はコマンドに対してはバイナリディレクトリを、パッケージに対してはパッケージディレクトリを返すように実装されているはずです。
- 標準ライブラリに対する
gccgo
の特別な扱い: 新たにif p.Standard
ブロックが追加されました。gccgo
を使用している場合でも、標準ライブラリはGo標準ツールチェイン(gc
)が配置する「通常の場所」にインストールされるように、p.target
をgoToolchain{}.pkgpath(dir, p)
で上書きしています。これは、gccgo
が標準ライブラリを独自のgccgo
固有のパスではなく、gc
と同じパスに配置することで、互換性を保つための措置と考えられます。
この修正により、p.target
の計算が、パッケージの種類(コマンドかライブラリか)と使用しているツールチェイン(gc
かgccgo
か)に応じて、より正確に行われるようになりました。特に、外部コマンドの場合、buildToolchain.pkgpath
が最終的に正しいバイナリディレクトリを指すように動作することで、回帰バグが解消されます。
コアとなるコードの変更箇所
変更はsrc/cmd/go/pkg.go
ファイルに集中しています。
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -279,17 +279,20 @@ func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string
p.target += ".exe"
} else {
- p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
- }
-
- // For gccgo, rewrite p.target with the expected library name. We won't do
- // that for the standard library for the moment.
- if !p.Standard {
dir := t.PkgDir()
+ // For gccgo, rewrite p.target with the expected library name.
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
+
+ // NB. Currently we have gccgo install the standard libraries
+ // in the "usual" location, where the Go toolchain puts them.
+ if p.Standard {
+ if _, ok := buildToolchain.(gccgoToolchain); ok {
+ p.target = goToolchain{}.pkgpath(dir, p)
+ }
+ }
}
var built time.Time
コアとなるコードの解説
変更されたsrc/cmd/go/pkg.go
のscanPackage
関数内のコードブロックを詳細に解説します。
元のコード:
} else {
p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
}
// For gccgo, rewrite p.target with the expected library name. We won't do
// that for the standard library for the moment.
if !p.Standard {
dir := t.PkgDir()
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
}
修正後のコード:
} else {
// 変更点1: p.targetの初期設定が削除された
// p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
// 変更点2: dirの初期化とp.targetの計算ロジックがこのブロック内に移動し、
// if !p.Standard の条件がなくなった
dir := t.PkgDir()
// For gccgo, rewrite p.target with the expected library name.
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
// 変更点3: 標準ライブラリに対するgccgoの特別な処理が追加された
// NB. Currently we have gccgo install the standard libraries
// in the "usual" location, where the Go toolchain puts them.
if p.Standard {
if _, ok := buildToolchain.(gccgoToolchain); ok {
p.target = goToolchain{}.pkgpath(dir, p)
}
}
}
変更点1: p.target
の初期設定の削除
- 削除された行:
- p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
- この行は、
p.target
(ビルド出力ファイルのパス)を、デフォルトでパッケージディレクトリ(t.PkgDir()
)とインポートパスを結合した.a
ファイル(アーカイブファイル)として設定していました。 - 問題は、これがコマンド(実行可能ファイル)に対しても適用されてしまう点でした。コマンドは
.a
ファイルではなく、実行可能ファイルとしてバイナリディレクトリにインストールされるべきです。 - この行を削除することで、
p.target
の初期値が誤ってパッケージディレクトリを指すことを防ぎ、後続のロジックで適切なパスが設定されるようにします。
変更点2: dir
の初期化とp.target
の計算ロジックの移動と変更
- 元のコードでは、
if !p.Standard
(標準ライブラリではない場合)という条件ブロックの中にdir
の初期化とp.target
の計算ロジックがありました。 - 修正後、このロジックは
else
ブロック(p.target
が.exe
で終わらない場合、つまりコマンドではないパッケージの場合)の直下に移動し、if !p.Standard
の条件がなくなりました。 dir := t.PkgDir()
: まず、現在のビルドツリーのパッケージディレクトリを取得します。これは、Goのパッケージが通常配置される場所のベースとなります。if _, ok := buildToolchain.(gccgoToolchain); ok { ... }
: ここで、現在使用されているビルドツールチェインがgccgoToolchain
であるかどうかをチェックします。- もし
gccgo
であれば、dir
のパスをgccgo
固有の構造に調整します。具体的には、filepath.Dir(dir)
で親ディレクトリを取得し、その中に"gccgo"
というサブディレクトリを追加し、元のdir
のベース名(例:linux_amd64
)を結合します。これにより、$GOPATH/pkg/gccgo/linux_amd64
のようなパスが生成され、gccgo
がパッケージを配置する場所を正確に反映します。
- もし
p.target = buildToolchain.pkgpath(dir, p)
: 最後に、現在のbuildToolchain
(gc
またはgccgo
)のpkgpath
メソッドを呼び出して、最終的なp.target
を計算します。- この
pkgpath
メソッドは、ツールチェインの種類、計算されたdir
、およびパッケージ情報p
に基づいて、適切な出力パス(パッケージの場合は.a
ファイル、コマンドの場合は実行可能ファイル)を返します。 - この変更により、標準ライブラリではないパッケージ(外部コマンドを含む)に対しても、ツールチェインに応じた正しい
p.target
が設定されるようになります。
- この
変更点3: 標準ライブラリに対するgccgo
の特別な処理の追加
- 追加されたブロック:
// NB. Currently we have gccgo install the standard libraries // in the "usual" location, where the Go toolchain puts them. if p.Standard { if _, ok := buildToolchain.(gccgoToolchain); ok { p.target = goToolchain{}.pkgpath(dir, p) } }
- このブロックは、現在処理しているパッケージがGoの標準ライブラリ(
p.Standard
がtrue
)であり、かつgccgo
ツールチェインを使用している場合にのみ実行されます。 p.target = goToolchain{}.pkgpath(dir, p)
: ここで重要なのは、goToolchain{}
というGo標準ツールチェインのインスタンスのpkgpath
メソッドを呼び出している点です。- これは、
gccgo
を使用している場合でも、Goの標準ライブラリは、Go標準ツールチェイン(gc
)が通常配置する場所(例:$GOROOT/pkg/linux_amd64
)にインストールされるように、p.target
を上書きすることを意味します。 - この措置は、
gccgo
とgc
の間で標準ライブラリのパスに互換性を持たせるためのもので、ユーザーがどちらのツールチェインを使っても標準ライブラリが同じ場所から参照できるようにするためのものです。
これらの変更により、scanPackage
関数は、Goのパッケージとコマンドのビルドターゲットパスを、使用されているツールチェイン(gc
またはgccgo
)とパッケージの種類(標準ライブラリ、外部パッケージ、コマンド)に応じて、より正確かつ一貫性のある方法で決定できるようになりました。これにより、外部コマンドが誤ったディレクトリにインストールされるという回帰バグが修正されました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go Modules (Go 1.11以降のパッケージ管理): https://go.dev/blog/using-go-modules (このコミットの時点ではGo Modulesは存在しませんが、現代のGo開発の文脈で関連する情報です)
- GCCGOに関する情報: https://go.dev/doc/install/gccgo
参考にした情報源リンク
- コミットのChange-Id:
https://golang.org/cl/5564072
(GoのGerritコードレビューシステムへのリンク) - Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語のビルドシステムに関する一般的な情報 (Goの公式ドキュメントやブログ記事など)
d6a14e6fac0c
コミットの検索結果 (このコミットが引き起こした回帰の元となったコミット):- https://github.com/golang/go/commit/d6a14e6fac0c
- このコミットは "go: add gccgo support" というメッセージで、
gccgo
のサポートを追加したことが確認できます。# [インデックス 11476] ファイルの概要
このコミットは、Go言語のビルドシステムにおける回帰バグを修正するものです。具体的には、gccgo
ツールチェインのサポートが追加された際に発生した、外部コマンド(Goツリーに含まれないコマンド)のインストールパスが誤って上書き(clobber)される問題に対処しています。これにより、外部コマンドが本来インストールされるべきバイナリディレクトリではなく、パッケージディレクトリにインストールされてしまうという不具合が解消されます。
コミット
commit d2599b431e80a3824cc587f8b23e3783fd241f3a
Author: Anthony Martin <ality@pbrane.org>
Date: Mon Jan 30 13:54:22 2012 -0500
go: don't clobber command install paths
This fixes a regression that was made when adding
support for building with gccgo (in d6a14e6fac0c).
External commands (those not from the Go tree) were
being installed to the package directory instead of
the binary directory.
R=golang-dev, rsc, adg, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/5564072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d2599b431e80a3824cc587f8b23e3783fd241f3a
元コミット内容
このコミットは、Go言語のビルドシステムにおいて、gccgo
ツールチェインのサポート導入によって引き起こされた回帰バグを修正するものです。具体的には、Goのソースツリー外のコマンド(外部コマンド)が、本来インストールされるべきバイナリディレクトリではなく、誤ってパッケージディレクトリにインストールされてしまう問題に対処しています。この問題は、コミットd6a14e6fac0c
でgccgo
サポートが追加された際に発生しました。
変更の背景
この変更の背景には、Go言語のビルドシステムが複数のコンパイラツールチェイン(標準のgc
とgccgo
)をサポートするようになった経緯があります。
Go言語の初期のビルドシステムは、主にGoチームが開発した公式コンパイラであるgc
(Go Compiler)を前提としていました。しかし、Go言語の普及に伴い、GCC(GNU Compiler Collection)をバックエンドとして利用するgccgo
という代替コンパイラが開発されました。gccgo
は、既存のGCCインフラストラクチャを活用することで、Goプログラムをより多くのプラットフォームでコンパイルできるようにすることを目指していました。
コミットメッセージに記載されているd6a14e6fac0c
は、Goのビルドツール(go
コマンド)にgccgo
を統合するための変更でした。この統合自体は、Go言語の柔軟性と移植性を高める上で重要なステップでしたが、その過程で予期せぬ副作用が発生しました。
具体的には、gccgo
のビルドパスの処理方法が、Goツリー外のコマンド(例えば、go get
でインストールされるサードパーティ製のツールなど)のインストールパスの決定ロジックに影響を与えてしまいました。結果として、これらの外部コマンドが、ユーザーの$GOBIN
(バイナリ実行ファイルが置かれるべき場所)ではなく、$GOPATH/pkg
(コンパイル済みパッケージが置かれる場所)のようなパッケージディレクトリに誤って配置されるという回帰バグが発生しました。
この問題は、ユーザーがgo install
やgo get
を使って外部ツールをインストールした際に、それらのツールが期待されるパスに存在せず、実行できないという形で現れました。このコミットは、この回帰バグを修正し、gccgo
を使用している場合でも、外部コマンドが正しいバイナリディレクトリにインストールされるようにすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドシステムと関連する概念についての知識が必要です。
-
Go言語のワークスペースと環境変数:
GOPATH
: Go 1.11以前のGoプロジェクトのワークスペースのルートディレクトリを指定する環境変数です。Goのソースコード、パッケージ、バイナリがこのディレクトリ構造内に配置されます。src
(ソースコード)、pkg
(コンパイル済みパッケージ)、bin
(コンパイル済みバイナリ)の3つのサブディレクトリが慣例的に存在します。GOBIN
: コンパイルされたGoプログラムの実行可能ファイルがインストールされるディレクトリを指定する環境変数です。GOBIN
が設定されていない場合、実行可能ファイルはGOPATH/bin
にインストールされます。go install
: ソースコードをコンパイルし、その結果生成されたパッケージ(.a
ファイルなど)をGOPATH/pkg
に、実行可能ファイル(コマンド)をGOPATH/bin
またはGOBIN
にインストールするGoコマンドです。go get
: リモートリポジトリからGoパッケージをダウンロードし、ビルドしてインストールするGoコマンドです。
-
Goのビルドツール (
go
コマンド):- Go言語のビルド、テスト、パッケージ管理などを行うための主要なコマンドラインツールです。
src/cmd/go
ディレクトリにそのソースコードがあります。 - このツールは、Goのソースコードをコンパイルし、実行可能ファイルを生成する際に、どのディレクトリに何を配置するかを決定するロジックを含んでいます。
- Go言語のビルド、テスト、パッケージ管理などを行うための主要なコマンドラインツールです。
-
Goのパッケージとコマンド:
- パッケージ: Goのコードの基本的な構成単位です。他のパッケージからインポートして利用されます。コンパイルされると、通常は
.a
(アーカイブ)ファイルとしてGOPATH/pkg
に保存されます。 - コマンド:
main
パッケージを持ち、main
関数を含むGoプログラムは、実行可能なコマンドとしてビルドされます。これらは通常、GOPATH/bin
またはGOBIN
にインストールされます。
- パッケージ: Goのコードの基本的な構成単位です。他のパッケージからインポートして利用されます。コンパイルされると、通常は
-
Goのツールチェイン:
- Goのソースコードをコンパイルし、実行可能ファイルを生成するための一連のツール(コンパイラ、アセンブラ、リンカなど)の集合です。
gc
(Go Compiler): Goチームが開発した標準のGoコンパイラです。Goの公式リリースに同梱されています。gccgo
: GCCのフロントエンドとして実装されたGoコンパイラです。GCCの最適化やバックエンドのサポートを利用できます。gccgo
は、gc
とは異なる内部的なパス構造や命名規則を持つことがあります。
-
build.Context
とbuild.Tree
:- Goのビルドツール内部で使用される構造体で、ビルド環境のコンテキスト(OS、アーキテクチャ、
GOPATH
など)や、ビルドツリー(ソースディレクトリ、パッケージディレクトリ、バイナリディレクトリなど)の情報を保持します。 t.PkgDir()
:build.Tree
オブジェクトのメソッドで、コンパイル済みパッケージが置かれるべきディレクトリのパスを返します。
- Goのビルドツール内部で使用される構造体で、ビルド環境のコンテキスト(OS、アーキテクチャ、
-
p.target
:- Goのビルドツール内部で、ビルド対象の最終的な出力ファイル(パッケージのアーカイブファイルや実行可能ファイル)のパスを保持するフィールドです。このフィールドの値が、最終的なファイルの配置場所を決定します。
-
filepath.Join
とfilepath.FromSlash
:- Goの標準ライブラリ
path/filepath
パッケージの関数です。 filepath.Join
: 複数のパス要素を結合して、OS固有のパス区切り文字(Windowsでは\
、Unix系では/
)を使用して正しいパスを生成します。filepath.FromSlash
: スラッシュ区切りのパスを、現在のOSのパス区切り文字に変換します。
- Goの標準ライブラリ
これらの概念を理解することで、コミットがなぜ必要とされ、どのように問題を解決しているのかが明確になります。特に、gccgo
が導入されたことで、従来のgc
とは異なるパスの扱いが必要になった点が重要です。
技術的詳細
このコミットが修正している問題は、gccgo
ツールチェインが導入された際に、Goのビルドツール(go
コマンド)が外部コマンドのインストールパスを誤って計算してしまうという回帰バグです。
問題の核心は、src/cmd/go/pkg.go
内のscanPackage
関数にあります。この関数は、Goパッケージのビルドターゲットパス(p.target
)を決定する役割を担っています。
元のコードでは、p.target
の初期設定は以下のようになっていました。
// (省略)
} else {
p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
}
// For gccgo, rewrite p.target with the expected library name. We won't do
// that for the standard library for the moment.
if !p.Standard {
dir := t.PkgDir()
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
}
このロジックの問題点は以下の通りです。
-
初期の
p.target
設定:p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
の行は、すべてのパッケージ(コマンドを含む)に対して、デフォルトでパッケージディレクトリ(t.PkgDir()
、通常は$GOPATH/pkg
)をベースとしたパスを設定していました。これは、コマンド(実行可能ファイル)が最終的に$GOBIN
または$GOPATH/bin
にインストールされるべきであるというGoの慣習と矛盾します。本来、コマンドのp.target
はバイナリディレクトリを指すべきです。 -
gccgo
特有のパス書き換えロジック: その後のif !p.Standard
ブロック内で、gccgo
ツールチェインが使用されている場合にp.target
を書き換えるロジックがありました。dir := t.PkgDir()
: ここで、パッケージディレクトリをベースとしています。dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
:gccgo
は、標準のGoツールチェインとは異なるディレクトリ構造を使用することがあるため、gccgo
固有のパッケージディレクトリパスを構築していました。例えば、$GOPATH/pkg/linux_amd64
が$GOPATH/pkg/gccgo/linux_amd64
のようになることを意図していたと考えられます。p.target = buildToolchain.pkgpath(dir, p)
: 最終的に、gccgo
ツールチェインのpkgpath
メソッドを使って、gccgo
の命名規則に沿ったパッケージパスを生成し、p.target
に設定していました。
この組み合わせにより、特にGoツリー外のコマンド(p.Standard
がfalse
のケース)の場合に問題が発生しました。コマンドであっても、初期段階でp.target
がパッケージディレクトリを指すように設定され、その後のgccgo
固有のパス書き換えもパッケージディレクトリをベースに行われるため、最終的にp.target
がバイナリディレクトリではなく、gccgo
のパッケージディレクトリ(例: $GOPATH/pkg/gccgo/...
)を指してしまい、実行可能ファイルが誤った場所にインストールされる結果となりました。
修正内容:
このコミットは、p.target
の初期設定ロジックを修正し、gccgo
のパス処理をより適切に統合することで、この問題を解決しています。
修正後のコードは以下のようになります。
// (省略)
} else {
// 変更点1: p.targetの初期設定が削除された
// p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
}
// 変更点2: dirの初期化がif/elseブロックの外に移動し、常にt.PkgDir()から始まる
dir := t.PkgDir()
// For gccgo, rewrite p.target with the expected library name.
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
// 変更点3: 標準ライブラリに対するgccgoの特別な処理が追加された
// NB. Currently we have gccgo install the standard libraries
// in the "usual" location, where the Go toolchain puts them.
if p.Standard {
if _, ok := buildToolchain.(gccgoToolchain); ok {
p.target = goToolchain{}.pkgpath(dir, p)
}
}
主要な変更点は以下の通りです。
p.target
の初期設定の削除:else
ブロック内のp.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
という行が削除されました。これにより、コマンドの場合に誤ってパッケージディレクトリを指す初期値が設定されることがなくなりました。dir
の初期化とp.target
の計算ロジックの変更:dir := t.PkgDir()
が、if !p.Standard
ブロックの外に移動しました。これにより、すべてのパッケージ(標準ライブラリと外部パッケージの両方)に対して、t.PkgDir()
をベースとしたdir
が初期化されます。- その後の
gccgo
固有のdir
の書き換えロジックはそのまま残っています。 - そして、
p.target = buildToolchain.pkgpath(dir, p)
が、if !p.Standard
の条件なしに実行されるようになりました。これは、buildToolchain.pkgpath
が、現在のツールチェイン(gc
またはgccgo
)に応じて適切なパスを生成することを期待しているためです。gc
ツールチェインの場合、pkgpath
はコマンドに対してはバイナリディレクトリを、パッケージに対してはパッケージディレクトリを返すように実装されているはずです。
- 標準ライブラリに対する
gccgo
の特別な扱い: 新たにif p.Standard
ブロックが追加されました。gccgo
を使用している場合でも、標準ライブラリはGo標準ツールチェイン(gc
)が配置する「通常の場所」にインストールされるように、p.target
をgoToolchain{}.pkgpath(dir, p)
で上書きしています。これは、gccgo
が標準ライブラリを独自のgccgo
固有のパスではなく、gc
と同じパスに配置することで、互換性を保つための措置と考えられます。
この修正により、p.target
の計算が、パッケージの種類(コマンドかライブラリか)と使用しているツールチェイン(gc
かgccgo
か)に応じて、より正確に行われるようになりました。特に、外部コマンドの場合、buildToolchain.pkgpath
が最終的に正しいバイナリディレクトリを指すように動作することで、回帰バグが解消されます。
コアとなるコードの変更箇所
変更はsrc/cmd/go/pkg.go
ファイルに集中しています。
--- a/src/cmd/go/pkg.go
+++ b/src/cmd/go/pkg.go
@@ -279,17 +279,20 @@ func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string
p.target += ".exe"
} else {
- p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
- }
-
- // For gccgo, rewrite p.target with the expected library name. We won't do
- // that for the standard library for the moment.
- if !p.Standard {
dir := t.PkgDir()
+ // For gccgo, rewrite p.target with the expected library name.
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
+
+ // NB. Currently we have gccgo install the standard libraries
+ // in the "usual" location, where the Go toolchain puts them.
+ if p.Standard {
+ if _, ok := buildToolchain.(gccgoToolchain); ok {
+ p.target = goToolchain{}.pkgpath(dir, p)
+ }
+ }
}
var built time.Time
コアとなるコードの解説
変更されたsrc/cmd/go/pkg.go
のscanPackage
関数内のコードブロックを詳細に解説します。
元のコード:
} else {
p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
}
// For gccgo, rewrite p.target with the expected library name. We won't do
// that for the standard library for the moment.
if !p.Standard {
dir := t.PkgDir()
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
}
修正後のコード:
} else {
// 変更点1: p.targetの初期設定が削除された
// p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
// 変更点2: dirの初期化とp.targetの計算ロジックがこのブロック内に移動し、
// if !p.Standard の条件がなくなった
dir := t.PkgDir()
// For gccgo, rewrite p.target with the expected library name.
if _, ok := buildToolchain.(gccgoToolchain); ok {
dir = filepath.Join(filepath.Dir(dir), "gccgo", filepath.Base(dir))
}
p.target = buildToolchain.pkgpath(dir, p)
// 変更点3: 標準ライブラリに対するgccgoの特別な処理が追加された
// NB. Currently we have gccgo install the standard libraries
// in the "usual" location, where the Go toolchain puts them.
if p.Standard {
if _, ok := buildToolchain.(gccgoToolchain); ok {
p.target = goToolchain{}.pkgpath(dir, p)
}
}
}
変更点1: p.target
の初期設定の削除
- 削除された行:
- p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a")
- この行は、
p.target
(ビルド出力ファイルのパス)を、デフォルトでパッケージディレクトリ(t.PkgDir()
)とインポートパスを結合した.a
ファイル(アーカイブファイル)として設定していました。 - 問題は、これがコマンド(実行可能ファイル)に対しても適用されてしまう点でした。コマンドは
.a
ファイルではなく、実行可能ファイルとしてバイナリディレクトリにインストールされるべきです。 - この行を削除することで、
p.target
の初期値が誤ってパッケージディレクトリを指すことを防ぎ、後続のロジックで適切なパスが設定されるようにします。
変更点2: dir
の初期化とp.target
の計算ロジックの移動と変更
- 元のコードでは、
if !p.Standard
(標準ライブラリではない場合)という条件ブロックの中にdir
の初期化とp.target
の計算ロジックがありました。 - 修正後、このロジックは
else
ブロック(p.target
が.exe
で終わらない場合、つまりコマンドではないパッケージの場合)の直下に移動し、if !p.Standard
の条件がなくなりました。 dir := t.PkgDir()
: まず、現在のビルドツリーのパッケージディレクトリを取得します。これは、Goのパッケージが通常配置される場所のベースとなります。if _, ok := buildToolchain.(gccgoToolchain); ok { ... }
: ここで、現在使用されているビルドツールチェインがgccgoToolchain
であるかどうかをチェックします。- もし
gccgo
であれば、dir
のパスをgccgo
固有の構造に調整します。具体的には、filepath.Dir(dir)
で親ディレクトリを取得し、その中に"gccgo"
というサブディレクトリを追加し、元のdir
のベース名(例:linux_amd64
)を結合します。これにより、$GOPATH/pkg/gccgo/linux_amd64
のようなパスが生成され、gccgo
がパッケージを配置する場所を正確に反映します。
- もし
p.target = buildToolchain.pkgpath(dir, p)
: 最後に、現在のbuildToolchain
(gc
またはgccgo
)のpkgpath
メソッドを呼び出して、最終的なp.target
を計算します。- この
pkgpath
メソッドは、ツールチェインの種類、計算されたdir
、およびパッケージ情報p
に基づいて、適切な出力パス(パッケージの場合は.a
ファイル、コマンドの場合は実行可能ファイル)を返します。 - この変更により、標準ライブラリではないパッケージ(外部コマンドを含む)に対しても、ツールチェインに応じた正しい
p.target
が設定されるようになります。
- この
変更点3: 標準ライブラリに対するgccgo
の特別な処理の追加
- 追加されたブロック:
// NB. Currently we have gccgo install the standard libraries // in the "usual" location, where the Go toolchain puts them. if p.Standard { if _, ok := buildToolchain.(gccgoToolchain); ok { p.target = goToolchain{}.pkgpath(dir, p) } }
- このブロックは、現在処理しているパッケージがGoの標準ライブラリ(
p.Standard
がtrue
)であり、かつgccgo
ツールチェインを使用している場合にのみ実行されます。 p.target = goToolchain{}.pkgpath(dir, p)
: ここで重要なのは、goToolchain{}
というGo標準ツールチェインのインスタンスのpkgpath
メソッドを呼び出している点です。- これは、
gccgo
を使用している場合でも、Goの標準ライブラリは、Go標準ツールチェイン(gc
)が通常配置する場所(例:$GOROOT/pkg/linux_amd64
)にインストールされるように、p.target
を上書きすることを意味します。 - この措置は、
gccgo
とgc
の間で標準ライブラリのパスに互換性を持たせるためのもので、ユーザーがどちらのツールチェインを使っても標準ライブラリが同じ場所から参照できるようにするためのものです。
これらの変更により、scanPackage
関数は、Goのパッケージとコマンドのビルドターゲットパスを、使用されているツールチェイン(gc
またはgccgo
)とパッケージの種類(標準ライブラリ、外部パッケージ、コマンド)に応じて、より正確かつ一貫性のある方法で決定できるようになりました。これにより、外部コマンドが誤ったディレクトリにインストールされるという回帰バグが修正されました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go Modules (Go 1.11以降のパッケージ管理): https://go.dev/blog/using-go-modules (このコミットの時点ではGo Modulesは存在しませんが、現代のGo開発の文脈で関連する情報です)
- GCCGOに関する情報: https://go.dev/doc/install/gccgo
参考にした情報源リンク
- コミットのChange-Id:
https://golang.org/cl/5564072
(GoのGerritコードレビューシステムへのリンク) - Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語のビルドシステムに関する一般的な情報 (Goの公式ドキュメントやブログ記事など)
d6a14e6fac0c
コミットの検索結果 (このコミットが引き起こした回帰の元となったコミット):- https://github.com/golang/go/commit/d6a14e6fac0c
- このコミットは "go: add gccgo support" というメッセージで、
gccgo
のサポートを追加したことが確認できます。